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

目录


*复杂的重载

前面的例子讲述了简单的重载功能,即只使用一种类型重载另外一种类型。事实上UVM支持连续的重载。依然以bird与parrot的例子讲述,现在从parrot又派生出了一个新的类big_parrot:

class big_parrot extends parrot;
	virtual function void hungry();
		$display("I am a big_parrot, I am hungry");
	endfunction
	function void hungry2();
		$display("I am a big_parrot, I am hungry2");
	endfunction
	`uvm_object_utils(big_parrot)
	function new(string name = "big_parrot");
		super.new(name);
	endfunction
endclass

在build_phase中设置如下的连续重载,并调用print_hungry函数:

function void my_case0::build_phase(uvm_phase phase);
	bird bird_inst;
	parrot parrot_inst;
	super.build_phase(phase);
	set_type_override_by_type(bird::get_type(), parrot::get_type());
	set_type_override_by_type(parrot::get_type(), big_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 big_parrot, I am hungry
# I am a bird, I am hungry2

除了这种连续的重载外,还有一种是替换式的重载。假如从bird派生出了新的鸟sparrow:

class sparrow extends bird;
	virtual function void hungry();
		$display("I am a sparrow,  I am hungry");
	endfunction
	function void hungry2();
		$display("I am a sparrow, I am hungry2");
	endfunction
	`uvm_object_utils(sparrow)
	function new(string name = "sparrow");
		super.new(name);
	endfunction
endclass

在build_phase中设置如下重载:

function void my_case0::build_phase(uvm_phase phase);
	bird bird_inst;
	parrot parrot_inst;
	super.build_phase(phase);
	set_type_override_by_type(bird::get_type(), parrot::get_type());
	set_type_override_by_type(bird::get_type(), sparrow::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 sparrow, I am hungry
# I am a bird, I am hungry2
# I am a parrot, I am hungry
# I am a bird, I am hungry2

这种替换式重载的前提是调用set_type_override_by_type时,其第三个replace参数被设置为1(默认情况下即为1)。如果为0,那么最终得到的结果将会是:

# 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

在创建bird的实例时,factory机制查询到两条相关的记录,它并不会在看完第一条记录后即直接创建一个parrot的实例,而是最终看完第二条记录后才会创建sparrow的实例。由于是在读取完最后的语句后才可以创建实例,所以其实下列的重载方式也是允许的:

function void my_case0::build_phase(uvm_phase phase);
	bird bird_inst;
	super.build_phase(phase);
	set_type_override_by_type(bird::get_type(), parrot::get_type());
86	set_type_override_by_type(parrot::get_type(), sparrow::get_type(), 0);
	bird_inst = bird::type_id::create("bird_inst");
	print_hungry(bird_inst);
endfunction

最终输出的结果是:

# I am a sparrow, I am hungry
# I am a bird, I am hungry2

代码第86行的重载语句与前面的重载四前提的第三条相违背,sparrow并没有派生自parrot但依然可以重载parrot。这样使用依然是有条件的,最终创建出的实例是sparrow类型的,而最初是bird类型的,这两者之间依然有派生关系。代码去掉了对parrot_inst的实例化。因为在第86行存在的情况下再实例化一个parrot_inst会出错。所以,重载四前提的第三条应该改为:

  • 在有多个重载时,最终重载的类必须派生自最初被重载的类,最初被重载的类必须是最终重载类的父类。

*factory机制的调试

factory机制的重载功能很强大,UVM提供print_override_info函数输出所有的打印信息,以上节new_monitor重载my_monitor为例:

set_inst_override_by_type("env.o_agt.mon", my_monitor::get_type(), 
new_monitor::get_type());

验证平台中仅仅有这一句重载语句,那么调用print_override_info函数打印的方式为:

function void my_case0::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	env.o_agt.mon.print_override_info("my_monitor");
endfunction

最终输出的信息为:

# Given a request for an object of type 'my_monitor' with an instance
# path of 'uvm_test_top.env.o_agt.mon', the factory encountered
# the following relevant overrides. An 'x' next to a match indicates a
# match that was ignored.
#
# Original Type Instance Path Override Type
# ------------- -------------------------- -------------
# my_monitor uvm_test_top.env.o_agt.mon new_monitor
#
# Result:
#
# The factory will produce an object of type 'new_monitor'

这里会明确地列出原始类型和新类型。在调用print_override_info时,其输入的类型应该是原始的类型,而不是新的类型。

print_override_info是个uvm_component的成员函数,它实质上是调用uvm_factory的debug_create_by_name。 除该函数外,uvm_factory还有debug_create_by_type,其原型为:

extern function
	void debug_create_by_type (uvm_object_wrapper requested_type,
						string parent_inst_path="",
						string name="");

使用它对new_monitor进行调试的代码为:其输出与使用print_override_info相同。

factory.debug_create_by_type(my_monitor::get_type(), "uvm_test_top.env.o_agt.mon");

除了上述两个函数外,uvm_factory还提供print函数:

extern function void print (int all_types=1);

这个函数只有一个参数,其取值可能为0、1或2为0时仅打印被重载的实例和类型,其打印出的信息大体如下:

#### Factory Configuration (*)
#
# Instance Overrides:
#
# Requested Type Override Path Override Type
# -------------- -------------------------- -------------
# my_monitor uvm_test_top.env.o_agt.mon new_monitor
#
# No type overrides are registered with this factory
  • 为1时打印参数为0时的信息,以及所有用户创建的、注册到factory的类的名称
  • 为2时打印参数为1时的信息,以及系统创建的、所有注册到factory的类的名称(如uvm_reg_item) 。

除上述这些函数外还有一个重要的工具可以显示出整棵UVM树的拓扑结构——uvm_root的print_topology函数。UVM树在build_phase执行完成后才完全建立完成,因此,这个函数应该在build_phase之后调用:uvm_top.print_topology();

最终显示的结果(部分) 为:
UVM实战 卷I学习笔记11——UVM中的factory机制(2)
UVM实战 卷I学习笔记11——UVM中的factory机制(2)
从这个拓扑结构可以清晰地看出,env.o_agt.mon被重载成了new_monitor类型。print_topology这个函数非常有用,即使在不进行factory机制调试的情况下,也可通过调用它显示整个验证平台的拓扑结构是否与自己预期的一致。因此可以把其放在所有测试用例的基类base_test中

常用的重载

*重载transaction

在有了factory机制的重载功能后,构建CRC错误的测试用例就多了一种选择。假设有如下的正常sequence,此sequence被作为某个测试用例的default_sequence:

class normal_sequence extends uvm_sequence #(my_transaction);
	…
	virtual task body();
		repeat (10) begin
			`uvm_do(m_trans)
		end
		#100;
	endtask
	`uvm_object_utils(normal_sequence)
endclass

现在要构建一个新的异常的测试用例测试CRC错误的情况。可以从这个transaction派生一个新的transaction:

class crc_err_tr extends my_transaction;
	…
	constraint crc_err_cons{
		crc_err == 1;
	}
endclass

如果按照上节内容,那么现在需要新建一个sequence,然后将这个sequence作为新的测试用例的default_sequence:

class abnormal_sequence extends uvm_sequence #(my_transaction);
	crc_err_tr tr;
	virtual task body();
		repeat(10) begin
			`uvm_do(tr)
		end
	endtask
endclass
function void my_case0::build_phase(uvm_phase phase);
	…
	uvm_config_db #(uvm_object_wrapper)::set(this,
										"env.i_agt.sqr.main_phase","default_sequence",
										abnormal_sequence::type_id::get());
endfunction

但有了factory机制的重载功能后,可以不用重新写一个abnormal_sequence,而继续使用normal_sequence作为新的测试用例的default_sequence,只是需要将my_transaction使用crc_err_tr重载:

function void my_case0::build_phase(uvm_phase phase);
	super.build_phase(phase);
	factory.set_type_override_by_type(my_transaction::get_type(), crc_err_tr::get_type());
	uvm_config_db#(uvm_object_wrapper)::set(this,
										"env.i_agt.sqr.main_phase","default_sequence",
										normal_sequence::type_id::get());
endfunction

经过这样的重载后normal_sequence产生的transaction就是CRC错误的transaction。这比新建一个CRC错误的sequence的方式简练很多。

*重载sequence

transaction可以重载,sequence同样也可以重载。上节使用的transaction重载能工作的前提是约束也可以重载。但很多人可能并不习惯于这种用法,而习惯于最原始的方法。

在其他测试用例中已经定义了如下的两个sequence:

class normal_sequence extends uvm_sequence #(my_transaction);
	…
	virtual task body();
		`uvm_do(m_trans)
		m_trans.print();
	endtask
	`uvm_object_utils(normal_sequence)
endclass
class case_sequence extends uvm_sequence #(my_transaction);
	…
	virtual task body();
		normal_sequence nseq;
		repeat(10) begin
			`uvm_do(nseq)
		end
	endtask
endclass

这里使用了嵌套的sequence。case_sequence被作为default_sequence。现在新建一个测试用例时,可以依然将case_sequence作为default_sequence,只需要从normal_sequence派生一个异常的sequence:

class abnormal_sequence extends normal_sequence;
	…
	virtual task body();
		m_trans = new("m_trans");
		m_trans.crc_err_cons.constraint_mode(0);
		`uvm_rand_send_with(m_trans, {crc_err == 1;})
		m_trans.print();
	endtask
endclass

并且在build_phase中将normal_sequence使用abnormal_sequence重载掉:

function void my_case0::build_phase(uvm_phase phase);
	…
	factory.set_type_override_by_type(normal_sequence::get_type(), 
										abnorma l_sequence::get_type());
	uvm_config_db#(uvm_object_wrapper)::set(this,
										"env.i_agt.sqr.main_phase","default_sequence",
										case_sequence::type_id::get());
endfunction

本节讲述的内容其实与上节的类似,都能实现同样的目的。这就是UVM的强大之处,对于同样的事情,它提供多种方式完成,用户可以*选择

*重载component

前面分别使用重载transaction和重载sequence的方式产生异常的测试用例。其实还可以使用重载driver的方式产生。

假设某个测试用例使用normal_sequence作为其default_sequence。这是个只产生正常transaction的sequence,使用它构造的测试用例是正常的用例。现在假如要产生一个CRC错误的测试用例,可以依然使用这个sequence作为default_sequence,只是需要定义如下的driver:

class crc_driver extends my_driver;
	…
	virtual function void inject_crc_err(my_transaction tr);
		tr.crc = $urandom_range(10000000, 0);
	endfunction
	virtual task main_phase(uvm_phase phase);
		vif.data <= 8'b0;
		vif.valid <= 1'b0;
		while(!vif.rst_n)
			@(posedge vif.clk);
		while(1) begin
			seq_item_port.get_next_item(req);
			inject_crc_err(req);
			drive_one_pkt(req);
			seq_item_port.item_done();
		end
	endtask
endclass

然后在build phase中将my_driver使用crc_driver重载:

function void my_case0::build_phase(uvm_phase phase);
	…
	factory.set_type_override_by_type(my_driver::get_type(), crc_driver::get_type());
	uvm_config_db#(uvm_object_wrapper)::set(this,"env.i_agt.sqr.main_phase","default_sequence",
											normal_sequence::type_id::get());
endfunction

本节所举的例子看不出重载driver的优势,因为CRC错误是个非常普通的异常测试用例。对于那些特别异常的测试用例,使用sequence实现起来非常麻烦的情况,重载driver就会显示出其优势。

除driver可以重载外,scoreboard与参考模型等都可以重载。尤其对于参考模型来说,处理异常的激励源是相当耗时的事情。可能一个DUT80%的代码都是用于处理异常情况,作为模拟DUT的参考模型来说更是如此。如果将所有异常情况都用一个参考模型实现,那么其代码量将会非常大。但如果将其分散为数十个参考模型,每个处理一种异常情况,当建立相应异常的测试用例时,将正常的参考模型由它替换掉。这样可使代码清晰并增加可读性。

重载driver以实现所有的测试用例

重载driver使得一些在sequence中比较难实现的测试用例轻易地在driver中实现。那么如果放弃sequence,只使用factory机制实现测试用例可能吗?答案确实是可能的。不用sequence时,那么要在driver中控制发送包的种类、数量,对于objection的控制又要从sequence中回到driver中,似乎一切都回到了起点。

但不推荐这么做:

  • 引入sequence的原因是将数据流产生的功能从driver中独立出来。取消sequence相当于一种倒退,会使得driver的职能不明确,与现代编程中模块化、功能化的趋势不合。
  • 虽然用driver实现某些测试用例比sequence更加方便,但对于另外一些测试用例,在sequence里做起来会比driver中更加方便。
  • sequence的强大之处在于,它可以在一个sequence中启动另外的sequence,从而可以最大程度实现不同测试用例之间sequence的重用。但driver要实现这样的功能,只能将一些基本的产生激励的函数写在基类driver中。用户会发现到最后这个driver的代码量非常恐怖。
  • 使用virtual sequence可以协调、同步不同激励的产生。当放弃sequence时,在不同的driver之间完成这样的同步则比较难。

基于以上原因,请不要将所有测试用例都使用driver重载实现只有将driver的重载与sequence相结合,才与UVM的最初设计初衷相符合,才能构建起可重用性高的验证平台。完成同样的事情有很多种方式,应综合考虑选择最合理的方式。

上一篇:sax解析


下一篇:iOS开发系列--C语言之数组和字符串