路科IC验证--SVlab2中的细节

仿真器使用VCS

tb4代码如下:

复制代码
`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
endinterface

package chnl_pkg;
  class chnl_trans;
    int data;
    int id;
    int num;
  endclass: chnl_trans
  
  class chnl_initiator;
    local string name;
    local int idle_cycles;
    local virtual chnl_intf intf;
  
    function new(string name = "chnl_initiator");
      this.name = name;
      this.idle_cycles = 1;
    endfunction
  
    function void set_idle_cycles(int n);
      this.idle_cycles = n;
    endfunction
  
    function void set_name(string s);
      this.name = s;
    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 chnl_write(input chnl_trans t);
      @(posedge intf.clk);
      // USER TODO 1.1
      // Please use the clocking drv_ck of chnl_intf to drive data
      intf.drv_ck.ch_valid <= 1;
      intf.drv_ck.ch_data <= t.data;
          @(negedge intf.clk);
      wait(intf.ch_ready === 'b1);
      $display("%t channel initiator [%s] sent data %x", $time, name, t.data);
      // USER TODO 1.2
      // Apply variable idle_cycles and decide how many idle cycles to be
      // inserted between two sequential data
      repeat(this.idle_cycles) chnl_idle();
    endtask
    
    task chnl_idle();
      @(posedge intf.clk);
      // USER TODO 1.1
      // Please use the clocking drv_ck of chnl_intf to drive data
      intf.drv_ck.ch_valid <= 0;
      intf.drv_ck.ch_data <= 0;
    endtask
  endclass: chnl_initiator
  
  class chnl_generator;
    chnl_trans trans[$];
    int num;
    int id;
    function new(int n);
      this.id = n;
      this.num = 0;
    endfunction
    function chnl_trans get_trans();
      chnl_trans t = new();
      t.data = 'h00C0_0000 + (this.id<<16) + this.num;
      t.id = this.id;
      t.num = this.num;
      this.num++;
      this.trans.push_back(t);
      return t;
    endfunction
  endclass: chnl_generator

  class chnl_agent;
    chnl_generator gen;
    chnl_initiator init;
    local int ntrans;
    virtual chnl_intf vif;
    function new(string name = "chnl_agent", int id = 0, int ntrans = 1);
      this.gen = new(id);
      this.init = new(name);
      this.ntrans = ntrans;
    endfunction
    function void set_ntrans(int n);
      this.ntrans = n;
    endfunction
    function void set_interface(virtual chnl_intf vif);
      this.vif = vif;
      init.set_interface(vif);
    endfunction
    task run();
      repeat(this.ntrans) this.init.chnl_write(this.gen.get_trans());
      this.init.chnl_idle(); // set idle after all data sent out
    endtask
  endclass: chnl_agent

  class chnl_root_test;
    chnl_agent agent[3];
    protected string name;
    function new(int ntrans = 100, string name = "chnl_root_test");
      foreach(agent[i]) begin
        this.agent[i] = new($sformatf("chnl_agent%0d",i), i, ntrans);
      end
      this.name = name;
      $display("%s instantiate objects", this.name);
    endfunction
    task run();
      $display("%s started testing DUT", this.name);
      fork
        agent[0].run();
        agent[1].run();
        agent[2].run();
      join
      $display("%s waiting DUT transfering all of data", this.name);
      fork
        wait(agent[0].vif.ch_margin == 'h20);
        wait(agent[1].vif.ch_margin == 'h20);
        wait(agent[2].vif.ch_margin == 'h20);
      join
      $display("%s: 3 channel fifos have transferred all data", this.name);
      $display("%s finished testing DUT", this.name);
    endtask
    function void set_interface(virtual chnl_intf ch0_vif, virtual chnl_intf ch1_vif, virtual chnl_intf ch2_vif);
      agent[0].set_interface(ch0_vif);
      agent[1].set_interface(ch1_vif);
      agent[2].set_interface(ch2_vif);
    endfunction
  endclass

  // each channel send data with idle_cycles inside [1:3]
  // each channel send out 200 data
  // then to finish the test
  class chnl_basic_test extends chnl_root_test;
    function new(int ntrans = 200, string name = "chnl_basic_test");
      super.new(ntrans, name);
      foreach(agent[i]) begin
        this.agent[i].init.set_idle_cycles($urandom_range(1, 3));
      end
      $display("%s configured objects", this.name);
    endfunction
  endclass: chnl_basic_test

  // USER TODO 4.2
  // Refer to chnl_basic_test, and extend another 2 tests
  // chnl_burst_test, chnl_fifo_full_test
  // each channel send data with idle_cycles == 0
  // each channel send out 500 data
  // then to finish the test
  class chnl_burst_test extends chnl_root_test;
    //USER TODO
    function new(int ntrans = 500, string name = "chnl_burst_test");
      super.new(ntrans, name);
      foreach(agent[i]) begin
        this.agent[i].init.set_idle_cycles(0);
      end
      $display("%s configured objects", this.name);
    endfunction
  endclass: chnl_burst_test

  // USER TODO 4.2
  // The test should be immediately finished when all of channels
  // have been reached fifo full state, but not all reaching
  // fifo full at the same time
  class chnl_fifo_full_test extends chnl_root_test;
    // USER TODO
    function new(int ntrans = 1_000_000, string name = "chnl_fifo_full_test");
      super.new(ntrans, name);
      foreach(agent[i]) begin
        this.agent[i].init.set_idle_cycles(0);
      end
      $display("%s configured objects", this.name);
    endfunction
    task run();
      $display("%s started testing DUT", this.name);
      fork: fork_all_run
        agent[0].run();
        agent[1].run();
        agent[2].run();
      join_none
      $display("%s: 3 agents running now", this.name);

      $display("%s: waiting 3 channel fifos to be full", this.name);
      fork
        wait(agent[0].vif.ch_margin == 0);
        wait(agent[1].vif.ch_margin == 0);
        wait(agent[2].vif.ch_margin == 0);
      join
      $display("%s: 3 channel fifos have reached full", this.name);

      $display("%s: stop 3 agents running", this.name);
      disable fork_all_run;
      $display("%s: set and ensure all agents' initiator are idle state", this.name);
      fork
        agent[0].init.chnl_idle();
        agent[1].init.chnl_idle();
        agent[2].init.chnl_idle();
      join

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

      $display("%s finished testing DUT", this.name);
    endtask
  endclass: chnl_fifo_full_test

endpackage: chnl_pkg


module tb4_ref;
  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

  // USER TODO 4.1
  // import defined class from chnl_pkg
  import chnl_pkg::*;

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

  chnl_basic_test basic_test;
  chnl_burst_test burst_test;
  chnl_fifo_full_test fifo_full_test;

  initial begin 
    basic_test = new();
    burst_test = new();
    fifo_full_test = new();

    // USER TODO 4.4
    // assign the interface handle to each chnl_initiator objects
    basic_test.set_interface(chnl0_if, chnl1_if, chnl2_if);
    burst_test.set_interface(chnl0_if, chnl1_if, chnl2_if);
    fifo_full_test.set_interface(chnl0_if, chnl1_if, chnl2_if);

    // USER TODO 4.5
    // START TESTs
    basic_test.run(); 
    burst_test.run();
    fifo_full_test.run();
    $display("*****************all of tests have been finished********************");
    $finish();
  end

endmodule
tb4
复制代码

 

复制代码
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
复制代码

【问题】:接口chnl_intf的clocking驱动或采样具体细节是怎么实现的?

【解答与分析】

可以通过时钟块来解决采样和数据驱动的竞争问题,如以上代码,在clk的上升沿前1ns时,对输入信号进行采样,在clk的上升沿后1ns时,对输出信号做驱动。

时钟块drv_ck中的信号是clocking采样以后的结果,会比原始clk时钟信号多一个延时。比如接口中定义的ch_data信号会比时钟块中定义的输出信号ch_data会更早发生。

注意:只要做采样,一定是采样clk信号对应左边的值。

      fork
        wait(agent[0].vif.ch_margin == 0);
        wait(agent[1].vif.ch_margin == 0);
        wait(agent[2].vif.ch_margin == 0);
      join

【问题】:为什么要wait接口中的ch_margin信号,接口时钟块中的ch_margin信号不行吗?

【解答与分析】

wait语句表明使用的是组合逻辑,有时钟参与的是时序逻辑。

上一问题的解答提到,时钟块drv_ck中的信号是clocking采样以后的结果,会比原始clk时钟信号多一个延时。

因此:直接使用接口中的信号能更早得到margin为0的信息,在代码的实现角度来说更好,当然使用时钟块drv_ck中的ch_margin信号也没问题。

 

 

复制代码
  task automatic chnl_write(input logic[31:0] data);
    @(posedge intf.clk);
    // USER TODO 1.1
    // Please use the clocking drv_ck of chnl_intf to drive data
    intf.drv_ck.ch_valid <= 1;
    intf.drv_ck.ch_data <= data;
    @(negedge intf.clk);
    wait(intf.ch_ready === 'b1);
    $display("%t channel initiator [%s] sent data %x", $time, name, data);
    // USER TODO 1.2
    // Apply variable idle_cycles and decide how many idle cycles to be
    // inserted between two sequential data
    repeat(idle_cycles) chnl_idle();
  endtask
复制代码

【问题】如何理解chnl_write任务里面的时序逻辑采样和组合逻辑采样?

【解答与分析】

在设计中实现的是组合逻辑,在验证中要恰当的模拟出一种组合逻辑。而组合逻辑是在一拍里边,它能做出一些相应的反应。

组合逻辑的上升沿一拍:

 

 

 

 因此在一拍内采样的信号都可以模拟组合逻辑,如取红线位置(时钟的下降沿)。

时序逻辑的上升沿:

 

 

 因此:

1.  intf.drv_ck.ch_valid <= 1;intf.drv_ck.ch_data <= data; 是按照时钟块采样得到稳定的值。采样稳定用clocking块。

2.  @(negedge intf.clk); wait(intf.ch_ready === 'b1); 通过clk模拟组合逻辑的一拍的过程,得到接口中ready信号为1的过程。不能用clocking块模拟组合逻辑,因为clocking块表示的是一个采样的过程,采样的过程一定是采样的clk的上一拍,所以clocking块无论如何都不能模拟出组合逻辑的效果。

 

复制代码
class chnl_generator;//tb3的代码
  chnl_trans trans[$];
  int num;
  int id;
  chnl_trans t;
  function new(int n);
    this.id = n;
    this.num = 0;
  endfunction
  function chnl_trans get_trans();
    t = new();
    t.data = 'h00C0_0000 + (this.id<<16) + this.num;
    t.id = this.id;
    t.num = this.num;
    this.num++;
    this.trans.push_back(t);
    return t;
  endfunction
endclass
复制代码

 

【问题】上面代码中t=new()放在get_trans( )函数里和放在new()函数里有什么区别?

【解答与分析】

t=new()若放在new( )函数里,则每次例化的实例只有一个,因为几个new几个实例。因此tb3后面的代码中repeat都是对同一个实例进行修改,即100个句柄指向同一个实例,而t=new( )若放在get_trans( )函数里,则100个句柄分别指向100个实例,并放入到队列中。

在tb3中两种放法直接运行都没问题,但是t=new()若放在new( )函数里会有一个潜在的问题,即不利于调试,在同一时刻只能看到一个实例中的数据。

 

posted @   傅红雪a  阅读(264)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
Live2D
欢迎阅读『路科IC验证--SVlab2中的细节』
点击右上角即可分享
微信分享提示