4. 序列sequence:从零开始理解断言的核心

各位同学,今天我们来聊聊断言验证里最基础、也最重要的概念——序列(sequence)。

说实话,我刚入行那会儿,看到sequence这个词就头大。总觉得它跟SV里的sequence有什么亲戚关系。后来才明白,这俩虽然名字一样,但完全是两码事。断言里的sequence,说白了就是描述信号之间时序关系的表达式。

4.1 什么是sequence?

先给个最直白的定义:sequence就是一系列布尔表达式在时间上的组合。

举个例子。你想检查“当reset拉低后,三个时钟周期内data必须有效”。这个描述里包含了:

  • 一个起始条件(reset拉低)
  • 一个时间关系(三个时钟周期内)
  • 一个结束条件(data有效)

把这些东西用SVA的语法写出来,就是一个sequence。

核心理解:sequence描述的是“什么事在什么时候发生”。它不负责判断对错,只负责描述时序关系。

4.2 基本语法

sequence的声明语法其实很简单。我个人习惯这样写:

sequence 序列名称;
  时序表达式;
endsequence

嗯,看起来跟module有点像对吧?但内容完全不同。

最基本的sequence可以只是一个信号:

sequence s_ready;
  ready;
endsequence

这个sequence表示“ready信号为高”。当然,实际项目中没人会这么写,太简单了。但你要理解,这是最基础的单元。

稍微复杂一点,我们可以用操作符组合:

sequence s_valid_data;
  valid && data != 0;
endsequence

这里用了逻辑与(&&),表示valid为高且data不为0。注意,这些表达式都是在同一个时钟周期内求值的。

4.3 时钟控制——让sequence活起来

没有时钟的sequence就像没有节拍的音乐。SVA里用@(posedge clk)来指定时钟。

我曾经在项目里见过一个坑:有人把时钟写在了sequence内部,结果跟property里的时钟冲突了。所以我的建议是:时钟统一写在property层,sequence里只写时序关系。

看个例子:

sequence s_req_ack;
  req ##1 ack;
endsequence

property p_req_ack;
  @(posedge clk) s_req_ack;
endproperty

assert property (p_req_ack);

这里##1是什么意思?它表示延迟一个时钟周期。所以整个sequence的意思是:

  • 当前周期req为高
  • 下一个时钟周期ack为高

你想想看,这不就是最简单的握手协议吗?

小技巧:##0表示零延迟(同一周期),##n表示n个周期延迟。我一般用##[1:3]表示1到3个周期内的任意延迟,这在处理不确定延迟时特别有用。

4.4 简单序列示例——从理论到实践

光说不练假把式。我们来看几个我在项目中实际用过的例子。

示例1:请求-应答握手

sequence s_handshake;
  req ##[1:5] ack;
endsequence

这个sequence检查:req拉高后,在1到5个周期内ack必须拉高。为什么用范围延迟?因为实际设计中,ack的返回时间可能因为流水线深度不同而变化。

示例2:连续两个周期有效

sequence s_two_cycles;
  valid ##1 valid;
endsequence

这个看起来简单,但容易误解。它检查的是:当前周期valid为高,下一个周期valid也为高。注意,它不要求中间不能有低电平——实际上##1就是跳过了一个周期。

避坑指南:我曾经犯过一个错误,以为##1表示“持续一个周期”。其实不是!##1表示“延迟一个周期后检查”。如果你想检查信号连续保持高电平,需要用另一种写法。

示例3:带条件的序列

sequence s_conditional;
  start ##1 (enable ? data_valid : 1);
endsequence

这里用了条件运算符。当enable为高时,检查data_valid;否则直接通过(1表示永远为真)。这种写法在控制路径验证中很常见。

4.5 序列的复合——搭积木的艺术

sequence最强大的地方在于可以嵌套。你可以把小的sequence组合成大的:

sequence s_phase1;
  req ##1 gnt;
endsequence

sequence s_phase2;
  gnt ##[1:3] done;
endsequence

sequence s_full_transfer;
  s_phase1 ##0 s_phase2;
endsequence

这里##0表示两个sequence无缝衔接。s_phase1的结束点(gnt为高)就是s_phase2的起始点。

我个人特别喜欢这种搭积木的方式。为什么?因为每个小sequence都可以单独验证,最后组合起来,调试起来特别方便。

4.6 知识体系总览

说了这么多,我们来画张图,把sequence的知识点串起来:

Sequence知识体系 Sequence 定义:时序关系描述 基本语法:sequence...endsequence 时钟控制:@(posedge clk) 布尔表达式 + 时间关系 操作符:&& || ! ## 延迟操作:##0 ##1 ##[1:5] 常见示例 1. 握手协议:req ##[1:5] ack 2. 连续有效:valid ##1 valid 3. 条件序列:start ##1 (en ? data_valid : 1) 4. 序列复合:s_phase1 ##0 s_phase2

4.7 避坑总结

最后,我把自己这些年踩过的坑总结一下:

常见错误 正确做法 我的经验
在sequence里写时钟 时钟写在property层 曾经调试一个bug花了半天,最后发现是时钟位置不对
混淆##1和持续检查 ##1是延迟,持续检查用ended或throughout 这个坑我至少踩过三次
sequence嵌套时忘记##0 明确写出衔接关系 不写##0默认是交叠,容易出问题

好了,sequence的基础内容就这些。记住一句话:sequence是断言的砖瓦,把砖瓦烧好了,盖房子才稳当。

下节课我们会讲property——也就是怎么用这些砖瓦砌成墙。到时候你会发现,sequence学好了,property就是水到渠成的事。


可可精品资料库