3. SVA序列与属性:sequence、property的构建与层次化

各位同学,今天我们来聊聊SVA里最核心的两个概念——sequence和property。说实话,我刚接触SystemVerilog Assertion那会儿,也被这两个东西绕得晕头转向。后来做项目做多了,才慢慢摸清楚它们的脾气。

简单来说,sequence描述的是信号在时间轴上的行为模式,而property则是在sequence的基础上加上逻辑判断和时序约束。你可以把sequence想象成“发生了什么”,property则是“这个发生应该满足什么条件”。

3.1 sequence:时间上的“故事线”

sequence说白了就是一组信号在时钟节拍上的变化序列。我习惯把它叫做“时间线上的故事”。

核心语法:

sequence  sequence_name;
  信号表达式  [时序操作符]  信号表达式  ...;
endsequence

举个例子,假设我们要描述“a信号拉高后,下一个时钟周期b信号也拉高”:

sequence seq_a_then_b;
  @(posedge clk) a ##1 b;
endsequence

这里的##1就是延迟一个时钟周期。我在项目中遇到过不少新手把##0和##1搞混——##0表示同一个时钟周期内,##1才是下一个时钟周期。嗯,这个坑我踩过。

3.2 property:给sequence加上“规矩”

sequence只管描述行为,但property要告诉验证环境:这个行为是必须发生的,还是禁止发生的。

基本结构:

property property_name;
  时序域  [disable iff(条件)]  断言操作符  sequence;
endproperty

我个人习惯把property分成三类:

  • assert property:必须成立,否则报错
  • assume property:假设成立,用于约束激励
  • cover property:覆盖监控,看看这个场景有没有跑到

你想想看,如果只写sequence不写property,那就像你画了一张地图但没告诉别人该往哪走——没什么用。

3.3 层次化构建:从小sequence搭出大property

实际项目中,我们不会把所有的逻辑都塞进一个sequence里。那样太乱了,也不好维护。我建议的做法是:先定义小的原子sequence,再组合成复杂的property。

举个例子,我们要验证“请求信号req拉高后,最多3个周期内必须收到应答ack”:

// 原子sequence:请求发生
sequence req_asserted;
  @(posedge clk) $rose(req);
endsequence

// 原子sequence:应答收到
sequence ack_received;
  @(posedge clk) $rose(ack);
endsequence

// 组合property:req后最多3拍内ack
property req_ack_max3;
  @(posedge clk) 
  req_asserted |-> ##[1:3] ack_received;
endproperty

assert property (req_ack_max3);

这样做的好处是什么?复用性强。我在一个PCIe项目中,把几十个原子sequence像搭积木一样组合,最后只用了不到200行代码就覆盖了上千条协议规则。要是全写在一起,那代码量至少翻三倍。

3.4 时序操作符:控制时间的“魔法棒”

SVA里最常用的时序操作符,我整理了一张表:

操作符 含义 示例
##n 延迟n个时钟周期 a ##2 b 表示a之后第2拍b
##[m:n] 延迟范围m到n拍 a ##[1:3] b 表示a之后1~3拍内b
[*n] 重复n次 a[*3] 表示a连续拉高3拍
[->n] 非连续重复n次 a[->2] 表示a出现2次,中间可间隔
intersect 两个序列同时结束 seq1 intersect seq2
and 两个序列都结束 seq1 and seq2
or 任一序列结束 seq1 or seq2

小技巧: 我个人习惯在写##[m:n]时,尽量把n设得小一点。曾经有个项目,我把超时窗口设成了##[0:100],结果仿真跑了三天三夜没跑完——因为SVA在后台展开了100个并行分支,内存直接爆了。

3.5 disable iff:优雅地处理复位

做验证的都知道,复位期间断言不能乱报。这时候disable iff就派上用场了。

property p_req_ack;
  @(posedge clk) disable iff (rst_n == 1'b0)
  req |-> ##[1:3] ack;
endproperty

我曾经见过有人不用disable iff,而是在每个sequence里手动判断复位信号。结果代码又臭又长,还容易漏掉某些路径。记住:能用语言特性解决的问题,别自己造轮子。

3.6 实战中的层次化设计思路

最后,我分享一下在项目中总结的层次化设计流程:

  1. 拆解协议规则:把复杂的协议拆成最小的原子行为
  2. 编写原子sequence:每个sequence只描述一个简单行为
  3. 组合成property:用逻辑操作符和时序操作符把sequence串起来
  4. 绑定到模块:用bind语句把property挂到DUT上

避坑指南: 我曾经在一个项目里,把所有的sequence和property都写在一个文件里,结果到了后期,想改一个时序参数,找了半天才找到。后来我强制团队按模块分文件,每个模块的sequence和property单独管理。嗯,从那以后,维护效率至少提升了50%。

好了,关于sequence和property的构建与层次化,今天就聊到这里。记住:好的SVA代码,读起来应该像一篇清晰的文档,而不是一团乱麻。

SVA序列与属性层次化结构 原子sequence 单个信号行为描述 原子sequence 单个信号行为描述 原子sequence 单个信号行为描述 组合sequence 使用 ##、and、or、intersect 等操作符 property(含 disable iff、断言操作符) assert / assume / cover bind 到 DUT / 验证平台调用

可可精品资料库