手把手教你用AXI4-Lite在ZYNQ上做个简易“聊天室”:PS发指令,PL回数据

张开发
2026/4/22 11:43:28 15 分钟阅读

分享文章

手把手教你用AXI4-Lite在ZYNQ上做个简易“聊天室”:PS发指令,PL回数据
用AXI4-Lite在ZYNQ上搭建PS与PL的对话系统想象一下ZYNQ芯片中的处理器系统(PS)和可编程逻辑(PL)就像两个需要频繁交流的同事。PS是大脑负责决策和复杂计算PL是四肢擅长快速执行特定任务。要让它们高效协作就需要一套可靠的对话机制——这就是AXI4-Lite总线协议的用武之地。本文将带你用Vivado和Vitis打造一个简易的聊天室让PS和PL能够自由交换数据。1. 项目规划与AXI4-Lite基础在开始动手前我们需要明确几个关键概念。AXI4-Lite是ARM AMBA总线协议的精简版本专为低复杂度、低功耗的寄存器级通信设计。它相比完整的AXI4协议去除了突发传输、缓存支持等高级功能保留了最基本的读写操作特别适合PS与PL之间的控制信号和小数据量传输。我们的聊天室项目需要实现以下功能PS端能够发送32位整数指令到PLPL端能够处理指令并返回32位结果数据通信过程稳定可靠时序符合AXI4-Lite规范硬件资源需求Xilinx ZYNQ系列开发板如Zybo、Pynq等Vivado设计套件2020.1或更新版本Vitis统一软件平台2. Vivado中的AXI-Lite IP核定制2.1 创建基础AXI-Lite外设启动Vivado后按照以下步骤创建自定义IP在Tools菜单中选择Create and Package New IP选择Create AXI4 Peripheral选项设置IP名称为axi_chatroom避免使用默认myip接口配置Interface Type: LiteInterface Mode: SlaveData Width: 32Number of Registers: 4注意虽然我们只需要2个寄存器但AXI-Lite IP核要求最少4个寄存器。多出的2个可以保持默认配置。2.2 添加自定义通信端口在自动生成的IP核代码中我们需要添加两个关键端口// 在axi_lite_slave.v的端口声明部分添加 output reg [31:0] pl_response_data, // PL→PS的数据通道 input wire [31:0] ps_command_data // PS→PL的指令通道然后修改寄存器读写逻辑将slv_reg0映射到PS指令slv_reg1映射到PL响应// 接收PS指令的逻辑 always (posedge S_AXI_ACLK) begin if (S_AXI_ARESETN 1b0) begin slv_reg0 0; end else if (slv_reg_wren 1 axi_awaddr[3:2] 2b00) begin slv_reg0 S_AXI_WDATA; ps_command_data S_AXI_WDATA; // 将写入值同时输出到自定义端口 end end // 发送PL响应的逻辑 always (posedge S_AXI_ACLK) begin if (S_AXI_ARESETN 1b0) begin slv_reg1 0; end else begin slv_reg1 pl_response_data; // 持续将PL数据反映到寄存器 end end2.3 集成IP核到Block Design完成代码修改后执行IP打包操作然后在Block Design中添加这个自定义IP右键画布选择Add IP搜索axi_chatroom连接AXI接口到ZYNQ处理器的M_AXI_GP0端口将自定义端口ps_command_data和pl_response_data引出到顶层运行自动连接验证设计无误后生成Bitstream关键连接检查点AXI时钟和复位信号正确连接自定义端口在顶层有明确定义地址空间分配合理可在Address Editor中查看3. Vitis中的PS端程序设计3.1 建立基础工程结构导出硬件后切换到Vitis环境新建Application Project选择从Vivado导出的.xsa文件创建空白C工程比模板工程更灵活在src文件夹下新建main.c文件包含必要的头文件#include stdio.h #include xparameters.h #include xil_io.h #include axi_chatroom.h3.2 实现双向通信逻辑首先定义寄存器偏移量提高代码可读性#define CMD_REG_OFFSET 0 // PS写指令的寄存器 #define RESP_REG_OFFSET 4 // PS读响应的寄存器然后编写主通信函数void chat_with_pl(u32 baseaddr, u32 command) { // PS发送指令 AXI_CHATROOM_mWriteReg(baseaddr, CMD_REG_OFFSET, command); printf(PS发送: %d\n, command); // 等待PL处理简单延时 for(int i0; i100000; i); // PS读取响应 u32 response AXI_CHATROOM_mReadReg(baseaddr, RESP_REG_OFFSET); printf(PL回复: %d\n, response); }在main函数中初始化并测试通信int main() { u32 baseaddr XPAR_AXI_CHATROOM_0_S_AXI_BASEADDR; printf(AXI-Lite聊天室启动...\n); // 测试通信 for(int i1; i5; i) { chat_with_pl(baseaddr, i*10); } return 0; }4. PL端逻辑设计与功能扩展4.1 基本响应逻辑实现在PL端我们可以设计简单的处理逻辑比如将接收到的数值加1后返回// 在自定义IP的顶层模块中添加处理逻辑 always (posedge S_AXI_ACLK) begin if (!S_AXI_ARESETN) begin pl_response_data 32h0; end else begin // 简单示例将PS发送的值1后返回 pl_response_data ps_command_data 1; end end4.2 进阶功能状态机实现要实现更复杂的交互可以在PL端添加状态机// 定义状态编码 localparam IDLE 2b00; localparam PROCESS 2b01; localparam RESPOND 2b10; reg [1:0] state; reg [31:0] processed_data; always (posedge S_AXI_ACLK) begin if (!S_AXI_ARESETN) begin state IDLE; pl_response_data 0; end else begin case(state) IDLE: if (ps_command_data ! 0) begin processed_data ps_command_data * 2; // 示例处理乘以2 state PROCESS; end PROCESS: begin pl_response_data processed_data 1; // 再加1 state RESPOND; end RESPOND: if (ps_command_data 0) // 等待PS清零 state IDLE; endcase end end4.3 调试技巧与性能优化在实际开发中有几个关键点需要注意时序约束确保AXI接口满足时序要求create_clock -name S_AXI_ACLK -period 10 [get_ports S_AXI_ACLK]调试信号添加ILA核监控关键信号ila_0 your_ila_instance ( .clk(S_AXI_ACLK), .probe0(ps_command_data), .probe1(pl_response_data), .probe2(state) );性能指标对比优化方式延迟(周期)资源消耗(LUT)直接传递150流水线处理375状态机实现5-10120在实际项目中我遇到过PL响应不及时导致PS读取旧数据的问题。解决方案是在PL处理完成后生成一个中断信号PS收到中断后再读取数据这样既保证了数据新鲜度又避免了PS不断轮询的开销。

更多文章