【博文精选】UVM使用双顶层的用法
在UVM中,我们一般都是使用单顶层的模式。也就是只有一个uvm_test_top顶层,然后下面有env,env下面有agent等。如下图所示:
通过uvm_top.print_topology()函数,可以打印uvm的拓扑结构。比如如下我的一个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#(virtualtest_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环境的复用性。让顶层验证,变得容易。