UVM入门与进阶学习笔记16——sequencer和sequence(1)


  • 就sequence挂载到sequencer的常用方法做出总结,可通过对这些常用方法和宏的介绍了解它们不同的使用场景。多个sequence需要同时挂载到sequencer时,那就面临仲裁的需要,uvm_sequencer自带仲裁特性,结合sequence的优先级设定,最终可以实现想要的效果。

sequence宏概述

  • 可以正确区别方法start()和宏uvm_do,就拿下了sequence发送和嵌套的半壁江山。本节系统性地阐述各种方法和宏之间的关系,以及讨论宏和方法何时可以使用。对于已习惯sequence宏使用的用户而言,当再切回到sequence方法或调试这些方法时会有不适感,但如果想要对sequence发送做出更准确的控制,还须正本清源,首先熟悉sequence的方法。

sequence和item发送实例

class bus_trans extends uvm_sequence_item;
	rand int data;
	`uvm_object_utils_begin(bus_trans)
		`uvm_field_int(data, UVM_ALL_ON)
	`uvm_object_utils_end
	...
endclass
class child_seq extends uvm_sequence;
	`uvm_object_utils(child_seq);
	...
	task body();
		uvm_sequence_item tmp;
		bus_trans req;
		tmp = create_item(bus_trans::get_type(), m_sequencer, "req");
		void'($cast(req, tmp));
		start_item(req);
		req.randomize with {data == 10;};
		finish_item(req);
	endtask
endclass
class top_seq extends uvm_sequence;
	`uvm_object_utils(top_seq)
	...
	task body();
		uvm_sequence_item tmp;
		child_seq cseq;
		bus_trans req;
		//create child sequence and items
		cseq = child_seq::type_id::create("cseq");
		tmp = create_item(bus_trans::get_type(), m_sequencer, "req");
		//send child sequence via start()
		cseq.start(m_sequencer, this);
		//send sequence item
		void'($cast(req, tmp));
		start_item(req);
		req.randomize with {data == 20;};
		finish_item(req);
	endtask
endclass
class sequencer extends uvm_sequencer;
	`uvm_component_utils(sequencer)
	...
endclass
class driver extends uvm_driver;
	`uvm_component_utils(driver)
	...
	task run_phase(uvm_phase phase);
		REQ tmp;
		bus_trans req;
		forever begin
			seq_item_port.get_next_item(tmp);
			void'($cast(req, tmp));
			`uvm_info("DRV", $sformatf("got a item \n %s", req.sprint()), UVM_LOW)
			seq_item_port.item_done();
		end
	endtask
endclass
class env extends uvm_env;
	sequencer sqr;
	driver drv;
	`uvm_component_utils(env)
	...
	function void build_phase(uvm_phase phase);
		sqr = sequencer::type_id::create("sqr", this);
		drv = driver::type_id::create("drv", this);
	endfunction
	function void connect_phase(uvm_phase phase);
		drv.seq_item_port.connect(sqr.seq_item_export);
	endfunction
endclass
class test1 extends uvm_test;
	env e;
	`uvm_component_utils(test1)
	...
	function void build_phase(uvm_phase phase);
		e = env::type_id::create("e", this);
	endfunction
	task run_phase(uvm_phase phase);
		top_seq seq;
		phase.raise_objection(phase);
		seq = new();
		seq.start(e.sqr);
		phase.drop_objection(phase);
	endtask
endclass
输出结果:
UVM_INFO @ 0: uvn_test_top.e.drv [DRV] got a item
...
UVM_INFO @ 0: uvm test_top.e.drv [DRV] got a item
...

发送sequence/item方法建议

上面代码主要使用了两种方法,第一个方法是针对将sequence挂载到sequencer上的应用
- uvm_sequence::start(uvm_sequencer_base sequencer, uvm_sequence_base_parent_sequence = null, int this_priority = -1, bit call_pre_post = 1)

  • 使用该方法的过程中首先应指明sequencer的句柄,如果该sequence是顶部的sequence,即没有更上层的sequence嵌套它,则它可以省略对第二个参数parent_sequence的指定
  • 第三个参数的默认值是-1,会使得该sequence如果有parent_sequence会继承其优先级值,如果它是顶部(root)sequence,则其优先级会被自动设定为100
  • 第四个参数默认值为1,默认uvm_sequence::pre_body()uvm_sequence::post_body()两个方法会在uvm_sequence::body()的前后执行。
  • child_seq被嵌套到top_seq中,继而在挂载时需要指定parent_sequence,而在test层调用top_seq时由于它是root sequence,则不用再指定parent sequence,这一点需注意。另外在调用挂载sequence时,需对这些sequence进行例化
  • 第二种发送方法是针对item挂载到sequencer上的应用
    • uvm_sequence::start_item(uvm_sequence_item item, int set_priority = -1, uvm_sequencer_base_sequence = null);
    • uvm_sequence::finish_item(uvm_sequence_item item, int set_priority = -1);
  • 对于start_item(),第三个参数需注意是否将item挂载到“非当前parent sequence挂载的sequencer”上,即如果将item和其parent sequence挂载到不同sequencer上面,就需要指定这个参数

发送sequence/item方法解析

在使用这对方法时用户除了需记得创建item,例如通过uvm_object::create()uvm_sequence::create_item(),还需要在它们之间完成item的随机化处理。

  • 对于一个item的完整传送,sequence要在sequencer一侧获得通过权限,才可以顺利将item发送至driver。拆解这些步骤如下:
    • 创建item
    • 通过start_item()方法等待获得sequencer的授权许可,其后执行parent sequence的方法pre_do()
    • item进行随机化处理。
    • 通过finish_item()方法在对item进行随机化处理之后执行parent sequencemid_do(),以及调用uvm_sequencer::send_request()uvm_sequencer::wait_for_item_done()将item发送至sequencer再完成与driver之间的握手。最后执行了parent_sequence的post_do()
  • 这些完整的细节有两个部分需要注意:
    • 第一,sequenceitem自身的优先级,可以决定何时可以获取sequencer的授权。
    • 第二,parent sequence的虚方法pre_do()mid_do()post_do()会发生在发送item的过程中间。
  • 如果对比start()方法和start_item()/finish_item(),首先要分清它们面向的挂载对象不同:执行start()过程中默认情况下会执行sequence的pre_body()post_body(),但如果start()的参数call_pre_post=0则不会这样执行,所以一些场景UVM用户会奇怪为什么这两个函数没有执行,它们并非一定会被执行,这一点同UVM的phase顺序执行是有区别的。
  • 下面代码是对start()方法执行过程的自然代码描述,可以看到它们执行的顺序关系和条件:
sub_seq.pre_start()				(task)
sub_seq.pre_body()				(task)		if call_pre_post=1
	parent_seq.pre_do(0)		(task)		if parent_sequence!=null
	parent_seq.mid_do(this)		(func)		if parent_sequence!=null
sub_seq.body()					(task)		//your stimulus code
	parent_seq.post_do(this)()	(func)		if parent_sequence!=null
sub_seq.post_body()				(task)		if call_pre_post=1
sub_seq.post_start()			(task)
  • 对于pre_do()、mid_do()、post_do()而言,子一级的sequence/item在被发送过程中会间接调用parent sequencepre_do()等方法

  • 只要在参数传递过程中确保子一级sequence/itemparent sequence的联系,那么这些执行过程是会按照上面的描述依次执行的。

  • start_item()/finish_item()的自然代码描述,表示执行发送item时的相关方法执行顺序:
    在这里插入图片描述

发送序列的相关宏

在这里插入图片描述

  • 通过这些sequence/item宏,可使用'uvm_do'uvm_do_with发送sequence和item。这种不区分对象的方式带来了不少便捷。使用之前需先了解它们背后的sequence和item各自发送的方法。
  • 不同宏可能会包含创建对象的过程,也可能不包含。如'uvm_do'uvm_do_with会创建对象,而'uvm_send不会创建对象,也不会将对象做随机处理,因此要了解它们各自包含的执行内容和顺序。
  • 还有其它的宏,例如将优先级作为参数传递的uvm_do_pri/uvm_do_on_prio等,还有专门针对sequence的uvm_create_seq/uvm_do_seq/uvm_do_seq_with等宏。

序列宏的示例

class child_seq extends uvm_sequence;
	...
	task body();  //这个任务等同于`uvm_do_with实现的功能
		bus_trans req;
		`uvm_create(req)
		`uvm_rand_send_with(req, {data == 10;})
	endtask
endclass
class top_seq extends uvm_sequence;
	...
	task body();
		child_seq cseq;
		bus_trans req;
		//send child sequence via start()
		`uvm_do(cseq)
		//send sequence item
		`uvm_do_with(req, {data == 20;})
	endtask
endclass
  • 关于发送sequence/item的建议
    • 无论sequence处于什么层次,都应该让sequence在test结束前执行完毕,还应保留一部分时间供DUT将所有发送的激励处理完毕,进入空闲状态才可结束测试
    • 尽量避免使用fork_join_anyfork_join_none控制sequence的发送顺序。因此如果想终止在后台运行的sequence线程而简单使用disable方式,就可能在不恰当的时间点上锁住sequencer
    • 一旦sequencer被锁住且无法释放,接下来就无法发送其它sequence。所以如果用户想实现类似fork_join_any或fork_join_none的发送顺序,应当在使用disable前对各个sequence线程的后台运行保持关注,尽量在发送完item完成握手之后再终止sequence,才能避免sequencer被死锁的问题。
    • 如果要使用fork_join方式,应确保有方法可以让sequence线程在满足一些条件后停止发送item,否则只要有一个sequence线程无法停止,则整个fork_join无法退出。这种情况需要用户考虑监测合适的事件或时间点,才能使用disable来关闭线程。

sequencer的仲裁特性

在这里插入图片描述

  • uvm_sequencer类自建了仲裁机制用来保证多个sequence同时挂载到sequencer时,可以按照仲裁规则允许特定sequence中的item优先通过。
  • 在实际使用中可通过uvm_sequencer::set_arbitration(UVM_SEQ_ARB_TYPE val)函数设置仲裁模式,这里的仲裁模式UVM_SEQ_ARB_TYPE有几种选择:
    • UVM_SEQ_ARB_FIFO默认模式。来自于sequences的发送请求,按照FIFO先进先出的方式被依次授权,和优先级没有关系。
    • UVM_SEQ_ARB_WEIGHTED:不同sequence的发送请求,将按照它们的优先级权重随机授权
    • UVM_SEQ_ARB_RANDOM:不同的请求会被随机授权,而无视它们抵达顺序和优先级。
    • UVM_SEQ_ARB_STRICT_FIFO:不同的请求,会按照它们的优先级以及抵达顺序来依次授权,与优先级和抵达时间都有关系。
    • UVM_SEQ_ARB_STRICT_RANDOM:不同的请求会按照它们的最高优先级随机授权,与抵达时间无关。
    • UVM_SEQ_ARB_USER:可自定义仲裁方法user_priority_arbitration()裁定哪个sequence的请求被优先授权。
  • 与priority有关的模式有UVM_SEQ_ARB_WEIGHTED、UVM_SEQ_ARB_STRICT_FIFO和UVM_SEO_ARB_STRICT_RANDOM,它们的区别在于,UVM_SEQ_ARB_WEIGHTED的授权可能会落到各个优先级sequence的请求上面,而UVM_SEO_ARB_STRICT_RANDOM只会将授权随机安排到最高优先级的请求上面UVM_SEQ_ARB_STRICT_FIFO则不会随机授权,而是严格按照优先级以及抵达顺序来依次授权。
  • 没有特别的要求,用户不用再额外自定义授权机制,因此使用UVM_SEQ_ARB_USER这一模式的情况不多见,其它模式可以满足绝大多数的仲裁需求。
  • 鉴于sequence传送的优先级可以影响sequencer的仲裁授权,结合sequencer的仲裁模式和sequence的优先级给出一段例码:
class top_seq extends uvm_sequence;
	...
	task body();
		child_seq seq1, seq2, seq3;
		m_sequencer.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
		fork
			`uvm_do_pri_with(seq1, 500, {base == 10;})
			`uvm_do_pri_with(seq2, 500, {base == 20;})
			`uvm_do_pri_with(seq3, 300, {base == 30;})
		join
	endtask
endclass
class sequencer extends uvm_sequencer;
	...
endclass
class bus_trans extends uvm_sequence_item;
	rand int data;
	...
endclass
class child_seq extends uvm_sequence;
	rand int base;
	task body();
		bus_trans req;
		repeat(2) `uvm_do_with(req, {data inside {[base:base+9]};})
	endtask
endclass
class driver extends uvm_driver;
	...
	task run_phase(uvm_phase phase);
		REQ tmp;
		bus_trans req;
		forever begin
			seq_item_port.get_next_item(tmp);
			void'($cast(req, tmp));
			`uvm_info("DRV", $sformatf("got a item %0d from parent sequence %s", req.data, req.get_parent_sequence().get_name()), UVM_LOW)
			seq_item_port.item_done();
		end
	endtask
endclass
class env extends uvm_env;
	sequencer sqr;
	driver drv;
	...
	function void build_phase(uvm_phase phase);
		sqr = sequencer::type_id::create("sqr", this);
		drv = driver::type_id::create("drv", this);
	endfunction
	function void connect_phase(uvm_phase phase);
		drv.seq_item_port.connect(sqr.seq_item_export);
	endfunction
endclass
class test1 extends uvm_test;
	env e;
	`uvm_component_utils(test1)
	...
	function void build_phase(uvm_phase phase);
		e = env::type_id::create("e", this);
	endfunction
	task run_phase(uvm_phase phase);
		top_seq seq;
		phase.raise_objection(phase);
		seq = new();
		seq.start(e.sqr);
		phase.drop_objection(phase);
	endtask
endclass

输出结果:
在这里插入图片描述

  • seq1、seq2、seq3在同一时刻发起传送请求,通过'uvm_do_prio_with的宏,在发送sequence时可以传递优先级参数。
  • 由于将seq1与seq2设置为同样的高优先级,而seq3设置为较低的优先级,在随后的UVM_SEQ_ARB_STRICT_FIFO仲裁模式下,可以看到输出结果按优先级高低和传送请求时间顺序,先将seq1和seq2中的item发送完毕,随后将seq3发送完。
  • 除了sequence遵循仲裁机制,一些特殊情况下有些sequence需要有更高权限取得sequencer的授权来访问driver。例如在需要响应中断的情况下,用于处理中断的sequence应该有更高的权限来获得sequencer的授权。

sequencer的锁定机制

  • uvm_sequencer提供两种锁定机制,分别通过lock()grab()方法实现,它们的区别在于:
    • lock()unlock()可以为sequence提供排外的访问权限,但前提条件是该sequence需按照sequencer的仲裁机制获得授权。获得授权后则无需担心权限被收回,只有该sequence主动解锁它的sequencer,才可以释放锁定的权限lock()是一种阻塞任务,只有获得权限才会返回。
    • grab()ungrab()也可以为sequence提供排外的访问权限,而且它只需在sequencer下次授权周期时就可无条件获得权限。与lock方法相比,它无视同一时刻内发起传送请求的其它sequence,可以阻止它的只有预先获得授权的其它lock或grab的sequence
    • 如果sequence使用了lock()grab()方法,必须在sequence结束前调用unlock()ungrab()方法释放权限,否则sequencer会进入死锁状态而无法继续为其余sequence授权。

sequencer的锁定示例

class bus_trans extends uvm_sequence_item;
	...
endclass
class child_seq extends uvm_sequence;
	...
endclass
class lock_seq extends uvm_sequence;
	...
	task body();
		bus_trans req;
		#10ns;
		m_sequencer.lock(this);
		`uvm_info("LOCK", "get exclusive access by lock()", UVM_LOW)
		repeat(3) #10ns `uvm_do_with(req, {data inside {[100:110]};})
		m_sequencer.unlock(this);
	endtask
endclass
class grab_seq extends uvm_sequence;
	...
	task body();
		bus_trans req;
		#20ns;
		m_sequencer.grab(this);
		`uvm_info("LOCK", "get exclusive access by grab()", UVM_LOW)
		repeat(3) #10ns `uvm_do_with(req, {data inside {[200:210]};})
		m_sequencer.ungrab(this);
	endtask
endclass
class top_seq extends uvm_sequence;
	...
	task body();
		child_seq seq1, seq2, seq3;
		lock_seq locks;
		grab_seq grabs;
		m_sequencer.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
		fork
			`uvm_do_pri_with(seq1, 500, {base == 10;})
			`uvm_do_pri_with(seq2, 500, {base == 20;})
			`uvm_do_pri_with(seq3, 300, {base == 30;})
			`uvm_do_pri(locks, 300)
			`uvm_do(grabs)
		join
	endtask
endclass

输出结果:
在这里插入图片描述
sequencer的锁定解析

  • 结合例码和输出结果,可以发现:
    • 对于sequence locks,在10ns时它跟其它几个sequence一同向sequencer发起请求,按照仲裁模式,sequencer先后授权给seq1、seq2、seq3,最后才授权给locks。
    • 而locks在获得授权之后,就可以一直享有权限而无需担心权限被sequencer收回,locks结束前需要通过unlock()方法返还权限
    • 对于sequence grabs,尽管在20ns时就发起了请求权限(实际上seq1、seq2、seq3也在同一时刻发起了权限请求),而由于权限已经被locks占用,所以它也无权收回权限。
    • 因此只有当locks在40ns结束时,grabs才可以在sequencer没有被锁定的状态下获得权限,而grabs在此条件下获取权限是无视同一时刻发起请求的其它sequence的
    • 同样的在grabs结束前也应通过ungrab()方法释放权限,防止sequencer的死锁行为。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值