MCDF_Lab2

Lab2主要是使用之前学习的接口、仿真开始和结束、类以及包的使用,来优化Lab1的验证结构。逐渐从使用硬件盒子过渡到使用接口和软件盒子(class)来验证设计。

一、接口的使用

  • 将验证组件和DUT之间通过接口来连接,所以验证组件chnl_initiator的端口变得非常干净,即chnl_intf。在使用接口之前需要定义接口chnl_intf和内部的接口,同时也要声明一个时钟块,它的功能是为了消除可能存在的竞争问题,确保时钟驱动数据之间有一定的延迟,以便于DUT顺利采样。
  • 引入接口后的代码,例化的实例包括了只产生数据的channel_generator,只负责发送数据的chnl_initiator以及作为验证组件和DUT之间的接口chnl_intf

实验要求:

  1. chnl_initiator发送的数据例如validdata与时钟clk均在同一个变化沿,没有任何延迟。这种0延迟的数据发送不利于波形的查看,要在之前的代码基础上使用intf.ck的方式来做数据驱动,再观察波形,查看驱动的数据与时钟上升沿的延迟。
  2. 为了更好地控制相邻数据之间的空闲间隔,引入了一个变量idle_cycles,它表示相邻有效数据之间的间隔。之前的代码会使得有效数据之间保持固定的一个空闲周期,需要使用idle_cycles来灵活控制有效数据之间的空闲周期。

二、仿真的开始和结束

使用fork-join语句来实现chnl_initiator同时发送数据。还将不同的test也组装到task中,以此来区分不同的测试内容,这是由于每一个测试任务的测试目的和要求都不相同。
实验要求:

  • 实现burst_test()方法,使得每个chnl_initiatoridle_cycles设置为0,同时发送500个数据,最后结束测试。
  • 实现fifo_full_test()方法,使得无论采取什么数值的idle_cycles也无论发送多少个数据,只要各个chnl_initiator不停发送使得对应的channel缓存变为满标志(ready拉低),那么可以在三个channel都拉低ready时(不用同时拉低,可以先后拉低),便可以立即结束测试。

三、类的例化和类的成员

将之前用来封装验证功能的硬件盒子(module)中的数据和内容移植到软件盒子(class)中来。
实验要求:

  • 将module的chnl_initiatorchnl_generator改成class的chnl_initiatorchnl_generator,同时定义一个用来封装发送数据的类chnl_trans
  • 由于每一个chnl_initiator都需要使用接口chnl_intf来发送数据,在发送数据之前需要确保chnl_initiator中的接口不是悬空的,即需要由外部被传递。所有需要通过调用chnl_initiator的方法来完成接口的传递。

四、包的定义和类的继承

进一步引入新的类chnl_agentchnl_root_testchnl_basic_testchnl_burst_testchnl_fifo_full_test,同时将所有的类(都是与channel相关的验证组件类)封装到专门包裹软件类的容器packagechnl_pkg中,且完成编译。编译后的chnl_pkg会被默认编译到work库中,与其他的module是并列放置的。
chnl_agent:将它作为一个标准组件单元,包括generator、

  • driver(initiator)和monitor。所以chnl_initiatorchnl_generator应该放在agent中例化。
  • task实现的测试任务由类来实现:父类chnl_root_test
    ,子类chnl_basic_testchnl_burst_testchnl_fifo_full_test

五、验证结构框架图

在这里插入图片描述
在这里插入图片描述

六、代码实现

接口的使用
接口chnl_intf

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 chnl0_if(.*);
  chnl_intf chnl1_if(.*);
  chnl_intf chnl2_if(.*);

将接口里面的各个信号连接到mcdt上面

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

chnl_initiator激励

module chnl_initiator(chnl_intf intf);
  string name;
  int idle_cycles = 1;
  function automatic void set_idle_cycles(int n);
    idle_cycles = n;
  endfunction
  function automatic void set_name(string s);
    name = s;
  endfunction
  task automatic chnl_write(input logic[31:0] data);
    @(posedge intf.clk);
    // 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);
    // Apply variable idle_cycles and decide how many idle cycles to be
    // inserted between two sequential data
    repeat(idle_cycles) chnl_idle();
  endtask
  task automatic chnl_idle();
    @(posedge intf.clk);
    // 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
endmodule

例化initiator,需要接口做激励,将接口作为参数

  chnl_initiator chnl0_init(chnl0_if);
  chnl_initiator chnl1_init(chnl1_if);
  chnl_initiator chnl2_init(chnl2_if);

chnl_generator产生数据

module chnl_generator;
  int chnl_arr[$];
  int num;
  int id;
  function automatic void initialize(int n);
    id = n;
    num = 0;
  endfunction
  function automatic int get_data();
    int data;
    data = 'h00C0_0000 + (id<<16) + num;
    num++;
    chnl_arr.push_back(data);
    return data;
  endfunction
endmodule

例化chnl_generator

  chnl_generator chnl0_gen();
  chnl_generator chnl1_gen();
  chnl_generator chnl2_gen();

通过intf.ck来做数据驱动

  	@(posedge intf.clk);
    intf.drv_ck.ch_valid <= 1;
    intf.drv_ck.ch_data <= data;
    @(negedge intf.clk);
    wait(intf.ch_ready === 'b1);

idle_cycles控制空闲间隔

int idle_cycles = 1;

function automatic void set_idle_cycles(int n);
    idle_cycles = n;
endfunction

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

// inserted between two sequential data
repeat(idle_cycles) chnl_idle();

验证组件初始化

initial begin 
    // verification component initializationi
    chnl0_gen.initialize(0);
    chnl1_gen.initialize(1);
    chnl2_gen.initialize(2);
    chnl0_init.set_name("chnl0_init");
    chnl1_init.set_name("chnl1_init");
    chnl2_init.set_name("chnl2_init");
    chnl0_init.set_idle_cycles(0);
    chnl1_init.set_idle_cycles(0);
    chnl2_init.set_idle_cycles(0);   
end

复位后连续发送数据

  initial begin
    @(posedge rstn);
    repeat(5) @(posedge clk);
    repeat(100) begin
      chnl0_init.chnl_write(chnl0_gen.get_data());      
    end
    chnl0_init.chnl_idle(); 
  end

仿真的开始和结束
fork...join连续并行发送数据

fork
      repeat(100) chnl0_init.chnl_write(chnl0_gen.get_data());
      repeat(100) chnl1_init.chnl_write(chnl1_gen.get_data());
      repeat(100) chnl2_init.chnl_write(chnl2_gen.get_data());
join

basic_test方法

  // each channel send data with idle_cycles inside [1:3]
  // each channel send out 100 data
  // then to finish the test
 task automatic basic_test();
    // verification component initializationi
    chnl0_gen.initialize(0);
    chnl1_gen.initialize(1);
    chnl2_gen.initialize(2);
    chnl0_init.set_name("chnl0_init");
    chnl1_init.set_name("chnl1_init");
    chnl2_init.set_name("chnl2_init");
    chnl0_init.set_idle_cycles($urandom_range(1, 3));
    chnl1_init.set_idle_cycles($urandom_range(1, 3));
    chnl2_init.set_idle_cycles($urandom_range(1, 3));
    $display("basic_test initialized components");
    wait (rstn === 1'b1);
    repeat(5) @(posedge clk);
    $display("basic_test started testing DUT");
    // Please check the SV book for fork-join basic knowledge
    // and get understood it is for parallel thread running
    fork
      repeat(100) chnl0_init.chnl_write(chnl0_gen.get_data());
      repeat(100) chnl1_init.chnl_write(chnl1_gen.get_data());
      repeat(100) chnl2_init.chnl_write(chnl2_gen.get_data());
    join
    $display("basic_test finished testing DUT");
  endtask

burst_test方法

  // each channel send data with idle_cycles == 0
  // each channel send out 500 data
  // then to finish the test
  task automatic burst_test();
    // verification component initializationi
    chnl0_gen.initialize(0);
    chnl1_gen.initialize(1);
    chnl2_gen.initialize(2);
    chnl0_init.set_name("chnl0_init");
    chnl1_init.set_name("chnl1_init");
    chnl2_init.set_name("chnl2_init");
    chnl0_init.set_idle_cycles(0);
    chnl1_init.set_idle_cycles(0);
    chnl2_init.set_idle_cycles(0);
    $display("basic_test initialized components");
    wait (rstn === 1'b1);
    repeat(5) @(posedge clk);
    $display("basic_test started testing DUT");
    // Please check the SV book for fork-join basic knowledge
    // and get understood it is for parallel thread running
    fork
      begin
	    repeat(500) chnl0_init.chnl_write(chnl0_gen.get_data());
		chnl0_init.chnl_idle();
	  end
      begin
	    repeat(500) chnl1_init.chnl_write(chnl1_gen.get_data());
		chnl1_init.chnl_idle();
	  end
	  begin
	    repeat(500) chnl2_init.chnl_write(chnl2_gen.get_data());
		chnl2_init.chnl_idle();
	  end
    join
	fork
      wait(chnl0_init.intf.ch_margin == 'h20);
      wait(chnl1_init.intf.ch_margin == 'h20);
      wait(chnl2_init.intf.ch_margin == 'h20);
    join
    $display("basic_test finished testing DUT");
  endtask

fifo_full_test方法

  // 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
  task automatic fifo_full_test();
    // verification component initializationi
    chnl0_gen.initialize(0);
    chnl1_gen.initialize(1);
    chnl2_gen.initialize(2);
    chnl0_init.set_name("chnl0_init");
    chnl1_init.set_name("chnl1_init");
    chnl2_init.set_name("chnl2_init");
    chnl0_init.set_idle_cycles(0);
    chnl1_init.set_idle_cycles(0);
    chnl2_init.set_idle_cycles(0);
    $display("fifo_full_test started testing DUT");
    fork: fork_all_run
	  forever chnl0_init.chnl_write(chnl0_gen.get_data());
	  forever chnl1_init.chnl_write(chnl1_gen.get_data());
	  forever chnl2_init.chnl_write(chnl2_gen.get_data());
    join_none
    $display("fifo_full_test: 3 initiators running now");

    $display("fifo_full_test: waiting 3 channel fifos to be full");
    fork
      wait(chnl0_init.intf.ch_margin == 0);
      wait(chnl1_init.intf.ch_margin == 0);
      wait(chnl2_init.intf.ch_margin == 0);
    join
    $display("fifo_full_test: 3 channel fifos have reached full");

    $display("fifo_full_test: stop 3 initiators running");
    disable fork_all_run;
    $display("fifo_full_test: set and ensure all agents' initiator are idle state");
    fork
      chnl0_init.chnl_idle();
      chnl1_init.chnl_idle();
      chnl2_init.chnl_idle();
    join

    $display("fifo_full_test waiting DUT transfering all of data");
    fork
      wait(chnl0_init.intf.ch_margin == 'h20);
      wait(chnl1_init.intf.ch_margin == 'h20);
      wait(chnl2_init.intf.ch_margin == 'h20);
    join
    $display("fifo_full_test: 3 channel fifos have transferred all data");

    $display("fifo_full_test finished testing DUT");
  endtask

初始化测试任务

  initial begin 
    basic_test(); 
    burst_test();
    fifo_full_test();
    $display("*****************all of tests have been finished********************");
    $finish();
  end

类的例化和类的成员
chnl_initiator从硬件盒子module转换成软件盒子class,chnl_initiator里面的接口参数以及端口,转变成了class里面的成员变量,注意class里面声明接口的指针必须要加上virtual关键字。

class chnl_initiator;
  local string name;
  local int idle_cycles;
  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);
 
      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);
 
      repeat(this.idle_cycles) chnl_idle();
    endtask
    
    task chnl_idle();
      @(posedge intf.clk);
 
      intf.drv_ck.ch_valid <= 0;
      intf.drv_ck.ch_data <= 0;
    endtask
endclass

chnl_trans类封装发送的数据

class chnl_trans;
  int data;
  int id;
  int num;
endclass

chnl_generator从硬件盒子module转换成软件盒子class

class chnl_generator;
  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

接口的例化以及chnl_initiatorchnl_generator句柄的声明

  chnl_intf chnl0_if(.*);
  chnl_intf chnl1_if(.*);
  chnl_intf chnl2_if(.*);
  
  chnl_initiator chnl0_init;
  chnl_initiator chnl1_init;
  chnl_initiator chnl2_init;
  chnl_generator chnl0_gen;
  chnl_generator chnl1_gen;
  chnl_generator chnl2_gen;

chnl_initiatorchnl_generator的例化

 initial begin 
 
    // instantiate the components chn0/1/2_init chnl0/1/2_gen
	chnl0_init = new("chnl0_init");
	chnl1_init = new("chnl1_init");
	chnl2_init = new("chnl2_init");
	chnl0_gen = new(0);
	chnl1_gen = new(1);
	chnl2_gen = new(2);

 
    // assign the interface handle to each chnl_initiator objects
	chnl0_init.set_interface(chnl0_if);
	chnl1_init.set_interface(chnl1_if);
	chnl2_init.set_interface(chnl2_if);

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

包的定义和类的继承
相比于之前的代码,新添加了一个chnl_agent类,该类包括了chnl_initiatorchnl_generator两个类,之前的代码chnl_initiatorchnl_generator直接在TB里面例化,而有了chnl_agent类之后,可以直接例化一个chnl_agent类,例化后就可以包括那两个类。

class chnl_agent;
    chnl_generator gen;
    chnl_initiator init;
    local int ntrans;
    local 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());
    endtask
 endclass: chnl_agent

chnl_root_test类,从顶层的test里面例化3个agent,也就是例化了3个initiator和3个generator。其中例化时传递的参数ntrans会供agent的run()来使用,进而run()会调用initiator的chnl_write()从generator中拿到发送数据。这样,chnl_root_testchnl_agent()之间建立了联系,chnl_agent()chnl_initiatorchnl_generator之间也建立了联系。而chnl_root_test类中也有run(),它的run()作用是调用agent的run(),并且通过fork…join并行运行3个agent。
new函数构建了整个验证环境的层次,而run函数使得层次之间可以互动起来。

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

chnl_basic_test类继承chnl_root_test类,在new函数里调用super.new(),否则将无法执行父类的new,也就无法例化父类中的agent。同时继承还可以减少重复的代码量。

 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

chnl_burst_test类继承chnl_root_test

 class chnl_burst_test extends chnl_root_test;
 
    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

chnl_fifo_full_test类继承chnl_root_test

  class chnl_fifo_full_test extends chnl_root_test;
    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

从包chnl_pkg中引入定义的类

  import chnl_pkg::*;

发送激励之前,还需要传递接口。TB看到的最高的验证环境的层次是软件的对象test,test裸露在TB环境中,想要把interface传递进来,那么test里面要有set_interface(),然后test拿到指针,这些指针通过agent再次调用agent的set_interface(),agent拿到指针后再次调用init.set_interface(),进而发送激励。整个interface的流转是从test->agent->initiator层层递进传递。最顶层test传递完接口之后,调用run()运行起来,test的run()又调用agent的run(),一层层调用各个组件的run()。test里面的new()是调用了agent里面的new(),agent里面的new()又调用了generator和initiator里面的new(),这样从顶层往下一层一层创建实例。
test继承的是构建环境的层次结构,变化的是激励发送的内容。

  initial begin 
  	// Instantiate the three test environment
    basic_test = new();
    burst_test = new();
    fifo_full_test = new();

 
    // 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);

 
    // START TESTs
    basic_test.run(); 
    burst_test.run();
    fifo_full_test.run();
    $display("*****************all of tests have been finished********************");
    $finish();
  end
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值