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


sequence进阶应用

嵌套的sequence

一个新的sequence可以交替产生两种包:

class case0_sequence extends uvm_sequence #(my_transaction);
	virtual task body();
		my_transaction tr;
		repeat (10) begin
			`uvm_do_with(tr, {tr.crc_err == 1; tr.dmac == 48'h980F;}) //产生CRC错误包
			`uvm_do_with(tr, {tr.crc_err == 0; tr.pload.size() == 1500; //产生长包
								tr.dmac == 48'hF675;})
		end
	endtask
endclass

似乎这样写起来显得特别麻烦。产生的两种不同的包中,第一个约束条件有两个,第二个约束条件有三个。假如约束条件有十个呢?如果整个验证平台中有30个测试用例都用到这样的两种包,那就要在这些测试用例的sequence中加入这些代码,这是一件相当恐怖的事情且特别容易出错。既然已经定义好crc_seq和long_seq,更简单的方法是在一个sequence的body中,除了可以使用uvm_do宏产生transaction外,其实还可以启动其他的sequence,即一个sequence内启动另外一个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

直接在新的sequence的body中调用定义好的sequence,从而实现sequence的重用。这个功能是非常强大的。上面代码中m_sequencer是case0_sequence在启动后所使用的sequencer的指针。但通常来说并不用这么麻烦,可以使用uvm_do宏来完成这些事情:

class case0_sequence extends uvm_sequence #(my_transaction);
	…
	virtual task body();
		crc_seq cseq;
		long_seq lseq;repeat (10) begin
			`uvm_do(cseq)
			`uvm_do(lseq)
		end
		…
	endtask
	…
endclass

uvm_do系列宏中的第一个参数可以是transaction的指针,也可以是某个sequence的指针。当第一个参数是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.dmac == 48'hFFFF;})
		end
		…
	endtask
	…
endclass

sequence里可以添加任意的rand修饰符规范它产生的transaction。sequence与transaction都可以调用randomize进行随机化,都可以有rand修饰符的成员变量,从某种程度上来说,二者的界限比较模糊。这也就是为什么uvm_do系列宏可以接受sequence作为其参数的原因。

在sequence中定义rand类型变量时要注意变量的命名。很多人习惯于变量的名字和transaction中相应字段的名字一致:

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

在case0_sequence中启动上述sequence并将dmac地址约束为48’hFFFF,此时将会发现产生的transaction的dmac并不是48‘hFFFF而是一个随机值!这是因为当运行到上述代码的#15行时,编译器首先去my_transaction寻找dmac,如果找到了就不再寻找。即#15行代码最后几行等价于:

`uvm_do_with(tr, {tr.crc_err == 0; tr.pload.size() == 1500; tr.dmac == tr.dmac;})

long_seq中的dmac并没有起到作用。所以在sequence中定义rand类型变量以向产生的transaction传递约束时,变量的名字一定要与transaction中相应字段的名字不同

*transaction类型的匹配

一个sequencer只能产生一种类型的transaction,一个sequence如果要想在此sequencer上启动,那么其所产生的transaction的类型必须是这种transaction或者派生自这种transaction。

如果一个sequence中产生的transaction的类型不是此种transaction,那么将会报错:

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

嵌套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中存在如下成员变量:

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

在build_phase中使用config_db::get得到这两个成员变量的值。之后sequence在发送transaction时,必须将目的地址设置为dmac,源地址设置为smac。问题是如何在sequence的body中得到这两个变量的值呢?

前面介绍嵌套的sequence时引入了m_sequencer这个属于每个sequence的成员变量,但如果直接使用m_sequencer得到这两个变量的值:

virtual task body();repeat (10) begin
		`uvm_do_with(m_trans, {m_trans.dmac == m_sequencer.dmac;
					m_trans.smac == m_sequencer.smac;})
	end
	… 
endtask

如上写法会引起编译错误。其根源在于m_sequencer是uvm_sequencer_base(uvm_sequencer的基类)类型,而不是my_sequencer类型的。m_sequencer的原型为:

protected uvm_sequencer_base m_sequencer;

但由于case0_sequence在my_sequencer上启动,其中的m_sequencer本质上是my_sequencer类型的,所以可以在my_sequence中通过cast转换将m_sequencer转换成my_sequencer类型,并引用其中的dmac和smac:

virtual task body();
	my_sequencer x_sequencer;
	…
	$cast(x_sequencer, m_sequencer);
	repeat (10) begin
		`uvm_do_with(m_trans, {m_trans.dmac == x_sequencer.dmac;
		m_trans.smac == x_sequencer.smac;})
	end
	… 
endtask

上述过程稍显麻烦。在实际的验证平台中用到sequencer中成员变量的情况非常多。UVM考虑到这种情况,内建了一个宏: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:

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

*sequence的派生与继承

sequence作为一个类,是可以从其中派生其他sequence的:

class base_sequence extends uvm_sequence #(my_transaction);
	`uvm_object_utils(base_sequence)
	`uvm_declare_p_sequencer(my_sequencer)
	function new(string name= "base_sequence");
		super.new(name);
	endfunction
	//define some common function and task
endclass
class case0_sequence extends base_sequence;
	…
endclass

由于在同一个项目中各sequence都是类似的,所以可以将很多公用的函数或者任务写在base sequence中,其他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的成员变量,所以无须再声明一次。如果再声明一次,系统也并不会报错:

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

虽然这相当于连续声明了两个成员变量p_sequencer,但是由于这两个成员变量一个是属于父类的,一个是属于子类的,所以并不会出错。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值