UVM 基础

field_automation 机制

定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
src/ch2/section2.3/2.3.7/my_transaction.sv
    class my_transaction extends uvm_sequence_item;
        rand bit[47:0] dmac;
        rand bit [47:0] smac;
        rand bit [15:0] ether_type;
        rand byte       pload[];
        rand bit [31:0] crc;
        
        `uvm_object_utils_begin(my_transaction)
            `uvm_field_int(dmac, UVM_ALL_ON)
            `uvm_field_int(smac, UVM_ALL_ON)
            `uvm_field_int(ether_type, UVM_ALL_ON)
            `uvm_field_array_int(pload, UVM_ALL_ON)
            `uvm_field_int(crc, UVM_ALL_ON)
        `uvm_object_utils_end
        
    endclass

上述代码中是先放入dmac, 再依次放入smac、 ether_type、 pload、 crc 是按field 的先后顺序来排的
这里使用 uvm_object_utils_beginuvm_object_utils_end 来实现my_transaction的factory注册,
在这两个宏中间, 使用uvm_field宏注册所有字段。 uvm_field系列宏随着transaction成员变量的不同而不同,
如上面的定义中出现了针对bit类型的uvm_field_int及针对byte类型动态数组的uvm_field_array_int

使用:

当使用上述宏注册之后, 可以直接调用copy、 compare、 print等函数, 而无需自己定义。 这极大地简化了验证平台的搭建, 提
高了效率

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
文件: src/ch2/section2.3/2.3.7/my_model.sv
    task my_model::main_phase(uvm_phase phase);
        my_transaction tr;
        my_transaction new_tr;
        super.main_phase(phase);
        while(1) begin
            port.get(tr);    // 从 FIFO 获得 tr
            new_tr = new("new_tr");
            new_tr.copy(tr);       // 复制 tr 中的数据  uvm_object_utils_begin和uvm_object_utils_end 包起来的数据 
            `uvm_info("my_model", "get one transaction, copy and print it:", UVM_LOW)
            new_tr.print();       // 格式化打印 tr 中 uvm_object_utils_begin和uvm_object_utils_end 包起来的数据 
            ap.write(new_tr);
        end
    endtask
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
文件: src/ch2/section2.3/2.3.7/my_scoreboard.sv
    
    while (1) begin
        act_port.get(get_actual);
        if(expect_queue.size() > 0) begin
            tmp_tran = expect_queue.pop_front();
            result = get_actual.compare(tmp_tran);
            if(result) begin
                `uvm_info("my_scoreboard", "Compare SUCCESSFULLY", UVM_LOW);
            end
            

上述例子直接调用 compare 来进行比较

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
文件: src/ch2/section2.3/2.3.7/my_driver.sv
task my_driver::drive_one_pkt(my_transaction tr);
    byte unsigned data_q[];
    int           data_size;
    data_size = tr.pack_bytes(data_q) / 8;
    `uvm_info("my_driver", "begin to drive one pkt", UVM_LOW);
    repeat(3) @(posedge vif.clk);
    for ( int i = 0; i < data_size; i++ ) begin
        @(posedge vif.clk);
        vif.valid <= 1'b1;
        vif.data <= data_q[i];
    end
    @(posedge vif.clk);
    vif.valid <= 1'b0;
    `uvm_info("my_driver", "end drive one pkt", UVM_LOW);
endtask

上述例子调用pack_bytes将tr中所有的字段变成byte流放入data_q中

与uvm_object 相关的宏

uvm_object_utils

它用于把一个直接或间接派生自uvm_object的类注册到factory中

uvm_object_param_utils

它用于把一个直接或间接派生自uvm_object的参数化的类注册到factory中
所谓参数化的类, 是指类似于如下的类:

1
class A#(int WIDTH=32) extends uvm_object;

参数化的类在代码可重用性中经常用到。 如果允许, 尽可能使用参数化的类, 它可以提高代码的可移植性

uvm_object_utils_begin

这个宏在第2章介绍my_transaction时出现过, 当需要使用field_automation 机制 时, 需要使用此宏。
如果使用了此宏, 而又没有把任何字段使用uvm_field系列宏实现, 那么会出现什么情况呢?

1
2
`uvm_object_utils_begin(my_object)
`uvm_object_utils_end

答案是不会出现任何问题, 这样的写法完全正确, 可以尽情使用。

uvm_object_param_utils_begin

uvm_object_param_utils_begin: 与uvm_object_utils_begin宏一样, 只是它适用于参数化的且其中某些成员变量要使用 field_automation机制实现的类

uvm_object_utils_end:

它总是与uvm_object_*_begin成对出现, 作为factory注册的结束标志。

与uvm_component相关的宏

uvm_component_utils:

它用于把一个直接或间接派生自uvm_component的类注册到factory中

uvm_component_param_utils:

它用于把一个直接或间接派生自uvm_component的参数化的类注册到factory中

uvm_component_utils_begin:

这个宏与uvm_object_utils_begin相似, 它用于同时需要使用factory机制和field_automation机制注册
的类。 在类似于my_transaction这种类中使用field_automation机制可以让人理解, 可是在component中使用field_automation机制有必
要吗? uvm_component派生自uvm_object, 所以对于object拥有的如compare、 print函数都可以直接使用。 但是filed_automation机制
对于uvm_component来说最大的意义不在于此, 而在于可以自动地使用config_db来得到某些变量的值。 具体的可以参考3.5.3节的
介绍。

uvm_component_param_utils_begin:

与uvm_component_utils_begin宏一样, 只是它适用于参数化的, 且其中某些成员变量要使用field_automation机制实现的类。

uvm_component_utils_end:

它总是与uvm_component_*_begin成对出现, 作为factory注册的结束标志。

uvm_component的限制

uvm_component是从uvm_object派生来的。 从理论上来说, uvm_component应该具有uvm_object的所有的行为特征。 但是, 由
于uvm_component是作为UVM树的结点存在的, 这一特性使得它失去了uvm_object的某些特征。
在uvm_object中有clone函数, 它用于分配一块内存空间, 并把另一个实例复制到这块新的内存空间中。 clone函数的使用方式
如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class A extends uvm_object;

endclass
class my_env extends uvm_env;
virtual function void build_phase(uvm_phase phase);
A a1;
A a2;
a1 = new("a1");
a1.data = 8'h9;
$cast(a2, a1.clone());
endfunction
endclass

上述的clone函数无法用于uvm_component中, 因为一旦使用后, 新clone出来的类, 其parent参数无法指定
copy函数也是uvm_object的一个函数, 在使用copy前, 目标实例必须已经使用new函数分配好了内存空间, 而使用clone函数
时, 目标实例可以只是一个空指针。 换言之, clone=new+copy。
虽然uvm_component无法使用clone函数, 但是可以使用copy函数。 因为在调用copy之前, 目标实例已经完成了实例化, 其
parent参数已经指定了。
uvm_component的另外一个限制是, 位于同一个父结点下的不同的component, 在实例化时不能使用相同的名字。 如下的方式
中都使用名字“a1”是会出错的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class A extends uvm_component;

endclass
class my_env extends uvm_env;
virtual function void build_phase(uvm_phase phase);
A a1;
A a2;
a1 = new("a1", this);
a2 = new("a1", this);
endfunction
endclass

uvm_component 中的 parent 参数

UVM通过uvm_component来实现树形结构。 所有的UVM树的结点本质上都是一个uvm_component。 每个uvm_component都有
一个特点: 它们在new的时候, 需要指定一个类型为uvm_component、 名字是parent的变量:

1
function new(string name, uvm_component parent);

一般在使用时, parent通常都是this。 假设A和B均派生自uvm_component, 在A中实例化一个B:

1
2
3
4
5
6
7
8
9
class B extends uvm_component;
e
ndclass
class A extends uvm_component;
B b_inst;
virtual function void build_phase(uvm_phase phase);
b_inst = new("b_inst", this);
endfunction
endclass

在b_inst实例化的时候, 把this指针传递给了它, 代表A是b_inst的parent。 为什么要指定这么一个parent呢? 一种常见的观点
是, b_inst是A的成员变量, 自然而然的, A就是b_inst的parent了, 无需再在调用new函数的时候指定, 即b_inst在实例化时可以这
样写:

1
b_inst = new("b_inst");     

虽然默认parent 是 this, 但是这种写法是不对的, 原因比较长, 这里不记录了, 详见 UVM 实战 P163

UVM 树的根

完整的UVM树


uvm_top是一个全局变量, 它是uvm_root的一个实例( 而且也是唯一的一个实例 [1], 它的实现方式非常巧妙) , 而uvm_root
派生自uvm_component, 所以uvm_top本质上是一个uvm_component, 它是树的根。 uvm_test_top的parent是uvm_top, 而uvm_top的
parent则是null。 UVM为什么不以uvm_test派生出来的测试用例( 即uvm_test_top) 作为树根, 而是搞了这么一个奇怪的东西作为树
根呢?
在之前的例子中, 所有的component在实例化时将this指针传递给parent参数, 如my_env在base_test中的实例化:

1
env = my_env::type_id::create("env", this);

但是, 假如不按照上面的写法, 向parent参数传递一个null会如何呢?

1
env = my_env::type_id::create("env", null);

如果一个component在实例化时, 其parent被设置为null, 那么这个component的parent将会被系统设置为系统中唯一的uvm_root 的实例uvm_top, 如下图所示:


可见, uvm_root的存在可以保证整个验证平台中只有一棵树, 所有结点都是uvm_top的子结点。
在验证平台中, 有时候需要得到uvm_top, 由于uvm_top是一个全局变量, 可以直接使用uvm_top。 除此之外, 还可以使用如:
下的方式得到它的指针:

1
2
uvm_root top;
top=uvm_root::get();

层次结构相关的函数

get_parent

用于得到当前实现的 parent
原型:

1
extern virtual function uvm_component get_parent ();

get_child

用于得到当前实现的 child
原型:

1
extern function uvm_component get_child (string name);

get_child需要一个string类型的参数name, 表示此child实例在实例化时指定的名字, 这个名字可以使用 get_children 函数得到

get_children

  • get_children

    原型:

    1
    
    extern function void get_children(ref uvm_component children[$]);
    

    e.g

    1
    2
    3
    4
    
    uvm_component array[$];
    my_comp.get_children(array);
    foreach(array[i])
        do_something(array[i]);
    
  • get_first_child和get_next_child

    除了一次性得到所有的child外, 还可以使用get_first_child和get_next_child的组合依次得到所有的child:
    原型:

    1
    2
    
    extern function int get_first_child (ref string name);
    extern function int get_next_child (ref string name);
    

    ref 即相当于指针的意思, get_first_child 把获得的值存到 ref 变量里
    示例:

    1
    2
    3
    4
    5
    6
    7
    
    string name;
    uvm_component child;
    if (comp.get_first_child(name))
        do begin
            child = comp.get_child(name);
            child.print();
        end while (comp.get_next_child(name));
    
  • get_num_childreN

    get_num_children函数用于返回当前component所拥有的child的数量:

    1
    
    extern function int get_num_children ();
    

field_automation 机制详解

宏相关

uvm_field 宏的种类

1
2
3
4
5
6
`define uvm_field_int(ARG,FLAG)
`define uvm_field_real(ARG,FLAG)
`define uvm_field_enum(T,ARG,FLAG)
`define uvm_field_object(ARG,FLAG)
`define uvm_field_event(ARG,FLAG)
`define uvm_field_string(ARG,FLAG)

上述几个宏分别用于要注册的字段是整数、 实数、 枚举类型、 直接或间接派生自uvm_object的类型、 事件及字符串类型。 这
里除了枚举类型外, 都是两个参数。 对于枚举类型来说, 需要有三个参数。 假如有枚举类型tb_bool_e, 同时有变量tb_flag, 那么
在使用field automation机制时应该使用如下方式实现:

1
2
3
4
5
typedef enum {TB_TRUE, TB_FALSE} tb_bool_e;

tb_bool_e tb_flag;

`uvm_field_enum(tb_bool_e, tb_flag, UVM_ALL_ON)

与动态数组有关的uvm_field系列宏

1
2
3
4
`define uvm_field_array_enum(ARG,FLAG)
`define uvm_field_array_int(ARG,FLAG)
`define uvm_field_array_object(ARG,FLAG)
`define uvm_field_array_string(ARG,FLAG)

与静态数组相关的uvm_field系列宏

1
2
3
4
`define uvm_field_sarray_int(ARG,FLAG)
`define uvm_field_sarray_enum(ARG,FLAG)
`define uvm_field_sarray_object(ARG,FLAG)
`define uvm_field_sarray_string(ARG,FLAG)

与队列相关的uvm_field系列宏

1
2
3
4
`define uvm_field_queue_enum(ARG,FLAG)
`define uvm_field_queue_int(ARG,FLAG)
`define uvm_field_queue_object(ARG,FLAG)
`define uvm_field_queue_string(ARG,FLAG)

与联合数组相关的uvm_field宏

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
`define uvm_field_aa_int_string(ARG, FLAG)
`define uvm_field_aa_string_string(ARG, FLAG)
`define uvm_field_aa_object_string(ARG, FLAG)
`define uvm_field_aa_int_int(ARG, FLAG)
`define uvm_field_aa_int_int_unsigned(ARG, FLAG)
`define uvm_field_aa_int_integer(ARG, FLAG)
`define uvm_field_aa_int_integer_unsigned(ARG, FLAG)
`define uvm_field_aa_int_byte(ARG, FLAG)
`define uvm_field_aa_int_byte_unsigned(ARG, FLAG)
`define uvm_field_aa_int_shortint(ARG, FLAG)
`define uvm_field_aa_int_shortint_unsigned(ARG, FLAG)
`define uvm_field_aa_int_longint(ARG, FLAG)
`define uvm_field_aa_int_longint_unsigned(ARG, FLAG)
`define uvm_field_aa_string_int(ARG, FLAG)
`define uvm_field_aa_object_int(ARG, FLAG)

这里一共出现了15种。 联合数组有两大识别标志, 一是索引的类型, 二是存储数据的类型。 在这一系列uvm_field系列宏中,
出现的第一个类型是存储数据类型, 第二个类型是索引类型, 如uvm_field_aa_int_string用于声明那些存储的数据是int, 而其索引
是string类型的联合数组。

宏中与if 结合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
`uvm_object_utils_begin(my_transaction)
    `uvm_field_int(dmac, UVM_ALL_ON)
    `uvm_field_int(smac, UVM_ALL_ON)
    if(is_vlan)begin
        `uvm_field_int(vlan_info1, UVM_ALL_ON)
        `uvm_field_int(vlan_info2, UVM_ALL_ON)
        `uvm_field_int(vlan_info3, UVM_ALL_ON)
        `uvm_field_int(vlan_info4, UVM_ALL_ON)
    end
    `uvm_field_int(ether_type, UVM_ALL_ON)
    `uvm_field_array_int(pload, UVM_ALL_ON)
    `uvm_field_int(crc, UVM_ALL_ON | UVM_NOPACK)
    `uvm_field_int(is_vlan, UVM_ALL_ON | UVM_NOPACK)
`uvm_object_utils_end

函数相关

copy

1
extern function void copy (uvm_object rhs);

如果要把某个A实例复制到B实例中, 那么应该使用B.copy( A) 。 在使用此函数前, B实例必须已经使用new函数分配好了内
存空间。

compare

1
extern function bit compare (uvm_object rhs, uvm_comparer comparer=null);

如果要比较A与B是否一样, 可以使用A.compare( B) , 也可以使用B.compare( A) 。 当两者一致时, 返回1; 否则为0。

pack_bytes

1
extern function int pack_bytes (ref byte unsigned bytestream[], input uvm_packer packer=null);

用于将所有的字段打包成byte流

1
2
3
byte unsigned     data_q[];
int  data_size;
data_size = tr.pack_bytes(data_q) / 8; 

unpack_bytes

1
extern function int unpack_bytes (ref byte unsigned bytestream[], input uvm_packer packer=null);

用于将一个byte流逐一恢复到某个类的实例中

pack

1
extern function int pack (ref bit bitstream[], input uvm_packer packer=null);

用于将所有的字段打包成bit流, pack函数的使用与pack_bytes类似。

unpack

1
extern function int unpack (ref bit bitstream[], input uvm_packer packer=null);

用于将一个bit流逐一恢复到某个类的实例中, unpack的使用与unpack_bytes类似

pack_ints

1
extern function int pack_ints (ref int unsigned intstream[], input uvm_packer packer=null);

用于将所有的字段打包成int( 4个byte, 或者dword) 流

unpack_ints

1
extern function int unpack_ints (ref int unsigned intstream[], input uvm_packer packer=null);

用于将一个int流逐一恢复到某个类的实例中

print

用于打印所有的字段

clone

1
extern virtual function uvm_object clone ();

标志位的使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//A=ABSTRACT Y=PHYSICAL
//F=REFERENCE, S=SHALLOW, D=DEEP
//K=PACK, R=RECORD, P=PRINT, M=COMPARE, C=COPY
//--------------------------- AYFSD K R P M C
parameter UVM_ALL_ON = 'b000000101010101;
parameter UVM_COPY = (1<<0);
parameter UVM_NOCOPY = (1<<1);
parameter UVM_COMPARE = (1<<2);
parameter UVM_NOCOMPARE = (1<<3);
parameter UVM_PRINT = (1<<4);
parameter UVM_NOPRINT = (1<<5);
parameter UVM_RECORD = (1<<6);
parameter UVM_NORECORD = (1<<7);
parameter UVM_PACK = (1<<8);
parameter UVM_NOPACK = (1<<9);

在这个17bit的数字中, bit0表示copy, bit1表示no_copy, bit2表示compare, bit3表示no_compare, bit4表示print, bit5表示
no_print, bit6表示record, bit7表示no_record, bit8表示pack, bit9表示no_pack。 剩余的7bit则另有它用, 这里不做讨论。
UVM_ALL_ON的值是’b000000101010101, 表示打开copy、 compare、 print、 record、 pack功能。 record功能是UVM提供的另外一个 功能,
但是其应用并不多, 所以在上节中并没有介绍。 UVM_ALL_ON|UVM_NOPACK的结果就是‘b000001101010101。 这样UVM 在执行pack操作时, 首先检查bit9,
发现其为1, 直接忽略bit8所代表的UVM_PACK。
除了UVM_NOPACK之后, 还有UVM_NOCOMPARE、 UVM_NOPRINT、 UVM_NORECORD、 UVM_NOCOPY等选项, 分别 对应compare、 print、 record、 copy等功能
示例:
考虑实现这样一种功能: 给DUT施加一种CRC错误的异常激励。 实现这个功能的一种方法是在my_transaction中添加一个
crc_err的标志位:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class my_transaction extends uvm_sequence_item;
    rand bit[47:0] dmac;
    rand bit [47:0] smac;
    rand bit [15:0] ether_type;
    rand byte       pload[];
    rand bit [31:0] crc;
    rand bit        crc_err;
    
    function void post_randomize();
        if(crc_err)
            ;//do nothing
        else
            crc = calc_crc;
    endfunction
    
endclass

这样, 在post_randomize中计算CRC前先检查一下crc_err字段, 如果为1, 那么直接使用随机值, 否则使用真实的CRC
在sequence中可以使用如下方式产生CRC错误的激励:

1
`uvm_do_with(tr, {tr.crc_err == 1;})

只是, 对于多出来的这个字段, 是不是也应该用uvm_field_int宏来注册呢? 如果不使用宏注册的话, 那么当调用print函数时,
在显示结果中就看不到其值, 但是如果使用了宏, 结果就是这个根本就不需要在pack和unpack操作中出现的字段出现了。 这会带
来极大的问题。
为了解决这一问题, 在 field 的时候使用如下方式即可

1
2
3
4
5
6
7
8
`uvm_object_utils_begin(my_transaction)
`uvm_field_int(dmac, UVM_ALL_ON)
`uvm_field_int(smac, UVM_ALL_ON)
`uvm_field_int(ether_type, UVM_ALL_ON)
`uvm_field_array_int(pload, UVM_ALL_ON)
`uvm_field_int(crc, UVM_ALL_ON)
`uvm_field_int(crc_err, UVM_ALL_ON | UVM_NOPACK)
`uvm_object_utils_end

使用上述语句后, 当执行pack和unpack操作时, UVM就不会考虑这个字段了

UVM 打印信息的控制

打印冗余度阈值

冗余度阈值定义

1
2
3
4
5
6
7
8
9
typedef enum
{
    UVM_NONE = 0,
    UVM_LOW = 100,
    UVM_MEDIUM = 200,
    UVM_HIGH = 300,
    UVM_FULL = 400,
    UVM_DEBUG = 500
} uvm_verbosity;

默认的冗余度阈值是UVM_MEDIUM, 所有低于等于 UVM_MEDIUM( 如UVM_LOW) 的信息都会被打印出来。

get_report_verbosity_level

可以通过 get_report_verbosity_level 函数得到某个component的冗余度阈值 ( 这个值是个整数, 对应UVM_LOW 等 )

1
2
3
virtual function void connect_phase(uvm_phase phase);
$display("env.i_agt.drv's verbosity level is %0d", env.i_agt.drv.get_report_verbosity_level());
endfunction

set_report_verbosity_level

UVM提供set_report_verbosity_level函数来设置某个特定component的默认冗余度阈值。
下面代码在base_test中将driver的冗余度阈值设置为UVM_HIGH( UVM_LOW、 UVM_MEDIUM、 UVM_HIGH的信息都会被打印):

1
2
3
4
5
文件: src/ch3/section3.4/3.4.1/base_test.sv
virtual function void connect_phase(uvm_phase phase);
    env.i_agt.drv.set_report_verbosity_level(UVM_HIGH);
    
endfunction

由于需要牵扯到层次引用, 所以需要在connect_phase及以后的phase才能调用这个函数。 如果不牵扯到任何层次引用, 如设置
当前component的冗余度阈值, 那么可以在connect_phase之前调用

set_report_verbosity_level_hier

递归的设置冗余度函数

1
env.i_agt.set_report_verbosity_level_hier(UVM_HIGH);

把env.i_agt及其下所有的component的冗余度阈值设置为UVM_HIGH

set_report_id_verbosity

区分不同的ID的冗余度阈值

1
2
`uvm_info("ID1", "ID1 INFO", UVM_HIGH)
`uvm_info("ID2", "ID2 INFO", UVM_HIGH)

使用下面语句后 ID1 INFO 会显示 但 ID2 INFO 不会显示

1
env.i_agt.drv.set_report_id_verbosity("ID1", UVM_HIGH);

set_report_id_verbosity_hier

递归的实现 set_report_id_verbosity 相同的功能

1
env.i_agt.set_report_id_verbosity_hier("ID1", UVM_HIGH);

在命令行中设置冗余度阀值

1
2
3
4
<sim command> +UVM_VERBOSITY=UVM_HIGH
或者:
<sim command> +UVM_VERBOSITY=HIGH
上面两个命令行参数是等价的, 即可以把冗余度级别的前缀“UVM_”省略。

上述的命令行参数会把整个验证平台的冗余度阈值设置为UVM_HIGH。 它几乎相当于是在base_test中调用
set_report_verbosity_level_hier函数,

1
set_report_verbosity_level_hier(UVM_HIGH)

把base_test及以下所有component的冗余度级别设置为UVM_HIGH
对不同的component设置不同的冗余度阈值非常有用。 在芯片级别验证时, 重用了不同模块( block) 的env。 由于个人习惯的
不同, 每个人对信息冗余度的容忍度也不同, 有些人把所有信息设置为UVM_MEDIUM, 也有另外一些人喜欢把所有的信息都设
置为UVM_HIGH。 通过设置不同env的冗余度级别, 可以更好地控制整个芯片验证环境输出信息的质量。

重载打印信息的严重性

set_report_severity_override

重载是深入到UVM骨子里的一个特性。 UVM默认有四种信息严重性: UVM_INFO、 UVM_WARNING、 UVM_ERROR、
UVM_FATAL。 这四种严重性可以互相重载。 如果要把driver中所有的UVM_WARNING显示为UVM_ERROR, 可以使用如下的函
数:

1
2
3
4
5
文件: src/ch3/section3.4/3.4.2/base_test.sv
virtual function void connect_phase(uvm_phase phase);
    env.i_agt.drv.set_report_severity_override(UVM_WARNING, UVM_ERROR);
    //env.i_agt.drv.set_report_severity_id_override(UVM_WARNING, "my_driver", UVM_ERROR);
endfunction
  • 重载前
    1
    
      `uvm_warning("my_driver", "this information is warning, but prints as UVM_ERROR")
    
    输出为 UVM_WARNING my_driver.sv(29) @ 1100000: uvm_test_top.env.i_agt.drv [my_driver]this information is …
  • 重载后
    1
    
      `uvm_warning("my_driver", "this information is warning, but prints as UVM_ERROR")
    
    输出为 UVM_ERROR my_driver.sv(29) @ 1100000: uvm_test_top.env.i_agt.drv [my_driver]this information is …

set_report_severity_id_override

重载严重性可以只针对某个component内的某个特定的ID起作用:

1
env.i_agt.drv.set_report_severity_id_override(UVM_WARNING, "my_driver", UVM_ERROR);

只重载 ID 为 my_driver 的 UVM_WARNING 为 UVM_ERROR

命令行重载严重性

1
<sim command> +uvm_set_severity=<comp>,<id>,<current severity>,<new severity>
  • 设置单个ID
    1
    
      <sim command> +uvm_set_severity="uvm_test_top.env.i_agt.drv,my_driver,UVM_WARNING,UVM_ERROR"
    
  • 设置所有ID
    1
    
      <sim command> +uvm_set_severity="uvm_test_top.env.i_agt.drv,_ALL_,UVM_WARNING,UVM_ERROR"
    

UVM_ERROR 到达一定数量结束仿真

当uvm_fatal出现时, 表示出现了致命错误, 仿真会马上停止。 UVM同样支持UVM_ERROR达到一定数量时结束仿真。 这个功
能非常有用。 对于某个测试用例, 如果出现了大量的UVM_ERROR, 根据这些错误已经可以确定bug所在了, 再继续仿真下去意
义已经不大, 此时就可以结束仿真, 而不必等到所有的objection被撤销

set_report_max_quit_count

1
2
3
4
5
6
文件: src/ch3/section3.4/3.4.3/base_test.sv
function void base_test::build_phase(uvm_phase phase);
    super.build_phase(phase);
    env = my_env::type_id::create("env", this);
    set_report_max_quit_count(5);
endfunction

上述代码把退出阈值设置为5。 当出现5个UVM_ERROR时, 会自动退出, 并显示如下的信息:

1
2
3
4
# --- UVM Report Summary ---
#
# Quit count reached!
# Quit count : 5 of 5

在测试用例中的设置方式与base_test中类似。 如果测试用例与base_test中同时设置了, 则以测试用例中的设置为准。 此外, 除
了在build_phase之外, 在其他phase设置也是可以的。

get_max_quit_count

可以用于查询当前的退出阈值。 如果返回值为0则表示无论出现多少个
UVM_ERROR都不会退出仿真

1
function int get_max_quit_count();

在命令行中设置退出阀值

1
<sim command> +UVM_MAX_QUIT_COUNT=6,NO

其中第一个参数6表示退出阈值, 而第二个参数NO表示此值是不可以被后面的设置语句重载, 其值还可以是YES。

设置计数的目标

默认的是只有 UVM_ERROR 加入计数目标
如果需要把 UVM_WARNING 或者其他也加入计数目标则需要另外设置

set_report_severity_action

  • 加入计数
    1
    2
    3
    4
    5
    6
    
        文件: src/ch3/section3.4/3.4.4/base_test.sv
        virtual function void connect_phase(uvm_phase phase);
            set_report_max_quit_count(5);
            env.i_agt.drv.set_report_severity_action(UVM_WARNING, UVM_DISPLAY|UVM_COUNT);
            
        endfunction
    
  • 移除计数
    1
    
      env.i_agt.drv.set_report_severity_action(UVM_WARNING, UVM_DISPLAY);
    

set_report_severity_action_hier

递归的实现 set_report_severity_action 相同的功能

  • 加入计数
    1
    
      env.i_agt.set_report_severity_action_hier(UVM_WARNING, UVM_DISPLAY| UVM_COUNT);
    
    把env.i_agt及其下所有结点的UVM_WARNING加入到计数目标中
  • 移除计数
    1
    
      env.i_agt.set_report_severity_action_hier(UVM_WARNING, UVM_DISPLAY);
    

set_report_severity_action及set_report_severity_action_hier的第一个参数除了是UVM_WARNING外, 还可以是UVM_INFO

set_report_id_action

对某个特定的ID进行计数

1
env.i_agt.drv.set_report_id_action("my_drv", UVM_DISPLAY| UVM_COUNT);

上述代码把ID为my_drv的所有信息加入到计数中, 无论是UVM_INFO, 还是UVM_WARNING或者是UVM_ERROR、 UVM_FATAL。

set_report_id_action_hier

递归的实现 set_report_id_action 相同的功能

1
env.i_agt.set_report_id_action_hier("my_drv", UVM_DISPLAY| UVM_COUNT);

set_report_severity_id_action

1
env.i_agt.drv.set_report_severity_id_action(UVM_WARNING, "my_driver", UVM_DISPLAY| UVM_COUNT);

同时把 严重性和 id 加入计数

set_report_severity_id_action_hier

递归的实现 set_report_severity_id_action_hier 相同的功能

1
env.i_agt.set_report_severity_id_action_hier(UVM_WARNING, "my_driver", UVM_DISPLAY| UVM_COUNT);

命令行中设置计数目标

1
<sim command> +uvm_set_action=<comp>,<id>,<severity>,<action>
1
<sim command> +uvm_set_action="uvm_test_top.env.i_agt.drv,my_driver,UVM_NG,UVM_DISPLAY|UVM_COUNT"

若要针对所有的ID设置, 可以使用_ALL_代替ID:

1
<sim command> +uvm_set_action="uvm_test_top.env.i_agt.drv,_ALL_,UVM_WARNING,UVM_DISPLAY|UVM_COUNT"

UVM 的断点功能

断点功能需要从仿真器的角度进行设置, 不同仿真器的设置方式不同。 为了消除这些设置方式的不同, UVM支持内建的断点
功能, 当执行到断点时, 自动停止仿真, 进入交互模式:

set_report_severity_action

1
2
3
4
5
文件: src/ch3/section3.4/3.4.5/base_test.sv
virtual function void connect_phase(uvm_phase phase);
    env.i_agt.drv.set_report_severity_action(UVM_WARNING, UVM_DISPLAY| UVM_STOP);
    
endfunction

命令行设置断点

1
<sim command> +uvm_set_action="uvm_test_top.env.i_agt.drv,my_driver,UVM_WARNING,UVM_DISPLAY|UVM_STOP"

将输出信息导入文件中

set_report_severity_file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
文件: src/ch3/section3.4/3.4.6/severity/base_test.sv
UVM_FILE info_log;
UVM_FILE warning_log;
UVM_FILE error_log;
UVM_FILE fatal_log;
virtual function void connect_phase(uvm_phase phase);
    info_log = $fopen("info.log", "w");
    warning_log = $fopen("warning.log", "w");
    error_log = $fopen("error.log", "w");
    fatal_log = $fopen("fatal.log", "w");
    env.i_agt.drv.set_report_severity_file(UVM_INFO, info_log);
    env.i_agt.drv.set_report_severity_file(UVM_WARNING, warning_log);
    env.i_agt.drv.set_report_severity_file(UVM_ERROR, error_log);
    env.i_agt.drv.set_report_severity_file(UVM_FATAL, fatal_log);
    env.i_agt.drv.set_report_severity_action(UVM_INFO, UVM_DISPLAY| UVM_LOG);
    env.i_agt.drv.set_report_severity_action(UVM_WARNING, UVM_DISPLAY|UVM_LOG);
    env.i_agt.drv.set_report_severity_action(UVM_ERROR, UVM_DISPLAY| UVM_COUNT | UVM_LOG);
    env.i_agt.drv.set_report_severity_action(UVM_FATAL, UVM_DISPLAY|UVM_EXIT | UVM_LOG);
    
endfunction

上述代码将env.i_agt.drv的UVM_INFO输出到info.log, UVM_WARNING输出到warning.log, UVM_ERROR输出到error.log, UVM_FATAL输出到fatal.log。

set_report_severity_file_hier

1
2
3
4
5
6
7
8
env.i_agt.set_report_severity_file_hier(UVM_INFO, info_log);
env.i_agt.set_report_severity_file_hier(UVM_WARNING, warning_log);
env.i_agt.set_report_severity_file_hier(UVM_ERROR, error_log);
env.i_agt.set_report_severity_file_hier(UVM_FATAL, fatal_log);
env.i_agt.set_report_severity_action_hier(UVM_INFO, UVM_DISPLAY| UVM_LOG);
env.i_agt.set_report_severity_action_hier(UVM_WARNING, UVM_DISPLAY| UVM_LOG);
env.i_agt.set_report_severity_action_hier(UVM_ERROR, UVM_DISPLAY| UVM_COUNT |UVM_LOG);
env.i_agt.set_report_severity_action_hier(UVM_FATAL, UVM_DISPLAY| UVM_EXIT | UVM_LOG);

上述代码将env.i_agt及其下所有结点的输出信息分类输出到不同的日志文件中。

set_report_id_file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
文件: src/ch3/section3.4/3.4.6/id/base_test.sv
UVM_FILE driver_log;
UVM_FILE drv_log;
virtual function void connect_phase(uvm_phase phase);
    driver_log = $fopen("driver.log", "w");
    drv_log = $fopen("drv.log", "w");
    env.i_agt.drv.set_report_id_file("my_driver", driver_log);
    env.i_agt.drv.set_report_id_file("my_drv", drv_log);
    env.i_agt.drv.set_report_id_action("my_driver", UVM_DISPLAY| UVM_LOG);
    env.i_agt.drv.set_report_id_action("my_drv", UVM_DISPLAY| UVM_LOG);
    
endfunction
virtual function void final_phase(uvm_phase phase);
    $fclose(driver_log);
    $fclose(drv_log);
endfunction

set_report_id_file_hier

递归的实现set_report_id_file 相同的功能

set_report_severity_id_file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
文件: src/ch3/section3.4/3.4.6/id_severity/base_test.sv
UVM_FILE driver_log;
UVM_FILE drv_log;
virtual function void connect_phase(uvm_phase phase);
    driver_log = $fopen("driver.log", "w");
    drv_log = $fopen("drv.log", "w");
    env.i_agt.drv.set_report_severity_id_file(UVM_WARNING, "my_driver",driver_log);
    env.i_agt.drv.set_report_severity_id_file(UVM_INFO, "my_drv", drv_log);
    env.i_agt.drv.set_report_id_action("my_driver", UVM_DISPLAY| UVM_LOG);
    env.i_agt.drv.set_report_id_action("my_drv", UVM_DISPLAY| UVM_LOG);
    
endfunction

以上代码把 UVM_WARNING 和 ID 为 “my_driver” 的打印信息输出到 driver_log

set_report_severity_id_file_hier

递归的实现set_report_severity_id_file 相同的功能

控制打印信息的行为

UVM共定义了如下几种行为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
typedef enum
{
    UVM_NO_ACTION = 'b000000,
    UVM_DISPLAY = 'b000001,
    UVM_LOG = 'b000010,
    UVM_COUNT = 'b000100,
    UVM_EXIT = 'b001000,
    UVM_CALL_HOOK = 'b010000,
    UVM_STOP = 'b100000
} uvm_action_type;

其中:
UVM_NO_ACTION是不做任何操作;
UVM_DISPLAY是输出到标准输出上;
UVM_LOG是输出到日志文件中, 它能工作的前提是设置好了日志文件;
UVM_COUNT是作为计数目标;
UVM_EXIT是直接退出仿真;
UVM_CALL_HOOK是调用一个回调函数;
UVM_STOP是停止仿真, 进入命令行交互模式。
默认情况下 UVM 设置了如下行为:

1
2
3
4
set_severity_action(UVM_INFO, UVM_DISPLAY);
set_severity_action(UVM_WARNING, UVM_DISPLAY);
set_severity_action(UVM_ERROR, UVM_DISPLAY | UVM_COUNT);
set_severity_action(UVM_FATAL, UVM_DISPLAY | UVM_EXIT);

用于设置打印行为的函数

  • set_report_severity_action
  • set_report_severity_action_hier
  • set_report_id_action
  • set_report_id_action_hier
  • set_report_severity_id_action
  • set_report_severity_id_action_hier

关闭输出

1
2
3
4
文件: src/ch3/section3.4/3.4.7/base_test.sv
virtual function void connect_phase(uvm_phase phase);
env.i_agt.drv.set_report_severity_action(UVM_INFO, UVM_NO_ACTION);
endfunction

无论原本的冗余度是什么, 经过上述设置后, env.i_agt.drv的所有的uvm_info信息都不会输出。

config_db 机制

UVM 中的路径

1
2
3
4
function void my_driver::build_phase();
super.build_phase(phase);
$display("%s", get_full_name());
endfunction

在component( 如my_driver) 内通过get_full_name( ) 函数可以得到此component的路径

set与get函数的参数

set

如在某个测试用例的build_phase中可以使用如下方式寄信

1
uvm_config_db#(int)::set(this, "env.i_agt.drv", "pre_num", 100);

其中第一个和第二个参数联合起来组成目标路径, 与此路径符合的目标才能收信。

  • 第一个参数必须是一个uvm_component实例的指针,
  • 第二个参数是相对此实例的路径。
  • 第三个参数表示一个记号, 用以说明这个值是传给目标中的哪个成员的,
  • 第四个参数是要设置的值

get

在 driver 中的build_phase 使用如下方式收信

1
uvm_config_db#(int)::get(this, "", "pre_num", pre_num);

get函数中的第一个参数和第二个参数联合起来组成路径。

  • 第一个参数也必须是一个uvm_component实例的指针,
  • 第二个参数是相对此实例的路径。 一般的, 如果第一个参数被设置为this, 那么第二个参数可以是一个空的字符串。
  • 第三个参数就是set函数中的第三个参数, 这两个参数必须严格匹配,
  • 第四个参数则是要设置的变量

第一个参数详解

当第一个参数为 null 时, UVM会自动把第一个参数替换为uvm_root::get() , 即uvm_top。 换句话说, 以下两种写法是完全等价的

1
2
3
4
5
6
initial begin
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.drv", "vif", input_if);
end
initial begin
uvm_config_db#(virtual my_if)::set(uvm_root::get(), "uvm_test_top.env.i_ag t. drv", "vif", input_if);
end

既然set函数的第一个和第二个参数联合起来组成路径, 那么在某个测试用例的build_phase中可以通过如下的方式设置
env.i_agt.drv中pre_num_max的值:

1
uvm_config_db#(int)::set(this.env, "i_agt.drv", "pre_num_max", 100);

把this替换为了this.env, 第二个参数是my_driver相对于env的路径
在driver的build_phase中

1
2
3
uvm_config_db#(int)::get(this.parent, "drv", "pre_num_max", pre_num_max);
或者:
uvm_config_db#(int)::get(null, "uvm_test_top.env.i_agt.drv", "pre_num_max", p re_num_max);

这些写法都是可以的, setget 的写法没有任何优势。

省略get 语句

set与get函数一般都是成对出现, 但是在某些情况下, 是可以只有set而没有get语句, 即省略get语句

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
文件: src/ch3/section3.5/3.5.3/my_driver.sv
    int pre_num;
`uvm_component_utils_begin(my_driver)
    `uvm_field_int(pre_num, UVM_ALL_ON)
`uvm_component_utils_end
function new(string name = "my_driver", uvm_component parent = null);
    super.new(name, parent);
    pre_num = 3;
endfunction
virtual function void build_phase(uvm_phase phase);
    `uvm_info("my_driver", $sformatf("before super.build_phase, the pre_num is %0d", pre_num), UVM_LOW);
    super.build_phase(phase);
    `uvm_info("my_driver", $sformatf("after super.build_phase, the pre_num is %0d", pre_num), UVM_LOW);
    if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
        `uvm_fatal("my_driver", "virtual interface must be set for vif!!!");
endfunction

只要使用uvm_field_int注册, 并且在build_phase中调用super.build_phase( ) , 就可以省略在build_phase中的如下get语句

1
uvm_config_db#(int)::get(this, "", "pre_num", pre_num);

这里的关键是build_phase中的 super.build_phase 语句, 当执行到driver的super.build_phase时, 会自动执行get语句。 这种做法的前提是:
第一, my_driver必须使用uvm_component_utils宏注册;
第二, pre_num必须使用uvm_field_int宏注册;
第三, 在调用set函数的时候, set函数的第三个参数必须与要get函数中变量的名字相一致, 即必须是pre_num。 所以上节中,
虽然说这两个参数可以不一致, 但是最好的情况下还是一致。 李四的信就是给李四的, 不要打什么暗语, 用一个“四”来代替李四。
这就是省略get语句的情况。 但是对于set语句, 则没有办法省略。

跨层次的多重设置

如果有多处set 那么, 以层次最高的set 为准, 如果在最高的层次里有多个 set, 则以最后的set 为准, UVM规定层次越高, 那么它的优先级越高
如:

层次不同时

在 uvm_test_top 中设置了 pre_num

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
文件: src/ch3/section3.5/3.5.4/normal/my_case0.sv
    function void my_case0::build_phase(uvm_phase phase);
        super.build_phase(phase);
        
            uvm_config_db#(int)::set(this,
                                     "env.i_agt.drv",
                                     "pre_num",
                                     999);
        `uvm_info("my_case0", "in my_case0, env.i_agt.drv.pre_num is set to 999",UVM_LOW)
        ...
endfunction

在 env 中设置 pre_num

1
2
3
4
5
6
7
文件: src/ch3/section3.5/3.5.4/normal/my_env.sv
virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    
    uvm_config_db#(int)::set(this, "i_agt.drv", "pre_num", 100);
    `uvm_info("my_env", "in my_env, env.i_agt.drv.pre_num is set to 100",UVM_LOW)
endfunction

则 driver 中获得的 pre_num 是 999, 因为 uvm_test_top 的层次比 env 高
假如 env 中改为如下设置

1
2
3
4
5
6
7
文件: src/ch3/section3.5/3.5.4/normal/my_env.sv
virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    
    uvm_config_db#(int)::set(uvm_root::get(), "i_agt.drv", "pre_num", 100);
    `uvm_info("my_env", "in my_env, env.i_agt.drv.pre_num is set to 100",UVM_LOW)
endfunction

则结果为 100, 因为在 env 中的设置, 其层次已经是 uvm_top, 在UVM 树中有最高的层次, 即最高优先级

层次相同时

在 uvm_test_top 中设置 pre_num

1
2
3
4
5
6
7
文件: src/ch3/section3.5/3.5.4/abnormal/my_case0.sv
function void my_case0::build_phase(uvm_phase phase);
    super.build_phase(phase);
    
    uvm_config_db#(int)::set(uvm_root::get(), "uvm_test_top.env.i_agt.drv", "pre_num", 999);
    `uvm_info("my_case0", "in my_case0, env.i_agt.drv.pre_num is set to 999", UVM_LOW)
endfunction

在 env 中设置 pre_num

1
2
3
4
5
6
7
文件: src/ch3/section3.5/3.5.4/normal/my_env.sv
virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    
    uvm_config_db#(int)::set(uvm_root::get(), "uvm_test_top.env.i_agt.drv", "pre_num", 100);
    `uvm_info("my_env", "in my_env, env.i_agt.drv.pre_num is set to 100",UVM_LOW)
endfunction

这种情况由于发信人都是 uvm_root::get() 所以最后设置的有效, 由于uvm_test_top 的层次比env 的高, 所以会先执行, 而后执行的 env 会覆盖掉先执行的结果,
所以上述代码get 的结果是 100

无论如何, 在调用set函数时其第一个参数应该尽量使用this。 在无法得到this指针的情况下( 如在top_tb中) , 使用null 或者uvm_root::get()

同一层次的多重设置

假设 pre_num在99%的测试用例中的值都是7, 只有在1%的测试用例中才会是其他值, 比较优雅的做法是在 base_test 的build_phase中使用config_db::set
设置 pre_num 为7, 这样, 当由base_test派生而来的case1~case99在执行super.build_phase( phase) 时, 都会进行设置, 当需要设置别的值时,
则在 super.build_phase 之后再 config_db::set 即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
classs base_test extends uvm_test;
function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    uvm_config_db#(int)::set(this, "env.i_agt.drv", pre_num_max, 7);
endfunction
endclass

class case1 extends base_test;
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
    endfunction
endclass

class case99 extends base_test;
function void build_phase(uvm_phase phase);
    super.build_phase(phase);
endfunction
endclass

当需要设置为别的值时则

1
2
3
4
5
6
class case100 extends base_test;
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        uvm_config_db#(int)::set(this, "env.i_agt.drv", pre_num_max, 100);
    endfunction
endclass

case100的build_phase相当于如下所示连续设置了两次

1
2
uvm_config_db#(int)::set(this, "env.i_agt.drv", "pre_num", 7);
uvm_config_db#(int)::set(this, "env.i_agt.drv", "pre_num", 100);

按照时间优先的原则, 后面config_db::set的值将最终被driver得到。

非直线的设置与获取


UVM树中, driver的路径为uvm_test_top.env.i_agt.drv。 在uvm_test_top, env或者i_agt中, 对driver中的某些变量
通过config_db机制进行设置, 称为直线的设置。
但是若在其他component, 如scoreboard中, 对driver的某些变量使用config_db机制 进行设置, 则称为非直线的设置。
在my_driver中使用config_db::get获得其他任意component设置给my_driver的参数, 称为直线的获取。
假如要在其他的 component, 如在reference model中获取其他component设置给my_driver的参数的值, 称为非直线的获取

非直线设置

在 scb 中设置

1
2
3
4
5
6
文件: src/ch3/section3.5/3.5.6/set/my_scoreboard.sv
function void my_scoreboard::build_phase(uvm_phase phase);
    
    uvm_config_db#(int)::set(this.m_parent, "i_agt.drv", "pre_num", 200);
    `uvm_info("my_scoreboard", "in my_scoreboard, uvm_test_top.env.i_agt.drv.pre_num is set to 200", UVM_LOW);
endfunction

或者

1
2
3
4
function void my_scoreboard::build_phase(uvm_phase phase);
    super.build_phase(phase);
    uvm_config_db#(int)::set(uvm_root::get(), "uvm_test_top.env.i_agt.drv", "pre_num", 200);
endfunction

无论哪种方式, 都带来了一个新的问题。 在UVM树中, build_phase是自上而下执行的, scb与i_agt处于同一级别中,
UVM并没有明文指出同一级别的build_phase的执行顺序。 所以当my_driver在获取参数值时, my_scoreboard的build_phase可能已经执行了,
也可能没有执行。 所以, 这种非直线的设置, 会有一定的风险, 应该避免这种情况的出现。

非直线获取

假如要在reference model中获取driver的pre_num的值

1
2
3
4
5
6
7
8
9
文件: src/ch3/section3.5/3.5.6/get/my_model.sv
function void my_model::build_phase(uvm_phase phase);
    super.build_phase(phase);
    port = new("port", this);
    ap = new("ap", this);
    `uvm_info("my_model", $sformatf("before get, the pre_num is %0d", drv_pre_num), UVM_LOW)
    void'(uvm_config_db#(int)::get(this.m_parent, "i_agt.drv", "pre_num", drv_pre_num));
    `uvm_info("my_model", $sformatf("after get, the pre_num is %0d", drv_pre_num), UVM_LOW)
endfunction

或者

1
void'(uvm_config_db#(int)::get(uvm_root::get(), "uvm_test_top.env.i_agt.drv", "pre_num", drv_pre_num));

非直线的获取可以在某些情况下避免config_db::set的冗余。 上面的例子在reference model中获取driver的pre_num的值, 如果
不这样做, 而采用直线获取的方式, 那么需要在测试用例中通过cofig_db::set分别给reference model和driver设置pre_num的值。
同样的参数值设置出现在不同的两条语句中, 这大大增加了出错的可能性。 因此, 非直线的获取可以在验证平台中多个组件
( UVM树结点) 需要使用同一个参数时, 减少config_db::set的冗余

config_db机制对通配符的支持

使用完整路径设置

1
2
3
4
5
initial begin
    uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.drv", "vif",input_if);
    uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.mon", "vif",input_if);
    uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.o_agt.mon", "vif",output_if);
end

使用通配符

1
2
3
4
5
initial begin
    uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt*", "vif", input_if);
    uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.o_agt*", "vif", output_if);
    `uvm_info("top_tb", "use wildchar in top_tb's config_db::set!", UVM_LOW)
end

进一步简化

1
2
3
4
initial begin
    uvm_config_db#(virtual my_if)::set(null, "*i_agt*", "vif", input_if);
    uvm_config_db#(virtual my_if)::set(null, "*o_agt*", "vif", output_if);
end

check_config_usage

1
2
3
4
5
6
function void my_case0::build_phase(uvm_phase phase);
   super.build_phase(phase);
   uvm_config_db#(uvm_object_wrapper)::set(this, "env.i_agt.sqr.main_phase", "default_sequence", case0_sequence::type_id::get());
   uvm_config_db#(int)::set(this, "env.i_atg.drv", "pre_num", 999);    // 把 agt 错写成了  atg , 没有地方接收
   uvm_config_db#(int)::set(this, "env.mdl", "rm_value", 10);
endfunction
1
2
3
4
5
文件: src/ch3/section3.5/3.5.8/my_case0.sv
virtual function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    check_config_usage();
endfunction

运行仿真的时候会打印如下信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
UVM_INFO @ 0: reporter [RNTST] Running test my_case0...
UVM_INFO @ 0: uvm_test_top [CFGNRD]  ::: The following resources have at least one write and no reads :::
============================================================================
default_sequence [/^uvm_test_top\.env\.i_agt\.sqr\.main_phase$/] : (class uvm_pkg::uvm_object_wrapper) ?
-  
  --------
  uvm_test_top reads: 0 @ 0  writes: 1 @ 0
============================================================================
 
============================================================================
pre_num [/^uvm_test_top\.env\.i_atg\.drv$/] : (int) 999
-  
  --------
  uvm_test_top reads: 0 @ 0  writes: 1 @ 0
============================================================================


述结果显示有两条设置信息分别被写过( set) 1次, 但是一次也没有被读取( get) 。 其中pre_num未被读取是因为错把i_agt
写成了i_atg。 default sequence的设置也没有被读取, 是因为default sequence是设置给main_phase的, 它在main_phase的时候被获
取, 而main_phase是在connect_phase之后执行的

set_config与get_config => UVM1.2 及以后的版本中已移除

set_config_int == uvm_config_db#(int)::set

get_config_int == uvm_config_db#(int)::get

set_config_string == uvm_config_db#(string)::set

get_config_string == uvm_config_db#(string)::get

set_config_object == uvm_config_db#(object)::set

get_config_object == uvm_config_db#(object)::get

命令行参数来对它们进行设置

config_db比set/get_config强大的地方在于, 它设置的参数类型并不局限于以上三种。 常见的枚举类型、 virtual interface、 bit类 型、 队列等都可以成为config_db设置的数据类型

1
2
<sim command> +uvm_set_config_int=<comp>,<field>,<value>
<sim command> +uvm_set_config_string=<comp>,<field>,<value>
1
<sim command> +uvm_set_config_int="uvm_test_top.env.i_agt.drv,pre_num,'h8"

在设置int型参数时, 可以在其前加上如下的前缀: ‘b、 ‘o、 ‘d、 ‘h, 分别表示二进制、 八进制、 十进制和十六进制的数据。 如果不加任何前缀, 则默认为十进制

config_db 的调试

1
2
3
4
5
文件: src/ch3/section3.5/3.5.10/my_case0.sv
virtual function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    print_config(1);
endfunction

其中参数1表示递归的查询, 若为0, 则只显示当前component的信息。 print_config的输出结果中有很多的冗余信息。 其运行结果大致如下:

 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
# UVM_INFO @ 0: uvm_test_top [CFGPRT] visible resources:
# <none>
# UVM_INFO @ 0: uvm_test_top.env [CFGPRT] visible resources:
# <none>
# UVM_INFO @ 0: uvm_test_top.env.agt_mdl_fifo [CFGPRT] visible resources:
# <none>
# UVM_INFO @ 0: uvm_test_top.env.i_agt.drv [CFGPRT] visible resources:
# vif [/^uvm_test_top\.env\.i_agt\.drv$/] : (virtual my_if) X X x x
# -
# pre_num [/^uvm_test_top\.env\.i_agt\.drv$/] : (int) 999
# -
#UVM_INFO @ 0: uvm_test_top.env.i_agt.mon [CFGPRT] visible resources:
# vif [/^uvm_test_top\.env\.i_agt\.mon$/] : (virtual my_if) X X x x
# -
#UVM_INFO @ 0: uvm_test_top.env.mdl [CFGPRT] visible resources:
# rm_value [/^uvm_test_top\.env\.mdl$/] : (int) 10
# -
#UVM_INFO @ 0: uvm_test_top.env.o_agt.mon [CFGPRT] visible resources:
# vif [/^uvm_test_top\.env\.o_agt\.mon$/] : (virtual my_if) X X x x
# -
...

它会遍历整个验证平台的所有结点, 找出哪些被设置过的信息对于它们是可见的。
如果有有用的设置信息则会打印出来, 没有则会是 <none>

命令行参数UVM_CONFIG_DB_TRACE

1
<sim command> +UVM_CONFIG_DB_TRACE

但是, 无论哪种方式, 如果set函数的第二个参数设置错误, 都不会给出错误信息。
本书会在10.6.3节提供一个函数, 它会检查set函数的第二个参数, 如果不可达, 将会给出UVM_ERROR的信息。

UVM命令行参数汇总

这里的命令行参数指的是运行时的命令行参数, 而不是编译时的命令行参数

打印出所有的命令行参数:

1
<sim command> +UVM_DUMP_CMDLINE_ARGS

指定运行测试用例的名称:

1
2
3
<sim command> +UVM_TESTNAME=<class name>
如:
<sim command> +UVM_TESTNAME=my_case0

在命令行中设置冗余度阈值:

1
2
3
<sim command> +UVM_VERBOSITY=<verbosity>
:
 <sim command> +UVM_VERBOSITY=UVM_HIGH                            

设置打印信息的不同行为:

1
2
3
<sim command> +uvm_set_action=<comp>,<id>,<severity>,<action>
如:
<sim command> +uvm_set_action="uvm_test_top.env.i_agt.drv,my_driver,UVM_WARNING,UVM_DISPLAY|UVM_COUNT"

重载冗余度:

1
2
3
<sim command> +uvm_set_severity=<comp>,<id>,<current severity>,<new severity>
:
 <sim command> +uvm_set_severity="uvm_test_top.env.i_agt.drv,my_driver,UVM_WAR NING,UVM_ERROR"                               

设置全局的超时时间:

1
2
3
<sim command> +UVM_TIMEOUT=<timeout>,<overridable>
如:
<sim command> +UVM_TIMEOUT="300ns, YES"

ERROR到达一定数量退出仿真:

1
2
3
<sim command> +UVM_MAX_QUIT_COUNT=<count>,<overridable>
:                                  
<sim command> +UVM_MAX_QUIT_COUNT=6,NO                                 

打开phase的调试功能:

1
<sim command> +UVM_PHASE_TRACE

打开objection的调试功能:

1
<sim command> +UVM_OBJECTION_TRACE

打开config_db的调试功能:

1
<sim command> +UVM_CONFIG_DB_TRACE

打开resource_db的调试功能:

1
<sim command> +UVM_RESOURCE_DB_TRACE

使用factory机制重载某个实例:

1
2
3
<sim command> +uvm_set_inst_override=<req_type>,<override_type>,<full_inst_pa th>
如:
<sim command> +uvm_set_inst_override="my_monitor,new_monitor,uvm_test_top.en v.o_agt.mon"

类型重载:

1
2
3
<sim command> +uvm_set_type_override=<req_type>,<override_type>[,<replace>]
如:
<sim command> +uvm_set_type_override="my_monitor,new_monitor"

第三个参数只能为0或者1, 默认情况下为1。

在命令行中使用set_config:

1
2
3
4
<sim command> +uvm_set_config_int=<comp>,<field>,<value>
<sim command> +uvm_set_config_string=<comp>,<field>,<value>
如:
<sim command> +uvm_set_config_int="uvm_test_top.env.i_agt.drv,pre_num,'h8"

UVM常用宏汇总

宏的定义方式

它有两种定义方式:

一是直接在源文件中中使用define进行定义:

1
2
3
`define MACRO
或者:
`define MACRO 100

二是在编译时的命令行中使用如下的方式:

1
2
3
<compile command> +define+MACRO
或者:
<compile command> +define+MACRO=100

扩展寄存器模型中的数据位宽:

1
`define UVM_REG_DATA_WIDTH 128

扩展寄存器模型中的地址位宽:

1
`define UVM_REG_ADDR_WIDTH 64

自定义字选择( byteenable) 位宽:

1
`define UVM_REG_BYTENABLE_WIDTH 8

去除OVM中过时的用法, 使用纯净的UVM环境:

1
`define UVM_NO_DEPRECATED

其他

除了上述通用的宏外, 针对不同的仿真工具需要定义不同的宏: QUESTA、 VCS、 INCA分别对应Mentor、 Synopsys和Cadence 公司的仿真工具。 UVM的源代码分为两部分, 一部分是SystemVerilog代码, 另外一部分是C/C++。 这两部分代码在各自编译时需要分别定义各自的宏。

seq_item_port and seq_item_export

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function void my_agent::connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    if (is_active == UVM_ACTIVE) begin
        // seq_item_port 是 uvm_driver 中的成员变量, seq_item_export 是uvm_sequencer 中的成员变量
        // 两者通过connect 建立联系后 driver 中就可以通过 get_next_item 任务向sequencer 申请transaction 了
        drv.seq_item_port.connect(sqr.seq_item_export);      
    end
    ap = mon.ap;
endfunction

task my_driver::main_phase(uvm_phase phase);
    `uvm_info("my_driver", "main_phase is call", UVM_LOW)
    fork
        while (1) begin
            seq_item_port.get_next_item(req);    // 请求sequence 发送driver
            drive_motor(req);
            seq_item_port.item_done();
        end
    join
endtask

req

只要类在定义时 extends 某个类的时候传入 uvm_sequence_item 类型的参数
那么这个类里面就可以直接用 req 来代替这个传入的 uvm_sequence_item 类型

Demo1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class motor_driver extends uvm_driver #(motor_transaction);
    ...
task motor_driver::main_phase(uvm_phase phase);
    `uvm_info("motor_driver", "main_phase is call", UVM_LOW)
    fork
        while (1) begin  
            seq_item_port.get_next_item(req);    // seq_item_port 是 uvm_driver 中的成员变量 已经在agent 中连接到sequencer
            drive_motor(req);                    // req 是 motor_driver 定义时传入的类型 motor_transaction  
            seq_item_port.item_done();
        end
    join
endtask

Demo2:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class case1_sequence extends uvm_sequence #(cfg_item);
    ...
    virtual task body();
        `uvm_info("case1_sequence", "case1_sequence is called", UVM_LOW);
        if(starting_phase != null) 
            starting_phase.raise_objection(this);
        #2000_000;
        `uvm_info("case1_sequence", "uvm do ", UVM_LOW);
        `uvm_do_with(req, {req.addr == 0;})
        if(starting_phase != null) 
            starting_phase.drop_objection(this);
    endtask
    `uvm_object_utils(case1_sequence)
endclass
Licensed under CC BY-NC-SA 4.0