江苏省网站建设_网站建设公司_网站备案_seo优化
2026/1/2 6:20:04 网站建设 项目流程

AXI DMA实战全解:从零理解FPGA高速数据搬运核心

你有没有遇到过这样的场景?
摄像头刚接上,画面就开始掉帧;ADC采样频率一提上去,CPU直接飙到100%;明明硬件带宽足够,数据就是“卡”在中间传不过去。

问题出在哪?不是算法太慢,也不是逻辑写错了——而是数据搬运方式不对

在高性能嵌入式系统中,CPU亲自搬数据的时代早已过去。真正高效的方案是:让硬件自动完成传输,CPU只管发号施令和处理结果。这就是AXI DMA的使命。

特别是在 Xilinx Zynq、Zynq UltraScale+ MPSoC 这类异构芯片里,AXI DMA 是连接 ARM 处理器(PS)与 FPGA 可编程逻辑(PL)之间的“高速公路收费站”。它不生产数据,但它决定数据能不能高效通行。

今天我们就来彻底讲清楚:
- AXI DMA 到底是什么?
- 它怎么工作?为什么能提升性能?
- 实际项目中该怎么用?有哪些坑要避开?


什么是 AXI DMA?别被名字吓住

先拆开看名字:A-X-I-D-M-A

  • AXI:Advanced eXtensible Interface,ARM 提出的总线协议标准,在 Zynq 中用于 PS 和 PL 之间的通信。
  • DMA:Direct Memory Access,直接内存访问,意思是外设可以直接读写内存,不用 CPU 插手。

合起来就是:一个基于 AXI 协议、实现直接内存访问的 IP 核。它的核心任务就两个:

  1. 把 PL 里的流数据存进 DDR 内存(S2MM)
  2. 把内存里的数据拿出来送给 PL 做处理(MM2S)

你可以把它想象成一个“快递中转站”:
- MM2S 是从仓库(DDR)往外发货(给 PL)
- S2MM 是把货从外面收进来入库(到 DDR)

而且这两个通道可以同时运行,互不干扰,支持全双工操作。


为什么非要用 AXI DMA?普通读写不行吗?

我们来做个对比。

假设你要传输一帧 1920x1080 的 RGB 图像,大约 6MB 数据。

方法一:CPU 轮询搬运

for (int i = 0; i < 6_000_000; i++) { data = read_from_pl(); ddr_buffer[i] = data; }

这期间 CPU 被完全占用,啥也不能干。延迟高、效率低,实时性差得离谱。

方法二:中断驱动

每次收到几个字节就触发一次中断。看似聪明,实则更糟——每秒几百万次中断下来,CPU 光响应中断都忙不过来。

方法三:AXI DMA 出场

CPU 只需告诉 DMA:“去把这 6MB 数据从地址 A 搬到 B”,然后就可以去做别的事了。
DMA 自己启动、搬运、完成后发个中断通知:“老板,活干完了。”

整个过程 CPU 参与时间不到 1%,其余时间都可以跑 OpenCV 算法或者网络传输。

这才是现代嵌入式系统的正确打开方式。


AXI DMA 怎么工作的?三个阶段讲明白

AXI DMA 的运行流程非常清晰,分为三步:配置 → 启动 → 完成。

第一步:配置(CPU 上场)

通过 AXI Lite 接口设置关键参数:
- 源地址 / 目标地址
- 传输长度(最多支持约 64MB 单次传输)
- 是否启用中断
- 是否开启循环模式(适合视频流连续采集)

这些信息写入 DMA 的控制寄存器,相当于下达任务清单。

第二步:启动(DMA 接手)

向控制寄存器写启动命令,DMA 开始干活。

比如 S2MM 方向:
- PL 端开始发送 AXI4-Stream 数据流
- DMA 接收数据,并根据配置好的目标地址,打包写入 DDR
- 收到TLAST信号表示一包结束,DMA 更新状态

MM2S 方向则是反过来:从内存读出数据,转成 Stream 发给 PL。

第三步:完成(中断回调)

传输结束后,DMA 设置完成标志位,并可触发中断。
CPU 在中断服务程序中检查状态,确认无误后启动后续处理,比如图像显示或编码上传。

全程除了开头配置和结尾响应,CPU 零参与。


关键特性解析:不只是简单的搬运工

很多人以为 AXI DMA 就是个“搬砖”的,其实它很聪明。

✅ 双通道独立运行

通道方向应用场景
MM2S内存 → 流图像输出、波形生成
S2MM流 ← 内存视频采集、传感器输入

两个通道各自有独立的控制逻辑、地址寄存器和中断线,完全可以并行工作。

✅ 支持 Scatter-Gather(分散-聚集)模式

传统 DMA 只能搬连续的一块内存。但现实应用中,数据往往是分段存储的,比如网络包、多帧缓存。

Scatter-Gather 模式允许你定义一个描述符链表,每个条目包含:
- 物理地址
- 数据长度
- 控制标志(如是否中断)

DMA 会按顺序自动执行所有描述符,无需 CPU 干预。这对大数据量、非连续存储场景特别有用。

⚠️ 注意:开启 Scatter-Gather 会增加资源消耗(需要 BRAM 存放描述符),且驱动复杂度上升,建议仅在必要时使用。

✅ 实现零拷贝传输

在 Linux 系统中,用户空间申请的内存通常是虚拟地址,物理上不连续,不适合 DMA 直接访问。

解决方案是使用 CMA(Contiguous Memory Allocator)区域分配连续物理内存,再通过 UIO 或dmaengine驱动映射到用户空间。

这样 PL → DDR → 用户程序的数据路径全程无需复制,真正做到“零拷贝”。

✅ Cache 一致性不能忽视

Zynq 的 ARM 核有 L1/L2 缓存。当 DMA 把新数据写入 DDR 后,CPU 如果直接从 Cache 读取,拿到的是旧数据!

常见解决办法:
- 手动刷新 Cache:调用Xil_DCacheInvalidateRange(addr, len)
- 使用 write-combine 映射:禁用写缓存,确保总是读 DDR 最新值
- 在驱动层使用__dma_map_area()类接口统一管理

这一点在裸机开发中容易忽略,但在实际项目中至关重要。


AXI4 vs AXI4-Stream:搞懂协议差异才能用好 DMA

AXI DMA 的本质是一个协议转换器,它打通了两种不同的 AXI 协议。

AXI4(Memory-Mapped)——有地址的空间

  • 用于访问 DDR、寄存器等具有固定地址的资源
  • 支持突发传输(Burst),提高带宽利用率
  • 读写通道分离,支持高并发
  • 连接到 PS 端的 HP(High Performance)端口

在 AXI DMA 中,它负责与 DDR 打交道。

AXI4-Stream —— 无地址的流水线

  • 不关心地址,只传递数据流
  • 核心信号:
  • TVALID/TREADY:握手机制,保证双方同步
  • TDATA:数据本身
  • TLAST:标记一个数据包结束
  • TKEEP:指示哪些字节有效(用于部分传输)

FPGA 内部很多模块都用这个接口:FFT、VDMA、以太网 MAC、摄像头接收器……

所以 AXI DMA 天然适合作为它们与系统内存之间的桥梁。


典型应用场景:图像采集系统实战

来看一个最常见的例子:FPGA 图像采集系统。

[摄像头] ↓ (MIPI/Parallel) [FPGA 解码 + 时序控制] ↓ (AXI4-Stream) [AXI DMA (S2MM)] —→ [DDR 内存] ↑ [中断] ↑ [ARM CPU] ↑ [OpenCV 处理 / 显示 / 网络推流]

工作流程详解

  1. 预分配缓冲区
    CPU 提前在 DDR 中分配一块连续物理内存作为帧缓存,获取其物理地址。

  2. 配置 DMA
    通过 AXI Lite 写入 S2MM 目标地址、帧大小、使能中断。

  3. 启动采集
    摄像头开始输出像素流,经 FPGA 解码后以 AXI4-Stream 形式送入 AXI DMA。

  4. DMA 自动写入
    当收到TLAST(表示一行或一帧结束),DMA 将数据写入指定 DDR 地址。

  5. 中断通知
    写完一帧后,DMA 触发中断,CPU 进入 ISR。

  6. 后续处理
    CPU 可立即处理该帧(如人脸检测),或将地址交给显示控制器渲染。

  7. 环形缓冲(可选)
    若启用循环模式,DMA 会在多个缓冲区间轮转,避免丢帧。


设计避坑指南:那些文档不会明说的经验

🛑 地址必须对齐

AXI 总线要求地址按数据宽度对齐。例如:
- 64 位数据 → 8 字节对齐
- 128 位 → 16 字节对齐

否则可能导致事务失败或性能严重下降。

📦 突发长度要合理

Burst Size 设置影响带宽利用率。太小:握手开销大;太大:可能被仲裁打断。

推荐值:16~32 beats,具体根据系统负载调整。

🔔 中断别太频繁

如果是音频采样(每毫秒一个小包),每一包都中断,CPU 很快就被拖垮。

建议采用“中断合并”策略:每 N 包才报一次中断,平衡实时性与开销。

🧯 错误检测不可少

定期检查状态寄存器中的错误位:
-Decoding Error:地址无效
-Slave Error:从设备异常
-Internal Error:DMA 内部故障

发现异常应及时复位通道,防止累积错误。

💡 资源占用心里要有数

AXI DMA IP 大概消耗:
- 5000 LUTs 左右
- 几个 BRAM 块(尤其开启 Scatter-Gather 时)

设计初期就要评估可用资源,避免后期布线失败。

🧪 仿真验证很重要

用 Vivado Simulator 搭建测试平台:
- 注入 AXI4-Stream 数据流
- 观察 DMA 是否正确写入模拟内存模型
- 检查中断时序、地址跳转是否正常

提前发现问题,比上板调试省十倍时间。


代码实战:裸机与 Linux 下如何控制 AXI DMA

裸机环境:寄存器级操作

#include "xparameters.h" #include "xil_io.h" #define AXI_DMA_BASEADDR 0x40400000 #define MM2S_CTRL_REG 0x00 #define MM2S_START_ADDR_REG 0x18 #define MM2S_LENGTH_REG 0x28 void start_dma_transfer(u32 src_addr, u32 length) { // 停止当前传输 Xil_Out32(AXI_DMA_BASEADDR + MM2S_CTRL_REG, 0x0); // 设置起始地址 Xil_Out32(AXI_DMA_BASEADDR + MM2S_START_ADDR_REG, src_addr); // 启动 Run/Stop 位 Xil_Out32(AXI_DMA_BASEADDR + MM2S_CTRL_REG, 0x1); // 写长度即触发传输 Xil_Out32(AXI_DMA_BASEADDR + MM2S_LENGTH_REG, length); }

📌 关键点:
- 先清控制寄存器
- 再写地址
- 最后写长度,才会真正启动

这是 Simple Mode 下的标准操作流程。


Linux 用户空间控制(UIO 示例)

想不写内核模块也能操控硬件?试试 UIO。

# 加载设备树节点后,绑定 UIO echo axi-dma > /sys/class/uio/uio0/name
int fd = open("/dev/uio0", O_RDWR); void *regs = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); // 配置 MM2S 通道 *((volatile u32*)(regs + MM2S_CTRL_REG)) = 0; *((volatile u32*)(regs + MM2S_START_ADDR_REG)) = phys_addr; *((volatile u32*)(regs + MM2S_CTRL_REG)) = 1; *((volatile u32*)(regs + MM2S_LENGTH_REG)) = frame_size; // 阻塞等待中断 int irq_count; read(fd, &irq_count, sizeof(int)); printf("✅ DMA transfer complete!\n");

这种方式非常适合快速原型开发,调试效率极高。


最后一句话总结

AXI DMA 不只是一个 IP 核,它是通往高性能 FPGA 系统的大门钥匙。
掌握它,意味着你不再只是“会写逻辑”,而是真正懂得如何构建一个高效、稳定、可扩展的软硬协同系统。

无论是做图像处理、高速采集、通信协议栈还是边缘 AI 推理,只要涉及大量数据流动,AXI DMA 都是你绕不开的核心组件。

现在回头看看你的项目,是不是也有“卡顿”、“丢帧”、“CPU 占满”的问题?
也许换个思路,让 DMA 来扛活,一切就通了。

如果你正在学习 Zynq 或 FPGA 开发,不妨动手搭个最简单的 AXI DMA 回环实验:从内存读数据 → 经 PL 绕一圈 → 写回内存。
跑通那一刻,你会对“硬件加速”四个字有全新的理解。

欢迎在评论区分享你的实践心得,我们一起交流进步!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询