3、AXI4-Lite接口实战:从PL侧控制PS侧GPIO、寄存器映射机制、自定义AXI4-Lite IP核封装
这一章,我们来啃一块硬骨头——AXI4-Lite。说实话,很多初学者一听到AXI总线就头大,觉得协议复杂、时序难懂。其实AXI4-Lite是AXI家族里最温柔的一个成员了。它没有突发传输,没有乱序响应,说白了就是一套带握手的地址+数据读写通道。我当年第一次在Zynq上跑通PL控制PS的GPIO时,那种感觉就像打通了任督二脉——原来软硬件协同就这么回事。
3.1 为什么需要从PL侧控制PS侧GPIO?
你可能会问:PS本身就有GPIO,为什么还要PL去控制?嗯,这个问题我刚开始也困惑过。后来在做一个高速数据采集项目时,PS的GPIO响应速度跟不上PL的逻辑处理速度,我才意识到——有些场景下,PL需要直接接管控制权。
典型的应用场景包括:
- 高速握手信号:PL需要快速拉高/拉低某个PS的GPIO,用于触发中断或状态指示
- 系统级复位控制:PL检测到异常后,直接通过PS的GPIO复位整个系统
- 多核同步:PL作为主控,统一管理PS侧的多个外设使能信号
说白了,就是让PL和PS之间有一条「高速公路」,而不是绕道DDR或者中断这种慢车道。
3.2 寄存器映射机制——你得懂的内存地图
在Zynq里,PS侧的GPIO被映射到固定的地址空间。具体来说,PS的GPIO控制器基地址是0xE000A000。这个地址,PL通过AXI4-Lite总线可以直接访问。
我习惯把寄存器映射理解成「门牌号系统」。每个寄存器就是一个房间,地址就是门牌号。PL要做的,就是拿着门牌号去敲门,然后读写房间里的数据。
PS GPIO的关键寄存器偏移如下:
| 寄存器名称 | 偏移地址 | 功能描述 |
|---|---|---|
| GPIO_DATA_RO | 0x000 | 读GPIO输入状态(只读) |
| GPIO_DATA | 0x000 | 写GPIO输出值(写有效) |
| GPIO_DIRM | 0x204 | 方向控制寄存器(0=输入,1=输出) |
| GPIO_OUTEN | 0x208 | 输出使能寄存器 |
3.3 自定义AXI4-Lite IP核封装——从零开始
Vivado里自带了一个很方便的工具——Create and Package New IP。我个人建议你直接用它生成模板,然后修改。自己手写AXI4-Lite接口?除非你想挑战自己,否则没必要。
具体步骤是这样的:
- 打开Vivado,点击Tools → Create and Package New IP
- 选择Create a new AXI4 peripheral
- 设置IP名称、版本、接口类型(选AXI4-Lite)
- 配置寄存器数量(一般4~8个够用)
- 生成模板后,修改RTL代码
生成的模板里,核心就是那个axi_lite_ipif模块。它帮你处理了地址译码、握手信号、读写响应。你只需要关注用户逻辑部分。
下面是我常用的一个简化版Verilog代码框架:
module my_axi_gpio_ctrl #
(
parameter integer C_S_AXI_DATA_WIDTH = 32,
parameter integer C_S_AXI_ADDR_WIDTH = 4
)
(
input wire S_AXI_ACLK,
input wire S_AXI_ARESETN,
// 写地址通道
input wire [C_S_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR,
input wire S_AXI_AWVALID,
output wire S_AXI_AWREADY,
// 写数据通道
input wire [C_S_AXI_DATA_WIDTH-1:0] S_AXI_WDATA,
input wire S_AXI_WVALID,
output wire S_AXI_WREADY,
// 写响应通道
output wire [1:0] S_AXI_BRESP,
output wire S_AXI_BVALID,
input wire S_AXI_BREADY,
// 读地址通道
input wire [C_S_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR,
input wire S_AXI_ARVALID,
output wire S_AXI_ARREADY,
// 读数据通道
output wire [C_S_AXI_DATA_WIDTH-1:0] S_AXI_RDATA,
output wire [1:0] S_AXI_RRESP,
output wire S_AXI_RVALID,
input wire S_AXI_BREADY,
// 用户逻辑接口
output wire [31:0] gpio_out,
input wire [31:0] gpio_in
);
// 寄存器声明
reg [31:0] slv_reg0; // 输出控制寄存器
reg [31:0] slv_reg1; // 方向控制寄存器
reg [31:0] slv_reg2; // 保留
reg [31:0] slv_reg3; // 保留
// 地址译码逻辑
always @(posedge S_AXI_ACLK)
begin
if (!S_AXI_ARESETN) begin
slv_reg0 <= 32'h0;
slv_reg1 <= 32'h0;
end else begin
if (S_AXI_AWVALID && S_AXI_WVALID) begin
case (S_AXI_AWADDR[3:2])
2'b00: slv_reg0 <= S_AXI_WDATA;
2'b01: slv_reg1 <= S_AXI_WDATA;
endcase
end
end
end
// 读操作
assign S_AXI_RDATA = (S_AXI_ARADDR[3:2] == 2'b00) ? slv_reg0 :
(S_AXI_ARADDR[3:2] == 2'b01) ? slv_reg1 :
gpio_in; // 读GPIO输入状态
// 输出赋值
assign gpio_out = slv_reg0;
endmodule
[3:2]而不是[1:0]。因为AXI4-Lite的地址是以字节为单位的,但数据总线是32位,所以最低两位其实没用。用[3:2]可以避免很多地址对齐的坑。
3.4 实战:PL控制PS的MIO
好了,理论说完了,咱们直接上实战。假设我们要用PL控制PS的MIO7(板载LED)。
第一步,在Vivado里添加我们刚才封装的IP核。然后连接:
- IP的AXI4-Lite接口连接到Zynq的M_AXI_GP0口
- IP的gpio_out[0]连接到PS的GPIO输入(通过EMIO)
第二步,在PS端配置EMIO。SDK里写一段简单的初始化代码:
#include "xgpio.h"
#include "xparameters.h"
XGpio ps_gpio;
int main()
{
// 初始化PS GPIO
XGpio_Initialize(&ps_gpio, XPAR_PS7_GPIO_0_DEVICE_ID);
// 设置EMIO方向为输入(从PL接收)
XGpio_SetDataDirection(&ps_gpio, 2, 0xFF); // Bank2对应EMIO
// 读取PL写入的值
u32 pl_value = XGpio_DiscreteRead(&ps_gpio, 2);
// 控制板载LED
XGpio_DiscreteWrite(&ps_gpio, 1, pl_value);
return 0;
}
第三步,在PL侧通过AXI4-Lite写寄存器。比如我们要让LED闪烁:
// 在PL的testbench或状态机中
// 地址0xE000A000 + 0x000 = GPIO_DATA
// 写入0x00000080 表示MIO7输出高电平
// 写入0x00000000 表示MIO7输出低电平
// 实际通过AXI4-Lite IP核操作
// 写slv_reg0 = 0x00000001 // 让gpio_out[0] = 1
// 等待一段时间
// 写slv_reg0 = 0x00000000 // 让gpio_out[0] = 0
3.5 避坑指南——我曾经踩过的雷
做这个实验时,我遇到过几个典型问题,分享给你:
- 地址不对齐:AXI4-Lite要求地址是4字节对齐的。如果你写了个奇数地址,总线会直接返回错误响应。我当时查了三天,最后发现是地址偏移算错了。
- 握手信号没处理好:AWREADY和WREADY必须同时为高,写操作才能完成。有些新手只拉了一个,结果数据写不进去。
- 复位状态不确定:PS的GPIO在复位后默认是输入状态。如果你PL一上来就写输出,PS可能不响应。记得先配置方向寄存器。
嗯,这些坑说大不大,但卡住你两三天是没问题的。我建议你调试时,先用ILA抓一下AXI总线的波形,看看地址和数据对不对。很多时候,问题出在你自己都没注意到的细节上。
3.6 知识体系总览
下面这张图,是我自己总结的AXI4-Lite控制PS GPIO的完整数据流。你看一眼,应该就能明白整个链路是怎么走的。
你看,整个链路其实很清晰。PL通过AXI4-Lite总线,把数据写到PS的GPIO寄存器里,PS再把这些数据驱动到物理引脚上。反过来,PL也可以读PS的GPIO输入状态。这就是所谓的「软硬件协同」——PL负责高速逻辑,PS负责管理和调度。
好了,这一章的内容就到这里。你如果跟着做一遍,应该能对AXI4-Lite有更深的体会。记住,纸上得来终觉浅,绝知此事要躬行。去打开Vivado,动手试试吧。