1. 随机激励基础:SystemVerilog中的随机化机制
做验证的兄弟都知道,芯片验证的核心就是「用随机来覆盖确定性」。说白了,我们没法手动写出所有可能的输入组合,那就让工具帮我们「碰」出那些边界情况。SystemVerilog的随机化机制,就是干这个活的。
我个人习惯把随机化分成三个层次来理解:声明随机变量 → 约束条件 → 调用随机化函数。今天我们先聊第一层——怎么声明一个随机变量。
1.1 rand 与 randc:两种随机模式
SystemVerilog里有两个关键字用来声明随机变量:rand 和 randc。它们有什么区别?我刚开始学的时候也搞混过,后来在项目里踩了一次坑才彻底明白。
| 关键字 | 含义 | 典型场景 |
|---|---|---|
rand |
每次随机化时,变量在取值范围内均匀分布 | 地址、数据、控制位等 |
randc |
循环随机,遍历完所有可能值后才重复 | 仲裁优先级、状态机状态等 |
rand 是「真随机」——每次调用 randomize(),它都可能取到相同的值。比如一个 2-bit 的 rand 变量,连续随机 10 次,可能 3 次都是 2'b00。
randc 是「循环随机」——它会记住上次取过的值,保证在遍历完所有可能值之前,不会重复。还是那个 2-bit 变量,用 randc 的话,前 4 次随机一定分别是 0、1、2、3(顺序不定),第 5 次才开始新一轮循环。
rand 是「每次独立」,randc 是「不重复遍历」。
1.2 随机化变量声明
声明随机变量很简单,在变量类型前面加上 rand 或 randc 就行。但要注意——只有类(class)里面的变量才能被随机化。module 里的变量不行,这是很多新手容易犯的错。
来看一个完整的例子:
class Packet;
rand bit [7:0] addr; // 地址,8位随机
rand bit [31:0] data; // 数据,32位随机
randc bit [1:0] priority; // 优先级,循环随机(0~3)
// 还可以声明随机数组
rand bit [7:0] payload[]; // 动态数组,长度也可随机化
rand int pkt_id; // 包ID
endclass
嗯,这里要注意:payload[] 是动态数组,它的长度默认不会自动随机化。如果你想让长度也随机,需要额外加约束。这个我们后面会讲。
1.3 随机化是怎么工作的?
声明了 rand 或 randc 之后,SystemVerilog 会在调用 randomize() 时,自动为这些变量生成随机值。它的底层机制其实是一个伪随机数生成器(PRNG),加上一个约束求解器。
流程大致是这样的:
- PRNG 生成一个初始随机数种子
- 约束求解器检查这个种子是否满足所有约束
- 如果不满足,求解器会调整种子,直到找到合法值
- 如果找不到合法值,
randomize()返回 0(失败)
我曾经在一个项目里遇到过:约束写得太紧,导致随机化经常失败。排查了半天才发现,是一个 inside 约束的范围写反了。从那以后,我每次写完约束都会先跑 100 次随机化,看看成功率。
$urandom() 或 $random() 在仿真中手动生成随机数,但它们的灵活度远不如 randomize()。我建议能用 randomize() 就别用系统函数。
1.4 随机化的调用方式
声明好随机变量后,怎么触发随机化?有两种方式:
Packet pkt;
pkt = new();
// 方式一:调用 randomize() 方法
if (pkt.randomize()) begin
$display("addr = %0d, data = %0d", pkt.addr, pkt.data);
end else begin
$error("随机化失败!");
end
// 方式二:使用 std::randomize() 函数(不常用)
// 这个函数可以随机化非类变量,但我不太推荐
我个人习惯用方式一。因为 randomize() 返回 0 或 1,可以很方便地判断是否成功。而且它支持内联约束,后面讲约束的时候会用到。
rand 或 randc 变量,调用 randomize() 会直接返回 1(成功),但什么都不会发生。别问我怎么知道的——我曾经 debug 了整整一个下午才发现这个「特性」。
1.5 知识体系结构图
下面这张图总结了随机化机制的核心脉络,我建议你把它记在脑子里:
1.6 避坑指南
最后分享几个我踩过的坑,希望能帮你省点时间:
- 别在 module 里用 rand——SystemVerilog 的随机化只支持 class,不支持 module。如果你非要在 module 里随机,可以用
$urandom,但那就失去约束能力了。 - randc 变量数量别太多——如果一个
randc变量的取值范围很大(比如 32-bit),它需要遍历 40 亿个值才能完成一轮循环。仿真时间会爆炸。我一般只在 2~4 bit 的变量上用randc。 - 记得检查 randomize() 返回值——很多新手写完
pkt.randomize()就直接用,不检查是否成功。万一约束冲突导致随机化失败,你拿到的全是 0,debug 起来很痛苦。
好了,随机化的基础就聊到这儿。记住三个关键词:rand、randc、randomize()。下一节我们聊约束怎么写,那才是随机化的灵魂所在。
rand 是「每次独立随机」,randc 是「循环不重复」。声明在 class 里,调用 randomize() 触发。就这么简单。