SV随机化约束

CRT机制

  • 在复杂数字电路中,完全依赖定向测试(手动编写特定测试用例)难以覆盖所有边界情况和异常场景
  • 受约束的随机测试(Constrained Random Testing,CRT)是产生复杂设计所需激励的唯一可行的方法

关键词

  • rand:声明普通随机变量,随机化范围内的取值概率是等同的,可能重复
  • randc:声明循环随机变量,所有可能值都被取过之前不重复
1
2
3
4
class Packet;
rand bit [7:0] addr; // 随机8位地址
randc bit [1:0] priority; // 循环随机优先级(0、1、2、3不重复出现)
endclass

约束块

  • 通过constraint块限制随机值的范围,可以使用特定的约束方法生成特定的随机序列
1
2
3
constraint addr_range {
addr inside {[0:100]}; // 限制addr在0到100之间
}

随机化方法

  • 调用randmozie()方法为随机变量选取满足所有约束条件的新值
    • 若随机化成功,则返回1,否则返回0
    • 当调用randomize()函数时只传递变量的一个子集,则只会随机化子集中的变量
    • randmozie()是类的方法,需要通过对象调用,不能直接作为独立函数使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class packet;
randc bit [31:0] addr; // 随机化地址
rand bit [7:0] data; // 随机化数据
int test;

constraint valid_addr { // 约束:地址在0x1000~0xFFFF范围内
addr inside {[32'h1000:32'hFFFF]};
}

constraint non_zero_data {
data != 0; // 约束:数据非零
}
endclass

packet pkt0,pkt1;
initial begin
pkt0 = new();
pkt1 = new();

pkt0.randomize(); // 随机化addr, data
pkt0.randomize(addr); // 随机化addr,不随机化data
pkt0.randomize(test); // 随机化test,即使不是随机变量

if (!pkt1.randomize()) // 成功返回1
$finish;
transmit(pkt1);
end

约束方法

唯一值约束

  • 唯一值约束来确保一组变量在随机化时被赋予互不相同的值
  • unique操作符:约束块内使用unique { }包裹变量集合;也可以对数组使用,确保数组中所有元素的值都是唯一的
1
2
3
4
5
6
7
class Packet;
rand bit [3:0] src_id, dst_id, seq_num; // 三个4位随机变量

constraint c_unique_ids {
unique { src_id, dst_id }; // 保证 src_id 和 dst_id 的值不同
} // seq_num 的值可以与它们相同,也可以不同,不受此约束影响
endclass

范围约束

  • 范围约束用于限制随机变量的取值范围,确保随机化值落在指定的区间集合内
    • 也可以把集合里的值保存到数组中,再进行范围约束
  • inside操作符指定范围,!inside指定排除范围
1
2
3
4
5
6
7
8
9
10
11
12
13
14
rand bit [7:0] data1, data2, data3;
bit [7:0] vals = '{1,2,3,5,8};

constraint c_range {
data1 inside { [0:100], 200, 201, 255 }; // 约束 data 在 0 到 100 之间,或者是 200, 201, 255
}

constraint c_exclude {
data2 !inside { [10:20] }; // data 的值不能在 10 到 20 之间
}

constraint c_fibonacci {
data3 inside vals;
}
  • 关系操作符><>=<===
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
rand int value;
rand bit [7:0] data;
bit [7:0] vals = '{1,2,3,5,8};

constraint c_relation {
value >= 50;
value < 1000;
}

constraint c_fibonacci { // 等价于上例中的c_fibonacci约束块
(data == vals[0]) || // data==1
(data == vals[1]) || // data==2
(data == vals[2]) || // data==3
(data == vals[3]) || // data==5
(data == vals[4]) || // data==8
}

条件约束

条件语句

  • if-else结构
1
2
3
4
5
6
7
8
9
rand bit mode;
rand int addr;

constraint c_cond_if {
if (mode == 0) // 如果 mode 为 0
addr inside { [0 : 127] }; // 地址在 0-127
else // 否则 (mode 为 1)
addr inside { [128 : 255] }; // 地址在 128-255
}

蕴含操作

  • 蕴含操作符->,逻辑上描述为“如果…,那么…”,可用于替代if-else
1
2
3
4
constraint c_cond_imply {
(mode == 0) -> (addr inside { [0 : 127] });
(mode == 1) -> (addr inside { [128 : 255] });
}
  • 蕴含操作A -> B逻辑等价于!A || B,其逆否命题!B->!A有相同的逻辑表达式和真值表
A B A→B,!B->!A
1
2
3
4
constraint c_cond_imply_contra {                  // c_cond_imply的等价约束
(addr !inside { [0 : 127] }) -> (mode == 1); // 对于bit mode, (mode != 0)就是(mode == 1)
(addr !inside { [128 : 255] }) -> (mode == 0);
}
  • 以上两个蕴含操作的例子都定义了以下行为
    • addr必须落在[0,255][0,255]之间,否则随机化失败
    • mode为0时,addr随机约束在[0,127][0,127]之间
    • mode为1时,addr随机约束在[128,255][128,255]之间

概率约束

引导顺序

  • 使用solve...before引导约束求解器的求解顺序,间接影响随机结果的概率分布,适用于存在约束关系的变量
  • 下例为不引导顺序的蕴含操作,a随机化概率并不均匀
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Example;
rand bit a;
rand bit [1:0] b;

constraint c { a -> b == 0; } // 若a为0,此约束不起作用;若a为1,则b必须为0
endclass

// 概率分布
P(a == 0) = 80%
P(b == 0 | a=0) = 25%
P(b == 1 | a=0) = 25%
P(b == 2 | a=0) = 25%
P(b == 3 | a=0) = 25%
P(a == 1) = 20%
P(b == 0 | a=1) = 100% (唯一选择)
P(b == 1 | a=1) = 0%
P(b == 2 | a=1) = 0%
P(b == 3 | a=1) = 0%
  • 此时若希望a的随机化概率均匀分布,就可以使用solve...before来引导求解顺序,间接影响概率分布
    • 告诉约束求解器,先随机化a在随机化b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Example;
rand bit a;
rand bit [1:0] b;

constraint c { a -> b == 0; } // 定义解空间
constraint prob_guide { solve a before b; } // 引导概率分布
endclass

// 概率分布
P(a == 0) = 50%
P(b == 0 | a=0) = 25%
P(b == 1 | a=0) = 25%
P(b == 2 | a=0) = 25%
P(b == 3 | a=0) = 25%
P(a == 1) = 50%
P(b == 0 | a=1) = 100% (唯一选择)
P(b == 1 | a=1) = 0%
P(b == 2 | a=1) = 0%
P(b == 3 | a=1) = 0%
  • 上述两例的解空间及概率分布如下
a b 是否合法 不引导顺序概率 引导顺序概率
0 0 1/5 1/8
0 1 1/5 1/8
0 2 1/5 1/8
0 3 1/5 1/8
1 0 1/5 1/2
1 1 0 0
1 2 0 0
1 3 0 0

权重分布

  • 使用dist操作符直接定义随机结果的概率权重,适用于独立变量
  • :=操作符:指定单个值的权限,所有权重值是绝对的
1
2
3
4
5
6
7
8
9
rand int opcode;

constraint c_dist {
opcode dist {
0 := 5, // opcode=0 的概率是 5/(5+3+2) = 50%
1 := 3, // opcode=1 的概率是 3/(5+3+2) = 30%
2 := 2 // opcode=2 的概率是 2/(5+3+2) = 20%
};
}
  • :/操作符:指定一个值范围的权重,该权重会平均分配给范围内的每一个值
1
2
3
4
5
6
7
8
rand int delay;

constraint c_dist_range {
delay dist {
[0:2] :/ 6, // 权重6平均分给0,1,2三个值,每个值权重为2,P(delay=0/1/2) = 2/10 = 20%
10 :/ 4 // 值10的权重为4,P(delay=10)= 4/10 = 40%
};
}

软约束

  • 所有约束默认都是硬约束;使用soft关键词声明的软约束在冲突时可被忽略
1
2
3
4
5
6
7
class Transaction;
rand int length;
soft constraint c_default_len { length inside { [32:64] }; } // 软约束
endclass

Transaction t = new;
t.randomize() with { length == 128; }; // 由于外部约束是硬的,c_default_len软约束会被违反,最终 length=128

内嵌约束

  • 在调用randomize()的同时,使用with关键词临时、局部地添加额外的约束

用法

  • 可以用来进一步限制变量的取值范围
1
2
3
4
5
6
7
8
9
10
class Packet;
rand int length;
constraint c_len { length inside {[64:1518]}; } // 通用约束:正常包长
endclass

initial
Packet pkt = new;
pkt.randomize(); // 通用随机化,生成64-1518的包
pkt.randomize() with { length inside {[64:128]}; }; // 使用内嵌约束,临时覆盖c_len,只生成小包
end
  • 可以用来临时定义类中变量中的关系
1
2
3
4
5
6
7
class Transaction;
rand bit [7:0] addr;
rand bit [7:0] data;
endclass

Transaction tr = new;
tr.randomize() with { data == (addr << 1); }; // 临时约束:让data的值总是addr的两倍(一个在类定义中不存在的规则)
  • 可以用来引用所在作用域的局部变量
1
2
3
4
5
task test_case(int desired_addr);
Packet pkt = new;
pkt.randomize() with { addr == desired_addr; }; // 使用任务传入的参数desired_addr作为约束条件
// ... 其他测试逻辑
endtask

优先级

  • 内嵌约束的优先级高于类内部定义的约束
    • 当内嵌约束与对象内部的约束发生冲突时,约束求解器会优先满足内嵌约束
    • 如果无法同时满足内嵌约束和内部约束,randomize()方法会失败并返回0
      • 可以通过在内部使用软约束,使冲突可被忽略
      • 或者通过禁用函数constraint_mode(0)禁止冲突的约束
1
2
3
4
5
6
7
8
9
10
11
class Simple;
rand int x;
constraint c_x { x > 10; } // 内部约束:x必须大于10
endclass

Simple s = new;
int status;

status = s.randomize() with { x < 20; }; // 内嵌约束与内部约束不冲突,结果:10 < x < 20,成功 (status=1)

status = s.randomize() with { x < 5; }; // 内嵌约束与内部约束冲突,冲突:无法同时满足 x>10 和 x<5,随机化失败 (status=0)

外部约束

  • 函数的函数体能在类的外部定义,同样约束的约束体也可以在类的外部定义
    • 和类的原型方法一样,外部约束必须事先在原来的类里定义外部约束的原型
    • 这种方法只能增加约束,不能改变已有的约束;相比于临时性的内嵌约束,具有更好的可复用性
  • 先定义一个只有空约束的类,后续在不同的测试里定义这个约束的不同版本以产生不同的激励
1
2
3
4
5
6
7
8
9
10
11
12
13
// packet.sv
class Packet;
rand bit [7:0] length;
rand bit [7:0] payload[];
constraint c_external;
endclass

// test.sv
program automatic test;
`include "packet.sv"
constraint Packet::c_external {length == 1;}
...
endprogram

迭代约束

随机数组

  • 使用foreach用于约束随机数组的每一个元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class RandArray;
rand byte payload[]; // 动态随机数组

constraint c_array_size {
payload.size() inside { [1:MAX_SIZE] }; // 约束数组大小
}

constraint c_array_vals {
foreach (payload[i]) {
payload[i] inside { [8'h00:8'h7F] }; // 约束每个元素的值
if (i > 0) {
payload[i] > payload[i-1]; // 约束元素值递增
}
}
}

function new();
payload = new[MAX_SIZE]; //按最大的容量分配
endfunction;
endclass

随机序列

  • 如果想为一个复杂的、多层次的协议生成激励,需要建立一个随机对象数组(随机序列)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Transaction; //简单事务
rand bit [3:0] src,dst;
endclass

class Transaction_seq;
rand Transaction items[10]; //事务句柄数组

function new();
foreach (items[i])
items[i] = new();
endfunction

constraint c_ascent { // 每个dst地址都比前一个大
foreach (items[i])
if (i>0) items[i].dst > items[i-1].dst;
}
endclass

initial begin
seq = new();
`SV_RAND_CHECK(seq.randomize());
foreach (seq.items[i])
$display("item[%0d] = %d", i, seq.items[i].dst);
end