龙驰虎骤网

异步fifo的UVM验证(最终版)

异步fifo的UVM验证(最终版)

这一篇帖子我鸽子了好久,异步验证前俩周忙教研室课题还要做实验,最终今天五月最后一天就把这个验证的异步验证框架全部更新完了,我真的最终没有太监掉这个帖子。后续各位就可以在这个基础上自己写test文件来实现一些功能验证了。异步验证顺便我把代码放到gitee上了,最终有需要的异步验证小伙伴可以私聊我。

写在前面的最终一些总结(很重要):

****之前我是将数据放置到数据包里面发送,但是异步验证我对于这个方式改了很久发现了几个bug实在是无法解决:第一个就是发送多个数据包的时候,读速度跟不上,最终在写满后,异步验证会直接结束仿真,最终而不是异步验证读出一个数据后继续发送。第二个就是最终数据包长度太长时,同样在写满后,异步验证存在数据包都没发完,直接结束仿真。这个没有很好的解决办法,所以我在后续的验证代码中只在数据包里放了一个随机数据。各位大佬如果有发现好的解决办法,烦请指点一下我,感激不尽。

****异步fifo功能比较简单。数据过了一下就走,所以我在后续里面并没有加入refer module与scoreboard,直接用两个monitor,来监测输入输出即可。这里有一个问题是输入检测的是wrdata,输出检测的是rddata,时钟不一样,所以不是同步进行的。有什么好的解决办法来实现输入输出比较吗?(我想着是不是也得像设计代码一样做一个跨时钟域处理)这一点也没有实现,同样欢迎各位大佬指点一下。

接下来就是正文了。

加入monitor

monitor的作用顾名思义,用来检测dut的输入输出是否正确,按照UVM验证的框架来说,monitor应当收集dut的输入,发送到refer module,然后refer module与检测dut输出的monitor一起传输给scoreboard,进行一个compare。这里我直接用monitor的打印输出来观察数据了。输入monitor检测wrdata,输出monitor检测rddata。考虑到检测端口不一致,这里我是写了两个monitor,也可以写一个monitor例化两个。给各位附上代码:

`ifndef FIFO_MONITOR__SV`define FIFO_MONITOR__SVimport uvm_pkg::*;`include "uvm_macros.svh"class fifo_monitor extends uvm_monitor;  `uvm_component_utils(fifo_monitor)    function new(string name = "fifo_monitor",uvm_component parent = null);    super.new(name,parent);  endfunction    virtual fifo_if vif;    virtual function void build_phase(uvm_phase phase);    super.build_phase(phase);	`uvm_info("fifo_monitor","build_phase is called",UVM_LOW)	if(!uvm_config_db#(virtual fifo_if)::get(this,"","vif",vif))	  `uvm_fatal("fifo_monitor","vif must be set")  endfunction                      extern task main_phase(uvm_phase phase);endclasstask fifo_monitor::main_phase(uvm_phase phase);  `uvm_info("fifo_monitor","main_phase is called",UVM_LOW)  while(1)begin	@(vif.init_done);	while(vif.init_done) begin	`uvm_info("fifo_monitor",$sformatf("monitor data : %d at %0t",vif.wr_data,$time),UVM_LOW)    @(posedge vif.wrclk);	#1;	end  end  `uvm_info("fifo_monitor","main phase is end",UVM_LOW) endtaskclass fifo_monitor_out extends uvm_monitor;  `uvm_component_utils(fifo_monitor_out)    function new(string name = "fifo_monitor_out",uvm_component parent = null);    super.new(name,parent);  endfunction    virtual fifo_if vif;    virtual function void build_phase(uvm_phase phase);    super.build_phase(phase);	`uvm_info("fifo_monitor_out","build_phase is called",UVM_LOW)	if(!uvm_config_db#(virtual fifo_if)::get(this,"","vif",vif))	  `uvm_fatal("fifo_monitor_out","vif must be set")  endfunction                      extern task main_phase(uvm_phase phase);endclasstask fifo_monitor_out::main_phase(uvm_phase phase);  `uvm_info("fifo_monitor_out","main_phase is called",UVM_LOW)  while(1)begin	@(vif.init_done);	while(vif.init_done) begin	`uvm_info("fifo_monitor_out",$sformatf("monitor data : %d at %0t",vif.rd_data,$time),UVM_LOW)    @(posedge vif.rdclk);	#1;	end  end  `uvm_info("fifo_monitor_out","main phase is end",UVM_LOW) endtask`endif

关于这部分代码各位应该都可以理解,我这里说两个比较关键的点就是:

1,monitor是需要在整个UVM验证流程随时监控的,所以这里再main_phase中,利用一个while(1)循环让其一直执行。

2,有关于monitor的数据收集,各位读者可以看到我在@(posedge vif.wrclk or vif.rdclk)这两行代码后都加入了一行 #1 这一行的目的就是为了让monitor并不是在边沿处采集数据。这么做的目的在于数据写入wrdata,读的过程同写的过程一起进行,结果会导致同一时刻数据并未写入,monitor已经去数据,相当于做一个建立保持时间。各位可以把这一行代码屏蔽掉,然后去观察一下输出。同样的解决办法也是可以在接口中定义时钟块来解决。

将driver分解成为sequence与sequencer

之前我们一直将driver作为产生激励,发送激励的地方。现在,我们将它拆解成为sequence与sequencer。这样driver的作用只是单纯的去发起请求,通过挂载于sequencer上的sequence来产生激励送给driver。首先我们看sequencer的代码,非常的简单,只需要定义一个类即可。

`ifndef FIFO_SEQUENCER__SV`define FIFO_SEQUENCER__SV  `include "fifo_transcation.sv"class fifo_sequencer extends uvm_sequencer#(fifo_transcation);  `uvm_component_utils(fifo_sequencer)    function new(string name, uvm_component parent);    super.new(name,parent);  endfunction  endclass`endif

接下来看sequence的代码:sequence的执行的任务我们集中于它里面的task body,其中传递的便是fifo_transcation。这里需要注意的是我们需要用default_sequence的方式来让这个objection自动进行挂起与撤销。所以这里涉及到一个starting_phase,用它来实现自动挂起与撤销,这样就不需要在sequencer中执行rasie,seq.start(this),drop...等等的操作了,很方便,还有要通过config_db来设置一下,我将它统一放在了top_tb中。这里,我们用uvm_do这个宏操作,来进行产生随机数据。常用的也就还有个uvm_do_with,主要作用是例化trans,随机化数据,最后发送给sequencer。更多的就不再赘述了,翻书看一看。这里我是发送了三十个数据后,然后延时50结束仿真。

`ifndef FIFO_SEQUENCE__SV`define FIFO_SEQUENCE__SV  `include "fifo_transcation.sv"  //`include "base_test.sv"class fifo_sequence extends uvm_sequence#(fifo_transcation);    `uvm_object_utils(fifo_sequence)  fifo_transcation f_trans;  function new(string name= "fifo_sequence");      super.new(name);  endfunction    virtual task body();    if(starting_phase != null)	  starting_phase.raise_objection(this);	  repeat(30) begin	  `uvm_do(f_trans)	  end	#50;    if(starting_phase != null)      starting_phase.drop_objection(this);	  endtaskendclass`endif

更改后的driver代码附上:这里也没什么好讲的,完整的sequence发送流程是通过seq的port利用get_next_item获得请求,然后发送数据,(这里也可以加上get rsp_id,这样是涉及到多个seq挂载同一sqr时候,需要告诉是哪个seq完成。我这里只有一个所以没有加)最后item_done()完成一次完整的发送过程。

`ifndef MY_DRIVER__SV`define MY_DRIVER__SV  `include "fifo_transcation.sv"  class fifo_driver extends uvm_driver#(fifo_transcation);  virtual fifo_if vif;    `uvm_component_utils(fifo_driver)  function new(string name = "fifo_driver",uvm_component parent = null);    super.new(name,parent);  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_data(fifo_transcation tr);endclasstask fifo_driver::main_phase(uvm_phase phase);这里注释掉raise drop 是为了让仿真自动结束,不然一直会卡在while(1)无法自动跳出  //phase.raise_objection(this);  `uvm_info("fifo_driver","main_phase is called",UVM_LOW)  fork    //wait 等待一次    @一直等待    @(vif.init_done == 0)begin	  vif.wr_data <= 'b0;	end		 	@(vif.init_done == 1) begin      //for(int i = 0;i < 30;i++) begin	  while(1)begin	    @(posedge vif.wrclk);	    if(vif.wr_full == 0)begin		  //while(1)begin		    seq_item_port.get_next_item(req);		    //vif.wr_data = req.dmac;			drive_one_data(req);		    `uvm_info("fifo_driver",$sformatf("drive one data:%d at %0t",vif.wr_data,$time),UVM_LOW)		    seq_item_port.item_done();			`uvm_info("fifo_driver",$sformatf("time is %0t",$time),UVM_LOW)		  //end		end	    else begin	      vif.wr_data <= vif.wr_data;	      `uvm_info("fifo_driver","fifo is full",UVM_LOW)	    end      end	  //end    end	  join  //phase.drop_objection(this);  `uvm_info("fifo_driver","drive is finished",UVM_LOW)endtasktask fifo_driver::drive_one_data(fifo_transcation tr);  bit[15:0] data_q;    data_q = tr.dmac;  vif.wr_data = data_q;endtask`endif

封装agent

说白了就是套娃,agent将driver monitor sqr套在了一起。还有一个输出agent,只不过里面只需要一个monitor即可。唯一需要注意的地方就是要在driver与sequencer上,通过connect_phase将端口连接起来,让数据可以传输,这里面用的是内部的一个port,一般来说发起端口都是port,接收端口就是export,这里好像是一个fifo的传输端口我也记不太清楚了,有了解的可以去看看tlm通信的内容。代码附上:

`ifndef FIFO_AGENT__SV`define FIFO_AGENT__SV`include "fifo_driver.sv"`include "fifo_monitor.sv"`include "fifo_sequencer.sv"`include "fifo_sequence.sv"class fifo_agent extends uvm_agent;  fifo_driver drv;  fifo_monitor mon;  fifo_sequencer sqr;    `uvm_component_utils(fifo_agent)    function new(string name ,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);	mon = fifo_monitor::type_id::create("mon",this);	sqr = fifo_sequencer::type_id::create("sqr",this);  endfunction    virtual function void connect_phase(uvm_phase phase);    super.connect_phase(phase);	drv.seq_item_port.connect(sqr.seq_item_export);  endfunction  endclassclass fifo_agent_out extends uvm_agent;    fifo_monitor_out mon_o;  `uvm_component_utils(fifo_agent_out)    function new(string name ,uvm_component parent);    super.new(name,parent);  endfunction    virtual function void build_phase(uvm_phase phase);    super.build_phase(phase);    mon_o = fifo_monitor_out::type_id::create("mon_o",this);  endfunctionendclass`endif

加入test用例

加入test之后,UVM树状图的最根部结构就是test了,代码附上:其实也没什么可说的,用一个更大的壳子将env包括在内。

`ifndef BASE_TEST__SV`define BASE_TEST__SV  `include "fifo_env.sv"class base_test extends uvm_test;  fifo_env env;  `uvm_component_utils(base_test)  function new(string name = "base_test", uvm_component parent = null);      super.new(name,parent);  endfunction    virtual function void build_phase(uvm_phase phase);    super.build_phase(phase);	env = fifo_env::type_id::create("env", this);  endfunctionendclass`endif

发一个我写的另外一个测试用例代码:这里这个fifo_case1用的就是case1_sequence,在编译时候我们可以让其通过+UVM_TESTNAME运行不同的test。

`ifndef CASE1_SEQUENCE__SV`define CASE1_SEQUENCE__SV  `include "fifo_transcation.sv"  `include "base_test.sv"class case1_sequence extends uvm_sequence;  `uvm_object_utils(case1_sequence)  fifo_transcation f_trans;  function new(string name= "case1_sequence");      super.new(name);  endfunction    virtual task body();    if(starting_phase != null)	  starting_phase.raise_objection(this);	  repeat(100) begin	  `uvm_do(f_trans)	  end	#10;    if(starting_phase != null)      starting_phase.drop_objection(this);	  endtaskendclassclass fifo_case1 extends base_test;function new(string name = "fifo_case1", uvm_component parent = null);      super.new(name,parent);   endfunction       extern virtual function void build_phase(uvm_phase phase);    `uvm_component_utils(fifo_case1)endclassfunction void fifo_case1::build_phase(uvm_phase phase);   super.build_phase(phase);endfunction`endif

更新top_tb:

top_tb需要加入几个config_db来设置default_sequence,同时,由于有多个test,我们也可以在run_test后不加名字,直接仿真快速启动想跑的用例即可。

`timescale 1ns/1ns`include "uvm_macros.svh"import uvm_pkg::*;`include "fifo_driver.sv"`include "fifo_if.sv"`include "fifo_env.sv"`include "fifo_monitor.sv"`include "fifo_agent.sv"`include "fifo_sequence.sv"`include "fifo_sequencer.sv"`include "base_test.sv"`include "fifo_case1.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_case1");    end    initial begin    uvm_config_db#(virtual fifo_if)::set(null, "uvm_test_top.env.i_agt.drv", "vif", intf);	uvm_config_db#(virtual fifo_if)::set(null, "uvm_test_top.env.i_agt.mon", "vif", intf);	uvm_config_db#(virtual fifo_if)::set(null, "uvm_test_top.env.o_agt.mon_o", "vif", intf);    uvm_config_db#(uvm_object_wrapper)::set(null,											  "uvm_test_top.env.i_agt.sqr.main_phase",											  "default_sequence",											  fifo_sequence::type_id::get());	uvm_config_db#(uvm_object_wrapper)::set(null,                                            "uvm_test_top.env.i_agt.sqr.main_phase",                                            "default_sequence",                                            case1_sequence::type_id::get());  endendmodule

最后附上剩余部分代码:

fifo_transcation:

`ifndef FIFO_TRANSACTION_SV`define FIFO_TRANSACTION_SV`include "uvm_macros.svh"import uvm_pkg::*;class fifo_transcation extends uvm_sequence_item;    rand bit[15:0] dmac;    function new(string name = "fifo_transcation");    super.new();  endfunction    function void my_print();    $display("dmac = %0h",dmac);  endfunction    `uvm_object_utils_begin(fifo_transcation)      `uvm_field_int(dmac, UVM_ALL_ON)   `uvm_object_utils_endendclass`endif

fifo_env:

`ifndef FIFO_ENV__SV`define FIFO_ENV__SV//import uvm_pkg::*;  //`include "uvm_macros.svh"`include "fifo_agent.sv"class fifo_env extends uvm_env;  `uvm_component_utils(fifo_env)    fifo_agent i_agt;  fifo_agent_out o_agt;   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);	i_agt = fifo_agent::type_id::create("i_agt",this);	o_agt = fifo_agent_out::type_id::create("o_agt",this);  endfunction    /* virtual task main_phase(uvm_phase phase);    fifo_sequence seq;	phase.raise_objection(this);	seq = fifo_sequence::type_id::create("seq");	seq.start(i_agt.sqr);	phase.drop_objection(this);  endtask */endclass`endif

fifo_if:

`ifndef FIFO_IF__SV`define FIFO_IF__SV`timescale 1ns/1nsinterface 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

最后附上结果图:

 所有的框架搭建完了,确实学了不少东西,祝各位都各有收获。

未经允许不得转载:龙驰虎骤网 » 异步fifo的UVM验证(最终版)