2. 经典握手模型:valid-ready、valid-ack 与双线握手
握手协议,说白了就是两个模块之间怎么“打招呼”。
我做了这么多年芯片验证,见过太多因为握手没握好导致的bug。有的数据丢了,有的卡死了,有的时序乱了套。其实核心就三种模型:valid-ready、valid-ack、还有双线握手。今天咱们一个一个说清楚。
2.1 valid-ready 握手协议
这是目前最主流的握手方式。你想想看,AXI总线、AXI-Stream、还有很多片内互联,用的都是它。
它的核心思想很简单:发送方说“我有数据”(valid),接收方说“我能收”(ready)。两边都点头了,数据才算真正传过去。
- valid 拉高后,必须等到 ready 也拉高,数据才被采样
- ready 可以提前拉高,也可以等 valid 来了再拉
- 一旦握手成功(valid & ready 同时为高),下一拍数据必须更新
我在项目中遇到过一种情况:发送方把 valid 拉高后,发现接收方 busy,就把 valid 又拉低了。这其实是不允许的。valid 一旦拉高,必须保持到握手完成。否则接收方可能采到不确定的数据。
// 典型的 valid-ready 握手逻辑
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
data_q <= '0;
else if (valid & ready)
data_q <= data_in; // 握手成功,更新数据
end
assign ready = !fifo_full; // 接收方可以收的条件
2.2 valid-ack 握手协议
这个模型更老一些,但有些场景下特别好用。比如控制信号、寄存器配置、或者低速接口。
它的逻辑是:发送方拉高 valid,接收方拉高 ack 表示收到。然后发送方可以拉低 valid,准备下一笔。
说白了,这就是一个“请求-应答”模式。我发一个,你回一个,咱们再发下一个。
我曾经在一个 SPI 控制器里用过这个模型。主机发一个命令,从机回一个应答。如果 ack 没回来,主机就一直等。这种设计在调试时特别友好——你一眼就能看出卡在哪一步。
// valid-ack 握手示例
always_ff @(posedge clk) begin
if (valid & ack) begin
// 握手成功,准备下一笔
valid <= 1'b0;
// 可以在这里更新数据
end
end
2.3 双线握手协议
双线握手,其实是一种跨时钟域的握手方案。它用两根线:req(请求)和 ack(应答),通过电平翻转来传递信息。
你想想看,如果两个模块时钟不同,valid-ready 那套就不太灵了。因为 ready 信号可能采不到。双线握手就是为了解决这个问题的。
它的工作流程是这样的:
- 发送方翻转 req 电平,表示“有新数据”
- 接收方看到 req 变化后,采样数据,然后翻转 ack 电平
- 发送方看到 ack 变化,知道数据已被收走
嗯,这里要注意:每次握手,req 和 ack 各翻转一次。所以叫“双线”。
- 天然抗亚稳态(通过两级同步器)
- 不需要时钟域之间的时序约束
- 适合低频、控制类信号的跨时钟域传输
我记得有一次做多时钟域验证,一个模块从 100MHz 域往 200MHz 域发数据。用 valid-ready 怎么调都偶尔丢数。后来换成双线握手,问题就解决了。说白了,跨时钟域的问题,用双线握手是最稳妥的。
// 双线握手:发送方逻辑
always_ff @(posedge clk_a) begin
if (data_valid)
req <= ~req; // 翻转请求线
end
// 接收方逻辑(经过两级同步)
always_ff @(posedge clk_b) begin
req_sync1 <= req;
req_sync2 <= req_sync1;
if (req_sync2 != req_sync1_old) begin
// 检测到 req 翻转,采样数据
ack <= ~ack;
end
req_sync1_old <= req_sync2;
end
2.4 三种模型的对比
我把它们放在一起对比一下,方便你选型:
| 特性 | valid-ready | valid-ack | 双线握手 |
|---|---|---|---|
| 吞吐量 | 高(支持流水线) | 低(每笔等应答) | 低(每笔等翻转) |
| 跨时钟域能力 | 弱(需额外处理) | 弱 | 强(天然支持) |
| 控制复杂度 | 中等 | 简单 | 中等 |
| 典型应用 | AXI、Stream、高速数据通路 | 寄存器配置、控制信号 | 跨时钟域、异步接口 |
2.5 知识体系图
下面这张图,帮你理清三种模型的关系和适用场景:
我个人建议:同时钟域用 valid-ready,跨时钟域用双线握手,控制类信号用 valid-ack。这个原则我用了很多年,基本没出过问题。
assert property (@(posedge clk) valid |=> $stable(valid) until ready); 从此再没犯过。
好了,三种模型讲完了。记住:握手协议的核心不是“怎么传”,而是“什么时候传”。把时序搞清楚了,验证就成功了一半。
可可精品资料库