1. 简介
什么是虚拟sequencers和虚拟sequences,在什么时候使用它们?当我们需要使用多种驱动agents协调生成激励的测试用例,则需要使用虚拟sequences来完成。今天学习的这篇论文[1]充分详细的介绍了m_sequencer和p_sequencer句柄以及与这些句柄一起使用的宏(`uvm_declare_p_sequencer)和方法。
2. 什么时候需要一个virtual sequencer?
如果只有一个激励驱动的 agent,则不需要virtual sequencer。如果你有多个驱动agent,但不需要激励之间的协调(在设计上是相互独立的),则不需要virtual sequencer。如果您有多个驱动 agent并且需要激励间的协调,则需要一个virtual sequencer。如果测试平台将来需要扩展成有多个agent,或者多个独立激励的测试平台需要设计成相互协调的激励,那么环境就要更新到包括一个或多个virtual sequencer。很明显在项目后期执行的这些更新可能会很痛苦(如果你对这个平台不了解),相反的如果从一开始就添加了virtual sequencer并在需要时利用虚拟sequencer就好多了。工程师可能要养成在大多数UVM测试台中添加virtual sequencers的习惯。
图片来源于论文
3. 为什么是“虚拟”的sequencers和sequences?
SystemVerilog具有虚拟类,虚[2]拟方法和虚拟接口,所有这三个都需要“ virtual”关键字。 UVM具有虚拟sequencers和虚拟sequences,但没有一个要求使用“virtual”关键字。 UVM中没有uvm_virtual_sequencer或uvm_virtual_sequence基类。所有sequencers和虚拟sequencers都是uvm_sequencer类的派生类,所有sequence和虚拟sequences都是uvm_sequence类的派生类。
4. 为什么虚拟的sequencers和sequences是虚拟的?
虚拟sequencers的三个属性是:
- 控制其他sequencers。
- 未连接到driver。
- 本身不处理事务。
虚拟sequencer未连接到driver。 它不是通过sequencers端口在driver上执行单个sequence items,而是通过句柄到sub-sequencer目标执行sub-sequences和在sequencers上的sequence items。《UVM用户指南》有时将sub-sequences称为“driver-sequence”。 虚拟sequencer是“虚拟的”,因为通常我们不会真正在此sequencer上运行sequence,而是通过虚拟sequencer中定义的句柄在sub-sequencer上运行序列。
虚拟sequence可以在多个实际sequencers上运行多种事务类型。 虚拟sequence通常只是在适当的子sequencer上协调其他sequences的执行。
在vsqr中声明了2个sqr
5.三种virtual sequencer模式
《UVM用户指南》描述了用户可以使用虚拟sequence与sub-sequnecer进行交互的三种方式:(1)“Business as usual”(也称为产生并行流量,多条流交替),(2)关闭sub-sequencers,(3)使用grab()和 ungrab()。
《UVM用户指南》声称“大多数用户关闭sub-sequencers仅从虚拟sequence中调用sequences”,但是我们的经验以及许多验证同事的经验是,最流行的virtual sequencer模式是产生并行流量,也称为“Business as usual”。
6.如何实现virtual sequencers?
virtual sequencer只不过是一个组件,它提供了一个位置和作用域来配置virtual sequence,并为virtual sequence所需的子序列提供句柄。virtual sequencer的代码非常简单,它在构建完所有组件之后(在buildphase()之后),通常通过在env中的connectphase()中设置虚拟sequencer中声明的子序列句柄,这些子句柄将通过配置数据库(uvm_config_db)进行指定。
例1-简单的virtual sequencer
例1是virtual sequencer的典型结构。在此示例中,用户定义的类名称是vsequencer。 可以看出virtual sequencer是从uvm_sequencer扩展的。 与普通sequencer不同,例1的virtual sequencer没有参数化事务类型,因为该sequencer将能够执行多种事务类型。 从uvm_sequencer基类扩展virtual sequencer而没有任何参数意味着virtual sequencer将使用uvm_sequence_item的默认参数化值。
virtual sequencerz中要声明sub-sequencer句柄。 在例1中,sub-sequencer句柄分别称为ahb_sqr和eth_sqr。 这两个子sequencer句柄将在end_of_elaboration_phase()期间由配置数据库中指定的值分配。
与用于连接UVM测试平台中大多数组件的事务级模型(TLM)连接不同,子sequencer句柄不是使用TLM connect()方法设置的,而是在env使用配置数据库指定的,虚拟sequencer的工作就是从配置数据库中提取那些句柄,并将它们分配给在虚拟sequencer中声明的两个句柄。 实际的子sequencer将在build_phase()中创建。 因此,它们的句柄只能由env在其connect_phase()中放入配置数据库。 因此,虚拟sequencer必须在下一phase检索它们:end_of_elaboration_phase()。
最后,vsequencer的例子包括所有UVM组件共有的典型new()构造函数。 从此示例可以看出,vsequencer只是sequencer和其他配置参数句柄的容器。 虚拟sequences假定在run_phase()中执行它之前已正确配置了虚拟sequencer。 那么,就可以通过其p_sequencer句柄访问虚拟sequencer中的这些配置参数。
6.1.简化virtual sequencer的实现
论文中[1]还提供了虚拟sequencer的简化版本。 简化版本不会从uvm_config_db中检索子序列句柄,因此不需要end_of_elaboration_phase()方法。在例2所示vsequencer的简化版本中,仍然声明了子sequencer句柄,但是代替虚拟sequencer从uvm_config_db中检索句柄,需要做的工作是将子sequencer句柄从实例化agents复制到在vsequencer中声明的句柄。
例2-简化virtual sequencer代码
与简化的vsequencer一起使用的env将在后面章节中展示。例1的vsequencer的优点是提取了子序列句柄,该子句柄将存储在uvm_config_db中,这可能会使例1的vsequencer易于在不同的层次结构中重用,但是例2中的vsequencer更简单,对于平台环境而言,将子序列句柄直接复制到简单的vsequencer中并不困难。 作者推荐使用vsequencer的简化版本,避免添加额外的end_of_elaboration_phase()方法。
7.Sequences细节
sequence在sequencer上运行,并被参数化为该sequencer处理的事务类型。
我们可以使用内置的sequence start()方法或使用`uvm_do()宏在sequencer上启动sequence。
每个sequence都有运行该sequence的sequencer句柄。 该句柄就是m_sequencer。
8.m_sequencer句柄是什么?
所有sequence都在sequencer上启动,通过tr_seq.start(env.vsqr)。当然`uvm_do宏也执行了该命令。 在sequencer上启动sequence后,该sequence的m_sequencer句柄被设置为env.vsqr。 m_sequencer句柄只是每个sequence中指向运行该sequence的sequencer的句柄,它是在将start()方法传递sequencer的句柄(env.vsqr)时设置的。
就像其他sequence一样,当使用start()方法或`uvm_do宏在虚拟sequencer上启动虚拟sequence时,虚拟sequence将自动具有正确指向虚拟sequencer的m_sequencer句柄。
9.p_sequencer句柄是什么?
常见问题有:(a)什么是p_sequencer?(b)p_sequencer与m_sequencer有何不同?
所有sequence都具有m_sequencer句柄,但是sequence不会自动设置p_sequencer句柄。 此外,m_sequencer变量是一个内部实现的变量,不应由验证工程师直接使用。 它是SystemVerilog语言的构件,缺少C ++的“friend”类概念,该变量是公共变量。 类似地,任何带有“ m_”前缀的变量或方法都不应直接使用。
p_sequencer不会自动声明和设置,但可以使用`uvm_declare_p_sequencer宏进行声明和设置。 如本文后续章节展示,`uvm_declare_p_sequencer宏和p_sequencer句柄对用户的便利。
从技术上讲,从不需要p_sequencer句柄,但与`uvm_declare_p_sequencer宏一起使用时,会在启动虚拟sequence时自动(1)声明,(2)设置和(3)检查,并正确指向该virtual sequencer, 运行virtual sequence。
下面介绍有关p_sequencer句柄及其用法。
10.`uvm_declare_p_sequencer(SEQUENCER)宏是什么?
`uvm_declare_p_sequencer宏代码在src/macros/sequence_define.svh文件中有定义。
`uvm_declare_p_sequencer(SEQUENCER)宏执行了两个步骤:
(1)这个宏声明了SEQUENCER类型的p_sequencer句柄。
(2)然后将m_sequencer句柄$cast()到p_sequencer句柄,并检查确保执行此sequence的sequencer是合适的类型。
通常将此宏放在sequence基类中,该基类将被扩展以创建使用指定的sequencer的所有sequence,无论是否使用虚拟sequencer。
在第1行,用户调用此宏,并传递将由sequence使用的sequencer的类型。 对于虚拟sequence,这是将在其上执行的指定虚拟sequencer的类名。
在第2行,使用句柄名称p_sequencer声明指定的sequencer。 对于此宏以及用户定义的虚拟sequence基类和扩展虚拟sequence类中其他代码的部分,虚拟sequencer将使用名称p_sequencer进行引用。 从现在开始,无需引用正在使用的虚拟sequencer的名称,用户只需引用p_sequencer(virtual sequencer)句柄即可。 这仅仅是一种便利,而非必要。
第3行是从第9行开始的虚拟void函数声明的开始。该void函数称为m_set_p_sequencer,当在其中一个虚拟sequence上调用sequence的start()方法或使用`uvm_do_on( )宏启动虚拟sequence时该方法会被调用。
第4行是如果virtual sequence是另一个virtual sequence的扩展,则父类的virtual sequence还将执行其自己的m_set_p_sequencer方法。
第5行是强制转换内部m_sequencer句柄,该句柄应该是虚拟sequencer的句柄在第2行声明的本地p_sequencer句柄。if-test检查$cast()操作是否失败(!$cast(...)) 如果$cast确实失败了,则第6-8行上的fatal消息将终止UVM仿真并报告,说明在转换为指定的虚拟sequencer类型时存在问题,即sequence正在错误的类型的sequencer上执行 。
11.virtual sequencer testbench的例子
本文将介绍在图4的virtual sequencer测试平台上运行virtual sequences的示例。
该tb的基本构成将在以下章节详细说明。
12.Virtual sequence base classes
所有virtual sequences都需要访问virtual sequencer中定义的子sequencer句柄。 要访问子sequencer句柄,虚拟sequence需要使用`uvm_declare_p_sequencer宏来声明和设置p_sequencer变量,以便可以访问子sequencer句柄。
由于每个虚拟sequence都需要执行这些步骤,因此建议将该代码放入虚拟sequence基类(vseq_base),然后通过扩展vseq_base类来创建所有的虚拟sequence。
13. Example vseq_base
对于示例1中所示的虚拟sequencer,我们可以使用示例3中所示的vseq_base定义。
vseq_base类使用`uvm_declare_p_sequencer(vsequencer)宏来声明vsequencer类型的p_sequencer句柄。 然后,vseq_base声明与示例1中所示的虚拟sequencer中声明的类型相同的ahb_sqr和eth_sqr句柄。然后,vseq_base将虚拟sequencer (p_sequencer)ahb_sqr和eth_sqr句柄复制到本地ahb_sqr和eth_sqr句柄。 vseq_base类使用p_sequencer句柄(应该已经由`uvm_declare_p_sequencer宏正确分配了该句柄)将句柄从virtual sequencer复制到此vritual sequence类。 使用`uvm_declare_p_sequencer宏,vseq_base类不需要检查sequencer类的类型,因为宏内置了一个void函数来执行该检查。
14. Creating virtual sequences
创建了virtual sequence base class之后,就可以创建从虚拟sequence基类扩展的virtual sequence。 从虚拟sequence基类扩展的每个虚拟sequence都继承正确类型的subsequencer句柄,并且已经正确分配了该subsequncer句柄。
参考示例4和示例5中所示的两个虚拟sequence示例。这些sequences是虚拟sequence的示例,这些示例是示例3中所示的虚拟序列基类的扩展。
在UVM中执行序列有两种常用的方法:(1)使用`uvm_do宏,通常这是最容易使用的宏,但仿真效率也较低(因为子序列在执行前总是要被分配和随机化),而且更难于理解用户是否曾经扩展过`uvm_do宏代码,(2)使用显式分配,并在使用start()方法所选subsequencer上执行序列之前直接分配或调用randomize(), 一般认为这需要更多的用户编码工作,但是这种方式很直接并且允许创建和执行更多的定向sequences。
示例4虚拟序列使用`uvm_do宏运行一个虚拟序列,以随机生成伪AHB数据包,然后生成两个伪以太网数据包序列,并以另一个伪AHB数据包序列结束。 下面章节将展示伪以太网和AHB事务的代码,以太网和AHB序列以及运行v_seq1的测试用例。
示例5虚拟序列使用对randomize()和start()方法的调用来运行虚拟sequence,随机生成伪AHB数据包,然后生成两个伪以太网数据包序列,并以另一个伪AHB数据包序列结束。 下面章节将展示伪以太网和AHB数据包的代码,以及以太网和AHB序列以及运行v_seq2的测试。
15. Calling sequences from virtual sequences
虚拟序列的一个重要特征是它们可以运行现有序列无需修改。 用户可以创建用于测试各个子模块的序列库,然后使用原始的子模块序列库在协调的虚拟序列中使用一样的序列来测试多个子模块。 无需重新编码或修改子块序列。
参考示例6所示的伪AHB数据包代码。这是一个“伪AHB数据包”,因为它仅包含两个非标准AHB字段,并且示例中将识别何时将这些字段发送到DUT并将该信息打印出来。
参考示例7所示的AHB序列代码。这个简单的AHB序列会随机生成2-5个AHB数据包。 示例4和示例5中的虚拟序列将此随机生成的AHB数据包发送到在vsequencer component中声明的ahb_sqr句柄。
参考示例8所示的伪以太网数据包代码。这是一个“伪以太网数据包”,因为它仅包含两个非标准的以太网字段,并且示例中将识别何时将这些字段发送到DUT并进行打印。
参考示例9所示的以太网sequence代码。这个简单的以太网sequence随机生成2-4个以太网数据包。示例4和示例5中的虚拟sequence将随机生成的以太网数据包发送到vsequencer component中所声明的eth_sqr句柄。
16. Starting virtual sequences
通常,虚拟sequence是使用sequence的start()方法从测试用例中启动的。 不能从测试用例component中调用`uvm_do_on宏,仅能从派生的sequences调用`uvm_do_on宏。 这些判断起来有些棘手,但是即使从一个测试用例的有效virtual sequencer句柄上调用了`uvm_do_on,`uvm_do_on宏也会调用在uvm/src/seq/uvm_sequence_base.svh文件中的uvm_sequence_base类。 必需的方法是create_item(),start_item()和finish_item(),而uvm_test基类或任何其他uvm_component类或派生类中没有这些方法。
比较好的方法是创建一个带有通用声明和方法的test_base类,该类将由验证环境中的所有其他测试用例使用。例10中所示的test_base类声明了env句柄,并在build_phase()中创建了env。在扩展此test_base类的测试用例中,无需重复这些操作。该test_base还包括一个start_of_simulation_phase(),用于在run_phase()中执行仿真之前打印测试平台结构和工厂重载内容。在start_of_simulation_phase()中打印测试台结构和工厂内容很有用,因为故障通常出现在run_phase()中,因此这些预运行打印输出可以帮助诊断是否有任何组件构造错误或是否省略了某些来自工厂的测试平台类。
对test_base类进行编码后,每个测试都可以扩展test_base以创建各个测试用例。 示例11显示了从test_base类扩展的test1类定义。 在test1示例中为此测试用例定义了run_phase(),这里声明了第一个虚拟序列(v_seq1)句柄vseq,并创建了vseq对象。 然后测试用例将调用raise_objection()方法,打印一条消息,在vseq序列上调用start()方法,并将环境虚拟sequencer路径(e.v_sqr)传递给start()方法。 虚拟序列完成后,测试将再打印一条消息,然后调用drop_objection()方法结束。
示例12中的test2代码与示例11中的test1代码具有相同的功能,不同之处在于test2代码将vseq句柄声明为第二个虚拟序列类型v_seq2。
17. The environment sets the handles in the virtual sequencer
示例13中所示的环境代码是非常典型的环境代码,不同之处在于它声明了一个虚拟sequencer句柄(vsequencer v_sqr),构建了虚拟sequencer并存储了以太网(eth_agnt)和AHB(ahb_agnt)sequencer句柄(sqr和sqr )在配置数据库中。如“如何实现虚拟sequencer”一节中所述。虚拟sequencer句柄由环境在connect_phase()中存储在配置数据库中,以供虚拟sequencer在end_of_elaboration()阶段进行检索,这与使用连接大多数测试平台组件的TLM连接相反。
17.1 Simplified environment implementation
作者还使用了虚拟sequencer的简化版本,在第6.1节所述。简化版本不会从uvm_config_db中检索子序列句柄,因此不需要end_of_elaboration_phase()代码。
在示例2中所示的vsequencer的简化版本中,仍然声明了subsequencer句柄,但不是虚拟sequencer从uvm_config_db检索句柄,而是将subsequencer句柄从实例化的agents复制到在此vsequencer中声明的句柄,如下所示,在示例14所示的env的connect_phase()中。
作者通常使用vsequencer和env的简化版本,以避免将额外的end_of_elaboration_phase()方法添加到vsequencer中。
本文介绍了创建有效的virtual sequencer环境的必要步骤,并说明了m_sequencer和p_sequencer句柄以及`uvm_declare_p_sequencer宏的用途。
后续提供本文描述的示例所需的可执行原代码。
如果文章对您有帮助,欢迎大家多多双击关注~~