1. AXI总线基础
大家好,我是老李。做图像处理硬件加速这些年,打交道最多的就是AXI总线了。今天咱们聊聊AXI的基础,这部分内容虽然基础,但却是整个课程的地基。我个人习惯把AXI总线比作芯片内部的「高速公路」——数据怎么跑、怎么让路、怎么保证不撞车,都靠这套规则。
1.1 为什么图像加速离不开AXI?
你想想看,一个典型的图像处理系统里,数据量有多大?1080p的RGB图像,一帧就是6MB多。如果帧率是60fps,那每秒要处理360MB的数据。这还只是单路。要是做多路拼接、缩放、叠加……数据量直接翻倍。
AXI总线的高带宽、低延迟特性,正好能扛住这种压力。我在项目中遇到过用AXI4-Stream做像素流传输,时钟跑200MHz,数据位宽64位,理论带宽就有1.6GB/s。嗯,这个数字对于大多数图像处理场景都够用了。
1.2 AXI4、AXI4-Lite、AXI4-Stream:三兄弟各司其职
ARM在AMBA 4.0规范里定义了三种AXI接口。说白了,它们就是针对不同场景设计的「专用通道」。
| 协议类型 | 主要用途 | 数据通道 | 地址映射 | 典型应用 |
|---|---|---|---|---|
| AXI4 | 高性能内存映射传输 | 读地址、读数据、写地址、写数据、写响应 | 是 | DDR控制器、DMA引擎 |
| AXI4-Lite | 寄存器配置与状态读取 | 同AXI4,但数据位宽固定 | 是 | 控制寄存器、状态寄存器 |
| AXI4-Stream | 高速数据流传输 | 仅数据通道(无地址) | 否 | 像素流、视频数据 |
我刚开始接触AXI时,总觉得AXI4-Lite和AXI4差不多,干嘛要多搞一个?后来做图像处理IP核的控制寄存器配置时才明白——AXI4-Lite虽然带宽低,但逻辑简单、资源少,配置几十个寄存器绰绰有余。而AXI4-Stream呢?它没有地址,数据像流水一样从源头流到终点,非常适合像素数据的逐行传输。
1.3 握手信号:VALID和READY的默契配合
AXI总线最核心的机制就是握手。所有通道都遵循同一套规则:发送方拉高VALID,表示数据有效;接收方拉高READY,表示可以接收。当VALID和READY同时为高时,传输发生。
为什么会这样设计?你想想看,如果发送方不管接收方能不能收,一股脑往外丢数据,那接收方肯定要丢包。反过来,如果接收方一直拉高READY,但发送方没数据,那也是白等。握手信号就是让双方「商量着来」。
握手传输的三种情况:
- 情况一:VALID先高,READY后高——发送方等接收方准备好
- 情况二:READY先高,VALID后高——接收方等发送方准备好
- 情况三:VALID和READY同时高——一拍完成传输,效率最高
我曾经在调试一个图像缩放模块时,发现数据总是断断续续。查了半天,原来是接收端的READY信号在数据还没处理完时就拉低了,导致发送端一直在等。嗯,这种问题用示波器看波形最直观——VALID和READY的握手时序一目了然。
1.4 AXI4-Stream:图像加速的「主力军」
在图像处理硬件加速中,AXI4-Stream用得最多。为什么?因为它简单、高效。没有地址,没有写响应,只有数据通道。数据从上游模块流到下游模块,中间可以插入FIFO、寄存器切片,甚至可以做数据格式转换。
AXI4-Stream的关键信号其实不多:
- TVALID:发送方表示数据有效
- TREADY:接收方表示可以接收
- TDATA:数据总线,位宽可以是8、16、32、64……
- TLAST:表示这是最后一笔数据(比如一行的最后一个像素)
- TUSER:用户自定义信号,我常用它来传递帧同步信号
- TKEEP:指示哪些字节是有效的(用于非对齐传输)
我的经验:在图像处理中,TUSER信号特别有用。我习惯把帧起始信号(SOF)和行起始信号(SOL)编码到TUSER里,这样下游模块不用额外计数就能知道当前处理到哪一帧、哪一行了。
1.5 AXI4与AXI4-Lite:地址映射传输
AXI4和AXI4-Lite都支持地址映射,区别在于:
- AXI4:支持突发传输(Burst),一次地址请求可以传输多笔数据。数据位宽可以到1024位,适合大块数据搬运。
- AXI4-Lite:不支持突发,每次只能传输一笔数据。数据位宽固定为32位或64位。逻辑简单,资源少。
在图像加速系统中,我通常这样分配:
- DDR读写 → AXI4(高带宽,突发传输)
- 寄存器配置 → AXI4-Lite(简单,不占资源)
- 模块间像素传输 → AXI4-Stream(无地址,流水线式)
1.6 握手时序:一个简单的Verilog示例
下面是一个AXI4-Stream发送端的简单实现。注意看VALID和READY的配合:
// AXI4-Stream发送端示例
module axis_sender (
input wire clk,
input wire rst_n,
// 上游数据接口
input wire [31:0] data_in,
input wire data_valid,
output wire data_ready,
// AXI4-Stream接口
output reg [31:0] m_axis_tdata,
output reg m_axis_tvalid,
input wire m_axis_tready,
output reg m_axis_tlast
);
// 握手逻辑:当VALID和READY同时为高时,数据被接收
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
m_axis_tvalid <= 1'b0;
m_axis_tdata <= 32'd0;
m_axis_tlast <= 1'b0;
end else begin
// 如果接收方准备好,且我们有数据
if (data_valid && m_axis_tready) begin
m_axis_tvalid <= 1'b1;
m_axis_tdata <= data_in;
// 这里假设每传输10笔数据后拉高TLAST
// 实际项目中需要根据帧/行长度判断
end else if (m_axis_tready) begin
// 没有数据时,拉低VALID
m_axis_tvalid <= 1'b0;
end
end
end
// 上游数据准备好信号
assign data_ready = m_axis_tready;
endmodule
注意:上面的代码是一个简化版本。实际项目中,你还需要考虑背压(Backpressure)处理、TLAST的精确生成、以及跨时钟域同步等问题。我曾经在一个项目中忽略了TLAST的时序,结果导致图像最后一行数据丢失,画面出现「黑边」。从那以后,我对TLAST的生成逻辑格外小心。
1.7 三种协议的对比总结
为了让你更直观地理解三种协议的区别,我画了一张对比图:
这张图很直观地展示了三者的定位。AXI4像个「大力士」,能扛能打;AXI4-Lite像个「小管家」,轻巧灵活;AXI4-Stream则像个「快递员」,只管送货不问地址。
1.8 实际项目中的避坑指南
最后,分享几个我在项目中踩过的坑:
- 不要忽略TLAST:在图像行传输中,TLAST必须精确对应每行的最后一个像素。我曾经因为TLAST提前了一个时钟周期,导致下游的行缓冲FIFO错位,整幅图像出现了「锯齿」。
- 注意背压传播:AXI4-Stream的背压会向上游传播。如果下游处理速度慢,上游的VALID会被拉低,整个流水线都会停顿。建议在关键路径上插入寄存器切片(Register Slice)来打断长路径。
- AXI4-Lite的地址对齐:AXI4-Lite要求地址按数据位宽对齐。比如32位总线,地址必须是4的倍数。这个细节很容易被忽略,但一旦出错,寄存器配置就会「写偏」。
- 跨时钟域处理:如果AXI4-Stream跨越了不同的时钟域,一定要用异步FIFO做同步。我见过有人直接用寄存器打两拍来处理,结果数据在跨时钟域时出现了亚稳态,导致图像出现「花屏」。
我的建议:刚开始接触AXI时,可以先从AXI4-Stream入手。它逻辑简单,容易上手。等熟悉了握手机制,再去看AXI4和AXI4-Lite的完整规范。ARM官方文档《AMBA AXI and ACE Protocol Specification》是必读的,虽然厚了点,但每读一遍都有新收获。