1、随机约束和分布
1.1 什么是随机?
- 芯片体积增大,复杂度日渐提高,在20年前定向测试已经无法满足验证的需求,而随机测试的比例逐渐提高。
- 定向测试能找到你认为可能存在的缺陷,而随机测试可以找到连你都没有想到的缺陷。
- 随机测试的环境要求比定向测试复杂,它需要激励、参考模型和在线比较。上百次的仿真不再需要人为参与,以此来提高验证效率。
- 随机测试相对于定向测试可以减少相当多的代码量,而产生的激励较定向测试也更多样。
- 如果说定向测试是"站着测",那么随机测试就是"躺着测"、"跪着测"、"跑着测”,各种花式测,只有想不到,没有测不到。
- 随机化是为了产生更多可能的驱动,因此在软件世界"class"一侧的运用更多,所以我们倾向于将相关数据有机整理在一个类的同时,也用 rand 关键词来表明它们的随机属性。
- randc 表示周期随机性,即所有可能的值都赋过值后随机值才可能重复。
- rand:类似掷骰子,每次的概率相同;
- randc:类似抽扑克,牌越来越少;
- 随机属性需要配合SV预定义的类随机函数 std::randomize( ) 使用。即只有通过声明rand变量,并且在后期通过对象调用randomize()函数才可以随机化变量。
- 约束 constraint 也同随机变量一起在类中声明。
- 不能在构造函数 new( ) 里随机化对象。
| class my_class; |
| |
| rand bit [31:0] src, dst, data[8]; |
| randc bit [7:0] kind; |
| |
| constraint c{ |
| src > 10; |
| src < 15 |
| }; |
| endclass |
| |
| my_class u_class |
| |
| initial begin |
| u_class = new(); |
| assert (u_class.randomize()) |
| else |
| $fatal (0, "u_class::randomize failed"); |
| transmit(u_class); |
| end |
1.2 什么是约束?
- 如果随机没有约束,意味着什么?是绝对的自由吗?肯定不是,它是一匹脱缰的野马,没有拘束,产生有效激励的同时也产生了很多无效和非法的激励。
- 那么我们想要的随机自由是一种合法的随机,需要限定激励的合法范围。同时,伴随测试的进行,约束甚至应该"变形”,变得更趋于为测试的数值范围或者期待的数值范围。
- 随机的对象不只是一个数据,而是有联系的变量集。通常这些变量会被封装在一个数据类中,同时需要在类中声明数据之间的约束关系。因此约束之后要产生随机数据需要一个"求解器”,即在满足数据本身和数据之间约束关系时的随机数值解。
- 约束不但可以指定数据的取值范围,还可以指定各个数值的随机权重分布。
- 约束表达式的求解是由SV的约束求解器(constraint solver)完成的。
- 求解器能够选择满足约束的值,这个值是有SV的伪随机数发生器PRNG(Pseudo random number generator)从一个初始值(seed)产生。只要改变种子的值,就可以改变CRT的行为。
- SV标准定义了表达式的含义以及产生的合法值,但没有规定求解器计算约束的准确顺序。这即是说,不同仿真器对于同一个约束类和种子值求解出的数值可能是不相同的。
- 什么可以被约束?SV只能随机化2值数据类型,但位可以是2值或4值。这即是说,无法随机化出X值和Z值,也无法随机化字符串。
| class date; |
| rand bit [2:0] month; |
| rand bit [4:0] day; |
| rand int year; |
| |
| constraint c_date{ |
| month inside { [1:12] }; |
| day inside { [1:31] }; |
| year inside { [2010:2030] }; |
| } |
| endclass |
- inside是常见的约束运算符,表示变量应该属于某一个值的集合,除非述存在其他约束,否则随机变量在集合重敢值的概率是相等的。
- 集合里也可以使用变量。
| rand int c; |
| int lo, hi; |
| |
| constraint c_range{ |
| c inside { [lo:hi] }; |
| rand bit [6:0] b; |
| rand bit [5:0] e; |
| |
| constraint c_range { |
| b inside { [$:4],[20:$] }; |
| e inside { [$:4],[20:$] }; |
| } |
或者通过 -> 或者 if-else 啦让一个约束表达式在特定时刻有效。
| class BusOp; |
| ... |
| constraint c{ |
| (io_mode) -> |
| addr == 8'b1; |
| } |
| endclass |
| |
| class BusOp; |
| ... |
| constraint c{ |
| if(io_mode) |
| addr inside([1:5]); |
| else |
| addr == 8'b0; |
| } |
| endclass |
可以通过取反操作符 ! 对约束取反:
| constraint c_range{ |
| !(c inside { [lo:hi] }); |
| } |
1.3 权重分布
- 关键词 dist 可以在约束中用来产生随机数值的权重分布,这样某些值的选取机会要比其他值更大一些。
- dist 操作符带有一个值的列表以及相应的权重,中间用 := 或 :/ 分开。
- := 操作符表示值范围内的每一个值的权重是相同的,
- :/ 操作符表示权重要平均分到值范围内的每一个值。
- 权重不用百分比表示,权重的和也不必是100。
- 值或权重可以是常数或者变量。
| rand int src, dst; |
| constraint c_dist { |
| src dist { 0 := 40, [1:3] := 60}; |
| |
| |
| |
| |
| dst dist { 0 :/ 40, [1:3] :/ 60}; |
| |
| |
| |
| |
| } |
1.4 双向约束
- 约束块不是自上而下执行的程序性代码,他们是声明性的,是并行的,所有约束表达式同时有效
- 增加或删除任何一个变量的约束,都会直接或间接的影响所有关于变量的值的选取。
| rand logic [15:0] r,s,t; |
| |
| constraint c{ |
| r < t; |
| s == r; |
| t < 30; |
| s > 25; |
| } |
如此得到的约束结果如下所示:

2、约束块控制
2.1 控制多个约束块
- 一个类可以包含多个约束块。可以把不同约束块用于不同测试。
- —般情况下,各个约束块之间的约束内容是互相协调不违背的,因此通过随机函数产生随机数时可以找到合适的解。
- 对于其它情况,例如根据不同需要,来选择使能哪些约束块,禁止哪些约束块的要求,可以使用内建的 constraint_mode() 函数打开或者关闭约束。
| class Packet; |
| rand int length; |
| |
| constraint c_short { |
| length inside{ [1:32] }; |
| } |
| constraint c_long { |
| length inside{ [1000 : 1023] }; |
| } |
| endclass |
| |
| Packet p; |
| |
| initial begin |
| p = new(); |
| |
| p.c_short.constraint_mode(0); |
| assert (p.randomize()); |
| transmit(p); |
| |
| p.constraint_mode(O); |
| p.c_short.constraint_mode(1); |
| assert (p .randomize()); |
| transmit(p); |
| end |
2.2 内嵌约束
- 伴随着复杂的约束,它们之间会相互作用,最终产生难以预测的结果。用来使能和禁止这些约束的代码也会增加测试的复杂性。
- 经常增加或修改类例的约束也可能会影响整个团队的工作,这需要考虑类的开放封闭原则(OCP)。
- SV允许使用 randomize( ) with 来增加额外的约束,这和在类里增加约束是等效的,但同时要注意类内部约束和外部约束之间应该是协调的,如果出现互相违背的情况,那么随机数值求解会失败。
- 设计 constraint 时,可以使用关键字 soft ,使得其约束的优先级更低,后续出现冲突的 randomize( ) with 时,不会报错。
| class Transaction; |
| rand bit [31:0] addr, data; |
| constraint c1 { |
| soft addr inside{ [0:100],[1000:2000] }; |
| } |
| endclass |
| |
| Transaction t; |
| |
| initial begin |
| t = new(); |
| |
| |
| assert(t.randomize() with{addr >= 50; |
| addr <= 1500; |
| data < 10; |
| } |
| ); |
| driveBus(t); |
| |
| assert(t.randomize() with{addr == 2000; |
| data > 10; |
| } |
| ); |
| driveBus (t); |
| end |
3、随机函数
3.1 回调函数pre_randomize/post_randomize
- 有时需要在调用 randomize( ) 之前或之后立即执行一些操作,例如在随机前设置类例的一些非随机变量(上下限、条件值、权重),或者在随机化之后需要计算随机数据的误差、分析和记录随机数据等。
- SV提供了两个预定义的 void 类型函数 pre_randomize( ) 和 post_randomize( ) 函数。用户可以类中定义这两个函数,分别在其中定义随机化前的行为和随机化后的行为。
- 如果某个类中定义了 pre_randomize( ) 或者 post_randomize( ) 函数,那么对象在执行了 randomize( ) 之前或者之后会分别执行这两个函数。
- pre_randomize( ) 和 post_randomize( ) 可以看做是 randomize( ) 函数的回调函数(callback function)。
3.2 随机数函数
SV提供了一些常用的系统随机函数。这些随机函数可以直接调用来返回随机数值。
- $random( ) —— 平均分布,返回32位有符号随机数。
- $urandom( ) —— 平均分布,返回32位无符号随机数。
- $urandom_range( ) —— 在指定范围内的平均分布。
- $dist_exponential( ) —— 指数衰落。
- $dist_normal( ) —— 钟型分布。
- $dist_poisson( ) —— 钟型分布。
- $dist_uniform( ) —— 平均分布。
| a = $urandom_range(3,10); |
| b = $urandom_range(10,3); |
| c = $urandom_range(10); |
3.3 随机化个别变量
- 在调用 randomize( ) 时可以传递变量的一个子集,这样只会随机化类里的几个变量。
- 只有参数列表里的变量才会被随机化,其它变量会被当做状态变量而不会被随机化。
- 所有的约束仍然保持有效。
- 初学者需要注意该种应用针对的是类里所有被指定或者没有被指定 rand 的变量都可以作为 randomize( ) 的参数而被随机化。
| class Rising; |
| byte low; |
| rand byte med,hi; |
| |
| constraint up{ low < med; med < hi; } |
| endclass |
| |
| initial begin |
| Rising r; |
| r = new ( ) ; |
| r .randomize () ; |
| r .randomize (med); |
| r .randomize (low); |
| end |
4、数组约束
4.1 数组的大小
在约束随机标量的同时,我们还可以对随机化数组进行约束。
| class dyn_size; |
| rand logic [31 :0] d[]; |
| constraint d_size{ |
| d.size() inside {[1 :10]}; |
| } |
| endclass |
多数情况下,数组的大小应该给定范围,防止生成过大体积的数组或者空数组。此外还可以在约束中结合数组的其它方法 sum ( ) , product ( ),and ( ) , or ( ) 和 xor( ) 。
4.2 约束数组中的元素
- SV 可以利用foreach对数组的每一个元素进行约束,和直接写出对固定大小数组的每一个元素的约束相比,foreach要更简洁。
- 针对动态数组,foreach更适合于对非固定大小数组中每个元素的约束。
| class good_sum; |
| rand uint len[]; |
| |
| constraint c_len { |
| foreach (len[i]) |
| len[i] inside{ [1:255]}; |
| len.sum() < 1024; |
| len.size() inside { [1:8]};} |
| endclass |
随机的输出结果如下所示:
| sum =1011, val = 83 249 197187 152 95 40 8 |
| sum =1012, val = 213 252 213 44 196 20 20 54 |
| sum = 370, val = 118 76 176 |
| sum = 976, val = 233 18744 157201 81 73 |
| sum = 412, val = 172 167 73 |
4.3 产生唯一元素值
如果想要产生一个随机数组,它的每一个元素的值都是唯一的。如果使用 randc 数组,那么数组中的每一个元素只会独立地随机化,并不会按照我们的本意使得数组中的元素值是唯一的。我们可以采用嵌套循环解决这个问题,如下所示:
| class Uniqueslow; |
| rand bit [7:0] ua [64]; |
| |
| constraint c { |
| foreach (ua[i]) |
| foreach (ua[j]) |
| if (i != j) |
| ua[i] != ua[j]; |
| } |
| endclass |
也可以利用 randc 变量来辅助生成唯一元素值的数组,如下所示:
| class randc8; |
| randc bit [7∶0] val; |
| endclass |
| |
| class LittleUniqueArray; |
| bit [ 7:0] ua [64]; |
| |
| function void pre_randomize(); |
| randc8 rc8 ; |
| rc8 = new(); |
| foreach (ua[i]) begin |
| assert (rc8.randomize()); |
| ua[i] = rc8.val; |
| end |
| endfunction |
| endclass |
这两种方法还是太笨了,最新的SV中加入了新的关键词 unique 来解决这个问题。
4.4 随机化句柄数组
- 随机句柄数组的功能是在调用其所在类的随机函数时,随机函数会随机化数组中的每一个句柄所指向的对象。因此随机句柄数组的声明一定要添加 rand 来表示其随机化的属性,同时在调用随机函数前要保证句柄数组中的每一个句柄元素都是非悬空的,这需要在随机化之前为每一个元素句柄构建对象。
- 如果要产生多个随机对象,那么你可能需要建立随机句柄数组。和整数数组不同,你需要在随机化前分配所有的元素,因为随机求解器不会创建对象。使用动态数组可以按照需要分配最大数量的元素,然后再使用约束减小数组的大小。在随机化时,动态句柄数组的大小可以保持不变或减小,但不能增加。
| parameter MAX_SIZE = 10; |
| |
| class Randstuff; |
| bit[1:0] value = 1 ; |
| endclass |
| |
| class RandArray; |
| rand Randstuff array[]; |
| constraint c { |
| array.size () inside{ [1:MAX_SIZE] }; |
| } |
| function new(); |
| |
| array = new[MAX_SIZE]; |
| foreach (array[i]) |
| array[i] = new(); |
| endfunction ; |
| endclass |
| |
| RandArray ra; |
| |
| initial begin |
| |
| ra = new(); |
| |
| assert(ra.randomize()); |
| foreach (ra.array [i]) |
| $display(ra.array[i].value); |
| end |
5、随机控制
5.1 随机序列
产牛事务序列的另一个方法是使用SV的 randsequence 结构。这对于随机委排组织原子(atomic)测试序列很有帮助。
| initial begin |
| for (int i=0; i<15 ; i++) begin |
| randsequence (stream) |
| stream : cfg_read := 1 | |
| io_read := 2 | |
| mem_read := 5; |
| cfg_read : { cfg_read_task; } | |
| { cfg_read_task; } cfg_read; |
| mem_read : { mem_read_task; } | |
| { mem_read_task; } mem_read; |
| io_read : { io_read_task; } | |
| { io_read_task; } io_read ; |
| endsequence |
| end |
| end |
5.2 随机控制
我们可以使用 randcase 来建立随机决策树,但它带来的问题是没有变量可供追踪调试。
| initial begin |
| int len; |
| randcase |
| 1 : len = $urandom_range (0, 2); |
| 8 : len = $urandom_range (3,5); |
| 1 : len = $urandom_range (6, 7); |
| endcase |
| $display( "len=%0d", len); |
| end |
- randsequence和randcase是针对轻量级的随机控制的应用。而我们可以通过定义随机类取代上述随机控制的功能,并且由于类的继承性使得在后期维护代码时更加方便。
- randsequence的相关功能我们在协调激励组件和测试用例时,可能会用到。
- randcase则对应着随机约束中的dist权重约束+if-else条件约束的组合。
参考资料:
[1] 路科验证V2教程
[2] 绿皮书:《SystemVerilog验证 测试平台编写指南》第2版
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步