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#(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环境的复用性。让顶层验证,变得容易。

此条目发表在验证相关分类目录。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。