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

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

说实话,我刚开始接触AXI时,觉得这玩意儿不就是个握手嘛,能有多复杂?结果第一次调DMA就翻车了。嗯,从那以后我再也不敢小看VALID和READY这两个信号了。

2.1 VALID/READY握手机制

AXI总线有五个独立通道,每个通道都用VALID/READY来握手。说白了,就是发送方说“我有数据了”(VALID拉高),接收方说“我准备好了”(READY拉高),两边都点头,数据就传过去了。

这里有个关键点——数据只在VALID和READY同时为高的时钟上升沿传输。我见过不少新手以为VALID拉高就算传了,结果仿真死活对不上。

三种握手场景:

  • VALID先于READY:发送方先准备好,等接收方就绪。这是最常见的场景。
  • READY先于VALID:接收方提前声明“我随时可以收”,发送方准备好后直接传。
  • 同时有效:一拍搞定,效率最高。

我个人习惯在设计时尽量让READY早于VALID拉高,这样能减少等待周期。但要注意,READY不能依赖VALID来产生,否则会形成组合逻辑环——这个坑我踩过。

2.2 通道依赖关系

AXI五个通道之间不是独立的,它们有严格的依赖关系。你想想看,如果写地址还没发出去,写数据就先到了,那从机怎么知道数据该写哪儿?

写事务通道依赖:

  • AWVALID(写地址)必须先于WVALID(写数据)或同时有效
  • WLAST(最后一个写数据)必须在BVALID(写响应)之前
  • BREADY(写响应就绪)可以提前拉高,但BVALID必须等WLAST之后

读事务通道依赖:

  • ARVALID(读地址)必须先于RVALID(读数据)
  • RREADY(读数据就绪)可以提前拉高
  • RLAST(最后一个读数据)和RVALID同时有效

我的经验:在验证环境中,我习惯用断言(assertion)来检查这些依赖关系。曾经有个项目,从机在没收到地址的情况下就返回了读数据,要不是断言抓住了,这bug流片前都发现不了。

2.3 背压处理

背压,说白了就是接收方来不及处理,让发送方等一等。AXI里用READY信号来实现背压——接收方把READY拉低,发送方就得保持当前数据不动。

背压的两种实现方式:

方式 描述 适用场景
READY背压 接收方拉低READY,发送方保持VALID和数据 大多数情况,简单可靠
VALID退让 发送方主动拉低VALID,等待接收方就绪 发送方需要暂停发送时

我在做DMA控制器时遇到过一个问题:DMA连续发数据,但DDR控制器偶尔会忙。如果DMA不管不顾一直发,数据就丢了。解决办法是让DMA监测从机的READY信号,READY拉低时DMA必须保持当前数据不变。

注意:背压处理时,VALID信号一旦拉高,必须等到握手成功才能拉低。不能因为READY没就绪就把VALID撤了——这是AXI协议的死规定。

2.4 握手时序图

下面我用SVG画了个典型的握手时序,展示了VALID和READY的交互过程:

T0 T1 T2 T3 T4 T5 VALID READY DATA 无效 无效 D0 D1 D2 VALID READY 数据传输

从时序图可以看到:T2周期VALID拉高,但READY还是低,所以数据没传。T3周期READY也拉高了,VALID和READY同时为高,D0成功传输。之后每个周期都传一笔数据,直到T5。

2.5 代码示例:握手状态机

下面是一个简单的AXI写地址通道握手状态机,我用Verilog写的:

// AXI写地址通道握手状态机
module axi_aw_handshake (
    input  clk,
    input  rst_n,
    input  awvalid,      // 主机发送地址有效
    output reg awready,   // 从机准备接收地址
    input  [31:0] awaddr, // 写地址
    output reg [31:0] awaddr_reg // 寄存后的地址
);

    // 状态定义
    localparam IDLE = 2'b00;
    localparam WAIT = 2'b01;
    localparam DONE = 2'b10;

    reg [1:0] state, next_state;

    // 状态转移
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            state <= IDLE;
        else
            state <= next_state;
    end

    // 下一状态逻辑
    always @(*) begin
        next_state = state;
        case (state)
            IDLE: begin
                if (awvalid) begin
                    if (awready)  // 同时有效,一拍完成
                        next_state = DONE;
                    else          // 等待从机就绪
                        next_state = WAIT;
                end
            end
            WAIT: begin
                if (awready)      // 从机就绪,握手成功
                    next_state = DONE;
            end
            DONE: begin
                if (!awvalid)     // 主机撤销请求,回到空闲
                    next_state = IDLE;
            end
        endcase
    end

    // 输出逻辑
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            awready <= 1'b0;
            awaddr_reg <= 32'b0;
        end else begin
            case (state)
                IDLE: begin
                    awready <= 1'b1;  // 默认就绪
                    if (awvalid & awready)
                        awaddr_reg <= awaddr;
                end
                WAIT: begin
                    awready <= 1'b1;  // 保持就绪
                    if (awready)
                        awaddr_reg <= awaddr;
                end
                DONE: begin
                    awready <= 1'b0;  // 握手完成,拉低就绪
                end
            endcase
        end
    end

endmodule

避坑指南:我曾经在状态机里把awready的赋值写成了组合逻辑,结果综合出来一堆latch。记住,awready最好用时序逻辑产生,除非你能保证组合路径上没有毛刺。

2.6 握手协议的关键要点

  • 数据有效性:VALID拉高后必须保持,直到握手成功。不能中途撤消。
  • 就绪信号:READY可以随时拉高或拉低,但拉高后最好保持到握手成功。
  • 依赖关系:读/写地址通道必须先于数据通道,响应通道必须在数据之后。
  • 背压处理:接收方通过拉低READY实现背压,发送方必须尊重。
  • 死锁避免:不要让VALID依赖READY,也不要让READY依赖VALID,否则会死锁。

嗯,AXI握手协议就这些。说白了就是“你情我愿”才能传数据。下一章咱们聊聊AXI的突发传输,那个更刺激。

可可精品资料库