sequence进阶

进阶应用

嵌套的sequence

在一个sequence的body中,除了可以使用uvm_do宏产生transaction外,还可以启动另外一个sequence,这就是嵌套的sequence:

class case0_sequence extends uvm_sequence #(my_transaction);virtual task body();
		crc_seq cseq;
		long_seq lseq;
…
		repeat (10) begin
			cseq = new("cseq");
			cseq.start(m_sequencer);
			lseq = new("lseq");
			lseq.start(m_sequencer);
		end
…
	endtask
…
endclass

也可以使用uvm_do宏来完成这些事:

repeat (10) begin
	`uvm_do(cseq)
	`uvm_do(lseq)
end
  • uvm_do系列宏中,其第一个参数除了可以是transaction的指针外,还可以是某个sequence的指针。当第一个参数是transaction时,调用start_item和finish_item;当第一个参数是sequence时,它调用此sequence的start任务。
  • 除了uvm_do宏外,前面介绍的uvm_send宏、uvm_rand_send宏、uvm_create宏,其第一个参数都可以是sequence的指针。唯一例外的是start_item与finish_item,这两个任务的参数必须是transaction的指针。

在sequence中使用rand类型变量

在transaction的定义中,通常使用rand来对变量进行修饰,说明在调用randomize时要对此字段进行随机化。而在sequence中也可以使用rand修饰符。
有如下的sequence,它有成员变量ldmac:

class long_seq extends uvm_sequence#(my_transaction);
	rand bit[47:0] ldmac;virtual task body();
		my_transaction tr;
		`uvm_do_with(tr, {tr.crc_err == 0;
		tr.pload.size() == 1500;
		tr.dmac == ldmac;})
		tr.print();
	endtask
endclass

这个sequence可以作为底层的sequence被顶层的sequence调用:

class case0_sequence extends uvm_sequence #(my_transaction);virtual task body();
		long_seq lseq;
…
		repeat (10) begin
			`uvm_do_with(lseq, {lseq.ldmac == 48'hFFFF;})
		end
…
	endtask
…
endclass

在sequence中定义rand类型变量以向产生的transaction传递约束时,变量的名字要与transaction中相应字段的名字不同。

transaction类型的匹配

  • 一个sequencer只能产生一种类型的transaction,一个sequence如果要想在此sequencer上启动,那么其所产生的transaction的类型必须是这种transaction或者派生自这种transaction。
  • 嵌套sequence的前提是,在套里面的所有sequence产生的transaction都可以被同一个sequencer所接受。
  • 如果要将两个截然不同的transaction交给同一个sequencer,需要将sequencer和driver能够接受的数据类型设置为uvm_sequence_item:
class my_sequencer extends uvm_sequencer #(uvm_sequence_item);
class my_driver extends uvm_driver#(uvm_sequence_item);

在sequence中可以交替发送my_transaction和your_transaction:

class case0_sequence extends uvm_sequence;
	my_transaction m_trans;
	your_transaction y_trans;virtual task body();
…
		repeat (10) begin
			`uvm_do(m_trans)
			`uvm_do(y_trans)
		end
…
	endtask

	`uvm_object_utils(case0_sequence)
endclass

这样带来的问题是,由于driver中接收的数据类型是uvm_sequence_item,如果它要使用my_transaction或者your_transaction中的成员变量,必须使用cast转换:

task my_driver::main_phase(uvm_phase phase);
	my_transaction m_tr;
	your_transaction y_tr;while(1) begin
		seq_item_port.get_next_item(req);
		if($cast(m_tr, req)) begin
			drive_my_transaction(m_tr);
			`uvm_info("driver", "receive a transaction whose type is 			my_transaction", UVM_MEDIUM)
		end
		else if($cast(y_tr, req)) begin
			drive_your_transaction(y_tr);
			`uvm_info("driver", "receive a transaction whose type is your_transaction", UVM_MEDIUM)
		end
		else begin
			`uvm_error("driver", "receive a transaction whose type is unknown")
		end
		seq_item_port.item_done();
	end
endtask

p_sequencer的使用

如果在sequencer中使用config_db::get得到了两个成员变量的值,如:

class my_sequencer extends uvm_sequencer #(my_transaction);
	bit[47:0] dmac;
	bit[47:0] smac;virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		void'(uvm_config_db#(bit[47:0])::get(this, "", "dmac", dmac));
		void'(uvm_config_db#(bit[47:0])::get(this, "", "smac", smac));
	endfunction

	`uvm_component_utils(my_sequencer)
endclass

之后sequence在发送transaction时,必须将目的地址设置为dmac,源地址设置为smac。可以使用以下方法在sequence的body中得到这两个变量的值:

class case0_sequence extends uvm_sequence #(my_transaction);virtual task body();
…
		repeat (10) begin
			`uvm_do_with(m_trans, {m_trans.dmac == p_sequencer.dmac;
						 m_trans.smac == p_sequencer.smac;})
		end
…
endtask

endclass

上述代码中uvm_declare_p_sequencer(SEQUENCER)宏声明了一个SEQUENCER类型的成员变量,如在定义sequence时,使用此宏声明sequencer的类型:

class case0_sequence extends uvm_sequence #(my_transaction);
	my_transaction m_trans;
	`uvm_object_utils(case0_sequence)
	`uvm_declare_p_sequencer(my_sequencer)
…
endclass

则相当于声明了如下的成员变量:

class case0_sequence extends uvm_sequence #(my_transaction);
	my_sequencer p_sequencer;
…
endclass

UVM之后会自动将m_sequencer通过cast转换成p_sequencer。这个过程在pre_body()之前就完成了。因此在sequence中可以直接使用成员变量p_sequencer来引用dmac和smac。

sequence的派生与继承

  • sequence作为一个类,是可以从其中派生其他sequence的,由于在同一个项目中各sequence都是类似的,所以可以将很多公用的函数或者任务写在base sequence中,其他sequence都从此sequence派生。
  • 但对于那些使用了uvm_declare_p_sequence声明p_sequencer的base sequence,在派生
    的sequence中则不需要调用此宏声明p_sequencer。因为uvm_declare_p_sequence的实质是在base sequence中声明了一个成员变量p_sequencer。当其他的sequence从其派生时,p_sequencer依然是新的sequence的成员变量,所以无须再声明一次了。

virtual sequence

在这里插入图片描述

实现sequence之间同步的最好的方式就是使用virtual sequence。从字面上理解,即虚拟的sequence。虚拟的意思就是它根本就不发送transaction,它只是控制其他的sequence,起统一调度的作用。为了使用virtual sequence,一般需要一个virtual sequencer。virtual sequencer里面包含指向其他真实sequencer的指针:

class my_vsqr extends uvm_sequencer;
	my_sequencer p_sqr0;
	my_sequencer p_sqr1;
…
endclass

在base_test中,实例化vsqr,并将相应的sequencer赋值给vsqr中的sequencer的指针:

class base_test extends uvm_test;

	my_env env0;
	my_env env1;
	my_vsqr v_sqr;
…
endclass


function void base_test::build_phase(uvm_phase phase);
	super.build_phase(phase);
	env0 = my_env::type_id::create("env0", this);
	env1 = my_env::type_id::create("env1", this);
	v_sqr = my_vsqr::type_id::create("v_sqr", this);
endfunction

function void base_test::connect_phase(uvm_phase phase);
	v_sqr.p_sqr0 = env0.i_agt.sqr;
	v_sqr.p_sqr1 = env1.i_agt.sqr;
endfunction

在virtual sequene中则可以使用uvm_do_on系列宏来发送transaction:

class case0_vseq extends uvm_sequence;
	`uvm_object_utils(case0_vseq)
	`uvm_declare_p_sequencer(my_vsqr)virtual task body();
		my_transaction tr;
		drv0_seq seq0;
		drv1_seq seq1;
…
		`uvm_do_on_with(tr, p_sequencer.p_sqr0, {tr.pload.size == 1500;})
		`uvm_info("vseq", "send one longest packet on p_sequencer.p_sqr0", UVM_MEDIUM)
		fork
			`uvm_do_on(seq0, p_sequencer.p_sqr0);
			`uvm_do_on(seq1, p_sequencer.p_sqr1);
		join
…
	endtask
endclass

virtual sequence和virtual sequencer中virtual的来源意思为:它们各自并不产生transaction,而只是控制其他的sequence为相应的sequencer产生transaction。virtual sequence和virtual sequencer只是起一个调度的作用。
由于根本不直接产生transaction,所以virtual sequence和virtual sequencer在定义时根本无需指明要发送的transaction数据类型。
此外,使用virtual sequence还需注意:

  1. objection的控制:我们可以只在最顶层的virtual sequence中控制objection。因为virtual sequence是起统一调度作用的,这种统一调度不只体现在transaction上,也应该体现在objection的控制上。在验证平台中使用objection时,经常会出现没有按照预期结束仿真的情况。这种情况下就需要层层地查找哪里有objection被提起了,哪里有objection被撤销了。如果大家约定俗成都只在最顶层的virtual sequence中控制objection,那么在问题时,只查找最顶层的virtual sequence即可,从而大大提高效率。
  2. 线程的使用:在virtual sequence中尽量避免使用fork join_none,可以使用wait fork或者fork join来代替。

config_db的使用

在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为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

除了向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.*。

wait_modified的使用

在向scoreboard传递参数时,scoreboard可以根据收到的参数参数决定是否对收到的transaction进行检查。在做一些异常用例测试的时候,经常用到这种方式。

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));
		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)); 
			end
		join_none
…
	endtask
endclass

response的使用

  • put_response与get_response

如果sequence需要得到driver的一个反馈。则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);
			`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);
		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);
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)是会出错的。

  • rsp与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);

sequence library

  • 随机选择sequence

所谓sequence library,就是一系列sequence的集合,其派生自uvm_sequence,从本质上说它是一个sequence。它根据特定的算法随机选择注册在其中的一些sequence,并在body中执行这些sequence。
一个sequence library的定义如下:

class simple_seq_library extends uvm_sequence_library#(my_transaction);
	function new(string name= "simple_seq_library");
		super.new(name);
		init_sequence_library();
	endfunction

	`uvm_object_utils(simple_seq_library)
	`uvm_sequence_library_utils(simple_seq_library);

endclass

在定义sequence library时有三点要特别注意:
一是从uvm_sequence派生时要指明此sequence library所产生的transaction类型,这
点与普通的sequence相同;
二是在其new函数中要调用init_sequence_library,否则其内部的候选sequence队列就是空的;
三是要调用uvm_sequence_library_utils注册。

一个sequence library在定义之后,如果没有其他任何的sequence注册到其中,是没有任何意义的。一个sequence在定义时使用宏uvm_add_to_seq_lib来将其加入某个sequence library中:

class seq0 extends uvm_sequence#(my_transaction);
…
	`uvm_object_utils(seq0)
	`uvm_add_to_seq_lib(seq0, simple_seq_library)
	virtual task body();
		repeat(10) begin
			`uvm_do(req)
			`uvm_info("seq0", "this is seq0", UVM_MEDIUM)
		end
	endtask
endclass

uvm_add_to_seq_lib有两个参数,第一个是此sequence的名字,第二个是要加入的sequence library的名字。一个sequence可以加入多个不同的sequence library中:

class seq0 extends uvm_sequence#(my_transaction);
	`uvm_object_utils(seq0)
	`uvm_add_to_seq_lib(seq0, simple_seq_library)
	`uvm_add_to_seq_lib(seq0, hard_seq_library)
	virtual task body();
		repeat(10) begin
			`uvm_do(req)
			`uvm_info("seq0", "this is seq0", UVM_MEDIUM)
		end
	endtask
endclass

同样的,可以有多个sequence加入同一sequence library中:

class seq1 extends uvm_sequence#(my_transaction);
…
	`uvm_object_utils(seq1)
	`uvm_add_to_seq_lib(seq1, simple_seq_library)
	virtual task body();
		repeat(10) begin
			`uvm_do(req)
			`uvm_info("seq1", "this is seq1", UVM_MEDIUM)
		end
	endtask
endclass

当sequence与sequence library定义好后,可以将sequence library作为sequencer的default sequence:

function void my_case0::build_phase(uvm_phase phase);
	super.build_phase(phase);

	uvm_config_db#(uvm_object_wrapper)::set(this,
											"env.i_agt.sqr.main_phase",
											"default_sequence",
											simple_seq_library::type_id::get());
endfunction
  • 控制选择算法
    sequence library随机从其sequence队列中选择几个执行。这是由其变量selection_mode决定的,这个变量的有以下几种算法:
  1. UVM_SEQ_LIB_RAND:完全的随机。
  2. UVM_SEQ_LIB_RANDC:将加入其中的sequence随机排一个顺序,然后按照此顺序执行。这可以保证每个sequence执行一遍,在所有的sequence被执行完一遍之前,不会有sequence被执行第二次,其配置方式如下:
function void my_case0::build_phase(uvm_phase phase);
…
	uvm_config_db#(uvm_sequence_lib_mode)::set(this,
											   "env.i_agt.sqr.main_phase",
											   "default_sequence.selection_mode",
											   UVM_SEQ_LIB_RANDC);
endfunction
  1. UVM_SEQ_LIB_ITEM的意思是sequence library并不执行其sequence队列中的sequence,而是自己产生transaction。换言之,sequence library在此种情况下就是一个普通的sequence,只是其产生的transaction除了定义时施加的约束外,没有任何额外的约束。
  2. UVM_SEQ_LIB_USER是用户自定义选择的算法。
  • 控制执行次数
    执行的次数是由sequence library内部的两个变量控制的:
int unsigned min_random_count=10;
int unsigned max_random_count=10;

sequence library会在min_random_count和max_random_count之间随意选择一个数来作为执行次数。这里只能选择10。当selection_mode为UVM_SEQ_LIB_ITEM时,将会产生10个item;为其他模式时,将会顺序启动10个sequence。可以设置这两个值为其他值来改变迭代次数:

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",
											simple_seq_library::type_id::get());
	uvm_config_db#(uvm_sequence_lib_mode)::set(this,
											   "env.i_agt.sqr.main_phase",
											   "default_sequence.selection_mode",
											   UVM_SEQ_LIB_ITEM);
	uvm_config_db#(int unsigned)::set(this,
									  "env.i_agt.sqr.main_phase",
                            		  "default_sequence.min_random_count",
									  5);
	uvm_config_db#(int unsigned)::set(this,
									  "env.i_agt.sqr.main_phase",
									  "default_sequence.max_random_count",
									  20);
endfunction

上述设置将会产生最多20个,最少5个transaction。

  • 使用sequence_library_cfg
    UVM提供了一个类uvm_sequence_library_cfg来对sequence library进行配置。它一共有三个成员变量:
class uvm_sequence_library_cfg extends uvm_object;
	`uvm_object_utils(uvm_sequence_library_cfg)
	uvm_sequence_lib_mode selection_mode;
	int unsigned min_random_count;
	int unsigned max_random_count;
…
endclass

通过配置如上三个成员变量,并将其传递给sequence library就可对sequence library进行配置:

function void my_case0::build_phase(uvm_phase phase);
	uvm_sequence_library_cfg cfg;
	super.build_phase(phase);
	cfg = new("cfg", UVM_SEQ_LIB_RANDC, 5, 20);

	uvm_config_db#(uvm_object_wrapper)::set(this,
											"env.i_agt.sqr.main_phase",
											"default_sequence",
											simple_seq_library::type_id::get());
	uvm_config_db#(uvm_sequence_library_cfg)::set(this,
												  "env.i_agt.sqr.main_phase",
												  "default_sequence.config",
												  cfg);
endfunction
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值