目录
6.1 什么需要随机化
器件配置
环境配置
原始输入数据
封装后的输入数据
协议异常、error和violation
延迟
6.2 sv中的随机
带有随机变量的class
检查随机后的结果
约束求解器(constraint solver)
什么可以随机
6.3 约束
简单表达式
权重分布
集合(set)成员和inside操作符
条件约束
双向约束
选用合适的运算符
6.4 解的概率
->是逻辑表达式
双向传播
solve…before
6.5 多约束块控制
6.6 有效约束
6.7 内嵌约束
6.8 pre_randomize和post_randomize函数
构造浴缸型分布
关于void函数
6.9 随机数函数
6.10 约束技巧和技术
用变量约束
用非随机值
用约束检查取值
随机化部分变量
开/关约束
使用内嵌约束
使用外部约束
扩展类
6.11 随机化的常见错误
小心使用有符号变量
提高solver的性能小技巧
6.12 迭代和数组约束
数组大小
元素之和
数组约束的问题
约束数组和队列的每一个元素
产生具有不同元素值的数组
随机化handle array
6.13 原子激励vs场景激励
基于历史的原子发生器
随机序列
随机object数组
组合序列
6.14 随机控制
randcase
randcase决策树
6.15 随机数发生器
伪随机数发生器(PRNG)
多个发生器
层次化种子
6.16 随机器件配置
6.1 什么需要随机化
通常能想到的是输入数据随机化,但这只能测出数据通路上的问题。我们还需要关注控制点。
下面是我们设计输入的各个方面

器件配置
我们的设计被用户使用的时候,要接多少个输入设备,多少个输出设备,这都是不确定的,所以在测试过程中,要考虑如果有多个不相关通道接了大量设备会不会出问题。
环境配置
我们的器件通常会和其他许多器件一起工作,testbench需要模拟这种多器件工作的情形。包括objects的数量以及它们如何配置。
原始输入数据
最容易想到的,但需要涉及协议的各个层次和故障注入。
封装后的输入数据
许多器件会处理协议的不同层次。每个层次都有自己的控制域,我们需要测试不同的组合,通过约束产生有效控制域,同时允许故障注入。
协议异常、error和violation
你需要预测哪里可能会出错,设计规范的边界处行为。比如两个器件通信时,如果通信中断会怎么样?正确的设计应该能够正常处理
延迟
测试平台应该合理利用延迟,使用随机,合法的延迟检测出设计中的问题。一些设计可能对时钟敏感,可以通过配置上升沿前后移动来模拟clock jitter检测是否正常。另外,之前说过时钟发生器应该在测试平台之外,但是它的频率,偏移应该是可配置的。
再次注意,你要测试的是功能性错误,而非时域错误。
6.2 sv中的随机
最好的随机是事务层的随机,而非一次性的随机值。
带有随机变量的class
下面是一个带有随机变量和约束的class。
rand类型,在每次对class进行随机化的时候都会被随机赋一个值。
randc类型,有周期的rand,只有在所有可能性都被测试完毕后才会重复。如果现在有一个8元素array是randc类型的,那么每个元素都有自己的周期,所以一共有8个周期。
constraint是约束,描述选择变量值必须满足的要求。
randomize函数在执行失败,遇到问题时会返回0。
$fatal在打印错误信息后会结束仿真。此后不再额外写出else语句。
Sample 6.1 Simple random class
class Packet;
// The random variables
rand bit [31:0] src, dst, data[8];
randc bit [7:0] kind;
// Limit the values for src
constraint c {src > 10;
src < 15;}
endclass
Packet p;
initial begin
p = new();// Create a packet
assert (p.randomize())
else $fatal(0, "Packet::randomize failed");
transmit(p);
end
不要在构造函数new()中做随机化,因为你可能希望开启/关闭约束,构造函数只是为了初始化的。
class中的所有变量都需要被随机化并公有。
检查随机后的结果
如果你的代码有违约束,会报错。randomize会随机化所有rand, randc变量并检查是否满足约束。
前面通过SVA来检查设计是否有误。你也可以自己写程序来检查。
约束求解器(constraint solver)
sv中的约束求解器会根据约束选出可用值,值来自PRNG(伪随机数发生器),有初始化的种子,如果你的testbench和seed都相同,结果也会是相同的。
不同solver会产生不同的向量vendor,所以不同仿真器的结果可能不同,相同仿真器的不同版本结果也可能不同。
什么可以随机
整型变量,就是常见的bit位。只能随机化为0/1。字符串不行,handle变量不能写进constraint。
截至2007(书里写的年份),实数随机化还比较困难,不能出现x==0.333这类句子。
但是在2023中已经加入了real变量,能解决了。
6.3 约束
简单表达式
好的简单表达式只能有一个比较符,下面的是不好的
Sample 6.4 Bad ordering constraint
class order;
rand bit [7:0] lo, med, hi;
constraint bad {lo < med < hi;} // Gotcha!
endclass
约束中不能有assign语句,但是可以用==来等效起作用,比如下面的都是合法的
len==42
len == header.addr_mode* 4 + payload.size()
权重分布
如果我们想控制一些值出现的概率,可以用dist。
dist中有两个操作符,第一个是:=,这个操作符左侧是取值或取值范围,右侧是它对应的权重,如果是取值范围,如下的src,那么其中每个值的权重都是右边的值。
第二个是:/,区别只在于左侧是取值范围时,右侧的值并不是每个取指的权重,而是取值范围的权重,每个取值的取值权重平分它。
对于两种操作符,右侧的权重加和都不要求是100。
Sample 6.7 Weighted random distribution with dist
rand int src, dst;
constraint c_dist {
src dist {0:=40, [1:3]:=60};
// src = 0, weight = 40/220
// src = 1, weight = 60/220
// src = 2, weight = 60/220
// src = 3, weight = 60/220
dst dist {0:/40, [1:3]:/60};
// dst = 0, weight = 40/100
// dst = 1, weight = 20/100
// dst = 2, weight = 20/100
// dst = 3, weight = 20/100
}
等式的右侧不一定非得是上面展示的数字,也可以是变量,如下所示
Sample 6.8 Dynamically changing distribution weights
// Bus operation, byte, word, or longword
class BusOp;
// Operand length
typedef enum {BYTE, WORD, LWRD } length_e;
rand length_e len;
// Weights for dist constraint
bit [31:0] w_byte=1, w_word=3, w_lwrd=5;
constraint c_len {
len dist {BYTE := w_byte, // Choose a random
WORD := w_word, // length using
LWRD := w_lwrd}; // variable weights
}
endclass
对于上面的代码,我们可以通过调整三个w_*的值来控制len的取值概率,甚至可以通过将一个变量的值设为0来删除对应值的出现可能。
集合(set)成员和inside操作符
写约束时,如果你不喜欢写表达式,也可以用inside操作符实现相同的功能
这样很方便修改取值范围
Sample 6.9 Random sets of values
rand int c; // Random variable
int lo, hi; // Non-random variables used as limits
constraint c_range {
c inside {[lo:hi]}; // lo <= c && c <= hi
}
如果你取值范围的上限/下限和变量的边界值一样,那么可以用$来直接表示
Sample 6.10 Specifying minimum and maximum range with $
rand bit [6:0] b; // 0 <= b <= 127
rand bit [5:0] e; // 0 <= e <= 63
constraint c_range {
b inside {[$:4], [20:$}; // 0 <= b <= 4 || 20 <= b <= 127
e inside {[$:4], [20:$}; // 0 <= e <= 4 || 20 <= e <= 63
}
同样,你也可以通过!操作符来选出目标集合之外的部分
Sample 6.11 Inverted random set constraint
constraint c_range {
!(c inside {[lo:hi]}); // c < lo or c > hi
}
你也可以在集合中使用数组
Sample 6.12 Random set constraint for an array
rand int f;
int fib[5] = '{1,2,3,5,8};
constraint c_fibonacci {
f inside fib;
}
书中举了一个例子,我截取了部分关键代码,对于下面这种数组,有重复值,这时候随机化选值从概率上看会怎么样呢?答案是12358被选中的概率基本相等。
Sample 6.14 Repeated values in inside constraint
class Weighted;
rand int val;
int array[] = '{1,1,2,3,5,8,8,8,8,8};
constraint c {val inside array;}
endclass
下面的代码展示另一种使用方法:定义好总的选择域,但是每次从队列中选,随机化之前把队列初始化为我们想选的值的队列。
Sample 6.16 Class to choose from an array of possible values
class Days;
typedef enum {SUN, MON, TUE, WED,
THU, FRI, SAT} days_e;
days_e choices[$];
rand days_e choice;
constraint cday {choice inside choices;}
endclass
Sample 6.17 Choosing from an array of values
initial begin
Days days;
days = new();
days.choices = {Days::SUN, Days::SAT};
assert (days.randomize());
$display("Random weekend day %s\\n", days.choice.name);
days.choices = {Days::MON, Days::TUE, Days::WED,
Days::THU, Days::FRI};
assert (days.randomize());
$display("Random week day %s", days.choice.name);
end
下面有一个问题,如果你想动态地向集合中添加/删除元素,要谨慎使用inside,因为每次修改集合中的元素个数,solver都会重新计算约束,可能是很大的计算量。
比如你想把集合中的元素每个都只输出一次,一种方法是删除约束中剩下的量,下面是另一种方法,用randc(有周期的随机)
Sample 6.18 Using randc to choose array values in random order
class RandcInside;
int array[]; // Values to choose
randc bit [15:0] index; // Index into array
function new(input int a[]); // Construct & initialize
array = a;
endfunction
function int pick; // Return most recent pick
return array[index];
endfunction
constraint c_size {index < array.size();}
endclass
initial begin
RandcInside ri;
ri = new('{1,3,5,7,9,11,13});
repeat (ri.array.size()) begin
assert(ri.randomize());
$display("Picked %2d [%0d]", ri.pick(), ri.index);
end
end
条件约束
如果有一些约束知在某些条件下生效,你可以用->或if-else
Sample 6.19 Constraint block with implication operator
class BusOp;
…
constraint c_io {
(io_space_mode) ->
addr[31] == 1Õb1;
}
Sample 6.20 Constraint block with if-else operator
class BusOp;
…
constraint c_len_rw {
if (op == READ)
len inside {[BYTE:LWRD]};
else
len == LWRD;
}
双向约束
所有的约束都是同时生效,解也是同时计算出的,而不是先解出一个再求解另一个
选用合适的运算符
在sv中,加、减、位运算都是好的,乘、除、模运算都是很慢的。
比如,你现在想生成一个靠近页边界的地址值,页大小是4096 bytes,那么下面两个是等效的,但6.23更好
Sample 6.22 Expensive constraint with mod and unsized variable
rand bit [31:0] addr;
constraint slow_near_page_boundary {
addr % 4096 inside {[0:20], [4075:4095]};
}
Sample 6.23 Efficient constraint with bit extract
rand bit [31:0] addr;
constraint near_page_boundry {
addr[11:0] inside {[0:20], [4075:4095]};
}
6.4 解的概率
->是逻辑表达式
注意,->不同于if,它是逻辑表达式,意思是他会把一个约束转换为对应的逻辑。
对于下面的约束,(x==0) -> y==0 等价于 !(x==0) || (y==0)
Sample 6.25 Class with implication
class Imp1;
rand bit x;
rand bit [1:0] y;
constraint c_xy {
(x==0) -> y==0;
}
endclass
因此解的概率为

双向传播
如果我们加入新的条件y>0,此时由于x=0会导致y=0,因此逻辑表达式会变为x不能等于0的样子
虽然从代码上看,是x约束y,但注意约束是双向传播的,任何变量的变化都会反向影响其他变量的变化
Sample 6.26 Class with implication and constraint
class Imp2;
rand bit x;
rand bit [1:0] y;
constraint c_xy {
y > 0;
(x==0) -> y==0;
}
endclass
此时解的概率为

solve…before
它不改变解空间,但会改变解出现的概率,如字面意思,如果我们写solve x before y,sv会先以1/2的概率选择x,再选择y,如果写solve y before x,会先以1/4的概率选择y,再选择x。
Sample 6.27 Class with implication and solve…before
class SolveBefore;
rand bit x;
rand bit [1:0] y;
constraint c_xy {
(x==0) -> y==0;
solve x before y;
}
endclass


一般情况下我们都不用solve…before,只有当你对解空间不满意,想更多地探索边界才会用。
6.5 多约束块控制
如果你想控制某个/某几个约束块生效,那么可以用一个函数。函数的标准形式为handle.constraint.constraint_mode(),它用来控制特定约束块; handle.constraint_mode()用来控制所有约束块,写0表示禁能1表示使能
Sample 6.28 Using 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();
// Create a long packet by disabling short constraint
p.c_short.constraint_mode(0);
assert (p.randomize());
transmit(p);
// Create a short packet by disabling all constraints
// then enabling only the short constraint
p.constraint_mode(0);
p.c_short.constraint_mode(1);
assert (p.randomize());
transmit(p);
end
6.6 有效约束
好的约束技巧是用约束块确保随即约束的有效性。
下面的约束描述了一个事务的正确行为,如果你想违反事务来测试,只需要把它设置无效。
Sample 6.29 Checking write length with a valid constraint
class Transaction;
rand enum {BYTE, WORD, LWRD, QWRD} length;
rand enum {READ, WRITE, RMW, INTR} opc;
constraint valid_RMW_LWRD {
(opc == RMW) -> length == LWRD;
}
endclass
6.7 内嵌约束
如果有一些约束只是少数几次测试用到,大部分时候用不到的,那么反复编写他们的disable代码会比较麻烦,sv中提供了一种不把约束块写在class内部的方法:randomize() with,他后面可以接一次性的约束。
randomize() with的作用域是class,所以你看到它后面写的是addr而不是t.addr。
Sample 6.30 The randomize() with statement
class Transaction;
rand bit [31:0] addr, data;
constraint c1 {addr inside{[0:100],[1000:2000]};}
endclass
Transaction t;
initial begin
t = new();
// addr is 50-100, 1000-1500, data < 10
assert(t.randomize() with {addr >= 50; addr <= 1500;
data < 10;});
driveBus(t);
// force addr to a specific value, data > 10
assert(t.randomize() with {addr == 2000; data > 10;});
driveBus(t);
end
6.8 pre_randomize和post_randomize函数
有时候我们需要在randomize之前或之后立即进行一些操作,比如随机化之前设置类中的一些非随机变量,又比如随机化之后计算随机数据的误差校正位。
构造浴缸型分布
有一些应用中我们需要非线性分布,比如浴缸型分布,它的特点是两端的概率大,中间的概率小。
$dist_exponential函数会随机在e指数函数上选取一个点,计算完毕后随机选择把它放在左边还是右边

关于void函数
pre和post这两个函数只能调用其他函数,不能调用消耗时间的任务,所以执行randomize期间无法产生一段延迟。如果想调试随机化过程中出现的问题,可以调用预先写好的void函数输出。
6.9 随机数函数
常见函数

6.10 约束技巧和技术
用变量约束
前面提到过两种,这里简单回顾,不贴出代码。
1.通过用变量设置inside的上下限
2.通过变量设置dist取值的出现概率
用非随机值
如果在产生激励的时候已经产生了大部分的激励,但是还缺少部分,这时候可以先写randomize,再通过rand_mode手动设置某个变量的值。
Sample 6.35 rand_mode disables randomization of variables
// Packet with variable length payload
class Packet;
rand bit [7:0] length, payload[];
constraint c_valid {length > 0;
payload.size() == length;}
function void display(string msg);
$display("\\n%s", msg);
$write("Packet len=%0d, payload size=%0d, bytes = ",
length, payload.size());
for(int i=0; (i<4 && i<payload.size()); i++)
$write(" %0d", payload[i]);
$display;
endfunction
endclass
Packet p;
initial begin
p = new();
// Randomize all variables
assert (p.randomize());
p.display("Simple randomize");
p.length.rand_mode(0); // Make length nonrandom,
p.length = 42; // set it to a constant value
assert (p.randomize()); // then randomize the payload
p.display("Randomize with rand_mode");
end
上面的代码约束length=payload.size,通过手动设置取消length的随机属性,再手动赋值,就可以控制payload数组的大小为42。
用约束检查取值
如果你手动改变了一些变量的值,可以通过handle.ran domize(null) 函数将所有变量都视为非随机,这样就能检查取值是否合法。
随机化部分变量
如果你只想随机化一部分变量,也可以,甚至可以单独随机化没有被标记为rand类型的变量。
这种方法不太常用,有时候可以用于探索边界。
Sample 6.36 Randomizing a subset of variables in a class
class Rising;
byte low;
// Not random
rand byte med, hi; // Random variable
constraint up
{ low < med; med < hi; } // See Section 6.4.2
endclass
initial begin
Rising r;
r = new();
r.randomize();
// Randomize med, hi; low untouched
r.randomize(med); // Randomize only med
r.randomize(low); // Randomize only low
end
开/关约束
如果你想得到两类完全不同的测试,常见办法是为每一类测试建立独自的约束,在随机化的时候启用/禁用。
Sample 6.38 Turning constraints on and off with constraint_mode
class Instruction;
rand opcode_e opcode;
constraint c_no_operands {
opcode == NOP || opcode == HALT;}
constraint c_one_operand {
opcode == CLR || opcode == NOT;}
endclass
Instruction instr;
initial begin
instr = new();
// Generate an instruction with no operands
instr.constraint_mode(0); // Turn off all constraints
instr.c_no_operands.constraint_mode(1);
assert (instr.randomize());
// Generate an instruction with one operand
instr.constraint_mode(0); // Turn off all constraints
instr.c_one_operand.constraint_mode(1);
assert (instr.randomize());
end
使用内嵌约束
前面已经介绍过,这里提一下它的问题。
1.由于内嵌约束分散在不同地方,所以当你在class内部的约束中加入新的时,可能会和某部分的内嵌约束冲突
2.这种内嵌约束难以重用
使用外部约束
外部约束和内嵌约束类似,都是可以增加约束,但它有一些优点。
1.可重用‘
2.可以对所有这个class的objects生效,而非仅仅在一次randomize中生效
如果外部约束声明了但是从来没使用会发生什么?不知道
Sample 6.39 Class with an external constraint
// packet.sv
class Packet;
rand bit [7:0] length;
rand bit [7:0] payload[];
constraint c_valid {length > 0;
payload.size() == length;}
constraint c_external;
endclass
Sample 6.40 Program defining an external constraint
// test.sv
program test;
constraint Packet::c_external {length == 1;}
…
endprogram
扩展类
未来会学到。有了它可以基于已有的类切换到增加了很多东西的扩展类。
6.11 随机化的常见错误
小心使用有符号变量
小心使用int,byte类有符号变量,除非你真的确定应该是有符号的。
对于下面的情况,我们可能得到32,32;也可能得到-63,127
Sample 6.41 Signed variables cause randomization problems
class SignedVars;
rand byte pkt1_len, pk2_len;
constraint total_len {
pkt1_len + pk2_len == 64;
}
endclass
你可能想那就不用有符号,换为下面的情况
也有问题,比如一个是32'h8000_0000和32'h8000_0040,相加后舍弃高位也可能符合结果
Sample 6.42 Randomizing unsigned 32-bit variables
class Vars32;
rand bit [31:0] pkt1_len, pk2_len; // unsigned type
constraint total_len {
pkt1_len + pk2_len == 64;
}
endclass
最好的办法是需要几位就给几位,尽量不要给32位这种很大的
Sample 6.43 Randomizing unsigned 8-bit variables
class Vars8;
rand bit [7:0] pkt1_len, pkt2_len; // 8-bits wide
constraint total_len {
pkt1_len + pkt2_len == 9Õd64;
}
endclass
提高solver的性能小技巧
1.不要用乘除,如果是2次幂的操作,用移位符
2.2次幂的取模操作可以替换为带掩码的位与
3.位宽小于32位的更快
6.12 迭代和数组约束
如何对数组进行约束?你可能想到foreach,但是它的性能比较差。我们可以用以下几个方面
数组大小
用.size约束数组大小。记得要加上界,否则可能会特别大。
Sample 6.44 Constraining dynamic array size
class dyn_size;
rand logic [31:0] d[];
constraint d_size {d.size() inside {[1:10]}; }
endclass
元素之和
如果我们要在10周期内串行发送4bit数,那么就需要一个信号来指示当前周期的数据是否有效,下面的例子用的是strobe,通过控制和为4控制了有效个数。
Sample 6.45 Random strobe pattern class
parameter MAX_TRANSFER_LEN = 10;
class StrobePat;
rand bit strobe[MAX_TRANSFER_LEN];
constraint c_set_four { strobe.sum() == 4Õh4; }
endclass
initial begin
StrobePat sp;
int count = 0; // Index into data array
sp = new();
assert (sp.randomize());
foreach (sp.strobe[i]) begin
@bus.cb;
bus.cb.strobe <= sp.strobe[i];
// If strobe is enabled, drive out next data word
if (sp.strobe[i])
bus.cb.data <= data[count++];
end
end
数组约束的问题
本节讲的问题前面出现过,这里简单表述一下。如果以一个数组的和作为约束点,要求和小于1024,数组的元素个数在1-8之间。
1.如果是有符号变量,那么产生的数值可能有正有负
2.如果以无符号为变量,每个变量为8bits,那么和也会是8bits,所以产生的数值可能正常,但计算出的和并不正常
3.如果用无符号32bit,那么会有大变量导致和溢出,反而使和变小
4.如果用10bit,那么每个元素的大小经常会大于255,这也是我们不想要的。
最后,单独从和的宏观角度进行约束是不够的,还是需要对单个元素进行约束。
约束数组和队列的每一个元素
最简单的约束方法就是用foreach
Sample 6.55 Simple foreach constraint: good_sum5
class good_sum5;
rand uint len[];
constraint c_len {foreach (len[i])
len[i] inside {[1:255]};
len.sum < 1024;
len.size() inside {[1:8]};}
endclass
也可以用foreach约束元素之间的关系,但是这么做的话要注意边界元素
Sample 6.57 Creating ascending array values with foreach
class Ascend;
rand uint d[10];
constraint c {
foreach (d[i]) // For every element
if (i>0) // except the first
d[i] > d[i-1]; // compare with previous element
}
endclass
产生具有不同元素值的数组
怎么样能够得到一个随机数组,它的元素值各不相同?你可能会想到用randc标记数组,但是这样随机化得到的数组元素值基本相同。你也可能想到在约束中写循环判断,但是这种方法的复杂度极高。
下面是一个标准方法。用一个随机标量去随机化,得到结果以后赋值给数组。
Sample 6.59 Creating unique array values with a randc helper class
class randc8;
randc bit [7:0] val;
endclass
class LittleUniqueArray;
bit [7:0] ua [64]; // Array of unique values
function void pre_randomize;
randc8 rc8;
rc8 = new();
foreach (ua[i]) begin
assert(rc8.randomize());
ua[i] = rc8.val;
end
endfunction
endclass
随机化handle array
handle array的随机化特点是在randomize之前要手动为每个handle创建object,也即array[i] = new()。object的内容不会被随机化。
Sample 6.63 Constructing elements in a random array
parameter MAX_SIZE = 10;
class RandStuff;
rand int value;
endclass
class RandArray;
rand RandStuff array[]; // DonÕt forget rand!
constraint c {array.size() inside {[1:MAX_SIZE]}; }
function new();
array = new[MAX_SIZE]; // Allocate maximum size
foreach (array[i])
array[i] = new();
endfunction;
endclass
RandArray ra;
initial begin
ra = new(); // Construct array and all objects
assert(ra.randomize()); // Randomize and maybe shrink array
foreach (ra.array[i])
$display(ra.array[i].value);
end
6.13 原子激励vs场景激励
基于历史的原子发生器
你可以写一个post_randomize函数复制历史事务用于下一次随机化
这种方法对较小规模的测试比较好。
随机序列
下面的代码就是随即序列,它是程序性代码,和类中写数据和约束的代码不一样。
stream表示序列由以下三个事务组成,每个事务由各自的权重。
cfg_read是对这个事务的描写,它的行为是可能执行一次cfg_read_task;也可能是执行一次cfg_read_task再执行一次cfg_read。
Sample 6.64 Command generator using randsequence
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 // for
end
task cfg_read_task;
…
endtask
使用它有几个问题。
1.随机序列是程序性代码,有别于其他代码,需要单独维护
2.如果你想加一个事务进去,必须要改源代码,不能用扩展的方式加
随机object数组
最后一种产生随机序列的形式是随机化整个对象数组。你可以建立指向数组的前一个和后一个对象的约束,solver会同时求解所有的约束。由于整个序列同时产生,你可以在发送第一个事务前就知道所有数据的校验和、事务的总数等信息。
组合序列
你可以把多个序列组合在一起,得到一个更真实地事务流。
6.14 随机控制
randcase
Sample 6.65 Random control with randcase and $urandom_range
initial begin
int len;
randcase
1: len = $urandom_range(0, 2); // 10%: 0, 1, or 2
8: len = $urandom_range(3, 5); // 80%: 3, 4, or 5
1: len = $urandom_range(6, 7); // 10%: 6 or 7
endcase
$display("len=%0d", len);
end
randcase比随即约束更难修改,如果是一个大类中的小部分,那么用随机约束要更好。
Sample 6.66 Equivalent constrained class
class LenDist;
rand int len;
constraint c
{len dist {[0:2] := 1, [3:5] := 8, [6:7] := 1}; }
endclass
LenDist lenD;
initial begin
lenD = new();
assert (lenD.randomize());
$display("Chose len=%0d", lenD.len);
end
小心使用randcase,因为它不留下任何线索,如果你想注入错误,就需要一个变量告诉scoreboard。如果在class中使用这个变量,就必须声明成rand,并用约束在不同测试中改变。
randcase决策树
Sample 6.67 Creating a decision tree with randcase
initial begin
// Level 1
randcase
one_write_wt: do_one_write();
one_read_wt: do_one_read();
seq_write_wt: do_seq_write();
seq_read_wt: do_seq_read();
endcase
end
// Level 2
task do_one_write;
randcase
mem_write_wt: do_mem_write();
io_write_wt: do_io_write();
cfg_write_wt: do_cfg_write();
endcase
endtask
task do_one_read;
randcase
mem_read_wt: do_mem_read();
io_read_wt: do_io_read();
cfg_read_wt: do_cfg_read();
endcase
endtask
6.15 随机数发生器
伪随机数发生器(PRNG)
state就是种子
Sample 6.68 Simple pseudorandom number generator
reg [31:0] state = 32Õh12345678;
function logic [31:0] my_random;
logic [63:0] s64;
s64 = state * state;
state = (s64 >> 16) + state;
my_random = state;
endfunction
多个发生器
如果所有线程共享一个generator,那么他们会污染彼此的数据,随机稳定性变差。
sv中为每一个object和thread配备了单独的发生器。
层次化种子
每个测试的PRNG的种子都来源于它的父种子,每个测试之间的种子都是互相独立的。
任何对于代码的修改都会改变测试的事务流,如果你在测试一半的时候因为修改代码导致某个事务不再出现了,这就不太好。解决办法是在原有代码下面新增代码,新开一个线程,这样虽然也会改变,但是会减小危害。
Sample 6.70 Test code after modification
function void build();
pci_gen gen0, gen1;
atm_gen agen;
gen0 = new();
gen1 = new();
gen = new();
fork
gen0.run();
gen1.run();
gen.run();
join
endfunction : build
6.16 随机器件配置
将“测试平台的硬件配置”封装成一个独立的随机类,然后在仿真流程的不同阶段(生成、构建、运行)使用这个随机结果,从而高效地验证 DUT 在不同配置模式下的行为。
比如,下面定义了4个以太网口的配置
Sample 6.71 Ethernet switch configuration class
class eth_cfg;
rand bit [ 3:0] in_use; // Ports used in test
rand bit [47:0] mac_addr[4]; // MAC addresses
rand bit [ 3:0] is_100; // 100mb mode
rand uint run_for_n_frames; // # frames in test
// Force some addr bits when running in unicast mode
constraint local_unicast {
foreach (mac_addr[i])
mac_addr[i][41:40] == 2'b00;
}
constraint reasonable { // Limit test length
run_for_n_frames inside {[1:100]};
}
endclass : eth_cfg
下面是把以太网口配置放到了environment中,通过随机化得到哪个口被使用,build是把要使用的gen和drv器件生成,run则是启动。
注意,build也可以不管使不使用都生成,这样run的时候也不用判断,坏处是内存消耗多一点
Sample 6.72 Building environment with random configuration
class Environment;
eth_cfg cfg;
eth_src gen[4];
eth_mii drv[4];
function new();
cfg = new(); // Construct the cfg
endfunction
function void gen_cfg;
assert(cfg.randomize()); // Randomize the cfg
endfunction
// Use random configuration to build the environment
function void build();
foreach (gen[i])
if (cfg.in_use[i]) begin
gen[i] = new();
drv[i] = new();
if (cfg.is_100[i])
drv[i].set_speed(100);
end
endfunction
task run();
foreach (gen[i])
if (cfg.in_use[i]) begin
// Start the testbench transactors
gen[i].run();
…
end
endtask
task wrap_up();
// Not currently used
endtask
endclass : Environment
下面是在测试中使用的示例
ample 6.73 Simple test using random configuration
program test;
Environment env;
initial begin
env = new(); // Construct environment
env.gen_cfg; // Create random configuration
env.build(); // Build the testbench environment
env.run(); // Run the test
env.wrap_up(); // Clean up after test & report
end
endprogram
约束随机测试(CRT)是产生验证复杂设计所需激励的唯一办法。
