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的知识点串起来:
4.7 避坑总结
最后,我把自己这些年踩过的坑总结一下:
| 常见错误 | 正确做法 | 我的经验 |
|---|---|---|
| 在sequence里写时钟 | 时钟写在property层 | 曾经调试一个bug花了半天,最后发现是时钟位置不对 |
| 混淆##1和持续检查 | ##1是延迟,持续检查用ended或throughout | 这个坑我至少踩过三次 |
| sequence嵌套时忘记##0 | 明确写出衔接关系 | 不写##0默认是交叠,容易出问题 |
好了,sequence的基础内容就这些。记住一句话:sequence是断言的砖瓦,把砖瓦烧好了,盖房子才稳当。
下节课我们会讲property——也就是怎么用这些砖瓦砌成墙。到时候你会发现,sequence学好了,property就是水到渠成的事。
可可精品资料库