ZYNQ AXI DMA实战:从PL到PS DDR的高效数据流设计

张开发
2026/5/7 3:14:18 15 分钟阅读
ZYNQ AXI DMA实战:从PL到PS DDR的高效数据流设计
1. 为什么需要AXI DMA在ZYNQ平台上PL可编程逻辑和PS处理系统之间的数据交互是很多嵌入式系统设计的核心问题。想象一下你正在设计一个高速数据采集系统比如数字示波器或者图像传感器接口PL端产生的数据量可能达到每秒几百MB甚至几个GB。这时候如果还用传统的AXI GPIO或者BRAM来传输数据就像用吸管喝一大桶水——效率实在太低了。我刚开始接触ZYNQ时也犯过这个错误。当时做了一个简单的ADC数据采集项目用AXI GPIO传输数据结果发现最大传输速度连10MB/s都达不到。后来改用AXI DMA后速度直接飙升到400MB/s以上这就是硬件加速的魅力所在。AXI DMA的全称是AXI Direct Memory Access它最大的优势就是能够实现零CPU干预的数据传输。PL端的数据可以直接写入PS端的DDR内存或者从DDR内存读取数据到PL端整个过程不需要PS端的ARM核参与搬运数据。这就好比在PL和PS之间修了一条高速公路数据可以自驾到达目的地。2. 硬件设计全解析2.1 ZYNQ Processing System配置在Vivado中创建Block Design时第一步就是配置ZYNQ Processing System IP核。这里有几个关键设置需要注意HP端口使能在PS-PL Configuration HP Slave AXI Interface下确保至少使能一个HPHigh Performance端口。我一般习惯用S_AXI_HP0因为它的默认时钟频率较高。中断配置在Interrupts PL-PS Interrupt Ports下使能Fabric Interrupts。这样PL端才能通过中断通知PS端DMA传输完成。时钟配置建议将HP端口的时钟频率设置到最高稳定值。在ZYNQ-7000上我通常用150MHz在UltraScale上可以到250MHz甚至更高。提示不同开发板的HP端口支持的最大频率可能不同建议查阅开发板手册确认。2.2 AXI DMA IP核配置AXI DMA IP核是整个数据通路的核心它的配置直接影响传输性能基本模式选择对于PL到PS的单向传输选择Scatter Gather模式即可。如果要做双向传输才需要选择Scatter Gather and Memory Map。通道使能因为我们只需要PL向PS写数据所以只使能S2MMStream to Memory Map通道。MM2S通道可以禁用。数据宽度这个参数必须和你的数据生成模块匹配。如果PL端数据是32位的这里就选32如果是64位就选64。突发长度建议设置为最大值256这样可以最大化总线利用率。我在一个图像处理项目中实测过将突发长度从16提升到256后传输效率提高了近30%。2.3 数据生成模块设计数据生成模块是PL端的数据源这里给出一个改进版的Verilog代码module dma_data_gen #( parameter DATA_WIDTH 32, parameter BURST_LEN 256 )( input clk, input resetn, input start, output [DATA_WIDTH-1:0] m_axis_tdata, output m_axis_tvalid, input m_axis_tready, output m_axis_tlast ); localparam IDLE 0; localparam TRANSFER 1; reg state; reg [DATA_WIDTH-1:0] counter; reg [7:0] burst_counter; always (posedge clk or negedge resetn) begin if (!resetn) begin state IDLE; counter 0; burst_counter 0; end else begin case (state) IDLE: begin if (start) begin state TRANSFER; counter 0; burst_counter 0; end end TRANSFER: begin if (m_axis_tready) begin if (burst_counter BURST_LEN-1) begin burst_counter 0; counter counter 1; end else begin burst_counter burst_counter 1; end if (counter 1023 burst_counter BURST_LEN-1) begin state IDLE; end end end endcase end end assign m_axis_tdata counter; assign m_axis_tvalid (state TRANSFER); assign m_axis_tlast (burst_counter BURST_LEN-1); endmodule这个模块相比原始版本有几个改进增加了参数化设计方便调整数据宽度和突发长度简化了状态机只保留必要的IDLE和TRANSFER状态增加了burst_counter确保每次传输都符合AXI协议的突发要求2.4 数据通路连接技巧在Block Design中连接各个IP时有几个关键点需要注意时钟域一致性确保AXI DMA、FIFO和数据生成模块使用同一个时钟。我遇到过因为时钟不同步导致的数据丢失问题调试了很久才发现。FIFO深度设置AXI Stream FIFO的深度要足够大一般至少设置为1024。太小的FIFO会导致数据吞吐量下降。SmartConnect使用在ZYNQ-7000上建议在DMA和HP端口之间添加AXI SmartConnect IP它可以优化AXI总线效率。但在UltraScale上可以直接使用NoCNetwork on Chip。3. 软件驱动开发3.1 DMA驱动初始化DMA驱动的初始化是软件部分的关键这里给出一个更健壮的初始化函数int dma_init(XAxiDma *dma_inst) { XAxiDma_Config *cfg; int status; // 查找DMA配置 cfg XAxiDma_LookupConfig(XPAR_AXIDMA_0_DEVICE_ID); if (!cfg) { xil_printf(DMA config lookup failed\r\n); return XST_FAILURE; } // 初始化DMA实例 status XAxiDma_CfgInitialize(dma_inst, cfg); if (status ! XST_SUCCESS) { xil_printf(DMA initialization failed: %d\r\n, status); return status; } // 检查Scatter Gather模式 if (XAxiDma_HasSg(dma_inst)) { xil_printf(DMA configured in SG mode\r\n); return XST_FAILURE; } // 复位DMA XAxiDma_Reset(dma_inst); while (!XAxiDma_ResetIsDone(dma_inst)); return XST_SUCCESS; }这个初始化函数增加了错误检查和复位操作可以避免很多潜在问题。我在实际项目中发现有时DMA IP核上电后状态不正常主动复位一次就能解决。3.2 中断处理优化DMA传输完成通常通过中断通知PS端下面是一个更完善的中断处理实现volatile int dma_done 0; volatile int dma_error 0; void dma_intr_handler(void *callback) { XAxiDma *dma (XAxiDma *)callback; u32 status; // 读取中断状态 status XAxiDma_IntrGetIrq(dma, XAXIDMA_DEVICE_TO_DMA); // 确认中断 XAxiDma_IntrAckIrq(dma, status, XAXIDMA_DEVICE_TO_DMA); // 错误处理 if (status XAXIDMA_IRQ_ERROR_MASK) { dma_error 1; XAxiDma_Reset(dma); while (!XAxiDma_ResetIsDone(dma)); return; } // 传输完成 if (status XAXIDMA_IRQ_IOC_MASK) { dma_done 1; } }这个中断处理程序增加了错误状态检测和自动复位功能可以应对传输过程中出现的各种异常情况。3.3 主程序流程主程序的典型流程如下#define BUF_SIZE (1024 * 1024) // 1MB缓冲区 int main() { int *buffer; int status; // 分配对齐的内存 buffer (int *)memalign(32, BUF_SIZE); if (!buffer) { xil_printf(Memory allocation failed\r\n); return XST_FAILURE; } // 初始化DMA status dma_init(dma); if (status ! XST_SUCCESS) { free(buffer); return status; } // 设置中断 setup_interrupts(dma, dma_intr_handler); // 启动DMA传输 status XAxiDma_SimpleTransfer(dma, (u32)buffer, BUF_SIZE, XAXIDMA_DEVICE_TO_DMA); if (status ! XST_SUCCESS) { free(buffer); return status; } // 等待传输完成 while (!dma_done !dma_error); if (dma_error) { xil_printf(DMA transfer error occurred\r\n); free(buffer); return XST_FAILURE; } // 验证数据 for (int i 0; i BUF_SIZE/4; i) { if (buffer[i] ! expected_value(i)) { xil_printf(Data verification failed at %d\r\n, i); free(buffer); return XST_FAILURE; } } free(buffer); return XST_SUCCESS; }这个主程序演示了完整的DMA传输流程包括内存分配、DMA初始化、中断设置、传输启动和数据验证。特别注意以下几点使用memalign分配对齐的内存这对DMA性能很重要每次传输后都要检查错误状态传输完成后验证数据正确性4. 调试技巧与性能优化4.1 常见问题排查在实际项目中我遇到过各种DMA传输问题总结出以下排查步骤检查DMA复位状态有时候DMA IP核没有正确复位会导致传输失败。可以在初始化后手动复位一次。验证内存地址确保传递给DMA的内存地址是物理地址并且在DMA可访问的范围内。在Linux系统中尤其要注意这一点。检查数据对齐DMA对数据地址和长度通常有对齐要求比如32字节对齐。不对齐会导致性能下降甚至传输失败。监控中断状态如果传输没有完成检查中断是否被正确触发。可以在中断处理函数中添加调试打印。使用ILA调试在Vivado中添加ILA核监控AXI Stream接口的信号可以直观地看到数据传输情况。4.2 性能优化技巧要提高DMA传输性能可以考虑以下几个方面增大突发长度AXI协议支持的最大突发长度是256尽量用满这个值。使用多通道DMA如果数据吞吐量要求很高可以考虑使用多个DMA通道并行传输。优化缓存使用在PS端合理使用缓存预取和刷新指令可以显著提高性能。比如在DMA传输前调用Xil_DCacheFlush()。提高时钟频率在PL端尽量提高AXI Stream接口的时钟频率但要确保时序收敛。使用数据打包如果数据位宽允许可以考虑将多个数据打包传输减少总线开销。在我的一个高速数据采集项目中通过综合应用这些优化技巧最终实现了稳定传输速度达到1.2GB/s接近理论带宽的80%。

更多文章