Zynq AXI DMA 环回测试调试指南:从 Cache 一致性到 Vitis 同步机制
在 Zynq SoC 的开发中,AXI DMA 是实现高速数据传输(如雷达、超声波成像系统数据采集)的核心部件。在实际开发中,开发者往往会遇到软硬件协同上的诸多细节问题。
本文基于一次真实的 AXI-Stream Data FIFO 环回测试(Loopback Test)调试过程,梳理了从底层硬件连线、处理器 Cache 一致性到 Python 上位机通信的常见排错思路。本文旨在为遇到数据校验失败、程序死锁(Timeout)或上位机无响应的开发者提供参考。
一、 硬件架构与基础概念
在进行 DMA 调试前,需明确以下核心概念:
- 数据流向 (MM2S 与 S2MM) :
MM2S(Memory-Map to Stream):发送通道,DMA 从 DDR 内存读取数据,转换为 AXI-Stream 流数据发送给 PL 端(如 FIFO)。S2MM(Stream to Memory-Map):接收通道,DMA 接收来自 PL 端的流数据,并将其写回 DDR 内存。
- 中断聚合 (Concat) :
Zynq 的 PS 侧仅提供有限的中断接口(如IRQ_F2P)接入中断控制器。当需要同时处理发送端和接收端的中断时,必须在 Vivado 中使用ConcatIP 核将两路中断信号拼接为一路总线。
二、 核心调试记录与底层分析
在本次环回测试中,主要遇到了两个导致系统异常的核心问题:
问题 1:数据校验失败(接收区全为 0)------ Cache 一致性问题
现象 :DMA 状态寄存器提示传输已完成,但 CPU 对比发送区和接收区数据时,发现所有数据比对失败,且从接收区读取到的值全部为 0。
原理 :这是 ARM 处理器典型的 Cache 一致性问题。在发送数据前,我们通常会调用 Xil_DCacheFlushRange() 将 Cache 中的数据刷入物理 DDR。但在接收端,DMA 是直接将数据写入 DDR 物理内存的,此过程绕过了 CPU 的 Cache。如果 CPU 在比对数据前未刷新 Cache,它将直接读取自身未更新的 Cache 行(即上电初始化的全 0 状态),从而导致校验失败。
解法 :在确认 DMA 接收完成之后,CPU 读取数据之前,必须强制失效接收区的 Cache。
c
// 强制 CPU 失效指定范围的 Cache,使其从物理 DDR 读取最新数据
Xil_DCacheInvalidateRange((UINTPTR)RxBufferPtr, TRANS_LENGTH * 2);
问题 2:底层死锁(TxDone: 1, RxDone: 0)------ Vivado 连线遗漏与 Vitis 同步异常
现象 :程序卡死在 while 循环中。通过读取寄存器发现,发送端状态为 0x1002(Idle,表示发送顺利完成),而接收端状态为 0x00000000(未完成,未报错,处于死等状态)。
原理:
- 硬件连线遗漏 :在 Vivado 的 Block Design 中,连接 Zynq
S_AXI_HP0端口的 AXI Interconnect(互联矩阵)配置错误,或者 DMA 接收端(M_AXI_S2MM)的 AXI 写通道未正确连接到互联矩阵的 Slave 接口,导致接收端的写请求发送到了悬空的总线上,无法收到写入完成响应(BRESP),从而陷入死锁。 - Vitis 工程未同步 :在 Vivado 中修复了上述连线遗漏并重新导出
.xsa硬件描述文件后,Vitis 并没有真正应用新的硬件配置。Vitis 的 Workspace 存在特定的缓存机制,直接点击运行有时会导致实际烧录进 FPGA 的依然是带有连线错误的旧版 Bitstream。
解法 :
一旦在 Vivado 中对底层硬件逻辑进行了修改(如补全连线、修改地址映射),为确保软硬件绝对同步,建议采取以下操作: - 在 Vitis 中直接删除 (Delete) 现有的 Platform 工程。
- 使用最新导出的
.xsa文件,重新创建一个 Platform 工程。 - 重新编译应用程序工程。
- 确保在 Run Configurations 中勾选了 Program FPGA,或通过菜单栏
Xilinx -> Program FPGA手动执行物理烧录。
三、 上位机通信的常见隐患(Python Pyserial)
在通过 Python 脚本实现对 Zynq 开发板的串口控制与监控时,若出现上位机接收数据全为 0 或无响应的情况,通常由以下三个因素引起:
-
串口资源被占用 :
串口属于独占性资源。在运行 Python 脚本前,必须确保 Vitis 自带的 Serial Terminal 或其他串口调试助手已断开连接,否则 Python 脚本将无法读取到任何数据。
-
DTR/RTS 引脚触发硬件复位 :
多数 Zynq 开发板的 USB-UART 芯片底层的 DTR/RTS 引脚在物理电路上连接到了 FPGA 的复位(Reset)引脚。Python 的
pyserial库在初始化serial.Serial()时,默认会改变这两个引脚的电平状态。这会导致 Python 脚本刚建立连接时,开发板发生硬件复位,中断当前运行的 C 程序。
解决方式:在 Python 脚本中显式禁用这两个引脚的状态更改。pythonser = serial.Serial() ser.port = 'COM3' ser.baudrate = 115200 ser.dtr = False # 禁用 DTR,防止触发硬件复位 ser.rts = False # 禁用 RTS,防止触发硬件复位 ser.open() -
标准库函数的行缓冲机制 :
在 C 语言程序中,若使用
getchar()等标准输入函数接收上位机指令,需注意其默认的行缓冲(Line Buffering)特性。如果 Python 脚本仅发送单字符(如b's'),C 语言端可能会一直阻塞等待换行符。因此,建议在 Python 发送指令时附加回车换行符(如ser.write(b's\r\n'))。
四、 总结与开发建议
Zynq 软硬协同开发的复杂度往往体现在系统边界的交互上。在此类高速数据传输实验中,建议遵循以下排错思路:
- 排除软件干扰:遇到数据异常时,优先排查 Cache 一致性操作是否遗漏。
- 读取底层状态:利用 DMA 的状态寄存器(Status Register)排查死锁位置,明确是发送端异常还是接收端异常。
- 确认软硬同步:如果硬件配置已在 Vivado 中确认无误但现象依旧,不要忽略 Vitis 工具链的工作空间同步问题。必要时重建 Platform 工程,确保当前运行的软件逻辑基于最新且正确的底层硬件架构。