第三章 随机化基础:$urandom、$random、rand与randc、约束块基础

3.1 为什么需要随机化?

做验证的,谁没被定向测试坑过?

我刚入行那会儿,写测试用例都是手填数据。一个地址写死,一个数据写死。跑一百遍,结果都一样。后来项目复杂度上来了,这种写法根本覆盖不到边界情况。说白了,你永远不知道芯片会在哪个犄角旮旯出问题。

随机化就是干这个的。让工具帮你生成各种合法、非法的输入组合。你只需要定好规则,剩下的交给随机数发生器。

嗯,这里要注意:随机不是乱来。随机化是有约束的随机。我们后面会细说。

3.2 SystemVerilog 里的随机函数

先聊聊最基础的几个随机函数。它们就像工具箱里的螺丝刀,各有各的用法。

$random 和 $urandom

$random 是 Verilog 时代的老将。每次调用返回一个 32 位有符号整数。范围是 -2^31 到 2^31-1。

reg [31:0] data;
data = $random;  // 可能得到负数

我个人不太喜欢 $random。为什么?因为它会生成负数。在验证里,负数地址、负数长度,很多时候是非法场景。你还要额外处理符号位,麻烦。

$urandom 就好多了。无符号,32位,0 到 2^32-1。干净利落。

reg [31:0] addr;
addr = $urandom;  // 全是正数

我在项目中遇到过一个问题:用 $random 生成数据包长度,结果跑出来一堆负数长度。仿真直接崩了。从那以后,我统一改用 $urandom。

小技巧: 想生成特定范围的值?用取模运算。
addr = $urandom % 256; // 生成 0~255 的地址

$urandom_range

这个函数更贴心。直接指定上下界。

data = $urandom_range(100, 200);  // 生成 100~200 之间的数

注意参数顺序:最大值在前,最小值在后。写反了也不会报错,但结果可能不是你想要的。

3.3 rand 与 randc:类成员的随机化

函数调用虽然方便,但在复杂场景下不够用。我们需要把随机变量封装到类里。

这时候就该 rand 和 randc 出场了。

rand:普通随机

每次随机化时,变量在合法范围内独立取值。可能出现重复值。

class Packet;
  rand bit [7:0] length;
  rand bit [31:0] data;
endclass

Packet p = new();
p.randomize();  // length 和 data 同时被随机化

每次调用 randomize(),length 和 data 都会重新取值。可能连续两次取到相同的值。

randc:循环随机

randc 保证在所有可能值遍历完之前,不会重复。就像一副扑克牌,抽完一轮才洗牌。

class Command;
  randc bit [1:0] opcode;  // 0,1,2,3 轮流出现
endclass

我记得有一次验证一个状态机,需要测试所有命令的排列组合。用 randc 就特别合适。它天然保证了覆盖率。

注意: randc 的循环周期取决于变量的位宽。位宽越大,遍历一轮需要的随机化次数越多。8位变量需要256次才能完成一轮。如果约束条件很紧,可能永远无法完成一轮遍历。

3.4 约束块基础

随机化如果没有约束,那就是瞎蒙。约束块就是给随机化加上规矩。

约束块的写法

class MyData;
  rand bit [7:0] addr;
  rand bit [7:0] data;

  constraint addr_range {
    addr inside {[0:100]};  // 地址范围 0~100
  }

  constraint data_valid {
    data != 8'h00;          // 数据不能为0
    data != 8'hFF;          // 数据不能为全1
  }
endclass

约束块写在类内部,用 constraint 关键字声明。可以写多个约束块,也可以写在一个块里。我个人习惯按功能拆分,这样好维护。

约束的常用操作符

操作符 说明 示例
inside 值在集合内 addr inside {[0:100], 200, 255}
dist 概率分布 len dist {1:=10, [2:5]:=20}
if-else 条件约束 if (mode == 1) { len == 8; }
-> 蕴含约束 mode == 1 -> len == 8;

你想想看,如果没有约束,随机出来的地址可能是 0xFFFFFFF。这在真实芯片里根本不存在。约束就是把这些不可能的情况排除掉。

约束的求解过程

约束求解器会做两件事:

  1. 检查所有约束是否一致(有没有矛盾)
  2. 在所有合法解中随机选一个

如果约束写矛盾了,比如同时要求 addr > 100 和 addr < 50,求解器会报错。我曾经在调试一个 bug 时,花了半天才发现是约束冲突。从那以后,我每次写完约束都会先跑一遍空仿真,确认没有解算失败。

3.5 知识体系总览

下面这张图把本章的核心内容串起来了。你可以把它当作一个快速索引。

随机化基础体系 随机函数 类成员随机化 约束块 $random 有符号 32 位 $urandom 无符号 $urandom_range 范围控制 rand 每次独立随机 randc 循环遍历所有值 inside / dist if-else / -> 蕴含 约束一致性检查 求解器自动解算 核心思想:有约束的随机才是有效的随机 定向测试 + 随机化 = 高效的验证策略

3.6 避坑指南

讲几个我踩过的坑,你遇到了能少走弯路。

坑一:约束过于严格导致无解
我曾经写过一个约束,要求地址既是偶数又是奇数。求解器直接罢工。检查约束时,记得看看有没有逻辑矛盾。
坑二:randc 变量位宽太大
32位的 randc 变量,遍历一轮需要 40 亿次随机化。仿真跑一天都跑不完。小范围用 randc,大范围用 rand。
坑三:忘记调用 randomize()
声明了 rand 变量,但没调 randomize()。变量值永远是 X 或 0。这种 bug 很难查,因为代码看起来没问题。

3.7 小结

随机化是验证的基石。从简单的 $urandom 到带约束的 rand 变量,每一步都让测试更智能。

记住:随机不是目的,覆盖才是。约束就是帮你把随机引导到正确的方向上。

下一章我们会深入约束的高级用法。到时候再聊。


可可精品资料库