3. SVA基础语法(一):断言的概念、立即断言与并发断言的基本结构

各位同学,今天我们正式开始啃SVA这块硬骨头。说实话,我刚接触断言那会儿,也觉得这东西不就是个检查器吗?后来在项目中吃过亏才明白——断言是芯片验证的“眼睛”,没有它,你就像在黑夜里摸路。

3.1 断言到底是什么?

断言,说白了就是一段“监视代码”。它时刻盯着你的信号,看它们是不是符合预期。如果不符合,就立刻报警。

我习惯把断言比作“交通摄像头”。你开车经过路口,摄像头拍到你闯红灯,咔嚓一张照片,罚单就来了。断言也一样——信号违规了,它就“咔嚓”一下,告诉你这里出问题了。

在SystemVerilog里,断言用 assert 关键字来写。但根据检查的时机不同,又分成两种:

  • 立即断言(Immediate Assertion):像“实时监控”,信号一变它就检查。
  • 并发断言(Concurrent Assertion):像“定时巡逻”,只在时钟沿检查。

嗯,这里要注意:两种断言各有各的用武之地,千万别混着用。

3.2 立即断言:最简单的检查器

立即断言长什么样?我直接给你看代码:

always_comb begin
  assert (data_valid == 1'b1) else $error("data_valid 应该为高,但现在是低!");
end

这段代码的意思是:只要 data_valid 不是高电平,就报错。注意,它写在 always_comb 里,所以是组合逻辑,信号一变就触发。

我在项目中遇到过一种情况:有人把立即断言写在 always_ff 里,结果仿真器在每个时钟沿都检查一次,但信号可能在时钟沿之间变化,导致漏报。所以记住——立即断言只适合组合逻辑或过程块

避坑指南: 我曾经在一个项目中,用立即断言检查一个多周期路径的信号。结果信号在中间状态时被断言抓到了,报了一堆假错。后来改成并发断言才解决。所以,立即断言只检查当前时刻的值,不关心时序关系

立即断言的基本语法很简单:

assert (expression) [else action_block]
  • expression:要检查的条件,必须为真。
  • else action_block:可选,条件为假时执行的动作,比如打印错误、停止仿真。

你想想看,如果条件为真,什么都不做;条件为假,才触发动作。这就是“断言”的本质——只关心异常情况

3.3 并发断言:时序检查的主力军

并发断言才是SVA的核心。它和时钟绑定,只在时钟沿采样信号,然后判断序列是否满足。

为什么需要并发断言?举个例子:你想检查“请求信号拉高后,两个时钟周期内必须收到应答”。用立即断言根本写不了,因为它不关心时间。但并发断言可以:

property p_req_ack;
  @(posedge clk) req |=> ##[1:2] ack;
endproperty

assert property (p_req_ack);

这段代码的意思是:在每个时钟上升沿,如果 req 为高,那么接下来1到2个时钟周期内,ack 必须为高。这就是并发断言的威力——它能描述时序关系

并发断言的基本结构分三部分:

  1. 序列(sequence):描述信号的变化模式。
  2. 属性(property):把序列组合成完整的检查条件。
  3. 断言(assert):把属性挂到仿真器上。

我习惯把这三部分比作“菜谱”:序列是食材,属性是烹饪步骤,断言是最后端上桌的菜。缺一个都不行。

3.4 立即断言 vs 并发断言:一张表说清楚

对比项 立即断言 并发断言
检查时机 信号变化立即检查 只在时钟沿检查
适用场景 组合逻辑、简单条件 时序逻辑、复杂序列
能否描述时序 不能 能(用 ## 操作符)
性能开销 稍大(但可接受)
典型写法 assert (a & b); assert property (@(posedge clk) a |=> b);

我个人建议:能用并发断言就别用立即断言。因为并发断言更规范,而且形式化验证工具(比如VC Formal)只支持并发断言。但如果你只是临时检查一个组合逻辑,立即断言更轻量。

3.5 一个完整的例子:从零写一个并发断言

咱们来写一个实际点的例子。假设有个握手协议:valid 拉高后,ready 必须在3个时钟内拉高,否则协议违规。

第一步,写序列:

sequence s_valid_ready;
  @(posedge clk) valid ##[1:3] ready;
endsequence

第二步,写属性:

property p_valid_ready;
  @(posedge clk) $rose(valid) |=> s_valid_ready;
endproperty

这里用了 $rose(valid),意思是“valid 的上升沿”。为什么不用 valid 本身?因为如果 valid 一直为高,你不想在每个时钟沿都触发检查,对吧?

第三步,挂断言:

assert property (p_valid_ready)
  else $error("valid 拉高后,ready 没有在3个时钟内响应!");

你看,三步走,清晰明了。我在项目中教新人写断言,都是让他们先写序列,再写属性,最后挂断言。这个顺序千万别搞反。

小技巧: 写并发断言时,我习惯先画一个时序图,把信号的波形画出来,再翻译成SVA。这样不容易漏掉边界情况。比如上面的例子,如果 ready 在第4个时钟才拉高,断言就会报错——这正是我们想要的。

3.6 常见错误与避坑

最后,分享几个我踩过的坑:

  • 忘了写时钟:并发断言必须指定时钟,否则仿真器不知道什么时候采样。我见过有人写 assert property (a |=> b); 没加 @(posedge clk),结果仿真报错。
  • 立即断言用在时序逻辑里:前面说过,立即断言不关心时钟,用在时序逻辑里会漏报或误报。
  • 断言写得太复杂:一个断言里塞了十几个信号,出了问题都不知道是哪个信号违规。我建议一个断言只检查一个行为

嗯,今天就先讲到这里。下一节我们会深入SVA的序列操作符,比如 ##andor 这些。到时候我会结合VC Formal工具,给大家演示怎么调试断言。咱们下节课见!