UVM实战 卷I学习笔记6——config_db机制


UVM中的路径

一个component内通过get_full_name()函数可以得到此component的路径:

function void my_driver::build_phase();
	super.build_phase(phase);
	$display("%s", get_full_name());
endfunction

上述代码如果是在下图所示的层次结构中的my_driver中,那么打印出来的值是uvm_test_top.env.i_agt.drv
在这里插入图片描述
为了方便,上图使用了new函数而不是factory式的create方式来创建实例。图中uvm_test_top实例化时的名字是uvm_test_top,这个名字是由UVM在run_test时自动指定的。uvm_top的名字是__top__,但在显示路径时并不会显示出这个名字,而只显示从uvm_test_top开始的路径

路径的概念与通常的层次结构不太一样(虽然基本上是一样的)。从图中的my_casen看,drv的层次结构是env.i_agt.drv,其相对于my_casen的相对路径是env.i_agt.drv。

如果drv在new时指定的名字是driver,即:drv = my_driver::type_id::create(“driver”); drv在my_casen看来,层次结构仍是env.i_agt.drv,但其路径变为了env.i_agt.driver。 好的编码习惯是,这种变量名与其实例化时传递的名字不一致的情况应尽量避免

set与get函数的参数

config_db机制用于在UVM验证平台间传递参数。它们通常都是成对出现的:set寄信,而get收信。如在某个测试用例的build_phase中可使用如下方式寄信:uvm_config_db#(int)::set(this, “env.i_agt.drv”, “pre_num”, 100);

前两个参数联合起来组成目标路径,与此路径符合的目标才能收信。第一个参数必须是一个uvm_component实例的指针,第二个参数是相对此实例的路径。第三个参数表示记号,用以说明这个值是传给目标中的哪个成员的,第四个参数是要设置的值

在driver中的build_phase使用如下方式收信:uvm_config_db#(int)::get(this, “”, “pre_num”, pre_num);

get函数中的前两个参数联合起来组成路径。第一个参数也必须是一个uvm_component实例的指针,第二个参数是相对此实例的路径。一般如果第一个参数被设置为this,第二个参数可以是空的字符串。第三个参数就是set函数中的第三个参数,这两个参数必须严格匹配,第四个参数是要设置的变量

前面在top_tb中通过config_db机制的set函数设置virtual interface时,set函数的第一个参数为null。这种情况UVM会自动把第一个参数替换为uvm_root::get(),即uvm_top。 换句话说,以下两种写法是完全等价的:

initial begin
	uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.drv", "vif", input_if);
end
initial begin
	uvm_config_db#(virtual my_if)::set(uvm_root::get(),"uvm_test_top.env.i_agt.drv","vif",input_if);
end

既然set函数的前两个参数联合起来组成路径,那么在某个测试用例的build_phase中可通过如下的方式设置env.i_agt.drv中pre_num_max的值:把this替换为了this.env,第二个参数是my_driver相对于env的路径。
uvm_config_db#(int)::set(this.env, “i_agt.drv”, “pre_num_max”, 100);

同样,get函数的参数也可以使用这种灵活的方式设置。在driver的build_phase中:

uvm_config_db#(int)::get(this.parent, "drv", "pre_num_max", pre_num_max);
或者:
uvm_config_db#(int)::get(null, "uvm_test_top.env.i_agt.drv", "pre_num_max", pre_num_max);

set和get函数中第三个参数可以与get函数中第四个参数不一样。如第四个参数是pre_num,那么第三个参数可以是p_num,只要保持set和get中第三个参数一致即可。可以这样理解:张三给李四寄了一封信,信上写了李四的名字,这样李四可以收到信。但由于保密的需要,张三只是在信上写了“四”这一个字,只要张三跟李四事先约定好了,那么李四一看到上面写着“四”的信就会收下来。

*省略get语句

set与get函数一般都是成对出现,但在某些情况下,是可以只有set而没有get语句,即省略get语句。

假设在my_driver中有成员变量pre_num,把其使用uvm_field_int实现field automation机制:

int pre_num;
`uvm_component_utils_begin(my_driver)
	`uvm_field_int(pre_num, UVM_ALL_ON)
`uvm_component_utils_end
funtion new(string name = "my_driver", uvm_component parent = null);
	super.new(name, parent);
	pre_num = 3;
endfunction
virtual function void build_phase(uvm_phase phase);
	`uvm_info("my_iver",$sformatf("before super.build_phase, the pre_num is %0d",pre_num),UVM_LOW)
	super.build_phase(phase);
	`uvm_info("my_driver",$sformatf("after super.build_phase,the pre_num is %0d",pre_num),UVM_LOW)
	if(!uvm_config_db#(virtual my_if)::get(this,"","vif",vif))
		`uvm_fatal("my_driver","virtual interface must be set for vif!!!")
endfunction

只要使用uvm_field_int注册,并且在build_phase中调用super.build_phase(),就可以省略在build_phase中的get语句:uvm_config_db#(int)::get(this, “”, “pre_num”, pre_num);

关键是build_phase中的super.build_phase语句,当执行到driver的super.build_phase时会自动执行get语句。这种做法的前提是:第一,my_driver必须使用uvm_component_utils宏注册;第二,pre_num必须使用uvm_field_int宏注册;第三,在调用set函数时其第三个参数必须与要get函数中变量的名字一致,即必须是pre_num,虽然说这两个参数可以不一致。

*跨层次的多重设置

在前面所有例子中都是设置一次,获取一次。但假如设置多次而只获取一次,最终会得到哪个值呢?

UVM中采用的机制是:先看发信人,哪个发信人最权威就听谁的,当同一个发信人先后发了两封信
时,那么最近收到的一封权威高,也就是发信人的优先级最高,而时间的优先级低。

假如uvm_test_top和env中都对driver的pre_num的值进行了设置:

function void my_case0::build_phase(uvm_phase phase);
	super.build_phase(phase);
	…
	uvm_config_db#(int)::set(this,"env.i_agt.drv","pre_num",999);
	`uvm_info("my_case0", "in my_case0, env.i_agt.drv.pre_num is set to 999",UVM_LOW)
endfunction

virtual function void build_phase(uvm_phase phase);
	super.build_phase(phase);
	…
	uvm_config_db#(int)::set(this,"i_agt.drv","pre_num",100);
	`uvm_info("my_env", "in my_env, env.i_agt.drv.pre_num is set to 100",UVM_LOW)
endfunction

driver中获取的值是999。UVM规定层次越高,优先级越高。层次指的是在UVM树中的位置,越靠近根结点uvm_top,层次越高。uvm_test_top的层次是高于env的,所以uvm_test_top中的set函数的优先级高。

UVM这样设置是因为相对于env来说,uvm_test_top更接近用户。用户会在uvm_test_top中设置不同的default_sequence,从而衍生出很多不同的测试用例。而对于env,它在uvm_test_top中实例化。有时这个env不是用户自己开发的,可能是别人已经开发好的一个非常成熟的可重用的模块。对于这种成熟的模块,如果觉得其中某些参数不合要求,那么难道要到env中去修改相关的参数吗?显然这是不合理的。比较合理的就是在uvm_test_top的build_phase中通过set函数的方式修改,从而极大方便了用户的使用。

上述结论在set函数的第一个参数为this时是成立的,但假如set函数的第一个参数不是this:

function void my_case0::build_phase(uvm_phase phase);
	super.build_phase(phase);
	…
	uvm_config_db#(int)::set(uvm_root::get(),"uvm_test_top.env.i_agt.drv","pre_num",999);
	`uvm_info("my_case0", "in my_case0, env.i_agt.drv.pre_num is set to 999", UVM_LOW)
endfunction
virtual function void build_phase(uvm_phase phase);
	super.build_phase(phase);
	…
	uvm_config_db#(int)::set(uvm_root::get(),"uvm_test_top.env.i_agt.drv","pre_num",100);
	`uvm_info("my_env", "in my_env, env.i_agt.drv.pre_num is set to 100",UVM_LOW)
endfunction

这种情况driver得到的pre_num的值是100。由于set函数的第一个参数是uvm_root::get(),所以寄信人变成了
uvm_top。这种情况只能比较寄信的时间。UVM的build_phase是自上而下执行的,my_case0的build_phase先于my_env的build_phase执行。所以my_env对pre_num的设置在后,其设置成为最终的设置。

假如uvm_test_top中set函数的第一个参数是this,而env中set函数的第一个参数是uvm_root::get(),那么driver得到的pre_num的值也是100。因为env中set函数的寄信人变成了uvm_top,在UVM树中具有最高的优先级。

因此,无论如何,在调用set函数时其第一个参数应该尽量使用this。在无法得到this指针的情况下(如在top_tb中)使用null或uvm_root::get()

*同一层次的多重设置

当处于同一层次时是时间优先:

uvm_config_db#(int)::set(this, "env.i_agt.drv", "pre_num", 100);
uvm_config_db#(int)::set(this, "env.i_agt.drv", "pre_num", 109);

当上面两个语句同时出现在测试用例的build_phase中时,driver最终获取到的值是109。像上面这种用法看起来完全是没有任何意义。但考虑这种情况:pre_num在99%的测试用例中的值都是7,只有在1%的测试用例中才会是其他值。

class case1 extends base_test;
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		uvm_config_db#(int)::set(this, "env.i_agt.drv", pre_num_max, 7);
	endfunction
endclass
… 
class case99 extends base_test;
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		uvm_config_db#(int)::set(this, "env.i_agt.drv", pre_num_max, 7);
	endfunction
endclass
class case100 extends base_test;
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		uvm_config_db#(int)::set(this, "env.i_agt.drv", pre_num_max, 100);
	endfunction
endclass

前面99个测试用例的build_phase里都是相同的语句,这种代码维护起来非常困难。因为可能忽然有一天99%的测试用例中pre_num_max的值要变成6,那么就需要把99个测试用例中所有的set语句都改变。这是相当耗时且极易出错。验证中写代码的一个原则是同样的语句只在一个地方出现,避免在多个地方出现。

解决这个问题的办法就是在base_test的build_phase中使用config_db::set进行设置,当由base_test派生的case1~case99在执行super.build_phase(phase)时,都会进行设置:

classs base_test extends uvm_test;
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		uvm_config_db#(int)::set(this, "env.i_agt.drv", pre_num_max, 7);
	endfunction
endclass
class case1 extends base_test;
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
	endfunction
endclass
… 
class case99 extends base_test;
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
	endfunction
endclass
class case100 extends base_test; //第100个测试用例和上面一样需要这么写
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		uvm_config_db#(int)::set(this, "env.i_agt.drv", pre_num_max, 100);
	endfunction
endclass//按照时间优先的原则,后面config_db::set的值将最终被driver得到

*非直线的设置与获取

UVM树中driver的路径为uvm_test_top.env.i_agt.drv。在uvm_test_top、env或i_agt中,对driver中的某些变量
通过config_db机制进行设置称为直线的设置。但若在其他component对driver的某些变量使用config_db机制进行设置,则称为非直线的设置

在my_driver中使用config_db::get获得其他任意component设置给my_driver的参数称为直线的获取。假如要在其他的component获取其他component设置给my_driver的参数的值,称为非直线的获取

进行非直线的设置需仔细设置set函数的前两个参数。以在scoreboard中设置driver的pre_num为例:

function void my_scoreboard::build_phase(uvm_phase phase);
	…
	uvm_config_db#(int)::set(this.m_parent,"i_agt.drv","pre_num",200);
	`uvm_info("my_scoreboard", "in my_scoreboard, uvm_test_top.env.i_agt.drv.pre_num is set to 200", U
endfunction
或者:
function void my_scoreboard::build_phase(uvm_phase phase);
	super.build_phase(phase);
	uvm_config_db#(int)::set(uvm_root::get(),"uvm_test_top.env.i_agt.drv","pre_num",200);
endfunction

无论哪种方式都带来了一个新的问题:在UVM树中build_phase是自上而下执行的,但对于UVM树,scb与i_agt处于同一级别中,UVM并没有明文指出同一级别的build_phase的执行顺序。所以当my_driver在获取参数值时my_scoreboard的build_phase可能已经执行也可能没有执行。 所以非直线的设置有一定的风险,应该避免这种情况的出现。

非直线的获取也只需要设置其第一和第二个参数。假如要在reference model中获取driver的pre_num的值:

function void my_model::build_phase(uvm_phase phase);
	super.build_phase(phase);
	port = new("port", this);
	ap = new("ap", this);
	`uvm_info("my_model", $sformatf("before get, the pre_num is %0d", drv_pre_num), UVM_LOW)
	void'(uvm_config_db#(int)::get(this.m_parent, "i_agt.drv", "pre_num", drv_pre_num));
	`uvm_info("my_model", $sformatf("after get, the pre_num is %0d", drv_pre_num), UVM_LOW)
endfunction
或者:
void'(uvm_config_db#(int)::get(uvm_root::get(),"uvm_test_top.env.i_agt.drv","pre_num",drv_pre_num));

非直线的获取可以在某些情况下避免config_db::set的冗余。上例在reference model中获取driver的pre_num的值,如果不这样做而采用直线获取的方式,那么需要在测试用例中通过cofig_db::set分别给reference model和driver设置pre_num的值。同样的参数值设置出现在不同的两条语句中,大大增加了出错的可能性。因此,非直线的获取可以在验证平台中多个组件(UVM树结点)需要使用同一个参数时,减少config_db::set的冗余

*config_db机制对通配符的支持

前面所有例子在config_db::set操作时,其第二个参数都提供了完整的路径,但实际上也可以不提供完整的路径。config_db机制提供对通配符的支持。

使用完整路径设置virtual interface的代码如下:

initial begin
	uvm_config_db#(virtual my_if)::set(null,"uvm_test_top.env.i_agt.drv","vif",input_if);
	uvm_config_db#(virtual my_if)::set(null,"uvm_test_top.env.i_agt.mon","vif",input_if);
	uvm_config_db#(virtual my_if)::set(null,"uvm_test_top.env.o_agt.mon","vif",output_if);
end

使用通配符,可以把第一和第二个set语句合并为一个set:

initial begin
	uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt*", "vif", input_if);
	uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.o_agt*", "vif", output_if);
	`uvm_info("top_tb", "use wildchar in top_tb's config_db::set!", UVM_LOW)
end

可以进一步简化为:

initial begin
	uvm_config_db#(virtual my_if)::set(null, "*i_agt*", "vif", input_if);
	uvm_config_db#(virtual my_if)::set(null, "*o_agt*", "vif", output_if);
end

这种写法极大简化了代码,用起来非常方便。但并不推荐使用通配符,通配符的存在使得原本非常清晰的设置路径变得扑朔迷离。除非是对整个验证平台的结构有非常明确的了解,否则根本不清楚最终是设置给哪个目标的。即使要用,也尽可能不要过于“省略”。上面的两种方式中,第一种要比第二种好很多。

*check_config_usage

config_db机制能在不同层次对同一参数实现配置。但它的致命缺点是其set函数的第二个参数是字符串如果写错,那么就不能正确设置参数值。假设要对driver的pre_num进行设置,但是在写第二个参数时,错把i_agt写成了i_atg,对于这种情况,UVM不会提供任何错误提示。同时由于第二个参数是字符串,虽然错了但也是一个字符串,所以SV仿真器不会给出任何参数错误提示

针对这种情况,UVM提供了函数check_config_usage:可以显示出截止到此函数调用时有哪些参数是被设置过但是却没有被获取过。由于config_db的set及get语句一般都用于build_phase阶段,所以此函数一般在connect_phase被调用(也可以在connect_phase后的任一phase被调用):

virtual function void connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	check_config_usage();
endfunction

假如在测试用例中有如下的三个设置语句:

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",
											case0_sequence::type_id::get());//设置default_sequence
	uvm_config_db#(int)::set(this,"env.i_atg.drv","pre_num",999);
							//设置driver中pre_num的值,但把i_agt写成了i_atg
	uvm_config_db#(int)::set(this,"env.mdl","rm_value",10);//设置reference model中rm_value的值
endfunction

在my_driver和my_model中分别获取pre_num和rm_value的值,调用check_config_usage的运行结果是:

# UVM_INFO @ 0: uvm_test_top [CFGNRD] ::: The following resources have at least one write and no reads
# default_sequence [/^uvm_test_top\.env\.i_agt\.sqr\.main_phase$/] : (class uvm_pkg::uvm_object_wrapper
# -
# 	--------
# 	uvm_test_top reads: 0 @ 0 writes: 1 @ 0
#
#	pre_num [/^uvm_test_top\.env\.i_atg\.drv$/] : (int) 999
# -
# 	--------
# 	uvm_test_top reads: 0 @ 0 writes: 1 @ 0
#

上述结果显示有两条设置信息分别被写过(set)1次,但是一次也没有被读取(get)。其中pre_num未被读取是因为错把i_agt写成了i_atg。default sequence的设置也没有被读取,是因为default sequence是设置给main_phase的,它在main_phase的时候被获取,而main_phase是在connect_phase之后执行的。

set_config与get_config

set_config与get_config是UVM标准的一部分,但UVM1.2发布后,set_config与get_config被从UVM标准中移除,成为过时的用法。

config_db的调试

UVM提供print_config函数:

virtual function void connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	print_config(1); //数1表示递归的查询,若为0则只显示当前component的信息
endfunction

print_config的输出结果中有很多的冗余信息。其运行结果大致如下:

# UVM_INFO @ 0: uvm_test_top [CFGPRT] visible resources:
# <none>
# UVM_INFO @ 0: uvm_test_top.env [CFGPRT] visible resources:
# <none>
# UVM_INFO @ 0: uvm_test_top.env.agt_mdl_fifo [CFGPRT] visible resources:
# <none>
...

它会遍历整个验证平台的所有结点,找出哪些被设置过的信息对于它们是可见的。

UVM还提供了一个命令行参数UVM_CONFIG_DB_TRACE来对config_db进行调试:

<sim command> +UVM_CONFIG_DB_TRACE

但无论哪种方式,如果set函数的第二个参数设置错误,都不会给出错误信息