AXI_DMA回环测试

简介: 本文详解ZYNQ(xc7z020)+ Petalinux 2018.3下AXI DMA高速回环实现:从Vivado硬件搭建(DMA+FIFO、双时钟域配置)、设备树自动生成、内核驱动适配,到Linux DMA Engine API测试驱动开发,并总结4个典型坑(通道申请失败、16384字节丢1字节、大包超时、terminate_all误用)及根因解决。

ZYNQ AXI DMA 高速数据传输实战:从 Vivado 硬件搭建到 Linux 回环测试(含 4 个踩坑)

适用场景:ZYNQ(xc7z020)+ Petalinux 2018.3 开发,想在 PS 和 PL 之间实现高速数据传输。本文从 Vivado 硬件搭建开始,完整记录 AXI DMA + AXI Stream Data FIFO 回环实验的全过程,包括设备树配置、内核驱动、Linux DMA Engine API 测试驱动,以及 4 个真实踩坑的排查与解决。

前言

在 ZYNQ 开发中,PS 和 PL 之间的数据传输是核心课题。GPIO、UART 这些低速接口显然不够用,AXI DMA 才是高速数据搬运的标配。

最近我在 AC880 开发板上做了 AXI DMA 回环实验,从 Vivado 硬件搭建到 Linux 驱动测试完整走通,256 字节到 1MB 数据全部传输正确。过程中踩了不少坑,特别是 dma_request_channel 拿不到通道、16384 字节差 1 字节、大传输超时这几个问题,折腾了挺久。

本文把完整流程和踩坑记录分享出来,希望能帮到正在做 AXI DMA 的朋友。

环境信息:

|
项
|
值

|
| --- | --- |
|
开发板
|
AC880 ZYNQ(xc7z020clg484-2)
|
|
Vivado 版本
|
2018.3
|
|
Petalinux 版本
|
2018.3
|
|
内核版本
|
4.14-xilinx-v2018.3
|
|
AXI DMA 模式
|
Simple(Scatter Gather 关闭)
|

一、AXI DMA 硬件架构速览

AXI DMA 有两个数据通道:

  • MM2S(Memory Map to Stream):从 DDR 读数据 → 输出 AXI Stream

  • S2MM(Stream to Memory Map):接收 AXI Stream → 写入 DDR

回环实验的数据通路:

DDR(tx_buf) ──MM2S──> AXI Stream ──FIFO──> AXI Stream ──S2MM──> DDR(rx_buf)

中间用一个 AXI Stream Data FIFO 把 MM2S 的输出连到 S2MM 的输入,形成回环。

二、Vivado 硬件搭建

2.1 添加 IP

在 Block Design 中添加:

  • AXI DMA:高速数据搬运

  • AXI Stream Data FIFO:回环缓冲

2.2 AXI DMA 配置

|
参数
|
值
|
说明

|
| --- | --- | --- |
|
Scatter Gather Engine
|
关闭
|
Simple 模式
|
|
Width of Buffer Length Register
|
14
|
决定单次最大传输 = 2^14 - 1 = 16383 字节
|
|
Memory Map Data Width
|
32
| |
|
Stream Data Width
|
32
| |
|
启用通道
|
MM2S + S2MM
| |

2.3 AXI Stream Data FIFO 配置

|
参数
|
值
|
说明

|
| --- | --- | --- |
|
TDATA Width (bytes)
|
4
|
32 位,必须和 DMA Stream 宽度一致
|
|
FIFO Depth
|
默认
|
经测试 4096 字节以内传输稳定
|

2.4 时钟域配置(关键!)

AXI DMA 的控制口和数据口必须用不同时钟域:

|
信号
|
连接到
|
时钟

|
| --- | --- | --- |
|
s_axi_lite_aclk(控制)
|
FCLK_CLK0
|
100 MHz
|
|
m_axi_mm2s_aclk(数据读)
|
FCLK_CLK1
|
200 MHz
|
|
m_axi_s2mm_aclk(数据写)
|
FCLK_CLK1
|
200 MHz
|
|
s_axis_aclk(FIFO)
|
FCLK_CLK1
|
200 MHz
|

控制口走 GP0(100MHz),数据口走 HP0(200MHz)。如果时钟域搞错,Vivado 会报一堆时钟不匹配错误。

2.5 HP 端口共享

如果 HP0 已经被 VDMA 占用,不需要启用 HP1,复用现有 axi_smc(AXI SmartConnect)即可:

  • S00: axi_vdma_0

  • S01: axi_dma_0 MM2S

  • S02: axi_dma_0 S2MM

配置为 3 Slave → 1 Master → S_AXI_HP0。

2.6 地址分配

|
接口
|
地址范围
|
大小

|
| --- | --- | --- |
|
S_AXI_LITE(控制)
|
0x4040_0000 - 0x4040_FFFF
|
64K
|
|
Data_MM2S(HP0)
|
0x0000_0000 - 0x3FFF_FFFF
|
1G DDR
|
|
Data_S2MM(HP0)
|
0x0000_0000 - 0x3FFF_FFFF
|
1G DDR
|

地址无冲突,无需手动分配。

2.7 时序分析

生成 bitstream 后时序报告显示 WNS = -0.174 ns(174ps 违例),仅 1/33712 端点失败。

这种程度的违例对功能无影响:

  • 174ps 是皮秒级,远小于 1ns

  • 是 AXI DMA IP 内部路径,无法修改 RTL

  • FCLK_CLK1 = 200MHz 不能降频

  • 实际芯片性能通常比 STA 悲观估计好

直接 Generate Bitstream 即可。

2.8 导出 HDF

File → Export → Export Hardware,勾选 Include bitstream,导出到 ~/ZYNQ/zynq_linux/hdf/。

三、Petalinux 配置

3.1 导入 HDF

cd ~/ZYNQ/zynq_linux
petalinux-config --get-hw-description=./hdf/
# menuconfig 直接 Save → Exit

3.2 内核配置

petalinux-config -c kernel
Device Drivers  --->
  [*] DMA Engine support  --->
        <*> Xilinx DMA Configuration Engine    ← CONFIG_XILAXIDMA

3.3 设备树(重要踩坑!)

HDF 导入后,Petalinux 会自动在 pl.dtsi 中生成 axi_dma_0 节点:

axi_dma_0: dma@40400000 {
   
    #dma-cells = <1>;
    clock-names = "s_axi_lite_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
    clocks = <&clkc 15>, <&clkc 16>, <&clkc 16>;
    compatible = "xlnx,axi-dma-7.1", "xlnx,axi-dma-1.00.a";
    interrupt-names = "mm2s_introut", "s2mm_introut";
    interrupt-parent = <&intc>;
    interrupts = <0 32 4 0 33 4>;
    reg = <0x40400000 0x10000>;
    ...
};

⚠️ 千万不要在 system-user.dtsi 里手动覆盖 axi_dma_0 节点!

我一开始手动写了一段,结果编译报错 Reference to non-existent node or label "gic"。原因是 ZYNQ 7000 的 GIC 标签是 intc 不是 gic,而且中断号我猜错了(写成 59/60,实际是 32/33)。

自动生成的节点完全正确,手动覆盖只会引入错误。

3.4 编译 + 打包

petalinux-build

petalinux-package --boot --fsbl images/linux/zynq_fsbl.elf \
  --fpga images/linux/system.bit \
  --u-boot images/linux/u-boot.elf \
  --output images/linux/BOOT.BIN --force

四、部署到 SD 卡(U-Boot + TFTP)

用 TFTP 更新 system.dtb、system.bit、zImage,BOOT.BIN 建议用读卡器直接拷贝(风险最高):

# U-Boot 命令行
tftpboot 0x2000000 system.dtb  && fatwrite mmc 0:1 0x2000000 system.dtb  ${filesize}
tftpboot 0x3000000 system.bit  && fatwrite mmc 0:1 0x3000000 system.bit  ${filesize}
tftpboot 0x2080000 zImage      && fatwrite mmc 0:1 0x2080000 zImage      ${filesize}

五、板端验证

# 1. DMA 通道设备节点
ls /sys/class/dma/
# 期望: dma1chan0 (MM2S), dma1chan1 (S2MM)

# 2. 驱动加载日志
dmesg | grep -i "xilinx\|dma"
# 期望: xilinx-vdma 40400000.dma: Xilinx AXI DMA Engine Driver Probed!!

# 3. 中断注册
cat /proc/interrupts | grep xilinx-dma
# 期望: mm2s(IRQ64), s2mm(IRQ65)

六、回环测试驱动(DMA Engine API)

6.1 核心 API 流程

// 1. 申请通道 (DMA_SLAVE mask, Xilinx DMA 只注册此能力)
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
tx_chan = dma_request_channel(mask, xilinx_dma_filter, NULL);  // MM2S
rx_chan = dma_request_channel(mask, xilinx_dma_filter, NULL);  // S2MM

// 2. 分配一致性内存
tx_buf = dma_alloc_coherent(dev, size, &tx_dma, GFP_KERNEL);
rx_buf = dma_alloc_coherent(dev, size, &rx_dma, GFP_KERNEL);

// 3. 先启动 S2MM (接收), 再启动 MM2S (发送)
rx_desc = dmaengine_prep_slave_single(rx_chan, rx_dma, len, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
rx_desc->callback = dma_rx_callback;
dmaengine_submit(rx_desc);
dma_async_issue_pending(rx_chan);

udelay(10);  // 确保 S2MM 就绪

tx_desc = dmaengine_prep_slave_single(tx_chan, tx_dma, len, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);
tx_desc->callback = dma_tx_callback;
dmaengine_submit(tx_desc);
dma_async_issue_pending(tx_chan);

// 4. 等待完成
wait_for_completion_timeout(&tx_done, 5*HZ);
wait_for_completion_timeout(&rx_done, 5*HZ);

// 5. 数据校验
memcmp(tx_buf, rx_buf, size);

6.2 通道过滤函数

static bool xilinx_dma_filter(struct dma_chan *chan, void *param)
{
   
    struct device *dev = chan->device->dev;
    const char *name;
    bool match = false;

    if (!dev) return false;
    name = dev_name(dev);

    // 策略 1: compatible 匹配
    if (dev->of_node && of_device_is_compatible(dev->of_node, "xlnx,axi-dma-1.00.a"))
        match = true;

    // 策略 2: 设备名含基地址 40400000
    if (name && strstr(name, "40400000"))
        match = true;

    return match;
}

6.3 大传输拆分(sg-length-width 限制)

设备树中 xlnx,sg-length-width = <0xe>(14 位),单次传输最大 2^14 - 1 = 16383 字节。超过必须拆分:

#define AXI_DMA_MAX_XFER  4096  // 不用 16383, 用 4096 避免 FIFO 溢出

for (offset = 0; offset < size; offset += chunk_len) {
   
    remaining = size - offset;
    chunk_len = (remaining > AXI_DMA_MAX_XFER) ? AXI_DMA_MAX_XFER : remaining;
    dma_xfer_chunk(tx_dma + offset, rx_dma + offset, chunk_len);
}

七、踩坑记录(4 个坑)

坑 1:failed to request MM2S channel

现象:dma_request_channel(DMA_MEM_TO_DEV) 返回 NULL。

原因:Xilinx AXI DMA 驱动只注册 DMA_SLAVE 能力,不注册 DMA_MEM_TO_DEV / DMA_DEV_TO_MEM。

解决:改用 DMA_SLAVE mask 申请通道。AXI DMA 通道顺序固定:chan0=MM2S,chan1=S2MM。

坑 2:16384 字节差 1 字节

现象:传输 16384 字节,最后 1 字节(index 16383)丢失,tx=0xFF,rx=0x00。

原因:sg-length-width=14 限制单次传输最大 16383 字节,驱动截断了最后 1 字节。

解决:拆分传输,每块 ≤ 16383 字节。

坑 3:65536+ 字节超时 + Channel has errors 10

现象:chunk 大小用 16383 时,大传输间歇性超时,DMA 通道报错误。

原因:AXI Stream Data FIFO 深度有限,16383 字节 chunk 可能导致 FIFO 溢出(MM2S 写入快于 S2MM 读取,共享 HP0 端口竞争)。

解决:chunk 大小改为 4096 字节(远小于 FIFO 深度,稳定可靠)。

坑 4:chunk 开头 terminate_all 导致 4096 也超时

现象:在每个 chunk 传输前调用 dmaengine_terminate_all() 重置通道,结果 4096 字节也超时。

原因:terminate_all 重置通道后,立即 prep_slave_single + submit 会失败,通道未就绪。

解决:去掉 chunk 开头的 terminate_all,只在出错时才调用清理。

八、测试结果

|
缓冲区大小
|
结果

|
| --- | --- |
|
256 字节
|
✅ PASSED
|
|
1024 字节
|
✅ PASSED
|
|
4096 字节
|
✅ PASSED
|
|
8192 字节
|
✅ PASSED
|
|
16384 字节
|
✅ PASSED(拆分为 4×4096)
|
|
65536 字节
|
✅ PASSED(拆分为 16×4096)
|
|
1,048,576 字节 (1MB)
|
✅ PASSED(拆分为 256×4096)
|
|
4096 字节 × 10 次连续
|
✅ 全部 PASSED
|

九、总结

AXI DMA 回环实验的核心要点:

  1. 时钟域分离:控制口 100MHz,数据口 200MHz,不能搞错

  2. 设备树不手动覆盖:pl.dtsi 自动生成的 axi_dma_0 节点完全正确

  3. 用 DMA_SLAVE 申请通道:Xilinx DMA 驱动只注册此能力

  4. 大传输拆分成 4096 字节 chunk:同时规避 sg-length-width 限制和 FIFO 溢出

  5. 不要在 chunk 开头 terminate_all:会导致通道未就绪

完整驱动源码在 apps/axi_dma_test/ 目录下,可直接编译使用。

相关文章
|
4天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
5746 8
|
2天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
1002 2
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
16天前
|
人工智能 自然语言处理 安全
阿里云千问办公 QwenWork详细介绍:产品核心能力、典型场景、价格及常见问题解答
千问办公是阿里云推出的一站式AI办公平台,主打"不止于对话,更注重交付",依托通义千问旗舰大模型,用户一句话即可完成数据分析、PPT生成、视频剪辑等复杂任务,直接输出可用成果。产品深度打通钉钉生态与企业OA,覆盖桌面端、网页端,提供企业标准版198元/人/月等多档订阅方案,新用户注册即赠2000积分,适配工程师、HR、财务等多职业办公场景,成为能动手干活的"全能AI同事"。
3226 9
|
3天前
|
人工智能 并行计算 PyTorch
秋叶 ComfyUI 2026 整合包 v3.2 完整部署教程:Python 3.13 + Torch 2.13 全栈升级
秋叶aaaki ComfyUI 2026年8月整合包v3.2正式发布!全面升级Python 3.13.11、PyTorch 2.13.0+cu130及ComfyUI v0.30.2,原生支持MiniMax H3、Wan 2.2、Qwen-Image-2.1等2026主流音视频/图像模型,解压即用,无需环境配置。
419 2
|
15天前
|
IDE 开发工具
Qoder 上线 Sonus 模型,Computer Use 能力全面增强
Qoder国际版上线全新内置大模型Sonus(/ˈsoʊnəs/),全球领先,专精超长任务执行与电脑操作(Computer Use)。配合Qoder桌面端0.2.3版本,可自主完成编程、金融建模、科研及表格制作等复杂工作。现全面支持Qoder全系产品,效率提升3.2倍。
1799 8
Qoder 上线 Sonus 模型,Computer Use 能力全面增强
|
11天前
|
缓存 IDE Java
【保姆级】Android Studio下载、安装和汉化教程(2026最新)
Android Studio 是 Google 官方推出的免费 Android 应用开发集成环境,基于 IntelliJ IDEA,内置模拟器、调试器、性能分析及 Compose 界面工具,功能全面,文档丰富,是安卓开发首选工具。(239字)
1185 1

热门文章

最新文章