在UVM中,我们一般都是使用单顶层的模式。也就是只有一个uvm_test_top顶层,然后下面有env,env下面有agent等。如下图所示:
通过uvm_top.print_topology()函数,可以打印uvm的拓扑结构。比如如下我的一个uvm环境,打印的拓扑结构如下:
如果我有另外一个uvm环境,那么怎么可以简单的,将两个uvm环境给集成到一起,进行整体验证了?
此时,就要用到uvm的双顶层结构。
其实uvm,并没有限定uvm_top下,只能有一个叶子节点,也就是uvm_test,也可以有多个叶子节点,也就是多个uvm_test。只不过,两个字节点的名字,都不能叫做uvm_test_top。
如果两个字节点的名字,是一样的,比如都叫uvm_test_top,那么仿真的时候会报错:
使用2个uvm_test顶层,结构如下:
如上图,有2个uvm_test顶层,一个uvm_test顶层名字叫做uvm_test_top,另外一个uvm_test顶层名字叫做uvm_test_top1。
这样的话,每个uvm_test下面,可以有自己的uvm环境。使用这种方式,就可以很容易的将两个,或者多个uvm环境,给集成到一起。
下面说一下,如何进行集成:
比如有另外一个uvm环境,叫做your uvm,那么首先要将这个uvm环境,封装成一个module的顶层wrapper,这样其他uvm环境使用这个uvm环境的时候,只需要例化这个顶层wrapper到他的环境即可。
如下图所示,将your uvm环境需要的RTL信号,定义在module的端口信号列表中,内部在连接到your uvm环境的interface中。
module test_bfm_top(clk,rstn,opcode);
input clk;
input rstn;
input [8:0] opcode;
test_bfm_if test_bfm_intf();
assign test_bfm_intf.clk = clk;
assign test_bfm_intf.rstn = rstn;
assign test_bfm_intf.opcode = opcode;
下面就是关键的代码了:
initial begin
uvm_config_db#(virtual test_bfm_if)::set(uvm_root::get(),"*","test_bfm_vif",test_bfm_intf);
`ifndef USER_RUN
run_test("test_bfm_base_test");
`else
begin
string testname = "test_bfm_base_test";
`ifdef UVM_1_1
uvm_factory fact = uvm_factory::get();
`else
uvm_coreservice_t coreservice = uvm_coreservice_t::get();
uvm_factory fact = coreservice.get_factory();
`endif
if($value$plusargs("+UVM_YOUR_TESTNAME",testname))
$display("found your testname: %s" testname);
else
$display("use default testname:test_bfm_base_teset");
fact.create_component_by_name(testname,"","bfm_test_top",uvm_top);
$display("init_test_bfm_teset:Root has %d children",uvm_top.get_num_children());
end
`endif
end
首先使用uvm_config_db机制,将interface,传递给your uvm环境中,需要使用的interface的component中去。
增加了USER_RUN这个宏,用来判断,your uvm环境,是单独运行,还是要放到其他的uvm环境中运行。
如果是单独运行,那么直接调用run_test函数,即可启动your uvm环境。
如果是放到其他的uvm环境中运行,那么就不能调用run_test,因为在uvm环境中,run_test不能调用2次。此时,需要自己将your uvm环境中的uvm_test,手动创建一个实例,然后给挂到uvm_top下。
关键的代码就在fact.create_component_by_name这行代码。使用factory的创建component实例机制,根据传入的testname,创建一个命名为bfm_test_top的实例,,并指定父节点为uvm_top。这样创建后,在uvm_top下,就会多一个bfm_test_top这个子节点,这样整个uvm环境,就多了一个uvm_test顶层。
为了让bfm_test_top这个uvm_test顶层,能够通过仿真参数,动态的修改需要跑的testcase,增加了+UVM_YOUR_TESTNAME这个仿真参数,来指定。
在顶层,直接将your uvm环境封装的顶层wrapper例化,然后将模块的端口信号连接一下即可。这样就完成了2个UVM环境的集成。
module tb_top();
reg clk;
reg rstn;
hw_wrapper u_hw_wrapper(
.clk(clk),
.rstn(rstn)
);
initial begin
clk = 1'b0;
forever #10 clk = ~clk;
end
initial begin
rstn = 1'n0;
repeat(100) @(posedge clk);
rstn = 1'b1;
end
// my uvm test
initial begin
uvm_config_db#(virtual sync_if)::set(uvm_root::get(),
"*",
"sync_intf",
tb_top.u_hw_wrapper.sync_intf);
end
// your uvm test
wire [8:0] opcode = u_hw_wrapper.opcode;
test_bfm_top u_test_bfm_top(clk,rstn,opcode);
endmodule
当然,在编译的时候,要加入+define+USER_RUN这个宏。
通过uvm_top.print_topology()函数,可以打印此时uvm的拓扑结构:
从打印可以看出,此时UVM环境,有2个uvm test顶层,每个顶层下面有自己的uvm环境。
通过以上的方式,就将2个uvm环境,给集成到了一个uvm环境中,并且可以通过仿真参数,+UVM_TESTNAME和+UVM_YOUR_TESTNAME,分别指定2个UVM环境,仿真需要跑的testcase。
以上只是一个简单的例子,来说明,在怎么将2个uvm环境给集成到一起,那么双顶层有什么好处了?
我认为,好处是可以增强uvm环境的复用性。在模块级别,搭建uvm环境验证,当模块层次上升,到ip级,那么这个uvm环境依然还是可以用。从ip级到top级,这个uvm环境依然可以使用。
下面举个例子来说明下。比如对于一个IP,分成2个模块A和B。数据流程从A到B。
模块A交由验证甲负责,模块B交由验证乙来负责。
在验证开始,验证甲搭建uvm环境,验证模块A。验证乙搭建uvm验证,验证模块B。
当验证甲和验证乙,分别验证完毕后,此时要进行IP级别的验证,那么此时IP集成的验证,就可以使用上面的双顶层方式,将模块A的uvm验证环境和模块B的uvm验证环境给集成到一起,进行IP级的验证,这样就实现了UVM环境的复用。
当然在这种情况下,需要将模块B验证环境中的uvm_driver给去掉,因为输入是来源于模块A,但是monitor以及scoreboar等都是可以复用的。
当IP验证完毕后,需要集成到top进行验证,那top也可以复用这个IP的uvm验证环境。
所以说,双顶层或者多顶层,能够提高uvm环境的复用性。让顶层验证,变得容易。