异步fifo的UVM验证(更新2)

fifo_dirver更新

上一阶段我只是简单的搭建了一个环境,甚至都没有形成一个简单的框架。这里,我们先更新以下driver,给driver引入factory机制与objection机制。

factory机制需要利用宏定义,将driver注册进去。它的作用有很多,最简单的就是可以自动创建类并且调用其中的func or task。在上一节的top_tb中,有如下代码:

initial begin
  fifo_driver drv;
  drv = new("drv",null);
  drv.main_phase(null);
  $finish();
end

利用factory机制,就可以去掉这些繁琐的代码过程,直接利用run_test();来进行仿真。

objection机制是在仿真过程中,driver会按照顺序去执行九个phase,通过objection来控制phase的挂起与执行。如果这个phase不被raise,就直接跳过该phase,里面的语句也不会执行。所以我们可以通过raise与drop来控制phase想要执行的内容:

task fifo_driver::main_phase(uvm_phase phase);
  fifo_transcation tr;
  phase.raise_objection(this);
  `uvm_info("fifo_driver","main_phase is called",UVM_LOW)
  fork
  
    //forever begin
      @(vif.init_done == 0)begin
	  vif.wr_data <= 'b0;
	end
	
	 
	@(vif.init_done == 1) begin
	  //@(posedge top_tb.wrclk);
      for(int i = 0;i < 2;i++) begin
        //@(posedge vif.wrclk); 
	    if(vif.wr_full == 0)begin
		//vif.wr_data <= vif.wr_data + 'b1 ;
	    //vif.wr_data <= $random();
	    //`uvm_info("fifo_driver",$sformatf("%0d is driverd at %0t",vif.wr_data,$time),UVM_LOW)
	    tr = new("tr");
		assert(tr.randomize() with {pload.size == 2;});
		`uvm_info("fifo_driver","drive one pkt",UVM_LOW)
		drive_one_pkt(tr);
		end
	    else begin
	      vif.wr_data <= vif.wr_data;
	      `uvm_info("fifo_driver","fifo is full",UVM_LOW)
	    end
	  end
    end
	
  join
  `uvm_info("fifo_driver","drive is finished",UVM_LOW)
  phase.drop_objection(this);
endtask

上图中,raise & drop之间就是我需要执行的内容。

小tips:在上一节的driver中,发送随机数据我在我的主机上测试的时候会发现它在某一个固定的时刻会连续发送两个相同的数值,我以为是哪里时序出了bug,结果发现是他randomize的时候不知道为啥就会在那个时刻产生连续的两个相同数值,很奇怪,我把随机产生数值换成数值自加,发现没有问题。现在也没有太搞明白这个是为啥,有大佬知道的求大佬指点一下我。

引入interface

接口有一个很大的作用就是与dut相连接,同时简化路径,还有就是里面有时钟块,可以用来提前检测input以及延后output,用来作建立保持时间。这里先不考虑太复杂的时序了(主要是水平有限,等后续再看看怎么搞),准确的说异步FIFO为了解决亚稳态的问题是需要利用接口时钟块来做处理的。这里附上代码:

`ifndef FIFO_IF__SV
`define FIFO_IF__SV
`timescale 1ns/1ns
interface fifo_if#(parameter WIDTH = 16);

  logic wrclk;
  logic rdclk;
  logic wr_rst_n;
  logic rd_rst_n;
  logic rd_en;
  logic wr_en;
  logic [WIDTH-1:0]wr_data;
  logic [WIDTH-1:0]rd_data;
  logic wr_full;
  logic rd_empty;
  
  reg init_done;
  
  initial begin
    wrclk = 0;
	forever begin
	  #2 wrclk = ~wrclk;
	end
  end
  
  initial begin
    rdclk = 0;
	forever begin
	  #4 rdclk = ~rdclk;
	end
  end
  
  initial begin
    wr_rst_n = 1;
	rd_rst_n = 1;
	wr_en    = 0;
	rd_en    = 0;
	wr_data  = 'b0;
	init_done= 0;
	
	#30 wr_rst_n = 0;
	    rd_rst_n = 0;
	#30 wr_rst_n = 1;
	    rd_rst_n = 1;
	
	#30 init_done = 1;
  end
  
  always@(*)begin
	if(init_done)begin
	  if(wr_full) wr_en = 0;
      else        wr_en = 1;  
	end
  end
  
  always@(*)begin
    if(init_done)begin
	  if(rd_empty) rd_en = 0;
	  else		   rd_en = 1;
	end
  end
  
  
endinterface:fifo_if
`endif

我这里是为了偷懒,同时让top_tb干净一点,干脆直接把所有的端口都写一起,标准一点的写法应该是按照接口类型进行分类,提供时钟还有复位一组接口,使能信号一组,wrdata rddata一组。各位可以根据自己的习惯来写。更新后的top_tb:

`timescale 1ns/1ns
`include "uvm_macros.svh"

import uvm_pkg::*;
`include "fifo_driver.sv"
`include "fifo_if.sv"
`include "fifo_transcation.sv"
`include "fifo_env.sv"
module top_tb;
  
  fifo_if intf();
  
  
  ASFIFO 
    #(.WIDTH(16),.PTR(4))
  ASFIFO
  (
  .wrclk(intf.wrclk),
  .rdclk(intf.rdclk),
  .rd_rst_n(intf.rd_rst_n),
  .wr_rst_n(intf.wr_rst_n),
  .wr_en(intf.wr_en),
  .rd_en(intf.rd_en),
  .wr_data(intf.wr_data),
  .rd_data(intf.rd_data),
  .wr_full(intf.wr_full),
  .rd_empty(intf.rd_empty)
  ); 
  
  
  initial begin
    run_test("fifo_env");  
  end
  
  initial begin
    uvm_config_db#(virtual fifo_if)::set(null, "uvm_test_top.drv", "vif", intf);
	
  end
endmodule

首先是要在top中例化接口,然后是跟dut的fifo进行连接。接下来在使用的时候,要把接口传递到你需要使用的地方。

这里需要用到config_db机制。它的作用是用来传递参数用的。

uvm_config_db#(句柄)::set(路径1,"路径2","被传递的成员",变量);
uvm_config_db#(句柄)::get(路径1,"路径2","被传递的成员",变量);

大概写一下函数的用法,set get成对出现,具体各位自行翻书即可。我们这里用的是virtual interface ,使用virtual 时,默认第一个路径1 为 null(也可以 uvm_root::get() )因为这里我引入了env,所以我的vif最后是传递到env中例化的driver上。

加入transaction

利用transaction可以发送一整个的数据包,我们在进行数据传输的时候有很多不同类型的数据,代表不同的意义。dmac, smac ,crc ,。。将他们打包到一个数据包中发送。这里的数据包我直接找的uvm实战那本书的数据包格式,更改了一下位数:

`ifndef FIFO_TRANSACTION_SV
`define FIFO_TRANSACTION_SV

`include "uvm_macros.svh"
import uvm_pkg::*;

class fifo_transcation extends uvm_sequence_item;
  
  rand bit[47:0] dmac;
  rand bit[47:0] smac;
  rand bit[15:0] ether_type;
  rand byte      pload[];
  rand bit[31:0] crc;

  constraint pload_cons{
    pload.size >= 0;
	pload.size <= 5;
  }  
  
  function bit[31:0] calc_crc();
    return 32'h0;
  endfunction
  
  function void post_randomize();
    crc = calc_crc;
  endfunction
  
  `uvm_object_utils(fifo_transcation)
  
  function new(string name = "fifo_transcation");
    super.new();
  endfunction
endclass

`endif

定义均为rand类型,后续直接随机生成。这里有一个constraint,用于做有条件的随机数生成。然后对driver进行进一步更新:

`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV

import uvm_pkg::*;
  `include "uvm_macros.svh"
  `include "fifo_transcation.sv"
  
class fifo_driver extends uvm_driver;
  virtual fifo_if vif;
  
  `uvm_component_utils(fifo_driver)
  function new(string name = "fifo_driver",uvm_component parent = null);
    super.new(name,parent);
	`uvm_info("fifo_driver","is called",UVM_LOW)
  endfunction
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
	`uvm_info("fifo_driver","build_phase is called",UVM_LOW)
	if(!uvm_config_db#(virtual fifo_if)::get(this,"","vif",vif))
	  `uvm_fatal("fifo_driver","vif must be set")
  endfunction
  
  extern task main_phase(uvm_phase phase);
  extern task drive_one_pkt(fifo_transcation tr);
endclass

task fifo_driver::main_phase(uvm_phase phase);
  fifo_transcation tr;
  phase.raise_objection(this);
  `uvm_info("fifo_driver","main_phase is called",UVM_LOW)
  fork
  
      @(vif.init_done == 0)begin
	  vif.wr_data <= 'b0;
	end
	
	 
	@(vif.init_done == 1) begin
      for(int i = 0;i < 2;i++) begin
	    if(vif.wr_full == 0)begin
	    tr = new("tr");
		assert(tr.randomize() with {pload.size == 2;});
		`uvm_info("fifo_driver","drive one pkt",UVM_LOW)
		drive_one_pkt(tr);
		end
	    else begin
	      vif.wr_data <= vif.wr_data;
	      `uvm_info("fifo_driver","fifo is full",UVM_LOW)
	    end
	  end
    end
	
  join
  `uvm_info("fifo_driver","drive is finished",UVM_LOW)
  phase.drop_objection(this);
endtask
 
task fifo_driver::drive_one_pkt(fifo_transcation tr);
  
  bit [47:0] tmp_data;
  bit [15:0] data_q[$];
  
  reg data_pkg_done;
 
  //push dmac 
  tmp_data = tr.dmac;
  for(int i = 0; i < 3; i++)begin
    data_q.push_back(tmp_data[15:0]);
	tmp_data = (tmp_data >> 16);
	`uvm_info("fifo_driver",$sformatf("%p is push at %0t",data_q,$time),UVM_LOW)
  end
  
  //push smac
  tmp_data = tr.smac;
  for(int i = 0; i < 3; i++)begin
    data_q.push_back(tmp_data[15:0]);
	tmp_data = (tmp_data >> 16);
	`uvm_info("fifo_driver",$sformatf("%p is push at %0t",data_q,$time),UVM_LOW)
  end
  
  //push ether_type
  tmp_data = tr.ether_type;
  `uvm_info("fifo_driver",$sformatf("%p is push at %0t",data_q,$time),UVM_LOW)
  data_q.push_back(tmp_data[15:0]);
  
  
  //push payload
  for(int i = 0;i < tr.pload.size; i++)begin
    data_q.push_back(tr.pload[i]);
	`uvm_info("fifo_driver",$sformatf("%p is push at %0t",data_q,$time),UVM_LOW)
  end
  
  //push crc
  tmp_data = tr.crc;
  for(int i = 0; i < 2; i++) begin
      data_q.push_back(tmp_data[15:0]);
	  `uvm_info("fifo_driver",$sformatf("%p is push at %0t",data_q,$time),UVM_LOW)
      tmp_data = (tmp_data >> 16);
  end
  
  `uvm_info("fifo_driver","begin to drive one pkt",UVM_LOW)
  
  //repeat(1) @(posedge vif.wrclk);
  
  while(data_q.size() >0) 
  begin
    @(posedge vif.wrclk);
	vif.wr_data = data_q.pop_front();
	`uvm_info("fifo_driver",$sformatf("%p is drived at %0t",vif.wr_data,$time),UVM_LOW)
  end 
  
  `uvm_info("fifo_driver","end drive",UVM_LOW)  
endtask
`endif

我们可以看到利用config_db在build phase时,将vif传递进来。后续在main phase中,我们主要执行的代码为:

  phase.raise_objection(this);
  `uvm_info("fifo_driver","main_phase is called",UVM_LOW)
  fork
  
      @(vif.init_done == 0)begin
	  vif.wr_data <= 'b0;
	end
	
	 
	@(vif.init_done == 1) begin
      for(int i = 0;i < 2;i++) begin
	    if(vif.wr_full == 0)begin
	    tr = new("tr");
		assert(tr.randomize() with {pload.size == 2;});
		`uvm_info("fifo_driver","drive one pkt",UVM_LOW)
		drive_one_pkt(tr);
		end
	    else begin
	      vif.wr_data <= vif.wr_data;
	      `uvm_info("fifo_driver","fifo is full",UVM_LOW)
	    end
	  end
    end
	
  join
  `uvm_info("fifo_driver","drive is finished",UVM_LOW)
  phase.drop_objection(this);

这里我的逻辑是在vif.init_done == 1时,利用for循环发送两个数据包。assert是断言,将tr按照一定约束随机化,之前pload.size我们设定在1-5之间,这里取2,将数据发送作为一个函数drive_one_pkt。

task fifo_driver::drive_one_pkt(fifo_transcation tr);
  
  bit [47:0] tmp_data;
  bit [15:0] data_q[$];
  
  reg data_pkg_done;
 
  //push dmac 
  tmp_data = tr.dmac;
  for(int i = 0; i < 3; i++)begin
    data_q.push_back(tmp_data[15:0]);
	tmp_data = (tmp_data >> 16);
	`uvm_info("fifo_driver",$sformatf("%p is push at %0t",data_q,$time),UVM_LOW)
  end
  
  //push smac
  tmp_data = tr.smac;
  for(int i = 0; i < 3; i++)begin
    data_q.push_back(tmp_data[15:0]);
	tmp_data = (tmp_data >> 16);
	`uvm_info("fifo_driver",$sformatf("%p is push at %0t",data_q,$time),UVM_LOW)
  end
  
  //push ether_type
  tmp_data = tr.ether_type;
  `uvm_info("fifo_driver",$sformatf("%p is push at %0t",data_q,$time),UVM_LOW)
  data_q.push_back(tmp_data[15:0]);
  
  
  //push payload
  for(int i = 0;i < tr.pload.size; i++)begin
    data_q.push_back(tr.pload[i]);
	`uvm_info("fifo_driver",$sformatf("%p is push at %0t",data_q,$time),UVM_LOW)
  end
  
  //push crc
  tmp_data = tr.crc;
  for(int i = 0; i < 2; i++) begin
      data_q.push_back(tmp_data[15:0]);
	  `uvm_info("fifo_driver",$sformatf("%p is push at %0t",data_q,$time),UVM_LOW)
      tmp_data = (tmp_data >> 16);
  end
  
  `uvm_info("fifo_driver","begin to drive one pkt",UVM_LOW)
   
  while(data_q.size() >0) 
  begin
    @(posedge vif.wrclk);
	vif.wr_data = data_q.pop_front();
	`uvm_info("fifo_driver",$sformatf("%p is drived at %0t",vif.wr_data,$time),UVM_LOW)
  end 
  
  `uvm_info("fifo_driver","end drive",UVM_LOW)  
endtask

这里定义了两个bit类型的数据,一个用来存放我们定义的数据包中的数据,一个队列,队列可以根据你用的空间来自动开辟空间,索引的时候直接索引就可以,比较方便。逻辑是先将transcation中的数据存入tmp_data,然后每十六位一个从队尾加入data_q,利用位移符号可以实现遍历所有的tmp_data。这里为了方便看数据是否存入,我将每次存入后的队列都打印出来,方便观察。

然后判断data_q里面是否有数据,只要有,在wrclk的上升沿我们就从队首开始传输给wr_data,再把数据打印出来。

这里面我最开始调试的时候没注意到 data_q给wr_data 赋值用的是非阻塞赋值,结果导致每次开始都会先传输一个0(fifo之前没有的时候默认里面都是0)找了几天问题后才发现。。。后来改成阻塞赋值就没问题了。所以各位注意时序,需要打拍的时候再考虑非阻塞赋值。

加入env

env相当于一个容器吧,将driver monitor sequence都打包一起,当然这些最后要封装成为agent放入env中的。这里只需要简单的定义一下,将driver例化进来就可以自动执行driver中我们定义好的发送数据的方法了。

`ifndef FIFO_ENV__SV
`define FIFO_ENV__SV

//import uvm_pkg::*;
`include "fifo_driver.sv"

class fifo_env extends uvm_env;
  `uvm_component_utils(fifo_env)
  
  fifo_driver drv;
  
  function new(string name = "fifo_env",uvm_component parent);
    super.new(name,parent);
  endfunction
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
	drv = fifo_driver::type_id::create("drv",this);
  endfunction
endclass
`endif

最后把输出的结果给各位看一下,我这里只发送了两个数据包:

这个是打印信息:

 

 这个是波形

波形可以看出在init_done下一个时钟周期开始写数据,跟预期结果相同。这个如果想同步的话应该可以在接口的时钟块里面设置一下,让init_done提前上升沿一点拉高就可以了,各位可以自己改一下去尝试了。

ps:代码可能放的有点乱,给各位说一下本节用到的完整的工程文件我有这几个:fifo_driver , fifo_if , fifo_transcation , top_tb , fifo_env 以及dut文件 FIFO , DPRAM 。

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
异步FIFO是一种常见的硬件设计模块,用于在不同的时钟域之间传输数据。UVM(Universal Verification Methodology)是一种用于验证硬件设计的方法学。在UVM验证中,验证工程师可以使用UVM中提供的各种类和方法来验证异步FIFO的功能和正确性。 下面是一个简单的UVM验证异步FIFO的示例: 首先,我们需要创建一个Transaction类来表示FIFO中的数据项。这个类可以包含需要验证的数据字段。 然后,我们创建一个Agent类来模拟FIFO的发送和接收端。这个Agent类可以包含两个接口,一个用于发送数据到FIFO,另一个用于从FIFO接收数据。Agent类还可以包含一个Monitor来监视FIFO的状态,并将收到的数据转换为Transaction对象。 接下来,我们创建一个Sequencer类来生成数据项并将其发送到FIFO的发送端口。Sequencer类可以使用UVM提供的随机化机制来生成不同的数据项。 然后,我们创建一个Driver类来驱动Sequencer生成的数据项,并将其发送到FIFO的发送端口。 最后,我们可以创建一个Test类来实例化和连接上述组件,并编写测试用例来验证异步FIFO的功能和正确性。 在验证过程中,我们可以使用UVM提供的各种断言和功能覆盖率工具来验证异步FIFO的正确性。通过生成不同的测试用例和使用各种场景和边界条件,我们可以尽可能地覆盖所有可能的情况,并验证异步FIFO的正确性。 需要注意的是,上述只是一个简单的UVM验证异步FIFO的示例,实际的验证过程可能更为复杂,需要根据具体的设计和需求进行调整和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值