ZCU106开发板PYNQ实战:手把手教你用DMA实现PS-PL高速数据回环(附完整代码)

张开发
2026/4/16 12:36:18 15 分钟阅读

分享文章

ZCU106开发板PYNQ实战:手把手教你用DMA实现PS-PL高速数据回环(附完整代码)
ZCU106开发板PYNQ实战手把手教你用DMA实现PS-PL高速数据回环附完整代码在嵌入式系统开发中处理单元(PS)和可编程逻辑(PL)之间的高效数据传输一直是开发者面临的挑战。ZCU106开发板结合PYNQ框架为我们提供了一条快速验证硬件加速方案的捷径。本文将带你从零开始一步步实现PS与PL之间的DMA高速数据回环并附上可直接运行的完整代码。1. 环境准备与基础概念在开始动手之前我们需要确保开发环境已经正确配置。ZCU106开发板需要预先刷好PYNQ镜像建议使用PYNQ v2.6或更高版本。Vivado方面2018.3版本是经过验证的稳定选择当然你也可以使用更新的版本但需要注意某些IP核的配置选项可能有所变化。**DMA直接内存访问**技术允许数据在外设和内存之间直接传输无需CPU介入。在Zynq MPSoC架构中DMA控制器可以高效地在PS和PL之间搬运数据这对于需要处理大量数据的应用场景尤为重要。需要准备的软件工具包括Vivado Design Suite含SDKPYNQ镜像文件Jupyter Notebook已包含在PYNQ镜像中硬件连接方面确保ZCU106开发板已正确供电USB转UART调试线已连接网线已连接用于PYNQ访问JTAG下载器已连接可选用于调试2. Vivado工程创建与DMA IP配置启动Vivado 2018.3创建一个新工程选择ZCU106开发板对应的器件型号xczu7ev-ffvc1156-2-e。工程创建完成后我们需要建立一个Block DesignBD这是Vivado中可视化设计硬件系统的核心界面。在BD中添加以下IP核ZYNQ UltraScale MPSoCAXI Direct Memory Access (AXI DMA)AXI SmartConnect可选用于简化连接关键配置步骤双击ZYNQ核进行配置在PS-PL Configuration中确保至少启用一个HP端口如HP0关闭不需要的外设以简化设计DMA IP核配置Basic选项卡 - 取消勾选Enable Scatter Gather - 数据宽度保持默认32位 - 内存映射数据宽度选择64位 MM2S选项卡 - 数据流宽度32 - 最大突发长度256 S2MM选项卡 - 配置与MM2S对称连接方案将DMA的M_AXI_MM2S和S_AXI_S2MM连接到ZYNQ的HP端口将DMA的AXI_MM2S和AXI_S2MM直接相连形成回环确保所有时钟和复位信号正确连接提示如果使用AXI SmartConnect可以自动处理部分连接逻辑但需要确保地址分配合理。完成连接后验证设计Validate Design确保没有未连接的接口。然后生成HDL Wrapper接着生成比特流。这个过程可能需要10-30分钟取决于你的电脑性能。3. 导出硬件文件与PYNQ准备比特流生成完成后我们需要导出三个关键文件到PYNQ环境.bit文件包含PL的配置比特流.hwh文件硬件描述文件bd.tcl文件Block Design的脚本描述在Vivado中这些文件可以通过以下步骤导出File - Export - Export Hardware 勾选Include bitstream和Local to project将导出的文件通过SCP或共享文件夹复制到PYNQ板的/home/xilinx/jupyter_notebooks目录下。建议创建一个专用文件夹存放DMA项目文件例如dma_loopback。在PYNQ端我们需要检查一些前置条件# 检查CMA内存分配 cat /proc/meminfo | grep Cma # 检查DMA通道 ls /sys/class/dma确保系统有足够的连续内存可供DMA操作。PYNQ默认已经配置了足够大的CMA池但如果需要调整可以修改内核启动参数。4. Jupyter Notebook代码实现现在我们可以开始编写Python代码来实现DMA数据传输测试了。在Jupyter Notebook中新建一个Python3笔记本以下是完整的实现代码import numpy as np from pynq import Xlnk, Overlay import time import matplotlib.pyplot as plt # 加载Overlay ol Overlay(/home/xilinx/jupyter_notebooks/dma_loopback/dma.bit) dma ol.axi_dma_0 # 初始化内存分配器 xlnk Xlnk() # 测试参数配置 BUFFER_SIZE 1024 * 1024 # 1MB DATA_TYPE np.uint32 # 创建输入输出缓冲区 input_buffer xlnk.cma_array(shape(BUFFER_SIZE//4,), dtypeDATA_TYPE) output_buffer xlnk.cma_array(shape(BUFFER_SIZE//4,), dtypeDATA_TYPE) # 填充输入缓冲区 print(填充输入缓冲区...) for i in range(len(input_buffer)): input_buffer[i] i % 256 # DMA传输函数 def dma_transfer(size): # 调整缓冲区大小 global input_buffer, output_buffer elements size // 4 input_buffer xlnk.cma_array(shape(elements,), dtypeDATA_TYPE) output_buffer xlnk.cma_array(shape(elements,), dtypeDATA_TYPE) # 填充数据 for i in range(elements): input_buffer[i] i % 256 # 执行DMA传输 start time.time() dma.sendchannel.transfer(input_buffer) dma.recvchannel.transfer(output_buffer) dma.sendchannel.wait() dma.recvchannel.wait() end time.time() # 计算传输速度 time_cost end - start speed size / (time_cost * 1024 * 1024) # MB/s return time_cost, speed # 测试不同数据大小的传输速度 sizes [64, 256, 1024, 4096, 16384] # KB times [] speeds [] print(开始DMA传输测试...) for size in sizes: bytes_size size * 1024 cost, speed dma_transfer(bytes_size) times.append(cost) speeds.append(speed) print(f传输 {size}KB 数据: 耗时 {cost:.5f}s, 速度 {speed:.2f}MB/s) # 绘制速度曲线 plt.figure(figsize(10,5)) plt.plot(sizes, speeds, bo-) plt.title(DMA传输速度随数据大小变化) plt.xlabel(数据大小 (KB)) plt.ylabel(传输速度 (MB/s)) plt.grid(True) plt.show() # 数据校验 print(\n执行最终25MB传输验证...) final_size 25 * 1024 * 1024 dma_transfer(final_size) # 简单的数据校验 errors 0 for i in range(0, min(1000, len(input_buffer))): # 只检查前1000个样本 if output_buffer[i] ! input_buffer[i]: errors 1 print(f数据校验完成发现 {errors} 处不一致) # 释放内存 del input_buffer del output_buffer xlnk.xlnk_reset()这段代码实现了以下功能加载我们生成的硬件比特流分配连续内存缓冲区实现不同数据大小的DMA传输测试测量并绘制传输速度曲线执行基本的数据校验5. 性能优化与问题排查在实际测试中你可能会遇到各种性能问题和错误。以下是常见问题及其解决方案常见问题1DMA传输速度低于预期检查ZYNQ核的HP端口配置确保使用了最高性能的接口确认DMA IP核配置中的最大突发长度设置为256尝试调整缓冲区大小较大的缓冲区通常能获得更好的性能常见问题2内存分配失败XlnkError: Failed to allocate memory解决方案检查CMA内存大小cat /proc/meminfo | grep Cma释放不必要的内存xlnk.xlnk_reset()减少测试缓冲区大小性能优化技巧优化方向具体方法预期效果缓冲区大小使用2MB以上的缓冲区提高20-30%速度内存对齐确保缓冲区64字节对齐提高10-15%速度DMA配置启用数据实时校验降低5%速度但提高可靠性时钟频率提高PL时钟频率线性提升速度高级优化代码示例# 使用对齐的内存分配 aligned_buffer xlnk.cma_array(shape(size,), dtypenp.uint32, cacheableFalse, alignment64) # 批量数据传输 def bulk_transfer(data_chunks): for chunk in data_chunks: dma.sendchannel.transfer(chunk) dma.sendchannel.wait()6. 实际应用扩展掌握了基本的DMA回环测试后我们可以将这些知识应用到更复杂的场景中应用场景1图像处理加速# 加载图像到DMA缓冲区 def load_image_to_buffer(image_path): img Image.open(image_path).convert(L) img_data np.array(img, dtypenp.uint32) img_buffer xlnk.cma_array(shapeimg_data.shape, dtypenp.uint32) np.copyto(img_buffer, img_data) return img_buffer应用场景2实时数据流处理class DMAController: def __init__(self, overlay_path): self.ol Overlay(overlay_path) self.dma self.ol.axi_dma_0 self.xlnk Xlnk() def create_ring_buffer(self, num_buffers, size): self.buffers [self.xlnk.cma_array(shape(size,), dtypenp.uint32) for _ in range(num_buffers)] self.current_idx 0 def get_next_buffer(self): buf self.buffers[self.current_idx] self.current_idx (self.current_idx 1) % len(self.buffers) return bufDMA与自定义IP核协同工作流程在Vivado中创建自定义IP核通过AXI Stream接口连接DMA和自定义IP在PYNQ中同时控制DMA和IP核的寄存器实现端到端的数据处理流水线7. 高级调试技巧当系统不能正常工作时系统的调试能力就显得尤为重要。以下是一些实用的调试方法Vivado硬件调试在Vivado中设置ILA集成逻辑分析仪核抓取AXI总线信号检查DMA状态寄存器PYNQ软件调试# 读取DMA寄存器状态 def read_dma_status(dma): print(fMM2S状态: {hex(dma.read(0x00))}) print(fS2MM状态: {hex(dma.read(0x30))}) print(fMM2S控制: {hex(dma.read(0x04))}) print(fS2MM控制: {hex(dma.read(0x34))}) # 调用函数 read_dma_status(dma)常见错误代码解析错误代码含义解决方案0x00010000DMA引擎暂停检查数据传输是否完成0x00020000DMA引擎停止复位DMA控制器0x00080000错误中断检查地址对齐和长度系统级调试步骤确认比特流正确加载检查DMA IP核是否出现在ol.ip_dict中验证内存分配是否成功逐步执行传输代码检查每一步的返回值

更多文章