SystemVerilog对重载的支持
*任务与函数的重载
SV是一种面向对象的语言。面向对象语言都有一大特征:
重载
(我更习惯称之为“
覆盖
”,这里依作者命名为主)。
在父类中定义一个函数/任务时,如果将其设置为virtual类型,那么就可以在子类中重载这个函数/任务
:
class bird extends uvm_object;
virtual function void hungry();
$display("I am a bird, I am hungry");
endfunction
function void hungry2();
$display("I am a bird, I am hungry2");
endfunction
…
endclass
class parrot extends bird;
virtual function void hungry();
$display("I am a parrot, I am hungry");
endfunction
function void hungry2();
$display("I am a parrot, I am hungry2");
endfunction
…
endclass
上述代码中的hungry就是
虚函数
,它
可以被重载
。但hungry2不是虚函数,不能被重载。重载的
最大优势是使得一个子类的指针以父类的类型传递时,其表现出的行为依然是子类的行为
:
62 function void my_case0::print_hungry(bird b_ptr);
b_ptr.hungry();
b_ptr.hungry2();
endfunction
function void my_case0::build_phase(uvm_phase phase);
bird bird_inst;
parrot parrot_inst;
super.build_phase(phase);
bird_inst = bird::type_id::create("bird_inst");
parrot_inst = parrot::type_id::create("parrot_inst");
74 print_hungry(bird_inst);
75 print_hungry(parrot_inst);
endfunction
如上所示的print_hungry函数,它能接收的函数类型是bird。所以在第74行的第一个调用时,对应第62行中b_ptr指向的实例是bird类型的,b_ptr本身是bird类型的,所以显示的是:
"I am a bird, I am hungry"
"I am a bird, I am hungry2"
而对于第75行的第二个调用,显示的是:
"I am a parrot, I am hungry"
"I am a bird, I am hungry2"
在这个调用中对应62行b_ptr指向的实例是parrot类型的,而b_ptr本身虽然是parrot类型的,但
在调用hungry函数时它被隐式转换成bird类型
。
hungry是虚函数,所以即使转换成bird类型,打印的还是parrot。 但hungry2不是虚函数,打印的就是bird了
。
这种函数/任务重载的功能在UVM中得到了大量的应用。其实最典型的莫过于各个phase。当各个phase被调用时,以build_phase为例,实际上系统是使用如下方式调用:c_ptr.build_phase();
其中c_ptr是uvm_component类型,如my_driver(但c_ptr指向的实例却是my_driver类型)。在一个验证平台中,
UVM树上的结点是各个类型的,UVM不必理会它们具体是什么类型,统一将它们当作uvm_component来对待,极大方便了管理
。
*约束的重载
在测试一个接收MAC功能的DUT时有多种异常情况需要测试,如preamble错误、sfd错误、CRC错误等。针对这些错误,在transaction中分别加入标志位:
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;
rand bit sfd_err;
rand bit pre_err;
…
`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_field_int(sfd_err, UVM_ALL_ON | UVM_NOPACK)
`uvm_field_int(pre_err, UVM_ALL_ON | UVM_NOPACK)
`uvm_object_utils_end
…
endclass
这些错误都是异常的情况,在大部分测试用例中它们的值都应该为0。如果在每次产生transaction时进行约束会非常麻烦:
uvm_do_with(tr, {tr.crc_err == 0; sfd_err == 0; pre_err == 0;})
由于它们出现的概率非常低,因此结合SV中的dist,在定义transaction时指定如下的约束:
constraint default_cons{
crc_err dist{0 := 999_999_999, 1 := 1};
pre_err dist{0 := 999_999_999, 1 := 1};
sfd_err dist{0 := 999_999_999, 1 := 1};
}
上述语句是在随机化时,crc_err、pre_err和sfd_err只有1/1_000_000_000的可能性取值会为1,其余均为0。但
最大的问题是其何时取1、何时取0是无法控制的
。如果某个测试用例用于测试正常的功能,则不能有错误产生,即crc_err、 pre_err和sfd_err的值要一定为0。上面的constraint明显不能满足这种要求,虽然只有1/1_000_000_000的可能性,在运行特别长的测试用例时,如发送了1_000_000_000个包,那么有非常大的可能会产生一个crc_err、pre_err或sfd_err值为1的包。
解决上述问题有两种解决方案。第一种是
在定义transaction时使用如下方式定义constraint
:
class my_transaction extends uvm_sequence_item;
…
constraint crc_err_cons{
crc_err == 1'b0;
}
constraint sfd_err_cons{
sfd_err == 1'b0;
}
constraint pre_err_cons{
pre_err == 1'b0;
}
…
endclass
正常的测试用例可以使用如下方式随机化:
my_transaction tr;
`uvm_do(tr)
异常的测试用例可以使用如下方式随机化:
virtual task body();
…
m_trans = new();
`uvm_info("sequence", "turn off constraint", UVM_MEDIUM)
m_trans.crc_err_cons.constraint_mode(0);
`uvm_rand_send_with(m_trans, {crc_err dist {0 := 2, 1 := 1};})
…
endtask
能够使用这种方式的前提是
m_trans已经实例化
。如果不实例化,直接使用uvm_do宏会报空指针的错误。
my_transaction m_trans;
m_trans.crc_err_cons.constraint_mode(0);
`uvm_do(m_trans)
sfd_err与pre_err的情况也可使用类似方式实现。上述语句中只单独关闭了某个约束,也可以使用如下语句关闭所有约束:
m_trans.constraint_mode(0);
这种情况下,随机化时就需要分别对crc_err、pre_err及sfd_err进行约束。
第二种方式,SV
支持约束的重载
。依然使用第一种方式中my_transaction的定义,在其基础派生一个新的transaction:
class new_transaction extends my_transaction;
`uvm_object_utils(new_transaction)
function new(string name= "new_transaction");
super.new(name);
endfunction
constraint crc_err_cons{
crc_err dist {0 := 2, 1 := 1};
}
endclass
这个新的transaction中将crc_err_cons重载了。因此在异常的测试用例中可使用如下方式随机化:
virtual task body();
new_transaction ntr;
…
repeat (10) begin
`uvm_do(ntr)
ntr.print();
end
…
endtask
使用factory机制进行重载
*factory机制式的重载
factory机制最伟大的地方在于其具有
重载
功能。重载并不是factory机制的发明,所有面向对象的语言都支持函数/任务重载,另外
SV还支持对约束的重载
。只是factory机制的重载与这些重载都不一样。
上节定义好bird与parrot并在测试用例中调用print_hungry函数。与前面代码不同的地方在于,将build_phase改为如下语句:
function void my_case0::build_phase(uvm_phase phase);
…
set_type_override_by_type(bird::get_type(), parrot::get_type());
bird_inst = bird::type_id::create("bird_inst");
parrot_inst = parrot::type_id::create("parrot_inst");
print_hungry(bird_inst);
print_hungry(parrot_inst);
endfunction
那么运行的结果将会是:
"I am a parrot, I am hungry"
"I am a bird, I am hungry2"
"I am a parrot, I am hungry"
"I am a bird, I am hungry2"
虽然print_hungry接收的是bird类型的参数,但从运行结果可以推测无论是第一次还是第二次调用print_hungry,传递的都是
类型为bird但指向parrot的指针
。对于第二次调用很好理解,但第一次却使人很难接受。这就是factory机制的重载功能,其原理如图所示:
虽然bird_inst在实例化以及传递给hungry的过程中,没有过与parrot的任何接触,但它最终指向了一个parrot的实例。这是因为bird_inst使用了UVM的factory机制式的实例化方式:
bird_inst = bird::type_id::create("bird_inst");
在实例化时UVM通过factory机制在内部表格查看是否有重载记录。
set_type_override_by_type相当于在factory机制的表格中加入一条记录
。
查到有重载记录时会使用新类型替代旧类型
。所以虽然在build_phase中写明创建bird的实例,但最终却创建了parrot的实例。
使用factory机制的重载是有前提的,并不是任意类都可以互相重载。要想使用重载的功能,必须满足以下要求:
-
无论是重载的类(parrot)还是被重载的类(bird),都要
在定义时注册到factory机制中
。 -
被重载的类(bird)在实例化时要使用factory机制的实例化方式
,不能使用传统的new方式。
如果在这个bird与parrot的例子中, bird在实例化时使用new方式:bird_inst = new(“bird_inst”); 那么上述的重载语句是不会生效的, 最终得到的结果与本节开头例子完全一样。
-
最重要的是,
重载的类(parrot)要与被重载的类(bird)有派生关系
。重载的类必须派生自被重载的类,
被重载的类必须是重载类的父类
。
如果没有派生关系,假如有bear定义如下:
class bear extends uvm_object;
virtual function void hungry();
$display("I am a bear, I am hungry");
endfunction
function void hungry2();
$display("I am a bear, I am hungry2");
endfunction
`uvm_object_utils(bear)
function new(string name = "bear");
super.new(name);
endfunction
endclass
在build_phase中使用bear重载bird:
function void my_case0::build_phase(uvm_phase phase);
…
set_type_override_by_type(bird::get_type(), bear::get_type());
…
endfunction
则会给出如下错误提示:
UVM_FATAL @ 0: reporter [FCTTYP] Factory did not return an object of type 'bird'.
A component of type 'bird'. A component of type 'bear' was returned instead.
Name=bird_inst Parent=null contxt=
如果重载的类与被重载的类之间有派生关系但顺序颠倒,即重载的类是被重载类的父类,那么也会出错。尝试着以bird重载parrot:
set_type_override_by_type(parrot::get_type(), bird::get_type());
那么也会给出错误提示:
UVM_FATAL @ 0: reporter [FCTTYP] Factory did not return an object of type 'parrot'.
A component of type 'parrot'. A component of type 'bird' was returned instead.
Name=parrot_inst Parent=null contxt=
-
component与object之间互相不能重载
。虽然uvm_component是派生自uvm_object,但这两者的血缘关系太远了,远到根本不能重载。从两者的new函数就可以看出,二者互相重载时,多出来的parent参数会使factory机制无所适从。
*重载的方式及种类
上节介绍使用set_type_override_by_type函数可以实现两种不同类型之间的重载。这个函数位于uvm_component中,其原型是:
extern static function void set_type_override_by_type
(uvm_object_wrapper original_type,
uvm_object_wrapper override_type,
bit replace=1);
在实际应用中一般只用前两个参数, 第一个参数是
被重载的类型
, 第二个参数是
重载的类型
。
但有时可能并不希望把验证平台中的A类型全部替换成B类型,
只替换其中的某一部分
,这种情况就要用到
set_inst_override_by_type
函数。这个函数的原型如下:
extern function void set_inst_override_by_type(string relative_inst_path,
uvm_object_wrapper original_type,
uvm_object_wrapper override_type);
其中第一个参数是
相对路径
,第二个参数是
被重载的类型
,第三个参数是
要重载的类型
。
假设有如下的monitor定义:
class new_monitor extends my_monitor;
`uvm_component_utils(new_monitor)
…
virtual task main_phase(uvm_phase phase);
fork
super.main_phase(phase);
join_none
`uvm_info("new_monitor", "I am new monitor", UVM_MEDIUM)
endtask
endclass
以之前提到的UVM树为例,要将env.o_agt.mon替换成new_monitor:
set_inst_override_by_type("env.o_agt.mon",my_monitor::get_type(),new_monitor::get_type());
经过替换后运行到main_phase时,会输出下列语句:
I am new_monitor
无论set_type_override_by_type还是set_inst_override_by_type,都是
uvm_object_wrapper
类型的参数,这种参数
通过xxx::get_type()的形式获得
。UVM提供另一种简单的方法替换这种晦涩的写法:
字符串
。
与set_type_override_by_type相对的是
set_type_override
,它的原型是:
extern static function void set_type_override(string original_type_name,
string override_type_name,
bit replace=1);
要使用parrot替换bird,只需要添加语句:
set_type_override("bird", "parrot")
与set_inst_override_by_type相对的是set_type_override(“bird”, “parrot”),它的原型是:
extern function void set_inst_override(string relative_inst_path,
string original_type_name,
string override_type_name);
对于上面使用new_monitor重载my_monitor的例子,可以使用语句:
set_inst_override("env.o_agt.mon", "my_driver", "new_monitor");
上述所有函数都是
uvm_component的函数
,但如果在一个无法使用component的地方就无法使用。UVM提供另外四个函数来替换上述的四个函数,原型是:
extern function
void set_type_override_by_type (uvm_object_wrapper original_type,
uvm_object_wrapper override_type,
bit replace=1);
extern function
void set_inst_override_by_type (uvm_object_wrapper original_type,
uvm_object_wrapper override_type,
string full_inst_path);
extern function
void set_type_override_by_name (string original_type_name,
string override_type_name,
bit replace=1);
extern function
void set_inst_override_by_name (string original_type_name,
string override_type_name,
string full_inst_path);
这四个函数都位于uvm_factory类中
- 第一个函数与uvm_component中的同名函数类似,传递的参数相同。
- 第二个对应uvm_component中的同名函数,只是其输入参数变了,这里需要输入字符串类型的full_inst_path。这个full_inst_path就是要替换的实例使用get_full_name()得到的路径值。
- 第三个与uvm_component中的set_type_override类似,传递的参数相同。
- 第四个对应uvm_component中的set_inst_override,也需要一个full_inst_path。
如何使用这四个函数呢?系统中
存在一个uvm_factory类型的全局变量factory
。
可以在initial语句里
使用如下方式调用这四个函数:
initial begin
factory.set_type_override_by_type(bird::get_type(), parrot::get_type());
end
在一个
component内
也可以直接调用factory机制的重载函数:
factory.set_type_override_by_type(bird::get_type(), parrot::get_type());
事实上,
uvm_component的四个重载函数直接调用了factory的相应函数
。
除了可以在代码中进行重载,还可以
在命令行中进行重载
。对于实例重载和类型重载,有各自的命令行参数:
<sim command> +uvm_set_inst_override=<req_type>,<override_type>,<full_inst_path>
<sim command> +uvm_set_type_override=<req_type>,<override_type>[,<replace>]
这两个命令行参数分别对应于set_inst_override_by_name和set_type_override_by_name。对于实例重载:
<sim command> +uvm_set_inst_override="my_monitor,new_monitor,uvm_test_top.env.o_agt.mon"
对于类型重载:
<sim command> +uvm_set_type_override="my_monitor,new_monitor"
类型重载的命令行参数中有三个选项,其中最后一个
replace表示是否可以被后面的重载覆盖
。