UVM实战 卷I学习笔记11——UVM中的factory机制(1)

  • Post author:
  • Post category:其他





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表示是否可以被后面的重载覆盖



版权声明:本文为weixin_41979380原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。