实验过程

项目梳理

MCDF功能描述

在这里插入图片描述

从上图的MCDF结构来看主要可以分为如下几个部分:

  • 上行数据的通道从端(Channel Slave),负责接收上行数据,并且存储到其FIFO中。
  • 仲裁器(Arbiter)可以选择从不同的FIFO中读取数据,进而将数据进一步传送至整形器(formatter)。
  • 整形器(Formatter)将数据按照一定的接口时序送出至下行接收端。
  • 控制寄存器(Control Registers)有专用的寄存器读写接口,负责接收命令并且对MCDF的功能做出修改。

接口描述

1、系统信号接口

CLK(0):时钟信号。
RSTN(0):复位信号,低位有效。

2、通道从端接口

CHx_DATA(31:0):通道数据输入。
CHx_VALID(0):通道数据有效标志信号,高位有效。
CHx_READY(0):通道数据接收信号,高位表示接收成功。

3、整形器接口

FMT_CHID(1:0):整形数据包的通道ID号。
FMT_LENGTH(4:0):整形数据包长度信号。
FMT_REQ(0):整形数据包发送请求。
FMT_GRANT(0):整形数据包被允许发送的接受标示。
FMT_DATA(31:0):数据输出端口。
FMT_START(0):数据包起始标示。
FMT_END(0):数据包结束标示。

4、控制寄存器接口

CMD(1:0):寄存器读写命令。
CMD_ADDR(7:0):寄存器地址。
CMD_DATA_IN(31:0):寄存器写入数据。
CMD_DATA_OUT(31:0):寄存器读出数据。

接口时序

通道从端接口时序

在这里插入图片描述

当valid为高时,表示要写入数据。如果该时钟周期ready为高,则表示已经将数据写入;如果该时钟周期ready为低,则需要等到ready为高的时钟周期才可以将数据写入。

整形器接口时序

在这里插入图片描述

  • 整形器发送数据是按照数据包的形式发送的,可以选择数据包的长度有4、8、16和32。整形器必须完整发送某一个通道的数据包后,才可以转而准备发送下一个数据包,在发送数据包期间,fmt_chid和fmt_length应该保持不变,直到数据包发送完毕。
  • 在整形器准备发送数据包时,首先应该将fmt_req置为高,同时等待接收端的fmt_grant。当fmt_grant变为高时,应该在下一个周期将fmt_req置为低。fmt_start也必须在接收到fmt_grant高有效的下一个时钟被置为高,且需要维持一个时钟周期。在fmt_start被置为高有效的同一个周期,数据也开始传送,数据之间不允许有空闲周期,即应该连续发送数据,直到发送完最后一个数据时,fmt_end也应当被置为高并保持一个时钟周期。
  • 相邻的数据包之间应该至少有一个时钟周期的空闲,即fmt_end从高位被拉低以后,至少需要经过一个时钟周期,fmt_req才可以被再次置为高。

控制寄存器接口时序

在这里插入图片描述

在控制寄存器接口上,需要在每一个时钟解析cmd。当cmd为写指令时,需要把数据cmd_data_in写入到cmd_addr对应的寄存器中;当cmd为读指令时,即需要从cmd_addr对应的寄存器中读取数据,并在下一个周期,将数据驱动至cmd_data_out接口。

寄存器描述

1、地址0x00 通道1控制寄存器 32bits 读写寄存器

bit(0):通道使能信号。1为打开,0位关闭。复位值为1。 bit(2:1):优先级。0为最高,3为最低。复位值为3。
bit(5:3):数据包长度,解码对应表为, 0对应长度4,1对应长度8,2对应长度16,3对应长度32,其它数值(4-7)均暂时对应长度32。复位值为0。
bit(31:6):保留位,无法写入。复位值为0。

2、地址0x04 通道2控制寄存器 32bits 读写寄存器

同通道1控制寄存器描述。
3、地址0x08 通道3控制寄存器 32bits 读写寄存器

同通道1控制寄存器描述。
4、地址0x10 通道1状态寄存器 32bits 只读寄存器

bit(7:0):上行数据从端FIFO的可写余量,同FIFO的数据余量保持同步变化。复位值为FIFO的深度数。
bit(31:8):保留位,复位值为0。

5、地址0x14 通道2状态寄存器 32bits 只读寄存器

同通道1状态寄存器描述。
6、地址0x18 通道3状态寄存器 32bits 只读寄存器

同通道1状态寄存器描述。

实验3-1:SystemVerilog的真正引入

image-20210708183029815

问1:generator中的run方法规定了执行次数,initiator方法未规定执行次数,test类中的如何finish()?

答:agent封装类中的run方法在调用chnl_generator和chnl_initiator的run方法时,用的是fork-join_any块,即只要数据发送完run方法结束

1
2
3
4
fork
gen.run();
init.run();
join_any

问2:如何保证generator生成一个,initiator发送一个?

答:通过mailbox建立的握手行为,initiator执行get完才能写,generator执行完get才能进入下一次生成数据的循环

问3:如何通过指令控制仿真环境

  • 重启仿真:”restart”

  • 生成随机数(改变随机种子):

    “vsim -novopt -solvefaildebug -sv_seed 0 work.tb1”

    “vsim -novopt -solvefaildebug -sv seed random work.tb1”,每次

    • 参数solvefaildebug:随机化失败时会提示错误信息
    • 参数0:随机种子编号,通过改变随机种子即可以改变每次生成的随机值
  • 添加参数并通过参数判断是否执行语句:

    “+TESTNAME=testname”

文件结构

包chnl_pck

  • class chnl_trans:定义激励数据的格式(数据包间隔,数据间隔,通道id,数据包id)

    • 初始化:记录创建了多少个对象

    • constraint cstr:

      ​ 通过约束+randomize()定义数据的初始值(被generator调用)

    • function chnl_trans clone():

      ​ 对实例化对象进行复制!!(无需通过new+参数初始化)

  • class chnl_initiator:构建和dut、generator相连的初始化模型(驱动器)

    • 初始化:命名

      • 属性变量只要interface(沟通dut)和name,还有interface
    • set_interface(virtual chnl_intf intf):

      ​ 传入实际的interface,给interface句柄赋值

    • driver():

      从generator、get数据,然后执行chnl_write(input chnl_trans t)将数据发给dut,因为write方法内有时钟和reday信号进行阻塞,所以必须等到write方法执行完后才能用mailbox的put,这样generator就被mailbox的get阻塞,无法进行下一次的数据发送

      ​ 通过mailbox和generator进行数据交换,先调用mailbox的get方法,从generator获取数据

    • chnl_write(input chnl_trans t):

      和dut进行数据交流

      ​ 身为驱动类的必备功能,给dut传数据

    • chnl_idle():

      ​ 将valid和data置0,结合数据包间隔,数据间隔,给数据传输添加空窗期

    • run():

      执行driver()

  • class chnl_generator:生成数据并发送给initiator

    • 初始化:(1)在此处对和initiatot交流的mailbox进行实例化;(2)对数据包id,通道id和ntrans赋初值;

      • 属性变量包括:通道id,数据包id,对chnl_trans的随机化对象进行约束;ntrans决定数据包的发送次数;
    • send_trans():

      ​ 对应initiator的driver()方法

      • 此处实例化chnl_trans的req和rsp两个对象,req随机化成功( this.pkt_id++;),发送给mailbox(也就是initiator),initiator克隆一份后将rsp中的rsp变量修改为1,rsp传回generator后通过rsp属性值判断,作为此次发送是否成功的断言!!!失败则打印
    • run():

      执行send_trans()

  • class chnl_agent:对chnl_generator和chnl_initiator进行封装,将对外接口进一步优化

    • 初始化:传入chnl_generator和chnl_initiator需要的参数
    • set_interface(virtual chnl_intf vif):给chnl_initiator的set_interface方法传入参数
    • run():将generator和initiator的run方法进行封装,同时用句柄赋值的方式,让generator和initiator的mailbox指向同一个地址
  • chnl_root_test:最顶层封装,tb文件直接调用的对象,创建3个agent,调用run和set_interface方法

    • 初始化:创建3个agent

tb文件

  • interface:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface chnl_intf(input clk, input rstn);
    logic [31:0] ch_data;
    logic ch_valid;
    logic ch_ready;
    logic [ 5:0] ch_margin;
    clocking drv_ck @(posedge clk);
    default input #1ns output #1ns;
    output ch_data, ch_valid;
    input ch_ready, ch_margin;
    endclocking
    endinterface
  • module:

    • dut的输入输出连接

    • 分配时钟和复位信号

    • 启动test(导包,声明接口,声明句柄并实例化,调用run方法)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41

      module tb1;
      logic clk;
      logic rstn;
      logic [31:0] mcdt_data;
      logic mcdt_val;
      logic [ 1:0] mcdt_id;

      mcdt dut(
      .clk_i (clk )
      ,.rstn_i (rstn )
      ,.ch0_data_i (chnl0_if.ch_data )
      ,.ch0_valid_i (chnl0_if.ch_valid )
      ,.ch0_ready_o (chnl0_if.ch_ready )
      ,.ch0_margin_o(chnl0_if.ch_margin )
      ,.ch1_data_i (chnl1_if.ch_data )
      ,.ch1_valid_i (chnl1_if.ch_valid )
      ,.ch1_ready_o (chnl1_if.ch_ready )
      ,.ch1_margin_o(chnl1_if.ch_margin )
      ,.ch2_data_i (chnl2_if.ch_data )
      ,.ch2_valid_i (chnl2_if.ch_valid )
      ,.ch2_ready_o (chnl2_if.ch_ready )
      ,.ch2_margin_o(chnl2_if.ch_margin )
      ,.mcdt_data_o (mcdt_data )
      ,.mcdt_val_o (mcdt_val )
      ,.mcdt_id_o (mcdt_id )
      );
      // clock generation
      initial begin
      clk <= 0;
      forever begin
      #5 clk <= !clk;
      end
      end

      // reset trigger
      initial begin
      #10 rstn <= 0;
      repeat(10) @(posedge clk);
      rstn <= 1;
      end

实验3-2:generator提取

爲了更方便的控制generator生成数据,将generator从agent中拿出,直接由test控制

image-20210708205714482

image-20210708223857777

文件变化

vsim+TESTNAME = chnl_burst_test work.tb2

包chnl_pck

  • chnl_generator:

    属性变量连原本由chnl_trans控制的data_size,pkt_nidles也加入了进来,目的也是让test可以完全掌握数据生成

    通过一层层的约束

    注意:为什么随机化里面用的是local::而不用this?

    local指的是generator 的实例对象,如果在这里使用this,指的是调用randomsize方法的数据实例对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
        rand int pkt_id = -1;
    rand int ch_id = -1;
    rand int data_nidles = -1;
    rand int pkt_nidles = -1;
    rand int data_size = -1;
    rand int ntrans = 10;
    constraint cstr{
    soft ch_id == -1;
    soft pkt_id == -1;
    soft data_size == -1;
    soft data_nidles == -1;
    soft pkt_nidles == -1;
    soft ntrans == 10;
    }
    task send_trans();
    chnl_trans req, rsp;
    req = new();
    assert(req.randomize with {local::ch_id >= 0 -> ch_id == local::ch_id;
    local::pkt_id >= 0 -> pkt_id == local::pkt_id;
    local::data_nidles >= 0 -> data_nidles == local::data_nidles;
    local::pkt_nidles >= 0 -> pkt_nidles == local::pkt_nidles;
    local::data_size >0 -> data.size() == local::data_size;
    })
  • class chnl_agent:将generator摘除

  • class chnl_root_test:变化最大

    • 初始化:agent和generator分别创建3个

      ​ 因为generator从agent中提出,建立initiator和generator的mailbox连接的任务交由test完成

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      function new(string name = "chnl_root_test");
      foreach(agent[i]) begin
      this.agent[i] = new($sformatf("chnl_agent%0d",i));
      this.gen[i] = new();
      // USER TODO 2.1
      // Connect the mailboxes handles of gen[i] and agent[i].init
      agent[i].init.req_mb = gen[i].req_mb;
      agent[i].init.rsp_mb = gen[i].rsp_mb;
      end
      this.name = name;
      $display("%s instantiate objects", this.name);
      endfunction
    • do_config():对创建的3个generator实例进行随机化(赋予数据),对比3-1中在generator中的随机化,明显对外暴露了更多接口可以自定义(只需在test中修改)

      1
      2
      assert(gen[0].randomize() with {ntrans==100; data_nidles==0; pkt_nidles==1; data_size==8;})
      assert(req.randomize with {ch_id == local::ch_id; pkt_id == local::pkt_id;})
    • run():同样因为gen和init的分离,run方法如何执行和结束也是一个问题

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      this.do_config();
      fork
      agent[0].run();
      agent[1].run();
      agent[2].run();
      join_none
      fork
      gen[0].run();
      gen[1].run();
      gen[2].run();
      join

实验3-3:增加新组件monitor和checker

monitor和checker干了嘛?

  • monitor: 分为chnl_monitor监控输入和mcdt_monitor监控输出

    • 如何保证采集数有效:就是允许数据传输的条件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      //chnl
      @(posedge intf.clk iff (intf.mon_ck.ch_valid==='b1 && intf.mon_ck.ch_ready==='b1));
      m.data = intf.mon_ck.ch_data;
      mon_mb.put(m);
      //mcdt
      @(posedge intf.clk iff intf.mon_ck.mcdt_val==='b1);
      m.data = intf.mon_ck.mcdt_data;
      m.id = intf.mon_ck.mcdt_id;
      mon_mb.put(m);
    • monitor的run方法:就是为了将采集数据放入mailbox

  • checker:run方法就是从monitor发送的mailbox中获取数据,比较chnl的mailbox和mcdt的mailbox的数据是否一致

问1:chnl_monitor和mcdt_monitor的数据对比如何保证数据的顺序是对应的?

两者的mailbox保证了上下的数据都是进到队列里面,然后从头部拿出比较,一方为空则比较,或者说checker的run就无法执行

问2:不用$finish()了,如何结束的进程

  • 结束整个进程:test自定义run_stop_callback()方法。方法内部通过获取3把钥匙进行阻塞,每有一个gen执行完发送,put一个钥匙

    在整个文件的最开始建立旗语, semaphore run_stop_flags = new(); 在generator中每有一个发送了ntrans个数据包截止的时候put进去一个钥匙,当三个gen都执行完,run_stop_flags获得三把钥匙,执行$finish(),归根结底还是用的finish

  • full_test中结束generator:先看一下full_test中给gen赋值的随机化约束:ntrans是不定值

    1
    2
    assert(gen[0].randomize() with {ntrans inside {[1000:2000]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
    else $fatal("[RNDFAIL] gen[0] randomization failure!");

问3:mailbox有几个,在哪些组件间建立连接

答:共有6个

  • 4个被checker和monitor持有,3个是agent的monitor和checker之间建立的连接,1个是mcdt的monitor和checker的连接

  • 2个被gen和init持有,用于两者的握手协议

  • 注意: 可以看到monitor的数据mon_data_t声明非常简单,其和checker的数据传输单纯就是一个存储的队列

  • 同样在root_test中建立连接

    1
    2
    3
    4
    5
    6
    7
    8
    9
    foreach(agents[i]) begin
    this.agents[i] = new($sformatf("chnl_agent%0d",i));
    this.gen[i] = new();
    // USER TODO 2.1
    // Connect the mailboxes handles of gen[i] and agents[i].init
    this.agents[i].init.req_mb = this.gen[i].req_mb;
    this.agents[i].init.rsp_mb = this.gen[i].rsp_mb;
    this.agents[i].mon.mon_mb = this.chker.in_mbs[i];
    end

image-20210709202107429

新加入文件结构

包chnl_pack

  • typedef struct packed:

    低配版的chnl_trans,作为monitor里的数据容器

  • class chnl_monitor:获取dut输入,发送给checker

    • 属性:name,interface,mailbox(未实例化)

    • set_interface:传入接口

    • mon_trans:发mail给ckecker

      • @(posedge intf.clk iff (intf.mon_ck.ch_valid==='b1 && intf.mon_ck.ch_ready==='b1))

        保证在数据传输的时候进行采样;

      • 并且将数据线上的数据装盒,放入mailbox

  • class mcdt_monitor:获取dut输出,发送给checker

  • class chnl_agent更新:将chnl_monitor加入,run方法中加入monitor.run();

  • class chnl_checker:

    • 属性:name,error_count,cmp_count,mailbox
    • 初始化:对邮箱实例化,错误和完成计数初始化
      • 注意:chnl_monitor有3个,需创建3个mailbox与之对应,监控3个chnl
    • do_compare():这里的阻塞
  • chnl_root_test:作为所有测试类的基类

    功能:

    • 初始化:实例化各部分组件,连接mailbox(generator和driver的连接,monitor和checker的连接)
    • 虚方法(do_config):执行随机化,可以通过断言判断随机化是否成功(在子类中实现)
  • chnl_basic_test:实现随机化的config方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    virtual function void do_config();
    super.do_config();
    assert(gen[0].randomize() with {ntrans==100; data_nidles==0; pkt_nidles==1; data_size==8;})
    else $fatal("[RNDFAIL] gen[0] randomization failure!");

    assert(gen[1].randomize() with {ntrans==50; data_nidles inside {[1:2]}; pkt_nidles inside {[3:5]}; data_size==6;})
    else $fatal("[RNDFAIL] gen[1] randomization failure!");

    assert(gen[2].randomize() with {ntrans==80; data_nidles inside {[0:1]}; pkt_nidles inside {[1:2]}; data_size==32;})
    else $fatal("[RNDFAIL] gen[2] randomization failure!");
    endfunction
  • chnl_burst_test:随机化存在差别,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    virtual function void do_config();
    super.do_config();
    assert(gen[0].randomize() with {ntrans inside {[80:100]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
    else $fatal("[RNDFAIL] gen[0] randomization failure!");
    assert(gen[1].randomize() with {ntrans inside {[80:100]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
    else $fatal("[RNDFAIL] gen[1] randomization failure!");
    assert(gen[2].randomize() with {ntrans inside {[80:100]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
    else $fatal("[RNDFAIL] gen[2] randomization failure!");
    endfunction
  • chnl_fifo_full_test:大数据量冲击

    1
    2
    3
    4
    5
    6
    7
    8
    9
    virtual function void do_config();
    super.do_config();
    assert(gen[0].randomize() with {ntrans inside {[1000:2000]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
    else $fatal("[RNDFAIL] gen[0] randomization failure!");
    assert(gen[1].randomize() with {ntrans inside {[1000:2000]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
    else $fatal("[RNDFAIL] gen[1] randomization failure!");
    assert(gen[2].randomize() with {ntrans inside {[1000:2000]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
    else $fatal("[RNDFAIL] gen[2] randomization failure!");
    endfunction

tb文件

优化:通过外部指令决定执行那个类

核心知识点:

  • 字符串索引的关联数组:类似哈希表,不过是将key变成了数组下标,实现字符串到数组元素的映射
  • 将3个实例化的test模块放入数组,通过命令键入的字符串取出,用父类test接收,决定执行那个test的方法!!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  import chnl_pkg3::*;

chnl_intf chnl0_if(.*);
chnl_intf chnl1_if(.*);
chnl_intf chnl2_if(.*);
mcdt_intf mcdt_if(.*);

chnl_basic_test basic_test;
chnl_burst_test burst_test;
chnl_fifo_full_test fifo_full_test;
chnl_root_test tests[string];
string name;

initial begin
basic_test = new();
burst_test = new();
fifo_full_test = new();
tests["chnl_basic_test"] = basic_test;
tests["chnl_burst_test"] = burst_test;
tests["chnl_fifo_full_test"] = fifo_full_test;
if($value$plusargs("TESTNAME=%s", name)) begin
if(tests.exists(name)) begin
tests[name].set_interface(chnl0_if, chnl1_if, chnl2_if, mcdt_if);
tests[name].run();
end
else begin
$fatal($sformatf("[ERRTEST], test name %s is invalid, please specify a valid name!", name));
end
end
else begin
$display("NO runtime optiont +TESTNAME=xxx is configured, and run default test chnl_basic_test");
tests["chnl_basic_test"].set_interface(chnl0_if, chnl1_if, chnl2_if, mcdt_if);
tests["chnl_basic_test"].run();
end
end
endmodule

实验4:新增结构

问1:param_def.v

如图:将reg,formatter拿入考量

image-20210711150431833

文件结构

param_def.v

通过宏('define)对部分参数进行定义,硬件部分使用

  • reg输入、输出:
    • 地址位宽
    • 数据位宽
    • 3个命令(对应2位2进制)
    • 6个寄存器地址
    • 6个寄存器编号
  • chnl:(输入reg的状态寄存器)
    • slave_fifo的余量位宽:`define FIFO_MARGIN_WIDTH
  • 优先级:(输出,给arbiter)
  • 数据包长度:(输出,给formatter)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

`define ADDR_WIDTH 8
`define CMD_DATA_WIDTH 32

`define WRITE 2'b10 //Register operation command
`define READ 2'b01
`define IDLE 2'b00

`define SLV0_RW_ADDR 8'h00 //Register address
`define SLV1_RW_ADDR 8'h04
`define SLV2_RW_ADDR 8'h08
`define SLV0_R_ADDR 8'h10
`define SLV1_R_ADDR 8'h14
`define SLV2_R_ADDR 8'h18


`define SLV0_RW_REG 0
`define SLV1_RW_REG 1
`define SLV2_RW_REG 2
`define SLV0_R_REG 3
`define SLV1_R_REG 4
`define SLV2_R_REG 5

`define FIFO_MARGIN_WIDTH 8

`define PRIO_WIDTH 2
`define PRIO_HIGH 2
`define PRIO_LOW 1

`define PAC_LEN_WIDTH 3
`define PAC_LEN_HIGH 5
`define PAC_LEN_LOW 3

1. 包chnl_pack(基层包,无需导包)

变化:结构更加规范:

  • class mcdt_monitor,class chnl_checker还有test均从该包拿出;
  • 仅保留和chnl相关的功能,即生成激励gen,发送数据driver(initiator改名),监控数据monitor;
  • driver存在些许改变,增加reset方法对复位信号做出响应

interface

和硬件交流的信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface chnl_intf(input clk, input rstn);
logic [31:0] ch_data;
logic ch_valid;
logic ch_ready;
clocking drv_ck @(posedge clk);
default input #1ns output #1ns;
output ch_data, ch_valid;
input ch_ready;
endclocking
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
input ch_data, ch_valid, ch_ready;
endclocking
endinterface

transcation

driver需要的数据:只有data是被硬件需要的,其他的ch_id,pkt_id是所在通道和包的信息,data_nidles,pkt_nidles是对间隔时间的配置

这里的clone()和sprint()方法,在UVM中可以通过自动化域的方法取代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class chnl_trans;
rand bit[31:0] data[];
rand int ch_id;
rand int pkt_id;
rand int data_nidles;
rand int pkt_nidles;
bit rsp;
constraint cstr{
soft data.size inside {[4:32]};
foreach(data[i]) data[i] == 'hC000_0000 + (this.ch_id<<24) + (this.pkt_id<<8) + i;
soft ch_id == 0;
soft pkt_id == 0;
soft data_nidles inside {[0:2]};
soft pkt_nidles inside {[1:10]};
};

function chnl_trans clone();
chnl_trans c = new();
c.data = this.data;
c.ch_id = this.ch_id;
c.pkt_id = this.pkt_id;
c.data_nidles = this.data_nidles;
c.pkt_nidles = this.pkt_nidles;
c.rsp = this.rsp;
return c;
endfunction

function string sprint();
string s;
s = {s, $sformatf("=======================================\n")};
s = {s, $sformatf("chnl_trans object content is as below: \n")};
foreach(data[i]) s = {s, $sformatf("data[%0d] = %8x \n", i, this.data[i])};
s = {s, $sformatf("ch_id = %0d: \n", this.ch_id)};
s = {s, $sformatf("pkt_id = %0d: \n", this.pkt_id)};
s = {s, $sformatf("data_nidles = %0d: \n", this.data_nidles)};
s = {s, $sformatf("pkt_nidles = %0d: \n", this.pkt_nidles)};
s = {s, $sformatf("rsp = %0d: \n", this.rsp)};
s = {s, $sformatf("=======================================\n")};
return s;
endfunction
endclass: chnl_trans

driver

核心功能:

  • 从generator接收激励:mailbox,do_driver();

  • 对接口进行驱动:do_driver{mailbox.get(REQ);chnl_write();mailbox.put(RSP)}

    将mailbox中拿出的trans类型的req,把数据放到接口上,vaild信号置为1;然后等待ready信号来就可以执行idle把数据关闭了

  • 复位:独立的监控线程,一旦检测复位信号,vailid和data信号拉低

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class chnl_driver;
local string name;
local virtual chnl_intf intf;
mailbox #(chnl_trans) req_mb;
mailbox #(chnl_trans) rsp_mb;

function new(string name = "chnl_driver");
this.name = name;
endfunction

function void set_interface(virtual chnl_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction

task run();
fork
this.do_drive();
this.do_reset();
join
endtask

task do_reset();
forever begin
@(negedge intf.rstn);
intf.ch_valid <= 0;
intf.ch_data <= 0;
end
endtask

task do_drive();
chnl_trans req, rsp;
@(posedge intf.rstn);
forever begin
this.req_mb.get(req);
this.chnl_write(req);
rsp = req.clone();
rsp.rsp = 1;
this.rsp_mb.put(rsp);
end
endtask

task chnl_write(input chnl_trans t);
foreach(t.data[i]) begin
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 1;
intf.drv_ck.ch_data <= t.data[i];
@(negedge intf.clk);
wait(intf.ch_ready === 'b1);
$display("%0t channel driver [%s] sent data %x", $time, name, t.data[i]);
repeat(t.data_nidles) chnl_idle();
end
repeat(t.pkt_nidles) chnl_idle();
endtask

task chnl_idle();
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 0;
intf.drv_ck.ch_data <= 0;
endtask
endclass: chnl_driver

generator

成员变量和transcation基本相同,无data数组,改为data.size;

generator的随机化约束只是为了使其内部随机化无法生效,需要外部控制

核心功能:send_trans(),实例化trans类型变量req,对req做transcation的随机化,然后将req传入mailbox,发送给driver!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class chnl_generator;
rand int pkt_id = 0;
rand int ch_id = -1;
rand int data_nidles = -1;
rand int pkt_nidles = -1;
rand int data_size = -1;
rand int ntrans = 10;

mailbox #(chnl_trans) req_mb;
mailbox #(chnl_trans) rsp_mb;

constraint cstr{
soft ch_id == -1;
soft pkt_id == 0;
soft data_size == -1;
soft data_nidles == -1;
soft pkt_nidles == -1;
soft ntrans == 10;
}

function new();
this.req_mb = new();
this.rsp_mb = new();
endfunction

task start();
repeat(ntrans) send_trans();
endtask

task send_trans();
chnl_trans req, rsp;
req = new();
assert(req.randomize with {local::ch_id >= 0 -> ch_id == local::ch_id;
local::pkt_id >= 0 -> pkt_id == local::pkt_id;
local::data_nidles >= 0 -> data_nidles == local::data_nidles;
local::pkt_nidles >= 0 -> pkt_nidles == local::pkt_nidles;
local::data_size >0 -> data.size() == local::data_size;
})
else $fatal("[RNDFAIL] channel packet randomization failure!");
this.pkt_id++;
$display(req.sprint());
this.req_mb.put(req);
this.rsp_mb.get(rsp);
$display(rsp.sprint());
assert(rsp.rsp)
else $error("[RSPERR] %0t error response received!", $time);
endtask

function string sprint();
string s;
s = {s, $sformatf("=======================================\n")};
s = {s, $sformatf("chnl_generator object content is as below: \n")};
s = {s, $sformatf("ntrans = %0d: \n", this.ntrans)};
s = {s, $sformatf("ch_id = %0d: \n", this.ch_id)};
s = {s, $sformatf("pkt_id = %0d: \n", this.pkt_id)};
s = {s, $sformatf("data_nidles = %0d: \n", this.data_nidles)};
s = {s, $sformatf("pkt_nidles = %0d: \n", this.pkt_nidles)};
s = {s, $sformatf("data_size = %0d: \n", this.data_size)};
s = {s, $sformatf("=======================================\n")};
return s;
endfunction

function void post_randomize();
string s;
s = {"AFTER RANDOMIZATION \n", this.sprint()};
$display(s);
endfunction
endclass: chnl_generator

monitor

定义monitor传输的数据类型,此时没有trans那么复杂了,因为只需要验证数据的一致性

1
2
3
4
typedef struct packed {
bit[31:0] data;
bit[1:0] id;
} mon_data_t;

monitor组件

同样需要传入接口,因为要从接口上拿数据

核心方法:mon_trans()

当vaild和ready信号都有效,即真正进行数据传输时,采集interface上的时钟块中的data!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class chnl_monitor;
local string name;
local virtual chnl_intf intf;
mailbox #(mon_data_t) mon_mb;
function new(string name="chnl_monitor");
this.name = name;
endfunction
function void set_interface(virtual chnl_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task run();
this.mon_trans();
endtask

task mon_trans();
mon_data_t m;
forever begin
@(posedge intf.clk iff (intf.mon_ck.ch_valid==='b1 && intf.mon_ck.ch_ready==='b1));
m.data = intf.mon_ck.ch_data;
mon_mb.put(m);
$display("%0t %s monitored channle data %8x", $time, this.name, m.data);
end
endtask
endclass

agent

整个chnl_pkg的顶层封装!!!实例化driver,monitor

核心方法run():调用driver和monitor的run()方法,两者并行执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class chnl_agent;
local string name;
chnl_driver driver;
chnl_monitor monitor;
local virtual chnl_intf vif;
function new(string name = "chnl_agent");
this.name = name;
this.driver = new({name, ".driver"});
this.monitor = new({name, ".monitor"});
endfunction

function void set_interface(virtual chnl_intf vif);
this.vif = vif;
driver.set_interface(vif);
monitor.set_interface(vif);
endfunction
task run();
fork
driver.run();
monitor.run();
join
endtask
endclass: chnl_agent

2. package rpt_pkg(report工具包)

梳理下功能,然后再看下在test中是如何被调用的

  • 枚举类型:

    • 信息类型
    • 通过权重对输出的信息进行过滤
    • 通过action选择选择执行步骤
    1
    2
    3
    typedef enum {INFO, WARNING, ERROR, FATAL} report_t;
    typedef enum {LOW, MEDIUM, HIGH, TOP} severity_t;
    typedef enum {LOG, STOP, EXIT} action_t;
  • 设置初始值:

    • 权重为low时,表示最低一级的消息都被记录;后期验证次数多了之后,功能逐渐完善,就可以将过滤等级调高
    • 文件名,将日志导出
    • 对各条信息的输出次数进行计数
    1
    2
    3
    4
    5
    6
    static severity_t svrt = LOW;
    static string logname = "report.log";
    static int info_count = 0;
    static int warning_count = 0;
    static int error_count = 0;
    static int fatal_count = 0;
  • 最关键的一点:信息收集方法rpt_msg() ,在do_report()方法中被调用,看下调用时传入参数的含义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    function void rpt_msg(string src, string i, report_t r=INFO, severity_t s=LOW, action_t a=LOG);
    integer logf;
    string msg;
    case(r)
    INFO: info_count++;
    WARNING: warning_count++;
    ERROR: error_count++;
    FATAL: fatal_count++;
    endcase
    if(s >= svrt) begin
    msg = $sformatf("@%0t [%s] %s : %s", $time, r, src, i);
    logf = $fopen(logname, "a+");
    $display(msg);
    $fwrite(logf, $sformatf("%s\n", msg));
    $fclose(logf);
    if(a == STOP) begin
    $stop();
    end
    else if(a == EXIT) begin
    $finish();
    end
    end
    endfunction


    function void do_report();
    string s;
    s = "\n---------------------------------------------------------------\n";
    s = {s, "REPORT SUMMARY\n"};
    s = {s, $sformatf("info count: %0d \n", info_count)};
    s = {s, $sformatf("warning count: %0d \n", warning_count)};
    s = {s, $sformatf("error count: %0d \n", error_count)};
    s = {s, $sformatf("fatal count: %0d \n", fatal_count)};
    s = {s, "---------------------------------------------------------------\n"};
    rpt_msg("[REPORT]", s, rpt_pkg::INFO, rpt_pkg::TOP);
    endfunction
    • src:提前声明的默认值,实际使用时传入执行者(调用他的类,如checker或agent等)

      1
      rpt_pkg::rpt_msg($sformatf("[%s]",this.name), s, rpt_pkg::INFO, rpt_pkg::TOP);

      当然,也可以传入一下象征意义的字符串,如”[CMPFAIL]”,”[CMPSUCD]”等

    • i:见do_report的s,对各类型的信息进行计数;

      也可以传入自己定义的字符串,如checker中的report;

    • report_t r:通过case语句,给相应的信息类型计数++

    • severity_t s:筛选等级,和定义好的svrt进行比较,比它大才能执行后面的打印信息,导出日志等操作

    • action_t:直接默认就好,此时只执行导出日志

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      function void do_report();
      string s;
      s = "\n---------------------------------------------------------------\n";
      s = {s, "CHECKER SUMMARY \n"};
      s = {s, $sformatf("total comparison count: %0d \n", this.total_count)};
      foreach(this.chnl_count[i]) s = {s, $sformatf(" channel[%0d] comparison count: %0d \n", i, this.chnl_count[i])};
      s = {s, $sformatf("total error count: %0d \n", this.err_count)};
      foreach(this.chnl_mbs[i]) begin
      if(this.chnl_mbs[i].num() != 0)
      s = {s, $sformatf("WARNING:: chnl_mbs[%0d] is not empty! size = %0d \n", i, this.chnl_mbs[i].num())};
      end
      if(this.fmt_mb.num() != 0)
      s = {s, $sformatf("WARNING:: fmt_mb is not empty! size = %0d \n", this.fmt_mb.num())};
      s = {s, "---------------------------------------------------------------\n"};
      rpt_pkg::rpt_msg($sformatf("[%s]",this.name), s, rpt_pkg::INFO, rpt_pkg::TOP);
      endfunction

3. package reg_pkg(导包:`include “param_def.v”)

组件结构和chnl_pkg很像

image-20210719203717852

问:

1.为什么do_writer方法中,传入的trans中cmd == `READ,也就是执行READ时,要等待两个下降沿再获取数据???

答:上升沿来,将命令cmd和地址addr放到interface上;下一个上升沿来,寄存器reg获取到cmd和addr,将内部数据放到interface上;之后的下降沿来,就可以把interface上的data拿走了

interface

cmd:读、写或等待命令

cmd_addr:寄存器地址

cmd_data_s2m:写入读写寄存器的配置信息

cmd_data_m2s:从读寄存器中读出salve_fifo余量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface reg_intf(input clk, input rstn);
logic [1:0] cmd;
logic [`ADDR_WIDTH-1:0] cmd_addr;
logic [`CMD_DATA_WIDTH-1:0] cmd_data_s2m;
logic [`CMD_DATA_WIDTH-1:0] cmd_data_m2s;
clocking drv_ck @(posedge clk);
default input #1ns output #1ns;
output cmd, cmd_addr, cmd_data_m2s;
input cmd_data_s2m;
endclocking
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
input cmd, cmd_addr, cmd_data_m2s, cmd_data_s2m;
endclocking
endinterface

transcation

不需要id,idle和重复产生激励,所以只生成需要的信号

注意随机化中:对约束增加了关系限制,不允许不合理的激励传入,否则报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class reg_trans;
rand bit[7:0] addr;
rand bit[1:0] cmd;
rand bit[31:0] data;
bit rsp;

constraint cstr {
soft cmd inside {`WRITE, `READ, `IDLE};
soft addr inside {`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR, `SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR};
addr[7:4]==0 && cmd==`WRITE -> soft data[31:6]==0;
soft addr[7:5]==0;
addr[4]==1 -> soft cmd == `READ;
};

function reg_trans clone();
reg_trans c = new();
c.addr = this.addr;
c.cmd = this.cmd;
c.data = this.data;
c.rsp = this.rsp;
return c;
endfunction

function string sprint();
string s;
s = {s, $sformatf("=======================================\n")};
s = {s, $sformatf("reg_trans object content is as below: \n")};
s = {s, $sformatf("addr = %2x: \n", this.addr)};
s = {s, $sformatf("cmd = %2b: \n", this.cmd)};
s = {s, $sformatf("data = %8x: \n", this.data)};
s = {s, $sformatf("rsp = %0d: \n", this.rsp)};
s = {s, $sformatf("=======================================\n")};
return s;
endfunction
endclass

driver

核心方法:do_drive()——reg_write()+mailbox传激励

  • 写方法,将地址,命令,数据放在接口上
  • 读方法,将地址,命令,放在接口上,下个时钟的下降沿从接口上拿数据(t.data <= intf.cmd_data_s2m;

不需要等待ready

generator是在时钟下降沿判断ready信号;

reg是在第二个时钟下降沿采集寄存器中的数据;读命令的时候要把读到的寄存器状态放入rsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
class reg_driver;
local string name;
local virtual reg_intf intf;
mailbox #(reg_trans) req_mb;
mailbox #(reg_trans) rsp_mb;

function new(string name = "reg_driver");
this.name = name;
endfunction

function void set_interface(virtual reg_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction

task run();
fork
this.do_drive();
this.do_reset();
join
endtask

task do_reset();
forever begin
@(negedge intf.rstn);
intf.cmd_addr <= 0;
intf.cmd <= `IDLE;
intf.cmd_data_m2s <= 0;
end
endtask

task do_drive();
reg_trans req, rsp;
@(posedge intf.rstn);
forever begin
this.req_mb.get(req);
this.reg_write(req);
rsp = req.clone();
rsp.rsp = 1;
this.rsp_mb.put(rsp);
end
endtask

task reg_write(reg_trans t);
@(posedge intf.clk iff intf.rstn);
case(t.cmd)
`WRITE: begin
intf.drv_ck.cmd_addr <= t.addr;
intf.drv_ck.cmd <= t.cmd;
intf.drv_ck.cmd_data_m2s <= t.data;
end
`READ: begin
intf.drv_ck.cmd_addr <= t.addr;
intf.drv_ck.cmd <= t.cmd;
repeat(2) @(negedge intf.clk);
t.data = intf.cmd_data_s2m;
end
`IDLE: begin
this.reg_idle();
end
default: $error("command %b is illegal", t.cmd);
endcase
$display("%0t reg driver [%s] sent addr %2x, cmd %2b, data %8x", $time, name, t.addr, t.cmd, t.data);
endtask

task reg_idle();
@(posedge intf.clk);
intf.drv_ck.cmd_addr <= 0;
intf.drv_ck.cmd <= `IDLE;
intf.drv_ck.cmd_data_m2s <= 0;
endtask
endclass

generator

核心方法:send_trans(),通chnl的send_trans

区别:cmd是read的时候,driver会把rsp中的数据改为读到的数据再发给generator,generator的组件实例可以将其获取到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class reg_generator;
rand bit[7:0] addr = -1;
rand bit[1:0] cmd = -1;
rand bit[31:0] data = -1;

mailbox #(reg_trans) req_mb;
mailbox #(reg_trans) rsp_mb;

reg_trans reg_req[$];

constraint cstr{
soft addr == -1;
soft cmd == -1;
soft data == -1;
}

function new();
this.req_mb = new();
this.rsp_mb = new();
endfunction

task start();
send_trans();
endtask

// generate transaction and put into local mailbox
task send_trans();
reg_trans req, rsp;
req = new();
assert(req.randomize with {local::addr >= 0 -> addr == local::addr;
local::cmd >= 0 -> cmd == local::cmd;
local::data >= 0 -> data == local::data;
})
else $fatal("[RNDFAIL] register packet randomization failure!");
$display(req.sprint());
this.req_mb.put(req);
this.rsp_mb.get(rsp);
$display(rsp.sprint());
if(req.cmd == `READ)
this.data = rsp.data;
assert(rsp.rsp)
else $error("[RSPERR] %0t error response received!", $time);
endtask

function string sprint();
string s;
s = {s, $sformatf("=======================================\n")};
s = {s, $sformatf("reg_generator object content is as below: \n")};
s = {s, $sformatf("addr = %2x: \n", this.addr)};
s = {s, $sformatf("cmd = %2b: \n", this.cmd)};
s = {s, $sformatf("data = %8x: \n", this.data)};
s = {s, $sformatf("=======================================\n")};
return s;
endfunction

function void post_randomize();
string s;
s = {"AFTER RANDOMIZATION \n", this.sprint()};
$display(s);
endfunction
endclass

monitor

核心方法:mon_trans():将接口上的cmd,addr,data都放入(reg_trans)m,然后将m放入mailbox

区别于chnl,没有定义monitor的数据类型,直接使用reg_trans

chnl:时钟上升沿+valid===1+ready===1

reg:时钟上升沿+复位(==1)+intf.mon.clk != IDLE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class reg_monitor;
local string name;
local virtual reg_intf intf;
mailbox #(reg_trans) mon_mb;
function new(string name="reg_monitor");
this.name = name;
endfunction
function void set_interface(virtual reg_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task run();
this.mon_trans();
endtask

task mon_trans();
reg_trans m;
forever begin
@(posedge intf.clk iff (intf.rstn && intf.mon_ck.cmd != `IDLE));
m = new();
m.addr = intf.mon_ck.cmd_addr;
m.cmd = intf.mon_ck.cmd;
if(intf.mon_ck.cmd == `WRITE) begin
m.data = intf.mon_ck.cmd_data_m2s;
end
else if(intf.mon_ck.cmd == `READ) begin
@(posedge intf.clk);
m.data = intf.mon_ck.cmd_data_s2m;
end
mon_mb.put(m);
$display("%0t %s monitored addr %2x, cmd %2b, data %8x", $time, this.name, m.addr, m.cmd, m.data);
end
endtask
endclass

agent

省略

4. package fmt_pkg

interface

忽然变复杂,信号有些多,理清输入输出!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface fmt_intf(input clk, input rstn);
logic fmt_grant;
logic [1:0] fmt_chid;
logic fmt_req;
logic [5:0] fmt_length;
logic [31:0] fmt_data;
logic fmt_start;
logic fmt_end;
clocking drv_ck @(posedge clk);
default input #1ns output #1ns;
input fmt_chid, fmt_req, fmt_length, fmt_data, fmt_start;
output fmt_grant;
endclocking
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
input fmt_grant, fmt_chid, fmt_req, fmt_length, fmt_data, fmt_start;
endclocking
endinterface

enum

1
2
typedef enum {SHORT_FIFO, MED_FIFO, LONG_FIFO, ULTRA_FIFO} fmt_fifo_t;
typedef enum {LOW_WIDTH, MED_WIDTH, HIGH_WIDTH, ULTRA_WIDTH} fmt_bandwidth_t;

本质上对fmt的驱动,就是模拟一个fifo,这里对模拟的fifo进行自定义的范围

transcation

只关心length,data,通道id,接口中的响应信号在driver中见

不需要对这3这进行随机化,本身就是接收数据的容器,不用产生激励,只需要对容器多变变形状

多了一个比较方法,因为最后checker中还是对数据包的对比,采用fmt_trans的类型,所以直接在这里定义比较方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class fmt_trans;
rand fmt_fifo_t fifo;
rand fmt_bandwidth_t bandwidth;
bit [9:0] length;
bit [31:0] data[];
bit [1:0] ch_id;
bit rsp;
constraint cstr{
soft fifo == MED_FIFO;
soft bandwidth == MED_WIDTH;
};
function fmt_trans clone();
fmt_trans c = new();
c.fifo = this.fifo;
c.bandwidth = this.bandwidth;
c.length = this.length;
c.data = this.data;
c.ch_id = this.ch_id;
c.rsp = this.rsp;
return c;
endfunction

function string sprint();
string s;
s = {s, $sformatf("=======================================\n")};
s = {s, $sformatf("fmt_trans object content is as below: \n")};
s = {s, $sformatf("fifo = %s: \n", this.fifo)};
s = {s, $sformatf("bandwidth = %s: \n", this.bandwidth)};
s = {s, $sformatf("length = %s: \n", this.length)};
foreach(data[i]) s = {s, $sformatf("data[%0d] = %8x \n", i, this.data[i])};
s = {s, $sformatf("ch_id = %0d: \n", this.ch_id)};
s = {s, $sformatf("rsp = %0d: \n", this.rsp)};
s = {s, $sformatf("=======================================\n")};
return s;
endfunction

function bit compare(fmt_trans t);
string s;
compare = 1;
s = "\n=======================================\n";
s = {s, $sformatf("COMPARING fmt_trans object at time %0d \n", $time)};
if(this.length != t.length) begin
compare = 0;
s = {s, $sformatf("sobj length %0d != tobj length %0d \n", this.length, t.length)};
end
if(this.ch_id != t.ch_id) begin
compare = 0;
s = {s, $sformatf("sobj ch_id %0d != tobj ch_id %0d\n", this.ch_id, t.ch_id)};
end
foreach(this.data[i]) begin
if(this.data[i] != t.data[i]) begin
compare = 0;
s = {s, $sformatf("sobj data[%0d] %8x != tobj data[%0d] %8x\n", i, this.data[i], i, t.data[i])};
end
end
if(compare == 1) s = {s, "COMPARED SUCCESS!\n"};
else s = {s, "COMPARED FAILURE!\n"};
s = {s, "=======================================\n"};
rpt_pkg::rpt_msg("[CMPOBJ]", s, rpt_pkg::INFO, rpt_pkg::MEDIUM);
endfunction
endclass

driver

用mailbox充当接收数据的fifo,定义fifo的宽度(32位),定义fifo的深度(fifo_bound),定义fifo的数据消耗速度(data_consum_peroid,1代表一个时钟周期就消耗掉一个data),以上变量可进行随机化。

fifo_bound:对应trans的fmt_fifo_t fifo;

data_consum_peroid:对应trans的fmt_bandwidth_t bandwidth;

核心方法:do_run()

  • this.do_receive():forever监控线程,条件满足时将数据放入fifo

    • @(posedge intf.fmt_req):fmt提出发送数据

      forever判断:fifo深度的余量是否大于length,小于则一直循环,大于往下执行(给fmt一个grant==1)

    • 一旦检测到start上升沿,开始数据传输(length个下降沿,将接口上的数据放到fifo中),同时将grant拉低(fork-join_none)

  • this.do_config():同样是forever监控线程,对自建的fifo进行揉捏

    • generator可以对宽度和深度进行随机化,再通过mailbox发送到driver
  • this.do_consume():try_get()从自建fifo中取数,取数的速度由随机方法定义

    • repeat($urandom_range(1, this.data_consum_peroid)) @(posedge intf.clk);

    • 将buffer中的信号导出(减小this.fifo.num(),这样才有空放新数):do_consume()

    • 消耗和接收是并行的 ,所以消耗的速度不一定和接收速度一样就得是1个周期完成,消耗的慢接收就不执行呗
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
class fmt_driver;
local string name;
local virtual fmt_intf intf;
mailbox #(fmt_trans) req_mb;
mailbox #(fmt_trans) rsp_mb;

local mailbox #(bit[31:0]) fifo;
local int fifo_bound;
local int data_consum_peroid;


function new(string name = "fmt_driver");
this.name = name;
this.fifo = new();
this.fifo_bound = 4096;
this.data_consum_peroid = 1;
endfunction

function void set_interface(virtual fmt_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction

task run();
fork
this.do_receive();
this.do_consume();
this.do_config();
this.do_reset();
join
endtask

task do_config();
fmt_trans req, rsp;
forever begin
this.req_mb.get(req);
case(req.fifo)
SHORT_FIFO: this.fifo_bound = 64;
MED_FIFO: this.fifo_bound = 256;
LONG_FIFO: this.fifo_bound = 512;
ULTRA_FIFO: this.fifo_bound = 2048;
endcase
this.fifo = new(this.fifo_bound);
case(req.bandwidth)
LOW_WIDTH: this.data_consum_peroid = 8;
MED_WIDTH: this.data_consum_peroid = 4;
HIGH_WIDTH: this.data_consum_peroid = 2;
ULTRA_WIDTH: this.data_consum_peroid = 1;
endcase
rsp = req.clone();
rsp.rsp = 1;
this.rsp_mb.put(rsp);
end
endtask

task do_reset();
forever begin
@(negedge intf.rstn)
intf.fmt_grant <= 0;
end
endtask

task do_receive();
forever begin
@(posedge intf.fmt_req);
forever begin
@(posedge intf.clk);
if((this.fifo_bound-this.fifo.num()) >= intf.fmt_length)
break;
end
intf.drv_ck.fmt_grant <= 1;
@(posedge intf.fmt_start);
fork
begin
@(posedge intf.clk);
intf.drv_ck.fmt_grant <= 0;
end
join_none
repeat(intf.fmt_length) begin
@(negedge intf.clk);
this.fifo.put(intf.fmt_data);
end
end
endtask

task do_consume();
bit[31:0] data;
forever begin
void'(this.fifo.try_get(data));
repeat($urandom_range(1, this.data_consum_peroid)) @(posedge intf.clk);
end
endtask
endclass

generator

这里的激励就不是数据了,就是对buffer的配置

  • 核心方法:send_trans()

    把fifo的配置信息(fifo,bandwidth)发送给driver,driver用do_config接收,对自建fifo进行揉捏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class fmt_generator;
rand fmt_fifo_t fifo = MED_FIFO;
rand fmt_bandwidth_t bandwidth = MED_WIDTH;

mailbox #(fmt_trans) req_mb;
mailbox #(fmt_trans) rsp_mb;

constraint cstr{
soft fifo == MED_FIFO;
soft bandwidth == MED_WIDTH;
}

function new();
this.req_mb = new();
this.rsp_mb = new();
endfunction

task start();
send_trans();
endtask

// generate transaction and put into local mailbox
task send_trans();
fmt_trans req, rsp;
req = new();
assert(req.randomize with {local::fifo != MED_FIFO -> fifo == local::fifo;
local::bandwidth != MED_WIDTH -> bandwidth == local::bandwidth;
})
else $fatal("[RNDFAIL] formatter packet randomization failure!");
$display(req.sprint());
this.req_mb.put(req);
this.rsp_mb.get(rsp);
$display(rsp.sprint());
assert(rsp.rsp)
else $error("[RSPERR] %0t error response received!", $time);
endtask

function string sprint();
string s;
s = {s, $sformatf("=======================================\n")};
s = {s, $sformatf("fmt_generator object content is as below: \n")};
s = {s, $sformatf("fifo = %s: \n", this.fifo)};
s = {s, $sformatf("bandwidth = %s: \n", this.bandwidth)};
s = {s, $sformatf("=======================================\n")};
return s;
endfunction

function void post_randomize();
string s;
s = {"AFTER RANDOMIZATION \n", this.sprint()};
$display(s);
endfunction

endclass

monitor

核心方法:mon_trans()

采样时机:@(posedge intf.mon_ck.fmt_start); 要把接口上的(ch_id,length,data)都放进fmt_trans,data是length个时钟上升沿,将接口上的data放入m的data数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class fmt_monitor;
local string name;
local virtual fmt_intf intf;
mailbox #(fmt_trans) mon_mb;
function new(string name="fmt_monitor");
this.name = name;
endfunction
function void set_interface(virtual fmt_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction

task run();
this.mon_trans();
endtask

task mon_trans();
fmt_trans m;
string s;
forever begin
@(posedge intf.mon_ck.fmt_start);
m = new();
m.length = intf.mon_ck.fmt_length;
m.ch_id = intf.mon_ck.fmt_chid;
m.data = new[m.length];
foreach(m.data[i]) begin
@(posedge intf.clk);
m.data[i] = intf.mon_ck.fmt_data;
end
mon_mb.put(m);
s = $sformatf("=======================================\n");
s = {s, $sformatf("%0t %s monitored a packet: \n", $time, this.name)};
s = {s, $sformatf("length = %0d: \n", m.length)};
s = {s, $sformatf("chid = %0d: \n", m.ch_id)};
foreach(m.data[i]) s = {s, $sformatf("data[%0d] = %8x \n", i, m.data[i])};
s = {s, $sformatf("=======================================\n")};
$display(s);
end
endtask
endclass

agent

省略

5. package mcdf_pkg(顶层包)

image-20210719151113192

核心就是checker的实现,和实验3相比,checker多了对寄存器信息的处理

问:

  1. 如何将寄存器信息用到数据的比对上?

    如上图所示,构建mcdf硬件参考模型(模仿硬件功能),参考模型通过reg_mb信箱获取寄存器内容,do_reg_update()实现对寄存器内容的更新,do_packet()实现对数据的打包。

    通过寄存器数据中的length,可以将chnl_mb中的数据放到length长度数组中,封装成fmt_trans格式,调用fmt_trans的compare与fmt_mb中的数据报进行对比

  2. 如何保证寄存器模型中的数据指令和数据输入刚好对应?寄存器数据由外部写入,真正数据传输的时候对应寄存器的数据如何得到?

  3. 三个寄存器模型如何代表dut中的6个寄存器?

    RW和R的低位都是0,4,8。通过reg[3:2]就可以把各个寄存器区分

interface

1
2
3
4
5
6
7
8
9
10
11
12
interface mcdf_intf(input clk, input rstn);
// USER TODO
// To define those signals which do not exsit in
// reg_if, chnl_if, arb_if or fmt_if
logic chnl_en[3];


clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
input chnl_en;
endclocking
endinterface

enum+reg_packet

mcdf_reg_t

除了读写寄存器的6位,还有代表读寄存器的8位信号,相当于一个RW寄存器和一个R寄存器集成了一个寄存器模型

1
2
3
4
5
6
7
  typedef struct packed {
bit[2:0] len;
bit[1:0] prio;
bit en;
bit[7:0] avail;
} mcdf_reg_t;
typedef enum {RW_LEN, RW_PRIO, RW_EN, RD_AVAIL} mcdf_field_t;

mcdf_refmod

问:优先级怎么没用到??

在checker中搞一个类似fmt的模型,结合寄存器中的配置信息,对三个通道采集到的数据进行打包,之后再和formattor的输出进行比较

此时传入的接口只用到了复位信号

核心方法:run():同样被阻塞,需要等待monitor在mailbox中放值

  • do_reg_update():forever监控寄存器配置,有就把它get到,把配置信息放入refmod的对应reg数组中

    开始的get方法:意味着reg_monitor没检测到数据,该方法被阻塞

    从reg_mb中获取寄存器数据,并将其保存下来;地址位对应寄存器模型数组的index

    对于读写寄存器:数据的每一位的功能都赋值到寄存器中;

    对于读寄存器:将后8位slave_fifo的余量获取到

  • do_packet():通过reg数组中获取的配置信息,模仿formattor的打包行为,对3个chnl获取的数据进行打包(3个reg分别对应3个chnl),通过reg的id获取到length

    数据包中的data数组的长度就为length!!

    开始的peek()方法:意味着chnl_monitor没检测到数据,该方法被阻塞

    this.get_field_value(id, RW_LEN)==0,对应ot.length = 4;

    this.get_field_value(id, RW_LEN)==1,对应ot.length = 8;以此类推至32

  • get_field_value(int id, mcdf_field_t f):

    把寄存器模型中的数据放到枚举类型里面,方便调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class mcdf_refmod;
local virtual mcdf_intf intf;
local string name;
mcdf_reg_t regs[3];
mailbox #(reg_trans) reg_mb;
mailbox #(mon_data_t) in_mbs[3];
mailbox #(fmt_trans) out_mbs[3];

function new(string name="mcdf_refmod");
this.name = name;
foreach(this.out_mbs[i]) this.out_mbs[i] = new();
endfunction

task run();
fork
do_reset();
this.do_reg_update();
do_packet(0);
do_packet(1);
do_packet(2);
join
endtask

task do_reg_update();
reg_trans t;
forever begin
this.reg_mb.get(t);
if(t.addr[7:4] == 0 && t.cmd == `WRITE) begin
this.regs[t.addr[3:2]].en = t.data[0];
this.regs[t.addr[3:2]].prio = t.data[2:1];
this.regs[t.addr[3:2]].len = t.data[5:3];
end
else if(t.addr[7:4] == 1 && t.cmd == `READ) begin
this.regs[t.addr[3:2]].avail = t.data[7:0];
end
end
endtask

task do_packet(int id);
fmt_trans ot;
mon_data_t it;
forever begin
this.in_mbs[id].peek(it);
ot = new();
ot.length = 4 << (this.get_field_value(id, RW_LEN) & 'b11);
ot.data = new[ot.length];
ot.ch_id = id;
foreach(ot.data[m]) begin
this.in_mbs[id].get(it);
ot.data[m] = it.data;
end
this.out_mbs[id].put(ot);
end
endtask

function int get_field_value(int id, mcdf_field_t f);
case(f)
RW_LEN: return regs[id].len;
RW_PRIO: return regs[id].prio;
RW_EN: return regs[id].en;
RD_AVAIL: return regs[id].avail;
endcase
endfunction

task do_reset();
forever begin
@(negedge intf.rstn);
foreach(regs[i]) begin
regs[i].len = 'h0;
regs[i].prio = 'h3;
regs[i].en = 'h1;
regs[i].avail = 'h20;
end
end
endtask

function void set_interface(virtual mcdf_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction

endclass

mcdf_checker

接口传入的目的:见下面各check方法

mailbox的创建:

  • 沟通monitor:3个chnl(左连3个monitor的mon_mb,右连3个refmod的in_mbs[3]),1个reg(左连monitor,右连refmod),1个fmt(连接monitor,refmod没有)
  • 沟通refmod:exp_mbs[3],无需创建,连接out_mbs[3];

核心方法:

  • new()

    • 实例mailbox和refmod,建立邮箱连接
  • run()

    • do_channel_disable_check(int id):

      用了mcdf的接口,通道的chnl_en信号为0时,如果valid和ready信号仍能为高,打印错误信息

    • do_arbiter_priority_check();get_slave_id_with_prio()

      用了arb的接口,通过优先级获取id,并通过id判断通道是否有效

  • 比较fmt输出和slave输入:do_compare()

    声明俩fmt_trans句柄,一个从fmt_mb里拿,一个从exp_mbs里拿,如上图所示;

    然后调用fmt_trans本身的比较方法,注意打印信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
class mcdf_checker;
local string name;
local int err_count;
local int total_count;
local int chnl_count[3];
local virtual chnl_intf chnl_vifs[3];
local virtual arb_intf arb_vif;
local virtual mcdf_intf mcdf_vif;
local mcdf_refmod refmod;
mailbox #(mon_data_t) chnl_mbs[3];
mailbox #(fmt_trans) fmt_mb;
mailbox #(reg_trans) reg_mb;
mailbox #(fmt_trans) exp_mbs[3];

function new(string name="mcdf_checker");
this.name = name;
foreach(this.chnl_mbs[i]) this.chnl_mbs[i] = new();
this.fmt_mb = new();
this.reg_mb = new();
this.refmod = new();
foreach(this.refmod.in_mbs[i]) begin
this.refmod.in_mbs[i] = this.chnl_mbs[i];
this.exp_mbs[i] = this.refmod.out_mbs[i];
end
this.refmod.reg_mb = this.reg_mb;
this.err_count = 0;
this.total_count = 0;
foreach(this.chnl_count[i]) this.chnl_count[i] = 0;
endfunction

function void set_interface(virtual mcdf_intf mcdf_vif, virtual chnl_intf chnl_vifs[3], virtual arb_intf arb_vif);
if(mcdf_vif == null)
$error("mcdf interface handle is NULL, please check if target interface has been intantiated");
else begin
this.mcdf_vif = mcdf_vif;
this.refmod.set_interface(mcdf_vif);
end
if(chnl_vifs[0] == null || chnl_vifs[1] == null || chnl_vifs[2] == null)
$error("chnl interface handle is NULL, please check if target interface has been intantiated");
else begin
this.chnl_vifs = chnl_vifs;
end
if(arb_vif == null)
$error("arb interface handle is NULL, please check if target interface has been intantiated");
else begin
this.arb_vif = arb_vif;
end
endfunction

task run();
fork
this.do_channel_disable_check(0);
this.do_channel_disable_check(1);
this.do_channel_disable_check(2);
this.do_arbiter_priority_check();
this.do_compare();
this.refmod.run();
join
endtask

task do_compare();
fmt_trans expt, mont;
bit cmp;
forever begin
this.fmt_mb.get(mont);
this.exp_mbs[mont.ch_id].get(expt);
cmp = mont.compare(expt);
this.total_count++;
this.chnl_count[mont.ch_id]++;
if(cmp == 0) begin
this.err_count++;
rpt_pkg::rpt_msg("[CMPFAIL]",
$sformatf("%0t %0dth times comparing but failed! MCDF monitored output packet is different with reference model output", $time, this.total_count),
rpt_pkg::ERROR,
rpt_pkg::TOP,
rpt_pkg::LOG);
end
else begin
rpt_pkg::rpt_msg("[CMPSUCD]",
$sformatf("%0t %0dth times comparing and succeeded! MCDF monitored output packet is the same with reference model output", $time, this.total_count),
rpt_pkg::INFO,
rpt_pkg::HIGH);
end
end
endtask

task do_channel_disable_check(int id);
forever begin
@(posedge this.mcdf_vif.clk iff (this.mcdf_vif.rstn && this.mcdf_vif.mon_ck.chnl_en[id]===0));
if(this.chnl_vifs[id].mon_ck.ch_valid===1 && this.chnl_vifs[id].mon_ck.ch_ready===1)
rpt_pkg::rpt_msg("[CHKERR]",
$sformatf("ERROR! %0t when channel disabled, ready signal raised when valid high",$time),
rpt_pkg::ERROR,
rpt_pkg::TOP);
end
endtask

task do_arbiter_priority_check();
int id;
forever begin
@(posedge this.arb_vif.clk iff (this.arb_vif.rstn && this.arb_vif.mon_ck.f2a_id_req===1));
id = this.get_slave_id_with_prio();
if(id >= 0) begin
@(posedge this.arb_vif.clk);
if(this.arb_vif.mon_ck.a2s_acks[id] !== 1)
rpt_pkg::rpt_msg("[CHKERR]",
$sformatf("ERROR! %0t arbiter received f2a_id_req===1 and channel[%0d] raising request with high priority, but is not granted by arbiter", $time, id),
rpt_pkg::ERROR,
rpt_pkg::TOP);
end
end
endtask

function int get_slave_id_with_prio();
int id=-1;
int prio=999;
foreach(this.arb_vif.mon_ck.slv_prios[i]) begin
if(this.arb_vif.mon_ck.slv_prios[i] < prio && this.arb_vif.mon_ck.slv_reqs[i]===1) begin
id = i;
prio = this.arb_vif.mon_ck.slv_prios[i];
end
end
return id;
endfunction

function void do_report();
string s;
s = "\n---------------------------------------------------------------\n";
s = {s, "CHECKER SUMMARY \n"};
s = {s, $sformatf("total comparison count: %0d \n", this.total_count)};
foreach(this.chnl_count[i]) s = {s, $sformatf(" channel[%0d] comparison count: %0d \n", i, this.chnl_count[i])};
s = {s, $sformatf("total error count: %0d \n", this.err_count)};
foreach(this.chnl_mbs[i]) begin
if(this.chnl_mbs[i].num() != 0)
s = {s, $sformatf("WARNING:: chnl_mbs[%0d] is not empty! size = %0d \n", i, this.chnl_mbs[i].num())};
end
if(this.fmt_mb.num() != 0)
s = {s, $sformatf("WARNING:: fmt_mb is not empty! size = %0d \n", this.fmt_mb.num())};
s = {s, "---------------------------------------------------------------\n"};
rpt_pkg::rpt_msg($sformatf("[%s]",this.name), s, rpt_pkg::INFO, rpt_pkg::TOP);
endfunction
endclass

mcdf_env

把agent,checker融会贯通(chnl_agt,reg_agt,fmt_agt),功能覆盖率收集

核心方法:

  • new()

    实例化组价,建立mailbox连接

  • run()

    所有组件并行run()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class mcdf_env;
chnl_agent chnl_agts[3];
reg_agent reg_agt;
fmt_agent fmt_agt;
mcdf_checker chker;
mcdf_coverage cvrg;
protected string name;

function new(string name = "mcdf_env");
this.name = name;
this.chker = new();
foreach(chnl_agts[i]) begin
this.chnl_agts[i] = new($sformatf("chnl_agts[%0d]",i));
this.chnl_agts[i].monitor.mon_mb = this.chker.chnl_mbs[i];
end
this.reg_agt = new("reg_agt");
this.reg_agt.monitor.mon_mb = this.chker.reg_mb;
this.fmt_agt = new("fmt_agt");
this.fmt_agt.monitor.mon_mb = this.chker.fmt_mb;
this.cvrg = new();
$display("%s instantiated and connected objects", this.name);
endfunction

virtual task run();
$display($sformatf("*****************%s started********************", this.name));
this.do_config();
fork
this.chnl_agts[0].run();
this.chnl_agts[1].run();
this.chnl_agts[2].run();
this.reg_agt.run();
this.fmt_agt.run();
this.chker.run();
this.cvrg.run();
join
endtask

virtual function void do_report();
this.chker.do_report();
this.cvrg.do_report();
endfunction

endclass

base_test

组件都封好了,env实例化,执行就好

核心方法:

  • new():

    gen的创建,和与env中各个agent的driver的连接

    对日志输出文件进行初始化

    1
    2
    rpt_pkg::logname = {this.name, "_check.log"};
    rpt_pkg::clean_log();
  • run():

    fork-join_none:env.run()

    每个agent都等待gen的激励才能执行,激励的配置方法在base_test的继承类中实现

  • 两个值是否相同的比较:diff_value(),用于比较寄存器中写入的数据是否正确

  • reg的三种命令:都是生成激励(随机化)并发送(start()方法),void’()表示此时不返回值

    每次执行完写方法后都执行一次读方法获取寄存器的值,依次来判断寄存器的数据读写是否正确

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
class mcdf_base_test;
chnl_generator chnl_gens[3];
reg_generator reg_gen;
fmt_generator fmt_gen;
mcdf_env env;
protected string name;
local int timeout = 10; // 10 * ms

function new(string name = "mcdf_base_test");
this.name = name;
this.env = new("env");

foreach(this.chnl_gens[i]) begin
this.chnl_gens[i] = new();
this.env.chnl_agts[i].driver.req_mb = this.chnl_gens[i].req_mb;
this.env.chnl_agts[i].driver.rsp_mb = this.chnl_gens[i].rsp_mb;
end

this.reg_gen = new();
this.env.reg_agt.driver.req_mb = this.reg_gen.req_mb;
this.env.reg_agt.driver.rsp_mb = this.reg_gen.rsp_mb;

this.fmt_gen = new();
this.env.fmt_agt.driver.req_mb = this.fmt_gen.req_mb;
this.env.fmt_agt.driver.rsp_mb = this.fmt_gen.rsp_mb;

rpt_pkg::logname = {this.name, "_check.log"};
rpt_pkg::clean_log();
$display("%s instantiated and connected objects", this.name);
endfunction

virtual task run();
fork
env.run();
join_none
rpt_pkg::rpt_msg("[TEST]",
$sformatf("=====================%s AT TIME %0t STARTED=====================", this.name, $time),
rpt_pkg::INFO,
rpt_pkg::HIGH);
this.do_reg();
this.do_formatter();
fork
this.do_data();
this.do_watchdog();
join_any
rpt_pkg::rpt_msg("[TEST]",
$sformatf("=====================%s AT TIME %0t FINISHED=====================", this.name, $time),
rpt_pkg::INFO,
rpt_pkg::HIGH);
this.do_report();
$finish();
endtask

// do register configuration
virtual task do_reg();
endtask

// do external formatter down stream slave configuration
virtual task do_formatter();
endtask

// do data transition from 3 channel slaves
virtual task do_data();
endtask

// timeout watchdog to avoid simulation pending
virtual task do_watchdog();
rpt_pkg::rpt_msg("[TEST]",
$sformatf("=====================%s AT TIME %0t WATCHDOG GUARDING=====================", this.name, $time),
rpt_pkg::INFO,
rpt_pkg::HIGH);
#(this.timeout * 1ms);
rpt_pkg::rpt_msg("[TEST]",
$sformatf("=====================%s AT TIME %0t WATCHDOG BARKING=====================", this.name, $time),
rpt_pkg::INFO,
rpt_pkg::HIGH);
endtask

// do simulation summary
virtual function void do_report();
this.env.do_report();
rpt_pkg::do_report();
endfunction

virtual function void set_interface(virtual chnl_intf ch0_vif
,virtual chnl_intf ch1_vif
,virtual chnl_intf ch2_vif
,virtual reg_intf reg_vif
,virtual arb_intf arb_vif
,virtual fmt_intf fmt_vif
,virtual mcdf_intf mcdf_vif
);
this.env.chnl_agts[0].set_interface(ch0_vif);
this.env.chnl_agts[1].set_interface(ch1_vif);
this.env.chnl_agts[2].set_interface(ch2_vif);
this.env.reg_agt.set_interface(reg_vif);
this.env.fmt_agt.set_interface(fmt_vif);
this.env.chker.set_interface(mcdf_vif, '{ch0_vif, ch1_vif, ch2_vif}, arb_vif);
this.env.cvrg.set_interface('{ch0_vif, ch1_vif, ch2_vif}, reg_vif, arb_vif, fmt_vif, mcdf_vif);
endfunction

virtual function bit diff_value(int val1, int val2, string id = "value_compare");
if(val1 != val2) begin
rpt_pkg::rpt_msg("[CMPERR]",
$sformatf("ERROR! %s val1 %8x != val2 %8x", id, val1, val2),
rpt_pkg::ERROR,
rpt_pkg::TOP);
return 0;
end
else begin
rpt_pkg::rpt_msg("[CMPSUC]",
$sformatf("SUCCESS! %s val1 %8x == val2 %8x", id, val1, val2),
rpt_pkg::INFO,
rpt_pkg::HIGH);
return 1;
end
endfunction

virtual task idle_reg();
void'(reg_gen.randomize() with {cmd == `IDLE; addr == 0; data == 0;});
reg_gen.start();
endtask

virtual task write_reg(bit[7:0] addr, bit[31:0] data);
void'(reg_gen.randomize() with {cmd == `WRITE; addr == local::addr; data == local::data;});
reg_gen.start();
endtask

virtual task read_reg(bit[7:0] addr, output bit[31:0] data);
void'(reg_gen.randomize() with {cmd == `READ; addr == local::addr;});
reg_gen.start();
data = reg_gen.data;
endtask
endclass

mcdf_data_consistence_basic_test

无需重复定义run方法,只需要把gen的start执行

3个读写reg的gen配置:固定写入命令;并将写入的命令读出,检查是否正确输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class mcdf_data_consistence_basic_test extends mcdf_base_test;
function new(string name = "mcdf_data_consistence_basic_test");
super.new(name);
endfunction

task do_reg();
bit[31:0] wr_val, rd_val;
// slv0 with len=8, prio=0, en=1
wr_val = (1<<3)+(0<<1)+1;
this.write_reg(`SLV0_RW_ADDR, wr_val);
this.read_reg(`SLV0_RW_ADDR, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));

// slv1 with len=16, prio=1, en=1
wr_val = (2<<3)+(1<<1)+1;
this.write_reg(`SLV1_RW_ADDR, wr_val);
this.read_reg(`SLV1_RW_ADDR, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV1_WR_REG"));

// slv2 with len=32, prio=2, en=1
wr_val = (3<<3)+(2<<1)+1;
this.write_reg(`SLV2_RW_ADDR, wr_val);
this.read_reg(`SLV2_RW_ADDR, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV2_WR_REG"));

// send IDLE command
this.idle_reg();
endtask

task do_formatter();
void'(fmt_gen.randomize() with {fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;});
fmt_gen.start();
endtask

task do_data();
void'(chnl_gens[0].randomize() with {ntrans==100; ch_id==0; data_nidles==0; pkt_nidles==1; data_size==8; });
void'(chnl_gens[1].randomize() with {ntrans==100; ch_id==1; data_nidles==1; pkt_nidles==4; data_size==16;});
void'(chnl_gens[2].randomize() with {ntrans==100; ch_id==2; data_nidles==2; pkt_nidles==8; data_size==32;});
fork
chnl_gens[0].start();
chnl_gens[1].start();
chnl_gens[2].start();
join
#10us; // wait until all data haven been transfered through MCDF
endtask
endclass

mcdf_full_random_test

3个读写reg的gen配置:随机写入命令;并将写入的命令读出,检查是否正确输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class mcdf_full_random_test extends mcdf_base_test;
function new(string name = "mcdf_full_random_test");
super.new(name);
endfunction

task do_reg();
bit[31:0] wr_val, rd_val;
// slv0 with len={4,8,16,32}, prio={[0:3]}, en={[0:1]}
wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,1);
this.write_reg(`SLV0_RW_ADDR, wr_val);
this.read_reg(`SLV0_RW_ADDR, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));

// slv0 with len={4,8,16,32}, prio={[0:3]}, en={[0:1]}
wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,1);
this.write_reg(`SLV1_RW_ADDR, wr_val);
this.read_reg(`SLV1_RW_ADDR, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV1_WR_REG"));

// slv0 with len={4,8,16,32}, prio={[0:3]}, en={[0:1]}
wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,1);
this.write_reg(`SLV2_RW_ADDR, wr_val);
this.read_reg(`SLV2_RW_ADDR, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV2_WR_REG"));

// send IDLE command
this.idle_reg();
endtask

task do_formatter();
void'(fmt_gen.randomize() with {fifo inside {SHORT_FIFO, ULTRA_FIFO}; bandwidth inside {LOW_WIDTH, ULTRA_WIDTH};});
fmt_gen.start();
endtask

task do_data();
void'(chnl_gens[0].randomize() with {ntrans inside {[400:600]}; ch_id==0; data_nidles inside {[0:3]}; pkt_nidles inside {1,2,4,8}; data_size inside {8,16,32};});
void'(chnl_gens[1].randomize() with {ntrans inside {[400:600]}; ch_id==1; data_nidles inside {[0:3]}; pkt_nidles inside {1,2,4,8}; data_size inside {8,16,32};});
void'(chnl_gens[2].randomize() with {ntrans inside {[400:600]}; ch_id==2; data_nidles inside {[0:3]}; pkt_nidles inside {1,2,4,8}; data_size inside {8,16,32};});
fork
chnl_gens[0].start();
chnl_gens[1].start();
chnl_gens[2].start();
join
#10us; // wait until all data haven been transfered through MCDF
endtask
endclass

验证文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
`timescale 1ns/1ps

`include "param_def.v"

interface chnl_intf(input clk, input rstn);
logic [31:0] ch_data;
logic ch_valid;
logic ch_ready;
clocking drv_ck @(posedge clk);
default input #1ns output #1ns;
output ch_data, ch_valid;
input ch_ready;
endclocking
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
input ch_data, ch_valid, ch_ready;
endclocking
endinterface

interface reg_intf(input clk, input rstn);
logic [1:0] cmd;
logic [`ADDR_WIDTH-1:0] cmd_addr;
logic [`CMD_DATA_WIDTH-1:0] cmd_data_s2m;
logic [`CMD_DATA_WIDTH-1:0] cmd_data_m2s;
clocking drv_ck @(posedge clk);
default input #1ns output #1ns;
output cmd, cmd_addr, cmd_data_m2s;
input cmd_data_s2m;
endclocking
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
input cmd, cmd_addr, cmd_data_m2s, cmd_data_s2m;
endclocking
endinterface

interface arb_intf(input clk, input rstn);
logic [1:0] slv_prios[3];
logic slv_reqs[3];
logic a2s_acks[3];
logic f2a_id_req;
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
input slv_prios, slv_reqs, a2s_acks, f2a_id_req;
endclocking
endinterface

interface fmt_intf(input clk, input rstn);
logic fmt_grant;
logic [1:0] fmt_chid;
logic fmt_req;
logic [5:0] fmt_length;
logic [31:0] fmt_data;
logic fmt_start;
logic fmt_end;
clocking drv_ck @(posedge clk);
default input #1ns output #1ns;
input fmt_chid, fmt_req, fmt_length, fmt_data, fmt_start;
output fmt_grant;
endclocking
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
input fmt_grant, fmt_chid, fmt_req, fmt_length, fmt_data, fmt_start;
endclocking
endinterface

interface mcdf_intf(input clk, input rstn);
// USER TODO
// To define those signals which do not exsit in
// reg_if, chnl_if, arb_if or fmt_if
logic chnl_en[3];
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
input chnl_en;
endclocking
endinterface

module tb;
logic clk;
logic rstn;

mcdf dut(
.clk_i (clk )
,.rstn_i (rstn )
,.cmd_i (reg_if.cmd )
,.cmd_addr_i (reg_if.cmd_addr )
,.cmd_data_i (reg_if.cmd_data_m2s)
,.cmd_data_o (reg_if.cmd_data_s2m)
,.ch0_data_i (chnl0_if.ch_data )
,.ch0_vld_i (chnl0_if.ch_valid )
,.ch0_ready_o (chnl0_if.ch_ready )
,.ch1_data_i (chnl1_if.ch_data )
,.ch1_vld_i (chnl1_if.ch_valid )
,.ch1_ready_o (chnl1_if.ch_ready )
,.ch2_data_i (chnl2_if.ch_data )
,.ch2_vld_i (chnl2_if.ch_valid )
,.ch2_ready_o (chnl2_if.ch_ready )
,.fmt_grant_i (fmt_if.fmt_grant )
,.fmt_chid_o (fmt_if.fmt_chid )
,.fmt_req_o (fmt_if.fmt_req )
,.fmt_length_o(fmt_if.fmt_length )
,.fmt_data_o (fmt_if.fmt_data )
,.fmt_start_o (fmt_if.fmt_start )
,.fmt_end_o (fmt_if.fmt_end )
);

// clock generation
initial begin
clk <= 0;
forever begin
#5 clk <= !clk;
end
end

// reset trigger
initial begin
#10 rstn <= 0;
repeat(10) @(posedge clk);
rstn <= 1;
end

import chnl_pkg::*;
import reg_pkg::*;
import arb_pkg::*;
import fmt_pkg::*;
import mcdf_pkg::*;

reg_intf reg_if(.*);
chnl_intf chnl0_if(.*);
chnl_intf chnl1_if(.*);
chnl_intf chnl2_if(.*);
arb_intf arb_if(.*);
fmt_intf fmt_if(.*);
mcdf_intf mcdf_if(.*);

// mcdf interface monitoring MCDF ports and signals
assign mcdf_if.chnl_en[0] = tb.dut.ctrl_regs_inst.slv0_en_o;
assign mcdf_if.chnl_en[1] = tb.dut.ctrl_regs_inst.slv1_en_o;
assign mcdf_if.chnl_en[2] = tb.dut.ctrl_regs_inst.slv2_en_o;

// arbiter interface monitoring arbiter ports
assign arb_if.slv_prios[0] = tb.dut.arbiter_inst.slv0_prio_i;
assign arb_if.slv_prios[1] = tb.dut.arbiter_inst.slv1_prio_i;
assign arb_if.slv_prios[2] = tb.dut.arbiter_inst.slv2_prio_i;
assign arb_if.slv_reqs[0] = tb.dut.arbiter_inst.slv0_req_i;
assign arb_if.slv_reqs[1] = tb.dut.arbiter_inst.slv1_req_i;
assign arb_if.slv_reqs[2] = tb.dut.arbiter_inst.slv2_req_i;
assign arb_if.a2s_acks[0] = tb.dut.arbiter_inst.a2s0_ack_o;
assign arb_if.a2s_acks[1] = tb.dut.arbiter_inst.a2s1_ack_o;
assign arb_if.a2s_acks[2] = tb.dut.arbiter_inst.a2s2_ack_o;
assign arb_if.f2a_id_req = tb.dut.arbiter_inst.f2a_id_req_i;

mcdf_data_consistence_basic_test t1;
mcdf_full_random_test t2;
mcdf_base_test tests[string];
string name;

initial begin
t1 = new();
t2 = new();
tests["mcdf_data_consistence_basic_test"] = t1;
tests["mcdf_full_random_test"] = t2;
if($value$plusargs("TESTNAME=%s", name)) begin
if(tests.exists(name)) begin
tests[name].set_interface(chnl0_if, chnl1_if, chnl2_if, reg_if, arb_if, fmt_if, mcdf_if);
tests[name].run();
end
else begin
$fatal($sformatf("[ERRTEST], test name %s is invalid, please specify a valid name!", name));
end
end
else begin
$display("NO runtime optiont +TESTNAME=xxx is configured, and run default test mcdf_data_consistence_basic_test");
tests["mcdf_data_consistence_basic_test"].set_interface(chnl0_if, chnl1_if, chnl2_if, reg_if, arb_if, fmt_if, mcdf_if);
tests["mcdf_data_consistence_basic_test"].run();
end
end
endmodule

实验5:功能覆盖率

coverage工具类

定义在最顶层的test类中

问1:为什么bins的定义要对bin的范围进行拆分,如cmd_addr的采样点的bins定义?

答:如果将所有bin集中在一个bins,则每次有一个数命中即全部命中。拆分之后的覆盖率则需要每个都命中才行

问2:“type_option.weight = 0”声明在coverpoint里的意义何在?

答:即该coverpoint在covergroup中的权重为0,不参与covergroup的最终覆盖率计算,不会生成bin;这也是为了方便cross覆盖率的定义

1. 寄存器读写覆盖

要求读写命令和地址交叉覆盖,此时才能准确的验证读写是否正确

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
covergroup cg_mcdf_reg_write_read;
addr: coverpoint reg_vif.mon_ck.cmd_addr {
type_option.weight = 0;
bins slv0_rw_addr = {`SLV0_RW_ADDR};
bins slv1_rw_addr = {`SLV1_RW_ADDR};
bins slv2_rw_addr = {`SLV2_RW_ADDR};
bins slv0_r_addr = {`SLV0_R_ADDR };
bins slv1_r_addr = {`SLV1_R_ADDR };
bins slv2_r_addr = {`SLV2_R_ADDR };
}
cmd: coverpoint reg_vif.mon_ck.cmd {
type_option.weight = 0;
bins write = {`WRITE};
bins read = {`READ};
bins idle = {`IDLE};
}
cmdXaddr: cross cmd, addr {
bins slv0_rw_addr = binsof(addr.slv0_rw_addr);
bins slv1_rw_addr = binsof(addr.slv1_rw_addr);
bins slv2_rw_addr = binsof(addr.slv2_rw_addr);
bins slv0_r_addr = binsof(addr.slv0_r_addr );
bins slv1_r_addr = binsof(addr.slv1_r_addr );
bins slv2_r_addr = binsof(addr.slv2_r_addr );
bins write = binsof(cmd.write);
bins read = binsof(cmd.read );
bins idle = binsof(cmd.idle );
bins write_slv0_rw_addr = binsof(cmd.write) && binsof(addr.slv0_rw_addr);
bins write_slv1_rw_addr = binsof(cmd.write) && binsof(addr.slv1_rw_addr);
bins write_slv2_rw_addr = binsof(cmd.write) && binsof(addr.slv2_rw_addr);
bins read_slv0_rw_addr = binsof(cmd.read) && binsof(addr.slv0_rw_addr);
bins read_slv1_rw_addr = binsof(cmd.read) && binsof(addr.slv1_rw_addr);
bins read_slv2_rw_addr = binsof(cmd.read) && binsof(addr.slv2_rw_addr);
bins read_slv0_r_addr = binsof(cmd.read) && binsof(addr.slv0_r_addr);
bins read_slv1_r_addr = binsof(cmd.read) && binsof(addr.slv1_r_addr);
bins read_slv2_r_addr = binsof(cmd.read) && binsof(addr.slv2_r_addr);
}
endgroup

2. 寄存器非法指令测试

包括给非法寄存器地址的写和读;给合法读写寄存器地址写入非法数据;对合法读寄存器写入数据(无所谓非法不非法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
covergroup cg_mcdf_reg_illegal_access;
addr: coverpoint reg_vif.mon_ck.cmd_addr {
type_option.weight = 0;
bins legal_rw = {`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR};
bins legal_r = {`SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR};
bins illegal = {[8'h20:$], 8'hC, 8'h1C};
}
cmd: coverpoint reg_vif.mon_ck.cmd {
type_option.weight = 0;
bins write = {`WRITE};
bins read = {`READ};
}
wdata: coverpoint reg_vif.mon_ck.cmd_data_m2s {
type_option.weight = 0;
bins legal = {[0:'h3F]};
bins illegal = {['h40:$]};
}
rdata: coverpoint reg_vif.mon_ck.cmd_data_s2m {
type_option.weight = 0;
bins legal = {[0:'hFF]};
illegal_bins illegal = default;
}
cmdXaddrXdata: cross cmd, addr, wdata, rdata {
bins addr_legal_rw = binsof(addr.legal_rw);
bins addr_legal_r = binsof(addr.legal_r);
bins addr_illegal = binsof(addr.illegal);
bins cmd_write = binsof(cmd.write);
bins cmd_read = binsof(cmd.read);
bins wdata_legal = binsof(wdata.legal);
bins wdata_illegal = binsof(wdata.illegal);
bins rdata_legal = binsof(rdata.legal);
bins write_illegal_addr = binsof(cmd.write) && binsof(addr.illegal);
bins read_illegal_addr = binsof(cmd.read) && binsof(addr.illegal);
bins write_illegal_rw_data = binsof(cmd.write) && binsof(addr.legal_rw) && binsof(wdata.illegal);
bins write_illegal_r_data = binsof(cmd.write) && binsof(addr.legal_r) && binsof(wdata.illegal);
}
endgroup

3. 通道关闭测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
covergroup cg_channel_disable;
ch0_en: coverpoint mcdf_vif.mon_ck.chnl_en[0] {
type_option.weight = 0;
wildcard bins en = {1'b1};
wildcard bins dis = {1'b0};
}
ch1_en: coverpoint mcdf_vif.mon_ck.chnl_en[1] {
type_option.weight = 0;
wildcard bins en = {1'b1};
wildcard bins dis = {1'b0};
}
ch2_en: coverpoint mcdf_vif.mon_ck.chnl_en[2] {
type_option.weight = 0;
wildcard bins en = {1'b1};
wildcard bins dis = {1'b0};
}
ch0_vld: coverpoint chnl_vifs[0].mon_ck.ch_valid {
type_option.weight = 0;
bins hi = {1'b1};
bins lo = {1'b0};
}
ch1_vld: coverpoint chnl_vifs[1].mon_ck.ch_valid {
type_option.weight = 0;
bins hi = {1'b1};
bins lo = {1'b0};
}
ch2_vld: coverpoint chnl_vifs[2].mon_ck.ch_valid {
type_option.weight = 0;
bins hi = {1'b1};
bins lo = {1'b0};
}
chenXchvld: cross ch0_en, ch1_en, ch2_en, ch0_vld, ch1_vld, ch2_vld {
bins ch0_en = binsof(ch0_en.en);
bins ch0_dis = binsof(ch0_en.dis);
bins ch1_en = binsof(ch1_en.en);
bins ch1_dis = binsof(ch1_en.dis);
bins ch2_en = binsof(ch2_en.en);
bins ch2_dis = binsof(ch2_en.dis);
bins ch0_hi = binsof(ch0_vld.hi);
bins ch0_lo = binsof(ch0_vld.lo);
bins ch1_hi = binsof(ch1_vld.hi);
bins ch1_lo = binsof(ch1_vld.lo);
bins ch2_hi = binsof(ch2_vld.hi);
bins ch2_lo = binsof(ch2_vld.lo);
bins ch0_en_vld = binsof(ch0_en.en) && binsof(ch0_vld.hi);
bins ch0_dis_vld = binsof(ch0_en.dis) && binsof(ch0_vld.hi);
bins ch1_en_vld = binsof(ch1_en.en) && binsof(ch1_vld.hi);
bins ch1_dis_vld = binsof(ch1_en.dis) && binsof(ch1_vld.hi);
bins ch2_en_vld = binsof(ch2_en.en) && binsof(ch2_vld.hi);
bins ch2_dis_vld = binsof(ch2_en.dis) && binsof(ch2_vld.hi);
}
endgroup

覆盖率收集合并分析

仿真

1
vsim -i -classdebug -solvefaildebug -coverage -coverstore ./mti_covdb -testname mcdf_full_random_test -sv_seed random +TESTNAME=mcdf_full_random_test -l mcdf_full_random_test.log work.tb
  • -classdebug,这是为了提供更多的 SV 类调试功能
  • -solvefaildebug,这是为了在 SV 随机化失败之后有更多的信息提供出来
  • -sv_seed 0,暂时给固定的随机种子 0 ,-sv_seed random则每次的随机种子也是随机生成
  • +TESTNAME=mcdf_full_random_test ,这是指定仿真选择的调试
  • -| mcdf_full_random _test.log,这是让仿真的记录保存在特定的测试文件名称中
  • -coverage:会在仿真时产生代码覆盖率数据,功能覆盖率数据则默认会生成,与此选项无关。
  • -coverstore COVERAGE_ STORAGE_ PATH:这个命令是用来在仿真在最后结束时,生成覆盖率数据并且存储到 COVERAGE_ STORAGE_ PATH。你可以自己制定COVERAGE_STORAGE_ PATH,但需要注意路径名中不要包含中文字符。
  • -testname. TESTNAME:这个选项是你需要添加本次仿真的 test 名称,你可以使用同+TESTNAME 选项一样的 test 名称。

这样在仿真结束后,将在COVERAGE_STORAGE_PATH 下产生一个覆盖率数据文件”{TESTNAME}_{SV_SEED}.data” 。由于仿真时我们传入的种子是随机值,因此我们每次提交测试,在测试结束后都将产生一个独一无的覆盖率数据。

下图为3个测试用例的运行结果,endstimulation后才会在文件夹中显示data文件

image-20210819161232731

合并覆盖率

运行不同的仿真,或者运行同一个 test,它们都会生成独一无二的数据库。之前统一在 COVERAGE_STORAGE_PATH 下面生成的 xxx.data 覆盖率数据可以通过命令进行合并。

在 Questasim 的仿真窗口中敲入命令:

1
vcover merge -out ./mti_covdb/merged_coverage.ucdb ./mti_covdb

注意这里的路径也就是上面.data文件的默认生成路径

这个命令即是将之前产生的若干个 xxx.data 的覆盖率合并在一起,生成一个合并在一起的覆盖率文件。所以,在测试前期提交的测试越多,那么理论上覆盖率的增长也就越明显。

  • 第一次运行full_random_test:

    image-20210819163931884

  • 第二次运行full_random_test:

    image-20210819164125470

  • 第一次运行mcdf_data_consistence_basic_test:

    image-20210819164314967

  • 第二次运行mcdf_data_consistence_basic_test:

    image-20210819164514215

  • 执行合并命令后生成ucdb文件:

    image-20210819164902452

  • 合并命令后:覆盖率提升,运行次数太少~
    image-20210820185313147

接下来,你可以点击 File -> Open 来打开这个合并后的 UCDB 覆盖率数据库(注意选择文件类型 UCDB 就可以看到这个文件了)。打开这个数据库之后,可以发现合并后的数据库要比之前单独提交的任何一个测试在仿真结束时的该次覆盖率都要高。

可以在 covergroups 窗口栏中查看功能覆盖率,也可以在 Analysis 窗口中查看代码覆盖率:

分析覆盖率

可以依旧使用 Questasim 来打开 UCDB 利用工具来查看覆盖率,或者更直观的方式是在打开当前覆盖率数据库的同时,生成 HTML 报告。选择 Tools -> Coverage

Report-> HTML,按照下图所示进行勾选:

image-20210815003952211

之后Questasim 就会生成一份详尽的 HTML 覆盖率文档

image-20210815004053727

UVM-实验1

1.1 工厂的注册、创建和覆盖机制

总结:

1.实例创建:和new相比,通过仓库进行实例创建时,name无法传进去;

object类型实例创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class object_create extends top;
trans t1, t2, t3, t4;
`uvm_component_utils(object_create)
function new(string name = "object_create", uvm_component parent = null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
uvm_factory f = uvm_factory::get(); // get singleton factory
super.build_phase(phase);
t1 = new("t1"); // direct construction
t2 = trans::type_id::create("t2", this); // common method
void'($cast(t3,f.create_object_by_type(trans::get_type(), get_full_name(), "t3"))); // factory method
void'($cast(t4,create_object("trans", "t4"))); // pre-defined method inside component
endfunction
endclass

执行仿真命令

1
vsim -novopt -classdebug +UVM_TESTNAME=object_create work.factory_mechanism

输出结果

在这里插入图片描述

component实例创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class component_create extends top;
unit u1, u2, u3, u4;
`uvm_component_utils(component_create)
function new(string name = "component_create", uvm_component parent = null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
uvm_factory f = uvm_factory::get(); // get singleton factory
super.build_phase(phase);
u1 = new("u1"); // direct construction
u2 = unit::type_id::create("u2", this); // common method
void'($cast(u3,f.create_component_by_type(unit::get_type(), get_full_name(), "u3", this))); // factory method
void'($cast(u4,create_component("unit", "u4"))); // pre-defined method inside component
endfunction
endclass

仿真命令:

1
vsim -novopt -classdebug +UVM_TESTNAME=component_create work.factory_mechanism

输出结果:

在这里插入图片描述

object覆盖类型

1
2
3
4
5
6
7
8
9
10
class object_override extends object_create;
`uvm_component_utils(object_override)
function new(string name = "object_override", uvm_component parent = null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
set_type_override_by_type(trans::get_type(), bad_trans::get_type()); //bad_trans替换trans
super.build_phase(phase);
endfunction
endclass

仿真语句:

1
vsim -novopt -classdebug +UVM_TESTNAME=object_override work.factory_mechanism

结果:

在这里插入图片描述

问:trans类型不是被覆盖了吗,执行结果为什么还会创建trans?

答:badtrans的new方法中,首先执行了父类的new方法:执行步骤为:

  • object_override的build_phase先执行类型覆盖,此时trans的工厂实例创建方法全部变成bantrans来创建!!!
  • 执行父类object_create的的build_phase,进行trans(实际上是bad_trans)的工厂实例化
  • badtrans的工厂实例化实际上还是执行badtrans的new方法,此时首先调用父类的new方法,完成trans的实例创建

component覆盖类型

1
2
3
4
5
6
7
8
9
10
class component_override extends component_create;
`uvm_component_utils(component_override)
function new(string name = "component_override", uvm_component parent = null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
set_type_override("unit", "big_unit");
super.build_phase(phase);
endfunction
endclass

仿真结果:

在这里插入图片描述

和object的覆盖方式类似

1.2 域自动化及UVM常用方法

使用域自动化的宏方法

1
2
3
4
5
6
`uvm_object_utils_begin(trans)
`uvm_field_int(addr, UVM_ALL_ON) //域自动化的声明
`uvm_field_int(data, UVM_ALL_ON)
`uvm_field_enum(op_t, op, UVM_ALL_ON)
`uvm_field_string(name, UVM_ALL_ON)
`uvm_object_utils_end

uvm_object::compare()方法

比较t1t2是否相等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
trans t1, t2;
bit is_equal;
phase.raise_objection(this);
t1 = trans::type_id::create("t1");
t1.data = 'h1FF;
t1.addr = 'hF100;
t1.op = WRITE;
t1.name = "t1";
t2 = trans::type_id::create("t2");
t2.data = 'h2FF;
t2.addr = 'hF200;
t2.op = WRITE;
t2.name = "t2";
is_equal = t1.compare(t2);
uvm_default_comparer.show_max = 10;
is_equal = t1.compare(t2);

全局控制对象

uvm_default_comparer(uvm_comparer类型)是默认的UVM全局比较器,show_max可以设置最大比较结果。

1
uvm_default_comparer.show_max = 10;

此时可以将自动化域中的所有变量比较完,并通过回调函数do_compare打印错误信息

回调函数

执行compare()函数会自动调用do_compare()这个回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function bit do_compare(uvm_object rhs, uvm_comparer comparer);
trans t;
do_compare = 1;
void'($cast(t, rhs));
if(addr != t.addr) begin
do_compare = 0;
`uvm_warning("CMPERR", $sformatf("addr %8x != %8x", addr, t.addr))
end
if(data != t.data) begin
do_compare = 0;
`uvm_warning("CMPERR", $sformatf("data %8x != %8x", data, t.data))
end
if(op != t.op) begin
do_compare = 0;
`uvm_warning("CMPERR", $sformatf("op %s != %8x", op, t.op))
end
if(addr != t.addr) begin
do_compare = 0;
`uvm_warning("CMPERR", $sformatf("name %8x != %8x", name, t.name))
end
endfunction

uvm_object::print()方法及uvm_object_copy()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if(!is_equal)
`uvm_warning("CMPERR", "t1 is not equal to t2")
else
`uvm_info("CMPERR", "t1 is equal to t2", UVM_LOW)

`uvm_info("COPY", "Before uvm_object copy() taken", UVM_LOW)
t1.print();
t2.print();
`uvm_info("COPY", "After uvm_object t2 is copied to t1", UVM_LOW)
t1.copy(t2);
t1.print();
t2.print();
`uvm_info("CMP", "Compare t1 and t2", UVM_LOW)
is_equal = t1.compare(t2);
if(!is_equal)
`uvm_warning("CMPERR", "t1 is not equal to t2")
else
`uvm_info("CMPERR", "t1 is equal to t2", UVM_LOW)

执行命令:

1
vsim -novopt -classdebug +UVM_TESTNAME=object_methods_test work.object_methods

在这里插入图片描述

在这里插入图片描述

1.3 phase机制

phase机制使得验证环境从组建、连接、执行,得以分阶段执行,按照层次结构和phase顺序严格执行,继而避免一些依赖关系,也使得可以正确地将不同的代码放置到不同的phase块中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class comp2 extends uvm_component;
`uvm_component_utils(comp2)
function new(string name = "comp2", uvm_component parent = null);
super.new(name, parent);
`uvm_info("CREATE", $sformatf("unit type [%s] created", name), UVM_LOW)
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("BUILD", "comp2 build phase entered", UVM_LOW)
`uvm_info("BUILD", "comp2 build phase exited", UVM_LOW)
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
`uvm_info("CONNECT", "comp2 connect phase entered", UVM_LOW)
`uvm_info("CONNECT", "comp2 connect phase exited", UVM_LOW)
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
`uvm_info("RUN", "comp2 run phase entered", UVM_LOW)
`uvm_info("RUN", "comp2 run phase entered", UVM_LOW)
endtask
function void report_phase(uvm_phase phase);
super.report_phase(phase);
`uvm_info("REPORT", "comp2 report phase entered", UVM_LOW)
`uvm_info("REPORT", "comp2 report phase exited", UVM_LOW)
endfunction
endclass

comp3类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class comp3 extends uvm_component;
`uvm_component_utils(comp3)
function new(string name = "comp3", uvm_component parent = null);
super.new(name, parent);
`uvm_info("CREATE", $sformatf("unit type [%s] created", name), UVM_LOW)
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("BUILD", "comp3 build phase entered", UVM_LOW)
`uvm_info("BUILD", "comp3 build phase exited", UVM_LOW)
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
`uvm_info("CONNECT", "comp3 connect phase entered", UVM_LOW)
`uvm_info("CONNECT", "comp3 connect phase exited", UVM_LOW)
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
`uvm_info("RUN", "comp3 run phase entered", UVM_LOW)
`uvm_info("RUN", "comp3 run phase entered", UVM_LOW)
endtask
function void report_phase(uvm_phase phase);
super.report_phase(phase);
`uvm_info("REPORT", "comp3 report phase entered", UVM_LOW)
`uvm_info("REPORT", "comp3 report phase exited", UVM_LOW)
endfunction
endclass

comp1

comp1中声明了comp2comp3作为成员变量,并且在build_phase中做了例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class comp1 extends uvm_component;
comp2 c2;
comp3 c3;
`uvm_component_utils(comp1)
function new(string name = "comp1", uvm_component parent = null);
super.new(name, parent);
`uvm_info("CREATE", $sformatf("unit type [%s] created", name), UVM_LOW)
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("BUILD", "comp1 build phase entered", UVM_LOW)
c2 = comp2::type_id::create("c2", this);
c3 = comp3::type_id::create("c3", this);
`uvm_info("BUILD", "comp1 build phase exited", UVM_LOW)
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
`uvm_info("CONNECT", "comp1 connect phase entered", UVM_LOW)
`uvm_info("CONNECT", "comp1 connect phase exited", UVM_LOW)
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
`uvm_info("RUN", "comp1 run phase entered", UVM_LOW)
`uvm_info("RUN", "comp1 run phase entered", UVM_LOW)
endtask
function void report_phase(uvm_phase phase);
super.report_phase(phase);
`uvm_info("REPORT", "comp1 report phase entered", UVM_LOW)
`uvm_info("REPORT", "comp1 report phase exited", UVM_LOW)
endfunction
endclass

phase_order_test

创建验证结构,uvm_test一定是顶层结构,而uvm_test的引擎是uvm_rootphase_order_test中包含了comp1 c1,并且在build_phase中也创建了c1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class phase_order_test extends uvm_test;
comp1 c1;
`uvm_component_utils(phase_order_test)
function new(string name = "phase_order_test", uvm_component parent = null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("BUILD", "phase_order_test build phase entered", UVM_LOW)
c1 = comp1::type_id::create("c1", this);
`uvm_info("BUILD", "phase_order_test build phase exited", UVM_LOW)
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
`uvm_info("CONNECT", "phase_order_test connect phase entered", UVM_LOW)
`uvm_info("CONNECT", "phase_order_test connect phase exited", UVM_LOW)
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
`uvm_info("RUN", "phase_order_test run phase entered", UVM_LOW)
phase.raise_objection(this);
#1us;
phase.drop_objection(this);
`uvm_info("RUN", "phase_order_test run phase exited", UVM_LOW)
endtask
function void report_phase(uvm_phase phase);
super.report_phase(phase);
`uvm_info("REPORT", "phase_order_test report phase entered", UVM_LOW)
`uvm_info("REPORT", "phase_order_test report phase exited", UVM_LOW)
endfunction

task reset_phase(uvm_phase phase);
`uvm_info("RESET", "phase_order_test reset phase entered", UVM_LOW)
phase.raise_objection(this);
#1us;
phase.drop_objection(this);
`uvm_info("RESET", "phase_order_test reset phase exited", UVM_LOW)
endtask

task main_phase(uvm_phase phase);
`uvm_info("MAIN", "phase_order_test main phase entered", UVM_LOW)
phase.raise_objection(this);
#1us;
phase.drop_objection(this);
`uvm_info("MAIN", "phase_order_test main phase exited", UVM_LOW)
endtask
endclass

执行命令

1
vsim -novopt -classdebug +UVM_TESTNAME=phase_order_test work.phase_order

仿真结果

在这里插入图片描述

在这里插入图片描述

总结:

  • comp2和comp3和同级,comp1中实例化了comp2和comp3,最顶层的test类实例化了comp1
  • build_phase:test-c1-c2-c3
  • connect_phase:c2-c3-c1-test
  • run_phase:c2-c3-c1-test(test挂起,进入c1-c2-c3,但是comp组件的run-phase组件都没挂起,直接退出)
  • main_phase:test
  • report_phase: c2-c3-c1-test

build_phaseconnect_phaserun_phasereport_phase都是按照自顶向下或者自底向上的顺序依次执行的。

run_phase是9种phase唯一的task,与run_phase平行执行的是12个分支phase,所以在0时刻,run_phasereset_phase是并行的,但是要等到reset_phase在执行完成之后,run_phase才能执行完。继而进入下一个extract_phase阶段。reset_phasemain_phase之间却是有先后顺序的,先执行reset_phase在1000时刻结束,然后main_phase才开始执行,在2000时刻结束。所以整个仿真最后耗时Time=2us。

1.4 config机制

实现接口从uvm_config模块到验证环境中的传递,使得c1c2可以得到接口,并且检查接口是否最终得到。

接口uvm_config_if和对象uvm_config

1
2
3
4
5
interface uvm_config_if;
logic [31:0] addr;
logic [31:0] data;
logic [ 1:0] op;
endinterface
1
2
3
4
5
6
7
8
9
class config_obj extends uvm_object;
int comp1_var;
int comp2_var;
`uvm_object_utils(config_obj)
function new(string name = "config_obj");
super.new(name);
`uvm_info("CREATE", $sformatf("config_obj type [%s] created", name), UVM_LOW)
endfunction
endclass

实现配置对象config_objuvm_config_testc1c2的传递。

底层comp2和次底层comp1类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class comp2 extends uvm_component;
int var2;
virtual uvm_config_if vif;
config_obj cfg;
`uvm_component_utils(comp2)
function new(string name = "comp2", uvm_component parent = null);
super.new(name, parent);
var2 = 200;
`uvm_info("CREATE", $sformatf("unit type [%s] created", name), UVM_LOW)
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("BUILD", "comp2 build phase entered", UVM_LOW)
if(!uvm_config_db#(virtual uvm_config_if)::get(this, "", "vif", vif))
`uvm_error("GETVIF", "no virtual interface is assigned")

`uvm_info("GETINT", $sformatf("before config get, var2 = %0d", var2), UVM_LOW)
uvm_config_db#(int)::get(this, "", "var2", var2);
`uvm_info("GETINT", $sformatf("after config get, var2 = %0d", var2), UVM_LOW)

uvm_config_db#(config_obj)::get(this, "", "cfg", cfg);
`uvm_info("GETOBJ", $sformatf("after config get, cfg.comp2_var = %0d", cfg.comp2_var), UVM_LOW)

`uvm_info("BUILD", "comp2 build phase exited", UVM_LOW)
endfunction
endclass

comp1类,在comp1中包含一个comp2对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class comp1 extends uvm_component;
int var1;
comp2 c2;
config_obj cfg;
virtual uvm_config_if vif;
`uvm_component_utils(comp1)
function new(string name = "comp1", uvm_component parent = null);
super.new(name, parent);
var1 = 100;
`uvm_info("CREATE", $sformatf("unit type [%s] created", name), UVM_LOW)
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("BUILD", "comp1 build phase entered", UVM_LOW)
if(!uvm_config_db#(virtual uvm_config_if)::get(this, "", "vif", vif))
`uvm_error("GETVIF", "no virtual interface is assigned")

`uvm_info("GETINT", $sformatf("before config get, var1 = %0d", var1), UVM_LOW)
uvm_config_db#(int)::get(this, "", "var1", var1);
`uvm_info("GETINT", $sformatf("after config get, var1 = %0d", var1), UVM_LOW)

uvm_config_db#(config_obj)::get(this, "", "cfg", cfg);
`uvm_info("GETOBJ", $sformatf("after config get, cfg.comp1_var = %0d", cfg.comp1_var), UVM_LOW)

c2 = comp2::type_id::create("c2", this);
`uvm_info("BUILD", "comp1 build phase exited", UVM_LOW)
endfunction
endclass

顶层test类

uvm_config模块,顶层test模块,实现所有object及单一变量的传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class uvm_config_test extends uvm_test;
comp1 c1;
config_obj cfg;
`uvm_component_utils(uvm_config_test)
function new(string name = "uvm_config_test", uvm_component parent = null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("BUILD", "uvm_config_test build phase entered", UVM_LOW)

cfg = config_obj::type_id::create("cfg");
cfg.comp1_var = 100;
cfg.comp2_var = 200;
uvm_config_db#(config_obj)::set(this, "*", "cfg", cfg); //将config_obj通过config_db传递给c1和c2

uvm_config_db#(int)::set(this, "c1", "var1", 10); //c1.var1和c2.var2的变量设置。
uvm_config_db#(int)::set(this, "c1.c2", "var2", 20);

c1 = comp1::type_id::create("c1", this);
`uvm_info("BUILD", "uvm_config_test build phase exited", UVM_LOW)
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
`uvm_info("RUN", "uvm_config_test run phase entered", UVM_LOW)
phase.raise_objection(this);
#1us;
phase.drop_objection(this);
`uvm_info("RUN", "uvm_config_test run phase exited", UVM_LOW)
endtask
endclass

uvm_config模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module uvm_config;

import uvm_pkg::*;
`include "uvm_macros.svh"
import uvm_config_pkg::*;

uvm_config_if if0(); //在module模块才可以看得到interface,实现例化

initial begin
//在验证环境中设置interface到c1、c2
uvm_config_db#(virtual uvm_config_if)::set(uvm_root::get(), "uvm_test_top.*", "vif", if0);
run_test(""); // empty test name
end

endmodule

总结

在这里插入图片描述

  • 接口都是在initial块中就set到config_db中,object和单一变脸都是在test中进行set(注意test类15行中*的用法)

  • 对于调用get方法获取接口或对象的类,其实例化必须发生在config_db的set之后

  • 注意传递interface时参数的固定写法!!! 通过.*可以同时代表comp1和comp2两个类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    uvm_config_db#(virtual uvm_config_if)::set(uvm_root::get(), "uvm_test_top.*", "vif", if0);
    run_test(""); // empty test name

    uvm_config_db#(config_obj)::set(this, "*", "cfg", cfg); //将config_obj通过config_db传递给c1和c2

    uvm_config_db#(int)::set(this, "c1", "var1", 10); //c1.var1和c2.var2的变量设置。
    uvm_config_db#(int)::set(this, "c1.c2", "var2", 20);

    c1 = comp1::type_id::create("c1", this);

执行仿真命令

1
vsim -novopt -classdebug +UVM_TESTNAME=uvm_config_test work.uvm_config

仿真结果

在这里插入图片描述

1.5 message管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("BUILD", "uvm_message_test build phase entered", UVM_LOW)
cfg = config_obj::type_id::create("cfg");
c1 = comp1::type_id::create("c1", this);
c2 = comp2::type_id::create("c2", this);
// set_report_verbosity_level_hier(UVM_NONE);

//TODO-5.2
//Use set_report_id_verbosity_level_hier() to disable all of
//"CREATE", "BUILD", "RUN" ID message under the uvm_message_test
// Think why message "CREATE" could not be disabled ?
//set_report_id_verbosity_hier("BUILD", UVM_NONE);
//set_report_id_verbosity_hier("CREATE", UVM_NONE);
//set_report_id_verbosity_hier("RUN", UVM_NONE);

//TODO-5.3
//Why the UVM message from config_obj type and uvm_message module
//could not be disabled? Please use the message filter methods
//to disable them
// uvm_root::get().set_report_id_verbosity_hier("CREATE", UVM_NONE);
// uvm_root::get().set_report_id_verbosity_hier("BUILD", UVM_NONE);
// uvm_root::get().set_report_id_verbosity_hier("RUN", UVM_NONE);
`uvm_info("BUILD", "uvm_message_test build phase exited", UVM_LOW)
endfunction

initial begin
//TODO-5.3
//Why the UVM message from config_obj type and uvm_message module
//could not be disabled? Please use the message filter methods
//to disable them
// uvm_root::get().set_report_id_verbosity_hier("TOPTB", UVM_NONE);
`uvm_info("TOPTB", "RUN TEST entered", UVM_LOW)
run_test(""); // empty test name
`uvm_info("TOPTB", "RUN TEST exited", UVM_LOW)
end

无错时的打印信息:

image-20210726100216554

5.1.使用冗余度进行过滤的方法set_report_verbosity_level_hier()

uvm_message_test::build_phase()中屏蔽所有层次的消息,也就是不允许有任何uvm_message_test及其以下组件的消息在仿真时打印出来。

1
set_report_verbosity_level_hier(UVM_NONE);

执行命令:

1
vsim -novopt -classdebug +UVM_TESTNAME=uvm_message_test work.uvm_message

结果:

很明显,只有设置冗余度之前的消息代码进行了执行

在这里插入图片描述

5.2 使用id+冗余度进行过滤:set_report_id_verbosity_hier(“CREATE”, UVM_NONE)

1
2
3
uvm_root::get().set_report_id_verbosity_hier("CREATE", UVM_NONE);
uvm_root::get().set_report_id_verbosity_hier("BUILD", UVM_NONE);
uvm_root::get().set_report_id_verbosity_hier("RUN", UVM_NONE);

结果:

在这里插入图片描述

5.3 在指定的phase中添加过滤器

end_of_elaboration_phase中使用set_report_id_verbosity_level_hier()来过滤ID的消息,可以使得uvm_message_test的[BUILD]顺利执行完,继而执行完c1c2的[BUILD]。

1
2
3
4
5
function void end_of_elaboration_phase(uvm_phase phase);
set_report_id_verbosity_hier("BUILD", UVM_NONE);
set_report_id_verbosity_hier("CREATE", UVM_NONE);
set_report_id_verbosity_hier("RUN", UVM_NONE);
endfunction

仿真结果:
在这里插入图片描述

5.4 如何屏蔽最顶层initial块中的info:

所有的打印消息,无论屏蔽与否,都会将config_obj的[CREATE]消息以及uvm_message模块里面的[TOPTB]消息打印出来。

可以使用消息过滤方法uvm_root::get()来获取最顶层的(即uvm_message_test的顶层),来控制过滤[CREATE]、[TOPTB]消息。

1
2
3
4
5
6
initial begin
uvm_root::get().set_report_id_verbosity_hier("TOPTB", UVM_NONE);
`uvm_info("TOPTB", "RUN TEST entered", UVM_LOW)
run_test(""); // empty test name
`uvm_info("TOPTB", "RUN TEST exited", UVM_LOW)
end

在这里插入图片描述

总结

过滤前执行的打印信息照常打印,过滤后执行的打印信息受过滤器管控

UVM-实验2

1.1 验证组件和层次构建

首先将各个package中的SV组件替换为UVM组件

实现组件对应原则:

  • SV的transaction类对应uvm_sequence_item
  • SV的driver类对应uvm_driver
  • SV的generator类对应uvm_sequence + uvm_sequencer
  • SV的monitor对应uvm_monitor
  • SV的agent对应uvm_agent
  • SV的env对应uvm_env
  • SV的checker对应uvm_scoreboard
  • SV的reference model和coverage model均对应uvm_component
  • SV的test对应uvm_test

在遵循上面的对应原则的过程中,在进行类的转换时,需要注意:

  • SV的上述类均需要继承于其对应的UVM类

  • 在类定义过程中,一定需要使用'uvm_component_utils()或者'uvm_object_utils()完成类的注册。

  • 在使用上述工厂注册宏的时候,会伴随着“域声明自动化”,一般而言,将sequence item类定义时,应当伴随着域声明,即利用'uvm_object_utils_begin

    'uvm_object_utils_end完成。这是由于对于sequence item对象的拷贝、比较和打印等操作比较多,因此在定义sequence item类时,最好需要完成域的自动化声明。

  • 一定要注意构建函数new()的声明方式,uvm_component的构建函数有两个参数new(string name, uvm_component parent),而uvm_object的构建函数只有一个参数new(string name)

  • 在组件之间的层次关系构建中,依然按照之前SV组件的层次关系,只需要在不同的phase阶段完成组件的例化和连接。

1.2 UVM文件结构

chnl_trans

相比于SV验证模块代码,成员变量没有发生什么变化,但是省略掉了clone()sprint()这两个方法,因为UVM做了类的注册以及域的自动化的声明,可以使用UVM核心基类的克隆、打印、比较等一些常见方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class chnl_trans extends uvm_sequence_item;
rand bit[31:0] data[];
rand int ch_id;
rand int pkt_id;
rand int data_nidles;
rand int pkt_nidles;
bit rsp;

constraint cstr{
soft data.size inside {[4:32]};
foreach(data[i]) data[i] == 'hC000_0000 + (this.ch_id<<24) + (this.pkt_id<<8) + i;
soft ch_id == 0;
soft pkt_id == 0;
soft data_nidles inside {[0:2]};
soft pkt_nidles inside {[1:10]};
};

`uvm_object_utils_begin(chnl_trans) //注册的同时,进行域自动化声明
`uvm_field_array_int(data, UVM_ALL_ON)
`uvm_field_int(ch_id, UVM_ALL_ON)
`uvm_field_int(pkt_id, UVM_ALL_ON)
`uvm_field_int(data_nidles, UVM_ALL_ON)
`uvm_field_int(pkt_nidles, UVM_ALL_ON)
`uvm_field_int(rsp, UVM_ALL_ON)
`uvm_object_utils_end

function new (string name = "chnl_trans");
super.new(name);
endfunction
endclass: chnl_trans

chnl_driver

  • 继承于uvm_driver是一个参数类,所以得uvm_driver #(chnl_trans)。而SV中的run()转换成了UVM中run_phase(uvm_phase phase)
  • do_driver()方法里面void'($cast(rsp, req.clone())),是因为UVM核心基类的克隆方法返回的是uvm_object类型,所以需要把父类的句柄转换为子类的句柄,而req.clone()这个父类句柄指向的是一个子类的对象,所以能转换成功。
  • chnl_write()方法中把SV中的$display替换成了UVM中的'uvm_info()
  • 全部的打印方法都由UVM的宏来完成!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 
class chnl_driver extends uvm_driver #(chnl_trans);
local virtual chnl_intf intf;
mailbox #(chnl_trans) req_mb;
mailbox #(chnl_trans) rsp_mb;

`uvm_component_utils(chnl_driver)

function new (string name = "chnl_driver", uvm_component parent);
super.new(name, parent);
endfunction

function void set_interface(virtual chnl_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction

task run_phase(uvm_phase phase);
fork
this.do_drive();
this.do_reset();
join
endtask

task do_reset();
forever begin
@(negedge intf.rstn);
intf.ch_valid <= 0;
intf.ch_data <= 0;
end
endtask

task do_drive();
chnl_trans req, rsp;
@(posedge intf.rstn);
forever begin
this.req_mb.get(req);
this.chnl_write(req);
void'($cast(rsp, req.clone()));
rsp.rsp = 1;
this.rsp_mb.put(rsp);
end
endtask

task chnl_write(input chnl_trans t);
foreach(t.data[i]) begin
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 1;
intf.drv_ck.ch_data <= t.data[i];
@(negedge intf.clk);
wait(intf.ch_ready === 'b1);
`uvm_info(get_type_name(), $sformatf("sent data 'h%8x", t.data[i]), UVM_HIGH)
repeat(t.data_nidles) chnl_idle();
end
repeat(t.pkt_nidles) chnl_idle();
endtask

task chnl_idle();
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 0;
intf.drv_ck.ch_data <= 0;
endtask
endclass: chnl_driver

chnl_generator

  • 相比于SV使用构建函数new()来创建对象, send_trans()方法中使用req = chnl_trans::type_id::create("req")来创建对象。
  • 打印消息使用了UVM中的'uvm_info()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// channel generator and to be replaced by sequence + sequencer later
class chnl_generator extends uvm_component;
rand int pkt_id = 0;
rand int ch_id = -1;
rand int data_nidles = -1;
rand int pkt_nidles = -1;
rand int data_size = -1;
rand int ntrans = 10;

mailbox #(chnl_trans) req_mb;
mailbox #(chnl_trans) rsp_mb;

constraint cstr{
soft ch_id == -1;
soft pkt_id == 0;
soft data_size == -1;
soft data_nidles == -1;
soft pkt_nidles == -1;
soft ntrans == 10;
}

`uvm_component_utils_begin(chnl_generator)
`uvm_field_int(pkt_id, UVM_ALL_ON)
`uvm_field_int(ch_id, UVM_ALL_ON)
`uvm_field_int(data_nidles, UVM_ALL_ON)
`uvm_field_int(pkt_nidles, UVM_ALL_ON)
`uvm_field_int(data_size, UVM_ALL_ON)
`uvm_field_int(ntrans, UVM_ALL_ON)
`uvm_component_utils_end

function new (string name = "chnl_generator", uvm_component parent);
super.new(name, parent);
this.req_mb = new();
this.rsp_mb = new();
endfunction

task start();
repeat(ntrans) send_trans();
endtask

task send_trans();
chnl_trans req, rsp;
req = chnl_trans::type_id::create("req");;
assert(req.randomize with {local::ch_id >= 0 -> ch_id == local::ch_id;
local::pkt_id >= 0 -> pkt_id == local::pkt_id;
local::data_nidles >= 0 -> data_nidles == local::data_nidles;
local::pkt_nidles >= 0 -> pkt_nidles == local::pkt_nidles;
local::data_size >0 -> data.size() == local::data_size;
})
else $fatal("[RNDFAIL] channel packet randomization failure!");
this.pkt_id++;
`uvm_info(get_type_name(), req.sprint(), UVM_HIGH)
this.req_mb.put(req);
this.rsp_mb.get(rsp);
`uvm_info(get_type_name(), rsp.sprint(), UVM_HIGH)
assert(rsp.rsp)
else $error("[RSPERR] %0t error response received!", $time);
endtask

function string sprint();
string s;
s = {s, $sformatf("=======================================\n")};
s = {s, $sformatf("chnl_generator object content is as below: \n")};
s = {s, super.sprint()};
// NOTE:: field automation already implemented clone() method
// s = {s, $sformatf("ntrans = %0d: \n", this.ntrans)};
// s = {s, $sformatf("ch_id = %0d: \n", this.ch_id)};
// s = {s, $sformatf("pkt_id = %0d: \n", this.pkt_id)};
// s = {s, $sformatf("data_nidles = %0d: \n", this.data_nidles)};
// s = {s, $sformatf("pkt_nidles = %0d: \n", this.pkt_nidles)};
// s = {s, $sformatf("data_size = %0d: \n", this.data_size)};
s = {s, $sformatf("=======================================\n")};
return s;
endfunction

function void post_randomize();
string s;
s = {"AFTER RANDOMIZATION \n", this.sprint()};
`uvm_info(get_type_name(), s, UVM_HIGH)
endfunction
endclass: chnl_generator

chnl_monitor

将SV中的run()替换成了UVM中的
run_phase(uvm_phase phase)以及使用UVM中的'uvm_info()打印消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// channel monitor
class chnl_monitor extends uvm_monitor;
local virtual chnl_intf intf;
mailbox #(mon_data_t) mon_mb;

`uvm_component_utils(chnl_monitor)

function new(string name="chnl_monitor", uvm_component parent);
super.new(name, parent);
endfunction

function void set_interface(virtual chnl_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction

task run_phase(uvm_phase phase);
this.mon_trans();
endtask

task mon_trans();
mon_data_t m;
forever begin
@(posedge intf.clk iff (intf.mon_ck.ch_valid==='b1 && intf.mon_ck.ch_ready==='b1));
m.data = intf.mon_ck.ch_data;
mon_mb.put(m);
`uvm_info(get_type_name(), $sformatf("monitored channel data 'h%8x", m.data), UVM_HIGH)
end
endtask
endclass: chnl_monitor

chnl_agent类

  • SV验证结构中例化和连接都发生在构建函数new()里面,而UVM中例化是在build_phase()方法中,并且通过create()来例化创建对象。
  • SV验证结构中run()需要调用子一级的run()方法,而在UVM中不需要手动去调用子一级的run_phase(),因为run_phase是按照层次来执行的,是由uvm_root来安排的,会自动调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// channel agent
class chnl_agent extends uvm_agent;
chnl_driver driver;
chnl_monitor monitor;
local virtual chnl_intf vif;

`uvm_component_utils(chnl_agent)

function new(string name = "chnl_agent", uvm_component parent);
super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
driver = chnl_driver::type_id::create("driver", this);
monitor = chnl_monitor::type_id::create("monitor", this);
endfunction

function void set_interface(virtual chnl_intf vif);
this.vif = vif;
driver.set_interface(vif);
monitor.set_interface(vif);
endfunction

task run_phase(uvm_phase phase);
// NOTE:: No more needed to call run manually
// fork
// driver.run();
// monitor.run();
// join
endtask
endclass: chnl_agent

reg_pkg.sv文件、fmt_pkg.sv文件、mcdf_pkg.sv文件修改的地方相似。

1.3 mcdf

测试的开始和结束

UVM验证环境测试的开始、环境构建的过程、连接以及结束的控制。

tb.sv

通过uvm_config_db完成了各个接口从TB(硬件一侧)到验证环境mcdf_env(软件一侧)的传递。实现了以往SV函数的剥离,即UVM不需要深入到目标组件一侧,调用其set_interface()即可完成传递。这种传递方式有赖于config_db的数据存储和层次传递特性。而在mcdf_env中,暂时保留了mcdf_env的set_interface()以及各个子组件的set_interface()函数。仅修改了TB与mcdf_env之间的接口传递,其实可以移除所有的set_interface()函数,完全使用uvm_config_db的set和get方法,从而使得mcdf_env与其各个子组件之间也实现“层次剥离”,这样也就进一步促进了组件之间的独立性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
initial begin 
// do interface configuration from top tb (HW) to verification env (SW)
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top", "ch0_vif", chnl0_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top", "ch1_vif", chnl1_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top", "ch2_vif", chnl2_if);
uvm_config_db#(virtual reg_intf)::set(uvm_root::get(), "uvm_test_top", "reg_vif", reg_if);
uvm_config_db#(virtual arb_intf)::set(uvm_root::get(), "uvm_test_top", "arb_vif", arb_if);
uvm_config_db#(virtual fmt_intf)::set(uvm_root::get(), "uvm_test_top", "fmt_vif", fmt_if);
uvm_config_db#(virtual mcdf_intf)::set(uvm_root::get(), "uvm_test_top", "mcdf_vif", mcdf_if);

// If no external configured via +UVM_TESTNAME=my_test, the default test is
// mcdf_data_consistence_basic_test
run_test("mcdf_data_consistence_basic_test");

end
endmodule

通过调用run_test()函数即完成了test的选择、例化和开始测试。可以在代码中指定UVM test,或者通过 +UVM_TESTNAME=mytest在仿真选项中灵活传递test名。在run_test()执行中,它会初始化objection机制,即查看objection有没有挂起的地方,因此在test或者generator中必须至少有一处地方使用phase.raise_objection()来挂起仿真,避免仿真退出,而在仿真需要结束时,使用phase.drop_objection()来允许仿真可以退出。同时run_test()可以创建uvm_test组件,及其以下的各层组件群,并且可以调用phase控制方法,按照所有phase顺序执行。在UVM中,将对象的例化放置在build_phase中,而将对象的连接放置在connect_phase中。

UVM-实验3

问:为啥有时候用tlm端口,有时候用tlm通信管道,这两者在执行效率上有什么差距吗

TLM通信的引入

image-20210727205241147

1. TLM单向通信和多向通信

在之前的monitor到checker的通信,以及checker与reference model之间的通信,都是通过mailbox以及在上层进行其句柄的传递实现的。这次实验则使用TLM端口进行通信,做逐步的通信元素和方法的替换。

agent

agent.monitor中的用来与checker中的mailbox通信的mon_mb句柄替换为对应的uvm_blocking_put_port类型。

以chnl的mailbox为例

1
2
3
uvm_blocking_put_port #(mon_data_t) mon_bp_port;
//实例化
mon_bp_port = new("mon_bp_port", this);

mcdf_pkg

在checker中声明与monitor通信的imp端口类型,以及reference model通信的imp端口类型,由于checker与多个monitor以及reference model通信,是典型的多方向通信类型,因此需要使用多端口通信的宏声明方法。在使用了宏声明端口类型之后,再在checker中声明其句柄,并且完成例化。

  • 3个monitor(port)
  • 1个reg(port)
  • 1个fmt(port)
  • 3个refmod的chnl(port)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 //宏声明端口
`uvm_blocking_put_imp_decl(_chnl0)
`uvm_blocking_put_imp_decl(_chnl1)
`uvm_blocking_put_imp_decl(_chnl2)
`uvm_blocking_put_imp_decl(_fmt)
`uvm_blocking_put_imp_decl(_reg)

`uvm_blocking_get_peek_imp_decl(_chnl0)
`uvm_blocking_get_peek_imp_decl(_chnl1)
`uvm_blocking_get_peek_imp_decl(_chnl2)

`uvm_blocking_get_imp_decl(_reg)

//声明句柄
uvm_blocking_put_imp_chnl0 #(mon_data_t, mcdf_checker) chnl0_bp_imp;
uvm_blocking_put_imp_chnl1 #(mon_data_t, mcdf_checker) chnl1_bp_imp;
uvm_blocking_put_imp_chnl2 #(mon_data_t, mcdf_checker) chnl2_bp_imp;
uvm_blocking_put_imp_fmt #(fmt_trans , mcdf_checker) fmt_bp_imp ;
uvm_blocking_put_imp_reg #(reg_trans , mcdf_checker) reg_bp_imp ;

uvm_blocking_get_peek_imp_chnl0 #(mon_data_t, mcdf_checker) chnl0_bgpk_imp;
uvm_blocking_get_peek_imp_chnl1 #(mon_data_t, mcdf_checker) chnl1_bgpk_imp;
uvm_blocking_get_peek_imp_chnl2 #(mon_data_t, mcdf_checker) chnl2_bgpk_imp;

uvm_blocking_get_imp_reg #(reg_trans , mcdf_checker) reg_bg_imp ;


//例化
chnl0_bp_imp = new("chnl0_bp_imp", this);
chnl1_bp_imp = new("chnl1_bp_imp", this);
chnl2_bp_imp = new("chnl2_bp_imp", this);
fmt_bp_imp = new("fmt_bp_imp", this);
reg_bp_imp = new("reg_bp_imp", this);

chnl0_bgpk_imp = new("chnl0_bgpk_imp", this);
chnl1_bgpk_imp = new("chnl1_bgpk_imp", this);
chnl2_bgpk_imp = new("chnl2_bgpk_imp", this);

reg_bg_imp = new("reg_bg_imp", this);

根据声明的import端口类型,实现其对应的方法。(同样依赖mailbox作为)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//实现put方法
task put_chnl0(mon_data_t t);
chnl_mbs[0].put(t);
endtask
task put_chnl1(mon_data_t t);
chnl_mbs[1].put(t);
endtask
task put_chnl2(mon_data_t t);
chnl_mbs[2].put(t);
endtask
task put_fmt(fmt_trans t);
fmt_mb.put(t);
endtask
task put_reg(reg_trans t);
reg_mb.put(t);
endtask

//实现get、peek方法
task peek_chnl0(output mon_data_t t);
chnl_mbs[0].peek(t);
endtask
task peek_chnl1(output mon_data_t t);
chnl_mbs[1].peek(t);
endtask
task peek_chnl2(output mon_data_t t);
chnl_mbs[2].peek(t);
endtask
task get_chnl0(output mon_data_t t);
chnl_mbs[0].get(t);
endtask
task get_chnl1(output mon_data_t t);
chnl_mbs[1].get(t);
endtask
task get_chnl2(output mon_data_t t);
chnl_mbs[2].get(t);
endtask
task get_reg(output reg_trans t);
reg_mb.get(t);
endtask

mcdf_refmod中声明用来与mcdf_checker中的`import连接的端口,并且完成例化。完成声明和例化后,使用TLM端口呼叫方法。

1
2
3
4
5
6
//声明端口
uvm_blocking_get_port #(reg_trans) reg_bg_port;
uvm_blocking_get_peek_port #(mon_data_t) in_bgpk_ports[3];

//端口例化
reg_bg_port = new("reg_bg_port", this);

mcdf_envconnect_phase()阶段,完成monitor的TLM port与mcdf_checker的TLM import的连接。

1
2
3
4
5
chnl_agts[0].monitor.mon_bp_port.connect(chker.chnl0_bp_imp);
chnl_agts[1].monitor.mon_bp_port.connect(chker.chnl1_bp_imp);
chnl_agts[2].monitor.mon_bp_port.connect(chker.chnl2_bp_imp);
reg_agt.monitor.mon_bp_port.connect(chker.reg_bp_imp);
fmt_agt.monitor.mon_bp_port.connect(chker.fmt_bp_imp);

mcdf_checker的connect_phase()阶段,完成refmod的TLM port与mcdf_checker`的TLM import的连接。

1
2
3
4
5
refmod.in_bgpk_ports[0].connect(chnl0_bgpk_imp);
refmod.in_bgpk_ports[1].connect(chnl1_bgpk_imp);
refmod.in_bgpk_ports[2].connect(chnl2_bgpk_imp);

refmod.reg_bg_port.connect(reg_bg_imp);

refmod中端口的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
task do_reg_update();
reg_trans t;
forever begin
//TODO-1.4 replace the reg_mb with the TLM port
this.reg_bg_port.get(t);
if(t.addr[7:4] == 0 && t.cmd == `WRITE) begin
this.regs[t.addr[3:2]].en = t.data[0];
this.regs[t.addr[3:2]].prio = t.data[2:1];
this.regs[t.addr[3:2]].len = t.data[5:3];
end
else if(t.addr[7:4] == 1 && t.cmd == `READ) begin
this.regs[t.addr[3:2]].avail = t.data[7:0];
end
end
endtask

task do_packet(int id);
fmt_trans ot;
mon_data_t it;
forever begin
//TODO-1.4 replace the in_mbs with the TLM ports
this.in_bgpk_ports[id].peek(it);
ot = new();
ot.length = 4 << (this.get_field_value(id, RW_LEN) & 'b11);
ot.data = new[ot.length];
ot.ch_id = id;
foreach(ot.data[m]) begin
//TODO-1.4 replace the in_mbs with the TLM ports
this.in_bgpk_ports[id].get(it);
ot.data[m] = it.data;
end
//TODO-2.1 replace the out_mbs[3] with uvm_tlm_fifo type
this.out_tlm_fifos[id].put(ot);
end
endtask

2. TLM通信管道

TLM通信的优点:

  • 通信函数可以定制化,例如可以定制put()/get()/peek()的内容和参数,这比mailbox的通信更加灵活。
  • 将组件实现了完全的隔离,通过层次化的TLM端口连接,可以很好地避免直接将不同层次的数据缓存对象的句柄进行“空传递”。

如果使用TLM端口,并且不用实现具体的put()/get()/peek()方法,可以使用uvm_tlm_fifo类。

将原本在mcdf_refmod中的out_mb替换成uvm_tlm_fifo类型,并且完成例化以及对应的变量名替换。

1
2
3
4
uvm_tlm_fifo #(fmt_trans) out_tlm_fifos[3];

//例化
foreach(out_tlm_fifos[i]) out_tlm_fifos[i] = new($sformatf("out_tlm_fifos[%0d]", i), this);

将原本在mcdf_checker中的exp_mbs[3]的邮箱句柄数组,替换为uvm_blocking_get_port类型句柄数组,并且做相应的例化以及变量名替换。

1
2
3
uvm_blocking_get_port #(fmt_trans) exp_bg_ports[3];
//实例化
foreach(exp_bg_ports[i]) exp_bg_ports[i] = new($sformatf("exp_bg_ports[%0d]", i), this);

mcdf_checker中,完成mcdf_checker中的TLM port端口到mcdf_refmod中的uvm_tlm_fifo自带的blocking_get_export端口的连接。

1
2
3
foreach(exp_bg_ports[i]) begin
exp_bg_ports[i].connect(refmod.out_tlm_fifos[i].blocking_get_export);
end

3. UVM回调类

逻辑:

原先mcdf_base_test类被子类mcdf_data_consistence_basic_test、mcdf_full_random_test类继承,不同的类激励生成的方式不同,依此来尽可能的完成覆盖率收集

增加回调类:

  • 相当于把产生激励的几个方法(do_data(),do_reg(),do_fmt())抽离出来,放入回调类

  • 在test类中进行绑定,测试类mcdf_base_test和回调类cb_mcdf_base

  • 测试类中的这几个方法,要插入回调类的相应方法,也就是执行回调方法!!!

    此时测试类mcdf_base_test和回调类cb_mcdf_base 都没有实现上述方法,同样需要回调类的子类实现

    值得注意的是:对测试类中用来执行的方法(run_phase中的do_data(),do_reg(),do_fmt()等),或者说要插入回调类的方法,必须要声明为虚方法,因为所有的子测试类虽然实际上都是在调用base_test类的run_phase,但执行的还得是其本身插入的回调类相应方法!!!

    这也是为啥在cb_mcdf_data_consistence_basic_test中,是父类的base_test类添加子类的回调类cb_mcdf_data_consistence_basic

    1
    2
    cb = cb_mcdf_data_consistence_basic::type_id::create("cb");		//创建回调函数
    uvm_callbacks#(mcdf_base_test)::add(this, cb); //添加回调函数
  • 测试类的子类mcdf_data_consistence_basic_test、mcdf_full_random_test类都可以定义对应的回调类(继承自base_test的回调类cb_mcdf_base ),实现(do_data(),do_reg(),do_fmt())

  • 此时的测试类就只需要把对应的回调类添加一下就好了,别的都不用管

mcdf_data_consistence_basic_test的执行路径:

  • 执行build_phase,添加自己的回调类
  • 本身没有run_phase,执行父类base_test的run_phase,父类run_phase中的方法(do_data(),do_reg(),do_fmt())又会找到回调类中的相应方法执行

在uvm_callback类中,预先定义需要的虚方法。(定义)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef class mcdf_base_test;

class cb_mcdf_base extends uvm_callback;
`uvm_object_utils(cb_mcdf_base)
mcdf_base_test test;
function new (string name = "cb_mcdf_base");
super.new(name);
endfunction

virtual task cb_do_reg();

endtask

virtual task cb_do_formatter();

endtask

virtual task cb_do_data();

endtask
endclass

使用callback对应的宏,完成目标uvm_test类型与目标uvm_callback类型的关联。(绑定)

1
`uvm_register_cb(mcdf_base_test, cb_mcdf_base)

在目标uvm_test类型指定的方法中,完成uvm_callback的方法回调指定。(插入)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// do register configuration
virtual task do_reg();
`uvm_do_callbacks(mcdf_base_test, cb_mcdf_base, cb_do_reg())
endtask

// do external formatter down stream slave configuration
virtual task do_formatter();
`uvm_do_callbacks(mcdf_base_test, cb_mcdf_base, cb_do_formatter())
endtask

// do data transition from 3 channel slaves
virtual task do_data();
`uvm_do_callbacks(mcdf_base_test, cb_mcdf_base, cb_do_data())
endtask

完整的mcdf_base_test类:

  • 17行:该类和回调类的绑定
  • 97,103,109:插入回调方法,也就是test类的方法执行时其实是进入回调类中的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// MCDF base test
class mcdf_base_test extends uvm_test;
chnl_generator chnl_gens[3];
reg_generator reg_gen;
fmt_generator fmt_gen;
mcdf_env env;
local int timeout = 10; // 10 * ms
virtual chnl_intf ch0_vif ;
virtual chnl_intf ch1_vif ;
virtual chnl_intf ch2_vif ;
virtual reg_intf reg_vif ;
virtual arb_intf arb_vif ;
virtual fmt_intf fmt_vif ;
virtual mcdf_intf mcdf_vif;

`uvm_component_utils(mcdf_base_test)
`uvm_register_cb(mcdf_base_test, cb_mcdf_base)

function new(string name = "mcdf_base_test", uvm_component parent);
super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
// get virtual interface from top TB
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","ch0_vif", ch0_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","ch1_vif", ch1_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","ch2_vif", ch2_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual reg_intf)::get(this,"","reg_vif", reg_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual arb_intf)::get(this,"","arb_vif", arb_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual fmt_intf)::get(this,"","fmt_vif", fmt_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual mcdf_intf)::get(this,"","mcdf_vif", mcdf_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end

this.env = mcdf_env::type_id::create("env", this);
foreach(this.chnl_gens[i]) begin
this.chnl_gens[i] = chnl_generator::type_id::create($sformatf("chnl_gens[%0d]",i), this);
end
this.reg_gen = reg_generator::type_id::create("reg_gen", this);
this.fmt_gen = fmt_generator::type_id::create("fmt_gen", this);
endfunction

function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// After get virtual interface from config_db, and then set them to
// child components
this.set_interface(ch0_vif, ch1_vif, ch2_vif, reg_vif, arb_vif, fmt_vif, mcdf_vif);

foreach(this.chnl_gens[i]) begin
this.env.chnl_agts[i].driver.req_mb = this.chnl_gens[i].req_mb;
this.env.chnl_agts[i].driver.rsp_mb = this.chnl_gens[i].rsp_mb;
end
this.env.reg_agt.driver.req_mb = this.reg_gen.req_mb;
this.env.reg_agt.driver.rsp_mb = this.reg_gen.rsp_mb;
this.env.fmt_agt.driver.req_mb = this.fmt_gen.req_mb;
this.env.fmt_agt.driver.rsp_mb = this.fmt_gen.rsp_mb;
endfunction

function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
uvm_root::get().set_report_verbosity_level_hier(UVM_HIGH);
uvm_root::get().set_report_max_quit_count(1);
uvm_root::get().set_timeout(10ms);
endfunction

task run_phase(uvm_phase phase);
// NOTE:: raise objection to prevent simulation stopping
phase.raise_objection(this);

`uvm_info(get_type_name(), "=====================STARTED=====================", UVM_LOW)
this.do_reg();
this.do_formatter();
this.do_data();
//TODO-4.2 remove watchdog() method
`uvm_info(get_type_name(), "=====================FINISHED=====================", UVM_LOW)

// NOTE:: drop objection to request simulation stopping
phase.drop_objection(this);
endtask

// do register configuration
virtual task do_reg();
//TODO-3.3 Use callback macro to link the callback method
`uvm_do_callbacks(mcdf_base_test, cb_mcdf_base, cb_do_reg())
endtask

// do external formatter down stream slave configuration
virtual task do_formatter();
//TODO-3.3 Use callback macro to link the callback method
`uvm_do_callbacks(mcdf_base_test, cb_mcdf_base, cb_do_formatter())
endtask

// do data transition from 3 channel slaves
virtual task do_data();
//TODO-3.3 Use callback macro to link the callback method
`uvm_do_callbacks(mcdf_base_test, cb_mcdf_base, cb_do_data())
endtask

endclass: mcdf_base_test

实现uvm_callback和对应test类的定义(添加)

build_phase中创建和添加回调类,需要注意这里语法的使用

1
2
cb = cb_mcdf_data_consistence_basic::type_id::create("cb");		//创建回调函数
uvm_callbacks#(mcdf_base_test)::add(this, cb); //添加回调函数

问:为啥这里的是mcdf_base_test,而不是实际的测试类

因为前面目标uvm_test类型与目标uvm_callback类型建立了绑定关系,这里传入的test类型还是父类的test

当test被执行时,首先执行父类test中的run_phase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
 class cb_mcdf_data_consistence_basic extends cb_mcdf_base;    //回调类
`uvm_object_utils(cb_mcdf_data_consistence_basic)
function new (string name = "cb_mcdf_data_consistence_basic");
super.new(name);
endfunction
task cb_do_reg();
bit[31:0] wr_val, rd_val;
super.cb_do_reg();
// slv0 with len=8, prio=0, en=1
wr_val = (1<<3)+(0<<1)+1;
test.write_reg(`SLV0_RW_ADDR, wr_val);
test.read_reg(`SLV0_RW_ADDR, rd_val);
void'(test.diff_value(wr_val, rd_val, "SLV0_WR_REG"));

// slv1 with len=16, prio=1, en=1
wr_val = (2<<3)+(1<<1)+1;
test.write_reg(`SLV1_RW_ADDR, wr_val);
test.read_reg(`SLV1_RW_ADDR, rd_val);
void'(test.diff_value(wr_val, rd_val, "SLV1_WR_REG"));

// slv2 with len=32, prio=2, en=1
wr_val = (3<<3)+(2<<1)+1;
test.write_reg(`SLV2_RW_ADDR, wr_val);
test.read_reg(`SLV2_RW_ADDR, rd_val);
void'(test.diff_value(wr_val, rd_val, "SLV2_WR_REG"));

// send IDLE command
test.idle_reg();
endtask

task cb_do_formatter();
super.cb_do_formatter();
void'(test.fmt_gen.randomize() with {fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;});
test.fmt_gen.start();
endtask

task cb_do_data();
super.cb_do_data();
void'(test.chnl_gens[0].randomize() with {ntrans==100; ch_id==0; data_nidles==0; pkt_nidles==1; data_size==8; });
void'(test.chnl_gens[1].randomize() with {ntrans==100; ch_id==1; data_nidles==1; pkt_nidles==4; data_size==16;});
void'(test.chnl_gens[2].randomize() with {ntrans==100; ch_id==2; data_nidles==2; pkt_nidles==8; data_size==32;});
fork
test.chnl_gens[0].start();
test.chnl_gens[1].start();
test.chnl_gens[2].start();
join
#10us; // wait until all data haven been transfered through MCDF
endtask
endclass: cb_mcdf_data_consistence_basic


//cb_mcdf_data_consistence_basic_test 中并没有实现do_reg()、do_formatter()、do_data(),
//运行test时会调用cb_mcdf_data_consistence_basic这个回调类,这里面实现了那三种方法。
class cb_mcdf_data_consistence_basic_test extends mcdf_base_test;
cb_mcdf_data_consistence_basic cb;
`uvm_component_utils(cb_mcdf_data_consistence_basic_test)

function new(string name = "cb_mcdf_data_consistence_basic_test", uvm_component parent);
super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
cb = cb_mcdf_data_consistence_basic::type_id::create("cb"); //创建回调函数
uvm_callbacks#(mcdf_base_test)::add(this, cb); //添加回调函数
endfunction

function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
cb.test = this;
endfunction
endclass: cb_mcdf_data_consistence_basic_test

UVM-实验4

在UVM入门实验3中,实现了monitorreference modelchecker之间的通信是通过TLM端口或者TLM FIFO来完成,相较于之前的mailbox句柄连接,更加容易定制,也使得组件的独立性提高。

本次实验需要实现:

  • 将产生transaction并且发送至driver的generator组件,拆分为sequence与sequencer。
  • 在拆分的基础上,实现底层的sequence。
  • 完成sequencer与driver的连接和通信工作。
  • 构建顶层的virtual sequencer。
  • 将原有的mcdf_base_test拆分为mcdf_base_virtual_sequence与mcdf_base_test,前者发挥产生序列的工作,后者只完成挂载序列的工作。
  • 将原有的mcdf_data_consistence_basic_test和mcdf_full_random_test继续拆分为对应的virtual sequence和轻量化的顶层test。
  • 通过实验4可以将generator、driver与test的关系最终移植为sequence、sequencer、driver和test的关系。

image-20210728213509317

1. generator到序列化的改建(reg_pkg)

trans

  • trans:继承uvm_sequence_item,变量添加到自动化域(随机化内容和原来相同)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class reg_trans extends uvm_sequence_item;
    rand bit[7:0] addr;
    rand bit[1:0] cmd;
    rand bit[31:0] data;
    bit rsp;

    constraint cstr {
    soft cmd inside {`WRITE, `READ, `IDLE};
    soft addr inside {`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR, `SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR};
    addr[7:4]==0 && cmd==`WRITE -> soft data[31:6]==0;
    soft addr[7:5]==0;
    addr[4]==1 -> soft cmd == `READ;
    };

    `uvm_object_utils_begin(reg_trans)
    `uvm_field_int(addr, UVM_ALL_ON)
    `uvm_field_int(cmd, UVM_ALL_ON)
    `uvm_field_int(data, UVM_ALL_ON)
    `uvm_field_int(rsp, UVM_ALL_ON)
    `uvm_object_utils_end

    function new (string name = "reg_trans");
    super.new(name);
    endfunction
    endclass

sequence

  • 激励产生交由sequence实现

    和原有的generator方法的明显区别:

    • 最大的区别:generator作为component,在顶层test中创建,随机化和给driver发送激励;

      sequence作为object,在自身的body方法中生成激励和调用发送方法,其可以声明在模块的包中,在顶层包中通过virtual sequence进行同一管理(连接和挂载)

    • 继承uvm_sequence,是参数化类,需要传入实例化的item类型

    • mailbox被取消,核心方法send_trans极大的简化

      1. 一个`uvm_do_with(req,{“约束”}),即可实现item的随机化创建+挂载
      2. 一个get_response()方法就可以获取到来自driver的相应rsp-item(前提是deriver返回rsp,两者相匹配)
    • reg的激励较为特殊,其要实现3种命令模式的分别创建,依此来进行寄存器的读写功能检测
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    class reg_base_sequence extends uvm_sequence #(reg_trans);
    rand bit[7:0] addr = -1;
    rand bit[1:0] cmd = -1;
    rand bit[31:0] data = -1;

    constraint cstr{
    soft addr == -1;
    soft cmd == -1;
    soft data == -1;
    }

    `uvm_object_utils_begin(reg_base_sequence)
    `uvm_field_int(addr, UVM_ALL_ON)
    `uvm_field_int(cmd, UVM_ALL_ON)
    `uvm_field_int(data, UVM_ALL_ON)
    `uvm_object_utils_end
    `uvm_declare_p_sequencer(reg_sequencer)

    function new (string name = "reg_base_sequence");
    super.new(name);
    endfunction

    task body();
    send_trans();
    endtask

    // generate transaction and put into local mailbox
    task send_trans();
    reg_trans req, rsp;
    `uvm_do_with(req, {local::addr >= 0 -> addr == local::addr;
    local::cmd >= 0 -> cmd == local::cmd;
    local::data >= 0 -> data == local::data;
    })
    `uvm_info(get_type_name(), req.sprint(), UVM_HIGH)
    get_response(rsp);
    `uvm_info(get_type_name(), rsp.sprint(), UVM_HIGH)
    if(req.cmd == `READ)
    this.data = rsp.data;
    assert(rsp.rsp)
    else $error("[RSPERR] %0t error response received!", $time);
    endtask

    function void post_randomize();
    string s;
    s = {s, "AFTER RANDOMIZATION \n"};
    s = {s, "=======================================\n"};
    s = {s, "reg_base_sequence object content is as below: \n"};
    s = {s, super.sprint()};
    s = {s, "=======================================\n"};
    `uvm_info(get_type_name(), s, UVM_HIGH)
    endfunction
    endclass: reg_base_sequence

    idle_reg_sequence

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class idle_reg_sequence extends reg_base_sequence;
    constraint cstr{
    addr == 0;
    cmd == `IDLE;
    data == 0;
    }
    `uvm_object_utils(idle_reg_sequence)
    function new (string name = "idle_reg_sequence");
    super.new(name);
    endfunction
    endclass: idle_reg_sequence

    write_reg_sequence

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class write_reg_sequence extends reg_base_sequence;
    constraint cstr{
    cmd == `WRITE;
    }
    `uvm_object_utils(write_reg_sequence)
    function new (string name = "write_reg_sequence");
    super.new(name);
    endfunction
    endclass: write_reg_sequence

    read_reg_sequence

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class read_reg_sequence extends reg_base_sequence;
    constraint cstr{
    cmd == `READ;
    }
    `uvm_object_utils(read_reg_sequence)
    function new (string name = "read_reg_sequence");
    super.new(name);
    endfunction
    endclass: read_reg_sequence

sequencer

sequencer的声明和定义都很简单,同样是参数化类,需要添加传入item类型

1
2
3
4
5
6
class reg_sequencer extends uvm_sequencer #(reg_trans);
`uvm_component_utils(reg_sequencer)
function new (string name = "reg_sequencer", uvm_component parent);
super.new(name, parent);
endfunction
endclass: reg_sequencer

driver

对应的核心方法do_drive(),通过seq_item_port的方法来获取和返回item;

参数化类,传入item类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class reg_driver extends uvm_driver #(reg_trans);   
...
task do_drive();
reg_trans req, rsp;
@(posedge intf.rstn);
forever begin
seq_item_port.get_next_item(req);
this.reg_write(req);
void'($cast(rsp, req.clone()));
rsp.rsp = 1;
rsp.set_sequence_id(req.get_sequence_id());
seq_item_port.item_done(rsp);
end
endtask

agent

这个就很简单了,sequencer,driver,monitor的创建,以及sequencer和driver的连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// register agent
class reg_agent extends uvm_agent;
reg_driver driver;
reg_monitor monitor;
// declare the sequencer
reg_sequencer sequencer;
local virtual reg_intf vif;

`uvm_component_utils(reg_agent)

function new(string name = "reg_agent", uvm_component parent);
super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
driver = reg_driver::type_id::create("driver", this);
monitor = reg_monitor::type_id::create("monitor", this);
// instantiate the sequencer
sequencer = reg_sequencer::type_id::create("sequencer", this);
endfunction

function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
//connect the driver and the sequencer
driver.seq_item_port.connect(sequencer.seq_item_export);
endfunction

function void set_interface(virtual reg_intf vif);
this.vif = vif;
driver.set_interface(vif);
monitor.set_interface(vif);
endfunction
endclass

2. 移除generator的踪迹(mcdf_pkg)

mcdf_base_test中移除generator的声明、创建以及和driver之间的连接。

  • 移除generator的声明句柄,创建,generator和driver的mailbox的连接

  • 不再调用原有的do_reg,do_data,do_fmt方法,激励的产生和发送全部由序列化完成!!!

    后面再看一下子类的run_top_virtual_sequence();方法如何实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
class mcdf_base_test extends uvm_test;
//remove the generators' handle clarification
//chnl_generator chnl_gens[3];
//reg_generator reg_gen;
//fmt_generator fmt_gen;
mcdf_env env;
virtual chnl_intf ch0_vif ;
virtual chnl_intf ch1_vif ;
virtual chnl_intf ch2_vif ;
virtual reg_intf reg_vif ;
virtual arb_intf arb_vif ;
virtual fmt_intf fmt_vif ;
virtual mcdf_intf mcdf_vif;

`uvm_component_utils(mcdf_base_test)

function new(string name = "mcdf_base_test", uvm_component parent);
super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
// get virtual interface from top TB
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","ch0_vif", ch0_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","ch1_vif", ch1_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","ch2_vif", ch2_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual reg_intf)::get(this,"","reg_vif", reg_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual arb_intf)::get(this,"","arb_vif", arb_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual fmt_intf)::get(this,"","fmt_vif", fmt_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual mcdf_intf)::get(this,"","mcdf_vif", mcdf_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end

this.env = mcdf_env::type_id::create("env", this);
//remove the generators' instantiation
//foreach(this.chnl_gens[i]) begin
// this.chnl_gens[i] = chnl_generator::type_id::create($sformatf("chnl_gens[%0d]",i), this);
//end
//this.reg_gen = reg_generator::type_id::create("reg_gen", this);
//this.fmt_gen = fmt_generator::type_id::create("fmt_gen", this);
endfunction

function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// After get virtual interface from config_db, and then set them to
// child components
this.set_interface(ch0_vif, ch1_vif, ch2_vif, reg_vif, arb_vif, fmt_vif, mcdf_vif);

// remove the cross-hierarchy handle connection between drivers
//and generators since they are now connected inside the agents by the
//TLM ports connection between the sequencer and the driver
/*
foreach(this.chnl_gens[i]) begin
this.env.chnl_agts[i].driver.req_mb = this.chnl_gens[i].req_mb;
this.env.chnl_agts[i].driver.rsp_mb = this.chnl_gens[i].rsp_mb;
end
this.env.reg_agt.driver.req_mb = this.reg_gen.req_mb;
this.env.reg_agt.driver.rsp_mb = this.reg_gen.rsp_mb;
this.env.fmt_agt.driver.req_mb = this.fmt_gen.req_mb;
this.env.fmt_agt.driver.rsp_mb = this.fmt_gen.rsp_mb;
*/
endfunction

function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
uvm_root::get().set_report_verbosity_level_hier(UVM_HIGH);
uvm_root::get().set_report_max_quit_count(1);
uvm_root::get().set_timeout(10ms);
endfunction

task run_phase(uvm_phase phase);
// NOTE:: raise objection to prevent simulation stopping
phase.raise_objection(this);

`uvm_info(get_type_name(), "=====================STARTED=====================", UVM_LOW)
this.do_reg();
this.do_formatter();
this.do_data();
`uvm_info(get_type_name(), "=====================FINISHED=====================", UVM_LOW)

// NOTE:: drop objection to request simulation stopping
phase.drop_objection(this);
endtask

// do register configuration
virtual task do_reg();
endtask

// do external formatter down stream slave configuration
virtual task do_formatter();
endtask

// do data transition from 3 channel slaves
virtual task do_data();
endtask

virtual function void set_interface(virtual chnl_intf ch0_vif
,virtual chnl_intf ch1_vif
,virtual chnl_intf ch2_vif
,virtual reg_intf reg_vif
,virtual arb_intf arb_vif
,virtual fmt_intf fmt_vif
,virtual mcdf_intf mcdf_vif
);
this.env.chnl_agts[0].set_interface(ch0_vif);
this.env.chnl_agts[1].set_interface(ch1_vif);
this.env.chnl_agts[2].set_interface(ch2_vif);
this.env.reg_agt.set_interface(reg_vif);
this.env.fmt_agt.set_interface(fmt_vif);
this.env.chker.set_interface(mcdf_vif, '{ch0_vif, ch1_vif, ch2_vif}, arb_vif);
this.env.cvrg.set_interface('{ch0_vif, ch1_vif, ch2_vif}, reg_vif, arb_vif, fmt_vif, mcdf_vif);
endfunction

virtual function bit diff_value(int val1, int val2, string id = "value_compare");
if(val1 != val2) begin
`uvm_error("[CMPERR]", $sformatf("ERROR! %s val1 %8x != val2 %8x", id, val1, val2))
return 0;
end
else begin
`uvm_info("[CMPSUC]", $sformatf("SUCCESS! %s val1 %8x == val2 %8x", id, val1, val2), UVM_LOW)
return 1;
end
endfunction

// remove the tasks and they are implemented already by register
//sequences
// -idle_reg()
// -write_reg()
// -read_reg()
virtual task idle_reg();
void'(reg_gen.randomize() with {cmd == `IDLE; addr == 0; data == 0;});
reg_gen.start();
endtask

virtual task write_reg(bit[7:0] addr, bit[31:0] data);
void'(reg_gen.randomize() with {cmd == `WRITE; addr == local::addr; data == local::data;});
reg_gen.start();
endtask

virtual task read_reg(bit[7:0] addr, output bit[31:0] data);
void'(reg_gen.randomize() with {cmd == `READ; addr == local::addr;});
reg_gen.start();
data = reg_gen.data;
endtask
endclass: mcdf_base_test

3. virtual sequence

既然是对所有sequence的统一管理,就必然牵扯到几点

  • 所有底层sequence的创建,并挂载到virtual sequencer的相应sequencer上
  • 挂载:其实就可以理解为底层sequence的body()方法的执行!!

base_virtual_sequence

主要是对所有virtual_sequence的统一性的配置:

  • 所有的sequence句柄

  • 传入virtual_sequencer句柄:`uvm_declare_p_sequencer(mcdf_virtual_sequencer)

  • body方法:对底层的sequence进行创建和挂载(可以看到底层的sequence中item的挂载都不用传入sequencer,就等着在这里将sequence挂载到sequencer上呢!!!

    这里传入的都是空的虚方法,具体方法在子类实现!!见下面的mcdf_data_consistence_basic_virtual_sequence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class mcdf_base_virtual_sequence extends uvm_sequence;
idle_reg_sequence idle_reg_seq;
write_reg_sequence write_reg_seq;
read_reg_sequence read_reg_seq;
chnl_data_sequence chnl_data_seq;
fmt_config_sequence fmt_config_seq;

`uvm_object_utils(mcdf_base_virtual_sequence)
`uvm_declare_p_sequencer(mcdf_virtual_sequencer)

function new (string name = "mcdf_base_virtual_sequence");
super.new(name);
endfunction

virtual task body();
`uvm_info(get_type_name(), "=====================STARTED=====================", UVM_LOW)
this.do_reg();
this.do_formatter();
this.do_data();
`uvm_info(get_type_name(), "=====================FINISHED=====================", UVM_LOW)
endtask

// do register configuration
virtual task do_reg();
//User to implment the task in the child virtual sequence
endtask

// do external formatter down stream slave configuration
virtual task do_formatter();
//User to implment the task in the child virtual sequence
endtask

// do data transition from 3 channel slaves
virtual task do_data();
//User to implment the task in the child virtual sequence
endtask

virtual function bit diff_value(int val1, int val2, string id = "value_compare");
if(val1 != val2) begin
`uvm_error("[CMPERR]", $sformatf("ERROR! %s val1 %8x != val2 %8x", id, val1, val2))
return 0;
end
else begin
`uvm_info("[CMPSUC]", $sformatf("SUCCESS! %s val1 %8x == val2 %8x", id, val1, val2), UVM_LOW)
return 1;
end
endfunction
endclass

mcdf_data_consistence_basic_virtual_sequence

牵扯到具体的测试类就需要给以确切的激励随机化目标了!!!

  • do_reg:对3个寄存器依次进行配置

    因为对于读命令的sequence,获取到的driver返回的data值会更新对象的data值,可以直接通过对象调用

  • do_fmt:对模拟fifo的boundwidth和消化速度进行配置

  • do_data:一个sequence的3个随机化实例挂载到3个sequencer上

每一次做相应配置,都需要调用宏`uvm_do_on_with,创建sequence并进行随机化,将sequence挂载到virtual sequencer上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class mcdf_data_consistence_basic_virtual_sequence extends mcdf_base_virtual_sequence;
`uvm_object_utils(mcdf_data_consistence_basic_virtual_sequence)
function new (string name = "mcdf_data_consistence_basic_virtual_sequence");
super.new(name);
endfunction
task do_reg();
bit[31:0] wr_val, rd_val;
// slv0 with len=8, prio=0, en=1
wr_val = (1<<3)+(0<<1)+1;
`uvm_do_on_with(write_reg_seq, p_sequencer.reg_sqr, {addr == `SLV0_RW_ADDR; data == wr_val;})
`uvm_do_on_with(read_reg_seq, p_sequencer.reg_sqr, {addr == `SLV0_RW_ADDR;})
rd_val = read_reg_seq.data;
void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));

// slv1 with len=16, prio=1, en=1
wr_val = (2<<3)+(1<<1)+1;
`uvm_do_on_with(write_reg_seq, p_sequencer.reg_sqr, {addr == `SLV1_RW_ADDR; data == wr_val;})
`uvm_do_on_with(read_reg_seq, p_sequencer.reg_sqr, {addr == `SLV1_RW_ADDR;})
rd_val = read_reg_seq.data;
void'(this.diff_value(wr_val, rd_val, "SLV1_WR_REG"));

// slv2 with len=32, prio=2, en=1
wr_val = (3<<3)+(2<<1)+1;
`uvm_do_on_with(write_reg_seq, p_sequencer.reg_sqr, {addr == `SLV2_RW_ADDR; data == wr_val;})
`uvm_do_on_with(read_reg_seq, p_sequencer.reg_sqr, {addr == `SLV2_RW_ADDR;})
rd_val = read_reg_seq.data;
void'(this.diff_value(wr_val, rd_val, "SLV2_WR_REG"));

// send IDLE command
`uvm_do_on(idle_reg_seq, p_sequencer.reg_sqr)
endtask
task do_formatter();
`uvm_do_on_with(fmt_config_seq, p_sequencer.fmt_sqr, {fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;})
endtask
task do_data();
fork
`uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[0], {ntrans==100; ch_id==0; data_nidles==0; pkt_nidles==1; data_size==8; })
`uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[1], {ntrans==100; ch_id==1; data_nidles==1; pkt_nidles==4; data_size==16;})
`uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[2], {ntrans==100; ch_id==2; data_nidles==2; pkt_nidles==8; data_size==32;})
join
#10us; // wait until all data haven been transfered through MCDF
endtask
endclass: mcdf_data_consistence_basic_virtual_sequence

4. virtual sequencer+env

  • 定义virtual sequencer,很简单,只需要把所有组件中的virtual sequencer进行囊括

  • 在顶层env中,也就是大环境中,需要把virtual sequencer和每一个组件中的sequencer连接起来

sequencer定义

1
2
3
4
5
6
7
8
9
class mcdf_virtual_sequencer extends uvm_sequencer;
reg_sequencer reg_sqr;
fmt_sequencer fmt_sqr;
chnl_sequencer chnl_sqrs[3];
`uvm_component_utils(mcdf_virtual_sequencer)
function new (string name = "mcdf_virtual_sequencer", uvm_component parent);
super.new(name, parent);
endfunction
endclass

env中建立句柄连接sequencer实例

可以看到,env中的属性只有所有的agent和virtual sequencer,build_phase中进行实例化,connect_phase中建立连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class mcdf_env extends uvm_env;
chnl_agent chnl_agts[3];
reg_agent reg_agt;
fmt_agent fmt_agt;
mcdf_checker chker;
mcdf_coverage cvrg;
//TODO-1.7 declare the virtual sequencer handle
mcdf_virtual_sequencer virt_sqr;

`uvm_component_utils(mcdf_env)

function new (string name = "mcdf_env", uvm_component parent);
super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
this.chker = mcdf_checker::type_id::create("chker", this);
foreach(chnl_agts[i]) begin
this.chnl_agts[i] = chnl_agent::type_id::create($sformatf("chnl_agts[%0d]",i), this);
end
this.reg_agt = reg_agent::type_id::create("reg_agt", this);
this.fmt_agt = fmt_agent::type_id::create("fmt_agt", this);
this.cvrg = mcdf_coverage::type_id::create("cvrg", this);
//TODO-1.7 instantiate the virtual sequencer
virt_sqr = mcdf_virtual_sequencer::type_id::create("virt_sqr", this);
endfunction

function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
chnl_agts[0].monitor.mon_bp_port.connect(chker.chnl0_bp_imp);
chnl_agts[1].monitor.mon_bp_port.connect(chker.chnl1_bp_imp);
chnl_agts[2].monitor.mon_bp_port.connect(chker.chnl2_bp_imp);
reg_agt.monitor.mon_bp_port.connect(chker.reg_bp_imp);
fmt_agt.monitor.mon_bp_port.connect(chker.fmt_bp_imp);
//TODO-1.7 connect the sequencer handles of the virtual sequencer to
//those dedicated sequencer objects inside the agents
virt_sqr.reg_sqr = reg_agt.sequencer;
virt_sqr.fmt_sqr = fmt_agt.sequencer;
foreach(virt_sqr.chnl_sqrs[i]) virt_sqr.chnl_sqrs[i] = chnl_agts[i].sequencer;
endfunction
endclass: mcdf_env

5. 重构test

最直观的改变:

将已经被从uvm_base_test移植到reg_pkg中的方法idle_reg()、write_reg()和read_reg()从uvm_base_test中移除。

由此uvm_base_test变为了单纯的容器,激励的生成场景都在virtual sequence中完成,在它内部主要由mcdf_env、mcdf_config配置对象以及被用来挂载的顶层sequence构成。

env中底层组件的run_phase执行时,把每个driver都启动起来,最后回到顶层的run_phase执行this.run_top_virtual_sequence();

重点需要思考的问题:程序最终如何终止?

除run_phase外所有内容都是0时刻执行,这个问题其实也就是问run_phase啥时候终止;

  • 已进入run_phase后,直接通过phase.raise_objection(this);挂起,然后执行底层run_phase,注意,这里底层的run_phase都是在0时刻启动,然后都会被do_driver()方法中的seq_item_port.get_next_item(req);卡住,同样0时刻也意味着此时的this.run_top_virtual_sequence();开始执行
  • 然后就是sequence,sequencer,driver之间的数据交换。

然后又有了新问题:按理说sequence数据发送结束后不就执行phase.drop_objection(this)了吗,那我driver和monitor怎么办??

  • 和前面sv的实验中一样,sequence是要接收driver反馈的RSP的 get_response(rsp);!!!所以只有driver把sequence给它的最后一个数据发送出去以后,返回rsp给sequence,且sequence接收到这个rsp,激励发送才是结束!!!!

  • 而monitor是检测有效数据传输,比如chnl的driver必须等到ready信号才算完成一次数据传输,进入idle阶段,所以monitor在driver刚把数据驱动到接口上就进行了采集

  • 综上,sequence一定是最后一个执行结束的!!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
class mcdf_base_test extends uvm_test;
//TODO-1.4 remove the generators' handle clarification
mcdf_env env;
virtual chnl_intf ch0_vif ;
virtual chnl_intf ch1_vif ;
virtual chnl_intf ch2_vif ;
virtual reg_intf reg_vif ;
virtual arb_intf arb_vif ;
virtual fmt_intf fmt_vif ;
virtual mcdf_intf mcdf_vif;

`uvm_component_utils(mcdf_base_test)

function new(string name = "mcdf_base_test", uvm_component parent);
super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
// get virtual interface from top TB
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","ch0_vif", ch0_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","ch1_vif", ch1_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","ch2_vif", ch2_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual reg_intf)::get(this,"","reg_vif", reg_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual arb_intf)::get(this,"","arb_vif", arb_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual fmt_intf)::get(this,"","fmt_vif", fmt_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual mcdf_intf)::get(this,"","mcdf_vif", mcdf_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end

this.env = mcdf_env::type_id::create("env", this);
//TODO-1.4 remove the generators' instantiation
endfunction

function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// After get virtual interface from config_db, and then set them to
// child components
this.set_interface(ch0_vif, ch1_vif, ch2_vif, reg_vif, arb_vif, fmt_vif, mcdf_vif);

//TODO-1.4 remove the cross-hierarchy handle connection between drivers
//and generators since they are now connected inside the agents by the
//TLM ports connection between the sequencer and the driver
endfunction

function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
uvm_root::get().set_report_verbosity_level_hier(UVM_HIGH);
uvm_root::get().set_report_max_quit_count(1);
uvm_root::get().set_timeout(10ms);
endfunction

task run_phase(uvm_phase phase);
// NOTE:: raise objection to prevent simulation stopping
phase.raise_objection(this);

this.run_top_virtual_sequence();

// NOTE:: drop objection to request simulation stopping
phase.drop_objection(this);
endtask

virtual task run_top_virtual_sequence();
// User to implement this task in the child tests
endtask

virtual function void set_interface(virtual chnl_intf ch0_vif
,virtual chnl_intf ch1_vif
,virtual chnl_intf ch2_vif
,virtual reg_intf reg_vif
,virtual arb_intf arb_vif
,virtual fmt_intf fmt_vif
,virtual mcdf_intf mcdf_vif
);
this.env.chnl_agts[0].set_interface(ch0_vif);
this.env.chnl_agts[1].set_interface(ch1_vif);
this.env.chnl_agts[2].set_interface(ch2_vif);
this.env.reg_agt.set_interface(reg_vif);
this.env.fmt_agt.set_interface(fmt_vif);
this.env.chker.set_interface(mcdf_vif, '{ch0_vif, ch1_vif, ch2_vif}, arb_vif);
this.env.cvrg.set_interface('{ch0_vif, ch1_vif, ch2_vif}, reg_vif, arb_vif, fmt_vif, mcdf_vif);
endfunction


//TODO-1.5 remove the tasks and they are implemented already by register
//sequences
// -idle_reg()
// -write_reg()
// -read_reg()
endclass: mcdf_base_test

来看下子类中的方法this.run_top_virtual_sequence();是怎么实现的

其实没啥花里胡哨,就是将virtual sequence挂载到virtual sequencer上,run_phase启动的时候所有的sequence也就启动了

注意上面sequence的执行顺序并非并行,一定是先do_reg,再do_formatter,再do_data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  class mcdf_data_consistence_basic_test extends mcdf_base_test;

`uvm_component_utils(mcdf_data_consistence_basic_test)

function new(string name = "mcdf_data_consistence_basic_test", uvm_component parent);
super.new(name, parent);
endfunction

task run_top_virtual_sequence();
mcdf_data_consistence_basic_virtual_sequence top_seq = new();
top_seq.start(env.virt_sqr);
endtask
endclass: mcdf_data_consistence_basic_test
//
class mcdf_full_random_test extends mcdf_base_test;

`uvm_component_utils(mcdf_full_random_test)

function new(string name = "mcdf_full_random_test", uvm_component parent);
super.new(name, parent);
endfunction

task run_top_virtual_sequence();
mcdf_full_random_virtual_sequence top_seq = new();
top_seq.start(env.virt_sqr);
endtask
endclass: mcdf_full_random_test

UVM_实验5

从寄存器模型开始,再重新对整个系统做一次梳理

mcdf_rgm_pkg

3个reg相关的类,全是object

1. reg类

正好回忆一下寄存器模型的内容

  • 声明寄存器域,对reg的功能进行拆解(rand)
  • 考虑寄存器模型的覆盖率收集,可以直接在reg中生成covergroup和对应方法(sample(),sample_values())
  • 构造方法:调用父类构造方法,传入:名称,位数,覆盖率类型;设置域的覆盖率采集;如果设置了,就可以实例化covergroup
  • build方法:寄存器每个域的创建(typeid+create创建,不需要注册)

读写寄存器ctrl_reg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class ctrl_reg extends uvm_reg;
`uvm_object_utils(ctrl_reg)
uvm_reg_field reserved;
rand uvm_reg_field pkt_len;
rand uvm_reg_field prio_level;
rand uvm_reg_field chnl_en;

covergroup value_cg;
option.per_instance = 1;
reserved: coverpoint reserved.value[25:0];
pkt_len: coverpoint pkt_len.value[2:0];
prio_level: coverpoint prio_level.value[1:0];
chnl_en: coverpoint chnl_en.value[0:0];
endgroup

function new(string name = "ctrl_reg");
super.new(name, 32, UVM_CVR_ALL);
void'(set_coverage(UVM_CVR_FIELD_VALS));
if(has_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg = new();
end
endfunction

virtual function void build();
reserved = uvm_reg_field::type_id::create("reserved");
pkt_len = uvm_reg_field::type_id::create("pkt_len");
prio_level = uvm_reg_field::type_id::create("prio_level");
chnl_en = uvm_reg_field::type_id::create("chnl_en");

reserved.configure(this, 26, 6, "RO", 0, 26'h0, 1, 0, 0);
pkt_len.configure(this, 3, 3, "RW", 0, 3'h0, 1, 1, 0);
prio_level.configure(this, 2, 1, "RW", 0, 2'h3, 1, 1, 0);
chnl_en.configure(this, 1, 0, "RW", 0, 1'h1, 1, 1, 0);
endfunction

function void sample(
uvm_reg_data_t data,
uvm_reg_data_t byte_en,
bit is_read,
uvm_reg_map map
);
super.sample(data, byte_en, is_read, map);
sample_values();
endfunction

function void sample_values();
super.sample_values();
if (get_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg.sample();
end
endfunction
endclass

状态寄存器stat_reg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class stat_reg extends uvm_reg;
`uvm_object_utils(stat_reg)
uvm_reg_field reserved;
rand uvm_reg_field fifo_avail;


covergroup value_cg;
option.per_instance = 1;
reserved: coverpoint reserved.value[23:0];
fifo_avail: coverpoint fifo_avail.value[7:0];
endgroup

function new(string name = "stat_reg");
super.new(name, 32, UVM_CVR_ALL);
void'(set_coverage(UVM_CVR_FIELD_VALS));
if(has_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg = new();
end
endfunction

virtual function void build();
reserved = uvm_reg_field::type_id::create("reserved");
fifo_avail = uvm_reg_field::type_id::create("fifo_avail");

reserved.configure(this, 24, 8, "RO", 0, 24'h0, 1, 0, 0);
fifo_avail.configure(this, 8, 0, "RO", 0, 8'h20, 1, 1, 0);
endfunction

function void sample(
uvm_reg_data_t data,
uvm_reg_data_t byte_en,
bit is_read,
uvm_reg_map map
);
super.sample(data, byte_en, is_read, map);
sample_values();
endfunction

function void sample_values();
super.sample_values();
if (get_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg.sample();
end
endfunction
endclass

2. reg_block类

  • 把6个寄存器都放进来,此时每个寄存器也声明为rand

  • 构造方法:同样调用父类构造方法,传入name,UVM_NO_COVERAGE(覆盖率不能重复收集)

  • build方法:

    寄存器配置三部曲:创建+配置+调用寄存器的build方法

    常见必须的map:map = create_map(“map”, ‘h0, 4, UVM_LITTLE_ENDIAN);

    在map中添加rgm中的寄存器,每个寄存器的偏移地址,寄存器的读写类型等

    给所有的reg绑定hdl路径

  • 切记,把模型锁住:lock_model()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class mcdf_rgm extends uvm_reg_block;
`uvm_object_utils(mcdf_rgm)
rand ctrl_reg chnl0_ctrl_reg;
rand ctrl_reg chnl1_ctrl_reg;
rand ctrl_reg chnl2_ctrl_reg;
rand stat_reg chnl0_stat_reg;
rand stat_reg chnl1_stat_reg;
rand stat_reg chnl2_stat_reg;

uvm_reg_map map;

function new(string name = "mcdf_rgm");
super.new(name, UVM_NO_COVERAGE);
endfunction

virtual function void build();
chnl0_ctrl_reg = ctrl_reg::type_id::create("chnl0_ctrl_reg");
chnl0_ctrl_reg.configure(this);
chnl0_ctrl_reg.build();

chnl1_ctrl_reg = ctrl_reg::type_id::create("chnl1_ctrl_reg");
chnl1_ctrl_reg.configure(this);
chnl1_ctrl_reg.build();

chnl2_ctrl_reg = ctrl_reg::type_id::create("chnl2_ctrl_reg");
chnl2_ctrl_reg.configure(this);
chnl2_ctrl_reg.build();

chnl0_stat_reg = stat_reg::type_id::create("chnl0_stat_reg");
chnl0_stat_reg.configure(this);
chnl0_stat_reg.build();

chnl1_stat_reg = stat_reg::type_id::create("chnl1_stat_reg");
chnl1_stat_reg.configure(this);
chnl1_stat_reg.build();

chnl2_stat_reg = stat_reg::type_id::create("chnl2_stat_reg");
chnl2_stat_reg.configure(this);
chnl2_stat_reg.build();

// map name, offset, number of bytes, endianess
map = create_map("map", 'h0, 4, UVM_LITTLE_ENDIAN);

map.add_reg(chnl0_ctrl_reg, 32'h00000000, "RW");
map.add_reg(chnl1_ctrl_reg, 32'h00000004, "RW");
map.add_reg(chnl2_ctrl_reg, 32'h00000008, "RW");
map.add_reg(chnl0_stat_reg, 32'h00000010, "RO");
map.add_reg(chnl1_stat_reg, 32'h00000014, "RO");
map.add_reg(chnl2_stat_reg, 32'h00000018, "RO");

// specify HDL path
chnl0_ctrl_reg.add_hdl_path_slice($sformatf("mem[%0d]", `SLV0_RW_REG), 0, 32);
chnl1_ctrl_reg.add_hdl_path_slice($sformatf("mem[%0d]", `SLV1_RW_REG), 0, 32);
chnl2_ctrl_reg.add_hdl_path_slice($sformatf("mem[%0d]", `SLV2_RW_REG), 0, 32);
chnl0_stat_reg.add_hdl_path_slice($sformatf("mem[%0d]", `SLV0_R_REG ), 0, 32);
chnl1_stat_reg.add_hdl_path_slice($sformatf("mem[%0d]", `SLV1_R_REG ), 0, 32);
chnl2_stat_reg.add_hdl_path_slice($sformatf("mem[%0d]", `SLV2_R_REG ), 0, 32);

add_hdl_path("tb.dut.ctrl_regs_inst");

lock_model();
endfunction
endclass

3. reg_adapter类

可以在uvm中找到adapter的实现

mcdf_bus_driver在读数时会将读回的数据填入到RSP并返回至sequencer,因此需要在adapter中使能provides_responses

reg2bus(const ref uvm_reg_bus_op rw):

  • 需要先创建reg_trans,然后调用create方法创建实例对象

  • 用uvm_reg_bus_op的数值对创建的trans进行非阻塞赋值,最后返回reg_trans

bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw)

  • 因为传入的参数类型是uvm_sequence_item,需要先进行类型转化,为reg_trans
  • 和上面的方法类似进行映射,需要注意的是rw.status = UVM_IS_OK;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class reg2mcdf_adapter extends uvm_reg_adapter;
`uvm_object_utils(reg2mcdf_adapter)
function new(string name = "reg2mcdf_adapter");
super.new(name);
provides_responses = 1;
endfunction
function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
reg_trans t = reg_trans::type_id::create("t");
t.cmd = (rw.kind == UVM_WRITE) ? `WRITE : `READ;
t.addr = rw.addr;
t.data = rw.data;
return t;
endfunction
function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
reg_trans t;
if (!$cast(t, bus_item)) begin
`uvm_fatal("CASTFAIL","Provided bus_item is not of the correct type")
return;
end
rw.kind = (t.cmd == `WRITE) ? UVM_WRITE : UVM_READ;
rw.addr = t.addr;
rw.data = t.data;
rw.status = UVM_IS_OK;
endfunction
endclass

mcdf_env

相比较实验4中的env,毫无疑问的多了点东西,就是多了的这些东西,才让后面寄存器模型的read,write方法的直接使用而不用挂载,

注意这里的uvm_reg_predictor,是参数类,同样需要传入reg_trans

问:为啥要定义和绑定predictor呢?

答:显示自动预测的需要

1
2
3
mcdf_rgm rgm;
reg2mcdf_adapter adapter;
uvm_reg_predictor #(reg_trans) predictor;

再来看看这几个东西是如何创建的

fgm调用build()方法,关系到reg和map的创建和配置,最终还关系到reg_field的创建和配置

1
2
3
4
rgm = mcdf_rgm::type_id::create("rgm", this);
rgm.build();
adapter = reg2mcdf_adapter::type_id::create("adapter", this);
predictor = uvm_reg_predictor#(reg_trans)::type_id::create("predictor", this);

因为这里创建了predictor,就不需要调用rgm.map.set_auto_predict();

这几个东西的连接也是重头戏

想象一下结构图,这里的连接就是以结构图为原型

  • adapter的集成:rgm.map、reg_agt.sequencer、adapter;以适配器为中心进行连接
  • predictor的集成:
1
2
3
4
5
rgm.map.set_sequencer(reg_agt.sequencer, adapter);
reg_agt.monitor.mon_ana_port.connect(predictor.bus_in);
predictor.map = rgm.map;
predictor.adapter = adapter;
virt_sqr.rgm = rgm;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
 // MCDF top environment
class mcdf_env extends uvm_env;
chnl_agent chnl_agts[3];
reg_agent reg_agt;
fmt_agent fmt_agt;
mcdf_checker chker;
mcdf_coverage cvrg;
mcdf_virtual_sequencer virt_sqr;
//declare the mcdf_rgm handle, reg2mcdf_adapter handle and the
//predictory handle
mcdf_rgm rgm;
reg2mcdf_adapter adapter;
uvm_reg_predictor #(reg_trans) predictor;

`uvm_component_utils(mcdf_env)
...

function void build_phase(uvm_phase phase);
super.build_phase(phase);
this.chker = mcdf_checker::type_id::create("chker", this);
foreach(chnl_agts[i]) begin
this.chnl_agts[i] = chnl_agent::type_id::create($sformatf("chnl_agts[%0d]",i), this);
end
this.reg_agt = reg_agent::type_id::create("reg_agt", this);
this.fmt_agt = fmt_agent::type_id::create("fmt_agt", this);
this.cvrg = mcdf_coverage::type_id::create("cvrg", this);
virt_sqr = mcdf_virtual_sequencer::type_id::create("virt_sqr", this);
//instantiate those objects
// -mcdf_rgm object
// -reg2mcdf_adapter object
// -predictory object
//and finish necessary configuration
rgm = mcdf_rgm::type_id::create("rgm", this);
rgm.build();
adapter = reg2mcdf_adapter::type_id::create("adapter", this);
predictor = uvm_reg_predictor#(reg_trans)::type_id::create("predictor", this);
endfunction

function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
chnl_agts[0].monitor.mon_bp_port.connect(chker.chnl0_bp_imp);
chnl_agts[1].monitor.mon_bp_port.connect(chker.chnl1_bp_imp);
chnl_agts[2].monitor.mon_bp_port.connect(chker.chnl2_bp_imp);
reg_agt.monitor.mon_bp_port.connect(chker.reg_bp_imp);
fmt_agt.monitor.mon_bp_port.connect(chker.fmt_bp_imp);
virt_sqr.reg_sqr = reg_agt.sequencer;
virt_sqr.fmt_sqr = fmt_agt.sequencer;
foreach(virt_sqr.chnl_sqrs[i]) virt_sqr.chnl_sqrs[i] = chnl_agts[i].sequencer;
// Link the register model with the adapter and the predictor
rgm.map.set_sequencer(reg_agt.sequencer, adapter);
reg_agt.monitor.mon_ana_port.connect(predictor.bus_in);
predictor.map = rgm.map;
predictor.adapter = adapter;
virt_sqr.rgm = rgm;
endfunction

寄存器模型的使用

  • connect阶段实现register block句柄的传递。
  • mcdf_data_consistence_basic_virtual_sequence原有的由总线sequence实现的寄存器读写,改为由寄存器模型操作的寄存器读写方式。
  • mcdf_full_random_virtual_sequence原有的由总线sequence实现的寄存器读写,改为由寄存器模型预先设置寄存器值,再统一做总线寄存器更新的方式,并且由后门读取的方式取得寄存器值。

1.virtual sequence获取rgm句柄,只需要mcdf_base_virtual_sequence进行获取

问:为啥让virtual sequence获取rgm句柄?

因为reg的激励环境配置也是在sequence中做的,就是把原先通过总线进行寄存器访问的方式改为了访问寄存器模型了

看一下base_sequence做的少许修改:

  • 声明reg_block的句柄rgm;
  • 在body任务中,给句柄赋值p_sequencer.rgm,因为在body中定义,所以起码得等到执行到test的run_phase时才能真正将rgm给到virtual sequence

再看一下virtual sequencer又是如何得到rgm实例的

  • 在virtual sequencer中声明rgm句柄
  • 在上面的mcdf_env的build_phase中,创建rgm实例,并在connect_phase中将rgm实例赋值给virtual sequencer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class mcdf_base_virtual_sequence extends uvm_sequence;
.......
mcdf_rgm rgm;
。。。。。
virtual task body();
`uvm_info(get_type_name(), "=====================STARTED=====================", UVM_LOW)
//TODO-2.1 connect rgm handle
rgm = p_sequencer.rgm;

this.do_reg();
this.do_formatter();
this.do_data();

`uvm_info(get_type_name(), "=====================FINISHED=====================", UVM_LOW)
endtask
endclass

2.将mcdf_data_consistence_basic_virtual_sequence原有的由总线sequence实现的寄存器读写,由寄存器模型操作的寄存器读写方式

  • 直接调用寄存器模型的写方法,通过wr_val对目的寄存器的状态值进行修改

    调用寄存器模型的读方法,获取到寄存器的状态值,输出rd_val

    这两种方法默认情况下都是前门访问,从总线访问过去的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
task do_reg();
bit[31:0] wr_val, rd_val;
uvm_status_e status;
// slv0 with len=8, prio=0, en=1
wr_val = (1<<3)+(0<<1)+1;
rgm.chnl0_ctrl_reg.write(status, wr_val);
rgm.chnl0_ctrl_reg.read(status, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));

// slv1 with len=16, prio=1, en=1
wr_val = (2<<3)+(1<<1)+1;
rgm.chnl1_ctrl_reg.write(status, wr_val);
rgm.chnl1_ctrl_reg.read(status, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV1_WR_REG"));

// slv2 with len=32, prio=2, en=1
wr_val = (3<<3)+(2<<1)+1;
rgm.chnl2_ctrl_reg.write(status, wr_val);
rgm.chnl2_ctrl_reg.read(status, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV2_WR_REG"));

// send IDLE command
`uvm_do_on(idle_reg_seq, p_sequencer.reg_sqr)
endtask

3.将mcdf_full_random_virtual_sequence原有的由总线sequence实现的寄存器读写,改为由寄存器模型预先设置寄存器值,再统一做总线寄存器更新的方式,并且由后门读取的方式取得寄存器值。

其实就是和2的东西做点区分,增加一点练习

  • reg_block(reg)的reset方法,给所有寄存器模型复位

  • 通过set设置寄存器模型中的value

  • 调用update更新dut寄存器的状态

  • 不在通过将读到的值和写入的值进行比较来检查,这里用reg的mirror方法进行检查,注意这里的参数传递

    如果dut寄存器状态值和软件模型的镜像值不同,会打印结果

    1
    rgm.chnl0_ctrl_reg.mirror(status, UVM_CHECK, UVM_BACKDOOR);

    也可以使用前门访问

    问:为啥不直接调用rgm的mirror方法

    答:有状态寄存器,镜像值和实际值很可能不一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
task do_reg();
bit[31:0] ch0_wr_val;
bit[31:0] ch1_wr_val;
bit[31:0] ch2_wr_val;
uvm_status_e status;

//reset the register block
rgm.reset();

//slv0 with len={4,8,16,32}, prio={[0:3]}, en={[0:1]}
ch0_wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,1);
ch1_wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,1);
ch2_wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,1);

//set all value of WR registers via uvm_reg::set()
rgm.chnl0_ctrl_reg.set(ch0_wr_val);
rgm.chnl1_ctrl_reg.set(ch1_wr_val);
rgm.chnl2_ctrl_reg.set(ch2_wr_val);

//update them via uvm_reg_block::update()
rgm.update(status);

//wait until the registers in DUT have been updated
#100ns;

//compare all of write value and read value
rgm.chnl0_ctrl_reg.mirror(status, UVM_CHECK, UVM_BACKDOOR);
rgm.chnl1_ctrl_reg.mirror(status, UVM_CHECK, UVM_BACKDOOR);
rgm.chnl2_ctrl_reg.mirror(status, UVM_CHECK, UVM_BACKDOOR);

// send IDLE command
`uvm_do_on(idle_reg_seq, p_sequencer.reg_sqr)
endtask

寄存器内建序列的应用

和上面的sequence大同小异,就是将body方法封装起来了而已,挂载的目的其实就是为了执行其内部的body方法!!!

mcdf_reg_builtin_virtual_sequence类中,使用uvm_reg_hw_reset_sequvm_reg_bit_bash_sequvm_reg_access_seq对MCDF寄存器模块展开全面测试。

几个内建序列的意思

在这里插入图片描述

测试过程:

  • 每一次都要进行硬件的复位和软件模型的复位

    reg_rst_seq.model = rgm;
    reg_rst_seq.start(p_sequencer.reg_sqr);

  • 给内建序列传入寄存器模型reg_block(==rgm = p_sequencer.rgm;),并且挂载到reg_sequencer上(就是为了让内建sequence的body方法能够执行)

  • 最终在test类中virtual sequence挂载到virtual sequencer的过程其实就对应了body的执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
task do_reg();
uvm_reg_hw_reset_seq reg_rst_seq = new();
uvm_reg_bit_bash_seq reg_bit_bash_seq = new();
uvm_reg_access_seq reg_acc_seq = new();

// wait reset asserted and release
@(negedge p_sequencer.intf.rstn);
@(posedge p_sequencer.intf.rstn);

`uvm_info("BLTINSEQ", "register reset sequence started", UVM_LOW)
rgm.reset();
reg_rst_seq.model = rgm;
reg_rst_seq.start(p_sequencer.reg_sqr);
`uvm_info("BLTINSEQ", "register reset sequence finished", UVM_LOW)

`uvm_info("BLTINSEQ", "register bit bash sequence started", UVM_LOW)
// reset hardware register and register model
p_sequencer.intf.rstn <= 'b0;
repeat(5) @(posedge p_sequencer.intf.clk);
p_sequencer.intf.rstn <= 'b1;
rgm.reset();
reg_bit_bash_seq.model = rgm;
reg_bit_bash_seq.start(p_sequencer.reg_sqr);
`uvm_info("BLTINSEQ", "register bit bash sequence finished", UVM_LOW)

`uvm_info("BLTINSEQ", "register access sequence started", UVM_LOW)
// reset hardware register and register model
p_sequencer.intf.rstn <= 'b0;
repeat(5) @(posedge p_sequencer.intf.clk);
p_sequencer.intf.rstn <= 'b1;
rgm.reset();
reg_acc_seq.model = rgm;
reg_acc_seq.start(p_sequencer.reg_sqr);
`uvm_info("BLTINSEQ", "register access sequence finished", UVM_LOW)
endtask

仿真命令

1
vsim -novopt -sv_seed random -classdebug +UVM_TESTNAME=mcdf_reg_builtin_test -l assertion.log work.tb

文件结构

image-20210809221600419

仿真结果

内建序列的反馈信息

由于uvm_reg_bit_bash_seq的结果太多了,就只把reg_access_seq的执行结果拿出来看一看,上面还有uvm_reg_hw_reset_seq,uvm_reg_bit_bash_seq

三者按顺序执行~

  • 很直观的一点:装载到reg_agt.sequencer的reg内建序列是执行主体,和我推测的:在挂载时执行内建序列的body方法一样

image-20210801132423605

把uvm_reg_hw_reset_seq的执行结果也拿出来看一下

很明显,每个addr的reg进行了轮询

  • driver发送读信号
  • 软件模型获取当前寄存器的复位值
  • predictor的显示预测值
  • monitor的监测值

image-20210801133236014

内建序列的功能覆盖率

以ctrl_reg的len域为例:

可以看出:3位的len [2:0]分配了8个bin,被覆盖到的bin只有0,1,2,4

因为看上面的打印信息就可以看出,只有uvm_reg_bit_bash_seq 在对读写寄存器寄存器的每一位做读写,且一次只有1位为1:
000,001,010,100

image-20210805151911576