为便于我们创建验证激励,SystemVerilog类和随机约束提供了非常强大的机制。为了在一个约束对象中对约束分层,SystemVerilog也提供了几种机制,约束可以通过派生类的继承添加。内联约束(即randomize with {…}或`uvm_do_with)允许在随机化一个对象时,指定特殊的附加约束。不幸的是,在随后的“randomize with”调用中,SystemVerilog并没有提供提供一些较好的方法保存这些内联约束,使其便于复用。
文提供了一种方法,把约束封装进可复用的随机化“策略”类中,并在随机化另外一个类的时候,使用其中的一种或者多种策略去声明约束。不同的测试测试场景可以采用这些策略规则的不同组合。这种方法适用于SystemVerilog或者诸如UVM的验证方法。
约束分层场景
考虑到多处理器存储系统的激励,事务地址中有几类比较典型也比较值得关注的约束类型:
- 限制系统存储器映射的地址范围,在仿真过程中,这些范围可能会动态产生。
- 避免保留区域的地址(例如用于testbench 控制的“magic”地址)
- 为从数据缓存中回收而对地址进行约束,这就需要持续追踪哪些地址正在缓存中,并产生不在缓存中的地址。
理论上,可以用合适的控制功能把这些约束类包含进一个transaction基类中,或者使用继承来添加这些约束。但是实际中,你很难预测test case到底想要怎样的约束,所以写test case的工程师会把约束写进新的派生类中。
使用内联约束 (randomize with{} 或 `uvm_do_with)对于相对简单的约束(如transaction的数目、简单的分布等)是可行的。但是对于复杂的约束类型,如果还使用这种方法的话,就比较难处理了。你需要精心地迭代(如foreach)约束或者在post_randomize方法中更新历史信息。
现有的约束分层技术
为说明我们提出的约束方法,我们从一个简单的transaction(addr_txn)开始分析,
这个transaction包括地址和数据大小(字节数)。从基类中我们派生得到read/write transaction (rw_txn):
我们可以在派生类中添加约束,对地址进行约束:
虽然这两种技术都可用,但是我们需要更多的约束时,派生类和内嵌的约束就变得非常难以管理了。很容易会想到,如果有一种方法把约束封装进一个可复用的模块就好了。
分层约束容器
SystemVerilog提供了层次化的约束机制。一个类可以声明一个对象成员(即类的实例)为“rand”类型,当顶层对象随机化后,底层的对象也会被随机化。顶层和底层的随机变量和约束同时定义生成。
采用这种机制来解决我们的问题,我们把一组约束移入单独的类中,并在base transaction中,为带约束的类声明随机的类句柄。这时,当顶层的transaction随机化时,子类实例也同时被随机化。然而这时我们有一个问题要解决:约束类有了它们自己独立的“addr” 和“size” ,它们是独立随机化的,而我们需要保证他们有相同的值。解决这个问题的一个方法是,在顶层添加额外的“等式约束”,以确保每个类中的“addr”和“size”成员有相同的值。这种方式当然是可行的,但是我们需要顶层约束知道哪种里层约束正在被使用。我们展示一种更好的解决方式。
在顶层实现多约束类还是比较容易的,具体可以从一个普通基类中派生所有的约束类,或者使用队列来包含任意数量的约束类。使用foreach来约束“addr”和“size”成员,可以在任意数量的约束容器中添加等式约束。
但是,保持顶层等式约束和约束对象同步是一个苦差事,例如,如果我们想要使用rw_txn里的“op”成员(READ 或 WRITE)添加一个新约束,我们需要同步顶层约束,使其支持新的成员。即使一个约束类中没有对某个顶层成员进行约束,约束类仍要对其进行声明,这样顶层等式约束才能有效。
消除顶层等式约束
为消除顶层等式约束,我们可以这样做,在每一个约束类中声明一个对象的句柄,这个句柄将包含要被随机化的顶层对象的索引,这样我们就可以访问所有的顶层对象成员,具体见图六。约束基类(addr_constraint_base)包含一个和顶层对象(addr_txn)一样的变量“item”。
另外,在随机化之前,还需要设置“item”变量指向顶层对象。如下所示,在pre_randomize方法中进行实现。
随机策略类
在我们的实例中,约束类还有一个硬编码类型。通过定义参数化基类,可以使随机化更通用一些。这个参数化基类带有指示被随机化的顶层对象的类型参数。我们还为设置item句柄而添加了set_item函数,我们称这种新的容器类型为“随机化策略”
把多个随机策略和单个对象捆绑在一起也不太难,我们可以创建一个包含了策略队列的policy_list类,set_item方法要为列表中所有的策略设置item句柄。所以,这就允许调用顶层set_item方法,递归设置item句柄。使用这种方法,我们可以把所有值得关注的策略放在一起,并通过一次赋值传送这些策略。
这些策略列表可以装载任意层次的约束,例如,我们可能想要把默认的地址映射规则(即允许和禁止地址),和一个默认的策略对象捆绑,随后将其添加到更高层次的策略对象里。
应用
既然我们已经有了合适的方式封装约束,我们就可以创建一些较为通用的约束策略了,我们关于地址范围的例子目前已经有了硬编码地址范围,我们可以创建可配置的许可/禁止地址策略:
在UVM中的应用
这些技术可以很容易应用于UVM环境。我们只需要从uvm_sequence_item得到我们现有的事务类,并在随机化sequence_item之前,在uvm_sequence中设置随机化策略。将原本一步的`uvm_do拆成这样三个步骤:`uvm_create、设置约束策略、和`uvm_rand_send。
为了简化这个过程,我们可以创建一个新的宏:“uvm_do_with_policy”。这个宏结合上文所述的三个步骤,并允许把策略对象作为一个附加的宏声明传递。
另一种可行的方式是使用UVM配置数据库。顶层序列将默认策略对象嵌入config_db中。
写入config_db时,我们使用顶层序列完整的层次名,并加上一个 “.*”通配符。从config_db读取数据时,我们使用sequence_item的完整的层次名。这种方案可以让我们为序列顶层的所有item设置一个默认策略,如果要覆盖默认策略,只需要添加有更详细路径的附加config_db语句。
结果
对本文提到的技术使用简单的UVM环境进行验证,每种技术方法随机化一个transaction 10000次,总运行时间从testbench 内部测得。为了更科学,数据从三大SystemVerilog仿真器中的两个仿真器收集。