MCDF实验3:群鸦的盛宴(从verilog到SV的入门lab3)

前言:在实验3添加了随机约束来随机化产生的数据,使用了信箱来完成类之间的通信,添加了monitor、checker让验证的组件越来越完整。

种一棵树最好的时间是十年前,其次是现在。不是吗?

实验3需要用到随机化、信箱、类的多态这几个主要知识,大家可以先去学习哦!

  • 在验证中习惯把各个验证文件独立放置,在这次实验把package的内容和tb内容分成两个文件,仿真编译先编译package,再编译tb文件。
  • 对实验2generator和initiator中数据生成和数据的传输做了升级。
  • 添加了验证的新组件monitor和checker。

源代码

将package和tb文件分开成两个文件。

1. package

在package包括了 chnl_trans、chnl_initiator、chnl_generator、chnl_monitor、chnl_checker和chnl_root_test。

  package chnl_pkg3;

  // static variables shared by resources
  semaphore run_stop_flags = new();

1.1 chnl_trans

chnl_trans在实验2用来存放数据,内部结构很简单。在实验3给他添加了随机约束,让产生的数据更复杂。
添加了控制数据传输间隔的变量,声明为了随机变量,trans中的数据从一个数据变成了多个数据。

function new:

1.11 trans中数据的随机化

变量声明前加rand,在constraint中添加约束。
新添加了pkt_nidles,代表数据包之间的间隔。假设一共有5批货,每批货100件,1件与1件的间隔是data_nidles,当发完第一批货的100件,与第二批货之间的间隔是pkt_nidles。
通过foreach遍历的方法,随机产生数据。简单的分析下产生的数据:ch_id左移了24位,pkt_id左移了8位,剩下的八位留给了变量i。假设有三个发送数据的端口,把3个ch_id赋值为00、01、02,这样传出的数据来自于哪个端口就一目了然。

  class chnl_trans;
    rand bit[31:0] data[];//rand随机
    rand int ch_id;
    rand int pkt_id;//数据包的id
    rand int data_nidles;//单个数据之间的间隔
    rand int pkt_nidles;//数据包之间的间隔
    bit rsp;
    local static int obj_id = 0; //local只能在本类中使用的变量obj_id
    
    constraint cstr{ //约束内容
      soft data.size inside {[4:8]};//数据是4到8位
      foreach(data[i]) data[i] == 'hC000_0000 + (this.ch_id<<24) + (this.pkt_id<<8) + i;
      soft ch_id == 0; //soft软约束
      soft pkt_id == 0;
      data_nidles inside {[0:2]};
      pkt_nidles inside {[1:10]};
    };

1.12 function new

每例化一次trans,让obj_id加1,用来统计总共例化了多少次。

    function new(); 	//new 函数,每例化一次trans,obj_id++
      this.obj_id++;
    endfunction

1.13 function clone

让trans具有克隆的功能,创建了句柄c。并例化,把上边的所有变量全部复制给句柄c指向的对象。最后返回值为句柄c。

    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

1.14 function sprint

一个用来打印的function。

    function string sprint();
      string s;
      s = {s, $sformatf("=======================================\n")};
      s = {s, $sformatf("chnl_trans object content is as below: \n")};
      s = {s, $sformatf("obj_id = %0d: \n", this.obj_id)};
      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

1.2 chnl_initiator

与之前的实验相比,init的功能主要还是chnl_write,不过是从信箱中获取数据,并且会告诉信箱,它有没有把数据消化完毕。

  • 声明了两个mail_box,但是并没有例化mail_box。但在test文件中,把gen中的信箱赋值给了initiator的信箱。
  • init会get信箱中的数据,并把数据使用chnl_write写入总线,并给信箱放入rsq,反馈信息给gen。
  • 新添加了task tun,其中的function new、set_interface和task chnl_write、chnl_idle与实验2相同。
  class chnl_initiator;
    local string name;
    local virtual chnl_intf intf;//接口
    mailbox #(chnl_trans) req_mb; //信箱req 和 rsp
    mailbox #(chnl_trans) rsp_mb;
  
    function new(string name = "chnl_initiator");
      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

1.21 task run

当调用run时,会执行dirve这个任务。

第一行调用类chnl_trans,生成了两个句柄req、rsp。当上升沿来临。开始死循环,不断从信箱中取数据req_mb.get(req),假如信箱中放入了req,执行chnl_write(req)。并把req的值克隆给句柄rsp,并把其中的rsp改为1。

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

    task drive();
      chnl_trans req, rsp;
      @(posedge intf.rstn);
      forever begin//死循环,不断要求从mb中取数据。
        this.req_mb.get(req);//initiator从信箱中取出gen放的数据req,此时mb里边就没有东西了
        this.chnl_write(req);//通过chnl_write把req中的data,写入interface中
        rsp = req.clone();//使用克隆
        rsp.rsp = 1;//trans类中定义了bit型的rsp,默认初始值为0,此时改变变量rsp的值
        this.rsp_mb.put(rsp);//把此时的rsp再传入gen,类似于给gen一个反馈,告诉gen自己已经消化完毕。
      end
    endtask

1.22 task chnl_write 、chnl_idle

    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 initiator [%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_initiator

1.3 chnl_generator

  • 类gen用来生成数据,并把产生数据放入信箱传递给init,再从init的信箱中获得init的反馈。
  • 在generator中也在变量前加了rand,在约束条件中生成的全是负数。如果没有外界使这些数变为正数,那么断言失败。
  class chnl_generator;
    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;

    mailbox #(chnl_trans) req_mb;//两个信箱
    mailbox #(chnl_trans) rsp_mb;

    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;
    }

1.31 new

在new函数中将两个信箱例化,现在信箱可以使用。

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

1.32 task run

重复ntrans次,task send_trans。 把数据传入到信箱给init,init取到数据后会给gen一个反馈。发送完数据后,会给semaphore 放入一把钥匙,在test中会使用到。

    task run();
      repeat(ntrans) send_trans();
      run_stop_flags.put(); // 
    endtask

1.33 task send_trans

随机化trans中的变量,并通过mailbox(mb)发送给initiator。

  • 首先,类chnl_trans 声明了两个句柄req, rsp。在下一行对req做了例化,调用req.randomize生成随机数,同时需要满足with中的约束。

asset断言类似于if_else。

  • with中的约束是当本地的ch_id大于0的时候,才会将本地的ch_id的值赋给req中的ch_id。而本地的ch_id值是负的,不满足with里的条件,所以就不会执行。只有当外界把generator中的值改为正的,才会运行
  • 成功运行后,pkt_id++, 利用mb传递信息,把gen中req对象的数据全部发送给initiator。initiator收到req,会利用克隆把req克隆给rsp,并把trans中的变量rsp的值改为1。之后initiator把这个rsq传送到信箱中。gen从信箱中取出句柄rsp并断言rsp.rsp。

chnl_trans中的有bit型的rsp,默认初始值为0,在initiator中将rsp的值赋为了1。

local::ch_id >= 0 -> ch_id == local::ch_id; 
    // generate transaction and put into local mailbox
    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());//req为trans,里边含有sprint打印函数
      this.req_mb.put(req);
      this.rsp_mb.get(rsp);
      $display(rsp.sprint());//rsp为trans,里边含有sprint打印函数
      assert(rsp.rsp)
        else $error("[RSPERR] %0t error response received!", $time);
    endtask

1.34 function sprint、post_randomize

    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

1.4 chnl_agent

  • 例化了initiator、chnl_monitor,把generation移出。
  • 现在agent只负责初始化,连接接口,运行init和monitor的run程序。
  class chnl_agent;
    local string name;
    chnl_initiator init; 
    chnl_monitor mon;
    // USER TODO 3.2
    // Refer to how we create, set virtual interface and run the initiator
    // object, use do the similar action to the monitor object
    virtual chnl_intf vif;
  //初始化
    function new(string name = "chnl_agent");
      this.name = name;
      this.init = new({name, ".init"});
      this.mon = new({name, ".mon"});
    endfunction

    function void set_interface(virtual chnl_intf vif);
      this.vif = vif;
      init.set_interface(vif);
      mon.set_interface(vif);
    endfunction
    //运行
    task run();
      fork
        init.run();
        mon.run();
      join
    endtask
  endclass: chnl_agent

1.5 chnl_root_test

在test中创建了task do_config,来专门随机化gen。 在test随机化gen,依靠gen被随机化的成员变量进一步随机gen中的trans,这样可以实现test灵活控制全局。

  class chnl_root_test;
    chnl_generator gen[3];
    chnl_agent agents[3];
    mcdt_monitor mcdt_mon;//monitor
    chnl_checker chker;//checker
    protected string name;//子类可以使用,但是外界无法调用
    event gen_stop_e;//事件event
//初始化
    function new(string name = "chnl_root_test");
      this.name = name;
      this.chker = new();
  • 在test中例化了需要的:3个agents、3个gen,而init和chnl_monitor在每个agents也完成了例化。例化了checker,mcdt_monitor。
  • 让gen中的信箱,与包含在agents中init的信箱建立了连接。
  • 把3个chnl_monitor的信箱与checker的3个输入信箱做了连接。
  • 把mcdt_monitor的信箱与checker的输出信箱做了连接。
      foreach(agents[i]) begin		//使用foreach全部遍历
        this.agents[i] = new($sformatf("chnl_agent%0d",i));
        this.gen[i] = new();
        // initior中的mb与gen中的mb相连接
        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
      this.mcdt_mon = new();
      this.mcdt_mon.mon_mb = this.chker.out_mb;
      $display("
      %s instantiated and connected objects", this.name);
    endfunction

1.51 task run

  • 首先调用do_config随机化数据,运行3个agents,3个agents对应的3个initiator开始不停的从信箱中取数据。因为无法结束,所以使用fork
    join_none。
  • 此时如果event触发,不会产生数据;event没有触发,执行下一步。
  • gen开始产生数据,并放入信箱,等init取,init取出并发送完数据,返还给gen一个反馈。
  • 当gen发送数据完毕后,会给最开始的semaphore放入一把钥匙。当三个gen都发送完毕数据,此时会有三把钥匙,满足run_stop_flags.get(3),执行finish。
    virtual task gen_stop_callback();
    endtask

    virtual task run_stop_callback(); //控制仿真停止
      $display("run_stop_callback enterred");
      // by default, run would be finished once generators raised 'finish'
      // flags 
      $display("%s: wait for all generators have generated and tranferred transcations", this.name);
      run_stop_flags.get(3); //当钥匙数量为3时,表示有三个gen完成了发送数据。
      $display($sformatf("*****************%s finished********************", this.name));
      $finish();
    endtask

    virtual task run(); //首先do_config生成随机数,分别运行三个agents,在agents的task run中再运行initiator
      $display($sformatf("*****************%s started********************", this.name));
      this.do_config();
      fork
        agents[0].run();
        agents[1].run();
        agents[2].run();
        mcdt_mon.run();
        chker.run();
      join_none

      // run first the callback thread to conditionally disable gen_threads
      fork
        this.gen_stop_callback();//当event触发时,停止gen的运行。
        @(this.gen_stop_e) disable gen_threads;
      join_none

      fork : gen_threads//控制产生数据的gen。
        gen[0].run();
        gen[1].run();
        gen[2].run();
      join

      run_stop_callback(); // wait until run stop control task finished
    endtask

1.52 set_interface

    virtual function void set_interface(virtual chnl_intf ch0_vif 
                                        ,virtual chnl_intf ch1_vif 
                                        ,virtual chnl_intf ch2_vif 
                                        ,virtual mcdt_intf mcdt_vif
                                      );
      agents[0].set_interface(ch0_vif);
      agents[1].set_interface(ch1_vif);
      agents[2].set_interface(ch2_vif);
      mcdt_mon.set_interface(mcdt_vif);
    endfunction

1.53 do_config

用来随机化gen,gen再随机化trans。

    virtual function void 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

  endclass

1.6 chnl_basic_test

修改了root_test里的do_config。

     class chnl_basic_test extends chnl_root_test;
    function new(string name = "chnl_basic_test");
      super.new(name);
    endfunction
    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
  endclass: chnl_basic_test

1.7 chnl_burst_test

  // USER TODO 2.4
  // each channel send data packet number inside [80:100]
  // data_nidles == 0, pkt_nidles == 1, data_size inside {8, 16, 32}
  class chnl_burst_test extends chnl_root_test;
    function new(string name = "chnl_burst_test");
      super.new(name);
    endfunction
    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
  endclass: chnl_burst_test

  // USER TODO 2.5
  // keep channel sending out data packet with number, and please
  // let at least two slave channels raising fifo_full (ready=0) at the same time
  // and then to stop the test

1.8 chnl_fifo_full_test

首先是初始化,do_config。

  class chnl_fifo_full_test extends chnl_root_test;
    function new(string name = "chnl_fifo_full_test");
      super.new(name);
    endfunction
    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

1.81 task gen_stop_callback

  • fifo_full_test对父类的同名任务做了修改,当运行.run程序时,会使用父类的.run程序,方法会使用fifo_full_test的。
  • 通过function来获取3个agent中的ch_ready,当准备信号<=1时,调用事件gen_stop_e,终止了数据的产生。
  • 对比实验2,判断fifo满的程序,发生了变化。
    // get all of 3 channles slave ready signals as a 3-bits vector
    local function bit[3] get_chnl_ready_flags();
      return {agents[2].vif.mon_ck.ch_ready
             ,agents[1].vif.mon_ck.ch_ready
             ,agents[0].vif.mon_ck.ch_ready
             };
    endfunction

    virtual task gen_stop_callback();
      bit[3] chnl_ready_flags;
      $display("gen_stop_callback enterred");
      @(posedge agents[0].vif.rstn);
      forever begin
        @(posedge agents[0].vif.clk);
        chnl_ready_flags = this.get_chnl_ready_flags();
        if($countones(chnl_ready_flags) <= 1) break;
      end

      $display("%s: stop 3 generators running", this.name);
      -> this.gen_stop_e;
    endtask

1.82 run_stop_callback

等数据传输完毕,结束仿真。

    virtual task run_stop_callback();
      $display("run_stop_callback enterred")

      $display("%s: waiting DUT transfering all of data", this.name);
      fork
        wait(agents[0].vif.ch_margin == 'h20);
        wait(agents[1].vif.ch_margin == 'h20);
        wait(agents[2].vif.ch_margin == 'h20);
      join
      $display("%s: 3 channel fifos have transferred all data", this.name);

      $display($sformatf("*****************%s finished********************", this.name));
      $finish();
    endtask
  endclass: chnl_fifo_full_test
endpackage

结构框图

  • root_test通过do_config随机化generator,并且将generator和agent中的initiator的信箱相连接。
  • generator产生数据,并传入信箱。initiator从信箱拿出数据,传入一个反馈到信箱,generator获得反馈并断言。

MCDF实验3:群鸦的盛宴(从verilog到SV的入门lab3)_第1张图片

1.9 monitor

  • monitor需要从interface采集到chnl的数据和mcdt的数据发送给checker。
  • chnl_monitor检测数据,initiator发送数据,把这两个都放到agent里边。
  • mcdt_monitor检测从arbiter发送出来的数据。

最后在checker做了数据比对。

//声明了结构体,储存检测到的id和data。
  typedef struct packed {
    bit[31:0] data;
    bit[1:0] id;
  } mon_data_t;
  

1.91 chnl_monitor

声明了信箱,但没有例化,最后在test中与checker连接。

  class chnl_monitor;
    local string name;
    local virtual chnl_intf intf;
    mailbox #(mon_data_t) mon_mb;//声明信箱mon_mb只能存放结构体mon_data_t中的变量
   

1.911 function new 、set_interface

初始化、连接interface会在agent里完成。

    function new(string name="chnl_monitor");//初始化name
      this.name = name;
    endfunction
    
    function void set_interface(virtual chnl_intf intf); //连接interface
      if(intf == null)
        $error("interface handle is NULL, please check if target interface has been intantiated");
      else
        this.intf = intf;
    endfunction

1.912 task run 、mon_trans

死循环,持续监视数据,当总线上的时序变化,符合有效数据的时序,把数据通过句柄写入checker。monitor和initiator都是死循环无法结束,结束只能停止发送数据,也就是停止agent。

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

    task mon_trans();
      mon_data_t m; //结构体m
      forever begin
        @(posedge intf.clk iff (intf.mon_ck.ch_valid==='b1 && intf.mon_ck.ch_ready==='b1)); //时序对把数据传入m.data,并放入信箱。
        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

1.92 mcdt_monitor

  • 监视arbiter发送出来的数据,首先连接mcdt的interface,接着获取数据,并赋值给结构体,放入checker的信箱。与输入的数据在checker作比对。

  • mcdt_monitor的例化在root_test里,在root_test里将checker的信箱与mcdt_monitor的信箱建立了连接。

class mcdt_monitor;
  local string name;
  local virtual mcdt_intf intf;
  
 mailbox #(mon_data_t) mon_mb; //信箱:监视mcdt输出的数据
  function new(string name="mcdt_monitor");
    this.name = name;
  endfunction

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

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

  task mon_trans();
    mon_data_t m;
    forever begin
      @(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);         
      $display("%0t %s monitored mcdt data %8x and id %0d", $time, this.name, m.data, m.id);
    end
  endtask
endclass

1.10 chnl_checker

  • 在test里例化了checker,并且将信箱与3个chnl_monitor和mcdt_monitor连接。
  • 从输入信箱、输出信箱取出数据,并且比较输入和输出的数据。
  class chnl_checker;
    local string name;
    local int error_count;//有一次错误加1
    local int cmp_count; //比较一次加1
    mailbox #(mon_data_t) in_mbs[3];//共4个信箱,3个输入1个输出,都完成了例化
    mailbox #(mon_data_t) out_mb;

    function new(string name="chnl_checker");
      this.name = name;
      foreach(this.in_mbs[i]) this.in_mbs[i] = new();
      this.out_mb = new();
      this.error_count = 0;
      this.cmp_count = 0;
    endfunction

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


    task do_compare();
      mon_data_t im, om;
      forever begin
        // USER TODO 3.3
        // compare data once there is data in in_mb0/in_mb1/in_mb2 and out_mb
        // first, get om from out_mb, and im from one of in_mbs
        out_mb.get(om);
        case(om.id)
          0: in_mbs[0].get(im);
          1: in_mbs[1].get(im);
          2: in_mbs[2].get(im);
          default: $fatal("id %0d is not available", om.id);
        endcase

        if(om.data != im.data) begin
          this.error_count++;
          $error("[CMPFAIL] Compared failed! mcdt out data %8x ch_id %0d is not equal with channel in data %8x", om.data, om.id, im.data);
        end
        else begin
          $display("[CMPSUCD] Compared succeeded! mcdt out data %8x ch_id %0d is equal with channel in data %8x", om.data, om.id, im.data);
        end
        this.cmp_count++;
      end
    endtask
  endclass

2. tb文件

在tb文件与之前最大的区别是加入了$value$plusargs("TESTNAME=%s", name)
通过在仿真器输入vsim -novopt -solvefaildebug -sv_seed random +TESTNAME=chnl_root_test work.tb2,来运行相应的test。

  • random是随机时的种子,每次的种子不同,产生的随机数才会不同,如果每次输入的种子,生成的随机数也是相同的。
  • chnl_root_test是测试的名字,可以修改为basic或者其他,设定好的名字。

传递不同的随机种子,和测试名称即可完成响应的测试,是回归测试的雏形。

`timescale 1ns/1ps

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
  clocking mon_ck @(posedge clk);
    default input #1ns output #1ns;
    input ch_data, ch_valid, ch_ready, ch_margin;
  endclocking
endinterface

interface mcdt_intf(input clk, input rstn);
  logic [31:0]  mcdt_data;
  logic         mcdt_val;
  logic [ 1:0]  mcdt_id;
  clocking mon_ck @(posedge clk);
    default input #1ns output #1ns;
    input mcdt_data, mcdt_val, mcdt_id;
  endclocking
endinterface

module tb3_ref;
  logic         clk;
  logic         rstn;
  
  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_if.mcdt_data  )
    ,.mcdt_val_o  (mcdt_if.mcdt_val   )
    ,.mcdt_id_o   (mcdt_if.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

  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

此时结构框图:

MCDF实验3:群鸦的盛宴(从verilog到SV的入门lab3)_第2张图片

  • do_congig随机化gen产生随机数,agent中的init通过信箱与gen连接,拿到数据后,init发送数据,并反馈给gen。gen得到反馈后会在sam放入钥匙,检测钥匙的数量,可以知道数据传输的状态。
  • init和monit是死循环,无法终止,仿真结束只能靠gen。

关于实验代码、验证相关的资料,你找的到的找不到的都可以从公众号"袁小丑"免费获取!

你可能感兴趣的:(System,Verilog,fpga开发)