文章目录
- 前言
- 一、scoreboard接收数据的方式和比较行为的策略选择
- 1.接受数据的方式
- 1. 首先是数据从哪儿来?
- 2.比较数据的方式
- 1.方案一
- 2.方案二
- 3. 方案的选择
- 二、scoreboard的实现
- 1.代码
- 三、tip
- 1.编辑断点
- 2. return
- 3.有关函数的返回值:函数内部隐式声明的变量
前言
- 来源路科验证
- 本节记录
scoreboard
接收数据方式和比较行为的策略选择以及如何实现scoreboard
- 三星级是在
sequence
里面做的数据比较,四星级在scoreboard
里面;
一、scoreboard接收数据的方式和比较行为的策略选择
1.接受数据的方式
1. 首先是数据从哪儿来?
1. **三星级**:将`sing_write`和`single_read`嵌套,对同一地址先写后读,然后比较写入的数据和读回的数据是否相同![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/aaaddd6880ef4e0e98c474c61da98562.png)2. **四星级**:`master_agent` 和`slave_agent` 都有`monitor`可以检测数据,这样的话可以拿到输入端的数据也可以拿到输出端的数据,做数据的比较就可以了;
2.比较数据的方式
1.方案一
- 将
master monitor
检测到的数据全部更新存放在scb
中的一个memory
里,同理slave monitor
也如此(即记录的matrix
的输出数据和输入数据),之后在scb
中做一个数据比较 - 在做数据比较时,不仅仅要考虑数据的个数,也要考虑数据的内容,还要考虑地址是否相同;首先保证个数的相同,其次考虑内容的相同;
- 这种方案是将数据通过关联数组记录下来
2.方案二
- 在
slave agent
里面都有一个 类svt_mem::ahb_slave_mem:
从master vip
输出的数据经过matrix
后都流向了三个mem
里面,(每一个slave agent
里面都有一个mem
,那么就有三个mem
)可以通过read
函数拿到这mem
里面的数据 master
写的数据可以记录下来,比如通过一个关联数据;slave
有三个mem
,也可以记录数据mem
记录三块还是一块?如果把整个系统按照地址寻址的话,往一个固定的地址写数据,不管是哪一个master
写数据写到一定的addr_range
里,最终都会路由到一个特定的slave mem
里面;外部的三个slave agent
是独立的,他们映射的地址范围是不一样的没有重叠的;所以可以这样理解,看起来是三块slave agent
,三块独立的mem
,实际上里面做成一块mem
是方便作比较的(不需要三块来回做切换),在这种情况下,master
检测到的数据就可以放在一个关联数据里面- 这种方案
master monitor
将数据写在关联数组里面,slave
的数据存放在slave agent
里面的mem
里
3. 方案的选择
- 如果
matrix
这个模块级的验证环境走到子系统级别,都利用monitor
的方案是完全可以复用的,因为他不依赖于vip slave agent
里面的mem
;后者在模块级别以上是完全无法复用的 - 什么时候做数据的检查?是在仿真过程中还是结束以后?在仿真的结束以后(
check_phase
中检查),如果仿真过程中在scb
中做检查的话,光依靠master
那边的数据做检查时间上不一定来得及,因为master
那边的数据要穿过matrix
到达slave
那边,尤其像burst
这种情况; - 最好的方式是两端都做检查:既要检查
master
关联数组 里面的地址和数据;同时也检查slave
的关联数据里面的地址和数据;即检查数量也检查地址和数据的内容; - 总的来说:既要考虑个数,也要考虑内容,对于第二种方案是没法计算个数的,就要单独搞一个计数器;第一种方案比较会更全面,且便于复用
二、scoreboard的实现
1.代码
- 首先建立
scb
的框架,然后在环境中例化,连接
`ifndef RKV_AHBMTX_SCOREBOARD_SV
`define RKV_AHBMTX_SCOREBOARD_SV
//代码中的注释和预处理指令(如ifndef和endif)用于条件编译,确保代码块只被编译一次
class rkv_ahbmtx_scoreboard extends rkv_ahbmtx_subscriber;`uvm_component_utils(rkv_ahbmtx_scoreboard)function new(string name="rkv_ahbmtx_scoreboard", uvm_component parent);super.new(name, parent);endfunctionendclass`endif // RKV_AHBMTX_SCOREBOARD_SV
连接:这里连接到了一个analysis port
上,那么如何区分传递过来的数据来自master还是slave?
-----------------------------env::connect_phase----------------------------
foreach(ahb_mst[i]) beginahb_mst[i].monitor.item_observed_port.connect(cov.ahb_trans_observed_imp);
endforeach(ahb_mst[i]) beginahb_mst[i].monitor.item_observed_port.connect(scb.ahb_trans_observed_imp);
endforeach(ahb_slv[i]) beginahb_slv[i].monitor.item_observed_port.connect(scb.ahb_trans_observed_imp);
end
注意:这里也要将cfg
在env
中传递到scb
中,在subscriber
中已经有get
了cfg
。scb
继承subscriber
,所以也会继承get cfg
- 代码书写
创建两个关联数组,用于存放slave monitor
和master monitor
传递过来的数据
typedef bit[3:0] addr_t;//typedet 必不可好;少了addr_t就是变量,而非数据类型bit[31:0] exp_mem[addr_t];bit[31:0] act_mem[addr_t]; //关联数组里的索引类型必须是有效的数据类型
在定义关联数组的位宽时,可以将其设置为参数,如果以后的位宽发送变化的话
比较逻辑:
先检查两边的地址是否一样,在一样的基础上检查数据是否一样
function void check_phase(uvm_phase phase); //在check_phase中比较保证数据全部传输完毕super.check_phase(phase);if (cfg.enable_scb) begin //检查的开关if (!check_mem_addr()) begin //注意check_mem_addr需要返回值`uvm_error("CHK_MEM_ADDR", "check addr is failure!!!")end else begin`uvm_info("cHK_PHASE", "check addr is success!!!", UVM_LOW)if (!check_mem_data()) begin`uvm_error("cHK_PHASE", "check data is failure!!!")end else begin`uvm_info("cHK_PHASE", "check data is success!!!", UVM_LOW)endendend
endfunction
检查地址是否一样,先检查地址的数量,然后检查地址的内容
function bit check_mem_addr(); //有返回值int exp_mem_size = exp_mem.size(); //sizeint act_mem_size = act_mem.size();if (exp_mem_size != act_mem_size) begin`uvm_error("CHK_MEM_ADDR", $sformatf("exp_addr_size[%0d] != act_addr_size[%0d]", exp_mem_size, act_mem_size))cfg.scb_check_error++;return 0; // Indicate failureend else begin`uvm_info("CHK_MEM_ADDR", $sformatf("exp_addr_size[%0d] == act_addr_size[%0d]", exp_mem_size, act_mem_size), UVM_LOW)endcfg.scb_check_count++;foreach (exp_mem[addr]) beginif (!act_mem.exists(addr)) begin //使用 exists() 方法检查某个KEY是否存在`uvm_error("CHK_MEM_ADDR", $sformatf("act_mem addr[%0x] does not exist", addr))cfg.scb_check_error++;return 0; // Indicate failureend else begin`uvm_info("CHK_MEM_ADDR", $sformatf("act_mem addr[%0x] exists", addr), UVM_LOW)cfg.scb_check_count++;//return 1; //不能放在这里返回,return语句会导致函数立即结束执行,相当于检查了一次endend// If all checks pass, return 1 to indicate successreturn 1;
endfunction
检查数据
function bit check_mem_data();foreach (exp_mem[addr]) beginif (exp_mem[addr] != act_mem[addr]) begin`uvm_error("CHK_MEM_DATA", $sformatf("Failure!!! act_mem[%0x]::[%0d] != exp_mem[%0x]::[%0d]", addr, act_mem[addr], addr, exp_mem[addr]))// return 0;cfg.scb_check_error++;return 0;end else begin`uvm_info("CHK_MEM_DATA", $sformatf("Success!!! act_mem[%0x]::[%0d] == exp_mem[%0x]::[%0d]", addr, act_mem[addr], addr, exp_mem[addr]), UVM_LOW)cfg.scb_check_count++;// return 1;endend// If all checks pass, return 1 to indicate successreturn 1;
endfunction
如何将数据写入到关联数组里面
virtual function void write(svt_ahb transaction tr);super.write(tr);if(cfg.enable_scb && cur_acc == MEMACC) begin //打开scb且访问的是外部的mem时,开始记录数据write_mem_from_trans(tr);endendfunctionfunction void write_mem_from_trans(svt_ahb_transaction tr);svt_ahb_master_transaction mst_trans;svt_ahb_slave_transaction slv_trans;case (tr.burst_type)svt_ahb_transaction::SINGLE: beginif ($cast(mst_trans, tr)) begin //通过将tr句柄转换来查看接收到的数据是哪一边的exp_mem[tr.addr] = tr.data[0];//如果tr句柄并不指向svt_ahb_master_transaction的对象,$cast 会失败end else if ($cast(slv_trans, tr)) beginact_mem[tr.addr] = tr.data[0];endenddefault:`uvm_fatal("TYPEERR", "burst type not supported")endcase
endfunction
通过tr
传过来的数据类型:如果可以转换为slave_transaction
; 那么就是slave monitor
那边传过来;master
同理(write
传过来的父类,为啥是父类?driver
和sequencer
的参数是子类,monitor
的无论如何都是父类
上面的代码另一种结构
virtual function void write(svt_ahb_transaction tr);svt_ahb_master_transaction_mst_tr;svt_ahb_slave_transaction slv_tr;super.write(tr);if (cfg.enable_scb && cur_acc == MEMACC) beginif ($cast(mst_tr, tr)) begin : master_monitor_to_exp_memwrite_mem_from_trans(exp_mem, tr);end else if ($cast(slv_tr, tr)) begin : slave_monitor_to_act_memwrite_mem_from_trans(act_mem, tr);endend
endfunction
//这里将关联数组作为参数传入到函数中了,注意ref的应用
function void write_mem_from_trans(ref bit[31:0] mem[addr_t], svt_ahb_transaction tr);// TODO// NOTE:: SINGLE only supported so farcase(tr.burst_type)svt_ahb_transaction::SINGLE: beginmem[tr.addr] = tr.data[0];enddefault:uvm_fatal("TYPEERR", $sformatf("burst type %s cannot be supported yet", tr.burst_type));endcase
endfunction
全部代码
class rkv_ahbmtx_scoreboard extends rkv_ahbmtx_subscriber;typedef bit [31:0] addr_t;bit [31:0] exp_mem [addr_t];bit [31:0] act_mem [addr_t];`uvm_component_utils(rkv_ahbmtx_scoreboard)function new(string name = "rkv_ahbmtx_scoreboard", uvm_component parent);super.new(name, parent);endfunctionvirtual function void write(svt_ahb_transaction tr);svt_ahb_master_transaction mst_tr;svt_ahb_slave_transaction slv_tr;super.write(tr);if (cfg.enable_scb && cur_acc == MEMACC) beginif ($cast(mst_tr, tr)) begin : master_monitor_to_exp_memwrite_mem_from_trans(exp_mem, tr);endelse if ($cast(slv_tr, tr)) begin : slave_monitor_to_act_memwrite_mem_from_trans(act_mem, tr);endendendfunctionfunction void write_mem_from_trans(ref bit[31:0] mem[addr_t], svt_ahb_transaction tr);// TODO// NOTE:: SINGLE only supported so farcase(tr.burst_type)svt_ahb_transaction::SINGLE: beginmem[tr.addr] = tr.data[0];enddefault: beginuvm_fatal("TYPEERR", $sformatf("burst type %s cannot be supported yet", tr.burst_type));endendcase
endfunctionfunction void check_phase(uvm_phase phase);super.check_phase(phase);if(cfg.enable_scb) beginif(!check_mem_addr()) beginuvm_error("CHK_MEM_ADDR", "check memory address content failed");end else beginuvm_info("CHK_MEM_ADDR", "check memory address content success", UVM_LOW);endif(!check_mem_data()) beginuvm_error("CHK_MEM_DATA", "check memory data content failed");end else beginuvm_info("CHK_MEM_DATA", "check memory data content success", UVM_LOW);endend
endfunctionfunction bit check_mem_addr();int exp_mem_size = exp_mem.size();int act_mem_size = act_mem.size();check_mem_addr = 1; // check address countif (exp_mem.size() != act_mem.size()) beginuvm_error("CHK_ADDR_CNT", $sformatf("check memory address count mismatched! exp_mem[%0d] != act_mem[%0d]", exp_mem_size, act_mem_size));endcfg.scb_check_count++;// check address contentforeach(exp_mem[addr]) beginif (!act_mem.exists(addr)) beginuvm_error("CHECK_ADDR_CTNT", $sformatf("expected memory address['h%x] does not exist in actual memory", addr));check_mem_addr = 0;endcfg.scb_check_count++;end
endfunctionfunction bit check_mem_data();check_mem_data = 1;foreach(exp_mem[addr]) beginif (exp_mem[addr] != act_mem[addr]) beginuvm_error("CHECK_DATA_CTNT", $sformatf("Failed! exp_mem['h%08x]::['h%08x]::['h%08x] != act_mem['h%08x]::['h%08x]", addr, exp_mem[addr], addr, act_mem[addr]));check_mem_data = 0;end else beginuvm_info("CHECK_DATA_CTNT", $sformatf("Success! exp_mem['h%08x]::['h%08x] == act_mem['h%08x]::['h%08x]", addr, exp_mem[addr], addr, act_mem[addr]), UVM_HIGH);endcfg.scb_check_count++;end
endfunction
endclass
三、tip
1.编辑断点
- cov和scb都继承subscriber,设置断点时,对于公共的部分如果只是想在scb的部分设置断点应该如何操作?
设置断点时,编辑断点指定class object
:将蓝色的拖拽到class object
中; - 要有
scb
的的实例拖拽,首先勾选下图的选项,接着run 0
创建实例
- 可通过下图判断是不是正确的设置了断点
2. return
return
的位置很重要;当一个函数遇到return
语句时;(1)
如果函数被声明为返回某种类型的值(即不是void
类型),return
语句后面通常会跟随一个表达式,这个表达式的值会被返回给函数的调用者。(2)return
语句会导致函数立即结束执行,控制流返回到调用该函数的代码处。
3.有关函数的返回值:函数内部隐式声明的变量
函数内部会隐式地声明一个与函数同名的局部变量。这个局部变量用于存储函数的返回值。因此,在函数内部看到check_mem_data
这样的赋值语句时,实际上是在给这个隐式声明的局部变量赋值,而不是在修改函数本身的其他属性。
这里的check_mem_data
既是函数的名称,也是函数内部隐式声明的局部变量的名称。这种设计允许你通过简单地给这个变量赋值来返回结果,而不需要显式地使用return
语句。
function bit check_mem_data();check_mem_data = 1; //将返回值初始化为1foreach(exp_mem[addr]) beginif (exp_mem[addr] != act_mem[addr]) beginuvm_error("CHECK_DATA_CTNT", $sformatf("Failed! exp_mem['h%08x]::['h%08x]::['h%08x] != act_mem['h%08x]::['h%08x]", addr, exp_mem[addr], addr, act_mem[addr]));check_mem_data = 0; end else beginuvm_info("CHECK_DATA_CTNT", $sformatf("Success! exp_mem['h%08x]::['h%08x] == act_mem['h%08x]::['h%08x]", addr, exp_mem[addr], addr, act_mem[addr]), UVM_HIGH);endcfg.scb_check_count++;end
endfunction
在写代码的时候,要搞一些防御性的编程:在各种出问题的地方,打印一些关键的信息,方便调试;比如在get cfg
句柄时的fatal
,判断句柄拿到没有