UVM实战 卷I学习笔记9——UVM中的sequence(6)

目录


在sequence中使用config_db

*在sequence中获取参数

config_db机制set函数的目标是一个component,或者说之前所有获取参数的操作都是在一个component中进行的。sequence机制是UVM中最强大的机制之一,config_db机制也对sequence机制提供了支持,可以在sequence中获取参数。

能够调用config_db::get的前提是已经进行了设置。sequence本身是一个uvm_object,它无法像uvm_component那样出现在UVM树中,从而很难确定在对其进行设置时的第二个路径参数。所以在sequence中使用config_db::get函数得到参数的最大障碍是路径问题

在UVM中使用get_full_name()可以得到一个component的完整路径,同样,此函数也可以在一个sequence中被调用,尝试着在一个sequence的body中调用此函数并打印出返回值,其结果大体如下:uvm_test_top.env.i_agt.sqr.case0_sequence

这个路径由两部分组成:此sequence的sequencer的路径和实例化此sequence时传递的名字。可使用如下方式为一个sequence传递参数:

function void my_case0::build_phase(uvm_phase phase);
	…
	uvm_config_db#(int)::set(this,"env.i_agt.sqr.*","count",9);
	…
endfunction

set函数的第二个路径参数里出现了通配符,是因为sequence在实例化时名字一般是不固定的,而且有时是未知的(如使用default_sequence启动的sequence的名字就是未知的),所以使用通配符。

在sequence中以如下方式调用config_db::get函数:

class case0_sequence extends uvm_sequence #(my_transaction);
	…
	virtual task pre_body();
		if(uvm_config_db#(int)::get(null, get_full_name(), "count", count))
		`uvm_info("seq0", $sformatf("get count value %0d via config_db", count), 
									UVM_MEDIUM)
		else
		`uvm_error("seq0", "can't get count value!")
	endtask
	…
endclass

这里需关注第一个参数:在get函数原型中第一个参数必须是一个component,而sequence不是一个component,所以这里不能使用this指针,只能使用null或者uvm_root::get()。 前文提过当使用null时,UVM会自动将其替换为uvm_root::get(),加上第二个参数get_full_name(),就可以完整得到此sequence的路径,从而得到参数

*在sequence中设置参数

与获取参数相比,在sequence中使用config_db::set设置参数就比较简单。有了在top_tb中设置virtual interface的经验,读者在这里可以使用类似的方式为UVM树中的任意结点传递参数:

class case0_vseq extends uvm_sequence;
	…
	virtual task body();
		…
		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);
			#10000;
			uvm_config_db#(bit)::set(uvm_root::get(),"uvm_test_top.env0.scb","cmp_en",1);
		end
		join
		…
	endtask
endclass

上例中是向scoreboard中传递了一个cmp_en的参数。除了向component中传递参数外,也可以向sequence中传递参数:

class drv0_seq extends uvm_sequence #(my_transaction);
	my_transaction m_trans;
	bit first_start;
	`uvm_object_utils(drv0_seq)
	function new(string name= "drv0_seq");
		super.new(name);
		first_start = 1;
	endfunction
	virtual task body();
		void'(uvm_config_db#(bit)::get(uvm_root::get(), get_full_name(), 
									"first_start", first_start));
		if(first_start)
			`uvm_info("drv0_seq", "this is the first start of the sequence", UVM_MEDIUM)
		else
			`uvm_info("drv0_seq", "this is not the first start of the 
						sequence",UVM_MEDIUM)
			uvm_config_db#(bit)::set(uvm_root::get(), "uvm_test_top.v_sqr.*", 
									"first_start", 0);
		…
	endtask
endclass

这个sequence向自己传递了一个参数:first_start。仿真中,当此sequence第一次启动时,其first_start值为1;当后面再次启动时,其first_start为0。根据first_start值的不同,可以在body中有不同的行为。

这里需要注意的是,由于此sequence在virtual sequence中被启动,所以其get_full_name的结果应该是uvm_test_top.v_sqr.*,而不是uvm_test_top.env0.i_agt.sqr.*,所以在设置时第二个参数应该是前者。

*wait_modified的使用

上节例子向scoreboard传递cmp_en的参数,scoreboard可根据此参数决定对收到的transaction是否进行检查。做异常用例测试时常用到这种方式。关键是如何在scoreboard中获取这个参数。

前面章节中scoreboard都是在build_phase中调用get函数,并调用的前提是参数已经被设置过。一个sequence是在task phase中运行的,当其设置一个参数时,其时间往往是不固定的。

针对这种不固定的设置参数方式,UVM提供wait_modified任务:有三个参数,与config_db::get的前三个参数完全一样。当它检测到第三个参数的值被更新过后就返回,否则一直等待在那里。其调用方式如下:

task my_scoreboard::main_phase(uvm_phase phase);
	…
	fork
		while(1) begin
			uvm_config_db#(bit)::wait_modified(this, "", "cmp_en");
			void'(uvm_config_db#(bit)::get(this, "", "cmp_en", cmp_en));
			`uvm_info("my_scoreboard", $sformatf("cmp_en value modified, 
												the new value is %0d", cmp_en),
		end
		…
	join
endtask

上述代码中wait_modified与main_phase中的其他进程在同一时刻被fork起来,当检测到参数值被设置后立刻调用config_db::get得到新的参数。其他进程可根据新参数值决定后续的比对策略。

与get函数一样除了可以在component中使用外,还可以在sequence中调用wait_modified任务:

class drv0_seq extends uvm_sequence #(my_transaction);
	…
	virtual task body();
		bit send_en = 1;
		fork
			while(1) begin
				uvm_config_db#(bit)::wait_modified(null, get_full_name(), "send_en");
				void'(uvm_config_db#(bit)::get(null, get_full_name, "send_en", send_en));
				`uvm_info("drv0_seq", $sformatf("send_en value modified, 
											the new value is %0d", send_en));
			end
		join_none
		…
	endtask
endclass

response的使用

*put_response与get_response

sequence机制提供了一种sequence→sequencer→driver的单向数据传输机制。但在复杂的验证平台中,sequence需要根据driver对transaction的反应来决定接下来要发送的transaction,即sequence需要得到driver的反馈。sequence机制提供对这种反馈的支持,允许driver将response返回给sequence。

如果需要使用response,那么在sequence中需要使用get_response任务:

class case0_sequence extends uvm_sequence #(my_transaction);
	…
	virtual task body();
		…
		repeat (10) begin
			`uvm_do(m_trans)
			get_response(rsp); //notice
			`uvm_info("seq", "get one response", UVM_MEDIUM)
			rsp.print();
		end
		…
	endtask
	`uvm_object_utils(case0_sequence)
endclass

driver中,则需要使用put_response任务:

task my_driver::main_phase(uvm_phase phase);
	…
	while(1) begin
		seq_item_port.get_next_item(req);
		drive_one_pkt(req);
		rsp = new("rsp");
		rsp.set_id_info(req);
		seq_item_port.put_response(rsp); //notice
		seq_item_port.item_done();
	end
endtask

关键是设置set_id_info函数,它将req的id等信息复制到rsp中。由于可能存在多个sequence在同一个sequencer上启动的情况,只有设置了rsp的id等信息,sequencer才知道将response返回给哪个sequence

除了使用put_response外,UVM还支持直接将response作为item_done的参数

while(1) begin
	seq_item_port.get_next_item(req);
	drive_one_pkt(req);
	rsp = new("rsp");
	rsp.set_id_info(req);
	seq_item_port.item_done(rsp); //notice
end

response的数量问题

通常来说一个transaction对应一个response,但事实上UVM也支持一个transaction对应多个response的情况,这种情况在sequence中需要多次调用get_response,而在driver中需要多次调用put_response

task my_driver::main_phase(uvm_phase phase);
	while(1) begin
		seq_item_port.get_next_item(req);
		drive_one_pkt(req);
		rsp = new("rsp");
		rsp.set_id_info(req);
		seq_item_port.put_response(rsp);
		seq_item_port.put_response(rsp);
		seq_item_port.item_done();
	end
endtask
class case0_sequence extends uvm_sequence #(my_transaction);
	virtual task body();
		repeat (10) begin
			`uvm_do(m_trans)
			get_response(rsp);
			rsp.print();
			get_response(rsp);
			rsp.print();
		end
	endtask
endclass

存在多个response时,将response作为item_done参数的方式就不适用了。由于一个transaction只能对应一个item_done,所以使用多次item_done(rsp)是会出错的。

response机制的原理是driver将rsp推送给sequencer,而sequencer内部维持一个队列,当有新的response进入时就推入此队列。但此队列的大小是有限制的,默认情况下大小为8。队列中有8个response时,如果driver再次向此队列推送新的response,UVM就会给出如下错误提示:

UVM_ERROR @ 1753500000: uvm_test_top.env.i_agt.sqr@@case0_sequence 
[uvm_test_top.env.i_agt.sqr.case0_sequence] Response queue overflow, response was dropped

因此,如果在driver中每个transaction后都发送一个response,而sequence又没能及时get_response,sequencer中的response队列就存在溢出的风险。

*response handler与另类的response

get_response和put_response一一对应。在sequence中启动get_response时,进程就会阻塞直到response_queue中被放入新的记录。如果driver能马上将response通过put_response方式传回sequence,那么sequence被阻塞的进程会得到释放,可以接着发送下一个transaction给driver。

但假如driver需要延时较长的一段时间才能将transaction传回在此期间,driver希望能够继续从sequence得到新的transaction并驱动它,但由于sequence被阻塞在了那里,根本不可能发出新的transaction。

发生上述情况的主要原因为sequence中发送transaction与get_response在同一个进程中执行,假如将二者分开,在不同进程中运行将会得到不同结果。这种情况需使用response_handler

class case0_sequence extends uvm_sequence #(my_transaction);
	…
	virtual task pre_body();
		use_response_handler(1); //notice
	endtask
	virtual function void response_handler(uvm_sequence_item response);
		if(!$cast(rsp, response))
			`uvm_error("seq", "can't cast")
		else begin
			`uvm_info("seq", "get one response", UVM_MEDIUM)
			rsp.print();
		end
	endfunction
	virtual task body();
		if(starting_phase != null)
		starting_phase.raise_objection(this);
		repeat (10) begin
			`uvm_do(m_trans)
		end
		#100;
		if(starting_phase != null)
			starting_phase.drop_objection(this);
	endtask
	`uvm_object_utils(case0_sequence)
endclass

response handler功能默认关闭,所以使用response_handler需先调用use_response_handler函数,打开sequence的response handler功能。

打开response handler功能后,用户需要重载虚函数response_handler。此函数的参数是一个uvm_sequence_item类型的指针,需首先将其通过cast转换变成my_transaction类型,之后就可根据rsp的值来决定后续sequence的行为

put/get_response或response_handler,都是新建一个transaction并将其返回给sequence。事实上当一个uvm_do语句执行完毕后,其第一个参数并不是一个空指针,而是指向刚刚被送给driver的transaction。利用这一点可以实现一种另类的response:

task my_driver::main_phase(uvm_phase phase);
	…
	while(1) begin
		seq_item_port.get_next_item(req);
		drive_one_pkt(req);
		req.frm_drv = "this is information from driver";
		seq_item_port.item_done();
	end
endtask

driver中向req中的成员变量赋值,而sequence则检测这个值:

class case0_sequence extends uvm_sequence #(my_transaction);
	…
	virtual task body();
		…
		repeat (10) begin
			`uvm_do(m_trans)
			`uvm_info("seq", $sformatf("get information from driver: %0s", 
										m_trans.frm_drv), UVM_MEDIUM)
		end
		…
	endtask
	`uvm_object_utils(case0_sequence)
endclass

这种另类的response在很多总线的driver中用到。

*rsp与req类型不同

前面所有例子的response类型都与req类型相同。UVM也支持response与req类型不同的情况。

uvm_driver、uvm_sequencer与uvm_sequence的原型分别是:

class uvm_driver #(type REQ=uvm_sequence_item, type RSP=REQ) extends uvm_component;
class uvm_sequencer #(type REQ=uvm_sequence_item, RSP=REQ) extends 
						uvm_sequencer_param_base #(REQ, RSP);
virtual class uvm_sequence #(type REQ = uvm_sequence_item,
								type RSP = REQ) extends uvm_sequence_base;

前面章节例子中只向它们传递了一个参数,因此response与req的类型是一样的。如果要使用不同类型的rsp与req,那么driver、sequencer与sequence在定义时都要传入两个参数

class my_driver extends uvm_driver#(my_transaction, your_transaction);
class my_sequencer extends uvm_sequencer #(my_transaction, your_transaction);
class case0_sequence extends uvm_sequence #(my_transaction, your_transaction);

之后,可以使用put_response来发送response:

task my_driver::main_phase(uvm_phase phase);
	…
	while(1) begin
		seq_item_port.get_next_item(req);
		drive_one_pkt(req);
		rsp = new("rsp");
		rsp.set_id_info(req);
		rsp.information = "driver information";
		seq_item_port.put_response(rsp);
		seq_item_port.item_done();
	end
endtask

使用get_response来接收response:

class case0_sequence extends uvm_sequence #(my_transaction, your_transaction);
	…
	virtual task body();
		…
		repeat (10) begin
			`uvm_do(m_trans)
			get_response(rsp);
			`uvm_info("seq", $sformatf("response information is: %0s", 
										rsp.information), UVM_MEDIUM)
		end
		…
	endtask
	`uvm_object_utils(case0_sequence)
endclass

除了put/get_response外,也可以使用response handler,这与req及rsp类型相同时完全一样。

上一篇:(7)UVM objection机制


下一篇:篇1-【未完成】config_db