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_range、data_valid 这种,一看就知道是干什么的。别用 c1、c2 这种,三个月后你自己都看不懂。
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 循环来初始化数组,结果编译不过——嗯,约束块里不能这么干。
$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)
下一章我们会聊约束表达式的高级用法,比如权重分布、集合操作这些。到时候你会发现,约束块能做的事情比你想的要多得多。
好,今天就到这。有问题欢迎交流。