3. 立即断言:语法、场景与并发断言的区别

好,咱们今天聊聊立即断言。说实话,这是断言里最基础、也最容易被忽视的一块。我刚入行那会儿,总觉得断言就得是高大上的并发断言,结果被 mentor 狠狠教育了一通——「你连立即断言都写不好,搞什么并发?」

嗯,咱们今天就把它彻底讲透。

3.1 立即断言语法

立即断言,说白了就是一个带检查的 if 语句。它的语法其实特别简单:

assert (expression) [else $fatal/$error/$warning]

核心就三部分:

  • assert 关键字:告诉工具我要做检查
  • 表达式:你要验证的条件,必须为真
  • else 分支:断言失败时执行的动作(可选但强烈建议加)

来看个实际例子:

always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    counter <= 0;
  end else if (en) begin
    counter <= counter + 1;
    // 立即断言:检查计数器是否溢出
    assert (counter < 8'hFF) else $error("Counter overflow detected!");
  end
end

这里我加了个 else 分支。为什么?因为默认的 assert 失败只会打印一条消息,但不会告诉你具体在哪。加上 $error 后,仿真器会直接定位到行号,调试效率翻倍。

重要提醒:立即断言是过程性语句,必须放在 always 块、initial 块或 function/task 里。你不能像并发断言那样直接写在 module 外面。

3.2 立即断言的使用场景

我在项目中总结出三个最常用的场景:

场景一:状态机非法状态检查

这个我踩过坑。有一次做 PCIe 控制器,状态机跑飞了,仿真跑了三天才发现。后来我学乖了:

always_ff @(posedge clk) begin
  case (state)
    IDLE: ...
    BUSY: ...
    DONE: ...
    default: assert (0) else $fatal("Illegal state detected: %0d", state);
  endcase
end

你看,default 分支里直接 assert(0),意思是「走到这里就是错」。这招帮我至少抓到了 5 个 bug。

场景二:输入数据合法性检查

我记得有一次,上游模块送来的数据长度居然为 0,导致下游除法器直接崩了。加个立即断言:

always_comb begin
  assert (data_len > 0) else $error("data_len must be positive, got %0d", data_len);
  // 后续逻辑...
end

你想想看,这种检查放在仿真阶段,比等到综合后才发现要省多少时间?

场景三:关键信号时序关系验证

有些信号之间的时序关系,用并发断言太重了,用立即断言刚刚好:

always_ff @(posedge clk) begin
  if (valid & ready) begin
    assert (data !== 'x) else $warning("Data is X when valid & ready!");
  end
end

我的习惯:对于「必须成立」的条件用 $fatal,对于「应该成立」的条件用 $error,对于「建议检查」的条件用 $warning。这样仿真时能快速区分问题的严重程度。

3.3 立即断言与并发断言的区别

好,这是很多人容易搞混的地方。我直接上表格对比:

对比维度 立即断言 并发断言
执行时机 在过程块中立即执行 在时钟边沿采样后评估
语法位置 always/initial/function/task 内部 module 内部或 interface 中
时序能力 只能检查当前时刻的值 支持 ## 延时、$past 等时序操作
适用场景 组合逻辑、状态机、数据合法性 协议时序、握手信号、流水线
仿真性能 轻量级,开销小 相对较重,需要采样器

说白了,区别就一句话:立即断言看的是「此时此刻」,并发断言看的是「一段时间内」

举个例子你就明白了:

// 立即断言:检查当前时钟周期 valid 和 ready 不能同时为高
always_ff @(posedge clk) begin
  assert (!(valid & ready)) else $error("Conflict!");
end

// 并发断言:检查 valid 拉高后,ready 必须在 3 个周期内拉高
assert property (@(posedge clk) valid |=> ##[1:3] ready);

看到了吗?立即断言只能检查「现在」,并发断言可以检查「未来几个周期内」。这就是本质区别。

我曾经犯过的错:在 always 块里用立即断言检查跨周期关系,比如「valid 拉高后下一拍 ready 必须拉高」。结果仿真全过了,上板就挂。因为立即断言只检查当前拍,根本看不到下一拍。后来全部改成并发断言才解决问题。

3.4 实战建议

最后给三点建议:

  1. 能用立即断言就别用并发断言——仿真跑得快,调试也简单。我见过有人连组合逻辑检查都用并发断言,纯属杀鸡用牛刀。
  2. 立即断言一定要加 else 分支——别偷懒。不加 else 的断言,失败了就一行消息,你根本不知道是哪条语句出的问题。
  3. 合理选择严重级别——$fatal 会终止仿真,$error 会报错但继续跑,$warning 只是提醒。根据场景选对级别,能省很多调试时间。

嗯,关于立即断言就聊这么多。下一章咱们讲并发断言,那才是真正体现断言威力的地方。到时候你会看到,同样的验证需求,用并发断言写出来有多优雅。