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),加上一个约束求解器。

流程大致是这样的:

  1. PRNG 生成一个初始随机数种子
  2. 约束求解器检查这个种子是否满足所有约束
  3. 如果不满足,求解器会调整种子,直到找到合法值
  4. 如果找不到合法值,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 知识体系结构图

下面这张图总结了随机化机制的核心脉络,我建议你把它记在脑子里:

SystemVerilog 随机化机制 声明随机变量 约束条件 调用 randomize() rand / randc 关键字 rand:均匀分布,可重复 randc:循环随机,不重复 仅限 class 内部声明 约束块 constraint 范围约束:inside, dist 关系约束:==, !=, >, < 条件约束:if-else, -> randomize() 调用 返回 1:成功 返回 0:失败 支持内联约束 底层机制:PRNG(伪随机数生成器)+ 约束求解器 种子 → 生成随机数 → 检查约束 → 调整 → 输出合法值

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() 触发。就这么简单。

可可精品资料库