路科验证MCDF_svlab2

会逐步将遇到的一些问题或者从视频中学到的点弄懂,发上来。持续更新

一、接口的使用

要求1.1.channel_initiator发送的数据例如valid和data与时钟clk均在同一个变化沿,没有任何延迟。这种0延迟的数据发送不利于波形的查看,要在之前的代码基础上使用intf.ck的方式来做数据驱动,并且再观察波形,查看驱动的数据与时钟上升沿的延迟。

时钟块代码:

  clocking drv_ck @(posedge clk);
    default input #1ns output #1ns;
    output ch_data, ch_valid;
    input ch_ready, ch_margin;
  endclocking

要查看利用时钟块进行驱动的波形,那么我们要选中其中一个接口的实例,添加波形之后运行。
在这里插入图片描述
可以看出,采用了时钟块的之后,data在clk上升沿之后的1ns才被驱动,与时钟块代码符合。如果没有选择时钟块里的信号,那么data会在clk上升沿时变化,看起来就不那么友好。

要求1.2.为了更好地控制相邻数据之间的空闲间隔,引入了一个变量idle_cycles,它表示相邻有效数据之间的间隔。之前的代码会使得有效数据之间保持固定的一个空闲周期,需要使用idle_cycles来灵活控制有效数据之间的空闲周期。

引入变量idle_cycles:

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

通过repeat控制空闲周期数:

repeat(idle_cycles) chnl_idle();

二、仿真的结束

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

  task automatic fifo_full_test();
    chnl0_gen.initialize(0);
    chnl0_gen.initialize(1);
    chnl0_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 initialized components");
    // send data
    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;   //FIFO都满了,就停止发送

    $display("fifo_full_test: set and ensure all agents' initiator are idle state");
    fork
      chnl0_init.chnl_idle();//让initiator处于空闲状态
      chnl1_init.chnl_idle();
      chnl2_init.chnl_idle();
    join

    $display("fifo_full_test: waiting DUT transferring all of data");
    fork
      wait(chnl0_init.intf.ch_margin == 'h20);//等待FIFO里的数据被送走
      wait(chnl1_init.intf.ch_margin == 'h20);
      wait(chnl2_init.intf.ch_margin == 'h20);
    join
    $display("fifo_full_test: 3channel fifos have transferred all data");

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

备注:
通过设置断点的方式(当然也可以直接看transcript窗口的打印信息),可以很方便地看出三个test运行的时间段。分清了之后,看波形就会清楚很多。当然,可能具体的数值会有点不一样,所以需要结合波形来确定边界。
三个test的运行时间段

三、类的例化和类的成员

此部分会将之前的module中的数据和内容都移植到class里。tb2和tb3的区别如下:
区别1
module可以声明端口,将接口的实例intf直接传递进去。而class没有端口,不能直接像module一样这么写,而是将接口作为class的成员变量写出,并且在之后通过set_interface将指针传递到组件里。并且class里的idle_cycles初始化是放在new里面,而module没有new。

module chnl_initiator(chnl_intf intf);
  string name;
  int idle_cycles = 1;
……
endmodule
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
  ……
endclass

区别2
module里默认生命周期是static,要声明为动态的就需要写明是automatic,而class里默认就是automatic,所以函数和任务都不用注明;

区别3
module里不能封装(local、protected)、而class可以。

区别4
module里的chnl_write发送的数据是单一变量data,而class发送的是chnl_trans,数据类型更为丰富。

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

chnl_trans也可换成结构体,但是放在class里更好,方便以后添加函数或任务来对数据进行处理,比如复制、克隆等操作。

//module
task automatic chnl_write(input logic[31:0] data);
    @(posedge intf.clk);
    intf.drv_ck.ch_valid <= 1;
    intf.drv_ck.ch_data <= data;
    wait(intf.ch_ready === 'b1);
    $display("%t channel initiator [%s] sent data %x", $time, name, data);
    repeat(idle_cycles) chnl_idle();
  endtask
//class
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

区别5
组件需要在module里例化(不用在initial里例化),而在class里则是需要在initial块里实例化

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

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

  chnl_generator chnl0_gen();
  chnl_generator chnl1_gen();
  chnl_generator chnl2_gen();
//class
initial begin 
	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);
	……
end

要求3.1
将chnl_initiator和chnl_generator从module改造为class,同时定义一个用来封装发送数据的类chnl_trans。要求3.1要求在initial块中分别例化3个已经声明过的chnl_initiator和chnl_generator。

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

需要注意new里面的参数有所不同,可根据chnl_initiator和chnl_generator里的new函数确定。

要求3.2
每一个chnl_initiator都需要使用接口chnl_intf来发送数据,在发送数据之前我们需要确保chnl_initiator中的接口不是悬空的,即需要由外部被传递。所以接下来的实验要求需要通过调用chnl_initiator里的方法来完成接口传递。

要求3.3
调用三个test任务进行测试。

要求3.4
观察chnl_generator在例化chnl_trans t时,有没有不恰当的地方?如果有,会有什么问题?

class chnl_generator;
  chnl_trans trans[$];
  int num;
  int id;
  chnl_trans t;
  function new(int n);
    this.id = n;
    this.num = 0;
    //t = new();位置1
  endfunction
  function chnl_trans get_trans();
  	t = new();//位置2
    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

实例化有点问题。
如果放在new函数里(位置1),那么chnl_generator就只会在 chnl0_init = new(“chnl0_init”);实例化1次,产生1个对象;
但实际上我们是想要产生多个实例的,因此需要把它放在get_trans()里(位置2),这样通过下面的代码就可以生成多个实例。

repeat(100) chnl0_init.chnl_write(chnl0_gen.get_trans());

四、包的定义和类的继承

tb4和tb3的区别
1.tb4多了agent结构,并且把chnl_generator和chnl_initiator的实例化放在了agent里的new函数进行

//tb4
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
	……
  endclass: chnl_agent
// tb3
  initial begin 
    #10 rstn <= 0;
    repeat(10) @(posedge clk);
    rstn <= 1;
  end

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

	chnl0_init.set_interface(chnl0_if);
	chnl1_init.set_interface(chnl1_if);
	chnl2_init.set_interface(chnl2_if);

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

要求1
从chnl_pkg中引入其中定义的类,让tb4能够识别类的句柄。

import chnl_pkg::*

要求2
实现chnl_burst_test和chnl_fifo_full_test

test的父类:

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
      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
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));//包括1-3
      end
      $display("%s configured objects", this.name);
    endfunction
  endclass: chnl_basic_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

chnl_burst_test和chnl_basic_test 基本一致,只是改一下idle_cycles的大小。
ps:我发现把idle_cycles设为$ urandom_range(1, 3)时,仿真了几次我看到的基本都是idle_cycles=3;后来我修改成$ urandom_range(1, 2),结果看到的基本都是idle_cycles=1了。可能这就是仿真器的特点哈
在这里插入图片描述
chnl_fifo_full_test单独拿出来说,因为有点不同。chnl_burst_test和chnl_basic_test可以直接继承chnl_root_test的所有变量和方法/任务,其中就包括task run。但是chnl_fifo_full_test 的run和chnl_root_test不同,因此采用同名的办法进行覆盖重写

class chnl_fifo_full_test extends chnl_root_test;
    // USER TODO
    function new(int ntrans = 500, 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("fifo_full_test started testing DUT");
      fork: fork_all_run
        agent[0].run();
        agent[1].run();
        agent[2].run();
      join_none
      $display("fifo_full_test: 3 initiators running now");

      $display("fifo_full_test: waiting 3 channel fifos to be full");
      fork
        wait(agent[0].vif.ch_margin == 0);
        wait(agent[1].vif.ch_margin == 0);
        wait(agent[2].vif.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
        agent[0].init.chnl_idle();
        agent[1].init.chnl_idle();
        agent[2].init.chnl_idle();
      join
      $display("fifo_full_test: waiting DUT to transfer all data");

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

一个有意思的地方:
大家可以看到在chnl_fifo_full_test中,将余量为’h20的fifo写满用了’h2f + 1个数(算上0这个数),那么这多出来的’h0f + 1其实就是被DUT读走的数,所以可以推测出写和读的速率比为’h2f + 1:'h0f + 1 =48:16=3:1

在这里插入图片描述

要求3
例化已经声明过的三个test组件。(要注意,声明是声明,例化是例化,不能混淆。声明完之后,还需要在initial块里例化)

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

要求4
完成从test一层的接口传递任务,使得其内部各个组件都可以得到需要的接口

错误写法:

	basic_test.set_interface(ch0_vif,ch1_vif,ch2_vif);
    burst_test.set_interface(ch0_vif,ch1_vif,ch2_vif);
    fifo_full_test.set_interface(ch0_vif,ch1_vif,ch2_vif);

错误原因:
ch0_vif是class chnl_root_test的内部变量,而我们要在test层传递接口,就得用最外层的实例化接口chnl_if(chnl0_if,chnl1_if,chnl2_if)。


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

正确写法:

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

要求5
调用各个test的方法,展开测试。

其他部分笔记:
1.这一段代码有啥用?

fork
      wait(chnl0_init.intf.ch_margin == 'h20);
      wait(chnl1_init.intf.ch_margin == 'h20);
      wait(chnl2_init.intf.ch_margin == 'h20);
join

有的小伙伴不知道为啥要在每个test的最后都来上这一段。为了解释这个问题,我把在burst_test里的这一段给去掉了。之后正常进行仿真。
你会在transcript窗口看到这样的情况。也就是fifo_full_test只是发送了一个数据,然后整个test就匆匆结束了。这是为啥咧?
在这里插入图片描述
其实就是因为:上面提到的那段代码相当于是把FIFO里的数据给清空。如果我们把这段代码从burst_test里删去,那么可以从下图看出,退出burst_test后,余量仅为1了,那么当fifo_full_test发往FIFO里送了一个数据之后,就FIFO也就满了,就退出这个测试了。这样的话这个测试就显得有点鸡肋。所以说,退出一个test前,最好是把FIFO给清空,等待余量为’h20再退出。
在这里插入图片描述
2.
待补充

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);
      repeat(this.idle_cycles) chnl_idle();
    endtask

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

@(negedge intf.clk)的作用:避免采样时的竞争问题。

  • 仿真的时候会存在时钟和信号的时序竞争问题。
  • 默认情况下,时钟对组合逻辑添加一个无限小的时间延迟,称为delta cycle。delta cycle比最小时间精度还要小,是一个相对的概念。
  • 注意,valid和data是时序逻辑,而ready是组合逻辑,因此存在一个delta cycle。
  • valid、data和ready在同一个delta cycle,就是在上升沿处。如果不加@(negedge intf.clk),本来ready是打算变为1的,结果因为delta cycle的存在使得ready采样采到了0,也就是采错了。
  • 解决方法:加延迟。比如像代码里在clk下降沿再采,其实就是添加了一个延迟。另一个方法是直接添加延迟#1 ps,但不推荐,一般建议不加固定的延时,因为不利于维护和看代码。(在clocking块里就要直接添加固定的延迟)

为什么valid、data用了时钟块,而ready却没用?

  • valid、data是时序逻辑部分,而ready是组合逻辑(因为ready信号要立即给valid和data反馈,接受还是不接受,而不是等到下一拍,因此是ready采用组合逻辑)
  • 时钟块是为了模拟时序逻辑里的建立保持时间,只在时序逻辑中出现,组合逻辑不用。
  • 要观测实时的信号就不用clocking

参考:
MCDF实验——Lab2

SystemVerilog的听课学习笔记,包括讲义截取、知识点记录、注意事项等细节的标注。 目录如下: 第一章 SV环境构建常识 1 1.1 数据类型 1 四、二值逻辑 4 定宽数组 9 foreach 13 动态数组 16 队列 19 关联数组 21 枚举类型 23 字符串 25 1.2 过程块和方法 27 initial和always 30 function逻辑电路 33 task时序电路 35 动态 静态变量 39 1.3 设计例化和连接 45 第二章 验证的方法 393 动态仿真 395 静态检查 397 虚拟模型 403 硬件加速 405 效能验证 408 性能验证 410 第三章 SV组件实现 99 3.1 接口 100 什么是interface 101 接口的优势 108 3.2 采样和数据驱动 112 竞争问题 113 接口中的时序块clocking 123 利于clocking的驱动 133 3.3 测试的开始和结束 136 仿真开始 139 program隐式结束 143 program显式结束 145 软件域program 147 3.4 调试方法 150 第四章 验证的计划 166 4.1 计划概述 166 4.2 计划的内容 173 4.3 计划的实现 185 4.4 计划的进程评估 194 第五章 验证的管理 277 6.1 验证的周期检查 277 6.2 管理三要素 291 6.3 验证的收敛 303 6.4 问题追踪 314 6.5 团队建设 321 6.6 验证的专业化 330 第六章 验证平台的结构 48 2.1 测试平台 49 2.2 硬件设计描述 55 MCDF接口描述 58 MCDF接口时序 62 MCDF寄存器描述 65 2.3 激励发生器 67 channel initiator 72 register initiator 73 2.4 监测器 74 2.5 比较器 81 2.6 验证结构 95 第七章 激励发生封装:类 209 5.1 概述 209 5.2 类的成员 233 5.3 类的继承 245 三种类型权限 protected/local/public 247 this super 253 成员覆盖 257 5.4 句柄的使用 263 5.5 包的使用 269 第八章 激励发生的随机化 340 7.1 随机约束和分布 340 权重分布 353 条件约束 355 7.2 约束块控制 358 7.3 随机函数 366 7.4 数组约束 373 7.5 随机控制 388 第九章 线程与通信 432 9.1 线程的使用 432 9.2 线程的控制 441 三个fork...join 443 等待衍生线程 451 停止线程disable 451 9.3 线程的通信 458 第十章 进程评估:覆盖率 495 10.1 覆盖率类型 495 10.2 功能覆盖策略 510 10.3 覆盖组 516 10.4 数据采样 524 10.5 覆盖选项 544 10.6 数据分析 550 第十一章 SV语言核心进阶 552 11.1 类型转换 552 11.2 虚方法 564 11.3 对象拷贝 575 11.4 回调函数 584 11.5 参数化的类 590 第十二章 UVM简介 392 8.2 UVM简介 414 8.3 UVM组件 420 8.4 UVM环境 425
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hardworking_IC_boy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值