UVM学习笔记(二)

本文

芯片验证系列文章主要分为三部分:验证工程师与芯片验证、SV学习笔记、UVM学习笔记。此为 UVM学习笔记 部分第二篇,主要介绍核心基类、phase机制、config机制和消息管理。

版本 说明
0.1 初版发布

参考

名称 作者 来源
《芯片验证漫游指南》 刘斌 书籍
《UVM实战》 张强 书籍

专业术语与缩略语

缩写 全称 说明

核心基类

uvm_object

  • UVM世界中的类最初都是从一个uvm_void根类(root class) 继承来的,而实际上这个类并没有成员变量和方法。
  • uvm_void只是一个虚类(virtual class),还在等待将来继承于它的子类去开垦。在继承于uvm_void的子类中,有两个类, 一个为uvm_object类,另外一个为uvm_port_base类
  • 在UVM世界的类库地图中除了事务接口(transaction interface)类继承于uvm_port_base,其它所有的类都是从uvm_object类一步步继承而来的。
  • 从uvm_object提供的方法和相关的宏操作来看,它的核心方法主要提供与数据操作的相关服务:
    1. Copy
    2. Clone
    3. Compare
    4. Print
    5. Pack/Unpack
  • 在SV模块的学习中,懂得了什么是句柄拷贝和对象拷贝。因此,无论是copy或者clone,都需要确保在操作的过程中,需要有source object和target object。

域的自动化(field automation)

  • 从UVM通过域的自动化,使得用户在注册UVM类的同时也可以 声明今后会参与到对象拷贝、克隆、打印等操作的成员变量
  • 域的自动化解放了verifier的双手,这使得在使用uvm_object提供的一些预定义方法时,非常便捷,而无需再实现自定义方法。
  • 在了解了 域的自动化常用的宏 之后,用户需要考虑哪些成员变量在注册UVM类(`uvm_{component/object}_utils) 的时候,也一并将它们归置到对应的域列表中,以便为稍后的域方法提供可以自动实现的基础。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class box extends uvm_object;
    int volume = 120;
    color_t color = WHITE;
    string name = "box";

    //域自动化声明变量
    //备注:红宝书P275,域的自动化宏列表
    `uvm_object_utils_begin(box)
        `uvm_field_int(volume, UVM_ALL_ON)
        `uvm_field_enum(color_t, color, UVM_ALL_ON)
        `uvm_field_string(name, UVM_ALL_ON)
    `uvm_object_utils_end
    ...
endclass

box b1, b2;
initial begin
    b1 = new("box1");
    b1.volume = 80;
    b1.color = BLACK;
    b2 = new();
    b2.copy(b1); //把b1里的成员变量数据拷贝到b2
    b2.name = "box2";
end
  • 从这个域的自动化宏的例子来看,在注册box的同时,也声明了将来会参与到uvm_object数据操作的成员变量。
  • 凡是声明了的成员变量,都将在数据操作时自动参与进来。
  • 如果有一些数据没有通过域的自动化来声明的话,它们也将不会自动参与到数据的拷贝、打印等操作,除非用户自己去定义这些数据操作方法。
  • 所以对于新手,请养成习惯,即:
    1. 在注册component或者object的时候,使用`uvm_{component/object}_utils_begin和`uvm_{component/object}_utils_end来配对包裹接下来的域的自动化。
    2. 域的自动化为了尽量识别多数的变量,以及做相应的处理,对应的宏的种类非常多,但是也不需要额外的担心,你可以使用红宝书来查各种域的自动化时所对应的宏。
    3. 域的自动化的相关宏都是`uvm_field_{int/object/string/enum/event/real…}{ARG,FLAG}。ARG表示成员变量,FLAG表示用来标记的数据操作。
    4. FLAG的具体表示可以参照红宝书的表10.3,初学者只需要默认采取UVM_ALL_ON或者UVM_DEFAULT,即将所有的数据操作方法都打开。
    5. 常见的数据操作方法,包括之前所介绍的copy,compare,print,record,pack和其它。将在接下来的部分逐一介绍这些数据操作方法。

拷贝(copy)

  • 在UVM的数据操作中,需要 对copy和clone加以区分 。前者默认已经创建好了对象,只需要对数据进行拷贝;后者则会自动创建对象并对source object进行数据拷贝,再返回target object句柄。
  • 无论是copy或者clone,都需要对数据进行复制。
  • 但是如果数据成员包括句柄,那么拷贝的时候,是否只拷贝该成员句柄本身,还是也额外创建新的对象,拷贝该句柄指向的对象?
  • 从示例可以看到,在进行copy时,默认进行的是深拷贝(deep copy) ,即会执行copy()和do_copy() 。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class ball extends uvm_object;
    int diameter = 10;
    color_t color = RED;
    `uvm_object_utils_begin(ball)
        `uvm_field_int(diameter, UVM_DEFAULT)
        `uvm_field_enum(color_t, color, UVM_NOCOPY)//不参与copy
    `uvm_object_utils_end
    ...

    function void do_copy(uvm_object rhs);//copy的回调函数
        ball b;
        $cast(b, rhs) ;
        $display("ball::do_copy entered..");
        if(b.diameter <= 20) begin
            diameter = 20;
        end
    endfunction
endclass

class box extends uvm_object;
    int volume = 120;
    color_t color = WHITE;
    string name="box";
    ball b;
    `uvm_object_utils_begin(box)
        `uvm_field_int(volume, UV_ALL_ON)
        `uvm_field_enum(color_t, color, UVM_ALL_ON)
        `uvm_field_string(name, UVM_ALL_ON)
        `uvm_field_object(b, UVM_ALL_ON)
    `uvm_object_utils_end
    ...
endclass

box bl, b2;
initial begin
    b1 = new("box1") ;
    b1.volume = 80;
    b1.color = BLACK;
    bl.b.color = WHITE;
    b2 = new() ;
    b2.copy(b1) ;
    b2.name = "box2";
    $display("%s", b1.sprint() ) ;
    $display("%s", b2.sprint() ) ;
end

//输出结果:
//ball::do_copy entered..
//-------------------------------
//Nama     Type     size   Value
//-------------------------------
//box1     box      -      @336
//  volume   integral 32     'h50
//  color    color_t  32     BLACK
//  name     string   4      box1
//  b        ball     -      @337
//    diameter integral 32     'ha
//    color    color_t  32     WHITE
//
//box2      box      -      @338
//  volume   integral 32     'h50
//  color    color_t  32     BLACK
//  name     string   4      box2
//  b        ball     -      @340
//    diameter integral 32     'h14
//    color    color_t  32     RED
  • 新添加了一个类ball,并且在box中例化了一个ball的对象。在拷贝过程中,box的其它成员都正常拷贝了,但对于box::b的拷贝则通过了ball的深拷贝方式进行。
  • 即先执行自动拷贝copy(),来拷贝允许拷贝的域,由于ball::color不允许拷贝,所以只拷贝了ball::diameter。
  • 接下来,再执行do_copy()函数,这个函数是需要用户定义的回调函数(callback function) ,即在copy()执行完后会执行do_copy() 。
  • 如果用户没有定义该函数,那么则不会执行额外的数据操作。从ball::do_copy()函数可以看到,如果被拷贝对象的diameter小于20,那么则将自身的diameter设置为20。因此,最后对象b2.b的成员与b1.b的成员数值不同。

比较(compare)

1
function bit compare (uvm_object rhs, uvm_comparer comparer=null);
  • 默认情况下,如果不对比较的情况作出额外配置,用户可以在调用compare()方法时,省略第二项参数,即采用默认的比较配置。
  • 比较方法经常会在两个数据类中进行。例如从generator产生的一个transaction(数据类) ,和在设计输出上捕捉的transaction(数据类) ,如果它们为同一种类型,除了可以自定义数据比较之外,也可以直接使用uvm_object::compare()函数来实现数据比较和消息打印。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class box extends uvm_object;
    int volume = 120;
    color_t color = WHITE;
    string name="box";
    `uvm_object_utils_begin(box)
        ...
    `uvm_object_utils_end

    ...
endclass

//============================
box b1, b2;
initial begin
    b1 = new("box1") ;
    b1.volume = 80;
    b1.color = BLACK;
    b2 = new("box2") ;
    b2.volume = 90;
    if(!b2.compare(b1)) begin
        `uvm_info("COMPARE", "b2  compared with b1 failure", UVM LOW)
    end
    else begin
        `uvm_info("COMPARE", "b2 compared with b1 success", UVM LOW)
    end
end

//==========================
//输出结果:
//UVM_INFO @O: reporter[MISCMP] Miscompare for box2.volume:lhs='h5a:rhs='h50
//UVM_INFO @0: reporter[MISCMP| Miscompare(s) for object box1@336 vs.box2@337
//UVM_INFO @O: reporter[COMPARE] b2 comapred with bl failure
  • 在上面的两个对象比较中,会将每一个自动化的域进行比较,所以在执行compare()函数时,内置的比较方法也会将比较错误输出。
  • 从结果来看,比较发生了错误,返回0值。那么,b1.color和b2.color虽然不相同,为什么没有比较错误的信息呢?
  • 原因在于,默认的比较器,即uvm_package::uvm_default_comparer最大输出的错误比较信息是1,也就是说当比较错误发生时,不会再进行后续的比较。
  • 实际上,在uvm_object使用到的方法compare()、print()和pack(),如果没有指定数据操作配置对象作为参数时,会使用在uvm_pkg中例化的全局数据操作配置成员。

全局对象

  • 在uvm_pkg中例化了不少全局对象,而在本节中我们会使用到的全局配置对象包括有uvm_default_comparer,uvm_default_printer和uvm_default_packer。
  • 如果用户不想使用默认的比较配置,而是想自己对比较进行设定,可以考虑创建一个uvm_comparer对象,或者修改全局的uvm_comparer对象。

打印(print)

  • 打印方法是核心基类提供的另外一种便于开发和调试的功能。
  • 通过field automation,使得声明之后的各个成员域会在调用uvm_object::print()函数时自动打印出来。
  • 相比于在仿真中设置断点,逐步调试,打印是另外一种调试方式。它的好处在于可以让仿真继续进行, 会在最终回顾执行过程中,从全局理解执行的轨迹和逻辑
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class box extends uvm_object;
    int volume = 120;
    color_t color = WHITE;
    string name = "box";
    `uvm_object_utils_begin(box)
        ...
    `uvm_object_utils_end
endclass

//==================================
box b1;
uvm_table_printer local_printer;
initial begin
    b1 = new("box1") ;
    local_printer = new() ;
    $display("default table printer format") ;
    b1.print() ;
    $display("default line printer format") ;
    uvm_default_printer = uvm_default_line_printer;
    b1.print();
    $display("default tree printer format") ;
    uvm_default_printer = uvm_default_tree_printer;
    b1.print() ;
    $display("customized printer format") ;
    local_printer.knobs.full_name = 1;
    b1.print(local_printer) ;
end

//====================================
//输出结果(只是显示格式不同而已):
//default  table    printer   format
//Name     Type     size      Value
//  box1   box      -         @336
//  volume integral 32        'h78
//  color  color_t  32        WHITE
//  name   string   4         box1
//
//default line printer format
//box1: (box@336) {volume:'h78 color; WHITE name: box1}
//
//default tree printer format
//box1: (box@336) {
//  volume: 'h78
//  color: WHITE
//  name: box1
//}
//
//customized printer format
//Name           Type     size   Value
//box1           box      -      @336
//  box1.volume  integral 32     'h78
//  box1.color   color_t  32     WHITE
//  box1.name    string   4      box1
  • 只要在field automation中声明过的域,在稍后的print()函数执行时,都将打印出它们的类型、大小和数值。如果用户不对打印的格式做出修改,那么在打印时,UVM会按照uvm_default_printer规定的格式来打印。
  • uvm_pkg所包含的用于打印的全局对象,它们分别是:
    1. uvm_default_tree_printer:可以将对象按照树状结构打印。
    2. uvm_default_line_printer:可以将对象数据打印到一行上面。
    3. uvm_default_table_printer:可以将对象按照表格的方式打印。
    4. uvm_default_printer:UVM环境默认的打印设置,该句柄默认指向了uvm_default_table_printer。
  • 通过给全局打印uvm_default_printer赋予不同的打印句柄,就可以在调用任何uvm_object的print()方法时,得到不同的打印格式。

打包和解包(pack & unpack)

1
2
function int pack(ref bit bitstream[], input uvm_packer packer=null);
function int unpack(ref bit bitstream[], input uvm_packer packer=null);
  • pack是为了将自动化声明后的域(标量) 打包为比特流(bit stream) 。即将各个散乱的数据,整理到bit数据串中,类似于struct packed的整理方式,但又能充分利用数据空间,也更容易与硬件之间进行数据传递和比对。
  • unpack与pack相反,即将串行数据解包变为原有的各自域。该操作适用于从硬件一侧接收串行数据,进行校验之后,还原为软件一侧对象中各自对应的成员变量。
  • pack与unpack在通常的UVM环境中使用较少,但是 当与外界环境,例如System C发生大规模数据传递,该方法是首选 ,因为可以通过简单数据流实现精确的数据传输,另外,在UVM与FPGA、emulator之间进行数据交换时,该方法也由于简便得到了青睐。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class box extends uvm_object;
    int volume = 120;
    int height = 20;
    color_t color = WHITE;
    `uvm_object_utils_begin(box)
        ...
    `uvm_object_utils_end
endclass

box b1, b2;
bit packed_bits[];
initial begin
    b1 = new("box1") ;
    b2 = new("box2") ;
    b1.volume = 100;
    b1.height = 40;
    b1.color = RED;
    b1.print();
    b1.pack(packed_bits);
    $display("packed bit stream size is %d \n", packed_bits.size()) ;
    b2.unpack(packed_bits) ;
    b2.print() ;
end

//================================
//输出结果:
//Name      Type     Size   Value
//box1      box      -      @336
//  volume  integral 32     'h64
//  height  integral 32     'h28
//  color   color_t  32      RED
//packed bits stream size is  96
//box2      box      -      @337
//  volume  integral 32     'h64
//  height  integral 32     'h28
//  color   color_t  32     RED

phase机制

写在前头

  • SV的验证环境构建中,传统的硬件设计模型在仿真开始前,已经完成例化和连接了;而SV的软件部分对象例化则需要在仿真开始后执行。
  • 虽然对象例化通过调用构建函数new()来实现,但是单单通过new()函数无法解决一个重要问题,那就是 验证环境在实现层次化时,如何保证例化的先后关系,以及各个组件在例化后的连接
  • 如果需要实现高级功能,例如在顶层到底层的配置时,SV也无法在底层组件例化之前完成对底层的配置逻辑。
  • 因此UVM在验证环境构建时,引入了phase机制,通过该机制我们可以很清晰地 将UVM仿真阶段层次化
  • 这里的层次化, 不单单是各个phase的先后执行顺序,而且处于同一phase中的层次化组件之间的phase也有先后关系
  • 如果暂时抛开phase的机制剖析,对于UVM组件的开发者而言,他们主要关心各个phase执行的先后顺序。
  • 在定义了各个phase虚方法后,UVM环境会按照phase的顺序分别调用这些方法。
phase 函数/任务 执行顺序 功能 典型应用
build 函数 自顶向下 创建和配置测试平台的结构 创建组件和寄存器模型,设置或者获取设置
connect 函数 自底向上 建立组件之间的连接 连接TLM的接口,连接寄存器模型和adapter
end_of_elaboration 函数 自底向上 测试环境的微调 显示环境结构、打开文件,为组件添加额外配置
start_of_simulation 函数 自底向上 准备测试环境的仿真 显示环境结构、设置断点,设置初始运行时的配置值
run 任务 自底向上 激励设计 提供激励、采集数据、数据比较
extract 函数 自底向上 从测试环境中收集数据 从测试平台提取剩余数据,从设计观察最终状态
check 函数 自底向上 检查任何不期望的行为 检查不期望的数据
report 函数 自底向上 报告测试数据 报告测试结果,并将其写入文件中
final 函数 自顶向下 完成测试活动、结束仿真 关闭文件,结束联合仿真引擎

执行机制

  • 源码:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class subcomp extends uvm_component;
    `uvm_component_utils(subcomp)

    function new(string name, uvm_component parent) ;
        super.new(name, parent);
    endfunction

    function void build_phase(uvm_phase phase);
        `uvm_info("build_phase", "", UVM_LOW)
    endfunction

    function void connect_phase(uvm_phase phanse);
        `uvm_info("connect_phase", "", UVM_LOW)
    endfunction

    function void end_of_elaboration_phase(uvm_phase phase);
        `uvm_info("end_of_elaboration_phase", UVM_LOW)
    endfunction

    function void start_of_simulation_phase(uvm_phase phase);
        `uvm_info("start_of_simulation_phase", "", UVM_LOW)
    endfunction

    task run_phase(uvm_phase phase);
        `uvm_info("run_phase", "", UVM_LOW)
    endtask

    function void extract_phase(uvm_phase phase);
        `uvm_info("extract_phase", "", UVM_LOW)
    endfunction

    function void check_phase(uvm_phase phase);
        `uvm_info("check_phase", "", UVM_LOW)
    endfunction

    function void report_phase(uvm_phase phase);
        `uvm_info("report_phase", "", UVM_LOW)
    endfunction

    function void final_phase(uvm_phase phase);
        `uvm_info("final_phase", "", UVM_LOW)
    endfunction
endclass

class topcomp extends subcomp;
    subcomp cl, c2;
    ...

    function void build_phase(uvm_phase phase);
        uvm_info("build_phase", "", UVM_LOW)
        c1 = subcomp::type_id::create("c1", this);
        c2 = subcomp::type_id::create("c2", this);
    endfunction
endclass

class test1 extends uvm_test;
    topcomp t1;
    ...
    function void build_phase(uvm_phase phase) ;
        t1 = topcomp::type_id::create("t1",this);
    endfunction
endclass
  • 输出结果
UVM_INFO @0: uvm_test_top.t1 [build_phase]
UVM_INFo @0: uvm_test_top.t1.c1 [build_phase】
UVM_INFO @0: uvm_test_top.t1.c2 [build_phase]
UVM_INFO @0: uvm_test_top.t1.cl [connectJphase]
UvM_INFO @0: uvm_test_top.t1.c2 [connect_phase]
UVM_INFO @0: uvm_test_top.t1 [connect_phase]
UVM_INFO @0: uvm_test_top.t1.c1 [end_of_elaboration_phase]
UVM_INFO @0: uvm_test_top.t1.c2 [end_of_elaboration_phase]
UVM_INFO @0: uvm_test_top.t1 [end_of_elaboration_phase]
UVM_INFO @O: uvm_test_top.t1.c1 [start_of_simulation_phase]
UVM_INFO @0: uvm_test_top.t1.c2 [start_of_simulation_phase]
UVM_INFO @0: uvm_test_top.t1.   [start_of_simulation_phase]
UVM_INFO @O: uvm_test_top.t1.c1 [run_phase]
UVM_INFO @0: uvm_test_top.t1.c2 [run_phase]
UVM_INFO @0: uvm_test_top.t1.   [run_phase]
UVM_INFO @O: uvm_test_top.t1.c1 [extract_phase]
UVM_INFO @0: uvm_test_top.t1.c2 [extract_phase]
UVM_INFO @0: uvm_test_top.t1.   [extract_phase]
UVM_INFO @O: uvm_test_top.t1.c1 [check_phase]
UVM_INFO @0: uvm_test_top.t1.c2 [check_phase]
UVM_INFO @0: uvm_test_top.t1.   [check_phase]
UVM_INFO @O: uvm_test_top.t1.c1 [report_phase]
UVM_INFO @0: uvm_test_top.t1.c2 [report_phase]
UVM_INFO @0: uvm_test_top.t1.   [report_phase]
UVM_INFO @O: uvm_test_top.t1.c1 [final_phase]
UVM_INFO @0: uvm_test_top.t1.c2 [final_phase]
UVM_INFO @0: uvm_test_top.t1.   [final_phase]

9个主要phase

  • 上面的九个phase对于一个测试环境的生命周期而言,是有固定的先后执行顺序的;同时对于同一个phase中的组件,执行也会按照层次的顺序或者自顶向下、或者自底向上来执行。
  • 对于 build phase,执行顺序按照自顶向下 ,这符合验证结构建设的逻辑。因为只有先例化高层组件,才会创建空间来容纳低层组件。
  • 只有uvm_component及其继承于uvm_component的子类,才会按照phase机制将上面的九个phase先后执行完毕。
  • 常用的phase包括build、connect、run和report,它们分别完成了组件的 建立、连接、运行和报告 。这些phase在uvm_component中通过_phase的后缀完成了虚方法的定义,比如build_phase()可以定义一些 组件例化和配置的任务
  • 在所有phase中, 只有run_phase方法是一个可以耗时的任务,这意味着该方法可以完成一些等待、激励、采样的任务。对于其它phase对应的方法都是函数, 必须立即返回(0耗时)
  • 在run_phase中, 用户如果要完成测试, 通常需要组织下面的激励序列:
    1. 上电
    2. 复位
    3. 寄存器配置
    4. 发送主要测试内容
    5. 等待DUT完成测试

12个分之phase

  • 在用户发送激励的 一种简单方式是,在run_phase中完成上面所有的激励;另外一种方式是,如果用户可以将上面几种典型序列划分到不同区间,让对应的激励按区间顺序发送的话,可以让测试更有层次 。因此run_phase又可以分为下面12个phase:
    1. pre_reset_phase
    2. reset_phase
    3. post_reset_phase
    4. pre_configure_phase
    5. configure_phase
    6. post_configure_phase
    7. pre_main_phase
    8. main_phase
    9. post_main_phase
    10. pre_shutdown_phase
    11. shutdown_phase
    12. post_shutdown_phase
  • 实际上run_phase任务和上面细分的12个phase是 并行 的,即在start_of_simulation_phase任务执行以后,run_phase和reset_phase开始执行,而在shutdown_phase执行完成之后,需要等待run_phase执行完才可以进入extract_phase。

UVM编译和运行顺序

UVM仿真开始

  • 要在仿真开始时建立验证环境,用户可以考虑选择下面几种方式:
    1. 可以通过全局函数(由uvm_pkg提供) run_test() 来选择性地指定要运行哪一个uvm_test。这里的test类均继承于uvm_test。这样的话, 指定的test类将被例化并指定为顶层的组件。一般而言,run_test()函数可以在合适的module/program中的initial进程块中调用。
    2. 如果没有任何参数传递给run_test(),那么用户可以在仿真时通过传递参数 +UVM_TEST NAME=<test_name> ,来指定仿真时调用的uvm_test。当然,即便run_test()函数在调用时已经有test名称传递,在仿真时+UVM_TEST NAME=<test_name>也可以从顶层覆盖已指定的test。这种方式使得仿真不需要通过再次修改run_test()调用的test名称和重复编译,就可以灵活选定test。
  • 无论上面哪一种方式,都必须在顶层调用全局函数run_test(),用户可以考虑不传递test名称作为参数,而在仿真时通过传递参数+UVM_TESTNAME=<test_name>来选择test。
  • 全局函数run_test()的重要性,正是从uvm_root创建了一个UVM世界

UVM世界的"诞生”

1
2
3
4
5
6
7
task run_test(string test_name="");
    uvm_root top;
    uvm_coreservice_t cs;
    cs=uvm_coreservice_t::get();
    top=cs.get_root();
    top.run_test(test_name);
endtask
  • UVM顶层类uvm_root。 该类也继承于uvm_component ,它也是UVM环境结构中的一员,而它可以作为顶层结构类。
  • 它提供了一些像run_test()的这种方法,来充当了UVM世界中的核心角色。
  • 在uvm_pkg中, 有且只有一个顶层类uvm_root所例化的对象,即uvm_top
  • uvm_top(也就是uvm_root)承担的核心职责包括:
    1. 作为隐形的UVM世界顶层,任何其它的组件实例都在它之下,通过创建组件时指定parent来构成层次。
    2. 如果parent设定为null,那么它将作为uvm_top的子组件。
    3. phase控制。控制所有组件的phase顺序。
    4. 索引功能。通过层次名称来索引组件实例。
    5. 报告配置。通过uvm_top来全局配置报告的繁简度(verbosity) 。
    6. 全局报告设备。由于可以全局访问到uvm_top实例, 因此UVM报告设备在组件内部和组件外部(例如module和sequence) 都可以访问。
  • 通过uvm_top调用方法run_test(test_name),uvm_top做了如下的初始化:
    1. 得到正确的test_name。
    2. 初始化objection机制(控制仿真退出)。
    3. 创建uvm_test_top实例。
    4. 调用phase控制方法,安排所有组件的phase方法执行顺序。
    5. 等待所有phase执行结束, 关闭phase控制进程。
    6. 报告总结和结束仿真。

UVM仿真结束

  • UVM-1.1之后, 结束仿真的机制有且只有一种,那就是利用objection挂起机制来控制仿真结束
  • uvm_objection类提供了一种供所有component和sequence共享的计数器。 如果有组件来挂起 *objection,那么它还应该记得落下objection
  • 参与到objection机制中的组件,可以独立的各自挂起objection,来防止run phase退出,但是只有这些组件都落下objection后,uvm_objection共享的counter才会变为0,这意味run phase退出的条件满足,因此可以退出run phase。
  • 对于uvm_objection类,用来反停止的控制方法包括:
    1. raise_objection(uvm_object obj=null, string description=”", int count=1) 挂起objection
    2. drop_objection(uvm_object obj=null, string description=”", int count=1) 落下objection
    3. set_drain_time(uvm_object obj=null, time drain) 设置退出时间
  • 对这几种方法,在实际应用中的建议有:
    1. 对于component()而言,用户可以在run_phase()中使用phase.raise_objection()/phase.drop_objection()来控制run phase退出。
    2. 用户最好为description字符串参数提供说明,这有利于后期的调试。
    3. 应该使用默认count值。
    4. 对于uvm_top或者uvm_test_top应该尽可能少地使用set_drain_time() 。

objection防止仿真退出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class test1 extends uvm_test;
    ...
    task run_phase(uvm_phase phase) ;
        phase.raise_objection(this) ;
        `uvm_info("run_phase", "entered..", UVM_LOW)
        #1us;
        `uvm_info("run_phase", "exited..", UVM_LOW)
        phase.drop_objection(this);
    endtask
endclass
  • 如果要在component中挂起objection,建议在一进入run_phase()后就挂起,保证objection counter及时被增加。
  • 另外,用户需要习惯在sequence中挂起objection,由于sequence不是uvm_component类,而是uvm_object类,因此它只有body()方法,而没有run_phase()方法。
  • 所以在sequence中使用objection机制时,可以在body()中的首尾部分挂起和落下objection。

config机制

写在前头

  • 在验证环境的创建过程build phase中, 除了组件的实例化,配置也是必不可少的
  • 为了验证环境的复用性,通过外部的参数配置,使得环境在创建时可以根据不同参数来选择创建的组件类型、组件实例数目、组件之间的连接以及组件的运行模式等。
  • 在更细致的环境调节(environment tuning)中有更多的变量需要配置,例如for-loop的阈值、字符串名称、随机变量的生成比重等。
  • 比起重新编译来调节变量,如果在仿真中可以通过变量设置来修改环境,那么就更灵活了,而UVM config机制正提供了这样的便捷。
  • 在UVM提供了uvm_config_db配置类以及几种方便的变量设置方法来实现仿真时的环境控制,常见的uvm_config_db类的使用方式包括:
    1. 传递virtual interface到环境中
    2. 设置单一变量值,例如int、string、enum等
    3. 传递配置对象(config object)到环境
1
2
uvm_config_db#(T)::set(uvm_component cntxt, string inst_name, string field_name, T value);
uvm_config_db#(T)::get(uvm_component cntxt, string inst_name, string field_name, inout T value);

interface传递

  • interface传递可以很好地解决了连接硬件世界和软件世界。
  • 而在之前SV验证模块中,虽然SV可以通过层次化的interface的索引来完成了传递,但是这种方式不利于软件环境的封装和复用。
  • UVM的uvm_config_db使得接口的传递和获取彻底分离开来。
  • 在实现接口传递的过程中需要注意:
    1. 接口传递应该发生在run_test()之前 。这保证了在进入build phase之前,virtual interface已经被传递到uvm_config_db中。
    2. 用户应当把interface与virtual interface的声明区分开来,在 传递过程中的类型应当为virtual interface ,即实际接口的句柄。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
interface intf1;
    logic enable = 0;
endinterface

class comp1 extends uvm_component;
    `uvm component_utils(comp1)
    virtual intf1 vif;
    ...
    function void build_phase(uvm_phase phase) ;
        if(!uvm_config_db#(virtual intf1)::get(this, "", "vif", vif) ) begin
            `uvm_error("GETVIF", "no virtual interface is assigned")
        end
        `uvm_info("SETVAL", $sformatf("vif.enable is %b before set", vif.enable), UVM_LOW)
        vif.enable = 1;
        `uvm_info("SETVAL", $sformatf("vif.enable is tb after set", vif.enable), UVM_LOW)
    endfunction
endclass

//===================================
class test1 extends uvm_test;
    `uvm_component_utils(test1)
    comp1 cl;
    ...
endclass

//===================================
intf1 intf();
initial begin
    uvm_config_db#(virtual intf1)::set(uvm_root::get() , "uvm_test_top.c1", "vif", intf);
    run_test("test1");
end

//===================================
//输出结果:
//UVM_INFO @O: reporter[RNTST] Running test test 1...
//UVM_INFO @O: uvm_test_top.c1 [SETVAL] vif.enable is O before set
//UVM_INFO @o: uvm_test_top.c1 [SETVAL] vif.enable is 1 after set

变量设置

  • 在各个test中,可以在build_phase对底层组件变量加以配置,进而在环境例化之前完成配置,使得环境可以按照预期运行。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class comp1 extends uvm_component;
    uvm_component_utils(comp1)
    int val1=1;
    string str1="null";

    function void build_phase(uvm_phase phase);
        `uvm_info("SETVAL", $sformatf("val1 is %d before get", val1), UVM_LOW)
        `uvm_info("SETVAL", $sformatf("str1 is %s before get", str1), UVM_LOW)
        uvm_config_db#(int)::get(this, "", "val1", vall);
        uvm config_db#(string)::get(this, "", "str1", str1);
        `uvm_info("SETVAL", $sformatf("val1 is·%d after get", v al1) , UVM_LOW)
        `uvm_info("SETVAL", $sformatf("str1 is %s after get", str1) , UVM_LOW)
    endfunction
endclass

//=====================================
class test1 extends uvm_test;
    `uvm_component_utils(test1)
    comp1 cl;
    ...

    function void build_phase(uvm_phase phase);
        uvm_config_db#(int)::set(this, "c1", "val1", 100);
        uvm_config_db#(string)::set(this, "c1", "str1", "comp1") ;
        c1 = comp1::type_id::create("c1", this);
    endfunction
endclass

//=====================================
//输出结果:
// UVM_INFO @O: uvm_test_top.c1 [SETVAL] val1 is 1 before get
// UVM_INFO @O: uvm_test_top.c1 [SETVAL] str1 is null before get
// UVM_INFO @O: uvm_test_top.c1 [SETVAL] val1 is 100 after get
// UVM INFO @O: uvm test_top.c1 [SETVALl str1 is comp1 after get
  • 注意细节: 先传递后创建

object传递

  • 在test配置中,需要配置的参数不只是数量多,而且可能还分属于不同的组件。
  • 那么如果对这么多层次中的变量做出类似上面的变量设置,那会需要更多的代码,容易出错还不易于复用,甚至底层组件的变量被删除后, 也无法通过uvm_config_db::set()得知配置是否成功。
  • 然而如果将每个组件中的变量加以整合,首先放置到一个uvm_object中,再对中心化的配置对象进行传递,那么将会更有利于整体环境的修改维护。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class config1 extends uvm_object;
    int val1 = 1;
    int str1 = "null";
    `uvm_object_utils(config1)
    ...
endclass

//=================================
class comp1 extends uvm_component;
    uvm_component_utils(comp1)
    config1 cfg;
    ...
    function void build_phase(uvm_phase phase) ;
        uvm_object tmp;
        uvm_config_db#(uvm_object)::get(this, "", "cfg", tmp);
        void'($cast(cfg, tmp));
        `uvm_info("SETVAL", $sformatf("cfg.val1 is %d after get", cfg.val1), UVM_LOW)
        `uvm_info("SETVAL", $sformatf("cfg.str1 is %s after get", cfg.str1) , UVM_LOW)
    endfunction
endclass

//=================================
class test1 extends uvm_test;
    `uvm_component_utils(test1)
    comp1 c1, c2;
    config1 cfg1, cfg2;
    ...
    function void build_phase(uvm_phase phase);
        cfg1 = config1::type_id::create("cfg1");
        cfg2 = config1::type_id::create("cfg2");
        cfg1.val1 = 30;
        cfgl.strl = "c1";
        cfg2.val1 = 50;
        cfg2.str1 = "c2";
        uvm_config_db#(uvm_object)::set(this, "c1", "cfg", cfg1) ;
        uvm_config_db#(uvm_object)::set(this, "c2", cfg, cfg2) ;
        c1 = comp1::type_id::create("c1", this);
        c2 = comp1::type_id::create("c2", this);
    endfunction
endclass

总结

  • 在使用uvm_config_db::set()/get()时,实际发生了这些后台操作:
    1. uvm_config_db::set()通过层次和变量名,将这些信息放置到uvm_pkg唯一的全局变量uvm_pkg::uvm_resources.
    2. 全局变量uvm_resources用来存储和释放配置资源信息(resource information) 。uvm_resources是uvm_resource_pool类的全局唯一实例,该实例中有两个resource数组用来存放配置信息,这两个数组中一个由层次名字索引,一个由类型索引,通过这两个关联数组可以存放通过层次配置的信息。
    3. 同时,底层的组件也可以通过层次或者类型来取得来自高层的配置信息。这种方式使信息的配置和获取得到剥离,便于调试复用。
    4. 在使用uvm_config_db::get()方法时,通过传递的参数构成索引层次,然后在uvm_resource已有的配置信息池中索引该配置,如果索引到,方法返回1,否则返回0。

建议

  • 在使思set()/get()方法时,传递的参数类型应当上下保持一致。对于uvm_object等实例的传递如果get型与set类型不一致,应当首先通过$cast()完成类型转换,再对类型转换后的对象进行操作。
  • set()/get()方法传递的参数可以使用通配符“*”来表示任意的层次,类似于正则表达式的用法。同时用户需要懂得“*.comp1”与“*comp1”的区别,前者表示在目前层次以下所有名称为“comp1”的组件,而后者表示包括当前层次及当前层次以下所有名为“comp1”的组件。
  • 在module环境中如果要使用uvm_config_db::set(),则传递的第一个参数uvm_component_cntxt参数用来表示当前的层次。如果当前的层次为最高层,用户可以设置为null,也可以设置为uvm_root::get()来表示uvm_root的全局顶层实例。
  • 在使用配置变量时,应当确保先进行uvm_config_db::get()操作,在获得了正确的配置值以后再使用。
  • 应当尽量确保uvm_config_db::set()方法在相关配置组件创建前调用。这是因为只有先完成配置,相关组件在例化前才可以得到配置值继而正确地例化。
  • 在set()方法第一个参数使用当前层次的前提下,对于同一组件的同一个变量,如果有多个高层组件对该变量进行设置,那么较高层组件的配置会覆盖较低层的配置,但是如果是同一层次组件对该变量进行多次配置时,应该遵循后面的配置会覆盖前面的配置。
  • 用户应该在使用uvm_config_db::get()方法时, 添加便于调试的语句,例如通过UVM报告信息得知get()方法中的配置变量是否从uvm_confog_db获取到,如果没有获取,是否需要采取其他措施。

消息管理

写在前头

  • 在一个好的验证系统应该具有消息管理特性,它们是:
    1. 通过一种标准化的方式打印信息
    2. 过滤(重要级别)信息
    3. 打印通道
  • 这些特性在UVM中均有支持, UVM提供了一系列丰富的类和方法来生成和过滤消息:
    1. 消息方法
    2. 消息处理
    3. 消息机制

消息方法

  • 在UVM环境中或者环境外,只要有引入uvm_pkg,均可以通过下面的方法来按照消息的严重级别和冗余度来打印消息。
1
2
3
4
5
6
7
function void uvm_report_info(string id, string message, int verbosity=UVM MEDIUM, string filename="", int line=0) ;

function void uvm_report_warning(string id, string message, int verbosity=UVM_MEDIUM, string filename="", int line=0) ;

function void uvm report error(string id, string message, int verbosity=UVM_LOW, string filename="", int line=0) ;

function void uvm_report_fatal(string id, string message, int verbosity=UVM_NONE, string filename="", int line=0) ;
  • 四个消息函数有若干共同的信息,它们是 严重级别(severity) 、冗余度(verbosity) 、消息ID、消息、文件名和行号
    1. 严重级别:从函数名本身也可以得出,这四个严重级别分别是UVM_INFO、UVM_WARNING、UVM_ERROR、UVM_FATAL。不同的严重级别在打印的消息中也会有不同的指示来区别,同时仿真器对不同严重级别消息的处理方式也不一样。例如对于UVM_FATAL的消息,默认情况下仿真会停止。
    2. 消息ID:该ID可以是任意的字符串,用来标记该消息。这个标记会同消息本身打印出来,同时不同的标记也可以用来进行消息处理。
    3. 消息:即消息文本的主体。
    4. 冗余度:冗余度与消息处理中的过滤直接相关。冗余度的设置如果低于过滤的开关,那么该消息会打印出来,否则不会被打印出来。但是无论信息是否会被打印出来,这都与对消息采取的其它措施没有关系,例如仿真停止。
    5. 文件名和行号:这些信息用来提供消息发生时所在的文件和行号。用户可以使用默认值, 而UVM后台会自动填补它们原本的文件名和行号, 同时也在打印时将文件名和行号输出。

消息处理

  • 与每一条消息对应的是如何处理这些消息。通常情况下,消息处理的方式是同消息的严重级别对应的。如果用户有额外的需求,也可以修改对各个严重级别的消息处理方式。
处理方式 说明
NO_ACTION 不做任何处理
UVM_DISPLAY 将消息输出到标准输出端口
UVM_LOG 将消息写入到文件
UVM_COUNT 增加退出计算变量quit_count,达到一定数值时则停止仿真
UVM_EXIT 立刻停止仿真
UVM_CALL_HOOK 调用对应的回调函数
UVM_STOP 停止仿真
  • 不同的严重级别消息,用户可以使用默认的消息处理方式
严重级别 默认处理方式
UVM_INFO UVM_DISPLAY
UVM_WARNING UVM_DISPLAY
UVM_ERROR UVM_DISPLAY & UVM_COUNT
UVM_FATAL UVM_DISPLAY & UVM_EXIT

消息宏

  • 如果要做自定义的消息处理方式,用户可以通过uvm_report_object类提供的方法进行配置。
  • uvm_report_object类是间于uvm_object类与uvm_component类之间的中间类,它的主要功能是完成消息打印和管理。
  • UVM也提供了一些宏来对应上面的消息方法,用户也可以使用这些宏来处理消息。
方法调用 宏调用
uvm_report_info() `uvm_info(ID, MESSAGE, VERBOSITY)
uvm_report_warning() `uvm_warning(ID, MESSAGE)
uvm_report_error() `uvm_error(ID, MESSAGE)
uvm_report_fatal() `uvm_fatal(ID, MESSAGE)

消息机制

  • 消息处理是由uvm_report_handler类来完成的,而每一个uvm_report_object类中都有一个uvm_report_handler实例。
  • 上面的uvm_report_object消息处理方法或者uvm_component消息处理方法, 都是针对于这些uvm_report_handler做出的配置。
  • 除了上面的常见使用方法,用户还可以做出更高级的消息控制。例如,当UVM_ERROR出现之后,仿真默认会停止,这是由于设置了UVM_ERROR的处理方式是UVM_COUNT数量达到上限(默认为1),即停止仿真。可以通过set_max_quit_count来修改UVM_COUNT值。

回调函数

  • 消息用户在处理信息时还希望做出额外的处理,这时回调函数就显得很有必要了,uvm_report_object类提供了下面的回调函数满足用户更多的需求:
1
2
3
4
5
6
7
8
9
function bit report_hook(string id, string message, int verbosity, string filename, int line );

function bit report_info_hook(string id, string message, int verbosity, string filename, int line );

function bit report_warning_hook(string id, string message, int verbosity, string filename, int line );

function bit report_error_hook(string id, string message, int verbosity, string filename, int line );

function bit report_fatal_hook(string id, string message, int verbosity, string filename, int line );
  • report_hook(函数通过结合消息管理时的UVM_CALL_HOOK参数,结合用户自定义的回调函数,就可以实现更丰富的配置。
  • 这样用户在调用回调函数时,首先会调用report_hook()函数,接下来才按照severity级别来选择更细致的回调函数report_SEVERITY_hook() 。
  • 默认情况下,report_hook()函数返回值为1,进而再转入severity hook函数。
  • 如果report_hook()函数由用户自定义且返回0的话,那么后续report_SEVERITY_hook()函数不会执行。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class test1 extends uvm_test;
    integer f;
    `uvm_component_utils(test1)
    ...
    function void build_phase(uvm_phase phase) ;
        set_report_severity_action(UVM_ERROR, UVM_DISPLAY | UVM_CALL_HOOK);
        set_report_verbosity_level(UVM_LOW) ;
    endfunction

    task run_phase(uvm_phase phase) ;
        uvm_report_info("RUN", "infol", UVM_MEDIUM) ;
        uvm_report_info("RUN", "info2", UVM_LOW) ;
        uvm_report_warning("RUN", "warning1", UVM_LOW) ;
        uvm_report_error("RUN", "error1", UVM LOW) ;
        uvm_report_error("RUN", "error2", UVM_HIGH) ;
        uvm_report_error("RUN", "error3", UVM_LOW) ;
    endtask

    function void report_phase(uvm_phase phase) ;
        $fclose(f) ;
    endfunction

    function bit report_hook(string id, string message, int verbosity, string filename, int line) ;
        uvm_report_info("RPTHOOK", $sformatf("%s: %s", id, message), UVM_LOW) ;
        return 1;
    endfunction

    function bit report_error_hook(string id, string message, int verbosity, string filename, int line) ;
        uvm_report_info("ERRHOOK", $sformatf("%s: %s", id, message) , UVM LOW) ;
        return 1;
    endfunction
endclass

消息管理

  • report_hook() 函数通过结合消息管理时的UVM_CALL_HOOK参数,结合用户自定义的回调函数,就可以实现更丰富的配置。
  • 这样用户在调用回调函数时,首先会调用report_hook()函数,接下来才按照severity级别来选择更细致的回调函数report_SEVERITY_hook() 。
  • 默认情况下,report_hook()函数返回值为1,进而再转入severity hook函数。
  • 如果report_hook()函数由用户自定义且返回0的话,那么后续report_SEVERITY_hook()函数不会执行。

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