概述
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更新所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复