UVM实战 卷I学习笔记13——UVM高级应用(4)

目录

  • 聚合参数
    • 聚合参数的定义
    • 聚合参数的优势与问题
  • config_db
    • 换一个phase使用config_db
    • *config_db的替代者
    • *set函数的第二个参数的检查


聚合参数

聚合参数的定义

验证平台用到的参数有两大类,一类是验证环境与DUT中都要用到的参数,这些参数通常都对应DUT中的寄存器,前面已经将这些参数组织成一个参数类;另一类是验证环境独有的,比如driver中要发送的preamble数量的上限和下限。本节讲述如何组织这类参数。

大项目要配置的参数可能有千百个。如果全部使用config_db写法,那么就会出现下面这种情况:

classs base_test extends uvm_test;
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		uvm_config_db#(int)::set(this, "path1", "var1", 7);
		…
		uvm_config_db#(int)::set(this, "path1000", "var1000", 999);
	endfunction
endclass

可以想像写这1000句set函数将是多么麻烦的一件事情。比较好的方法是将这1000个变量放在一个专门的类里面来实现

class my_config extends uvm_object;
	rand int var1;
	…
	rand int var1000;
	constraint default_cons{
		var1 = 7;
		…
		var1000 = 999;
	}
	`uvm_object_utils_begin(my_config)
		`uvm_field_int(var1, UVM_ALL_ON)
		…
		`uvm_field_int(var1000, UVM_ALL_ON)
	`uvm_object_utils_end
endclass

经过上述定义之后,可以在base_test中这样写:

classs base_test extends uvm_test;
	my_config cfg;
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		cfg = my_config::type_id::create("cfg");
		uvm_config_db#(my_config)::set(this, "env.i_agt.drv", "cfg", cfg);
		uvm_config_db#(my_config)::set(this, "env.i_agt.mon", "cfg", cfg);
		…
	endfunction
endclass

这样省略了绝大多数的set语句。在driver中以如下方式使用这个聚合参数类:

class my_driver extends uvm_driver#(my_transaction);
	my_config cfg;
	`uvm_component_utils_begin(my_driver)
		`uvm_field_object(cfg, UVM_ALL_ON | UVM_REFERENCE)
	`uvm_component_utils_end
	extern task main_phase(uvm_phase phase);
endclass
task my_driver::main_phase(uvm_phase phase);
	while(1) begin
		seq_item_port.get_next_item(req);
		pre_num = $urand_range(cfg.pre_num_min, cfg.pre_num_max);//drive this pkt, and the number of preamble is pre_num
		seq_item_port.item_done();
	end
endtask

如果在某个测试用例中想要改变某个变量的值,可以这样做:

class case100 extends base_test;
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		cfg.pre_num_max = 100;
		cfg.pre_num_min = 8;
		…
	endfunction
endclass

聚合参数的优势与问题

使用聚合参数后,可以将此参数类的指针放在virtual sequencer中

class my_vsqr extends uvm_sequencer;
	my_config cfg;
	…
endclass
classs base_test extends uvm_test;
	my_config cfg;
	my_vsqr vsqr;
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		cfg = my_config::type_id::create("cfg");
		vsqr = my_vsqr::type_id::create("vsqr", this);
		vsqr.cfg = this.cfg;
		…
	endfunction
endclass

这样,当sequence要动态地改变某个验证平台中的变量值时,可使用如下方式:

class vseq extends uvm_sequence;
	`uvm_object_utils(vseq)
	`uvm_declare_p_sequencer(vsequencer)
	task body();//send some transaction
		p_sequencer.cfg.pre_num_max = 99;//send other transaction
	endtask
endclass

聚合参数方便了在sequence中改变验证平台参数。某些情况下甚至可以将interface也放入此聚合参数类中

class my_config extends uvm_object;
	`uvm_object_utils(my_config)
	virtual my_if vif;
	function new(string name = "my_config");
		super.new(name);
		$display("%s", get_full_name());
		if(!uvm_config_db#(virtual my_if)::get(null, get_full_name(), "vif", vif))
			`uvm_fatal("my_config", "please set interface")
	endfunction
endclass

这样无论在driver还是monitor,都可直接使用cfg.vif,而不必使用config_db得到interface

task my_driver::main_phase(uvm_phase phase);
	cfg.vif.data <= 8'b0;
	cfg.vif.valid <= 1'b0;
	while(!cfg.vif.rst_n)
		@(posedge cfg.vif.clk);
	…
endtask

同样,如果将该cfg的指针赋值给普通sequencer,那么在前面心跳sequence的实现中,sequencer也不必再使用uvm_config_db::get得到接口。

上面my_config中使用config_db的形式得到vif。这里出现了uvm_config_db::get(),由于my_config是一个object而不是component,所以get_full_name得到的结果是其实例化时指定的名字。所以base_test中实例化cfg的名字要与top_tb中config_db::set的路径参数一致。如:

function void base_test::build_phase(uvm_phase phase);
	…
	cfg = new("cfg");
endfunction
module top_tb;
	…
	initial begin
		uvm_config_db#(virtual my_if)::set(null, "cfg", "vif", input_if);
	end
endmodule

或者:

function void base_test::build_phase(uvm_phase phase);
	…
	cfg = new({get_full_name(), ".cfg"});
endfunction
module top_tb;
	…
	initial begin
		uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.cfg", "vif", input_if);
	end
endmodule

最方便还是用直接赋值的形式。在top_tb中将interface通过config_db::set方式传递给base_test,在base_test中实例化cfg后就可以直接赋值:

function void base_test::build_phase(uvm_phase phase);
	…
	cfg = new("cfg");
	cfg.vif = this.vif.
endfunction

将所有参数聚合的做法方便了验证平台的搭建。将这个聚合类的指针赋值给任意component,这些component再也不必用config_db::get函数来获取参数。当验证平台某个组件要增加参数时,只需在这个聚合类中加入此参数,在测试用例中直接为其赋值,然后在验证平台中就可直接使用

class my_config extends uvm_object;
	rand int new_var;
	…
endclass
function void my_case0::build_phase(uvm_phase phase);
	…
	cfg.new_var = 1;
endfunction
task my_driver::main_phase(uvm_phase phase);
	if(cfg.new_var)
	…
endtask

假如不使用聚合类而使用config_db,那么需要在测试用例中进行设置:

function void my_case0::build_phase(uvm_phase phase);
	…
	uvm_config_db#(int)::set(this, "uvm_test_top.env.i_agt.drv", "new_var", 1); 
endfunction

在driver中增加一个变量并使用get语句获取它:

function void my_driver::build_phase(uvm_phase phase);void'(uvm_config_db#(int)::get(this, "", "new_var", new_var);
endfunction
task my_driver::main_phase(uvm_phase phase);
	if(new_var)
	…
endtask

可以看出使用聚合类减少了config_db::set的使用,也大大降低出错的概率

但聚合参数也不是完美的。聚合参数的本质是将一些属于某个uvm_component的变量变成对所有uvm_component可见,从而使得这些变量错误地被其他uvm_component修改

另外,聚合参数整合了整个验证平台的参数,一定程度上降低了验证平台的可重用性。 之前讲述了env级别的重用,聚合参数类对于这种重用没有任何问题。但在实际中能够做到env级别重用的IC公司并不多。很多公司使用的是基于agent的重用

假如某个agent中需要的参数只占聚合参数类10%的参数,现在这个agent被其他项目重用,那么在新项目中也需实例化这个聚合参数类。但在新项目中这个聚合参数类可能90%的参数没有用。解决这个问题的方式是缩小聚合参数的粒度,将一个聚合参数类分成多个小的聚合参数类,如将agent所有参数定义为一个聚合参数类,在大的聚合参数类中实例化这个小的聚合参数类。只是这样可能每个聚合参数类中只有一两个参数。与直接使用config_db相比并没有方便多少

config_db

换一个phase使用config_db

前面介绍使用config_db几乎都是在build_phase中。由于其config_db::set的第二个参数是字符串而经常出错。要想避免config_db::set第二个参数引起的问题,一种可行的想法是把这个参数使用get_full_name()

如在测试用例中对driver中某个参数进行设置:

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

若要对sequence的某个参数设置,可以:

uvm_config_db#(int)::set(null, {env.i_agt.sqr.get_full_name(),.*}, "pre_num", 100);

但在build_phase时,整棵UVM树还没有形成,使用env.i_agt.drv的形式进行引用会引起空指针的错误。所以要想这么使用有两种方法,一种是所有的实例化工作都在各自的new函数中完成

function base_test::new(string name, uvm_component parent);
	super.new(name, parent);
	env = my_env::type_id::create("env", this);
endfunction
function my_env::new(string name, uvm_component parent);
	super.new(name, parent);
	i_agt = my_agent::type_id::create("i_agt", this);
	o_agt = my_agent::type_id::create("o_agt", this);
	…
endfunction
…

这种情况下当整个验证平台运行到build_phase时UVM树已实例化完毕,在uvm_confg_db::set中使用get_full_name没有任何问题。

第二种方式是将uvm_config_db::set移到connect_phase中去。由于connect_phase是由下向上执行base_test(或者测试用例)的connect_phase几乎是最后执行的,因此应该在end_of_elaboration_phase或start_of_simulation_phase调用uvm_config_db::get

function void my_case0::connect_phase(uvm_phase phase);
	uvm_config_db#(int)::set(null, env.i_agt.drv.get_full_name();, "pre_num", 100);
endfunction
function void my_driver::end_of_elaboration_phase(uvm_phase phase);
	void'(uvm_config_db#(int)::get(this, "", "pre_num", pre_num));
endfunction

两种方式对top_tb的config_db::set无效,因为在top_tb中很难用env.i_agt.sqr.get_full_name()的类似方式获得一个路径值。幸运的是top_tb中的config_db::set语句不多,且它们相对比较固定,通常不会出问题。

*config_db的替代者

已经见识到config_db::set函数的第二个参数带来的不便,因此要尽量减少config_db的使用。那么有没有可能完全不使用config_db

完全可以。config_db设置的参数有两种,一种是结构性的参数,如控制driver是否实例化的参数is_active:

function void my_agent::build_phase(uvm_phase phase);
	super.build_phase(phase);
	if (is_active == UVM_ACTIVE) begin
		sqr = my_sequencer::type_id::create("sqr", this);
		drv = my_driver::type_id::create("drv", this);
	end
	mon = my_monitor::type_id::create("mon", this);
endfunction

对于这种参数,可以在实例化agent时同时指明其is_active的值

virtual function void build_phase(uvm_phase phase);
	super.build_phase(phase);
	if(!in_chip) begin
		i_agt = my_agent::type_id::create("i_agt", this);
		i_agt.is_active = UVM_ACTIVE;
	end
	…
endfunction

这是本书一直使用的方式。对于在build_phase中设置的一些非结构性的参数,如向某个driver传递某个参数:

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

可以完全在build_phase之后的任意phase中使用绝对路径引用进行设置

function void my_case0::connect_phase(uvm_phase phase);
	env.i_agt.drv.pre_num = 100;
endfunction

对于向sequence传递参数,可在virtual sequence中启动sequence并通过赋值的方式传递。但这样的前提是virtual sequence已经启动。那么如何启动virtual sequence呢?大部分例子都通过default_sequence启动

function void my_case0::build_phase(uvm_phase phase);
	super.build_phase(phase);
	uvm_config_db#(uvm_object_wrapper)::set(this, "v_sqr.main_phase",
										"default_sequence", 
										case0_vseq::type_id::get());
endfunction

但其实可以在测试用例的main_phase中手工启动此sequence

task my_case0::main_phase(uvm_phase phase);
	case0_vseq vseq;
	super.main_phase(phase);
	vseq = new("vseq");
	vseq.starting_phase = phase;
	vseq.start(vsqr);
endtask

这样可以不用再在build_phase中设置default_sequence。

如何为sequence中的set语句寻找替代者呢?可通过uvm_root::get()得到UVM树真正的根uvm_top,从uvm_top的孩子中找到base_test(大多数情况下uvm_top只有一个名字为uvm_test_top的孩子,不过也不能排除有多个孩子的情况)的实例,并通过绝对路径引用赋值

class case0_vseq extends uvm_sequence;
	…
	virtual task body();
		my_transaction tr;
		drv0_seq seq0;
		drv1_seq seq1;
		base_test test_top;
		uvm_component children[$];
		uvm_top.get_children(children);//notice
		foreach(children[i]) begin
			if($cast(test_top, children[i])) ;
		end
		if(test_top == null)
			`uvm_fatal("case0_vseq", "can't find base_test 's instance")
		fork
			`uvm_do_on(seq0, p_sequencer.p_sqr0);
			`uvm_do_on(seq1, p_sequencer.p_sqr1);
			begin
				#10000;
				//uvm_config_db#(bit)::set(uvm_root::get(), "uvm_test_top.env0.scb", "cmp_en", 0);
				test_top.env0.scb.cmp_en = 0;
				#10000;
				//uvm_config_db#(bit)::set(uvm_root::get(), "uvm_test_top.env0.scb", "cmp_en", 1);
				test_top.env0.scb.cmp_en = 1;
			end
		join
		#100;
	endtask
endclass

至于在top_tb中使用config_db对interface进行的传递,可使用绝对路径的方式:

function void base_test::connect_phase(uvm_phase phase);
	env0.i_agt.drv.vif = testbench.input_if0;
	…
endfunction

这里用到了绝对路径。如果不使用绝对路径,可通过静态变量实现新建一个将此验证平台中所有可能用到的interface包含的类中作为成员变量

class if_object extends uvm_object;static if_object me;
	static function if_object get();
		if(me == null) begin
			me = new("me");
		end
		return me;
	endfunction
	virtual my_if input_vif0;
	virtual my_if output_vif0;
	virtual my_if input_vif1;
	virtual my_if output_vif1;
endclass

在top_tb中为这个类的interface赋值:

module top_tb;
	…
	initial begin
		if_object if_obj;
		if_obj = if_object::get();
		if_obj.input_vif0 = input_if0;
		if_obj.input_vif1 = input_if1;
		if_obj.output_vif0 = output_if0;
		if_obj.output_vif1 = output_if1;
	end
endmodule

get函数是if_object的一个静态函数,通过它可得到if_object的一个实例,并对此实例中的interface进行赋值。

在base_test的connect_phase(或build_phase之后的任一phase)对所有的interface进行赋值:

function void base_test::connect_phase(uvm_phase phase);
	if_object if_obj;
	if_obj = if_object::get();
	v_sqr.p_sqr0 = env0.i_agt.sqr;
	v_sqr.p_sqr1 = env1.i_agt.sqr;
	env0.i_agt.drv.vif = if_obj.input_vif0;
	env0.i_agt.mon.vif = if_obj.input_vif0;
	env0.o_agt.mon.vif = if_obj.output_vif0;
	env1.i_agt.drv.vif = if_obj.input_vif1;
	env1.i_agt.mon.vif = if_obj.input_vif1;
	env1.o_agt.mon.vif = if_obj.output_vif1;
endfunction

使用上述方式,可以在验证平台中完全避免config_db的使用。

*set函数的第二个参数的检查

无论如何,config_db机制是UVM一项重要的机制,完全不用config_db是走向另一个极端。config_db机制最大问题在于不对set函数的第二个参数进行检查。本节介绍一个函数,可在一定程度上(并不能检查所有!)实现对第二个参数有效性的检查。可将这个函数加入到自己的验证平台中。

函数的代码如下:

function void check_all_config();
	check_config::check_all();
endfunction

这个全局函数会调用check_config的静态函数check_all:

static function void check_all();
	uvm_component c;
	uvm_resource_pool rp;
	uvm_resource_types::rsrc_q_t rq;
	uvm_resource_types::rsrc_q_t q;
	uvm_resource_base r;
	uvm_resource_types::access_t a;
	uvm_line_printer printer;
	c = uvm_root::get();
	if(!is_inited)
		init_uvm_nodes(c);
	rp = uvm_resource_pool::get();
	q = new;
	printer=new();
	foreach(rp.rtab[name]) begin
		rq = rp.rtab[name];
		for(int i = 0; i < rq.size(); ++i) begin
			r = rq.get(i);
			//$display("r.scope = %s", r.get_scope());
			if(!path_reachable(r.get_scope)) begin
				`uvm_error("check_config", "the following config_db::set's path is not reachable in
				r.print(printer);
				r.print_accessors();
			end
		end
	end
endfunction

该函数先根据is_inited值来调用init_nodes函数,将uvm_nodes联合数组初始化。is_inited和uvm_nodes是check_config的两个静态成员变量:

class check_config extends uvm_object;
	static uvm_component uvm_nodes[string];
	static bit is_inited = 0;

在init_nodes函数中用递归的方式遍历整棵UVM树并将树上所有结点加入到uvm_nodes中uvm_nodes的索引是相应结点的get_full_name值,而存放的值就是相应结点的指针

static function void init_uvm_nodes(uvm_component c);
	uvm_component children[$];
	string cname;
	uvm_component cn;
	uvm_sequencer_base sqr;
	is_inited = 1;
	if(c != uvm_root::get()) begin
		cname = c.get_full_name();
		uvm_nodes[cname] = c;
		if($cast(sqr, c)) begin
			string tmp;
			$sformat(tmp, "%s.pre_reset_phase", cname);
			uvm_nodes[tmp] = c;
			…
			$sformat(tmp, "%s.main_phase", cname);
			uvm_nodes[tmp] = c;
			…
			$sformat(tmp, "%s.post_shutdown_phase", cname);
			uvm_nodes[tmp] = c;
		end
	end
	c.get_children(children);
	while(children.size() > 0) begin
		cn = children.pop_front();
		init_uvm_nodes(cn);
	end
endfunction

初始化工作只进行一次。下次调用此函数将不会进行初始化。初始化完成后check_all函数会遍历config_db库中所有记录。对任意记录检查其路径参数,并将该参数与uvm_nodes中所有路径参数对比,如果能够匹配,说明这条路径在验证平台中是可达的。这里调用了path_reachable函数:

static function bit path_reachable(string scope);
	bit err;
	int match_num;
	match_num = 0;
	foreach(uvm_nodes[i]) begin
		err = uvm_re_match(scope, i);
		if(err) begin
			//$display("not_match: name is %s, scope is %s", i, scope);
		end
		else begin
			//$display("match: name is %s, scope is %s", i, scope);
			match_num++;
		end
	end
	return (match_num > 0);
endfunction

config_db::set的第二个参数支持通配符,所以path_reachable通过调用uvm_re_match函数检查路径是否匹配。uvm_re_match是UVM实现的一个函数,检查两条路径是否一样。uvm_nodes遍历完成后如果匹配的数量为0,说明路径根本不可达,此时将给出一个UVM_ERROR的提示。

在UVM中使用很多的是default_sequence的设置:

uvm_config_db#(uvm_object_wrapper)::set(this, 
                                      "env.i_agt.sqr.main_phase",
								      "default_sequence", 
								     case0_sequence::type_id::get());

在这个设置的第二个参数中出现了main_phase。如果只是将sequencer的get_full_name的结果与这个路径相比,那么path_reachable函数认为不匹配。所以init_nodes函数对sequencer在其中加入了对各phase的支持

由于要遍历整棵UVM树的结点,所以check_all_config函数只能在build_phase之后才能被调用,如connect_phase等。不匹配时它会给出一条UVM_ERROR的提示信息,如前面在i_agt后插入一个空格,它将会给出如下错误提示:

UVM_ERROR check_config.sv(101) @ 0: reporter [check_config] the following config_db::set's path is 
not reachable in your verification environment, please check
default_sequence [/^uvm_test_top\.env\.i_agt \.sqr\.main_phase$/] : (class uvm_pkg::uvm_object_wrapper) ?
default_sequence: (<unknown>@478) @478
--------
uvm_test_top reads: 0 @ 0 writes: 1 @ 0

这个函数可以在很多地方调用。如在sequence中使用config_db::set函数后就可以立即调用该函数检查有效性。

需要说明的是这个函数有一些局限,其中之一就是不支持config_db::set向object传递的参数,如前面向my_config传递virtual interface出现错误就不能通过该函数检查出来。幸运的是这种传递参数的方式并不多见,出现错误的概率也比较低。

你可能感兴趣的:(UVM实战卷I,学习笔记,测试用例,功能测试,模块测试,测试覆盖率)