第三章 随机化变量与随机化函数
各位同学,今天我们来聊聊验证中特别有意思的一个话题——随机化。说实话,我刚入行那会儿,对随机化是又爱又怕。爱的是它能帮我自动生成海量测试,怕的是它生成的玩意儿完全不受控。后来做多了才明白,关键就在于怎么用好 rand、randc 和 randomize() 这几个家伙。
3.1 rand 与 randc:两种随机风格
先说说最基础的 rand。这个关键字的意思是「每次随机化时,这个变量独立地取一个随机值」。说白了,就是每次调用 randomize(),它都重新掷一次骰子。
class Packet;
rand bit [7:0] length;
rand bit [3:0] priority;
endclass
Packet p = new();
repeat(5) begin
assert(p.randomize());
$display("length=%0d, priority=%0d", p.length, p.priority);
end
运行结果可能是:
length=127, priority=9
length=34, priority=2
length=200, priority=15
length=88, priority=7
length=127, priority=9 // 注意:可能重复!
看到了吗?rand 每次独立取值,所以可能出现重复值。这在某些场景下是好事,比如你想测试同一个长度值在不同优先级下的表现。但有些场景就不行了。
我个人习惯:当需要覆盖所有可能取值时,我会优先考虑 randc。比如地址遍历测试,用 randc 能保证每个地址都被访问到,不会漏掉边界情况。
那 randc 又是什么?它是「循环随机」的意思。它会遍历所有可能的值,直到全部取完一轮,才会重新开始。你想想看,这就像一副扑克牌,每次抽一张,抽完一轮才洗牌。
class AddressGen;
randc bit [3:0] addr; // 0~15,共16个值
endclass
AddressGen ag = new();
repeat(20) begin
assert(ag.randomize());
$display("addr=%0d", ag.addr);
end
输出会是:
addr=7, addr=2, addr=14, ... // 第一轮:0~15各出现一次
addr=3, addr=11, ... // 第二轮:重新开始新的一轮
避坑指南:我曾经在一个项目中用 randc 做 FIFO 深度测试,结果发现随机化器在每轮结束时总会卡一下。后来才意识到,randc 在遍历完一轮后需要重新计算排列,如果取值范围很大(比如 32 位),这个开销可不小。所以,randc 适合小范围遍历,大范围还是用 rand 加约束更靠谱。
3.2 randomize():随机化的核心函数
好,变量声明好了,怎么触发随机化?答案就是 randomize()。这个函数是 SystemVerilog 内置的,它会自动为所有 rand 和 randc 变量赋随机值。
调用方式很简单:
class MyClass;
rand int x, y;
constraint c { x > 0; y < 100; }
endclass
MyClass obj = new();
if (obj.randomize()) begin
// 随机化成功,x和y都有了新值
end else begin
// 随机化失败,可能是约束冲突
$error("Randomization failed!");
end
这里有个细节要注意:randomize() 返回的是 bit 类型,1 表示成功,0 表示失败。我建议你每次调用都用 assert 或者 if 检查返回值,否则随机化失败时你根本不知道。
重要提醒:randomize() 只会随机化 rand 和 randc 声明的变量。普通变量不会被修改。如果你想让某个变量在随机化时保持不变,就别加 rand 关键字。
3.3 pre_randomize 与 post_randomize:随机化的钩子函数
有时候,你需要在随机化前后做一些额外操作。比如,随机化前要设置一些初始值,随机化后要计算校验和。这时候 pre_randomize 和 post_randomize 就派上用场了。
这两个函数是 SystemVerilog 预留的回调函数。你只需要在类中定义它们,randomize() 会自动调用:
class Packet;
rand bit [7:0] data[];
rand int length;
bit [7:0] crc;
constraint c_data { data.size() == length; }
function void pre_randomize();
// 随机化前:设置默认值
this.length = 8; // 默认长度8
$display("Pre-randomize: setting default length to %0d", this.length);
endfunction
function void post_randomize();
// 随机化后:计算CRC
this.crc = 0;
foreach (data[i]) begin
this.crc ^= data[i];
end
$display("Post-randomize: computed CRC = 0x%0h", this.crc);
endfunction
endclass
运行流程是这样的:
- 调用
obj.randomize() - 自动执行
pre_randomize()(你可以在里面设初始值) - 随机化所有
rand/randc变量 - 自动执行
post_randomize()(你可以在里面做后处理) - 返回成功/失败状态
我遇到过的一个坑:有一次我在 pre_randomize 里修改了 rand 变量,以为这样能影响随机化结果。结果发现,pre_randomize 执行完后,randomize() 会覆盖掉我设的值。所以记住:pre_randomize 适合设非随机变量,或者记录随机化前的状态,别想着用它来「引导」随机化结果——那是约束该干的事。
3.4 知识体系总览
下面这张图帮你理清本章的核心逻辑:
3.5 实用技巧总结
| 特性 | rand | randc |
|---|---|---|
| 取值方式 | 每次独立随机 | 循环遍历所有值 |
| 重复可能性 | 高(可能连续重复) | 低(一轮内不重复) |
| 适用场景 | 一般随机测试、大范围 | 遍历测试、小范围覆盖 |
| 性能开销 | 低 | 高(大范围时明显) |
| 约束支持 | 完全支持 | 完全支持 |
我的个人建议:刚开始做验证时,可以先用 rand 搭框架,等覆盖率分析发现某些值没覆盖到,再考虑换成 randc 或者加约束。别一开始就追求完美随机,迭代优化才是正道。
好了,关于随机化变量和函数,今天就聊到这儿。记住三个核心点:rand 是独立随机,randc 是循环随机,pre_randomize 和 post_randomize 是随机化前后的钩子。下次写验证环境时,试试用这些特性让你的激励生成更智能。