2. 立即断言:基础中的基础
各位同学,咱们今天聊聊断言验证里最基础、也最直接的一种——立即断言。
我记得刚入行那会儿,带我的老工程师跟我说:“断言这东西,说白了就是给仿真器装上一双眼睛。” 我当时还不太理解,后来踩了不少坑才明白——没有断言,你就是在黑盒里瞎摸。
好,咱们直接进入正题。
2.1 什么是立即断言?
立即断言,英文叫 Immediate Assertion。它本质上就是一个运行时检查。
你想想看,写 RTL 的时候,我们经常用 if-else 判断条件。立即断言就是把这个判断“固化”下来,一旦条件不满足,仿真立刻报错。
它的语法很简单:
assert (expression) [else $fatal/$error/$warning/$info];
注意,这里没有 property,没有 sequence,就是一个纯粹的布尔表达式。这也是它和并发断言最大的区别。
核心要点:立即断言是过程性的,它必须写在 initial、always 或者 task/function 里。它不像并发断言那样可以独立于过程块存在。
2.2 使用场景:什么时候该用它?
我个人习惯把立即断言用在三个地方:
- 接口协议检查:比如握手信号的时序、数据有效标志的配合。
- 内部状态机检查:状态跳转是否合法、状态编码是否冲突。
- 数据通路完整性检查:数据在流水线中是否被意外修改。
举个例子,我在项目中遇到过这样一个场景:一个 AXI 从机接口,要求 awvalid 和 awready 不能同时为高超过一个周期。我当时就用立即断言来抓这个违规:
always @(posedge clk) begin
if (awvalid & awready) begin
assert (awvalid & awready) else $error("awvalid和awready同时为高超过1周期!");
end
end
嗯,这里要注意,这个写法其实有个小问题——它只在 awvalid & awready 为真的那个周期检查一次。如果持续多个周期,它只会报一次错。但对我们调试来说,一次就够了。
2.3 代码示例:从简单到复杂
咱们来看几个实际例子。
示例1:最简单的检查
module test_assert;
logic clk, rst_n;
logic [7:0] data;
always @(posedge clk) begin
if (!rst_n) begin
data <= 8'h00;
end else begin
data <= data + 1;
end
// 立即断言:data不能超过100
assert (data <= 8'd100) else $fatal("data overflow!");
end
endmodule
这个例子很简单。但我想提醒你:立即断言的位置很重要。如果你把断言写在赋值之前,检查的是旧值;写在赋值之后,检查的是新值。我见过不少新手在这里翻车。
示例2:带 else 分支的断言
always @(posedge clk) begin
if (valid) begin
assert (data != 0)
else $error("valid时data不能为0,当前data=%0d", data);
end
end
这里我用了 $error,它会让仿真继续,但会打印错误信息。如果你用 $fatal,仿真会直接停止。我个人习惯:调试阶段用 $fatal,回归阶段用 $error。为什么?因为调试时我希望立刻停下来看波形;回归时我希望跑完所有 case,统计错误数量。
示例3:状态机检查
typedef enum {IDLE, READ, WRITE, DONE} state_t;
state_t state, next_state;
always @(posedge clk) begin
if (!rst_n) begin
state <= IDLE;
end else begin
state <= next_state;
end
// 检查:状态不能是未知态
assert ($onehot({state, next_state}))
else $warning("状态编码异常!");
end
小技巧:用 $onehot 检查状态编码是个好习惯。我曾经在一个项目中,因为状态机编码冲突,导致芯片功能异常。后来加了这条断言,问题立刻暴露。
2.4 常见错误:我踩过的坑
下面这些错误,我几乎都犯过。你注意听。
| 错误类型 | 错误示例 | 正确做法 |
|---|---|---|
| 断言写在过程块外 | assert (a & b); 写在 module 里 |
必须写在 always、initial 或 task 中 |
| 忘记 else 分支 | assert (cond); 没有 else |
至少加 else $error,否则断言失败不报错 |
| 断言位置错误 | 在赋值前检查新值 | 理解阻塞/非阻塞赋值的时序 |
| 使用 $fatal 过度 | 每个断言都用 $fatal | 区分致命错误和普通错误 |
避坑指南:我曾经在一个项目中,把所有断言都写成了 assert (cond) else $fatal。结果仿真跑到一半,因为一个非关键路径的断言失败,整个仿真停了。后来我花了整整一天去排查,才发现那个失败其实不影响功能。从那以后,我学会了区分 $fatal 和 $error。
2.5 知识体系图
下面这张图,帮你理清立即断言的核心脉络:
2.6 小结
立即断言,说白了就是给仿真加个“哨兵”。它简单、直接、有效。但正因为简单,很多人容易忽略细节。
我个人建议:每个模块至少加 3-5 个立即断言。别嫌多,等你调试的时候就知道它们有多香了。
好,这一章就到这儿。记住:断言不是负担,是你的护身符。
可可精品资料库