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


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

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值