3. AXI握手协议:VALID/READY握手机制、通道依赖关系、背压处理

大家好,我是老周。今天我们来聊聊AXI总线里最核心的东西——握手协议。

说实话,我见过不少工程师,RTL写得飞起,但一问到VALID和READY怎么配合,就开始含糊了。这其实是个大坑。我在一个AI加速器项目里,就遇到过因为握手没处理好,导致整个系统死锁的情况。那次debug花了整整三天,最后发现就是一个ready信号拉早了。

好,咱们从头捋一遍。

3.1 VALID/READY握手机制

AXI的每个通道,都靠一对信号来完成数据传输:VALID 和 READY。

  • VALID:由源端(发送方)驱动。表示当前数据/地址/控制信号是有效的。
  • READY:由目的端(接收方)驱动。表示当前可以接收数据。

说白了,就是源端说“我有货了”,目的端说“我能收了”。两边都点头,数据才算真正传过去。

握手有五种情况,我画了个图帮大家理解:

AXI握手五种情况 情况1:VALID先有效,READY后有效 源端先准备好,目的端稍后响应 情况2:READY先有效,VALID后有效 目的端提前告知可以接收 情况3:VALID和READY同时有效 一拍完成握手,效率最高 情况4:VALID先有效,READY无效 源端等待,数据保持稳定 情况5:READY先有效,VALID无效 目的端等待,READY可以保持有效 核心规则: • 数据只在VALID和READY同时为高的时钟上升沿传输 • VALID一旦拉高,必须保持到握手完成,不能中途撤销

嗯,这里要注意:VALID一旦拉高,就不能再拉低,直到握手成功。这是AXI协议的铁律。我见过有人为了省功耗,在等待时把VALID拉低,结果直接违反了协议。

关键点:数据只在VALID和READY同时为高的那个时钟上升沿传输。其他时候,数据都处于“等待”状态。

3.2 通道依赖关系

AXI有五个通道:读地址(AR)、读数据(R)、写地址(AW)、写数据(W)、写响应(B)。它们之间不是独立的,有严格的依赖关系。

我习惯把通道依赖总结成一句话:地址通道先走,数据通道后跟,响应通道收尾。

依赖关系 说明 实际影响
AR → R 读地址必须先于读数据 从机收到AR后,才能准备R数据
AW → W 写地址可以先于写数据 地址和数据可以乱序,但必须匹配
W → B 写响应必须在写数据之后 从机收到最后一个W数据后,才能发B
AW + W → B 地址和数据都到齐,才能发响应 从机需要同时跟踪地址和数据

你想想看,如果从机还没收到写地址,就先收到了写数据,它怎么知道数据该往哪里写?所以协议规定了这些依赖关系。

我在一个多端口DMA控制器项目里,就踩过这个坑。当时为了性能,让写地址和写数据走不同的FIFO路径,结果地址比数据晚到了,从机直接报错。后来我加了一个地址-数据匹配逻辑,才把问题解决。

个人建议:设计从机时,最好把AW和W通道的握手信号独立处理。不要假设它们会同时到达。用两个独立的FIFO分别缓存地址和数据,等两者都到齐了再一起处理。

3.3 背压(Backpressure)处理

背压,说白了就是接收方告诉发送方“我吃不下了,你慢点”。在AXI里,背压是通过READY信号来实现的。

当接收方拉低READY时,发送方的数据就会被堵住。这就是背压。

背压处理有三个常见场景:

  1. FIFO满时拉低READY:这是最直接的做法。FIFO快满时,提前拉低READY,防止溢出。
  2. 处理延迟时拉低READY:比如从机需要多个周期才能处理一笔数据,可以在处理期间拉低READY。
  3. 多通道同步时拉低READY:比如写数据通道在等待写地址通道时,可以暂时拉低WREADY。

我曾经在一个视频处理芯片里,遇到过背压导致的性能问题。当时从机的FIFO深度只有4,而主机的突发长度是16。结果每传4笔数据就要等一次背压,吞吐率直接掉了60%。

后来我做了两件事:

  • 把FIFO深度增加到16
  • 在FIFO剩余空间小于4时就开始拉低READY,提前预警

这样既保证了不溢出,又让主机有足够时间调整节奏。

注意:背压处理不当会导致死锁!比如,从机A在等待从机B的数据,同时从机B在等待从机A释放READY,这就形成了循环依赖。设计时一定要检查是否存在这种闭环。

3.4 实战代码示例

下面是一个简单的AXI从机写数据通道的Verilog代码片段,展示了VALID/READY握手和背压处理:

module axi_slave_wchannel (
    input  wire        clk,
    input  wire        rst_n,
    // 写数据通道
    input  wire [31:0] wdata,
    input  wire [3:0]  wstrb,
    input  wire        wvalid,
    output reg         wready,
    // 内部FIFO接口
    output reg         fifo_wr_en,
    output reg [31:0]  fifo_wr_data
);

    // FIFO状态
    reg [4:0] fifo_used;  // FIFO已用空间
    parameter FIFO_DEPTH = 16;
    parameter ALMOST_FULL_TH = 12;  // 提前预警阈值

    // 背压控制:FIFO快满时拉低READY
    always @(*) begin
        if (fifo_used >= ALMOST_FULL_TH)
            wready = 1'b0;  // 拉低READY,产生背压
        else
            wready = 1'b1;  // 可以接收数据
    end

    // 握手成功时,写入FIFO
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            fifo_wr_en <= 1'b0;
            fifo_wr_data <= 32'b0;
        end else begin
            if (wvalid && wready) begin
                fifo_wr_en   <= 1'b1;
                fifo_wr_data <= wdata;
            end else begin
                fifo_wr_en <= 1'b0;
            end
        end
    end

    // FIFO使用量统计(简化版)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            fifo_used <= 5'b0;
        else if (wvalid && wready && fifo_rd_en)
            fifo_used <= fifo_used;
        else if (wvalid && wready)
            fifo_used <= fifo_used + 1'b1;
        else if (fifo_rd_en)
            fifo_used <= fifo_used - 1'b1;
    end

endmodule

这段代码里,我用了提前预警的策略。当FIFO使用量超过12(总深度16)时,就开始拉低READY。这样即使主机有流水线延迟,也不会导致FIFO溢出。

核心要点回顾:

  • VALID/READY同时为高时,数据才传输
  • VALID一旦拉高,必须保持到握手完成
  • 通道间有严格的依赖关系,设计时要注意
  • 背压通过拉低READY实现,要提前预警,避免死锁

好了,AXI握手协议就讲到这里。这些内容看起来简单,但实际项目中出问题最多的就是这些基础点。希望大家在写代码时,多想想握手时序,多检查通道依赖,别像我当年那样debug三天。

可可精品资料库