MCDF实验2:冰雨的风暴(从verilog到SV的入门lab2)

前言: 在实验1,通过A产生动态数组并赋值给B,B再传给c,完成了激励的发送。总体来说很容易上手。而在实验2产生数据的方式、赋值的方式变为了抽象的概念,对于初学者可能不是很好理解,但你看完这篇文章,不会都难。

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

实验2需要用到接口、类、包这几个主要知识,大家可以先去学习哦!在实验1使用到的大多是module,在实验2把这些模块修改为了类,并使用包来包装class。

结构框图如下:
在这里插入图片描述
与上次的框图相比主要有这几点变化:

  1. 接口:将之前传输数据的几条线改为了一t条总线ch_if。

  2. 实验1产生数据发送数据在一个模块中,实验2将这两部分抽象的分开。
    将产生什么样的数据放在了generator这个类中;将怎么按照时序传输放在了initiator这个类中;将传输数据的数量放在在了chnl_root_test这个类中。

  3. 类:将之前定义传输数据方法的模块chnl_init,修改为了类initiator。

  4. 包: 将generator和initiator装在一个包中。

下面是实验2的代码,将实验代码分成了几个部分,每一步都做了详细的讲解,想学的话有点耐心,看完包会。

源代码

1. interface

第一个部分是接口interface。将传输数据时用到的时序集中放到interface中,并使用时钟块clocking驱动。

`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

2. 定义包

将仿真需要测试全部放到包中,将之前使用的模块修改为了类。其中包括chnl_trans、chnl_initiator、chnl_generator、chnl_agent、chnl_root_test、chnl_burst_test、chnl_basic_test、chnl_fifo_full_test。

package chnl_pkg;
endpackage

2.1 chnl_trans

可以理解为一个空白的类,先往下看。

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

2.2 chnl_initiator

在chnl_initiator中定义了function :new、set_idle_cycles、set_name,task:set_interface、chnl_idle。

  class chnl_initiator;
    local string name;//名字
    local int idle_cycles;//循环时间
    local virtual chnl_intf intf;//与接口相接
2.21 function new

当调用initiator中的new函数时,修改name。initiator.new (“无敌”),此时名字为无敌。

    function new(string name = "chnl_initiator"); // 将上边的变量初始化
      this.name = name;
      this.idle_cycles = 1;
    endfunction
2.22 function set_idle_cycles

当调用 set_idle_cycles,可以将idle_cycles改为设定的值。例如initiator.set_idle_cycles (1),此时idle_cycles为1。

    function void set_idle_cycles(int n);//可以设定时间的函数
      this.idle_cycles = n;
    endfunction
2.23 function set_name

set_name可以修改name的function。

    function void set_name(string s);//可以设定名字的函数
      this.name = s;
    endfunction
2.24 function set_interface

set_interface 用来实现initiator与interface的连接。

    function void set_interface(virtual chnl_intf intf); //将interface做了例化,当成功例化,initior的intf会和接口相接。
      if(intf  ==  null)
        $error("interface handle is NULL, please check if target interface has been intantiated");
      else
        this.intf = intf;
    endfunction
2.25 task chnl_write

chnl_write是发送数据的任务。

  • chnl_write会把chnl_trans中的data发送出去。
  • 每次发送数据时需要考虑时序,将这些时序放在任务chnl_write中,这样每次发送数据只需要写入数据即可。
  • 发送数据完成后,进入循环repeat。其中循环次数为上文中的idle_cycles,循环的内容为另一个task chnl_idle。循环为了产生几次空拍,也可以不要空拍。
 	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
2.26 task chnl_idle

chnl_idle中将valid和data都置为0,意思是此时不传输数据,也就是空拍。repeat(this.idle_cycles) chnl_idle(); = repeat(循环次数)空拍,这句话用来循环几次空拍。

    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
 

2.3 chnl_generator

定义了function new、get_trans。

在generator中用到了之前说的空白的类,声明了句柄trans并定义为队列,这个队列用来干嘛呢?先不管它。

  class chnl_generator;
    chnl_trans trans[$]; //例化chnl_trans
    int num;
    int id;
2.31 function new

调用new()给igeneration 中的变量id赋值 ,并初始化num。

    function new(int n);
      this.id = n;
      this.num = 0;
    endfunction
2.32 function get_trans

get_trans是在对象中产生数据,并把句柄传到上边产生的队列trans[$]中。

  • 首先第二行是声明句柄t,并例化开辟动态空间。t.data = 'h00C0_0000 + (this.id<<16) +this.num;是将产生的数据传入动态空间。
  • t.id = this.id;将generation的id 传入动态空间。
  • t.num = this.num;将generation中的num传入动态空间。最后返回值为句柄t。

动态空间也就是对象。

    function chnl_trans get_trans();
      chnl_trans t = new();//声明句柄t并例化
      t.data = 'h00C0_0000 + (this.id<<16) + this.num; //值
      t.id = this.id;
      t.num = this.num;
      this.num++;
      this.trans.push_back(t); //将t句柄放到队列里
      return t;
    endfunction
  endclass: chnl_generator

2.4 chnl_agent

定义了function new、set_ntrans、set_interface和task run。
并将initiator,generator例化在了类agent里。

  class chnl_agent;
    chnl_generator gen;
    chnl_initiator init;//将initiator,generator例化在类agent里
    local int ntrans;
    virtual chnl_intf vif;
2.41 function new

通过agent new 把int id、string name、int ntrans传给 gen的new函数、init的new函数和ntrans。

    function new(string name = "chnl_agent", int id = 0, int ntrans = 1);
      this.gen = new(id);
      this.init = new(name);
      this.ntrans = ntrans;
    endfunction
2.42 function set_ntrans

通过set_ntrans来改变ntrans的值。

    function void set_ntrans(int n);
      this.ntrans = n;
    endfunction  
2.43 function set_interface

调用set_interface,可以实现agent 连接interface,并通过调用initiator中的set_interface,让initiator也连接interface。

    function void set_interface(virtual chnl_intf vif);
      this.vif = vif;
      init.set_interface(vif);
    endfunction
2.44 task run

把task的行为用中文写出来大概就是:

repeat(发送多少次数据)发送数据的task chnl_write(产生的数据的task get_trans)。调用init.产生空拍的任务。

  • 在get_trans这个任务中,把数据通过句柄t放入到了动态空间里,返回值为句柄t。
  • task chnl_write这个任务中输入就是get_trans的返回值t,并将t.data发送出去。
  • 最后调用产生数据空拍的任务。
    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

2.5 chnl_root_test

在chnl_root_test中,定义了function new、set_interface,task run。
在agent这个类中已经将initiator和generator包在了一起,需要用到3个agent。

  class chnl_root_test;
    chnl_agent agent[3];//3个agent
    protected string name;
2.51 function new

root_test中的new函数可以定义ntrans,name。并通过freach遍历agen[0]、agen[1]、agen[2],同时调用了agent中的new函数。

    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

简单代入i=0,

this.agent[0] = new($sformatf("chnl_agent%0d",0), 0, 100);

$sformatf(“chnl_agent%0d”,0)的意思等同于(“chnl_agent0”),有人可能会想那直接写后边的(“chnl_agent0”)不就行了?为啥还要弄变量i?弄这么复杂?
如果写(“chnl_agent0”)那肯定还需要写(“chnl_agent1”)(“chnl_agent2”),需要三行, $sformatf(“chnl_agent%0d”,i)引入了变量只需要写一行,100个agent也是一行搞定。

this.agent[0] = new("chnl_agent0", 0, 100);

agent的new函数调用了gen和init的new函数。

      this.gen = new(0);
      this.init = new("chnl_agent0");
      this.ntrans = 100;

在gen的new函数中会把0调用,此时gen中的id为0。function get_trans中的id也会使用传入来的id 即0。

function new(0);
      this.id = 0;
      this.num = 0;
endfunction

在init的new函数中name变为了"chnl_agent0",name在init中主要用在打印上。下面代码是init.new。

    function new( "chnl_agent0"); // 将上边的变量初始化
      this.name = "chnl_agent0";
      this.idle_cycles = 1;
    endfunction
2.52 task run

task run 实现了数据的传输,当三个fifo传输完毕数据后,退出task。

    task run();//运行三个agent
      $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//等三个fifo传输完毕,退出
        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

task run调用了agent run,三个agent分别调用各自的run。 ntrans在上边new函数中将值赋给了各个agent。下面代码是agent.run。

   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

task run中的第一个fork join是等三个agent都运行各自的run后才会退出这个fork join执行下一步。
第二个fork join想要表达的是等三个ch_margin都满足过 == 'h20,也就是数据传输完毕,才会退出这个fork join执行下一步。对于为何ch_margin == 'h20代表的是传输完毕呢?其实这个东西在fifo的设计中可能明确的说明了。
值得主要的点是fork join 、wait的用法,而不是张三为啥叫张三,1为啥是1,ch_margin == 'h20为啥数据传输完毕?

2.53 function set_interface

root_test调用了agent中的set_interface。

    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

agent中的set_interface,实现了agent与interface的连接,并且调用了init.set_interface。

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

init.set_interface实现了initiator与interface的连接。

    function void set_interface(virtual chnl_intf intf); //将interface做了例化,当成功例化,initior的intf会和接口相接。
      if(intf  ==  null)
        $error("interface handle is NULL, please check if target interface has been intantiated");
      else
        this.intf = intf;
    endfunction

上述连接interface的过程可以类比为root_test给三个agent规定了暗号,agent把暗号传给了他的小弟initiator。
发送数据的过程,initiator中的组织A可以让小兵在合适的时间进攻,组织B可以让小兵休息一定时间;generator中的组织专门产生小兵的地方;agent把generator中的小兵送到了initator的组织中,同时规定了冲锋多少次,每次冲锋按照组织A规定的时间冲锋,并且冲锋后按照组织B计划的休息时间时间。
通透了吧???你应该通透了。

下文basic_test、burst_test、 fifo_full_test都是root_test的子类。
你可能疑问直接调用root_test不行吗?为啥还要弄这么多子类?
可以这样理解:root_test是一个非常全面的模板,写文章时可以直接套用模板,也可以选择复制模板到新world中修改。直接使用模板,这次文章写完,当下次需要修改一些地方的时候,分不清楚哪里是模板哪里是你写的。每次复制粘贴写文章,改动哪里一目了然。而子类就是"复制粘贴"。

2.6 子类chnl_basic_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

需要注意的是,使用super.new()来调用父类的new(),在这个test中随机了set_idle_cycles,也就是idle_cycles的重复次数。

2.7 子类chnl_burst_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

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

fifo_full_test与root_test之间的区别主要在下面这段代码。

  • 使用fork join_none,运行三个agent。
  • 发送给fifo的数据写满后(fifo容量满),再disable停止三个agent的运行。
  • 之后三个agent调用了init中的空拍,等待数据传输完毕,退出task run。
     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;
     fork
       agent[0].init.chnl_idle();
       agent[1].init.chnl_idle();
       agent[2].init.chnl_idle();
     join
    

2.9 tb文件

现在的tb文件对比之前实验的tb文件,结构一目了然,方法、数据全部存放在了对应的类中,tb文件中只有例化和运行。

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 
  initial begin 
    clk <= 0;
    forever begin
      #5 clk <= !clk;
    end
  end
  
  // reset
  initial begin 
    #10 rstn <= 0;
    repeat(10) @(posedge clk);
    rstn <= 1;
  end


  // import类
  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();

 
    // 将暗号一级一级的传递
    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);


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

endmodule

tb文件结构是不是很清晰?

结语

看完代码,对整个实验过程应该通透了吧?
对于实验2,要学会接口、类、类中句柄、类中的指针this
fork join的使用。
关于实验代码、验证相关的资料,你找的到的找不到的都可以从公众号"袁小丑"免费获取。

  • 18
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值