4. 激励生成基础:$random与$urandom、约束块、随机化类、权重分布
各位同学,今天我们来聊聊验证中最核心的一个话题——激励生成。说白了,就是怎么给我们的DUT(待测设计)喂数据。
我刚入行那会儿,写测试用例都是手动算好每个值,然后一个个塞进去。后来发现,这根本行不通。一个稍微复杂点的模块,手动构造的测试向量连10%的覆盖率都跑不到。嗯,从那时起,我就彻底倒向了随机化验证。
4.1 $random 与 $urandom:随机数的起点
SystemVerilog里最基础的随机函数,就是这两个。很多新手会问:它们到底有什么区别?
| 函数 | 返回值范围 | 特点 |
|---|---|---|
| $random | 32位有符号整数 | 可能返回负数,每次调用种子自动更新 |
| $urandom | 32位无符号整数 | 只返回正数,行为更可预测 |
我个人习惯用 $urandom。为什么?因为负数的处理在验证中容易出边界问题,你想想看,地址、长度这些值,哪有负的?
避坑指南:我曾经在一个项目中,用$random生成数据包长度,结果跑出了负数长度。仿真没报错,但后端的覆盖率模型直接崩了。排查了两天才找到原因——从那以后,我所有长度、地址类的随机,一律用$urandom。
// 基础用法示例
bit [7:0] data;
data = $urandom; // 生成0-255之间的随机数
data = $urandom_range(100, 50); // 生成50-100之间的随机数
4.2 约束块:让随机变得有意义
纯随机是没用的。你想想看,如果给一个FIFO的写地址随机生成0到1百万之间的值,但实际设计只支持0到1023,那99.9%的随机都是浪费仿真时间。
约束块就是干这个的——给随机加上边界,让它落在我们关心的区域。
class Packet;
rand bit [7:0] length;
rand bit [7:0] payload [$];
// 约束块
constraint valid_length {
length inside {[10:100]}; // 长度必须在10到100之间
length % 2 == 0; // 长度必须是偶数
}
constraint max_payload {
payload.size() == length;
}
endclass
这里要注意一个细节:约束块是声明式的,不是过程式的。你不需要告诉工具怎么去求解,它自己会找到满足所有约束的解。
我的经验:约束块不要写得太复杂。我曾经见过一个同事,一个约束块里写了20多个条件,结果仿真器求解花了5分钟。后来拆成3个约束块,速度提升了10倍。记住,约束是给工具看的,不是给你炫技的。
4.3 随机化类:把数据和行为打包
有了约束块,我们还需要一个容器来装这些随机数据。这就是随机化类的作用。
SystemVerilog中,用 rand 和 randc 来声明随机变量:
- rand:每次随机化时,变量独立取值
- randc:循环随机,在所有可能值遍历完之前,不会重复
class Transaction;
rand bit [31:0] addr;
randc bit [3:0] id; // 0-15循环随机,不会重复
constraint addr_range {
addr inside {[0:255], [1024:2047]};
}
function void display();
$display("addr=%0d, id=%0d", addr, id);
endfunction
endclass
// 使用示例
Transaction tr = new();
repeat(20) begin
assert(tr.randomize());
tr.display();
end
我个人特别喜欢 randc。为什么?因为它能保证所有ID都被覆盖到。在验证总线仲裁器时,我用randc来生成master ID,确保每个master都能被测试到,而不是随机到某个master被反复测试而其他master被冷落。
4.4 权重分布:控制随机倾向
有时候,我们希望某些值出现的概率更高一些。比如,测试一个缓存模块,我们希望大部分访问命中,少部分访问缺失。这时候就需要权重分布。
SystemVerilog提供了两种权重分布方式:
| 操作符 | 语法 | 说明 |
|---|---|---|
| := | value := weight | 指定具体值的权重 |
| :/ | value :/ weight | 指定范围的权重,内部均匀分布 |
class CacheTest;
rand bit [1:0] access_type;
constraint weight_dist {
access_type dist {
0 := 70, // 读操作,权重70%
1 := 20, // 写操作,权重20%
2 :/ 10 // 其他操作,权重10%
};
}
endclass
注意:权重分布不是精确的概率控制。它只是告诉求解器「倾向于」选择某个值。如果你需要精确的序列控制,应该用randc或者手动构造序列。
我在项目中遇到过这样一个场景:测试一个网络交换机,需要模拟90%的普通包和10%的控制包。用权重分布轻松搞定,而且仿真效率很高。如果用手动构造,你得写一堆条件判断,代码量翻倍不说,还容易出错。
知识体系总览
下面这张图,是我自己总结的激励生成知识体系。每次带新人,我都会先让他们看这张图,建立整体认知。
这张图把四个知识点串起来了。你从下往上看:最底层是随机函数,提供原始随机能力;往上是约束块,给随机加规则;再往上是随机化类,把数据和规则打包;最顶层是权重分布,控制随机倾向。每一层都在前一层的基础上增加了智能性。
好了,关于激励生成的基础,我就讲到这里。记住,验证的核心不是写多少代码,而是用最少的代码覆盖最多的场景。随机化就是实现这个目标的最强工具。
最后说一句:别怕随机化。刚开始可能觉得不可控,但一旦你掌握了约束和权重的技巧,你会发现随机化比手动构造高效得多。我在项目中用随机化发现的bug,比手动构造多出至少3倍。这就是它的价值。