Sequence可在测试用例中被用来构造不同的测试场景,可以说是UVM验证中非常重要甚至最为重要的一部分。开发并应用好sequence,可以极大地提高测试用例的开发效率和质量。
前面两篇文章已经介绍了sequence item的基本构造,以及driver和sequencer的握手机制和使用模型。本文将继续关注sequence这一主题,介绍sequence的基础内容。
本文内容包括sequence的重要成员变量和方法,怎么写sequence,sequence如何被启动,文章最后会告诉你为什么不要用default sequence。
01 Sequence重要成员和方法
uvm_sequence派生自uvm_sequence_item,它有一个重要的任务方法body()和两个重要的成员变量m_sequencer和p_sequencer后面会介绍。
body()
任务方法body()是sequence作为测试序列的主体部分,我们需要在body()方法中定义该sequence的功能和行为。
其实在body()方法的前后,分别还有pre_body()和post_body()可能会被调用。只不过大多数时候,定义在pre_body()或者post_body()方法中功能都可以直接写到body()方法中,此外,这两个方法是否被调用还取决于sequence的启动方式或参数,具有不确定性。所以,就算在成熟的UVM Testbench中也很少见对pre_body()或者post_body()方法的重写。
m_sequencer
接下来是m_sequencer成员。m_sequencer是一个指向与当前sequence“绑定”的sequencer的句柄。Sequence基本的工作流程是通过sequencer将sequence_item发送给driver,那么自然需要有一个sequencer与之关联,而m_sequencer正是关联sequence和sequencer的纽带。
除此之外,m_sequencer可以被用来访问UVM component层次结构中的其他资源或者配置信息等。但有一点需要注意,m_sequencer的类型是uvm_sequencer_base,而我们通常绑定给sequence的sequencer是uvm_sequencer_base的子类,所以,当该句柄引用子类对象时,可引用的资源将受到限制。
p_sequencer
为了解决上面提到的资源引用问题,UVM又搞了一个p_sequencer的句柄,用户可以通过宏`uvm_declare_p_sequencer来创建。
p_sequencer的类型是我们绑定给sequence的sequencer的实际类型,它会通过$cast函数去引用到m_sequencer引用的对象,这样一来,我们就可以通过p_sequencer去引用实际sequencer中的资源了。
02 怎么写Sequence
Sequence的基本结构如下图所示,主要包含五个部分:对宏的调用,sequence_item的声明,成员变量声明,构造函数,最后是在body中定义sequence的主要逻辑功能。
该示例代码不针对其他sequence的嵌套,以及virtual sequence的使用。关于对sequence的嵌套,仲裁,以及virtual sequence的使用,会在后续文章中专门介绍。
在有些代码实现中会在pre_body()中raise objection,在post_body()中drop objection。但本文不建议这么做,在Sequence中不要直接对objection进行控制,我们可以将对objection的控制统一放到测试用例顶层的位置,方便维护和调试。
另外,UVM类库还提供了uvm_do、uvm_do_with、uvm_do_pri、uvm_do_pri_with等宏用来“自动”实例化、随机化和发送sequence item。不过我仍然觉得start_item()和finish_item()已经足够简单和直观了,那些宏在编译阶段实际上也是展开成对start_item()和finish_item()的调用。
03 怎么启动Sequence
结论:Sequence启动可以用start()方法,也可以用uvm_do宏,还可以用default_sequence。但建议用start(),不建议用default_sequence。
start()方法的原型如上图所示,总共有四个参数。其中用的最多的是第一个参数sequencer,用来指定sequence运行在哪个sequencer上面。第二个参数parent_sequence,如果不指定,则表示当前sequence为根sequence;如果指定了父sequence,则sequence在运行的过程中会去调用上级sequence的pre_do(),mid_do()和post_do()函数。第三个参数是当前sequence的优先级,在多sequence的情况下会有用。第四个参数call_pre_post,默认值为1,表示sequence在运行过程中会调用pre_body()和post_body()任务。
再来看uvm_do宏,展开之后最终还是调用的start函数。区别在于有两个参数设置,parent_sequence参数配置成了this,call_pre_post参数配置成了0。uvm_do更多的是用在一个父sequence对子sequence的启动上:父sequence的do函数会被执行,同时不需要执行子sequence的pre_body()和post_body()。
之所以建议用start(),是因为宏尽管便利,但会隐藏掉一些必要的信息,比如不了解uvm_do宏展开的工程师,他就不知道pre_body()和post_body()为什么不会被执行;而如果用start(),一方面不需要关心所谓的父或子sequence的关系,可以很明确的通过配置参数来达到想要的执行内容。
04 Default_sequence
最后谈一下default_sequence的启动方式。这种启动方式来自OVM,通过将某个sequence配置为default_sequence,可以使得该sequence在仿真开始之后被自动启动。
之所以不建议用default_sequence,大致有这么一些原因:设置default_sequence是在connect_phase或者build_phase中完成,这就意味着将测试用例跟Testbench的构建混在了一起,在环境架构上不干净;使用default_sequence隐藏了太多信息,比如用户并不是很明确sequence在哪个时候被启动,甚至不知道是不是被其他同事配置了default_sequence,一旦出问题很难调试;无法在同一个sequencer上启动其他sequence,而这个需求又是经常存在的。
如果非要使用default_sequence,请重读上面一段话。
参考资料
[1] Accellera Systems Initiative. "Universal Verification Methodology (UVM) 1.2 Class Reference" (2014).
[2] Accellera Systems Initiative. "Universal Verification Methodology (UVM) 1.2 User's Guide" (2015).
领取专属 10元无门槛券
私享最新 技术干货