第六章 随机化

6.1. 介绍

随着设计规模的扩大,创造一套完整的刺激来检查它们的功能变得越来越困难。您可以编写一个定向测试用例来检查一组特定的特性,但是当每个项目的特性数量不断增加时,您无法编写足够的定向测试用例。更糟糕的是,所有这些功能之间的交互是最严重的漏洞的来源,并且是最不可能通过一大堆功能被发现的。

解决方案是使用受限随机测试(CRT)自动创建测试用例。定向测试可以找到你认为存在的bug,而CRT则可以通过随机刺激找到你从未想过的bug。您可以通过使用约束将测试场景限制为既有效又有趣的场景。

为CRT创建环境比为定向测试创建环境需要更多的工作。一个简单的定向测试只是应用刺激,然后你手动检查结果。这些结果被捕获为一个黄金日志文件,并与未来的模拟进行比较,以查看测试是通过还是失败。CRT需要一个环境来预测结果,使用参考模型、传递函数或其他技术,再加上功能覆盖来衡量刺激的有效性。然而,一旦该环境就位,您就可以运行数百个测试,而不必手工检查结果,从而提高了生产率。这种测试编写时间(你的工作)与CPU时间(机器工作)的权衡使得CRT如此有价值。

CRT由两部分组成:使用一个随机值流创建DUT输入的测试代码,以及伪随机数生成器(PRNG)的种子,见本章末尾的6.16.1节。你可以通过使用一个新的种子使一个CRT表现不同。该特性允许您利用每个测试,因此每个测试都是许多定向测试的功能对等物,只需更改种子即可。与直接测试相比,使用这些技术可以创建更多的等效测试。

你可能会觉得这些随机测试就像扔飞镖。您如何知道您已经涵盖了设计的所有方面?刺激空间太大,无法产生所有可能的输入,所以你需要生成一个有用的子集。在第9章中,您将学习如何通过使用功能覆盖率来度量验证进度。

使用随机化的方法有很多,本章给出了很多例子。它强调最有用的技巧,但是选择最适合你的。

6.2. 如何随机

当你考虑随机化设计刺激时,你首先想到的可能是数据字段。这些是最容易创建的-只需调用$random。问题是,这种方法在发现bug方面的回报非常低:您只能发现数据路径bug,可能还有位级错误。这种测试仍然是固有的。具有挑战性的错误在控制逻辑中。因此,您需要随机化DUT中的所有决策点。只要控制路径出现分歧,随机化就会增加你在每个测试案例中选择不同路径的可能性。

您需要广泛地考虑所有的设计输入,如以下项目。

  • 设备配置

  • 环境配置

  • 主要输入数据

  • 封装的输入数据

  • 协议异常

  • 延迟

  • 交易状态

  • 错误和违规

6.2.1 设备配置

在测试RTL设计时遗漏bug最常见的原因是什么?没有尝试足够的不同配置!大多数测试只是在重新设置后使用设计,或者应用一组固定的初始化向量将其置于已知状态。这就像在PC安装完之后测试它的操作系统,而没有任何应用程序;当然,性能很好,也没有崩溃。

随着时间的推移,在真实的环境中,DUT的配置变得越来越随机。在一个真实的例子中,一个验证工程师必须验证一个有600个输入通道和12个输出通道的timedivision多路复用开关。当设备安装在终端客户的系统中时,通道将被一次又一次地分配和回收。在任何时候,几乎没有

相邻通道之间的相关性。换句话说,构型看起来是随机的。

为了测试这个设备,验证工程师必须编写几十行Tcl代码来配置每个通道。因此,她永远无法尝试使用超过少数几个通道的配置。使用CRT方法,她编写了一个测试平台,将单个通道的参数随机化,然后将其放入循环中来配置整个设备。现在她有信心,她的测试将发现以前可能被遗漏的错误。

6.2.2 环境配置

您正在设计的设备在包含其他设备的环境中运行。在验证DUT时,它连接到一个模拟此环境的测试平台。您应该随机化整个环境,包括对象的数量和它们的配置方式。

另一家公司正在创建连接多个总线到内部内存总线的PCI交换机。在模拟开始时,客户使用随机选择PCI总线的数量(1-4)、每个总线上的设备数量(1-8)以及每个设备的参数(主或从、CSR地址等)。尽管有很多可能的组合,这家公司知道所有的都已经被覆盖了。

6.2.3 主要输入数据

在阅读有关随机刺激的内容时,您可能首先想到的是:取一个事务,比如总线写入或ATM单元,然后用一些随机值填充它。这能有多难呢?实际上,只要您仔细准备事务类,这是相当简单的。您应该预料到任何分层协议和错误注入。

6.2.4 封装的输入数据

许多设备处理多层次的刺激。例如,一个设备可能创建TCP流量,然后在IP协议中进行编码,最后以以太网数据包的形式发送出去。每个关卡都有自己的控制字段,可以随机化以尝试新的组合。所以你是随机的数据和围绕它的层。您需要编写创建有效控制字段但也允许注入错误的约束。

6.2.5 协议异常、错误和违反

任何可能出错的事,最终都会出错的。设计和验证中最具挑战性的部分是如何处理系统中的错误。您需要预测所有可能出错的情况,将它们注入到系统中,并确保设计能够优雅地处理它们,而不会锁定或进入非法状态。一个优秀的验证工程师会在功能规范的边缘,有时甚至超出功能规范的范围来测试设计的行为。

当两个设备通信时,如果传输中途停止会发生什么?您的测试台可以模拟这些中断吗?如果存在错误检测和纠正字段,则必须确保尝试所有组合。

这些错误的随机部分是,您的testbench应该能够发送功能正确的刺激,然后,通过反转配置位,开始以随机间隔注入随机类型的错误。

6.2.6 延迟

许多通信协议规定了延迟的范围。总线授予在请求之后进行一到三个周期。来自内存的数据在第四到第十总线周期有效。然而,许多针对最快的模拟进行了优化的定向测试使用了最短的延迟,只有一个测试只尝试各种延迟。在每次测试中,您的测试平台应该总是使用随机的、合法的延迟,试图找到(有希望的)暴露设计缺陷的组合。

在周期水平以下,有些设计对时钟抖动很敏感。通过少量地来回滑动时钟边缘,您可以确保您的设计对时钟周期的微小变化不会过度敏感。

时钟生成器应该位于testbench之外的模块中,以便它在活动区域中创建事件以及其他设计事件。然而,生成器应该具有一些参数,例如频率和偏移量,这些参数可以由测试台在配置阶段设置。

请注意,本书中描述的方法是用于查找功能错误,而不是时间错误。你的受限的随机测试工作台不应该完全违背设置和保持需求。使用时间分析工具可以更好地验证这些。

6.3. 随机化在SystemVerilog

SystemVerilog中的随机刺激生成在与OOP一起使用时最有用。首先创建一个类来保存一组相关的随机变量,然后让随机求解器用随机值填充它们。您可以创建约束来将随机值限制为合法值,或者测试特定的特性。

您可以随机化单个变量,但这种情况是最不有趣的。真正的约束随机刺激是在事务级别创建的,而不是一次一个值。

6.3.1 带有随机变量的简单类

样例6.1显示了一个带有随机变量和约束的包类,以及构造并随机化包的测试bench代码。

示例6.1简单随机类

这个类有四个随机变量。前三种方法使用rand修饰符,这样每次对类进行随机化时,变量都会被赋值。想象一下掷骰子,每次掷骰子都可以是一个新值或重复当前值。kind变量为randc,即随机循环,因此随机求解器不会重复一个随机值,直到每个可能的值都被分配。想象一下从一副牌中发牌,你以随机顺序发牌,然后洗牌,再以不同的顺序发牌。注意,循环模式是针对单个变量的。具有5个元素的randc数组有5个不同的模式,比如并行处理的5副牌。模拟器只需要实现具有256个不同值的8位宽的randc变量,但是大多数都支持更大的范围。

约束只是一组关系表达式,对于所选择的变量值必须为真。在本例中,src变量必须大于10且小于15。注意约束表达式是用花括号:{}分组的。这是因为这段代码是声明性的,而不是过程性的,它使用了begin…end。

如果发现约束问题,randomize函数返回0。代码检查结果,如果有问题,则使用$finish停止模拟。或者,您可能希望在完成一些内部事务(如打印摘要报告)之后,调用一个特殊的例程来结束模拟。本书的其余部分使用了一个宏而不是这些额外的代码。

不应该在类构造函数中随机化对象。您的测试可能需要打开或关闭约束,更改权重,甚至在随机化之前添加新的约束。构造函数是用来初始化对象的变量的,如果在这个早期阶段调用randomize,那么最终可能会丢弃结果。

类中的变量应该是随机的和公共的。这使你的测试对DUT的刺激和控制有最大的控制权。可以禁用变量的随机化,如第一部分所示

6.11.2。如果您忘记让一个变量是随机的,您必须编辑环境,这是您希望避免的。例外的是,像权重和限制这样的配置变量不应该是这样的

在事务类中随机,因为它们的值在模拟开始时被选择,并且不更改。

6.3.2 检查随机化结果

randomize函数将随机值赋给类中标记为rand或randc的任何变量,并确保遵守所有活动约束。如果你的代码有冲突的约束,随机化可能会失败(见下一节),所以你应该总是检查状态。如果不进行检查,这些变量可能会得到意想不到的值,从而导致模拟失败。

本书中其余的代码示例使用了样例6.2中的宏来检查随机化的结果。如果采用这种风格,您可以很容易地添加代码来给出有意义的错误消息并优雅地结束模拟。这个宏展示了几个编码技巧,包括将生成的代码包装在一个do…while状态中,这样就可以像使用以分号结束的普通语句一样使用它,包括在一个if-else语句中,VMM日志宏做得对,但OVM不行。

样本6.2随机检查宏和示例

6.3.3 约束解算器

求解约束表达式的过程由SystemVerilog约束求解器处理。求解器选择满足约束条件的值。这些值来自SystemVerilog的PRNG,它以一个初始种子开始。如果您给SystemVerilog模拟器相同的种子和相同的测试台,它应该总是产生相同的结果。请注意,更改工具版本或开关(如debug级别)可能会更改结果。请参阅本章末尾的练习,了解如何指定初始种子。

求解器是特定于仿真厂商的,当在不同的模拟器上运行时,甚至在同一工具的不同版本上运行时,约束随机测试可能不会给出相同的结果。SystemVerilog标准指定了表达式的含义和所创建的合法值,但没有详细说明求解器操作的精确顺序。关于随机数生成器的更多细节,请参阅6.16节。

6.3.4 什么可以随机化?

SystemVerilog允许您随机化整变量,即包含一组简单位的变量。这包括两种状态和四种状态类型,尽管随机化只生成两种状态的值。你可以有整数,位向量等等。不能使用随机字符串或引用约束中的句柄。在LRM中还没有定义实变量的随机化。

6.4. 约束条件的细节

有用的刺激不仅仅是随机值——变量之间存在着关系。否则,可能需要很长时间来生成有趣的刺激值,或者刺激值可能包含非法的值。您可以使用包含一个或多个约束表达式的约束块在SystemVerilog中定义这些交互。SystemVerilog选择随机值,以便表达式为真。

每个表达式中至少有一个变量是随机的,可以是rand或randc。以下类别在随机化时是失败的,除非年龄恰好在正确的范围内。解决方案是在年龄之前添加修饰词rand或randc。

示例6.3没有随机变量的约束

randomize函数尝试给随机变量分配新值,并确保所有约束都得到满足。在Sample 6.3中,因为没有随机变量,所以randomize只是检查age值,看看它是否在约束c_teenager指定的范围内。除非变量在13:19的范围内,否则ran- domize会失败。虽然可以使用约束来检查非随机变量是否具有有效值,但可以使用assert或if语句。调试过程检查程序代码要比阅读来自随机求解器的错误消息容易得多。

6.4.1 约束的介绍

示例6.4显示了一个带有随机变量和约束的简单类。具体的构造将在下面的章节中解释。注意,在约束块中,您使用花括号{}将多个表达式组合在一起。begin…end关键字用于过程代码。

示例6.4约束随机类

6.4.2 简单的表达式

样例6.4展示了一个包含几个表达式的约束块。前两个控件控制len变量的值。如您所见,一个变量可以在多个表达式中使用。

一个表达式中最多只能有一个操作符,例如<、<=、==、>=或>。示例6.5显示了SystemVerilog的缺陷,它错误地尝试以固定顺序生成三个变量。

示例6.5糟糕的排序约束

示例6.6是由错误的排序约束导致的

示例6.6显示了结果,这不是我们想要的结果。示例6.5中的约束bad被分解为多个二进制关系表达式,从左到右依次为:((lo < med) < hi)。首先,计算表达式(lo < med),结果是0或1。那么hi被限制为大于结果。变量lo和med是随机的,但不受限制。示例6.7中显示了正确的约束。更多的例子,见Sutherland(2007)。

样例6.7约束变量以固定顺序排列

6.4.3 等价表达式

约束最常见的错误是试图在只能包含表达式的约束块中进行赋值。相反,使用等价操作符将一个随机dom变量设置为一个值,例如len==42。您可以构建复杂的

一个或多个随机变量之间的关系:addr_ mode * 4 + payload.size())。

6.4.4 加权分布

如果应用足够多的模式,在受限的随机刺激下,可能会发现DUT中的bug。然而,生成一个特定的极端情况可能需要很长时间。当评审功能覆盖结果时,查看是否生成了极端案例。如果不是,你可以使用一个加权分布来扭曲a中的刺激

特定的方向,从而加速发现bug。dist操作符允许你创建加权分布,这样某些值会比其他值更容易被选择。

dist操作符接受一组值和权重值,用:=或:/操作符分隔。值和权重可以是常量或变量。这些值可以是单个值,也可以是一个范围,如[lo:hi]。权重不是百分比,也不必加起来等于100。: =操作符指定的权重对范围内的每个指定值都相同,而:/操作符指定的权重在所有值之间平均分配。

样本8。8加权随机分布

在示例6.8中,src获得值0、1、2或3。0的权重是40,而1、2、3的权重都是60,总共是220。选0的概率是40/220,选1 2 3的概率都是60/220。

接下来,dst将获得值0、1、2或3。0的权重是40,而1、2和3的总权重是60,总共是100。选0的概率是40/100,选1 2 3的概率都是20/100。

同样,值和权重可以是常量或变量。您可以使用可变权重来动态更改分布,甚至通过将权重设置为0来消除选项,如示例6.9所示。

样例6.9动态改变分布权重

在示例6.9中,len枚举变量有三个值。使用默认的权重值,长字长度会被更频繁地选择,因为w_lwrd的值最大。不要担心,您可以在模拟过程中动态更改权重,以获得不同的分布。

6.4.5 集合成员和内部操作符

可以使用inside操作符创建一组值。SystemVerilog求解器以相等的概率在集合中的值之间进行选择,除非对该变量有其他约束。一如既往,您可以在集合中使用变量。

样本6.10随机值集

在示例6.10中,SystemVerilog使用lo和hi的值来确定可能值的范围。您可以使用这些变量作为约束的参数,这样testbench就可以在不重写约束的情况下改变刺激生成器的行为。注意,如果lo > hi,则形成一个空集合,约束失败。

如果你想要任何值,只要它不在集合中,就用not操作符反转约束:!如示例6.11所示。

样本6.11倒置随机集约束

6.4.6 在集合中使用数组

样例6.12展示了如何通过将一组值存储在数组中来进行选择。

示例6.12数组的随机集约束

这被扩展到示例6.13中的约束中。

样例6.13等价约束集

同样,您可以使用NOT操作符告诉SystemVerilog选择除示例6.14中所示数组中的值以外的任何值。

选择数组之外的任何值

总是确保你的约束如你所期望的那样工作。您可以创建功能性覆盖组并生成报告,或者使用样例6.15中的代码打印一个值的直方图,输出样例6.16中的结果。

示例6.15打印直方图

样本6.16内部约束直方图

样例6.17和6.18从枚举的值列表中选择一周中的哪一天。你可以在飞行中更改选项列表。如果您选择一个randc变量,模拟器会在重复之前尝试每一个可能的值。

示例6.17类从一个数组中选择可能的值

样例6.18从值数组中选择

name函数返回一个包含枚举值名称的字符串。

如果要动态地从集合中添加或删除值,在使用inside操作符之前要考虑到它的性能。也许你有一组价值观,你只想选择一次。您可以使用inside从队列中选择值,然后删除它们,从而缓慢地收缩队列。这要求求解器解出N个约束条件,其中N是剩下的元素数

队列。相反,使用一个randc变量作为选项数组的索引,如示例6.19和6.20所示。选择一个randc值只需要很短的常量时间,而解决大量的约束则需要花费更多的时间,特别是当数组中有几十个元素时。

使用randc以随机顺序选择数组值

Sample 6.20 Testbench for randc choosing array values in random order

注意约束和例程可以以任何顺序混合。

6.4.7 双向约束

现在你可能已经意识到约束块不是过程代码,exe-从上到下切割。它们是声明性代码,同时都是活动的。如果使用set[10:50]的inside操作符约束一个变量,并且使用另一个表达式约束该变量大于20,SystemVerilog将同时解决这两个约束,并且只选择21到50之间的值。SystemVerilog约束是双向求解的,这意味着所有随机变量的约束都是并发求解的。对任何一个变量添加或删除一个约束将影响为所有相关变量选择的值

直接或间接。考虑样例6.21中的约束。

示例6.21双向约束

SystemVerilog求解器同时查看所有四个约束。变量r必须小于t, t必须小于10。但是,r也必须等于s, s大于5。即使没有直接的限制

t越小,s的约束就会限制选择。表6.1显示了这三个变量的可能值。

表6.1双向约束的解决方案

解决方案 r 年代 t
一个 6 6 7
B 6 6 8
C 6 6 9
D 7 7 8
E 7 7 9
F 8 8 9

6.4.8 隐含的约束

通常,所有的约束表达式在一个块中都是活动的。如果您想让表达式只在某些时候激活,该怎么办?设置最高地址,但只针对IO空间模式。SystemVerilog支持两个隐含操作符,->和if。

带有隐含算子的约束块

表达式A->B等价于表达式(!当隐含运算符出现在约束中时,求解器为A和B选择值,因此表达式为真。真值表6.2给出了A和B的逻辑值表达式的值。

表6.2隐含算子真值表

A - B >

B = false

B = true

一个= false

真正的

真正的

一个= true

真正的

当A为真时,B必须为真,但当A为假时,B可以为真或为假。请注意,这是部分双向约束,但a ->B并不意味着B-> a。这两种表达产生了不同的结果。

在样本6.23中,当d==1时,变量e必须为1,但当e==1时,d可以为

0或1。

示例6.23隐含运算符

如果添加约束{e==0;},变量d必须为0;但是,如果添加了一个约束{e==1;},则d的值不受约束,它仍然可以是0或1。

示例6.24展示了如何使用if隐含约束编写示例6.22。

样本6.24带有if隐含算子的约束块

if-else运算符是在多个表达式之间进行选择的一种很好的方法。例如,样例6.9中定义的总线可能支持字节、字和长字读,但只有像样例6.25那样编写时才支持长字写。

带有if-else操作符的约束块

if (A) B else C;等于两个约束(A && B);(!& & C);。示例6.26展示了如何将多个选项链接在一起。

带有多个if-else操作符的约束块

6.4.9 等价算子

等价运算符<->是双向的。A<−>B定义为((A->B) && (B->A))。表6.3是样本6.27中约束的A和B的逻辑值的真值表。

表6.3等价算子真值表A<->B B = false B = true = false 真正的 假

一个= true 假 真正的

样本6.27等价约束

当d为真时,e也必须为真,当d为假时,e也必须为假。所以这个运算符和逻辑上的XNOR是一样的。如果你从约束d<−>e开始,然后添加一个约束,比如d==1,那么解算器就会把e设为1。约束d<−>e和e==0导致d被求解器设置为0。如果你的类有三个约束,d<−>e, d==1,和e==0,求解器将不能选择d和e的值。

6.5. 解决方案的可能性

当您处理随机值时,您需要理解结果的概率。SystemVerilog不保证得到随机约束求解器找到的精确解决方案,但您可以影响分布。任何时候你

使用随机数时,你必须查看成千上万个或数百万个数值来平均掉噪音。一些模拟器,如Synopsys VCS,有多个解决方案,让你可以在内存使用和性能之间进行权衡。分布在不同的模拟器之间会有所不同。这些表是用Synopsys VCS 2011.03生成的。

6.5.1 无约束

从一个没有约束的类中的两个随机变量开始,如示例6.28所示。

示例6.28类Unconstrained

表6.4给出了八种可能的解决方案。因为没有限制条件,每一个都有相同的概率。你必须进行成千上万的随机操作,才能看到实际的结果接近列出的概率。

表6.4无约束类的解决方案

解决方案

x

y

概率

一个

0

0

1/8

B

0

1

1/8

C

0

2

1/8

D

0

3.

1/8

E

1

0

1/8

F

1

1

1/8

G

1

2

1/8

H

1

3.

1/8

6.5.2 含义

在示例6.29中,y的值取决于x的值。这是用以下约束中的隐含操作符表示的。这个示例和本节中的其他示例也以与if隐含操作符相同的方式表现。

带有隐含约束的示例6.29类

表6.5给出了可能的解决方案和概率。你可以看到,ran- dom求解器识别出x和y的8种组合,但所有x==0的解(解A-D)都被合并在一起。

表6.5 Imp1类的解决方案

解决方案 x y 概率

一个0

0

1/2

B0

1

0

C0

2

0

D0

3.

0

E1

0

1/8

F1

1

1/8

G1

2

1/8

H1

3.

1/8

6.5.3 隐含和双向约束

注意,隐含操作符说,当x==0时,y被强制为0,但当y==0时,对x没有约束。然而,隐含是双向的,因为如果y被强制为非零值,x必须为1。样例6.30有约束条件y>0,所以x不可能是0,表6.6给出了解决方案。

带有隐含约束和附加约束的示例6.30类

表6.6 Imp2类的解决方案

解决方案

x

y

概率

一个

0

0

0

B

0

1

0

C

0

2

0

D

0

3.

0

E

1

0

0

F

1

1

1/3

G

1

2

1/3

H

1

3.

1/3

  1. 之前用解决…来指导分配

您可以使用示例6.31中看到的“solve…before”约束来指导SystemVerilog求解器。

Sample 6.31 Class with implication and solve…before

约束前的解不改变解空间,只改变结果的概率。求解者以相等的概率选择x(0,1)的值。在1000次随机调用中,x约为0 500次,1约为500次。当x = 0时,y = 0。当x = 1时,y可以为0、1、2、3,概率相等,如表6.7所示。

表6.7解x前解y

约束

解决方案x

y

概率

一个0

0

1/2

B0

1

0

C0

2

0

D0

3.

0

E1

0

1/8

F1

1

1/8

G1

2

1/8

H1

3.

1/8

如果您在x之前使用约束解y,您会得到一个非常不同的分布,如表6.8所示。

表6.8解y在x之前

约束

解决方案x

y

概率

一个0

0

1/8

B0

1

0

C0

2

0

D0

3.

0

E1

0

1/8

F1

1

1/4

G1

2

1/4

H1

3.

1/4

如果你不满意某些值出现的频率,只使用solve…before。过度使用会减慢约束求解器的速度,并使他人难以理解您的约束。

对于示例6.31中的简单类,等价操作符<->给出了与隐含操作符->相同的解决方案。尝试添加额外的约束条件,并为您最喜欢的模拟器绘制结果。

6.6. 控制多个约束块

一个类可以包含多个约束块。一个块可以确保您有一个有效的事务,如6.7节所述,但是在测试DUT的错误处理时可能需要禁用它。或者,您可能希望为每个测试都有一个单独的约束。也许一个约束会限制数据长度以创建小事务(这对于测试拥塞很好),而另一个约束则会创建长事务。

可以使用constraint_mode函数打开或关闭约束。您可以使用handle控制单个约束。约束。constraint_mode (arg)。要控制对象中的所有约束,请使用handle。constraint_mode (arg),如示例6.32所示。当constraint_mode的参数为0时,约束被关闭,当参数为1时,约束被打开。

示例6.32使用constraint_mode

虽然许多小的限制可以给你更多的灵活性,但是打开和关闭它们的过程更加复杂。例如,当您关闭所有创建数据的约束时,您也将禁用所有检查数据有效性的约束。

如果你只是想让一个随机变量变得非随机,可以使用6.11.2节中描述的rand_mode。

6.7. 有效的约束

一个好的随机化技巧是创造一些约束来确保你的随机刺激的正确性,即所谓的“有效约束”。在样例6.33中,总线的read-modify-write命令只允许长字数据长度。

样例6.33使用有效的约束检查写长度

现在您知道总线事务遵守规则。稍后,如果你想违反规则,可以使用constraint_mode来关闭这个约束。当您想要生成错误时,可以使用constraint_mode关闭这些功能。例如,如果一个包的有效载荷长度为零会怎样?您应该有一个命名约定来突出这些约束,例如如上所示使用有效的前缀。

6.8. 内联约束

当您编写更多的测试时,您最终会得到许多约束。它们可以以意想不到的方式相互交互,并且启用和禁用它们的额外代码增加了测试的复杂性。此外,不断地向类添加和编辑约束可能会在团队环境中引起问题。

许多测试只在代码中的一个地方随机化对象。SystemVerilog允许您使用randomize with添加额外的约束。这相当于给现有的约束添加一个额外的约束。样例6.34显示了一个带有约束的基类,然后两个随机化with语句。

示例6.34 randomize() with语句

额外的约束被添加到现有的约束中。如果需要禁用冲突约束,请使用constraint_ mode。注意,在with{}语句中,SystemVerilog使用了类的作用域。这就是为什么样本6。34只使用了addr,而不是t。addr。

一个常见的错误是用圆括号而不是花括号{}来包围行内约束。请记住,约束块使用花括号,所以你的内联约束也必须使用它们。大括号用于声明性代码。

6.9. pre_randomize和post_randomize函数

有时你需要在每次randomize调用之前或之后立即执行一个操作。例如,您可能想要在随机化开始之前设置一些非随机类变量(例如限制或权重),或者您可能需要计算随机数据的错误纠正位。SystemVerilog允许您使用pre_randomize和post_randomize两个函数来实现这一点,这两个函数是在任何带有随机变量的类中自动创建的。

6.9.1 建造浴缸分布

对于某些应用,你想要一个非线性随机分布。例如,大小数据包更容易发现缓冲区溢出等设计错误

比中型包。所以你想要浴缸形状的分布;两头高,中间低。你可以创建一个精细的dist约束,但这可能需要大量调整才能得到你想要的形状。Verilog有几个用于非线性分布的函数,比如$ dist_index,但是没有用于普通分布的函数。图6.1中的图表显示了如何组合两条指数曲线来制作浴缸曲线。示例6.35中的pre_randomize方法计算指数曲线上的一个点,然后随机选择将其放在左曲线或右曲线上。当您在左右曲线上选择点时,您将逐渐构建一个组合值的分布。

图6.1建筑浴缸分布图

示例6.35构建浴缸分布

每次该对象被随机化时,变量值都会更新。通过许多随机化,您将看到所需的非线性分布。由于变量是按程序计算的,而不是通过随机约束求解器,所以它不需要rand修正器。

参见示例6.64了解post_randomize的另一个示例。

6.9.2 Void函数的注意事项

pre_randomize和post_randomize函数只能调用其他函数,而不能调用可能会消耗时间的任务。在调用randomize过程中不能有延迟。在调试随机化问题时,如果提前计划并将其设置为空函数,则可以调用显示例程。

第8章描述了高级面向对象的概念,包括扩展类和虚拟方法。pre_randomize和post_randomize函数不是虚函数,因此根据句柄的类型调用它们,而不是对象。此外,如果扩展类的pre_randomize或post_randomize需要基类的pre_randomize和post_randomize函数中的功能,它们应该使用super前缀调用这些方法,如super.pre_randomize。

6.10. 随机数函数

您可以使用所有Verilog-1995发行版函数,以及SystemVerilog的几个新函数。有关“dist”函数的更多细节,请查阅统计书籍。一些有用的函数包括以下内容。

  • $random -平面分布,返回有符号的32位随机

  • $urandom -平面分布,返回32位无符号随机

  • $urandom_range -在一个范围内平面分布

  • $ dist_index -指数衰减,如图6.1所示

  • $dist_normal -钟形分布

  • $dist_poisson -钟形分布

  • $dist_uniform -平面分发

$urandom_range函数接受两个参数,一个可选的低值和一个高值,如示例6.36所示。

示例6.36 $urandom范围的使用

6.11. 约束技巧和技术

如何创建易于修改的约束随机测试?你可以使用一些技巧。最常用的技术是使用OOP来扩展原始类,如第6.11.8和8.2.4节所述,但这需要更多的计划。所以,首先学习一些简单的技巧,但要对其他方法保持开放的心态。

6.11.1 约束和变量

本书中的大多数约束示例都使用了常量,以使它们更具可读性。在示例6.37中,length在一个使用变量作为上界的范围内随机化。

示例6.37带有变量边界的约束

默认情况下,该类会创建1到100之间的随机长度,但是通过更改变量max_length,可以更改上限。

你可以使用dist约束中的变量来打开和关闭值和范围。

在示例6.38中,每个总线命令都有一个不同的权重变量。

样本6.38变量权重的dist约束

默认情况下,这个约束以相同的概率生成每个命令。如果您希望拥有更多的READ8命令,请增加read8_wt权重变量。最重要的是,您可以通过将命令的权重降低到0来关闭命令的生成。

6.11.2 使用非随机值

如果你有一组约束,产生的刺激几乎是你想要的,但不完全是,你可以调用randomize,然后设置一个变量为你想要的值-你不必使用随机值。然而,根据您为检查有效性而创建的约束条件,您的刺激值可能不正确。

如果只是有一些随机变量你想要覆盖,使用rand_ mode函数使它们非随机。当您使用参数为0的随机变量调用此方法时,rand或randc限定符将被禁用,并且随机求解器不再更改变量的值,但如果值出现在约束中,则仍然检入。将随机模式设置为1将打开限定符,这样求解器就可以更改变量。

示例6.39 rand_mode禁用变量的随机化

在示例6.39中,包的大小以随机可变长度存储。测试的前半部分将长度变量和负载动态数组的内容随机化。后半部分调用rand_mode使length成为一个非随机变量,将其设置为42,然后调用randomize。约束将有效载荷大小设置为常数42,但是数组仍然充满了随机值。

6.11.3 使用约束检查值

如果你随机化一个对象,然后修改一些变量,你可以检查对象是否仍然有效,如果所有的约束仍然被遵守。调用处理。randomize(null)和SystemVerilog将所有变量视为非随机(“状态变量”),并只是确保满足所有约束,即。所有的表达式都是正确的。如果不满足任何约束,randomize函数返回0。

6.11.4 随机化个体变量

假设您想要在一个类中随机化几个变量。可以使用变量的子集调用ran- domize。只有那些传入参数列表的变量才会被随机化;其余的将被视为状态变量而不是随机的。所有的限制仍然有效。在示例6.40中,第一次调用randomize仅更改两个rand变量med和hi的值。第二次调用只更改med的值,而hi保留了它之前的值。令人惊讶的是,您可以传递一个非随机变量(如最后一次调用所示),只要low遵守约束,它就会得到一个随机值。

样本6.40随机化类中的变量子集

这种只随机化变量子集的技巧并不常用于真正的测试中,因为你限制了刺激的随机性。您希望您的测试平台探索法律价值的全部范围,而不仅仅是一些角落。

6.11.5 关闭和打开约束

第6.6和6.7节讨论了有效约束和constraint_mode。关闭单个约束对于错误生成是好的,但是应该适当使用。

6.11.6 使用内联约束在测试中指定约束

如果你一直向一个类添加约束,它就会变得很难管理和控制。很快,每个人都会从源代码控制系统中检出相同的文件。很多时候,一个约束只被一个测试使用,那么为什么它对每个测试都是可见的呢?本地化约束效果的一种方法是使用内联约束,随机化约束,见第6.8节。如果您的新约束是默认约束的附加约束,那么这将很好地工作。如果您遵循6.7节中的建议来创建“有效约束”,您可以快速地约束有效序列。对于错误注入,您可以禁用任何与您试图做的事情相冲突的约束。注入特定类型的损坏数据的测试将首先关闭检查该错误的特定有效性约束。

使用内联约束有几个折衷。首先,现在您的约束位于多个位置,这可能会使它更难理解所有的活动约束。如果您向原始类添加了一个新的约束,它可能会与内联约束冲突。第二个问题是,您很难跨多个测试重用内联约束。根据定义,内联约束只存在于一段代码中。您可以将它放在一个单独文件中的例程中,然后根据需要调用它。在这一点上,它几乎变成了一种外部约束。

6.11.7 在带有外部约束的测试中指定约束

约束的主体不必在类中定义,就像例程的主体可以在外部定义一样,如5.10节所示。数据类可以定义在一个文件中,其中包含一个空约束。然后,每个测试都可以定义这个约束的自己版本,以生成自己口味的刺激,如示例所示

6.41和6.42。

带有外部约束的示例6.41类

样例6.42定义外部约束的程序

外部约束比内联约束有几个优点。它们可以放在一个文件中,从而在测试之间重用。外部约束适用于类的所有实例,而内联约束仅影响随机化的单个调用。因此,外部约束提供了无需学习高级OOP技术就可以更改类的基本方法。请记住,使用这种技术,您只能添加约束,而不能改变现有的约束,并且您需要在原始类中定义外部约束原型。

与内联约束一样,外部约束也会导致问题,因为约束分布在多个文件中。LRM要求在与原始类相同的范围内定义外部约束。在包中定义的类必须在同一个包中定义它的外部约束,这限制了它的用途。这就是样例6.42包含类定义而不是使用包的原因。

最后要考虑的是,当外部约束的主体从未定义时,会发生什么。SystemVerilog LRM当前没有指定在这种情况下应该发生什么。在构建带有许多外部约束的测试台前,请了解模拟器如何处理缺失的定义。这是一个阻止模拟的错误,只是一个警告,还是根本没有消息?

6.11.8 扩展一个类

在第8章中,你将学习如何扩展一个类。使用这种技术,您可以使用一个使用给定类的testbench,并交换一个扩展类,该扩展类具有额外的或重新定义的约束、例程和变量。关于典型的测试工作台,请参阅示例8.10。请注意,如果在扩展类中定义的约束与基类中的约束同名,则扩展的约束将替换基类中的约束。

学习OOP技术需要更多的学习,但是这种新方法的灵活性带来了巨大的回报。

6.12. 常见的随机化问题

您可能熟悉过程代码,但编写约束条件和理解随机分布需要一种新的思维方式。以下是你在尝试创造随机刺激时可能会遇到的一些问题。

6.12.1 小心使用带符号的变量

在创建testbench时,您可能会倾向于使用int、byte或其他有符号类型的计数器和其他简单变量。不要在随机约束中使用它们,除非你真的想要有符号的值。当样本6。43中的类被随机化时产生了什么值?它有两个随机变量,希望它们的和是64。

样本6。43有符号变量导致随机化问题

显然,您可以获得(32,32)和(2,62)等成对的值。此外,你可以看到(−63,127),这是方程的合理解,尽管它可能不是你想要的。为了避免无谓的值,例如负长度,只使用无符号随机变量,如示例6.44所示。

样本6.44随机化32位无符号变量

即使是这个版本也会导致问题,因为pkt1_len和pkt2_len的大值(如32'h80000040和32'h80000000)在加在一起时会绕起来,得到32'd64或32'h40。您可能会考虑添加另一对约束来限制这两个变量的值,但最好的方法是根据需要将它们设置为适当的范围,并避免在约束中使用32位变量。在示例6.45中,将两个8位变量的和与一个9位值进行比较。

样本6.45随机无符号8位变量

6.12.2 解算器性能技巧

每个约束求解器都有自己的长处和弱点,但是你可以遵循一些指导方针来提高约束随机变量模拟的速度。工具总是在不断改进,所以请向您的供应商查询更具体的信息。

如果你只是需要用原始数据填充数组,不要使用求解器,因为它有一些选择值的开销,即使是一个没有约束的变量。不要将这些数组声明为rand,而是使用$urandom或$urandom_range计算pre_randomize中的值。这些函数计算值的速度比求解器快100倍,这在您需要快速计算1000个值时非常重要。通常,数组越大,单个值就越不重要,也就越不需要使用求解器。即使您需要一个不一致的值范围,或者值之间存在简单的关系,您也可以使用if语句。

6.12.3 选择合适的算术运算符来提高效率

求解器在约束条件下可以非常有效地处理简单的算术运算,如加减、位提取和移位。然而,乘法、除法和取模对于32位值来说是非常昂贵的。记住,任何没有显式大小的常量,比如42,都被视为32位值32'd42。

如果您想生成靠近页面边界的随机地址,其中一个页面是4096字节,您可以编写以下代码,但如果您使用示例6.46中的约束,求解器可能需要很长时间才能找到合适的addr值。

示例6.46使用mod和unsize变量的昂贵约束

硬件中的许多常量都是2的幂,所以要利用比特提取而不是除法和取模。只约束重要的位,而不是上面的位。同样地,2的乘方可以用移位代替。请注意,一些约束求解器会自动进行这些优化示例

6.47将MOD操作符替换为一个bit extract。

样例6.47带bit提取的有效约束

6.13. 迭代和数组约束

到目前为止提供的约束允许您指定对单个变量的限制。如果你想要随机化一个数组呢?foreach约束和几个数组函数允许您塑造值的分布。

使用foreach约束会创建许多会减慢模拟速度的约束。一个好的求解者可以快速地解决数百个约束条件,但可能会因为数千个约束条件而放慢速度。嵌套的foreach约束尤其慢,因为它们为大小为n的数组产生N2约束。请参阅6.13.5节,了解使用randc变量代替嵌套foreach的算法。

6.13.1 数组大小

最容易理解的数组约束是size函数。在样例6.48中,您将指定动态数组或队列中的元素数量。

样例6.48约束动态数组大小

使用inside约束可以设置数组大小的上下边界。在许多情况下,您可能不想要一个空数组,即size==0。记住要指定上限;否则,你可能会得到成千上万个元素,这可能会导致随机求解器花费过多的时间。

6.13.2 元素的总和

您可以将一个随机的数据数组发送到设计中,但是您也可以使用它来控制流。也许您有一个必须传输四个数据词的接口。字可以连续发送,也可以在多个周期内发送。频闪信号告诉数据信号何时有效。图6.2显示了一些合法的频闪模式,在10个周期内发送4个值。

图6.2随机频闪波形

您可以使用随机数组创建这些模式,如示例6.49所示。使用sum函数将其限制为在整个范围外启用四位。

样本6.49随机频闪模式类

正如你在第二章中所记得的,一个由单个位元素组成的数组的和通常是单个位,例如0或1。样本6.49比较了频闪仪。求和为一个4位值(4’h4),因此求和以4位精度计算。本例使用4位精度存储最大元素数,即10。

6.13.3 数组约束的问题

sum函数看起来很简单,但由于Verilog的算术规则,可能会导致一些问题。以下是一位作者经历过的一个简单问题——创造约束随机刺激。您希望生成1到8个事务,这样所有事务的总长度都小于1024字节。样本

6.50显示第一次尝试,6.51显示测试程序,6.52显示输出。的

len字段是原始事务中的一个字节。

示例6.50第一次尝试sum约束:bad_sum1

示例6.51程序尝试使用数组和约束

示例6.52 bad_sum1的输出

这会产生一些更小的长度,但总和有时是负的,总是小于127 -绝对不是你想要的!样例6.53显示了另一个尝试,但这次用无符号字段替换字节数据类型。显示功能不变。示例6.54显示了输出。

示例6.53秒尝试求和约束:bad_sum2

示例6.54 bad_sum2的输出

样本6.53有一个微妙的问题。所有事务长度的总和总是小于256,即使您将数组长度限制为小于1024。这里的问题是,在Verilog中,使用8位结果计算许多8位值的总和。样例6.55使用2.8节中的uint类型将len字段增加到32位。

示例6.55第三次尝试求和约束:bad_sum3

示例6.56 bad_sum3的输出

在样本6。56中发生了什么?这类似于第6.12.1节中的有符号问题,因为两个非常大的数的和可以绕成一个很小的数。您需要根据约束中的比较来限制大小。样本6.57和6.58显示了下一次尝试和结果。

示例6.57第四次尝试求和约束:bad_sum4

示例6.58 bad_sum4的输出

因为单个的len字段大于8位,所以len值通常大于255,所以这也行不通。您需要指定每个len字段在1到255之间,但是使用10位字段,以便它们的和正确。这需要约束数组中的每个元素,如下节所示。

6.13.4 约束单个数组和队列元素

SystemVerilog允许使用foreach约束数组的单个元素。虽然您可以通过列出每个元素来编写固定大小数组的约束,但foreach风格更紧凑。约束动态数组或队列的唯一实用方法是使用foreach,如示例6.59和6.60所示。

示例6.59简单foreach约束:good_sum5

示例6.60 good_sum5的输出

为单个元素添加约束修正了示例。注意,len数组可以是10位或10位以上,但必须是无符号的。

只要注意端点,就可以在数组元素之间指定约束。样例6.61中的类通过将每个元素与前一个元素进行比较(第一个元素除外)来创建一个升序值列表。

示例6.61使用foreach创建升序数组值

这些约束会变得多复杂?为了解决爱因斯坦的问题(一个由五个人组成的逻辑谜题,每个人都有五个独立的属性),八皇后问题(在国际象棋棋盘上放八个皇后,这样就没有人能抓到对方),甚至数独,已经编写了一些约束。

6.13.5 生成唯一值数组

如何创建一个随机唯一值数组?如果你的数组有N个元素,并且元素值的范围从0..N-1,您可以简单地使用2.6.3节介绍的array shuffle函数。

如果值的范围大于数组元素的数量怎么办?如果您尝试创建一个randc数组,每个数组元素都将被独立随机化,因此几乎可以肯定会得到重复的值。

您可能会倾向于使用一个约束求解器来比较嵌套foreach循环的每个元素,如示例6.62所示。这会产生超过4000个单独的约束,这可能会减慢模拟。

示例6.62使用foreach创建唯一数组值

相反,您应该使用示例6.63中所示的过程代码,并使用包含一个randc变量的helper类,这样您就可以反复随机化相同的变量。

示例6.63使用randc helper类创建唯一的数组值

示例6.64和6.65给出了更一般的解决方案。例如,您可能需要为N个总线驱动器分配ID号,它们的范围是0到MAX-1,其中MAX >=N。

示例6.64惟一值生成器

样例6.65类生成一个惟一值的随机数组

示例6.66有一个程序。下面是一个使用UniqueArray的程序

类。

示例6.66使用UniqueArray类

6.13.6 将句柄数组随机化

如果需要创建多个随机对象,可以创建一个句柄的随机数组。与整数数组不同,您需要在随机化之前分配所有元素,因为随机解算器永远不会构造对象。如果你有一个动态数组,分配你可能需要的最大数量的元素,然后使用一个约束来调整数组的大小,如示例6.67所示。在随机化过程中,句柄的动态数组可以保持相同的大小或缩小,但它的大小永远不会增加。

样例6.67构造随机数组类中的元素

以上代码适用于单个数组随机化。如果需要反复随机化同一个数组,那么分配数组并在pre_randomize中构造元素。有关句柄数组的更多信息,请参阅5.14.4节。

6.14. 原子刺激生成vs.场景生成

到目前为止,您已经看到了原子随机事务。您已经学习了如何进行单个随机总线事务、单个网络数据包或单个处理器指令。这是一个良好的开始,但是您的工作是验证设计是否有效

与现实世界的刺激。总线可能有很长的事务序列,如DMA传输或缓存填充。当您同时阅读电子邮件、浏览网页和从网上下载音乐时,网络流量由扩展的数据包序列组成,所有这些都是并行的。处理器有很深的管道,其中充满了用于例程调用、for循环和中断处理程序的代码。一次生成一个事务不太可能模拟这些场景。

6.14.1 一个有历史的原子发电机

创建相关事务流的最简单方法是让原子生成器基于以前事务的一些随机值。该类可以约束总线事务在80%的时间里重复前面的命令(比如写),还可以使用前面的目标地址加上增量。可以使用post_randomize函数复制生成的事务,以便下次调用randomize时使用。

这种方案在较小的情况下工作得很好,但当您需要提前了解整个序列的信息时,就会遇到麻烦。DUT在开始之前可能需要知道网络事务序列的长度。

6.14.2 对象随机数组

如果你想为一个复杂的、多层次的协议产生刺激,你可以建立一个代码和随机对象数组的组合。UVM和VMM都允许您通过一组复杂的类和宏生成随机序列。本节展示了一个简化的随机序列。

生成随机序列的一种方法是随机化整个对象数组。您可以创建引用数组中上一个和下一个对象的约束,并且SystemVerilog求解器同时解决所有约束。由于整个序列是一次生成的,因此您可以在发送第一个事务之前提取诸如事务总数或所有数据值的校验和之类的信息。或者,您可以为DMA传输构建一个严格限制为1024字节的序列,并让求解器选择正确的事务数量来达到这个目标。

示例6.68显示了一个简单的事务序列,每个事务的目标地址都大于前一个事务。它建立在示例6.61中所示的数组约束之上。

示例6.68带有升序值的简单随机序列

6.14.3 结合序列

您可以将多个序列组合在一起,以形成更真实的事务流。例如,对于网络设备,您可以制作一个类似于下载电子邮件的序列,第二个类似于查看web页面,第三个类似于在基于web的表单中输入单个字符。组合这些流的技术超出了本书的范围,但是您可以从VMM中了解更多,如Bergeron等人(2005)所述。

6.14.4 Randsequence

您可能会发现编写随机约束很有挑战性,因为它们不像过程语句那样按顺序执行。创建随机序列的另一种方法是使用声明式风格描述协议语法,使用类似于BNF (Backus-Naur形式)和随机加权case语句的语法。

SystemVerilog的randsequence构造类似于您传统上使用的算法代码,但仍然具有挑战性。

样例6.69生成一个名为stream的序列。流可以是cfg_ read、io_read或mem_read。随机序列引擎随机选择一个。cfg_read标签的权重为1,io_read的权重是cfg_read的两倍,因此被选中的可能性是cfg_read的两倍。标签mem_read最有可能被选中,其权重为5。

示例6.69命令生成器使用randsequence

cfg_read可以是对cfg_read_task的单个调用,也可以是对任务的调用,后面跟着另一个cfg_read。因此,该任务总是至少被调用一次,也可能被调用多次。

randsequence的一大优点是它是过程代码,您可以通过逐步执行或添加$display语句来调试它。当您为一个对象调用randomize时,它要么全部工作,要么全部失败,但您无法看到获得结果所采取的步骤。

使用randsequence有几个问题。生成序列的代码是独立的,与序列使用的带有数据和约束的类风格非常不同。因此,如果你同时使用randomize和randsequence,你就必须掌握两种不同的随机化形式。更严重的是,如果您想要修改一个序列,也许是添加一个新的分支或操作,您必须修改原始的序列代码。你不能只是打个分机。正如您将在第8章中看到的,您可以扩展一个类来添加新的代码、数据和约束,而不必编辑原来的类。

6.15. 随机控制

此时,你可能会认为这个过程是为你的设计创造一长串随机输入的好方法。或者,如果您想要做的只是偶尔在代码中做出随机决定,那么您可能会认为这是一项工作量很大的工作。您可能更喜欢使用一组可以使用调试器逐步执行的过程语句。

  1. 介绍randcase

您可以使用randcase在几个操作之间进行加权选择,而不必创建类和实例。示例6.70根据权重选择三个分支中的一个。SystemVerilog将权重相加(1+8+1 = 10),在这个范围内选择一个值,然后选择适当的分支。这些分支不依赖于顺序,权重可以是变量,它们的总和不必达到100%。函数$urandom_range在第6.10节中描述。

示例6.70随机控制与randcase和$urandom_range

您可以使用一个类和randomize函数编写示例6.70。对于这个小例子,示例6.71中的OOP版本要大一些。但是,如果这是一个更大的类的一部分,那么约束将比等效的randcase语句更紧凑。

示例6.71等效约束类

使用randcase的代码比随机约束更难覆盖和修改。修改随机结果的唯一方法是重写代码或使用可变权重。

使用randcase时要小心,因为它不会留下任何痕迹。例如,您可以使用它来决定是否在事务中注入错误。问题是下游的事务处理程序和记分牌需要知道这个选择。通知他们的最好方法是在事务或环境中使用变量。但是,如果您要创建一个变量,它是这些类的一部分,那么您可以使它成为一个随机变量,并使用约束在不同的测试中更改它的行为。

  1. 用randcase构建决策树

您可以使用randcase语句来创建决策树。样例6.72只有两层过程代码,但是您可以看到如何扩展它以使用更多的过程代码。

示例6.72使用randcase创建决策树

6.16. 随机数生成器

SystemVerilog有多随机?一方面,你的测试平台依赖于不相关的随机值流来创建超出任何定向测试的刺激模式。另一方面,您需要在特定测试的调试过程中反复重复这些模式,即使设计和测试工作台只做了很小的更改。

6.16.1 伪随机数生成器

Verilog使用一个简单的PRNG,您可以通过$random函数访问它。生成器有一个内部状态,您可以通过向$random提供种子来设置它。所有兼容ieee -1364的Verilog模拟器都使用相同的算法来计算值。

示例6.73显示了一个简单的PRNG,而不是SystemVerilog使用的PRNG。PRNG的状态为32位。要计算下一个随机值,将状态平方生成一个64位值,取中间的32位,然后加上原始值。

示例6.73简单伪随机数生成器

您可以看到,这段简单的代码是如何生成看似随机的值流的,但是可以通过使用相同的种子值来重复。SystemVerilog调用自己的PRNG来为randomize和randcase生成一个新值。

6.16.2 随机稳定性-多个发电机

Verilog有一个用于整个模拟的PRNG。如果SystemVerilog保持这种方法会发生什么?测试台上通常有几个刺激发生器并行运行,为被测试的设计创建数据。如果两个流共享相同的PRNG,它们每个都得到随机值的子集。

图6.3共享单个随机生成器

在图6.3中,有两个刺激发生器和一个PRNG产生值a、b、c等。Gen2有两个随机对象,因此在每个周期中,它使用的随机值是Gen1的两倍。

如图6.4所示,当其中一个类发生变化时,可能会出现问题。Gen1获得一个额外的随机变量,因此每次调用它时都会消耗两个随机值。这种方法不仅会更改第1代使用的值,还会更改第2代使用的值。

图6.4第一个发电机使用附加值

在SystemVerilog中,每个对象和线程都有一个单独的PRNG。图6.5显示了一个对象的变化如何不会影响其他对象看到的随机值。

图6.5每个对象独立的随机生成器

6.16.3 随机稳定性和分层播种

在SystemVerilog中,每个对象和线程都有自己的PRNG和唯一的种子。当一个新的对象或线程启动时,它的PRNG将从它的父对象或线程的PRNG中进行播种。因此,在模拟开始时指定的单个种子可以产生许多不同的随机刺激流。

在调试测试平台时,可以添加、删除和移动代码。即使具有随机稳定性,您的更改也可能导致testbench生成不同的随机dom值。如果您正在调试一个DUT故障,而testbench不再创建相同的刺激,那么这可能会非常令人沮丧。您可以通过在现有对象或线程之后添加任何新对象或线程来最小化代码修改的影响。样例6.74显示了一个来自testbench的例程,它构造了对象,并在并行线程中运行它们。

样例6.74修改前测试代码

样例6.75添加了一个新的生成器,并在一个新的线程中运行它。新对象是在现有对象之后构造的,新线程是在旧线程之后生成的。

示例6.75修改后的测试代码

随着新代码的添加,您可能无法保持随机流与旧流相同,但您可能能够推迟这些更改带来的任何副作用。

6.17. 随机设备配置

测试DUT的一个重要部分是内部DUT设置和围绕它的系统的配置。如第6.2.1节所述,您的测试应该随机化环境,这样您就可以确信它已经在尽可能多的模式下进行了测试。

样例6.76显示了一个可以在测试级别上根据需要修改的随机测试工作台配置。EthCfg类描述了4端口以太网交换机的配置。它在一个环境类中实例化,这个环境类反过来在测试中使用。测试覆盖其中一个配置值,启用所有4个端口。

示例6.76以太网交换机配置类

configuration类在环境类的几个阶段中使用。配置是在环境构造函数中构造的,但在gen_cfg阶段(如示例6.77所示)之前不是随机的。这允许您在调用randomize之前打开和关闭约束。然后,您可以在构建阶段围绕DUT创建虚拟组件之前覆盖生成的值。(EthGen和EthMii等类没有显示)。

样本6.77随机配置的建筑环境

现在您已经拥有了构建测试所需的所有组件,该测试在程序块中进行了描述。样例6.78中的测试实例化了环境类,然后运行每一步。

示例6.78使用随机配置的简单测试

您可能想要覆盖随机配置,可能是为了达到某个特殊情况。样例6.79中的测试将配置类随机化,然后启用所有端口。

示例6.79覆盖随机配置的简单测试

请注意,在示例6.77中,所有生成器都构造好了,但只运行了少数几个,这取决于随机配置。如果您只构造了正在使用的生成器,那么您必须使用in_ use [i]测试来包围对gen [i]的任何引用,否则您的测试平台在尝试使用时将会崩溃

参考不存在的生成器。这些生成器占用的额外内存对于一个更稳定的测试平台来说只是一个小代价。

6.18. 结论

约束随机测试是产生验证复杂设计所需刺激的唯一可行方法。SystemVerilog提供了许多创建随机刺激的方法,本章提供了许多替代方法。

测试需要灵活,允许您使用默认生成的值,或者约束或覆盖这些值,以便您能够达到目标。在创建您的测试平台时,总是预先计划,留下足够的“挂钩”,这样您就可以在不修改现有代码的情况下从测试中控制测试平台。

6.19. 练习

  1. 为以下项目编写SystemVerilog代码。

    1. 创建一个类Exercise1,包含两个随机变量,8位数据和4位地址。创建一个约束块,将address保持为3或4。

    2. 在初始块中,构造一个Exercise1对象并将其随机化。检查随机化的状态。

  2. 修改练习1的解决方案,创建一个新的类Exercise2,以便:

    1. data总是等于5

    2. 地址==0的概率为10%

    3. 地址在[1:14]之间的概率是80%

    4. 地址==15的概率是10%

  3. 使用练习1或练习2的解决方案,通过生成20个新数据和地址值来演示其用法,并检查约束求解器是否成功。

  4. 创建一个将Exercise2类随机化1000次的测试平台。

    1. 计算每个地址值出现的次数,并在直方图中打印结果。你是否看到了10% / 80% / 10%的确切分布?为什么或为什么不?

    2. 用3个不同的随机种子运行模拟,创建直方图,然后对结果进行评论。下面是如何使用seed 42运行模拟。

VCS: > simv +ntb_random_seed=42 IUS: > irun exercise4。sv−svseed 42 Questa: > vsim−sv_seed 42

  1. 对于样例6.4中的代码,请描述len、dst和src的约束

变量。

  1. 请按以下约束填写表6.9。

表6.9解概率

解决方案

x

y

概率

一个

0

0

B

0

1

C

0

2

D

0

3.

E

1

0

F

1

1

G

1

2

H

1

3.

  1. 对于下面的类,create:

    1. 一个限制读取事务地址范围为0到7(包括0到7)的约束。

    2. 编写行为代码来关闭上述约束。使用内联约束构造并运行MemTrans对象,该约束将读事务操作地址限制在0到8(包括0到8)范围内。测试内联约束是否有效。

  1. 为10x10像素的图形图像创建一个类。每个像素的值可以随机化为黑色或白色。随机生成一张平均20%的白色图像。打印图像并报告每种类型的像素数。

  2. 创建一个类StimData,包含一个整数样本数组。随机化数组的大小和内容,将大小限制在1到1000之间。通过生成20个事务并报告大小来测试约束。

  3. 展开下面的事务类,使相同类型的背靠背事务没有相同的地址。通过生成20个事务来测试约束。

  1. 展开下面的RandTransaction类,使相同类型的背靠背事务没有相同的地址。通过生成20个事务来测试约束。

results matching ""

    No results matching ""