MCDF-实验2——硬件验证方式与软件验证方式的过渡

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

本次实验使用接口、类和包,完成软件验证方式

提示:以下是本篇文章正文内容,下面案例可供参考

一、接口的使用——tb1.sv

定义接口和内部端口,并且为了消除竞争需要在其中声明一个时钟块,确保时钟驱动信号间的时序关系。

输入端口(采样信号):ch_ready,ch_margin
输出端口(驱动信号):ch_valid,ch_data
时钟块触发事件:clk上升沿
接口信号的赋值必需采用非阻塞赋值<=(同步驱动)

在这里插入图片描述


数据发送组件chnl_initiator:

相较于上个实验的的tb4.sv,chnl_initiator的端口更简单,使用了接口来代替。
在这里插入图片描述

数据产生组件chnl_generator:

相较于上一个实验,数据存放由数组改为了队列chnl_arr[$],定义了两个变量和两个方法
变量:
id:用来标识slave_channel
num:用来递增数据
方法:
initialize:初始化变量
get_data:产生递增数据
在这里插入图片描述
测试代码:

initial begin
// 初始化验证组件的initial块
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块**
>     initial begin
>     @(posedge rstn);
>     repeat(5) @(posedge clk);
>     repeat(100) begin **//写入100个**
>       chnl0_init.chnl_write(chnl0_gen.get_data());      
>     end
>     chnl0_init.chnl_idle();    
>     end 
>     initial begin
>     @(posedge rstn);
>     repeat(5) @(posedge clk);
>     repeat(100) begin
>       chnl1_init.chnl_write(chnl1_gen.get_data());  
>     end
>     chnl1_init.chnl_idle();    
>     end
>     initial begin
>     @(posedge rstn);
>     repeat(5) @(posedge clk);
>     repeat(100) begin
>       chnl2_init.chnl_write(chnl2_gen.get_data());
>     end
>     chnl2_init.chnl_idle();    
>     end

仿真波形如下:并行发送数据,而且valid与data均与clk在同一变化沿。时钟信号与数据信号没有延迟。
在这里插入图片描述

1.使用时钟块来做数据驱动

即在写入数据的任务中使用时钟块索引为ch_valid和ch_data赋值

在这里插入图片描述
仿真结果:驱动信号在时钟上升沿之后的1ns处变化。
在这里插入图片描述

2.设置空闲周期

加入变量idle_cycles,用来设置有效数据间隔;首先找到设置空闲周期的任务chnl_idle()它是等待下一时钟上升沿执行的,即空闲1个周期,我们需要设置变量来设置空闲周期数目。使用repeat(idle_cycles)可以实现这一目的。
且使用函数set_idle_cycles设置变量大小
在这里插入图片描述在这里插入图片描述
只需将变量设置为0即可实现连续发送。
在这里插入图片描述

二、设置测试和仿真的结束——tb2.sv

使用fork…join并行发送数据,将不同的test组装到task中,以此区分不同的测试。

1.使用task封装tb1.sv中的初始化过程块并将三个测试过程块改为并行线程

参考basic_test改写burst_test,要求idle_cycles==0,且同时发送500个数据
在这里插入图片描述
在这里插入图片描述

burst_test():
在这里插入图片描述
在这里插入图片描述

仿真结果如下:
在这里插入图片描述

  • 【问题】之前没有注意到ready的波形有些问题,在仿真运行到一定时间时,ready开始每隔一个时钟周期拉高(参考slave_fifo设计代码,实际上是余量为0,即FIFO满状态时ready会拉低,然后一取一放导致ready一高一低),导致有些数据处于ready==0的状态。翻看之前的tb1.sv仿真结果也有这样的问题。(实验1中发送的数据少且不紧凑,所以没发现😭)
    也就是说上一个数据还没等到ready拉高,就开始同步下一个数据了。需要添加@语句,等待时钟的下降沿触发为什么?
    在这里插入图片描述

设置断点,分析仿真过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
26,27同步数据 —— 28打印语句 —— 29等待时钟下降沿(黄线处) —— 31打印发送成功信息(645ns处)—— 25等待时钟上升沿—— 30等待ready高电平(这里应该是与31同时进行)——接25一直等到时钟上升沿(蓝线之后的那个上升沿处,下一个数据)
29是否有必要。不加29行,波形有误,但找不到添加的原因


2.完成task fifo_full_test()

参考task basic_test(),要求:无论采取什么数值的idle_cycles,无论发送多少个数据,只要各chnl_initiator的不停发送使得channel缓存变满(full,设计代码中规定FIFO满了后ready会拉低),那么在三个channel都拉低过ready时(先后拉低也可以),便结束测试结束测试不是结束仿真,即停止发送数据
首先,该任务与basic_test()区别在于,数据是一直发送的,没有固定数目;
其次,idle_cycles是随机的
最后,测试结束是依靠FIFO为满状态,即余量为0,且三个都到过0,而不是之前的需要等到发送完固定数目的数据

  • 组件初始化化阶段:
    在这里插入图片描述
  • 测试阶段:forever-loop用fork…join_none保证不阻塞,然后等待三个channel_fifo满状态都已经触发过。紧跟着结束测试线程。
    在这里插入图片描述
  • 空闲阶段:在结束测试后一定要设置空闲周期,否则会不断向fifo放入最后一个数据
    在这里插入图片描述
  • 等待阶段:等待存入fifo中的数据全部发送完后结束仿真
    在这里插入图片描述
    在这里插入图片描述
    仿真结果:
    在这里插入图片描述

三、类的使用——tb3.sv

1.改造原有module

  • 需要注意的是class没有端口列表,接口需要声明virtual,
  • 同时注意在函数和任务的参数列表中要声明参数的传递方向
  • 变量声明类型为logic
  • new函数用来对变量初始化
  • class中的方法无需声明automatic这部分知识需要复习
  • 方法中的变量需要加上this区分类属性和方法属性的同名变量
  • 添加了参数类,存放相关数据,队列从存放数据改为存放该类的句柄
  • 由于num,id,data存放在参数类中,因此使用时需用句柄索引
  • 产生数据的函数改为了get_trans,它返回的不再是数据data而是参数类的句柄t

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


chnl_generator
在这里插入图片描述

在这里插入图片描述


2.在测试模块tb3里的initial块中例化相应类

  • 接口例化时其端口列表可以使用通配符
  • 例化启动类以及数据产生类时无需传递参数
  • 接口的传递需单独进行,详见下一步
    在这里插入图片描述

3.给启动类传递外部接口

在这里插入图片描述

4.检查并调用已经定义好的三个test任务

**检查:**可以在创建两个类的对象时,对channel的id和name作初始化,避免在每个task中初始化参数。只留下task前面关于idle_cycles的部分。还有就是chnl_generator内产生数据的函数名不是get_data了已经改为get_trans所以需要更改。
在这里插入图片描述

在这里插入图片描述
测试块
在这里插入图片描述

仿真结果如下:
在这里插入图片描述
从结果可以看到问题出在chnl_generator.get_trans这里。检查后发现创建对象时都是chnl0_gen.
在这里插入图片描述
【考虑:chnl_generator在例化chnl_trans t时是否合适?】
chnl_trans t是在类中例化的,在方法内使用,若方法内出现同名实例是否会找不到指定的实例。
方法内使用则在方法内例化
在这里插入图片描述

四、包的定义和类的继承——tb4.sv

引入class chnl_agent、chnl_root_test、chnl_basic_test、chnl_burst_test、chnl_fifo_full_test,将所有的类封装到软件包package chnl_pkg中。

  • chnl_agent应该包括generator、driver、monitor。tb3中只使用了generator和initiator,因此只需要将它们在agent中例化
  • 三个测试任务也需要用类来实现,利用类的继承改写三个测试任务

class_agent

  class chnl_agent;
  //例化generator和initiator
    chnl_generator gen; 
    chnl_initiator init;
  //ntrans是用来定义发送数据的个数
    local int ntrans;
    local virtual chnl_intf vif;
  //new函数初始化变量
    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
  //连接agnet和initiator的接口
    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

class_root_test

  class chnl_root_test;
  //存放3个channel事务类的句柄的队列
    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

1.在顶层模块module tb4中导入软件包

直接使用import + 软件包名字使用通配符导入全部内容
在这里插入图片描述

2.改写测试task为class

  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
  class chnl_fifo_full_test extends chnl_root_test;
    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("%s started testing DUT", this.name);
      fork: fork_all_run; 
        forever agent[0].run();
        forever agent[1].run();
        forever agent[2].run();
      join_none
      $display("fifo_full_test: 3 initiators running now");
      fork
      	wait(agent[0].vif.ch_margin == 'h0);
      	wait(agent[1].vif.ch_margin == 'h0);
      	wait(agent[2].vif.ch_margin == 'h0);
    	join
		$display("fifo_full_test: 3 channel fifos havereached full");
    disable fork_all_run; 
    $display("fifo_full_test: stop 3 initiator running");  
 		$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 transfering all of 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("fifo_full_test: 3 channel fifos have transferred all data");  
      $display("%s finished testing DUT", this.name);
  endtask
  endclass: chnl_fifo_full_test

3.顶层例化这三个测试类

代码如下(示例):
在这里插入图片描述

4.从test一层传递接口

在这里插入图片描述

5.调用run,开始测试

在这里插入图片描述


编译没有问题,仿真报错
在这里插入图片描述

  • Illegal access to local member agent[2].vif.ch_margin.
    字面意思,vif是在agent中定义的局部变量,不能在外部访问,去掉local即可

问题解决方案!感谢博主!!
仿真结果:没有等到所有数据发送完
在这里插入图片描述
查看波形:最后一个数据发完后没有调用chnl_idle()重置
在这里插入图片描述
修改:在task run内调用写入任务chnl_write后,再调用chnl_idle()
在这里插入图片描述
然后与参考代码的仿真结果对比发现仿真时长不够,仔细查看波形,发现问题出在实验1的疑问,即等待ready拉高前所添加的时钟下降沿触发语句。
在这里插入图片描述
为什么要添加@(negedge clk)
@(negedge clk)在当前发送数据的这一时钟周期内等待ready拉高,若为高则数据发送成功,打印语句。

总结:验证环境结构

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值