第三章 随机化变量与随机化函数

各位同学,今天我们来聊聊验证中特别有意思的一个话题——随机化。说实话,我刚入行那会儿,对随机化是又爱又怕。爱的是它能帮我自动生成海量测试,怕的是它生成的玩意儿完全不受控。后来做多了才明白,关键就在于怎么用好 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

运行流程是这样的:

  1. 调用 obj.randomize()
  2. 自动执行 pre_randomize()(你可以在里面设初始值)
  3. 随机化所有 rand/randc 变量
  4. 自动执行 post_randomize()(你可以在里面做后处理)
  5. 返回成功/失败状态

我遇到过的一个坑:有一次我在 pre_randomize 里修改了 rand 变量,以为这样能影响随机化结果。结果发现,pre_randomize 执行完后,randomize() 会覆盖掉我设的值。所以记住:pre_randomize 适合设非随机变量,或者记录随机化前的状态,别想着用它来「引导」随机化结果——那是约束该干的事。

3.4 知识体系总览

下面这张图帮你理清本章的核心逻辑:

随机化激励设计核心流程 声明随机变量 rand vs randc? rand:独立随机 randc:循环随机 调用 randomize() pre_randomize → 随机化 → post_randomize 图:随机化激励设计核心流程

3.5 实用技巧总结

特性 rand randc
取值方式 每次独立随机 循环遍历所有值
重复可能性 高(可能连续重复) 低(一轮内不重复)
适用场景 一般随机测试、大范围 遍历测试、小范围覆盖
性能开销 低 高(大范围时明显)
约束支持 完全支持 完全支持

我的个人建议:刚开始做验证时,可以先用 rand 搭框架,等覆盖率分析发现某些值没覆盖到,再考虑换成 randc 或者加约束。别一开始就追求完美随机,迭代优化才是正道。

好了,关于随机化变量和函数,今天就聊到这儿。记住三个核心点:rand 是独立随机,randc 是循环随机,pre_randomize 和 post_randomize 是随机化前后的钩子。下次写验证环境时,试试用这些特性让你的激励生成更智能。

可可精品资料库