3、约束块入门:constraint块语法,约束块的位置与作用域,约束块的开启与关闭

好,咱们今天聊聊约束块。这是随机化验证里最基础、也最核心的一块。

说白了,约束块就是告诉仿真器:「嘿,你随机出来的数据,得按我的规矩来。」没有约束的随机,那叫瞎蒙。有了约束,才是我们想要的「受控随机」。

3.1 constraint块语法:怎么写一个约束?

语法其实很简单。你定义一个类,然后在类里面写 constraint 关键字,后面跟一个名字,再跟一个大括号。大括号里放的就是你的约束条件。

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

    constraint addr_range {
        addr inside {[0:100]};
    }
endclass

你看,这个 addr_range 约束块,就限制了 addr 只能在 0 到 100 之间随机。嗯,这里要注意:变量前面必须加 rand 或者 randc 关键字,不然约束块对它不起作用。

我个人习惯把约束块写在类的最后面,变量声明之后。这样代码读起来比较顺——先看有什么变量,再看变量怎么被约束。

小技巧: 约束块的名字可以随便起,但最好起得有意义。比如 addr_rangedata_valid 这种,一看就知道是干什么的。别用 c1c2 这种,三个月后你自己都看不懂。

3.2 约束块的位置与作用域

约束块必须写在类里面。你不能在类外面写一个约束块,然后指望它约束类里面的变量——编译器会直接报错。

作用域呢?约束块里能访问类里的所有成员变量,包括 rand 和非 rand 的。但要注意,约束块里只能写约束表达式,不能写过程代码(比如 if-else 语句、for 循环这些)。

class packet;
    rand bit [7:0] len;
    rand bit [7:0] payload[];

    constraint len_c {
        len inside {[1:10]};
        payload.size() == len;  // 约束数组大小等于len
    }
endclass

这里 payload.size() == len 就是一个约束表达式。它告诉仿真器:数组的大小必须等于 len 的值。我在项目中遇到过有人想在这里写 for 循环来初始化数组,结果编译不过——嗯,约束块里不能这么干。

注意: 约束块里不能调用任务(task)或函数(function),除非这个函数是纯组合逻辑的、没有副作用的。我曾经看到有人试图在约束块里调用 $display 来打印调试信息,结果仿真器直接报错。想调试?用 randomize() 之后的 post_randomize() 函数。

3.3 约束块的开启与关闭

有时候,你不想用某个约束。比如默认情况下 addr 要在 0~100 之间,但某个测试用例需要 addr 在 200~255 之间。怎么办?

SystemVerilog 提供了两种方式:

  • 约束块名 + .constraint_mode():可以单独开关某个约束块。
  • 直接写多个约束块:需要时打开,不需要时关掉。
class my_packet;
    rand bit [7:0] addr;

    constraint addr_low {
        addr inside {[0:100]};
    }

    constraint addr_high {
        addr inside {[200:255]};
    }
endclass

// 使用示例
my_packet pkt = new();

// 默认两个约束都生效?不,这样会冲突,随机会失败
// 所以我们需要手动开关

pkt.addr_low.constraint_mode(0);   // 关闭低地址约束
pkt.addr_high.constraint_mode(1);  // 打开高地址约束
assert(pkt.randomize());
// 此时 addr 在 200~255 之间

你看,constraint_mode(0) 就是关掉,constraint_mode(1) 就是打开。默认情况下,所有约束块都是打开的(constraint_mode(1))。

为什么会这样设计?说白了,就是为了复用。你写一个通用的约束类,不同的测试用例可以灵活地开关不同的约束块,而不需要修改类的定义。

核心要点:
  • 约束块用 constraint 关键字定义,写在类内部。
  • 约束块里只能写约束表达式,不能写过程代码。
  • constraint_mode() 来开关约束块,0 是关,1 是开。
  • 多个约束块同时生效时,它们之间是「与」的关系——所有条件都得满足。

3.4 避坑指南:我踩过的几个坑

嗯,这里分享几个我实际项目中遇到的坑,你们注意一下。

坑一:约束冲突导致随机失败

我曾经写过一个约束,既要求 len 在 1~10 之间,又要求 len 大于 20。结果仿真器直接报随机化失败。记住:约束之间不能矛盾,否则仿真器找不到解。

坑二:忘记加 rand 关键字

这个我犯过不止一次。变量声明时忘了写 rand,然后约束块写得再漂亮也没用——仿真器根本不随机它。嗯,检查一下:你要随机的变量,前面有没有 rand

坑三:约束块里用了 if-else

约束块里不能用 if-else 语句,但可以用 if-else 约束表达式。区别在哪?

// 错误写法
constraint bad_c {
    if (mode == 1) {
        addr inside {[0:100]};
    } else {
        addr inside {[200:255]};
    }
}

// 正确写法
constraint good_c {
    (mode == 1) -> addr inside {[0:100]};
    (mode != 1) -> addr inside {[200:255]};
}

看到了吗?约束块里用 -> 操作符来表示条件约束。左边是条件,右边是约束。条件成立时,右边的约束才生效。

我的建议: 刚开始学约束块时,先写简单的、单个的约束。等熟练了,再尝试组合多个约束块。别一上来就写复杂的条件约束,容易把自己绕晕。

3.5 小结

约束块入门其实不难。记住三件事:

  • 怎么写:constraint 名字 { 约束表达式; }
  • 写在哪:类里面,变量声明之后
  • 怎么开关:约束块名.constraint_mode(0/1)

下一章我们会聊约束表达式的高级用法,比如权重分布、集合操作这些。到时候你会发现,约束块能做的事情比你想的要多得多。

好,今天就到这。有问题欢迎交流。