本文转自:http://www.eetop.cn/blog/html/28/1561828-5940198.html
在之前的文章中,读者了解了sequencer与driver之间的传递sequence item的握手过程,同时也掌握了sequence与item之间的关系。接下来,我们需要就sequence挂载到sequencer上的常用方法做出总结,读者可以通过对这些常用方法和宏的介绍,了解到它们的不同的使用场景。另外,面对多个sequence如果需要同时挂载到sequencer时,那么就面临着仲裁的需要,uvm_sequencer自带有仲裁特性,结合着sequence的优先级设定,最终可以实现想要的效果。
常见sequence/item发送的方法和宏
对于UVM的初学者,往往我们给出的小白建议是,区别好方法start和宏`uvm_do就拿下了sequence发送和嵌套的半壁江山。然而,考虑到路粉们对自身技艺的高要求,我们这里就系统性地来阐述一些各种方法和宏之间的关系,以及讨论什么时候可以使用方法、什么时候可以使用宏。
对于已经习惯于sequence宏使用的用户而言,当他们再切回到sequence方法、或者调试这些方法时,会有一种不适感,路桑曾经也如此。但是想要对sequence发送做出更准确的控制,我们还应当正本清源,先熟悉sequence的方法。下面给出一段代码,这段代码中的top_seq嵌套了其它sequence和item,我们就使用到的几种方法做出说明。
输出结果:
在这段例码中,主要使用了两种方法:
uvm_sequence::start(uvm_sequencer_base sequencer, uvm_sequence_base parent_sequence = null,int this_priority = -1, bit call_pre_post = 1)
这个方法是针对将sequence挂载到sequencer上的应用。在使用该方法的过程中,用户首先应该指明sequencer的句柄。如果该sequence是顶部的sequence,即没有更上层的sequence嵌套它,则它可以省略对第二个参数parent_sequence的指定。第三个参数的默认值-1会使得该sequence如果有parent_sequence,即继承下来,如果是root sequence,则将其优先级设定为100。 第四个参数建议使用默认值,这样的话,uvm_sequence::pre_doby()和uvm_sequence::post_body()两个方法会在uvm_sequence::body()的前后执行。
在上面的例子中,child_seq被嵌套到top_seq中,继而在挂载时需要指定parent_sequence;而在test一层调用top_seq时,由于它是root sequence,则不需要再指定parent sequence,这一点用户需要注意。另外,在调用挂载sequence时,需要对这些sequence进行例化。
uvm_sequence::start_item (uvm_sequence_item item, int set_priority = -1, uvm_sequencer_base sequencer=null);
uvm_sequence::finish_item (uvm_sequence_item item, int set_priority = -1);
这一对方法是将item挂载到sequencer上的应用。对于start_item()的使用,第三个参数用户需要注意是否要将item挂载到非当前parent sequence挂载到的sequencer上面,有点绕口是吗?简单来说,如果你想将item和parent sequence挂载到不同的sequencer上面,你就需要指定这个参数。默认情况下,“父”与“子”都是走的一条道路(virtual sequence除外,我们会在后面《层次化》中讨论)。在使用这一对方法时,用户除了需要记得创建item,例如通过uvm_object::create()或者uvm_sequence::create_item(),还需要在它们之间完成item的随机化处理。从这一点建议来看,也需要读者了解到,对于一个item的完整传送,sequence要才sequencer一侧获得什么权限,也可以顺利将其发送至driver。我们可以通过拆解这三个步骤得到更多的细节:
-
创建item
-
通过start_item(),等待获得sequence的授权许可,其后执行parent sequence的方法pre_do()。
-
对item进行随机化处理
-
通过finish_item(),在对item进行了随机化处理之后,执行parent sequence的mid_do(),以及调用uvm_sequencer::send_request()和uvm_sequencer::wait_for_item_done()来将item发送至sequencer再完成与driver之间的握手。最后,执行了parent_sequence的post_do()。
这些完整的细节有两个部分需要注意。第一,sequence和item自身的优先级,可以决定什么时刻可以获得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用户会奇怪为什pre_body()和post_body()没有被执行。在这里,用户需要注意,pre_body()和post_body()并不是一定会被执行的,这一点同UVM的phase顺序执行时有区别的。因此,一个建议是,用户可以在base sequence中自定义一些方法,确保它们会按照顺序执行,譬如下面这段例码。用户可以分别在user_pre_body()、user_post_body()和user_body()中填充代码,确保这些方法会被顺序执行,或者也可以考虑使用pre_start()和post_start()这两个预定义的方法。
下面是一段对这些执行过程的自然代码描述,读者可以看到它们执行的顺序关系和条件:
而对于pre_do()、mid_do()、post_do()而言,这是子一级的item/sequence在调用过程中,会间接调用parent sequence的pre_do()等方法。只要在参数传递过程中,确保child item/sequence与parent sequence的联系,那么这些执行过程是会按照上面的描述依次执行的。下面的也给出一段自然代码描述,来表示执行item时的相关方法执行顺序:
在熟悉了sequence/item的传送方法之后,我们就可以进一步来看看常见的宏有哪些,它们的主要作用是什么。下面给出了一张列表对这些宏做出总结:
对上面的这张表,我们还需给出几点说明:
-
正是通过几个sequence/item宏打天下的方式,用户们可以通过`uvm_do/`uvm_do_with来发送无论是sequence还是item。这种不区分对象是sequence还是item的方式,带来了不少便捷,但也容易引起verifier们的惰性。所以在使用它们之前,需要先了解它们背后的sequence和item各自发送的方法。
-
不同的宏,可能会包含创建对象也可能不创建对象。例如`uvm_do/`uvm_do_with会创建对象,而`uvm_send则不会创建对象,也不会对对象做随机处理。因此要了解它们各自包含的执行顺序。
-
此外,其实还有其它的宏,读者们可以在UVM关于sequence的宏部分做深入了解。例如,将优先级作为参数传递的`uvm_do_pri/`uvm_do_on_prio等,还有专门针对sequence的`uvm_create_seq/`uvm_do_seq/`uvm_do_seq_with等宏。不过,我们在列表中给出的宏已经可以满足大多数的场景应用,而且整齐统一,便于用户的记忆和使用,所以,我们在这里不再对其它的一些宏做额外的说明。
下面的这段例码,我们将之前的例码中的方法对应到一些简单的宏上面,便于读者参照。
最后是路桑给出的关于发送sequence/item的几点建议:
-
无论sequence处于什么层次,都应当让sequence最终在test结束前执行完毕。
-
尽量避免使用fork-join_any或者fork-join_none来控制sequence的发送顺序。因为这背后隐藏的风险是,如果用户想终止在背后运行的sequence线程而简单使用disable的方式,那么就可能在不恰当的时间点上锁住sequencer。一旦sequencer被锁住,而又无法释放,接下来也就无法无法其它的sequence。所以,如果用户想实现类似fork-join_any或者fork-join_none的发送顺序,还应当在使用disable前,对各个sequence线程的后台保持关注,尽量在发送完item完成握手之后,再终止sequence, 这样才能避免一些问题。
-
如果用户要使用fork-join的方式,那么应当确保有方法可以让sequence线程在满足一些条件后停止发送item。否则,只要有一个sequence线程无法停止,则整个fork-join无法退出。面对这种情况,仍然需要用户考虑监测合适的时间点,才能够使用disable来关闭线程。
在上面谈到的建议中,用户需要认识到,disable的手段对于这种需要严格完成握手的传送方式,是需要额外处理的。否则,轻易的停止sequence可能导致整个sequence与sequencer之间的传送瘫痪。