我是靠谱客的博主 称心小蝴蝶,最近开发中收集的这篇文章主要介绍异步fifo的UVM验证(更新2)fifo_dirver更新,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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 。

最后

以上就是称心小蝴蝶为你收集整理的异步fifo的UVM验证(更新2)fifo_dirver更新的全部内容,希望文章能够帮你解决异步fifo的UVM验证(更新2)fifo_dirver更新所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(46)

评论列表共有 0 条评论

立即
投稿
返回
顶部