这一篇帖子我鸽子了好久,异步验证前俩周忙教研室课题还要做实验,最终今天五月最后一天就把这个验证的异步验证框架全部更新完了,我真的最终没有太监掉这个帖子。后续各位就可以在这个基础上自己写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
最后附上结果图:
所有的框架搭建完了,确实学了不少东西,祝各位都各有收获。