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


sequence基础

从driver中剥离激励产生功能

前面例子中,激励最初产生在driver中,后来产生在sequence中。为什么会有这个过程呢?最开始时,driver的main_phase是这样的:

task my_driver::main_phase(uvm_phase phase);
	my_transaction tr;
	phase.raise_objection(this);
	for(int i=0; i<10; i++) begin
		tr = new;
		assert(tr.randomize);
		drive_one_pkt(tr);
	end
	phase.drop_objection(this);
endtask

如果只是施加上述一种激励,这样是可以的。但当要对DUT施加不同的激励时应该怎么办呢?上述代码中是施加了正确的包,而下一次测试中要在第9个transaction中加入CRC错误的包,那么可以这么写:

task my_driver::main_phase(uvm_phase phase);
	my_transaction tr;
	phase.raise_objection(this);
	for(int i = 0; i < 10; i++) begin
		tr = new;
		if(i == 8)
			assert(tr.randomize with {tr.crc_err == 1;});
		else
			assert(tr.randomize);
		drive_one_pkt(tr);
	end
	phase.drop_objection(this);
endtask

这相当于将整个main_phase重新写了一遍。如果现在有新的需求,需要再测一个超长包。那么就需要再次改写main_phase,也就是每多测一种情况就要多改写一次main_phase。如果经常改写某个任务或函数,就很容易将之前对的地方改错。所以这种方法是不可取的,因为它的可扩展性太差,会经常带来错误。

仔细观察main_phase,其实只有从tr=new语句至drive_one_pkt之间的语句在变。有没有方法可以将这些语句从main_phase中独立出来呢? 最好的方法是在不同测试用例中决定这几行语句的内容。这种想法中已经包含了激励的产生与驱动的分离这个观点。drive_one_pkt是驱动,这是driver应该做的事情,但是像产生什么样的包、如何产生等这些事情应该从driver中独立出去。要实现上述目标,可以使用一个函数来实现:

function void gen_pkt(ref my_transaction tr);
	tr = new;
	assert(tr.randomize);
endfunction
task my_driver::main_phase(uvm_phase phase);
	my_transaction tr;
	bit send_crc_err = 0;
	phase.raise_objection(this);
	for(int i = 0; i < 10; i++) begin
		gen_pkt(tr);
		drive_one_pkt(tr);
	end
	phase.drop_objection(this);
endtask

如上所示可以定义产生正常包的gen_pkt函数,但如何定义一个CRC错误包的函数:

function void gen_pkt(ref my_transaction tr);
	tr = new;
	assert(tr.randomize with {crc_err == 1;});
endfunction

这样最大的问题是gen_pkt函数的重复定义,显然是不允许的。为避免重复定义,有两种策略:第一种是使用虚函数。将gen_pkt定义为virtual类型,然后在建造CRC错误的测试用例时,从my_driver派生一个新的crc_err_driver并重载gen_pkt函数。但新问题又出现了:如何在这个测试用例中实例化新的driver呢?似乎只能重新定义一个my_agent,为了实例化这个新的agent又只能重新定义一个my_env。这种方式显然不可取。第二种解决方式是使定义的函数名不一样,但在driver的main_phase中又无法执行这种具有不同名字的函数。

这个问题单纯用SV提供的一些接口根本无法实现。UVM为解决这个问题引入了sequence机制,在解决过程中还使用了factory机制、config机制。使用sequence机制在不同测试用例中,将不同sequence设置成sequencer的main_phase的default_sequence。当sequencer执行到main_phase时发现有default_sequence,那么它就启动sequence。

sequencer启动sequence并执行的过程相当于之前的gen_pkt,只是调用的位置从driver变到sequencer。sequencer将sequence产生的transaction交给driver,这其实与在driver里面调用gen_pkt没有本质的区别。

*sequence的启动与执行

当完成一个sequence的定义后,可使用start任务将其启动

my_sequence my_seq;
my_seq = my_sequence::type_id::create("my_seq");
my_seq.start(sequencer);

除了直接启动之外,还可使用default_sequence启动。事实上default_sequence会调用start任务,它有两种调用方式,其中一种是前文已经介绍过的:

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

另外一种方式是先实例化要启动的sequence,之后再通过default_sequence启动

function void my_case0::build_phase(uvm_phase phase);
	case0_sequence cseq;
	super.build_phase(phase);
	cseq = new("cseq"); //先实例化要启动的sequence
	uvm_config_db#(uvm_sequence_base)::set(this, "env.i_agt.sqr.main_phase",
											"default_sequence", cseq);
endfunction

当一个sequence启动后会自动执行sequence的body任务。除body外还会自动调用sequence的pre_body与post_body

class case0_sequence extends uvm_sequence #(my_transaction);
	…
	virtual task pre_body();
		`uvm_info("sequence0", "pre_body is called!!!", UVM_LOW)
	endtask
	virtual task post_body();
		`uvm_info("sequence0", "post_body is called!!!", UVM_LOW)
	endtask
	virtual task body();
	…
	#100;
	`uvm_info("sequence0", "body is called!!!", UVM_LOW)
	…
	endtask
	`uvm_object_utils(case0_sequence)
endclass

上述的sequence在执行时,会打印出:

# UVM_INFO my_case0.sv(11) @ 0: uvm_test_top.env.i_agt.sqr@@cseq [sequence0] pre_body is called!!!
# UVM_INFO my_case0.sv(22) @ 100000: uvm_test_top.env.i_agt.sqr@@cseq [sequence0] body is called!!!
# UVM_INFO my_case0.sv(15) @ 100000: uvm_test_top.env.i_agt.sqr@@cseq [sequence0] post_body is called!!

sequence的仲裁机制

在同一sequencer上启动多个sequence

在前文所有例子中,同一时刻在同一sequencer上只启动了一个sequence。UVM支持同一时刻在同一sequencer上启动多个sequence。在my_sequencer上同时启动两个sequence:sequence0和sequence1,代码如下所示:

task my_case0::main_phase(uvm_phase phase);
	sequence0 seq0;
	sequence1 seq1;
	seq0 = new("seq0");
	seq0.starting_phase = phase;
	seq1 = new("seq1");
	seq1.starting_phase = phase;
	fork
		seq0.start(env.i_agt.sqr);
		seq1.start(env.i_agt.sqr);
	join
endtask

其中sequence0和sequence1的定义为:

class sequence0 extends uvm_sequence #(my_transaction);
	...
	virtual task body();
		repeat(5) begin
			`uvm_do(m_trans)
			`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
		end
		#100;
		...
	endtask
	`uvm_object_utils(sequence0)
endclass
class sequence1 extends uvm_sequence #(my_transaction);
	…
	virtual task body();repeat (5) begin
		`uvm_do_with(m_trans, {m_trans.pload.size < 500;})
		`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
	end
	#100;
	…
	endtask
	`uvm_object_utils(sequence1)
endclass

运行如上代码后,会显示两个sequence交替产生transaction

# UVM_INFO my_case0.sv(15) @ 85900: uvm_test_top.env.i_agt.sqr@@seq0 [sequence0] send one transaction
# UVM_INFO my_case0.sv(37) @ 112500: uvm_test_top.env.i_agt.sqr@@seq1 [sequence1] send one transaction
# UVM_INFO my_case0.sv(15) @ 149300: uvm_test_top.env.i_agt.sqr@@seq0 [sequence0] send one transaction
# UVM_INFO my_case0.sv(37) @ 200500: uvm_test_top.env.i_agt.sqr@@seq1 [sequence1] send one transaction
# UVM_INFO my_case0.sv(15) @ 380700: uvm_test_top.env.i_agt.sqr@@seq0 [sequence0] send one transaction
# UVM_INFO my_case0.sv(37) @ 436500: uvm_test_top.env.i_agt.sqr@@seq1 [sequence1] send one transaction

sequencer怎么选择使用哪个sequence的transaction呢?这是UVM的sequence机制中的仲裁问题。对于transaction来说存在优先级的概念,通常优先级越高越容易被选中。当使用uvm_do或uvm_do_with宏时,产生的transaction优先级是默认的优先级(即1)。可通过uvm_do_pri及uvm_do_pri_with改变所产生的transaction的优先级

class sequence0 extends uvm_sequence #(my_transaction);
	…
	virtual task body();repeat (5) begin
			`uvm_do_pri(m_trans, 100)
			`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
	end
	#100;
	…
endtask
	…
endclass
class sequence1 extends uvm_sequence #(my_transaction);
	…
	virtual task body();repeat (5) begin
			`uvm_do_pri_with(m_trans, 200, {m_trans.pload.size < 500;})
			`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
		end
		…
	endtask
	…
endclass

uvm_do_pri与uvm_do_pri_with的第二个参数是优先级,其数值必须是一个大于等于-1的整数。数字越大优先级越高

由于sequence1中transaction的优先级较高,按照预期应先选择sequence1产生的transaction。当sequence1的transaction全部生成完毕后,再产生sequence0的transaction。但运行代码发现并没有按预期进行,而是sequence0与sequence1交替产生transaction。

这是因为sequencer的仲裁算法有很多种:

  • SEQ_ARB_FIFO 默认算法,按照“先入先出”的顺序,不考虑优先级
  • SEQ_ARB_WEIGHTED 加权的仲裁
  • SEQ_ARB_RANDOM 完全随机选择
  • SEQ_ARB_STRICT_FIFO 严格按照优先级,同一优先级的sequence按先入先出的顺序
  • SEQ_ARB_STRICT_RANDOM 严格按照优先级,同一优先级的sequence随机从最高优先级中选择
  • SEQ_ARB_USER 用户可以自定义

要使优先级起作用,应设置为SEQ_ARB_STRICT_FIFO或SEQ_ARB_STRICT_RANDOM

task my_case0::main_phase(uvm_phase phase);
	…
	env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO);
	fork
		seq0.start(env.i_agt.sqr);
		seq1.start(env.i_agt.sqr);
	join
endtask

经过如上设置后会发现直到sequence1发送完transaction后,sequence0才开始发送。

除transaction有优先级外,sequence也有优先级的概念。可在sequence启动时指定其优先级:

 task my_case0::main_phase(uvm_phase phase);
	…
	env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO);
	fork
		seq0.start(env.i_agt.sqr, null, 100);
		seq1.start(env.i_agt.sqr, null, 200);
	join
endtask

start任务的第一个参数是sequencer,第二个参数是parent sequence(可设为null),第三个参数是优先级,不指定则此值为-1,不能设置为小于-1的数字

如果前面sequence0和sequence1的代码中,不在uvm_do系列宏中指定它们的优先级。运行代码会发现sequence1中的transaction完全发送完后才发送sequence0中的transaction。所以,对sequence设置优先级的本质即设置其内产生的transaction的优先级

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值