AHB Matrix项目理解--框架理解与关键代码
框架理解
验证内容:3master连接3slave的AHB Matrix
- matrix上的master口在真实情况下会有一个slave外设。在AHB VIP的验证环境中,没有slave外设的rtl,因此必须用验证环境提供这个slave的角色,因此我在dw ahb matrix右边增加三个ahbram作为3个slave
-
在验证环境中使用三个ahb master agent作为激励,发送给dw ahb matrix后,DUT输出信号通过接口到达输出端,此时可以通过读取接口中的信号来验证DUT的正确性
项目关键代码理解
项目关键代码1---并行访问测试的思考
设计目前做不到支持并行的,因为你必须要让同一组adapt predict、寄存器,通过一个sequencer,write、read,然后response过来以后再做下一个。
不能在同一个映射中切换,理论上虽然是同一个映射,你不能既通过sequencer,也通过master进行访问。因此无法同时进行并行访问。
如果要做并行的访问的话,这个测试就不要用寄存器模型了,直接利用地址去做访问,而做地址访问实现并行的访问时,可以用那么一一个、两个、三个特定的地址。此时测试的这个目标不是寄存器模型的集成,而是为了测试我三个寄存器的接口能否接受。
项目关键代码2---driver和monitor的时序更新
【更新时序说明】
为使用寄存器内建测试序列来测试寄存器模型,必须保证driver和monitor的同步(即driver不能比monitor的时序块),而原有vip中的时序是driver在第二拍(数据周期)的下降沿得到数据,monitor在第三拍(数据周期的下一拍)的上升沿得到数据(详见《AHB-RAM学习记录05-06》),这显然是不符合寄存器内建测试序列使用要求的。
【更新方向】
因此使得driver和monitor同步成为该版本的重点。这里有两种方法,1.覆盖原有VIP的monitor,覆盖其原有接受数据的时序 2.覆盖寄存器内建测试序列,覆盖直接延时使其等待monitor更新reg model之后再继续发送下一个数据
本文章着重介绍方法1
【采样逻辑】
【VIP—lvc driver时序】
如driver的write动作为例
virtual task do_init_write(REQ t);
wait_for_bus_grant(); //第一个周期的上升沿
vif.cb_mst.htrans <= NSEQ;
vif.cb_mst.haddr <= t.addr;
vif.cb_mst.hburst <= t.burst_type;
vif.cb_mst.hsize <= t.burst_size;
vif.cb_mst.hwrite <= 1'b1;
@(vif.cb_mst); //第一个周期的下降沿
if(t.burst_type == SINGLE) begin
_do_drive_idle();
end
vif.cb_mst.hwdata <= t.data[0];
// check ready with delay in current cycle
forever begin
@(negedge vif.hclk); //第二个周期的下降沿
if(vif.hready === 1'b1) begin
break;
end
else
@(vif.cb_mst);
end
// update current trans status
t.trans_type = NSEQ;
t.current_data_beat_num = 0; // start beat from 0 to make consistence with data array index
t.all_beat_response[t.current_data_beat_num] = response_type_enum'(vif.hresp);
endtask
virtual task wait_for_bus_grant();
@(vif.cb_mst iff vif.cb_mst.hgrant === 1'b1);
endtask
```
【覆盖VIP—user monitor时序】
首先删除所有clocking,因为时钟块采样一定是比直接采样慢一点。
`ifndef user_lvc_ahb_master_monitor
`define user_lvc_ahb_master_monitor
class user_lvc_ahb_master_monitor extends lvc_ahb_monitor;//为什么继承于LVC_AHB_master_MONITOR?见obsidian
`uvm_component_utils(user_lvc_ahb_master_monitor)
function new(string name = "user_lvc_ahb_master_monitor", uvm_component parent = null);
super.new(name, parent);
endfunction
task collect_transfer(output lvc_ahb_transaction t);
// collect transfer from interface
t = lvc_ahb_transaction::type_id::create("t");
wait(vif.htrans == NSEQ); #1ps; //不用clocking,就在当前一拍采样。第一拍地址周期的下降沿前面一点点
t.trans_type = trans_type_enum'(vif.htrans);
t.xact_type = xact_type_enum'(vif.hwrite);
t.burst_type = burst_type_enum'(vif.hburst);
t.burst_size = burst_size_enum'(vif.hsize);
t.addr = vif.haddr;
forever begin
monitor_valid_data(t);
if(t.trans_type == IDLE)
break;
end
t.response_type = t.all_beat_response[t.current_data_beat_num];
endtask
task monitor_valid_data(lvc_ahb_transaction t);
@(vif.cb_mon); #1ps; //第二拍数据周期的上升沿往后一点点
wait(vif.hready === 1); #1ps; //第二拍的下降沿之前
//在后面添加1ps,可以保证检测到的数据避免delta cycle的问题
//当前这一拍去检查hready信号,此时monitor能跟上driver
//因为driver是第二拍的下降沿附近,现在的monitor检查的也是第二拍的下降沿附近,细致来说,monitor获取数据在driver之前
//monitor得到数据就是要在driver返回rsp之前啊,不然rgm的内建序列测试会报错的 //这句话重中之重
//monitor要先拿到数据返回给寄存器模型更新镜像值 //这句话也是重中之重
t.increase_data();
t.current_data_beat_num = t.data.size() - 1;
// get draft data from bus
t.data[t.current_data_beat_num] = t.xact_type == WRITE ? vif.hwdata : vif.hrdata;
// NOTE:: alinged not to extract the valid data after shifted
// extract_vali_data(t.data[t.current_data_beat_num], t.addr, t.burst_size);
t.all_beat_response[t.current_data_beat_num] = response_type_enum'(vif.hresp);
t.trans_type = trans_type_enum'(vif.htrans);
endtask
endclass
`endif // LVC_AHB_MONITOR_SV
项目关键代码3---寄存器前门访问、后门访问
添加前门后门访问的相对路径
//为什么取名“ipl1”?
//VCS的设计层次rkv_ahbmtx_tb---dut(rkv_DW_ahb)---
//U_arb(rkv_DW_ahb_arb)---U_arbif(rkv_DW_ahb_arbif)
//找到reg类型的变量,ipl1对应AHB_PL1,以此类推
this.AHB_PL1.add_hdl_path_slice("ipl1",0,32);
this.AHB_PL2.add_hdl_path_slice("ipl2",0,32);
this.AHB_PL3.add_hdl_path_slice("ipl3",0,32);
this.AHB_EBTCOUNT.add_hdl_path_slice("ebtcount",0,32);
this.AHB_EBT_EN.add_hdl_path_slice("ebten",0,32);
this.AHB_EBT.add_hdl_path_slice("ebt",0,32);
this.AHB_DFLT_MASTER.add_hdl_path_slice("idef_mst",0,32);
this.AHB_VERSION_ID.add_hdl_path_slice("iahb_version_id",0,32);
//添加前门后门访问的相对路径
`ifndef RKV_AHBMTX_REG_ACCESS_VIRT_SEQ_SV
`define RKV_AHBMTX_REG_ACCESS_VIRT_SEQ_SV
class rkv_ahbmtx_reg_access_virt_seq extends rkv_ahbmtx_base_virtual_sequence;
`uvm_object_utils(rkv_ahbmtx_reg_access_virt_seq)
function new (string name = "rkv_ahbmtx_reg_access_virt_seq");
super.new(name);
endfunction
virtual task body();
uvm_reg_access_seq reg_access_seq = uvm_reg_access_seq::type_id::create("reg_access_seq");
super.body();
`uvm_info("body", "Entered...", UVM_LOW)
//disable coverage model
cfg.enable_cov = 0;
reg_access_seq.model = rgm;
reg_access_seq.start(null);
foreach (rgm.maps[i]) begin
rgm.ahbmtx.AHB_DFLT_MASTER.write(status,'h3,UVM_FRONTDOOR,rgm.maps[i]);
rgm.ahbmtx.AHB_DFLT_MASTER.mirror(status,UVM_CHECK,UVM_BACKDOOR, uvm_reg_map::backdoor());
//观察源代码知uvm_reg_map::backdoor()不用也没事
// rgm.ahbmtx.AHB_DFLT_MASTER.mirror(status,UVM_CHECK,UVM_BACKDOOR);
rgm.ahbmtx.AHB_DFLT_MASTER.write(status,'h1,UVM_BACKDOOR,rgm.maps[i]);
rgm.ahbmtx.AHB_DFLT_MASTER.mirror(status,UVM_CHECK,UVM_FRONTDOOR,rgm.maps[i]);
end
//上述代码都是根据DVT里面uvm_reg_access_seq的(140-164行)源代码抄来修改的
`uvm_info("body", "Exiting...", UVM_LOW)
endtask
endclass
`endif
项目关键代码4---细致理解:hbusreq_masked = vif.hbusreq & (~(16'h1 << cur_mid));
//来源于rkv_ahbmtx_cov.sv
...
function void write(lvc_ahb_transaction tr);
logic[15:0] hbusreq_masked;
super.write(tr);
if(cfg.enable_cov)begin
hbusreq_masked = vif.hbusreq & (~(16'h1 << cur_mid));
//代码的正确性:
//左移1位后取反
//如cur_mid = 1时,
case(cur_acc)
REGACC:begin:regacc_cov_collect
case(cur_reg.get_name())
"AHB_PL1":cgs_reg_prioX[0].sample(tr.data[0][3:0]);
"AHB_PL2":cgs_reg_prioX[1].sample(tr.data[0][3:0]);
"AHB_PL3":cgs_reg_prioX[2].sample(tr.data[0][3:0]);
endcase
if($countones(hbusreq_masked) == 0) //满足条件,single slave(单个) 在访问
cgs_single_slave_access_reg[cur_mid - 1].sample(1);
if($countones(hbusreq_masked) == cfg.mst_num - 1)
cgs_all_slaves_access_reg[cur_mid - 1].sample(1);
end
MEMACC:begin
if($countones(hbusreq_masked) == 0)
cgs_single_slave_access_mem[cur_mid - 1].sample(1);
if($countones(hbusreq_masked) == cfg.mst_num - 1)
cgs_all_slaves_access_mem[cur_mid - 1].sample(1);
end
ILLACC:begin
cgs_illegal_addr_access[cur_mid - 1].sample(1);
end
endcase
end
endfunction
...
//
hbusreq_masked = vif.hbusreq & (~(16'h1 << cur_mid));
if($countones(hbusreq_masked) == 0) //满足条件,single slave(单个) 在访问
cgs_single_slave_access_reg[cur_mid - 1].sample(1);
目前设计只能一个master占用总线,可以取cur_mid = 1 (~(16'h1 << cur_mid)) (~(16'h1 << 1)) = 16'h FFFD = 1111 1111 1111 1101 若当前master1向总线发起请求 vif.hbusreq = 16'h 0000 0000 0000 0010 此时hbusreq_masked = 0 符合条件,single slave覆盖组采样
若此时当前master2向总线发起请求
访问时(只有在访问了vif.xxx才会变。请求和实际访问是不同的两拍) vif.hbusreq = 16'h 0000 0000 0000 0100 但是mst2请求访问的时候vif.xxx不会变,因为使用lvc_ahb_master访问DUT的slave,rkv_ahbmtx_if中hbusreq是DUT发送出来的信号(见cb_mst方向)
下一拍mst2实际占用总线了,vif.xxx采会跳到002,然后占用总线的mst发送数据transaction,而cur_mst_id是monitor检测得到的数据。
因此此时:cur_mid = 2 vif.hbusreq = 16'h 0000 0000 0000 0100 (~(16'h1 << cur_mid)) (~(16'h1 << 1)) = 16'h FFFB = 1111 1111 1111 1011
此时hbusreq_masked = 0 符合条件,single slave覆盖组采样
if($countones(hbusreq_masked) == cfg.mst_num - 1)
cgs_all_slaves_access_reg[cur_mid - 1].sample(1);
例如:当前cur_mst_id为1,master1占用了总线,此时,发起总线请求的可以用master1 2 3,但是vif.hbusreq中最多只有master2、3,因为当前的master1占用了总线,下一拍的时候vif.hbusreq中才能出现master1
因此此时:cur_mid = 1 vif.hbusreq = 16'h 0000 0000 0000 1100 (~(16'h1 << cur_mid)) (~(16'h1 << 1)) = 16'h FFFB = 1111 1111 1111 1101
1100 & 1101
$countones(hbusreq_masked) **=** $countones(0000 0000 0000 1100) = 2 =cfg.mst_num -1 = 3-1
注意:这个写法值得借鉴
作用就是通过总线上的hbusreq信号和cur_mid,来确定单slave 或者多slave的覆盖组采样
项目关键代码5---硬件复位的覆盖率收集
//cov.sv中
...
forever begin :listen_to_reset_event
realtime last_tr_time_wd;
realtime reset_assert_time;//开始reset的时刻
realtime next_tr_time_wd;
@(negedge vif.rstn iff cur_tr_time != 0ns);//排除第一次的reset
//check if last trans and next trans timing window
reset_assert_time = $time;
last_tr_time_wd = reset_assert_time - cur_tr_time;
//cur_tr_time为进入write的时刻,继承于subscriber里面的变量
fork:wait_next_trans_thread
ahbmtx_regacc_fd_e.wait_trigger();//抄I2C的cgm 370行里面的写法
ahbmtx_memacc_fd_e.wait_trigger();
join_any
disable wait_next_trans_thread;
next_tr_time_wd = $time - reset_assert_time;//结束reset,然后开启下一trans的时刻
if(last_tr_time_wd < 100ns && next_tr_time_wd < 100ns)
cg_reg_hard_reset.sample(1);
end
...
注意:这里已经默认假设一个trans发送不超过100ns
项目关键代码6---工厂机制覆盖 set_type_override_by_type
set_type_override_by_type (lvc_ahb_master_monitor::get_type(),user_lvc_ahb_master_monitor::get_type());
set_type_override_by_type (uvm_reg_bit_bash_seq::get_type(),user_uvm_reg_bit_bash_seq::get_type());
用user_lvc_ahb_master_monitor 覆盖lvc_ahb_master_monitor;
用user_uvm_reg_bit_bash_seq 覆盖uvm_reg_bit_bash_seq;
项目关键代码7---user_lvc_ahb_master_monitor要继承于lvc_ahb_master_monitor?
lvc_ahb_master_monitor是继承于lvc_ahb_monitor,那么user_lvc_ahb_master_monitor能否和lvc_ahb_master_monitor同样继承于lvc_ahb_monitor呢?
答案是不能,这主要原因应该是执行顺序有关。代码中工厂覆盖在lvc_ahb_master_monitor创建完后对其类型替换为user_lvc_ahb_master_monitor。如果两者同级(都继承于lvc_ahb_monitor),先覆盖后创建lvc_ahb_master_monitor可能就报错。
因此user_lvc_ahb_master_monitor继承于lvc_ahb_master_monitor。
项目关键代码8---maps[]的理解
map 主要用于寻址,将 uvm_reg 变量与地址关联起来。map 完成后,操作 uvm_reg 时,就不用考虑寻址了。
reg_block 中一般至少有 1 个 map。reg_block 可以有子 reg_block。reg_block 的 map 通过 add_submap 把子 reg_block 的 map 关联起来。
AHB matrix配置三个master 三个slave,在使用寄存器的测试中,三个agent 、master对应三个predictor,分别有三个对应的maps映射到寄存器模型,然后有三个挂载到三个mst_sqr,返回给driver item_done。
每一个maps[i]对应的都是完整的一套寄存器,而不是一个。
比如maps[0]里面已经包含了:
项目关键代码9---logic [15:0] hbusreq
master有16个,完全可以由4位来表示其编号15:0
用16位的是因为添加这个信号的目的本身就是为了方便观察多个master占用总线的情况,如hbusreq[0] = 1;hbusreq[12] = 1; 可以很清楚的观察,若只用4位表示,需要经过处理后才能清楚观察(如使用enum)
hgrant同理,直接每个master的信号对应一位数值即可
项目关键代码10---(重点)为什么t3p6的cov达不到100%?
t3p6的测试覆盖率采样条件是三个master同时发送请求,而在AHB matrix设计在某一个时刻只有一个master占用总线,而三个master是有优先级的,如master0的优先级最小,master2的优先级最大。
此时就会出现一个情况,master1和2互相争夺总线,master0只有请求总线的份,当master2发送数据结束后,才有机会给master0占用。此时不满足覆盖率收集的条件,因为当master0占用总线的时候,master2压根不会去请求访问总线,最多就只有master0和1请求访问。
解决方案:在t3p6的seq里面增加寄存器的优先级配置
项目关键代码11---(重点)为什么对mem访问,要对reg做配置呢?(验证环境中寄存器的理解)
给寄存器做配置和访问mem的seq没有关系,都属于正常操作
注意:给寄存器做配置和有没有寄存器模型是没有关系的
mcdf工程中也是先配置功能,再传输数据
dut内部的控制寄存器是用来配置功能的,现有的默认功能不符合测试要求,当然就需要通过寄存器配置了。直接读写和用寄存器读写是一样的,只是rgm简化了一些操作,加入了一些新的特性。
代码中rgm是一个reg_block,包含了句柄类型为reg_block的ahbmtx。因此rgm不单单包含寄存器模型,还有寄存器的一些配置。而ahbmtx句柄里面是包含了要用到的各个寄存器。
项目关键代码12---pool和event在项目中的作用
在subscriber的write函数里面使用event触发事件,然后在cov的write函数里面使用ahbmtx_regacc_fd_e.wait_trigger_data(tmp);
ahbmtx_regacc_fd_e.wait_trigger();
ahbmtx_memacc_fd_e.wait_trigger();
来实现对要收集覆盖组的采样