Bootstrap

MCDF实验——Lab4

在之前的Lab3中,通过一个初具规模的MCDT的验证环境,学习到:

  • 验证环境按照隔离的观念,应分为硬件DUT,软件验证环境,和处于信号媒介的接口interface。
  • 对于软件验证环境,需要经历建立阶段(build),连接阶段(connect),产生激励阶段(generate)和发送激励阶段(transfer),只有当所有的激励发送完毕并且比较完全之后,才可以结束该测试。

从Lab4开始,要验证更大的子系统,即MCDF。与MCDT相比,MCDF主要添加了寄存器控制状态显示功能,同时也添加了一个重要的数据整形功能。不同的package之间的功能是独立的,同一个package中的各个验证组件的功能也是独立的。

一、本次实验的测试功能点

测试功能点测试内容测试通过标准测试类名
寄存器读写测试所有控制寄存器的读写测试;所有状态寄存器的读写测试读写值是否正确mcdf_reg_write_read_test
寄存器稳定性测试非法地址读写;对控制寄存器的保留域进行读写;对状态寄存器进行写操作通过写入和读出,确定寄存器的值时预期值,而不是紊乱值,同时非法寄存器操作也不能影响MCDF的整体功能mcdf_reg_illegal_access_test
数据通道开关测试对每一个数据通道对应的控制寄存器域en配置为0,在关闭状态下测试数据写入是否通过在数据通过关闭情况下,数据无法写入,同时ready信号应该保持为低,表示不接收数据,但又能使得数据不被丢失,因此数据只会停留在数据通道端口mcdf_channel_disable_test
优先级测试将不同数据通道配置为相同或者不同的优先级,在数据通道使能的情况下进行测试如果优先级相同,那么arbiter应该采取轮询机制从各个通道接受数据,如果优先级不同,那么arbiter应该先接收高优先级通道的数据,同时最终所有的数据都应该从MCDF发送出来mcdf_arbiter_priority_test
发包长度测试将不同数据通道随机配置为各自的长度,在数据通道使能的情况下进行测试从formatter发送出来的数据包长度应该同对于通道寄存器的配置值保持一一对应,同时数据也应该保持完整mcdf_formatter_length_test
下行从端低带宽测试将MCDF下行数据接收端设置为小存储量,低带宽的类型,由此使得在由formatter发送出数据之后,下行从端有更多的机会延迟grant信号的置位,用来模拟真实场景在req拉高之后,grant应该在至少两个时钟周期以后拉高,以此来模拟下行从端数据余量不足的情况,当这种激励时序发生10次之后,可以停止测试mcdf_formatter_grant_test

二、验证环境框图

在这里插入图片描述

三、代码实现

mcdf_pkg.sv

mcdf_pkg包实现了mcdf_checker功能,reg_agentchnl_agentfmt_agent三个组件中的monitor都会把监测到的数据交给mcdf_checkerchnl_agent监测到的数据是单个的数据,即32bit的数据,而fmt_agent监测到的数据是一个数据包,意味着chnl_mbfmt_mb里面的数据格式不一样,所以在比较之前,要将reg和channel监测到的数据通过mcdf_refmod这个参考模型来做整形转化
在这里插入图片描述
mcdf_reg_t结构体,用来描述寄存器数据的构成,包括长度、优先级、使能以及存储余量。

  typedef struct packed {
    bit[2:0] len;
    bit[1:0] prio;
    bit en;
    bit[7:0] avail;
  } mcdf_reg_t;

mcdf_refmod

  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;
	  bit[2:0] len;
      forever begin
        this.in_mbs[id].peek(it);
        ot = new();
        len = this.get_field_value(id, RW_LEN);
        ot.length = len > 3 ? 32 : 4 << len;
        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

  class mcdf_checker;
    local string name;
    local int err_count;
    local int total_count;
    local int chnl_count[3];
    local virtual mcdf_intf intf;
    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 intf);
      if(intf == null)
        $error("interface handle is NULL, please check if target interface has been intantiated");
      else
        this.intf = intf;
        this.refmod.set_interface(intf);
    endfunction

    task run();
      fork
        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

    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

  class mcdf_env;
    chnl_agent chnl_agts[3];
    reg_agent reg_agt;
    fmt_agent fmt_agt;
    mcdf_checker chker;
    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;
      $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();
      join
    endtask

    virtual function void do_config();
    endfunction

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

  endclass

mcdf_base_test

  class mcdf_base_test;
    chnl_generator chnl_gens[3];
    reg_generator reg_gen;
    fmt_generator fmt_gen;
    mcdf_env env;
    protected string name;

    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();
      this.do_data();
      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

    // 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 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);
    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 类,数据一致性测试。

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

rpt_pkg.sv

package rpt_pkg;
  typedef enum {INFO, WARNING, ERROR, FATAL} report_t;
  typedef enum {LOW, MEDIUM, HIGH, TOP} severity_t;
  typedef enum {LOG, STOP, EXIT} action_t;

  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;

  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

  function void clean_log();
    integer logf;
    logf = $fopen(logname, "w");
    $fclose(logf);
  endfunction
endpackage

四、运行仿真

vsim -novopt -classdebug -solvefaildebug -sv_seed 0 +TESTNAME=mcdf_data_consistence_basic_test -l mcdf_data_consistence_basic_test.log work.tb
log -r /*
run -all

仿真器会不停的发送激励、监测激励、数据比较
在这里插入图片描述

五、数据通道开关检查

在Lab4中,数据通道的开关,在数据通道关闭的情况下,mcdf_checker应该不会收到输入端或者输出端的监测数据,因此不应该出现数据比较的信息。那么在这种情况下如何判断这个测试通过?而且还需要检查一个简单的时序,即当slave channel被关闭时,当valid拉高时,ready信号不应该出现拉高的情况,否则设计是有问题的,因为数据可能没有被真正写入到FIFO,或者slave channel此时并没有被真正关闭。

针对这个问题,在mcdf_checker,将en信号检测到,将其传入mcdf_checker,同时也将3个chnl_intf传入mcdf_checker,这样mcdf_checker可以观测到所需的validreadyen信号,来完成这个检查。

do_channel_disable_check()
    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

通过forever不断的监测,在每一个时钟上升沿的时候,并且复位信号已经复位的情况下,要监测chal_en[id]这个信号,这个chal_en[id]信号是从设计里面监测到的,即把这个信号放到mcdf_vif中监测。mcdf_vif会监测一些硬件信号,然后把监测到的硬件信号传给内部的验证组件。

mcdf_intf()
interface mcdf_intf(input clk, input rstn);
  
  // 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

然后例化 mcdf_intf()并且从DUT中抓取信号

  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;

slv0_en_oslv1_en_oslv2_en_o在设计中,这三个bit分别送给设计中的Slave0Slave1Slave2用来控制数据通道开关闭。mcdf_if会抓取到这三个bit的信号,放到chnl_en[3]中,mcdf_checker会时刻监测这三个内部信号。检测到chnl_en === 0 时,那么当ch_valid === 1的情况下,ch_ready就不应该为1,即当数据通道关闭的情况下,valid和ready信号不应该同时拉高。否则将会报错误。

除了监测en内部信号,还可以调用mcdf_refmod里面的get_field_value()得到RW_EN,即代码改成

@(posedge this.mcdf_vif.clk iff (this.mcdf_vif.rstn && refmod.get_field_value(id, RW_EN));

六、优先级的检查

在Lab4中,对于优先级的检查,mcdf_checker依然无法从mcdf_refmod的预测数据中获取数据包前后的信息,也就是说mcdf_checker无法判断最终从mcdf发送出的数据是否符合了优先级的数据包发送顺序,那么将需要观测arbiter信号在mcdf_intf中声明并且观测。

do_arbiter_priority_check()
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

当时钟的上升沿到来,并且f2a_id_req===1,表示formatter发送请求给arbiter准备取数据,然后调用get_slave_id_with_prio()判断哪个id的优先级最高。

get_slave_id_with_prio()
    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

得到最高优先级的那个slave之后,等待下一个时钟上升沿,判断arbiter给slave的ack信号是否为1,即a2s_acks[id] !== 1。通过仲裁arbiter得到的slave的id和对应的ack[id]相比较,看是否是一致的,不一致则会报错。即需要判断当req发送请求之后,ack是否拉高同意请求。

;