4、序列组合:序列的连接(##),序列的交集(and),序列的或(or),序列的交叉(intersect)
好,咱们今天聊聊序列的组合。说白了,就是怎么把多个小序列拼成一个大序列,或者让它们按一定逻辑关系一起工作。
我在项目里见过不少新人,写断言时一个序列从头写到尾,又长又乱。其实你完全可以把复杂场景拆成几个小序列,再用组合操作符拼起来。这样代码可读性高,调试也方便。
4.1 序列的连接(##)
连接操作符 ##,表示前一个序列结束后,经过多少个时钟周期,再开始匹配后一个序列。
举个例子:
sequence s1;
a ##1 b;
endsequence
sequence s2;
c ##2 d;
endsequence
sequence s_concat;
s1 ##1 s2;
endsequence
这个 s_concat 的意思是:先看到 a 拉高,下一拍 b 拉高,然后过一拍,再看到 c 拉高,再过两拍 d 拉高。整个序列才算匹配成功。
重点:## 后面的数字是时钟周期数,不是绝对时间。你写 ##0 表示同一拍,##1 表示下一拍。
我的习惯:连接操作符我一般用在协议类断言里。比如 AXI 的地址和数据通道,地址发出去后几拍数据必须有效,用 ## 连起来特别直观。
4.2 序列的交集(and)
and 操作符要求两个序列同时开始,同时结束。注意,是同时开始,不是同时结束哦——两个序列的起始点必须对齐,但长度可以不一样。
我举个例子你就明白了:
sequence s1;
a ##[1:3] b;
endsequence
sequence s2;
c ##[2:4] d;
endsequence
sequence s_and;
s1 and s2;
endsequence
这个断言什么时候成功?当 a 和 c 在同一拍拉高,然后 b 在 a 之后的 1 到 3 拍内拉高,同时 d 在 c 之后的 2 到 4 拍内拉高,并且 b 和 d 必须在同一拍拉高。嗯,这里要注意,两个序列的结束点必须对齐。
我曾经踩过的坑:有一次我写总线仲裁的断言,用 and 检查两个请求信号同时有效。结果发现其中一个请求信号因为毛刺提前了一拍,导致断言一直不通过。后来我加了个 $rose 过滤上升沿,问题就解决了。所以用 and 时,一定要确认你的信号有没有毛刺或者延迟。
4.3 序列的或(or)
or 操作符就简单了——两个序列只要有一个匹配成功,整个断言就通过。同样要求同时开始。
sequence s1;
a ##1 b;
endsequence
sequence s2;
a ##2 c;
endsequence
sequence s_or;
s1 or s2;
endsequence
这个断言的意思是:a 拉高后,如果下一拍 b 拉高,或者下两拍 c 拉高,都算匹配成功。
我个人建议:or 适合用在「多种合法路径」的场景。比如一个状态机,从 IDLE 状态可以跳到 READ 状态,也可以跳到 WRITE 状态,用 or 就能优雅地表达这种多分支。
4.4 序列的交叉(intersect)
intersect 和 and 有点像,但要求更严格。它要求两个序列同时开始,同时结束,并且长度完全一样。
你想想看,如果两个序列长度不一样,intersect 就不会匹配。它本质上是在找两个序列的「公共部分」。
sequence s1;
a ##2 b;
endsequence
sequence s2;
c ##2 d;
endsequence
sequence s_intersect;
s1 intersect s2;
endsequence
这个断言要求:a 和 c 同一拍拉高,然后经过正好两拍,b 和 d 在同一拍拉高。少一拍不行,多一拍也不行。
关键区别:
| 操作符 | 开始时间 | 结束时间 | 长度要求 |
|---|---|---|---|
| and | 同时 | 同时 | 可以不同 |
| intersect | 同时 | 同时 | 必须相同 |
| or | 同时 | 任意一个结束 | 可以不同 |
我曾经犯过的错:刚开始用 intersect 时,我把它和 and 搞混了。写了一个断言检查握手协议,要求请求和应答信号在固定周期内完成。结果用 and 写,发现应答信号可以提前一拍,断言也通过了。这显然不对。后来改成 intersect,才严格卡死了时序。所以,如果你需要「精确匹配」,用 intersect;如果只是「同时结束但允许长度不同」,用 and。
4.5 实际项目中的组合使用
在实际项目中,这些操作符经常组合使用。我给你看一个我去年做过的例子:
// 场景:DMA 读操作
// 要求:地址有效后,数据必须在 3-5 拍内返回
// 同时,中断信号必须在数据返回后的 2 拍内拉高
sequence addr_phase;
dma_req ##1 dma_addr_valid;
endsequence
sequence data_phase;
##[3:5] dma_data_valid;
endsequence
sequence int_phase;
##2 dma_int;
endsequence
sequence dma_read_ok;
(addr_phase and data_phase) ##1 int_phase;
endsequence
你看,我把整个操作拆成了三个小序列:地址阶段、数据阶段、中断阶段。然后用 and 把地址和数据组合起来(要求它们同时结束),再用 ##1 连接到中断阶段。这样写,逻辑清晰,出了问题也容易定位。
我的经验:写复杂断言时,我习惯先画个时序图,把各个信号的相对关系标清楚。然后根据时序图拆成小序列,再用组合操作符拼起来。这样不容易漏掉边界情况。
好了,序列组合这块就讲到这里。说白了,## 是串行,and 和 intersect 是并行但要求不同,or 是二选一。你只要记住它们的核心区别,写断言时就不会用错。