AXI 总线详解与 FPGA 实现实战指南
AXI 总线作为 ARM AMBA 协议簇的核心成员,是高性能片上互联的事实标准。在 FPGA 与 SoC 开发中,它是连接 CPU、DDR、DMA 及各类外设 IP 的高速桥梁,掌握其机制对于理解芯片内部数据交互至关重要。
1. AXI 总线的核心特性
AXI 之所以成为主流,源于其设计上的几个关键特性,所有设计都围绕「提升传输效率、降低复杂度」展开:
- 地址与控制信号分离:地址和控制信息先传输,数据随后独立传输,互不阻塞,大幅提升利用率。
- 5 个独立的单向传输通道:这是 AXI 高性能的核心,通道间完全独立、互不干扰。
- 支持突发传输(Burst Transfer):只需发送一次地址 + 控制信息,即可连续传输多拍数据,适合大数据块读写。
- READY-VALID 流控握手:所有通道遵循这套异步握手机制,无时钟同步要求。只有当
VALID & READY = 1时,本次传输才算真正完成。这完美解决了总线拥塞和跨时钟域问题。 - 读写分离:读操作和写操作的通道完全独立,可同时进行。
2. AXI 的三大核心子集
在 FPGA 开发中,我们主要用到以下三个子集,它们定位不同但设计思想一致:
2.1 AXI4-Lite(轻量级)
用于低速、寄存器级读写。只支持单拍传输,信号线极少,实现难度低。常用于外设 IP 的配置与控制,如 GPIO、SPI、UART 等寄存器的读写。
2.2 AXI4(标准版)
用于高速、大数据块传输。支持 1~256 拍突发传输,位宽灵活。适用于 DDR 读写、DMA 数据搬移等高速场景。
2.3 AXI4-Stream(流式)
无地址线,纯数据流。专为连续串行数据传输设计。核心握手机制为 TVALID-TREADY,可选 TLAST 标记帧结束。这是 FPGA 中最高频的接口,广泛用于视频流、ADC 采集、以太网包处理等。
3. FPGA 实现 AXI 的两种思路
在 FPGA 中实现 AXI 总线,主要有两种方式,建议优先使用第一种。
3.1 调用官方 IP 核(推荐)
FPGA 厂商(Xilinx/Intel)对 AXI4 协议做了原生固化支持。所有官方 IP 核(DDR、DMA、以太网等)的顶层接口均为 AXI 标准接口。
核心优势:内置完整协议逻辑,经过量产验证,稳定性高。
实现流程:
- 新建工程,按需添加 AXI IP 核(如 AXI GPIO、AXI DMA)。
- 图形化配置参数(位宽、突发长度等)。
- 使用 Block Design 或 Platform Designer 工具,拖拽连接各 IP 核的 AXI 接口,工具会自动生成互联逻辑。
- 生成 HDL 代码并编译下载。
这种方式相当于'拼积木',无需手写协议细节即可完成大部分开发需求。
3.2 手动编写接口适配(进阶)
当官方 IP 无法满足定制化需求时,需手动编写少量 Verilog 代码实现接口适配。
核心原则:只写「接口适配层」,不写完整协议栈。重点实现 READY/VALID 握手和数据映射。
以下是两个高频场景的代码示例。
案例 1:手动实现 AXI4-Lite 从机
此模块实现一个简单的 AXI4-Lite 从机,包含两个 32 位寄存器。主机可通过 AXI4-Lite 读写这两个寄存器。核心逻辑是处理地址、数据、响应的握手时序。
// AXI4-Lite 从机核心逻辑 (32 位地址/数据)
module axi4_lite_slave(
input clk,
input rst_n,
// 写地址通道
input [31:0] s_axi_awaddr,
input s_axi_awvalid,
output reg s_axi_awready,
// 写数据通道
input [31:0] s_axi_wdata,
input s_axi_wvalid,
output reg s_axi_wready,
// 写响应通道
output reg [1:0] s_axi_bresp,
output reg s_axi_bvalid,
input s_axi_bready,
// 读地址通道
input [31:0] s_axi_araddr,
input s_axi_arvalid,
output reg s_axi_arready,
// 读数据通道
output reg [31:0] s_axi_rdata,
output reg [1:0] s_axi_rresp,
output reg s_axi_rvalid,
input s_axi_rready
);
// 自定义寄存器:地址 0x00=控制寄存器,0x04=状态寄存器
reg [31:0] ctrl_reg;
reg [31:0] stat_reg;
// 写事务:地址握手 + 数据握手 + 响应
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
s_axi_awready <= 1'b0;
s_axi_wready <= 1'b0;
s_axi_bvalid <= 1'b0;
ctrl_reg <= 32'd0;
end else begin
// 写地址就绪:当地址有效且数据未就绪时
if(s_axi_awvalid && !s_axi_awready) begin
s_axi_awready <= 1'b1;
end
// 写数据就绪:当地址已就绪且数据有效时
if(s_axi_wvalid && !s_axi_wready && s_axi_awready) begin
s_axi_wready <= 1'b1;
// 根据地址写寄存器
case(s_axi_awaddr)
32'h00000000: ctrl_reg <= s_axi_wdata;
32'h00000004: stat_reg <= s_axi_wdata;
default: ;
endcase
// 写响应有效
s_axi_bvalid <= 1'b1;
s_axi_bresp <= 2'b00; // 响应:成功
end
// 响应握手完成,清零就绪和有效信号
if(s_axi_bvalid && s_axi_bready) begin
s_axi_awready <= 1'b0;
s_axi_wready <= 1'b0;
s_axi_bvalid <= 1'b0;
end
end
end
// 读事务:地址握手 + 数据握手
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
s_axi_arready <= 1'b0;
s_axi_rvalid <= 1'b0;
s_axi_rdata <= 32'd0;
end else begin
// 读地址就绪:当地址有效且数据未就绪时
if(s_axi_arvalid && !s_axi_arready) begin
s_axi_arready <= 1'b1;
// 根据地址读寄存器
case(s_axi_araddr)
32'h00000000: s_axi_rdata <= ctrl_reg;
32'h00000004: s_axi_rdata <= stat_reg;
default: s_axi_rdata <= 32'h00000000;
endcase
// 读数据有效
s_axi_rvalid <= 1'b1;
s_axi_rresp <= 2'b00; // 响应:成功
end
// 读数据握手完成,清零就绪和有效信号
if(s_axi_rvalid && s_axi_rready) begin
s_axi_arready <= 1'b0;
s_axi_rvalid <= 1'b0;
end
end
end
endmodule


