1. 断言是什么?为什么你需要它?
如果你做过数字电路设计或者验证,肯定遇到过这种情况:辛辛苦苦跑了好几个小时的仿真,结果发现波形里某个信号在某个时间点出现了不该有的毛刺,或者两个信号同时有效了。这种问题,靠人眼盯着波形看,效率低不说,还特别容易漏掉。SystemVerilog Assertions(SVA) 就是为了解决这个痛点而生的。
简单来说,断言就是嵌入在你设计代码里的“监视器”和“预言家”。它用声明式的语言,明确地告诉仿真器:“我的设计应该满足这样的行为规则”。比如,“读信号和写信号绝对不能同时为高”,或者“一个请求发出后,必须在接下来的两个时钟周期内收到应答”。仿真过程中,一旦这些规则被违反,断言就会立刻报错,把问题暴露在萌芽状态,而不是等到功能彻底出错才发现。
我刚开始接触断言时,也觉得它是一堆复杂的语法符号。但用久了才发现,它其实是提升验证效率和代码质量的神器。想象一下,你把设计的关键接口协议、状态机跳转规则、数据一致性要求都用断言写清楚,它们就会在每次仿真中自动帮你检查,相当于请了一个不知疲倦的“代码审查员”。这比写一大堆定向测试用例去覆盖这些场景要高效得多。
断言主要分两大类:即时断言(Immediate Assertions) 和并发断言(Concurrent Assertions)。即时断言就像你写在 always 块里的 if 语句,在程序执行到那个点时立刻检查条件,主要用于检查静态的、不依赖于时钟的组合逻辑条件。而并发断言才是SVA的精华,它基于时钟事件来描述跨越多个时钟周期的时序行为,是验证协议、接口和复杂状态机的核心工具。我们这篇文章的重点,就是带你从最基础的并发断言语法开始,一步步深入到高级的时序验证场景。
2. 从零开始:理解并发断言的核心语法
要玩转并发断言,你得先熟悉它的几个核心构建块:序列(Sequence) 和属性(Property)。你可以把序列想象成乐高积木里的小方块,它描述的是一个简单的、有时序关系的事件链。而属性则是用这些小方块搭建起来的完整模型,它定义了设计必须遵守的行为规则。
2.1 序列:描述事件的时间线
序列的基本语法很简单,就是用 ## 操作符来连接不同时钟周期的事件。##n 表示延迟 n 个时钟周期。
sequence s_req_ack;
req ##1 ack;
endsequence
这个序列 s_req_ack 描述的是:当 req 信号为高时,下一个时钟周期 ack 信号也必须为高。这常用来检查一个简单的握手协议。
但实际协议往往更灵活。比如,应答可能在1到3个周期内到来。这时就需要用到区间延迟操作符 ##[min:max]。
sequence s_flexible_ack;
req ##[1:3] ack;
endsequence
这个序列的意思是:req 为高后,ack 在接下来的第1、第2或第3个时钟周期为高都算匹配成功。这比写三个独立的序列方便多了。
2.2 属性:定义行为的规则
序列本身只是描述了“发生了什么”,我们需要用属性来声明“这应该发生”。属性用 property 关键字定义,里面可以包含序列,也可以直接写时序表达式。
property p_handshake;
@(posedge clk) req |-> ##[1:3] ack;
endproperty
assert property (p_handshake);
这里出现了一个关键操作符:蕴含(Implication) |->。它是断言逻辑的灵魂。a |-> b 的意思是:如果前提 a 发生了,那么后果 b 必须发生。如果前提 a 根本没发生,那这个属性就“空真”(vacuously true),直接通过检查。这非常符合我们的直觉:我们只关心在特定条件下设计的行为是否正确。
上面这个属性 p_handshake 就断言:每当在时钟上升沿采样到 req 为高,那么在接下来的1到3个周期内,必须采样到 ack 为高。我们用 assert 语句来启动对这个属性的检查。
2.3 采样与边沿检测:$rose, $fell, $stable, $past
在并发断言的世界里,所有信号的检查都基于“采样值”,默认是在时钟沿前的Preponed区域采样的。这就引出了几个非常实用的


977

被折叠的 条评论
为什么被折叠?



