4. SVA序列操作:序列的延迟、重叠、连接,序列的重复与逻辑操作

好,咱们今天聊聊SVA序列操作。说实话,刚接触SystemVerilog Assertion那会儿,我觉得序列就是简单的「信号A拉高后信号B拉高」。但实际项目一上手,发现远没那么简单。延迟、重叠、连接、重复、逻辑操作——这些才是真正让序列变得强大的地方。

我个人习惯把序列操作分成两大类:时间控制类和逻辑组合类。时间控制类管的是「什么时候发生」,逻辑组合类管的是「怎么组合这些事件」。咱们一个一个来。

4.1 序列的延迟操作

延迟操作,说白了就是「等几个时钟周期」。SVA里用 ## 来表示。

核心语法:

  • ##n:延迟n个时钟周期
  • ##[m:n]:延迟范围,从m到n个周期
  • ##[*]:延迟0到无穷(慎用!)

举个例子:

// 信号a拉高后,正好过3个周期,信号b拉高
sequence s_delay3;
  a ##3 b;
endsequence

// 信号a拉高后,在1到5个周期内,信号b拉高
sequence s_delay_range;
  a ##[1:5] b;
endsequence

我在项目中遇到过一个问题:用 ##[*] 做超时检测,结果仿真跑了一整天没结束。为什么?因为 ##[*] 匹配0到无穷个周期,如果信号一直不来,仿真器会一直尝试匹配下去。嗯,这里要注意——能用具体范围就别用无穷。

4.2 序列的重叠与连接

重叠和连接,这两个概念容易搞混。我刚开始也分不清,后来用一句话记住了:重叠是「同时发生」,连接是「先后发生」。

4.2.1 重叠操作

重叠用 and 关键字。两个序列在同一时刻都满足,才算匹配。

// 序列A和序列B在同一时刻都成立
sequence s_overlap;
  seq_a and seq_b;
endsequence

你想想看,这就像两个人同时到达约定地点。一个人到了,另一个人没到,就不算重叠。

4.2.2 连接操作

连接用 ## 直接连。序列A结束后,紧接着序列B开始。

// 序列A结束后,下一个周期序列B开始
sequence s_concatenation;
  seq_a ##1 seq_b;
endsequence

注意这里的 ##1 表示「序列A结束后的下一个时钟周期」。如果序列A本身占用了3个周期,那序列B从第4个周期开始。

小技巧: 我习惯把连接操作想象成「流水线」。上一级处理完,下一级立刻接手。这在验证数据通路时特别有用。

4.3 序列的重复操作

重复操作,就是让某个事件或序列重复发生。SVA提供了三种重复方式:

操作符 含义 示例
[*n] 连续重复n次 a[*3] 表示a连续拉高3个周期
[*m:n] 连续重复m到n次 a[*2:5] 表示a连续拉高2到5个周期
[->n] 非连续重复n次(goto重复) a[->3] 表示a总共出现3次,中间可以隔周期
[=n] 非连续重复n次(non-consecutive) a[=3] 类似goto,但结束时不要求a保持

我曾经在验证一个DMA控制器时,需要检查「连续4个周期都有数据请求」。用 req[*4] 一行搞定。如果用传统波形检查,得盯着看半天。

// 连续重复:信号a连续拉高3个周期
sequence s_consecutive;
  a[*3];
endsequence

// goto重复:信号a总共出现3次,不要求连续
sequence s_goto;
  a[->3];
endsequence

避坑指南: 我曾经用 a[->3] 检查中断信号,结果发现仿真一直不匹配。后来才意识到,goto重复要求「最后一次出现后立即结束序列」。如果后面还有别的条件,得小心处理。

4.4 序列的逻辑操作

逻辑操作让序列可以组合成更复杂的条件。常用的有 and、or、not、intersect。

4.4.1 and 和 or

and 要求两个序列同时结束。or 只要其中一个结束就算匹配。

// 两个序列同时结束
sequence s_and;
  seq_a and seq_b;
endsequence

// 任一序列结束即可
sequence s_or;
  seq_a or seq_b;
endsequence

说白了,and 是「都要」,or 是「有就行」。我在验证握手协议时经常用 and 检查「valid和ready同时拉高」。

4.4.2 intersect 操作

intersect 比 and 更严格。它不仅要求两个序列同时结束,还要求它们长度完全一致。

// 两个序列必须长度相同且同时结束
sequence s_intersect;
  seq_a intersect seq_b;
endsequence

你想想看,and 允许一个序列长一个序列短,只要同时结束就行。但 intersect 要求「你俩从开始到结束,每一步都得同步」。这在检查严格时序对齐时非常有用。

4.4.3 not 操作

not 就是取反。但注意,SVA里的 not 是对整个序列取反,不是对单个信号。

// 序列seq_a不应该发生
sequence s_not;
  not seq_a;
endsequence

我曾经用 not 检查「写操作期间不应该有读请求」。结果发现误报了一堆——原来 not 检查的是「整个仿真期间都没有seq_a」,而不是「某个时间段内没有」。嗯,这里要注意作用域。

4.5 知识体系总览

下面这张图总结了SVA序列操作的核心内容,我画成了流程图,方便你对照理解:

SVA序列操作知识体系 延迟操作 ##n 固定延迟 ##[m:n] 范围延迟 ##[*] 0到无穷 慎用无穷范围 重叠与连接 and 重叠(同时) ## 连接(先后) 流水线式衔接 注意时序对齐 重复操作 [*n] 连续重复 [->n] goto重复 [=n] non-consecutive 区分连续与非连续 逻辑操作 and / or intersect not 取反 注意作用域 核心原则:时间控制决定「何时发生」,逻辑组合决定「如何组合」 实战建议 1. 能用具体范围就别用无穷延迟,避免仿真爆炸 2. 重叠(and)和连接(##)容易混淆,记住「同时 vs 先后」 3. 重复操作优先用连续重复,非连续重复要确认语义 4. 逻辑操作注意作用域,not 是对整个序列取反

4.6 综合示例:一个实际场景

最后,我分享一个实际项目中的例子。验证一个AXI总线从设备,需要检查「写地址通道和写数据通道同时准备好后,下一个周期写响应必须回来」。

// 写地址有效且写数据有效
sequence s_wr_addr_data;
  awvalid and wvalid;
endsequence

// 写地址和数据同时准备好后,下一个周期写响应
property p_wr_response;
  @(posedge clk) disable iff (!rst_n)
    s_wr_addr_data |=> ##1 bvalid;
endproperty

assert property(p_wr_response);

这里用了 and 做重叠检查,|=> 做延迟一个周期。你看,序列操作组合起来,就能描述复杂的时序行为。

好了,关于SVA序列操作就聊到这儿。记住:延迟控制时间,逻辑控制组合,重复控制次数。把这三点吃透了,写断言就跟搭积木一样简单。