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;  // 接收方可以收的条件
我的小技巧:验证的时候,别忘了测“ready 在 valid 之前拉高”和“ready 在 valid 之后拉高”两种情况。时序收敛往往差在这里。

2.2 valid-ack 握手协议

这个模型更老一些,但有些场景下特别好用。比如控制信号、寄存器配置、或者低速接口。

它的逻辑是:发送方拉高 valid,接收方拉高 ack 表示收到。然后发送方可以拉低 valid,准备下一笔。

说白了,这就是一个“请求-应答”模式。我发一个,你回一个,咱们再发下一个。

注意:valid-ack 模型天然不支持流水线。一笔交易必须等 ack 回来才能发下一笔。吞吐量上不去,但控制逻辑简单。

我曾经在一个 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 信号可能采不到。双线握手就是为了解决这个问题的。

它的工作流程是这样的:

  1. 发送方翻转 req 电平,表示“有新数据”
  2. 接收方看到 req 变化后,采样数据,然后翻转 ack 电平
  3. 发送方看到 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 接收方:ready 同时为高 → 握手成功 支持流水线 高吞吐量 ★ 最常用 valid-ack 发送方:valid 接收方:ack 请求-应答模式 每笔等应答 控制简单 ★ 适合控制信号 双线握手 发送方:req 接收方:ack 电平翻转传递 跨时钟域 抗亚稳态 ★ 跨时钟域首选 选择哪种模型,取决于你的时钟域、吞吐量需求和设计复杂度

我个人建议:同时钟域用 valid-ready,跨时钟域用双线握手,控制类信号用 valid-ack。这个原则我用了很多年,基本没出过问题。

避坑指南:我曾经在验证一个 valid-ready 接口时,发现接收方 ready 拉高后,发送方下一拍就把 valid 拉低了。结果接收方只采到一半数据。后来加了断言:assert property (@(posedge clk) valid |=> $stable(valid) until ready); 从此再没犯过。

好了,三种模型讲完了。记住:握手协议的核心不是“怎么传”,而是“什么时候传”。把时序搞清楚了,验证就成功了一半。

可可精品资料库