无DMA场景下Zynq PS读取AXI连接FIFO的SDK方法及替代方案咨询
刚接触Zynq FPGA开发就遇到DMA资源被占的场景,确实得灵活调整方案。我来一步步帮你解决这两个问题:
首先明确:你通过AXI Interconnect挂到PS的FIFO,应该是AXI Memory-Mapped (MM) 接口的FIFO(或是搭配了AXI Stream转MM的桥接IP)。这类FIFO会被映射到PS的内存地址空间,你可以直接通过寄存器读写获取数据,步骤如下:
步骤1:确认FIFO的寄存器映射
在Vivado导出硬件到SDK后,打开xparameters.h文件,里面会有你的自定义FIFO基地址(比如XPAR_MY_CUSTOM_FIFO_0_BASEADDR)。同时要明确两个核心寄存器的偏移地址:
- 状态寄存器:用来判断FIFO是否非空/满,通常偏移
0x00,比如FIFO_STATUS_REG = 基地址 + 0x00 - 数据寄存器:读取FIFO数据的端口,通常偏移
0x04,比如FIFO_DATA_REG = 基地址 + 0x04
(注:如果是你自己用Verilog写的FIFO,要确保读数据寄存器时,FIFO会自动弹出当前数据、读指针自动递增)
步骤2:轮询方式读取(基础版)
如果数据量不大,轮询是最简单的实现方式。用Xilinx提供的Xil_In32()函数直接读写寄存器,示例代码如下:
#include "xil_io.h" #include "xparameters.h" #include "xil_types.h" // 根据你的硬件定义调整地址和掩码 #define FIFO_BASE_ADDR XPAR_MY_CUSTOM_FIFO_0_BASEADDR #define FIFO_STATUS_REG (FIFO_BASE_ADDR + 0x00) #define FIFO_DATA_REG (FIFO_BASE_ADDR + 0x04) #define FIFO_EMPTY_MASK 0x01 // 假设状态寄存器bit0表示FIFO为空 u32 read_fifo(u32 *recv_buffer, u32 max_read_num) { u32 read_count = 0; while (read_count < max_read_num) { // 读取状态寄存器,检查FIFO是否非空 u32 fifo_status = Xil_In32(FIFO_STATUS_REG); if (!(fifo_status & FIFO_EMPTY_MASK)) { // 读取一个32位数据,FIFO自动弹出下一个数据 recv_buffer[read_count++] = Xil_In32(FIFO_DATA_REG); } else { // FIFO空了,退出循环(也可以加小延时重试) break; } } return read_count; }
步骤3:中断方式读取(高效版)
如果数据是周期性产生,或是不想占用PS过多CPU资源,建议用中断触发:
- 在Vivado中把FIFO的非空中断信号连接到PS的GIC(通用中断控制器),并在SDK的
xparameters.h中配置对应中断号。 - 在SDK中初始化GIC、注册中断处理函数,当FIFO非空时触发中断,在中断函数中批量读取数据。
核心中断处理逻辑示例:
void fifo_interrupt_handler(void *CallbackRef) { u32 *buffer = (u32 *)CallbackRef; static u32 idx = 0; // 读取数据直到FIFO为空 while (!(Xil_In32(FIFO_STATUS_REG) & FIFO_EMPTY_MASK)) { buffer[idx++] = Xil_In32(FIFO_DATA_REG); // 务必清除中断标志,避免重复触发 Xil_Out32(FIFO_STATUS_REG, 0x00); // 假设写0清除中断 } }
根据你的数据量、速率需求,还有这些可行方案可选:
共享内存传输(推荐大数据量场景)
利用PS和PL共享的内存(片上OCM或DDR),PL通过AXI HP/ACP接口把数据写入共享内存区域,PS直接访问该内存。注意:- 在Vivado中配置共享内存的地址范围,确保PL和PS都能访问
- PS读取前要调用
Xil_DCacheInvalidateRange()清除缓存,确保读到最新数据 - 可配合中断:PL写完数据后触发中断,通知PS读取,避免轮询开销
AXI Stream转MM桥接
如果PL端是AXI Stream格式的数据,用Xilinx官方的AXI Stream to Memory MappedIP把流数据转换成MM接口,PS直接读取映射地址即可。这种方式适合连续流数据,无需自己编写FIFO逻辑。MicroBlaze代理传输
如果项目中有MicroBlaze软核,可以让MicroBlaze先接收PL的数据(比如读FIFO),再通过AXI接口转发到PS的内存或寄存器。适合需要对数据做预处理(格式转换、过滤)的场景,能减轻PS的计算负担。AXI Lite寄存器批量写入
在PL中设计多个AXI Lite寄存器,把数据分批写入这些寄存器,PS一次性读取所有寄存器。适合小批量数据,比单个FIFO更灵活,但需要硬件端额外设计寄存器组。GPIO模拟简易总线(仅应急小数据量)
极端情况下,用PS的GPIO引脚模拟并行总线(地址线、数据线、读写控制),PL把数据放到GPIO数据线上,PS通过GPIO读取。但这种方式速率极低,仅适合几个字节的极小数据量,不推荐常规使用。
内容的提问来源于stack exchange,提问作者Nikolay Josh Konovalenko

