UVM与验证环境

本文

主要介绍什么是UVM和验证平台,以及如何搭建UVM验证平台。

版本 说明
0.1 初版发布

参考

参考自文彬师兄的UVM培训资料。

初识UVM

什么是UVM?

  • VMM(Verification Methodology Manual),Synopsys在2006年推出的,VMM当中集成了寄存器解决方案RAL(Register Abstraction Layer)。
  • OVM(Open Verification Methodology),Cadence和Mentor在2008年推出的,它引进了factory机制,功能非常强大,但是没有寄存器解决方案。
  • UVM(Universal Verification Methodology),即通用验证方法学,其正式版本在2011年2月由Accellera推出的,UVM几乎完全继承了OVM,同时又采纳了VMM中的寄存器解决方案。

什么是UVM验证平台?

  • UVM是基于System Verilog的一种验证方法学,也可以看成是一个库,提供一系列的接口,可以利用UVM搭建验证平台,用于验证数字逻辑电路的正确性。
  • 注意,UVM本身并不是一个验证平台,他只是一个库,而一个验证平台引入了UVM相关库,称为基于UVM的验证平台,或者简称为UVM验证平台。
  • 支持UVM的EDA厂商:Cadence、Synopsys、Mentor…

UVM基础

一个基于SV的简单验证平台

注:这里“基于SV”指平台仅仅使用SystemVerilog语言搭建。

一个基于UVM的简单验证平台

基于UVM验证平台原则

  • 类: UVM中几乎所有的东西都是用类(class)来实现的,所以,搭建uvm平台第一条原则,所有的组件都用类来完成。
  • 基于UVM类: 当要实现一个功能时,首先应该想到的是从UVM的某个类派生出一个新的类来实现期望功能,所以,搭建uvm平台第二条原则,所有的组件应该派生自uvm类。

UVM中两大最重要基类

  • uvm_object: 它是UVM最基本的类,几乎所有的类都派生自uvm_object,它的拓展性是最好的,当然能力也是最差的。它主要构成了环境的属性(例如配置)和数据传输。
  • uvm_component: 它派生自uvm_object ,却拥有自己独有的强大特性,它有两大独有特点,一是通过new形成树形结构,二是自动执行phase。它主要构成了环境的层次。

UVM中常用类的继承关系

什么是UVM Factory

所谓Factory就是工厂,是通过一个字符串(类名)创建一个此字符串所代表的的类的一个实例,并且能够自动调用其phase执行的机制,也就相当于加工工厂。理解上可能比较抽象,我们举例来说明。

如下图,一个汽车工厂好比我们的验证平台,支持宝马和奔驰两条生产线,两位老板只需下令生产奔驰还是宝马,工厂按照生产线自动加工,最终产出汽车,这里指令(奔驰/宝马)就是上述中的字符串,生产线就是调用其phase执行的机制,整个验证平台也就相当于工厂,这就是UVM Factory。

我们的工厂可以支持生产哪些“汽车”,是由验证平台决定的,如果向验证平台输入不支持的指令,比如指定工厂生产“永久自行车”,会被视为错误指令。

如果就想生产“永久自行车”,我们需要添加永久自行车生产线:

然后我们就可以向工厂输入指令,生产“永久自行车”了。

总结

  • run_test()语句会通过传入的字符串,创建一个类名为输入字符串的类的实例,并且会自动调用此类的phase,但是前提是你已经注册了这个类。
  • 对于uvm_component类,注册是通过uvm_component_utils宏来进行的。所有派生自uvm_component以及其派生类都应使用uvm_component_utils来注册
  • 对于uvm_object类,注册是通过uvm_object_utils宏来进行的。所有派生自uvm_object以及其派生类(除uvm_component外)都应使用uvm_object_utils来注册
  • 由上述例子可以看出:run_test()是启动整个验证平台的UVM库函数。

UVM验证平台启动执行流程

搭建UVM验证平台

UVM验证平台基本组成

一个只有driver的测试平台

driver代码示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class my_driver extends uvm_driver;     //继承uvm库中的uvm_driver类
       `uvm_component_utils(my_driver)  //将my_driver类注册到factory

       virtual my_if vif;   //声明driver的interface, interface my_if的定义这里不再介绍

       extern virtual function void build_phase(uvm_phase phase);
       extern virtual task void main_phase(uvm_phase phase);
endclass

function void my_driver:: build_phase(uvm_phase phase);  //将顶层实例化的interface指针传递给driver的insterface
        super.build_phase(phase);
        if(!uvm_config_db# (virtual my_if)::get(this, "", "vif", vif))
                `uvm_fatal("my_driver", "virtual interface must be set for vif !!!")
endfunction

task void my_driver:: main_phase(uvm_phase phase);
       //略去
endtask

top_tb代码示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
module tob_tb
       my_if input_if(clk, rstn);  //声明top_tb的interface,在dut实例化时可直接使用
       dut my_dut(.clk(clk),
                          .rst_n(rst_n),
                          .rxd(input_if.data));  //dut实例化
       initial begin
             uvm_config_db# (virtual my_if)::set(null, "uvm_test_top", "vif", input_if) //与driver的interface建立连接,将top_tb.input_if与my_driver.vif连接起来
             run_test("my_driver");  //实例化my_driver类,实例的名字是uvm_test_top,并执行类my_driver中的main_phase任务
        end
endmodule

注意: uvm_test_top是run_test产生的my_driver类的实例化对象名字,run_test(“my_driver”)可以简单的看成:

1
2
my_driver uvm_test_top;
uvm_test_top.main_phase();

这里,任何被run_test()实例化的类的对象的名字都会被UVM平台默认成uvm_test_top,这个被实例化的类也就是整个uvm平台的顶层,而且只允许有一个顶层,即一个验证平台只调用一个run_test()。

top_tb顶层与uvm树形结构的交互:

  • uvm_config_db
1
2
3
4
5
function void my_driver:: build_phase(uvm_phase phase);  //将顶层实例化的interface指针传递给driver的insterface
        super.build_phase(phase);
        if(!uvm_config_db# (virtual my_if)::get(this, "", "vif", vif))
                `uvm_fatal("my_driver", "virtual interface must be set for vif !!!")
endfunction

top_tb顶层与uvm树形结构的交互为什么要用这种看起来很怪的uvm_config_db方式获得top_tb的interface,不能直接调用得到吗? 按道理来讲,是可以的,uvm本来就是基于sv的函数库,其底层肯定也是有sv去实现的,既然uvm将其封装为uvm_config_db,建议统一使用此方法,使用uvm提供的函数也是最好的避免出错的办法。

  • top_tb顶层与uvm树形结构:
  • 如果将top_tb中的第3点去掉,请回答以下问题:
    • top_tb如何获取dut内部信号?答:通过top_tb.my_dut.xxx可以获取。
    • top_tb如何获取右侧树形结构的内部信息?答:通过top_tb.uvm_test_top.xxx是不可行的,因为run_test实例化了一个脱离了top_tb层次结构的实例对象,建立了一个新的层次,所以不能通过top_tb.uvm_test_top.xxx直接访问。所以针对这种情况,UVM引入了config_db的机制,也就是前面分别在top_tb和my_driver类中build_phase提到的:
1
2
uvm_config_db# (virtual my_if)::set(null, "uvm_test_top", "vif", input_if)
uvm_config_db# (virtual my_if)::get(this, "", "vif", vif)

这样我们通过uvm_config_db将top_tb顶层与uvm树形结构打通。

  • top_tb与树形结构中driver的交互流:

注意:top_tb与树形结构之间的交互用虚线,是因为my_driver.interface.output_xxx不是直接给top_tb.interface.input_xxx传值,而是在采用了config_db机制后,类似于两边在操作同一个指针地址,即改变my_driver.interface.output_xxx的值,就等于直接改变了top_tb.interface.input_xxx中变量的值。另外:uvm_config_db将top_tb顶层与uvm树形结构打通,我们可以将top_tb与树形结构任意组件进行交互,而不仅限于driver和monitor。

树形结构构造

目前,一个只含有driver驱动的UVM验证平台已经形成,那么接下来要考虑树形结构的构造,即添加新部件并使其层次化。

driver、monitor、agent和env:

注意:此时树形结构的顶层变成了my_env, 所以在top_tb中run_test(“my_driver”)应改成run_test(“my_env”),之前讲过,run_test(“my_driver”)实例化之后对象的名字是uvm_test_top, 那么run_test(“my_env”)实例化之后顶层对象的名字是什么?答案是仍然为uvm_test_top。树形结构发生了层次改变,此时top_tb怎么和my_driver交互?如下top_tb代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
module tob_tb
       my_if input_if(clk, rstn);  //声明top_tb的interface,在dut实例化时可直接使用
       dut my_dut(.clk(clk),
                          .rst_n(rst_n),
                          .rxd(input_if.data));  //dut实例化
       initial begin
             // uvm_config_db# (virtual my_if)::set(null, "uvm_test_top", "vif", input_if)
             uvm_config_db# (virtual my_if)::set(null, "uvm_test_top.agt.drv", "vif", input_if) //与driver的interface建立连接,将top_tb.input_if与my_driver.vif连接起来
             // run_test("my_driver")
             run_test("my_env");  //实例化my_env类,实例的名字是uvm_test_top,并执行类my_env中的phase
        end
endmodule

加入checker:

注意:这里的checker是将reference model和scoreboard统一看成一个整体。reference model和scoreboard的定义和其他component的方式一样,这里不再赘述。

  • 树形结构通信通道

加入sequencer:

这里sequencer是一个固定组件,sequencer主要将激励承接给driver,my_transaction是一个数据包,也就是测试激励,这里还要有一个sequence概念,sequence里存放一组trans,提供给sequencer,可见sequencer属于component类,sequence和trans属于object类。关于sequencer、sequence和trans,还有这样一个比喻,trans好比子弹,sequence好比弹夹,而sequencer是枪。

加入transaction:

  • transaction使用`uvm_object_utils注册。
  • transaction可以看成是数据包,把数据打包传输,便于交互。
  • 在组件之间(driver,checker,monitor等)的信息传递都是基于transaction。

加入transaction后driver的变化:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class my_driver extends uvm_driver  #(my_transaction); //uvm_driver是参数化类,参数类型为my_transaction
       `uvm_component_utils(my_driver)
       virtual my_if vif;
       ……
endclass

task void my_driver:: main_phase(uvm_phase phase);
     将:
          vif.data <= 8'b1;
     改成:
          req = new(req); //等价于my_transaction tr; tr = new(“tr”);
          req.data = 8b1;   //等价于tr.data = 8’b1;
          vif.data <= req.data; //等价于vif.data <= tr.data;
endtask
  • req是父类uvm_driver中变量,类型是传递给uvm_driver的参数,这里传递的参数是my_transaction,所以父类uvm_driver中的req类型就是my_transaction。
  • my_driver继承了uvm_driver类,所以可以直接使用uvm_driver中的req,而不需要在my_driver中声明定义req。

加入sequence:

前面所示代码中激励都是在driver产生的,正常情况driver只是传递激励,而不是产生激励,所以要将激励产生从driver中移除,从外界获得激励,那driver的激励应该从哪里产生呢?这就是sequencer要做的事情,也就是说sequencer要提供req(这里为my_transaction)给my_driver。那么sequencer的transaction从哪里来?

`uvm_do(my_trans)实现了以下操作:

  • 创建一个my_transaction的实例my_trans
  • 将其随机化
  • 最终将其传送给my_sequencer(此宏不能做到自动连接my_sequencer并将my_trans直接传送给my_sequencer)

sequence工作机制:

  • 对于1, my_sequencer等待。
  • 对于2, my_sequencer等待。
  • 对于3, my_sequencer将my_sequence中的my_trans发送给my_driver。

待解决问题:

  • my_driver如何向my_sequencer发送transaction接收请求?
  • my_sequence如何向my_sequencer发送transaction?

my_driver向my_sequencer发送申请:

注意:

  • seq_item_port是uvm_driver中的成员变量。
  • seq_item_export是uvm_sequencer中的成员变量。

my_sequence向my_sequencer发送my_transaction:

前面提到, my_sequence中的`uvm_do不能做到自动连接my_sequencer并将my_trans直接传送给my_sequencer,所以需要额外启动连接,以在当前顶层my_env中手工启动为例(一般都是在顶层启动):

1
2
3
4
5
task void my_env:: main_phase(uvm_phase phase);
      my_sequence seq;
      seq = my_sequence::type_id::create("seq");
      seq.start(agt.sqr); //将此sequence产生的transaction发送给agent中的my_sequencer.
endtask

my_sequence自动启动机制default_sequence:

前面my_sequence是在my_env中手工启动的,default_sequence可以自动启动my_sequence 。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
task void my_env:: main_phase(uvm_phase phase);
    将下列代码去掉:
         my_sequence seq;
         seq = my_sequence::type_id::create("seq");
         seq.start(agt.sqr); //将此sequence产生的transaction发送给agent中的my_sequencer.
endtask

virtual function void my_env::build_phase(uvm_phase phase);
     uvm_config_db# (uvm_object_wrapper)::set(this, agt.sqr.main_pahse, default_sequence, my_sequence::type_id::get());
endtask

树形结构通信通道变化:

原始:

现在:

注意:

  • 蓝色部分: top_tb与树形结构的连接
  • 紫色部分: transaction在my_checker和monitor之间的通信通道
  • 红色部分: transaction在my_sequence,my_sequencer和my_driver之间的通信通道
  • 单箭头虚线部分: 实际没有显性直接通道,均通过上一层connect实现
  • 双箭头虚线部分: 代表同一模块

截至目前的树形结构:

目前,树形结构除my_case0顶层之后,均已构建完成,并全部打通。后面要添加my_case0。

加入base_test:

base_test.sv代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class base_test extends uvm_test;
      `uvm_component_utils(base_test)
      my_env env; //my_env会在base_test中实例化,base_test取代env称为顶层。
endclass
function void base_test::build_phase(uvm_phase phase);
    //my_sequence需要在顶层启动连接,因为base_test变成顶层,所以从my_env中移到此处
    uvm_config_db# (uvm_object_wrapper)::set(this, "env.agt.sqr.main_pahse",
                                                                     "default_sequence",
                                                                     my_sequence::type_id::get());
endfunction

top_tb.sv代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
module tob_tb
    initial begin
//       将:
//            uvm_config_db# (virtual my_if)::set(null, "uvm_test_top.agt.drv", "vif", input_if)
//            run_test("my_env");
//       改成:
            uvm_config_db# (virtual my_if)::set(null, "uvm_test_top.env.agt.drv", "vif", input_if)
            run_test("base_test");
    end
endmodule

加入my_case0:

1
2
3
4
5
6
7
class my_case0 extends base_test;
       `uvm_component_utils(my_case0)
endclass
function void my_case0::build_phase(uvm_phase phase);
   //以下重载了base_test中的my_sequence的启动,注意bast_test中的仍保留。
   uvm_config_db# (uvm_object_wrapper)::set(this, "env.agt.sqr.main_pahse", "default_sequence", my_sequence::type_id::get());
endfunction

注意:

  • my_case0是继承了base_test的一个子类, 是base_test的一个更具体的实现,也就从这里形成了testcase的概念,这个testcase的名字就是my_case0。
  • my_case0没有改变激励产生的方式,即仍然是启动了my_sequence,并利用my_sequence中`uvm_do(my_trans)来全随机产生激励。

加入my_case0_sequence:

my_case0_sequence.sv代码:

1
2
3
4
5
6
7
8
class my_case0_sequence extends my_sequence #(my_transaction);
     `uvm_object_utils(my_sequence)
      my_transaction my_trans;

      virtual task body();
          `uvm_do_with(my_trans, {my_trans.data == 8'hff;})
      endtask
endclass

注意:

  • my_case0_sequence是继承了my_sequence的一个子类, 是my_sequence的一个更具体的实现。
  • my_case0_sequence重载了my_sequence 的body()任务,并利用`uvm_do_with()来产生次数值都为8’hff的数据激励。

my_case0.sv代码:

1
2
3
4
5
6
7
class my_case0 extends base_test;
       `uvm_component_utils(my_case0)
endclass
function void my_case0::build_phase(uvm_phase phase);
   //以下将my_case0_sequence中的trans传递给了sequencer,最终传给driver。
   uvm_config_db# (uvm_object_wrapper)::set(this, "agt.sqr.main_pahse", "default_sequence", my_case0_sequence::type_id::get());
endfunction

注:

  • 这里制造了一个用例my_case0,此用例每个transaction产生8’hff数据的激励。
  • 这里my_case0成为新的顶层。

将顶层base_test替换成my_case0:

1
2
3
4
5
6
7
8
module tob_tb
    initial begin
//       将:
//            run_test("base_test");
//       改成:
            run_test("my_case0");
    end
endmodule

run_test()作用:它会通过传入的字符串,创建一个类名为输入字符串的类的实例,并且会自动调用此类的main_phase。
run_test给我们最初的印象是构建了一个脱离了top_tb的树形结构,并完成了所有内部需要交互部件的打通。现在我们需要改变一下思维,这里当调用run_test(“my_case0”)时,不再考虑树形结构,我们用一个更抽象的概念来描述run_test的行为,那就是:它执行一个用例my_case0,而且只有一个用例在执行,这条用例每个transaction都在产生数值为8’hff的数据。我们平时所谓的跑各种各样的用例,这些用例其实都是基于这个去构造和命名的(在uvm平台中)。

用例构造

构造另一个用例my_case1:

  • my_case1_sequence.sv:
1
2
3
4
5
6
7
8
class my_case1_sequence extends my_sequence #(my_transaction);
     `uvm_object_utils(my_sequence)
      my_transaction my_trans;

      virtual task body();
          `uvm_do_with(my_trans, {my_trans.data == 8'haa;})
      endtask
endclass

注意:my_case1_sequence重载了my_sequence 的body()任务,并利用`uvm_do_with()来产生次数值都为8’haa的数据激励。

  • my_case1.sv:
1
2
3
4
5
6
7
8
9
class my_case1 extends base_test;
       `uvm_component_utils(my_case1)
endclass
function void my_case1::build_phase(uvm_phase phase);
   //以下将my_case1_sequence中的trans传递给了sequencer,最终传给driver。
   uvm_config_db# (uvm_object_wrapper)::set(this, "agt.sqr.main_pahse",
                                                                "default_sequence",
                                                                my_case1_sequence::type_id::get());
endfunction

注意:这里制造了一个用例my_case1,此用例每个transaction产生8’haa数据的激励; 如果想要运行这个用例,那my_case1将成为新的顶层,也就是将top_tb中的 run_test(“my_case0”) 改为 run_test(“my_case1”)

UVM测试用例启动

由于run_test在top_tb中只能调用一次,所以每次跑新的用例,都要手动改一下run_test()的参数名字,试想我们有10000个用例,如果都手动改,那肯定是不可行的,所以UVM提供了另外一种启动方式。

1
2
3
4
5
module top_tb
    initial begin
        run_test();
    end
endmodule

此方式将run_test()中的参数去掉,并利用UVM_TESTNAME从命令行中获得测试用例的名字,例如:

<sim command> … + UVM_TESTNAME=my_case0
<sim command> … + UVM_TESTNAME=my_case1

注:sim command为eda厂商提供的仿真命令,后面会有介绍。

UVM验证平台启动和封装

非基于uvm验证平台仿真启动

注意:.f文件里分别是验证环境和设计的代码文件列表。

  • sysnopsys:
    • 编译:vcs –f env_vcs.f –f design_vcs.f –verdi_compile_option –coverage_compile_option ……
    • 仿真:./simv –verdi_rrun_option –coverage_run_option ……
  • cadence:
    • 编译:irun –f env_irun.f –f design_irun.f –verdi_compile_option –coverage_compile_option ……
    • 仿真:irun –verdi_irun_option –coverage_run_option ……
    • 编译:xrun –f env_xrun.f –f design_xrun.f –verdi_compile_option –coverage_compile_option ……
    • 仿真:xrun –verdi_run_option –coverage_run_option ……

基于uvm验证平台仿真启动

  • sysnopsys:
    • 编译:vcs –f env_vcs.vf –f design_vcs.f –verdi_compile_option –coverage_compile_option –ntb_opts uvm ……
    • 仿真:./simv –verdi_rrun_option –coverage_run_option +UVM_TESTNAME=my_case0 ……
  • cadence:
    • 编译:irun –f env_irun.vf –f design_irun.f –verdi_compile_option –coverage_compile_option –uvm +UVM_TESTNAME=my_case0 ……
    • 仿真:irun –verdi_irun_option –coverage_run_option +UVM_TESTNAME=my_case0 ……
    • 编译:xrun –f env_xrun.vf –f design_xrun.f –verdi_compile_option –coverage_compile_option –uvm +UVM_TESTNAME=my_case0 ……
    • 仿真:xrun –verdi_run_option –coverage_run_option +UVM_TESTNAME=my_case0 ……

注意:需要在env.vf中包含uvm的库文件:

$UVM_HOME/src/uvm_macros.svh
$UVM_HOME/src/uvm.sv
$UVM_HOME/src/uvm_pkg.sv
$UVM_HOME/dpi/uvm_dpi.sv
+incdir+$UVM_HOME/src

对于vcs和irun/xrun还有一点需要注意:

  • 对于vcs,使用uvm库时需: include ”uvm_pkg.sv”
  • 对于irun/xrun,使用uvm库时需: import uvm_pkg::*

基于uvm验证平台的封装

为什么要对验证平台封装?

到目前为止,我们就可以利用基于uvm的验证平台跑用例进行验证了。经历了漫长痛苦的uvm环境开发之后,当我们在自己独立开发的uvm验证环境中,成功跑完第一条用例my_case0仿真用例的那一刻,发现之前的付出都是值得的,当我们利用自己制造的人生第一条用例my_case0找到人生第一个设计bug的那一刻,发现人生已经达到了巅峰。但是作为一名优秀的验证工程师,我们的成就不仅如此,因为我们的目的不仅仅是找到bug,而是快速高效的找到bug。

当基本验证平台可以使用,进入初期验证阶段之后,你会发现,可能会有不同的验证工程师在此验证环境中开发新的功能,可能会有不同的设计人员在此验证环境中复现bug,也可能包括自己在内的工程师需要在此验证环境中运行各种各样配置的用例,如果每次都需要自己去改变底层仿真命令或者告诉其它人怎么改底层仿真命令,你会发现整个人都不好了。而且如果所有人都自己去手动改环境底层代码跑用例,到最后整个验证平台也会变得非常杂乱,非常不好维护,并且就实际情况,大部分设计工程师是不接受每次跑用例都需要自己手动改代码的。

为了解决这些问题,使环境变得更加整洁高效,维护简单,便于扩展,我们将环境进行封装。

封装的原则是什么?

  • 对自己白盒: 所有的底层运行验证环境的编译选项和仿真选项都要自己维护开发;所有的新的开发需求需要自己来指定结构和位置;所有的内部uvm固有部件骨架都要自己维护开发。
  • 对验证工程师灰盒: 熟悉整体环境运行原理;熟悉各编译和仿真选项含义,验证过程中知道如何增减选项;在指定的结构和位置开发新的功能;不需要对此uvm环境固有骨架进行全面掌握。
  • 对设计工程师黑盒: 不需要知道任何环境内部构造,只需要按照验证工程师提供的脚本命令运行用例即可。

如何对验证平台封装?

环境采用脚本封装,一般只需提供命令和接口选项,脚本可以使用Makefile,python,perl,shell等。以python脚本封装为例。

  • 仿真命令: ./runtest.py testname=my_case0 seed=123456 –dump –cov –funcov –debug –covmerge simpath=./sim
    • seed : 提供仿真种子号
    • -dump : 产生波形
    • -cov : 打开code coverage收集
    • -funcov : 打开function coverage收集
    • -debug : 仿真结束后自动弹出波形
    • -covermerge : 仿真结束后自动merge coverage数据
    • simpath : 指定生成log的文件夹

上述接口选项最终都会呈现在底层编译和仿真命令中,使用者可以根据需求打开关闭提供选项,而不必需要知道环境内部的细节。如果有新的需求,开发完毕后提供对应的接口即可,大大减少了使用的低效性。

封装脚本的构造

封装脚本一般分为两部分: 单条用例运行脚本 和 回归用例运行脚本。

  • 单条用例运行脚本:

    • 用户提供testcase list。
    • 根据脚本提供用例名字在tclist找到对应的用例属性,包括uvm中提供的test名字(如my_case0),用例所在路径等。
    • 在sim下创建以用例名字+种子号命名的文件夹,后面生成的这条用例的所有相关信息,包括log和波形等都会存放在这里。
    • 根据脚本提供选项进行整个环境的编译工作。
    • 根据脚本提供选项进行整个环境的仿真运行。
    • 用例运行结束后,根据仿真产生的log,得到并打印出pass还是fail的信息,方便使用者进行快速判断。
    • 使用者到对应的sim/testcase_name_seed/下查找所有相关log和波形,进行相关debug。
    • 添加其它功能,例如coverage的merge,但是一般只有回归才会涉及到merge的工作,所以这部分功能可以放到回归脚本中。
  • 回归用例运行脚本:

    • 识别脚本提供的tclist中提供的回归组名,将所有指定组的用例进行仿真回归。
    • 内部调用单条用例脚本,并将回归用例脚本输入参数全部转换为单条用例脚本输入参数需要的格式。
    • 监测每条用例的仿真结果,并累加计数得到总的tc数,总pass tc数,总fail tc数,和总的没有产生仿真log的tc数。
    • 判断是否所有用例运行结束,并打印最终回归报告。
    • 如果打开coverge merge选项,会自动merge所有回归用例的coverage数据。
    • 回归结束后在sim下生成pass,fail的tclist, 同样每条用例的结果也都存在以用例名字+种子号命名的文件夹中,可以实时查看。
  • 跑单条用例命令: ./rt.py testname=my_case0 seed=123456 –dump –cov –funcov –debug –covmerge simpath=./sim

  • 跑回归用例命令: ./rt.py –regress –rgr_group=my_regression -seedrand –dump –cov –funcov –covmerge simpath=./sim

UVM验证平台的优化

优化目的是什么?

  • 针对平台结构优化: 使环境结构简洁,清晰明了,重用性好,移植性强,拓展性高,让新的用户和开发者能够快速切入。
  • 针对平台性能优化: 使环境运行速度提升,提高验证效率

如何优化验证平台?

  • 针对平台结构优化:

    • 加入readme,包含运行方法等必要信息。
    • 加入setup脚本,所需一切配置均在此一键完成。
    • 环境中用到所有路径宏均统一管理,方便更改和移植。
    • 运行中间文件统一管理,方便查看和删除。
    • 目录结构清晰,文件夹命名需简洁易懂。
    • 删除开发过程中的无效代码和目录。
    • 加入必要信息打印开关,方便读取层次结构。
    • 撰写平台使用手册,进行环境详细说明。
  • 针对平台性能优化:

    • 检查变量定义,减少存储空间占用,如需要超大容量数组时,需使用关联数组。
    • 检查哪些任务可以并行执行,改成fork_join*多线程机制。
    • 检查是否transaction约束过多,如过多速度会明显变慢。
    • 检查dut文件列表,去掉不必要文件,减少环境编译时间。
    • 检查是否有不必要打印,关掉以减少log输出占用的时间。
    • 检查model,checker,driver中是否有过多占用时间的函数,重新考虑是否有高效替代方案。
    • 利用第三方工具得到平台各个部件的时间占用分布,分析占用最多的几个部件原因,寻找解决方案。

uvm验证平台目录组织结构

verif/my_ut/env下文件:

  1. agents/ : agent文件目录,内含一个或多个agent,agent包含driver、sequencer、monitor组件,agent从行为上可以理解为与dut交互的模块或组织。
  2. coverage/ : coverage文件目录,内含一个或多个模块的覆盖率文件,也就是covergroup。
  3. checker/ : checker文件目录,也就是reference model和scoreboard文件,不过有时候会将两者融合成一个checker文件。
  4. include/ : include文件,一些环境路径的宏定义以及其他include文件存放在这里。
  5. interface/ : interface文件目录,内含一个或多个接口文件。
  6. script/ : script文件目录,一些验证平台封装的脚本文件存放在这里。
  7. setup/ : setup文件目录,一些平台初始化文件存放在这里,如环境变量的初始化和工具的配置。
  8. tests/ : tests文件目录,所有测试用例存放在这里。
  9. readme : readme文件,对环境和使用的说明文件。
  10. tb.v : tb文件,顶层testbench。
  11. env_vcs.f : env_vcs.f文件,针对vcs工具的filelist文件,包含设计和验证平台的文件列表。
  12. env_xrun.f : env_xrun.f文件,针对xrun工具的filelist文件,包含设计和验证平台的文件列表。
  13. tc.list : tc.list文件,包含所有测试用例信息,可为测试用例设定分组,方便回归测试。
  14. my_env_cfg.sv : my_env_cfg.sv文件,整个验证平台的配置文件,内含静态变量,可传递至平台任意模块,完成验证平台的配置。

文章原创,可能存在部分错误,欢迎指正,联系邮箱 cao_arvin@163.com。