SV学习(9)——随机函数、数组约束、随机控制
1. 随机函数
1.1. pre_randomize(0和post_randomize()
- 有时需要在调用randomize()之前或之后立即执行一些操作,例如在随机前设置例化的一些非随机变量(上下限、条件值、权重),或者在随机化之后需要计算随机数据的误差、分析和记录随机数据等
- SV提供了两个预定义的void类型函数pre_randomize()和post_randomize()函数。用户可以在类中定义这两个函数,分别在其中定义随机化前的行为和随机化后的行为
- 如果某个类中定义了pre_randomize()和post_randomize()函数,那么对象在执行randomize()之前或者之后会分别执行这两个函数。所以,pre_randomize()和post_randomize()函数可以看作是randomize()函数的回调函数(callback function)
1.2. 随机数函数
- SV提供了一些常用的系统随机函数,可以直接调用来返回随机数值
- $random()平均分布,返回32位有符号随机数
- $urandom()平均分布,返回32位无符号随机数
- $urandom_range()在指定范围内的平均分布
a = $urandom_range(3:10); // 值的范围是3~10
b = $urandom_range(10:3); // 值的范围是3~10
c = $urandom_range(5); // 值的范围是0~5
1.3. 随机化个别变量
- 在调用randomize()时可以传递变量的一个子集,这样只会随机化类里的几个变量
- 只有参数列表里的变量才会被随机化,其他变量会被当做状态变量而不被随机化
- 所有的约束仍然有效
- 所有被指定或者没有被指定rand的变量都可以作为randomize()的参数而被随机化
class Rising;
byte low; // 非随机变量,初始值为0
rand byte med, hi; // 随机变量,初始值为0
constraint up {
low < med;
med < hi;
}
endclass
module
initial begin
Rising r;
r = new();
r.randomize(); // 随机化med、hi,但不改变low
r.randomize(med); // 只随机化med
r.randomize(low); // 只随机化low
end
endmodule
【Q】
如下代码,例化r之后,先调用r.randomize(low),那么low、med、hi的组合可能是什么?
class Rising;
byte low;
rand byte med, hi; // Random varlable
constraint up {
low < med;
med < hi;
}
endclass
module test;
initial begin
Rising r;
r = new();
r.randomize(low);
$display(r.low, r.med, r.hi);
end
endmodule
A. low = -1, med = 0, hi = 0
B. low = -1, med = 1, hi = 2
C. low = 报错, med = 0, hi = 0
D. low = 报错, med = null, hi = 报错
【An】
约束不一致,无法被解决。
让执行r.randomize(low)的时候,med、hi没有被随机,默认为0,约束条件不满足,err
2. 数组约束
2.1. 数组的属性约束
- 在约束随机标量的同时,还可以对随机化数组进行约束
class dyn_size;
rand logic [31: 0] d[];
constraint d_size{
d.size() inside {[1:10]}; // 约束动态数组的大小
}
endclass
- 多数情况下,数组的大小应该给定范围,防止生成体积过大的数组或者空数组
- 还可以在约束中结合数组的其他方法sum()、product()、and()、or()和xor()
2.2. 约束数组中的元素
- SV可以利用foreach对数组的每一个元素进行约束
- 针对动态数组,foreach更合适于对非固定大小数组中每个元素的约束
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
2.3. 产生唯一元素值的数组
- 产生一个随机数组,它的每一个元素的值都是唯一的
方法1
class UniqueSlow;
rand bit [7:0] ua[64];
constraint c {
foreach (ua[i]) // 对数组中的每一个元素操作
foreach (ua[j])
if (i != j) // 除了元素自己
ua[i] != ua[j]; /// 和其他元素比较
}
endclass
方法2
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
说明:
rc8 = new(); 需要放在foreach循环外部,能很好的完成randc的要求,相当于一直在一副扑克牌中抽,1/255、1/254、1/253、1/252、…;如果放在里面,相当于每次抽牌都拿来一副新的扑克牌,1/255、1/255、1/255、1/255、…
2.4. 数组约束例题
class packed
rand bit [3:0] da[];
constraint da {
da.size() inside {[3:5]};
foreach (da[i])
da[i] <= da[i+1];
}
endclass
module test;
packet p;
initial begin
p = new();
p.randomize() with {da.size() inside {3, 5};} // 数组大小3或5
end
endmodule
【Q】
下面哪些可能是随机化的p.da的数组值?
A. da[0] = 0, da[1] = 2, da[2] = 4
B. da[0] = 0, da[1] = 2, da[2] = 4, da[3] = 7
C. da[0] = 0, da[1] = 2, da[2] = 4, da[3] = 7, da[4] = 10
D. 以上全错
【An】
以上全错。当约束da.size = 5,那么da数组的索引就是[0:4],foreach里面的循环变量i的索引就是[0:4],但进行到最后一遍循环的时候,da[4] <= da[5],da[5]索引超出范围了,约束冲突,随机化失败,err。修改方案如下,
foreach (da[i])
if (i <= da.size()-2) // i: [0:3]
da[i] <= da[i+1];
2.5. 随机化句柄数组
- 随机句柄数组的功能是在调用其所在类的随机函数时,随机函数会随机化数组中的每一个句柄所指向的对象。因此随机句柄数组的声明一定要添加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
module test;
RandArray ra;
initial begin
ra = new();
assert(ra.randomize() with {array.size == 2;});
foreach (ra.array[i])
$display (ra.array[i].value);
end
endmodule
因为RandStuff类中的变量没有进行随机化声明,默认为1。
3. 随机控制
3.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
3.2. 随机控制
- 可以使用randcase来建立随机决策树,但它带来的问题时没有变量可供追踪调试
initial begin
int len;
randcase
1: len = $urandom_range(0, 2); // 10%: 0, 1, 2
8: len = $urandom_range(3, 5); // 80%: 3, 4, 5
1: len = $urandom_range(6, 7); // 10%: 6, 7
end
3.3. 总结
- randsequence和randcase是针对轻量级的随机控制的应用。我们可以通过定义随即类取代上述随机控制的功能,并且由于类的继承性使得在后期维护代码时更加方便
- randsequence的相关功能我们在协调激励组件和测试用例时,可能会用到
- randcase则应对这随机约束中的dist权重约束+if-else条件约束的组合