第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的交互过程:
从时序图可以看到: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的突发传输,那个更刺激。