UVM中的factory机制

SystemVerilog对重载的支持

1.

SystemVerilog是一种面向对象的语言。面向对象语言都有一大特征:重载。当在父类中定义一个函数/任务时,如果将其设置为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

重载的最大优势是使得一个子类的指针以父类的类型传递时,其表现出的行为依然是子类的行为:

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");
	print_hungry(bird_inst);
	print_hungry(parrot_inst);
endfunction

这里打印结果分别为:

"I am a bird, I am hungry"
"I am a bird, I am hungry2"

"I am a parrot, I am hungry"
"I am a bird, I am hungry2"

2.

UVM支持对约束的重载。

首先定义my_transaction。

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

然后在此基础上派生一个新的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机制进行重载

1.

factory机制最伟大的地方在于其具有重载功能。面向对象语言基本都支持对函数/任务的重载,此外,SystemVerilog还额外支持对约束的重载。但factory机制的重载与这些重载都不一样。

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

这里使用set_type_override_by_type函数可以实现两种不同类型之间的重载。

使用factory机制的重载是有前提的,并不是任意的类都可以互相重载。要想使用重载的功能,必须满足以下要求:

  • 第一,无论是重载的类(parrot)还是被重载的类(bird),都要在定义时注册到factory机制中。

  • 第二,被重载的类(bird)在实例化时,要使用factory机制式的实例化方式,而不能使用传统的new方式。

  • 第三,最重要的是,重载的类(parrot)要与被重载的类(bird)有派生关系。重载的类必须派生自被重载的类,被重载的类必须是重载类的父类。

  • ·第四,component与object之间互相不能重载。虽然uvm_component是派生自uvm_object,但是这两者的血缘关系太远了,远到根本不能重载。从两者的new参数的函数就可以看出来,二者互相重载时,多出来的一个parent参数会使factory机制无所适从。

2.

前面的重载函数set_type_override_by_type的原型是:

extern static function void set_type_override_by_type
	(uvm_object_wrapper original_type,
	uvm_object_wrapper override_type,
	bit replace=1);

这个函数有三个参数,其中第三个参数是replace,将会在下节讲述这个参数。在实际应用中一般只用前两个参数,第一个参数是被重载的类型,第二个参数是重载的类型。

但是有时候可能并不是希望把验证平台中的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

如果要将env.o_agt.mon替换成new_monitor:

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

经过上述替换后,当运行到main_phase时,会输出下列语句:

I am new_monitor

无论是set_type_override_by_type还是set_inst_override_by_type,它们的参数都是一个uvm_object_wrapper型的类型参数,这种参数通过xxx::get_type()的形式获得。

VM还提供了另外一种简单的方法来替换这种晦涩的写法:字符串。与set_type_override_by_type相对的是set_type_override。比如要使用parrot替换bird,只需要添加如下语句:

set_type_override("bird", "parrot")

与set_inst_override_by_type相对的是set_inst_override,它的原型是:

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的地方,如在top_tb的initial语句里,就无法使用。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类型的全局变量factory。可以在initial语句里使用如下的方式调用这四个函数:

initial begin
	factory.set_type_override_by_type(bird::get_type(), parrot::get_type());
end

3.

UVM支持连续的重载。

比如现在从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

注意在有多个重载时,最终重载的类要与最初被重载的类有派生关系。最终重载的类必须派生自最初被重载的类,最初被重载的类必须是最终重载类的父类。

4.

可以用以下函数对factory机制进行调试。

  • print_override_info函数

  • print函数

  • print_topology函数,用于显示出整棵UVM树的拓扑结构,UVM树在build_phase执行完成后才完全建立完成,因此,这个函数应该在build_phase之后调用:

uvm_top.print_topology();

可以把其放在所有测试用例的基类base_test中。

常用的重载

假设有如下的正常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的方式简练了很多。

factory机制的实现

1.

factory机制提供了一系列接口来创建实例。

create_object_by_name,用于根据类名字创建一个object。

my_transaction tr;
void'($cast(tr, factory.create_object_by_name("my_transaction")));

create_object_by_type,根据类型创建一个object。

my_transaction tr;
void'($cast(tr, factory.create_object_by_type(my_transaction::get_type())));

create_component_by_name,根据类名创建一个component,其原型为

function uvm_component uvm_factory::create_component_by_name (string
	requested_type_name,
	string parent_inst_path="",
	string name,
	uvm_component parent);

有四个参数,第一个参数是字符串类型的类名,第二个参数是父结点的全名,第三个参数是为这个新的component起的名字,第四个参数是父结点的指针。在调用这个函数时,这四个参数都要使用:

my_scoreboard scb;
void' ($cast(scb, factory.create_component_by_name("my_transaction", get_full
_name(), "scb", this)));

2.

在没有factory机制之前,要创建一个类的实例,只能使用new函数。但是有了factory机制之后,除了使用new函数外,还可以根据类名创建这个类的一个实例;另外,还可以在创建类的实例时根据是否有重载记录来决定是创建原始的类,还是创建重载的类的实例。

所以,从本质上来看,factory机制其实是对SystemVerilog中new函数的重载。因为这个原始的new函数实在是太简单了,功能太少了。经过factory机制的改良之后,进行实例化的方法多了很多。这也体现了UVM编写的一个原则,一个好的库应该提供更多方便实用的接口,这种接口一方面是库自己写出来并开放给用户的,另外一方面就是改良语言原始的接口,使得更加方便用户的使用。

参考自张强《UVM实战》

公众号:程序员Marshall


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