ZCU106开发板PYNQ实战:从零构建DMA回环测速系统
第一次拿到ZCU106开发板时,看着这块集成了Zynq UltraScale+ MPSoC的硬件平台,既兴奋又忐忑。作为嵌入式开发者,我们常需要处理PS(处理器系统)与PL(可编程逻辑)之间的数据交互,而DMA(直接内存访问)正是实现高效数据传输的利器。本文将带你完整实现一个DMA回环测速系统,从Vivado配置到PYNQ实测,手把手解决每个环节可能遇到的问题。
1. 硬件环境准备与Vivado工程搭建
在开始之前,确保你已经完成以下准备工作:
- 安装Vivado 2018.3或更高版本(本文以2018.3为例)
- ZCU106开发板及配套电源、线缆
- 已生成PYNQ镜像并成功启动(参考前序教程)
创建Vivado工程的关键步骤:
- 新建RTL工程,选择ZCU106开发板型号
- 添加ZYNQ UltraScale+ MPSoC IP核到Block Design
- 配置ZYNQ核时,务必在PS-PL Configuration中启用一个HP(High Performance)端口
注意:DMA传输需要通过HP端口访问DDR内存,这是后续数据传输的物理通道
2. DMA IP核配置与硬件设计
DMA IP核是构建PS与PL数据通道的核心组件。在Vivado的Block Design中添加AXI DMA IP后,需要进行以下关键设置:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| Enable Scatter Gather | 关闭 | 简单回环测试无需SG模式 |
| Width of Buffer Length Register | 23 | 决定最大传输长度 |
| Memory Map Data Width | 64 | 匹配HP端口位宽 |
| Stream Data Width | 32 | 常见数据位宽 |
连接示意图:
S_AXIS_S2MM → M_AXIS_MM2S (输入) (输出)这种回环连接方式可以直接测试DMA本身的传输性能,无需额外逻辑。
完成连接后,记得:
- 验证地址映射是否正确
- 生成HDL Wrapper
- 执行综合与实现
3. 生成PYNQ可用的Overlay文件
硬件设计完成后,需要准备三个关键文件供PYNQ使用:
.bit文件:包含FPGA配置比特流
- 路径:
<工程目录>/<项目名>.runs/impl_1/*.bit
- 路径:
.hwh文件:硬件描述文件
- 路径:
<工程目录>/<项目名>.srcs/sources_1/bd/<bd_name>/hw_handoff/*.hwh
- 路径:
.tcl文件:Block Design重建脚本
- 通过菜单"File → Export → Export Block Design"生成
将这三个文件复制到PYNQ板的/home/xilinx目录下备用。这里有个常见坑点:文件权限问题。建议使用chmod 644命令确保Jupyter有访问权限。
4. Jupyter Notebook中的DMA测速实现
打开PYNQ的Jupyter环境,新建Notebook,我们分步骤实现测速功能:
4.1 初始化Overlay与DMA
from pynq import Xlnk, Overlay import numpy as np import time # 加载Overlay ol = Overlay('/home/xilinx/zcu106_dma/dma.bit') dma = ol.axi_dma_0 # 获取DMA实例4.2 内存分配与数据准备
PYNQ通过Xlnk管理CMA(连续内存分配器)内存:
xlnk = Xlnk() # 分配25MB的输入输出缓冲区 input_buffer = xlnk.cma_array(shape=(6553600,), dtype=np.uint32) output_buffer = xlnk.cma_array(shape=(6553600,), dtype=np.uint32) # 填充测试数据(可选) for i in range(len(input_buffer)): input_buffer[i] = i % 2564.3 DMA传输与性能测试
核心测速代码实现:
def measure_dma_speed(data_size): # 重置缓冲区 output_buffer[:] = 0 # 执行传输并计时 start = time.time() dma.sendchannel.transfer(input_buffer[:data_size]) dma.recvchannel.transfer(output_buffer[:data_size]) dma.sendchannel.wait() dma.recvchannel.wait() end = time.time() # 计算速度 time_cost = end - start speed = (data_size * 4) / (time_cost * 1024 * 1024) # MB/s return time_cost, speed4.4 多规模测试与结果分析
建议测试不同数据量下的表现:
test_sizes = [64*1024, 1*1024*1024, 10*1024*1024, 25*1024*1024] # 64KB到25MB results = [] for size in test_sizes: time_cost, speed = measure_dma_speed(size//4) # 除以4因为uint32占4字节 results.append((size, time_cost, speed)) print(f"Size: {size/1024/1024:.1f}MB | Time: {time_cost:.3f}s | Speed: {speed:.2f}MB/s")典型结果可能如下:
| 数据量 | 耗时(秒) | 传输速率(MB/s) |
|---|---|---|
| 64KB | 0.0012 | 52.08 |
| 1MB | 0.0185 | 54.05 |
| 10MB | 0.182 | 54.95 |
| 25MB | 0.455 | 54.95 |
5. 常见问题排查与优化建议
在实际操作中,你可能会遇到以下情况:
问题1:DMA传输速度远低于预期(如<100MB/s)
- 检查HP端口是否已启用
- 确认Vivado中DMA配置的位宽设置
- 尝试增加传输数据量(小数据量测试开销占比高)
问题2:Jupyter报错"Unable to allocate memory"
- 检查PYNQ镜像版本(建议v2.6+)
- 尝试减小测试数据量
- 重启kernel释放内存
问题3:传输数据校验失败
- 使用np.array_equal(input_buffer, output_buffer)验证
- 检查DMA是否配置为回环模式
- 确认buffer数据类型一致
性能优化技巧:
- 增大DMA缓冲区长度寄存器位宽
- 尝试使用多个HP端口并行传输
- 考虑使用HLS优化数据流
6. 扩展应用:实际场景中的DMA使用
掌握了基础DMA操作后,可以尝试以下进阶应用:
- 图像处理加速:将摄像头数据通过DMA传输到PL进行实时处理
- 网络数据加速:实现TCP/IP协议栈与自定义硬件的快速数据交换
- 多DMA通道管理:同时控制多个数据传输流
一个实用的技巧是封装DMA操作为Python类:
class DMAController: def __init__(self, bitfile_path): self.ol = Overlay(bitfile_path) self.dma = self.ol.axi_dma_0 self.xlnk = Xlnk() def transfer(self, data): in_buf = self.xlnk.cma_array(shape=data.shape, dtype=data.dtype) out_buf = self.xlnk.cma_array(shape=data.shape, dtype=data.dtype) np.copyto(in_buf, data) start = time.time() self.dma.sendchannel.transfer(in_buf) self.dma.recvchannel.transfer(out_buf) self.dma.sendchannel.wait() self.dma.recvchannel.wait() return out_buf, time.time() - start在实际项目中,我发现DMA性能与内存访问模式密切相关。连续的大块传输通常能获得最佳性能,而随机小数据包会导致吞吐量显著下降。建议根据应用特点设计合适的数据缓冲策略。