AHB Matrix项目理解--框架理解与关键代码

发布时间 2023-07-01 20:16:39作者: 傅红雪a

框架理解

验证内容: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();
来实现对要收集覆盖组的采样