AXI DMA实战入门:从零理解如何让FPGA与CPU高效协同
你有没有遇到过这样的场景?
一个摄像头每秒输出120帧高清图像,数据速率轻松突破几百MB/s。如果让CPU亲自去读每一个像素、再写进内存——还没处理完一帧,下一帧就已经溢出了。系统卡顿、丢帧严重,调试到深夜也没找到“瓶颈”在哪。
问题出在哪儿?
答案是:你不该让CPU干搬运工的活。
在现代高性能嵌入式系统中,真正聪明的做法是——让硬件自动搬数据,CPU只负责发号施令和收尾处理。而实现这一理念的核心技术之一,就是本文要讲透的:AXI DMA。
为什么我们需要AXI DMA?
先来看一组真实对比:
| 场景 | CPU轮询搬运 | 使用AXI DMA |
|---|---|---|
| 1080p视频采集(约1.5 Gbps) | CPU占用率 >90% | <5% |
| 数据延迟 | 不确定,易抖动 | 确定性延迟,微秒级响应 |
| 是否能持续工作 | 难以维持,常丢帧 | 可稳定运行数小时以上 |
看到差距了吗?关键就在于“谁在搬数据”。
传统的做法是:CPU从外设寄存器一个个读取数据,再写入内存缓冲区。这种方式简单直观,但效率极低,尤其面对高速流式数据时几乎不可行。
而 AXI DMA 的出现,彻底改变了这个模式。它本质上是一个专用的数据搬运引擎,集成在FPGA逻辑中,能够直接连接AXI总线,实现外设与DDR内存之间的直通传输,全程无需CPU插手。
典型应用平台如 Xilinx Zynq-7000 或 Zynq UltraScale+ MPSoC,其中:
-PS端(Processing System):ARM Cortex-A系列处理器,负责控制、调度、算法决策;
-PL端(Programmable Logic):FPGA部分,执行高速并行逻辑,比如图像预处理、协议解析;
-AXI DMA就部署在两者之间,作为大数据通道的“高速公路收费站”,精准引导车流(数据流)进出内存。
AXI DMA到底是什么?用“人话”解释清楚
我们可以把 AXI DMA 想象成一个智能物流中心:
- 货物 = 数据包(比如一帧图像)
- 卡车 = AXI4-Stream 数据流(串行高速通道)
- 仓库 = DDR 内存(大容量存储空间)
- 物流调度员 = DMA 控制器
- 总经理 = CPU(只下命令:“运这批货到3号仓”,然后喝茶去了)
整个过程不需要总经理盯着每一辆卡车怎么装卸,只要提前告诉调度员:
- 货从哪来?
- 运到哪个仓库地址?
- 多少件?
- 完事后通知我一声。
这就是 AXI DMA 的核心思想:配置 + 启动 + 中断通知,剩下的全由硬件自动完成。
它有两个主干道:MM2S 和 S2MM
这是 AXI DMA 最重要的两个通道,名字看着拗口,其实很好记:
| 缩写 | 全称 | 中文含义 | 方向 |
|---|---|---|---|
| MM2S | Memory-Mapped to Stream | 内存 → 流 | 发送数据给FPGA |
| S2MM | Stream to Memory-Mapped | 流 → 内存 | 接收FPGA传来数据 |
举个例子:
- 你要把一张图片发给HDMI显示模块 → 用MM2S,DMA从DDR取出数据,变成AXI4-Stream送给PL。
- 你在采集摄像头数据 → 用S2MM,PL把像素流交给DMA,DMA自动存进DDR指定位置。
这两个通道可以同时工作,形成双向全双工通信,非常适合音视频、雷达、工业相机等需要实时收发的场景。
工作流程拆解:一次完整的DMA传输经历了什么?
别被“高大上”的术语吓住,我们一步步来看它是怎么跑起来的。
第一步:CPU准备阶段(管理员发指令)
虽然说“零CPU干预”,但初始化还是得靠CPU来启动。这就像快递公司接单前,得先把运单填好。
CPU要做几件事:
1. 在内存里划出一块区域当接收缓冲区(Buffer),并获取它的物理地址;
2. 告诉AXI DMA:“我要用S2MM通道,目标地址是0x1A00_0000,长度1920×1080×2字节”;
3. 开启中断,以便传输完成后能收到“已送达”通知;
4. 写一个“启动”命令到控制寄存器 —— 相当于按下“开始派送”按钮。
代码层面大概是这样(后面会详解):
XAxiDma_SimpleTransfer(&dma, rx_phys_addr, frame_size, XAXIDMA_DEVICE_TO_DMA);第二步:DMA接管数据流(自动搬运开始)
一旦启动,DMA就开始监听来自PL端的数据流。
假设摄像头通过LVDS接口接入FPGA,经过Video-In IP核打包成AXI4-Stream格式,送到AXI DMA的S2MM入口。此时:
- DMA根据之前设置的目标地址,逐拍将数据写入DDR;
- 写满一帧后,自动触发中断;
- 如果开启了Scatter-Gather模式,还能跨多个非连续内存块传输,像拼图一样把分散的缓冲区连起来。
整个过程中,CPU可以去做别的事:图像识别、网络上传、用户交互……完全不受影响。
第三步:完成上报与循环复用(闭环处理)
中断来了,CPU知道:“哦,新的一帧到了。”
于是它可以:
- 标记该帧为“就绪”;
- 启动下一个缓冲区继续接收(双缓冲机制);
- 把刚收到的数据交给OpenCV处理或编码保存。
这种“启动→等待中断→处理→再启动”的循环,构成了一个稳定的流水线系统。
关键特性一览:AXI DMA凭什么这么强?
下面这些参数不是随便列的,每一个都直接影响你的系统性能设计。
| 特性 | 实际意义 |
|---|---|
| 支持64/128位数据宽度 | 数据越宽,每次传得越多。128位@200MHz ≈ 3.2 GB/s理论带宽 |
| 突发传输(Burst Length up to 256) | 一次性传一大块,减少地址建立开销,提升总线利用率 |
| Scatter-Gather 支持 | 不怕内存碎片!即使缓冲区分段分配也能无缝接收整帧 |
| BD(Buffer Descriptor)链表管理 | 类似快递订单队列,DMA自己读取下一单,无需CPU频繁干预 |
| 多种中断类型 | 可分别使能“传输完成”、“错误”、“帧同步”等中断,便于精细化监控 |
| AXI Lite 配置接口 | 寄存器可通过CPU轻量访问,方便动态调整 |
⚠️ 注意:Scatter-Gather功能需要额外启用,且消耗更多FPGA资源。对于简单的单缓冲应用,可关闭以节省逻辑单元。
实战代码演示:裸机环境下如何驱动AXI DMA
很多初学者卡在第一步:不知道怎么让DMA跑起来。下面我们用Xilinx官方库写一个最简化的例子,适合用于学习和原型验证。
环境前提
- 平台:Zynq-7000
- 开发工具:Vivado + SDK
- 使用 Xilkernel 提供的标准驱动
xaxidma.h
#include "xaxidma.h" #include "xparameters.h" #include "xil_printf.h" static XAxiDma axi_dma; // DMA实例 // 初始化函数 int dma_init() { XAxiDma_Config *cfg; int status; // 查找设备配置(基于硬件设计中的ID) cfg = XAxiDma_LookupConfig(XPAR_AXI_DMA_0_DEVICE_ID); if (!cfg) { xil_printf("Error: No config found for device ID %d\r\n", XPAR_AXI_DMA_0_DEVICE_ID); return XST_FAILURE; } // 初始化驱动实例 status = XAxiDma_CfgInitialize(&axi_dma, cfg); if (status != XST_SUCCESS) { xil_printf("Error: DMA initialization failed\r\n"); return XST_FAILURE; } // 检查是否支持Scatter-Gather if (XAxiDma_HasSg(&axi_dma)) { xil_printf("Info: Scatter-Gather mode is enabled.\r\n"); } else { xil_printf("Info: Simple mode only.\r\n"); } return XST_SUCCESS; }启动接收(S2MM)—— 把FPGA传来的数据存进内存
int start_receive(u32 buffer_phys_addr, u32 length) { int status; status = XAxiDma_SimpleTransfer(&axi_dma, 0, // MM2S源地址(此处不用) buffer_phys_addr, // S2MM目的地址 length, // 字节数 XAXIDMA_DEVICE_TO_DMA); // 方向:设备→内存 if (status != XST_SUCCESS) { xil_printf("Error: S2MM transfer failed\r\n"); return XST_FAILURE; } return XST_SUCCESS; }启动发送(MM2S)—— 把内存数据推给FPGA
int start_transmit(u32 buffer_phys_addr, u32 length) { int status; status = XAxiDma_SimpleTransfer(&axi_dma, buffer_phys_addr, // 源地址 0, // S2MM不使用 length, XAXIDMA_DMA_TO_DEVICE); // 内存→设备 if (status != XST_SUCCESS) { xil_printf("Error: MM2S transfer failed\r\n"); return XST_FAILURE; } return XST_SUCCESS; }关键注意事项(新手必看!)
必须使用物理地址
虚拟地址是操作系统玩的概念,DMA直接连总线,只能认物理地址。如果你用了MMU,记得做地址映射。缓存一致性问题
ARM有Cache,DMA写的是DDR。如果CPU直接读Cache里的旧数据,就会出错!
解决办法:
- 接收前:Xil_DCacheInvalidateRange(addr, len)—— 让Cache失效,强制从DDR读最新数据
- 发送前:Xil_DCacheFlushRange(addr, len)—— 把Cache里的数据刷进DDR,确保DMA能拿到最新的
地址对齐要求
AXI协议要求突发传输起始地址对齐。例如128位宽需16字节对齐,否则可能报错或性能下降。不要频繁调用SimpleTransfer
它适用于单次小任务。高频场景建议使用中断+环形缓冲+BD链表,避免CPU忙等。
典型应用场景:图像采集系统的架构长什么样?
我们来看一个实际的Zynq视频采集系统结构:
[CMOS Sensor] ↓ (MIPI/LVDS) [FPGA PL: Image Receiver IP] ↓ (AXI4-Stream) [AXI DMA - S2MM Channel] ↓ (Memory-Mapped Write) DDR ←─── [ARM A9/A53] ↑ [App: Display / Encode / AI] ↑ [User Interface]反向路径用于输出:
[Image in DDR] ↓ [AXI DMA - MM2S] ↓ (AXI4-Stream) [Video Timing Ctrl + HDMI PHY] ↓ [Monitor]在这个架构中,AXI DMA 成为了真正的“中枢神经”,实现了:
- 高速采集不丢帧
- 图像处理与显示解耦
- 多路视频切换成为可能(通过切换DMA目标地址)
常见坑点与避坑秘籍
❌ 坑1:明明传了数据,CPU读出来却是乱码
✅ 原因:Cache没处理!CPU从Cache读了旧数据。
🔧 解法:接收前务必调用Xil_DCacheInvalidateRange()。
❌ 坑2:DMA启动后无反应,也不报错
✅ 原因:地址未对齐,或外设没送出有效tvalid信号。
🔧 解法:用ILA抓AXI4-Stream信号,检查tvalid和tready是否握手成功。
❌ 坑3:传输一半突然中断
✅ 原因:写响应超时(Write Response Timeout),可能是DDR不稳定或地址越界。
🔧 解法:启用DMA错误中断,捕获ERROR_IRQ;检查目标地址是否在可用内存范围内。
❌ 坑4:想传1080p却只能收720p
✅ 原因:AXI总线带宽不足!计算一下:
1080p @ 60fps × 2B/pixel = 1920×1080×60×2 ≈2.5 Gbps
若AXI只有32位@100MHz → 带宽仅800 MB/s ≈ 6.4 Gbps?等等……不对!
⚠️ 注意单位换算:
- AXI 32位@100MHz → 每秒传输 100M × 4B =400 MB/s
- 实际可用约350 MB/s,远不够2.5 Gbps需求!
✅ 解法:升级到64位或128位AXI,或降低分辨率/帧率。
设计建议:如何写出稳定可靠的DMA系统?
优先使用双缓冲机制
准备两个接收缓冲区,交替使用。当前缓冲区满时,切换到另一个,保证数据流不断。结合中断使用,别轮询!
轮询浪费CPU资源。正确姿势是:c enable_interrupt(); start_dma_transfer(); while(!transfer_done); // 错误示范!
应改为:c request_irq(dma_irq, dma_isr); // 注册中断服务程序 start_dma_transfer(); // 启动即返回 // CPU去做别的事...合理规划内存布局
- 分配大块物理连续内存(可用Xil_Memalign)
- 避免与其他DMA设备争抢带宽
- 考虑NUMA效应(在多核MPSoC中尤为关键)开启错误中断监控
即使正常运行时不常用,也建议开启错误中断,关键时刻能帮你快速定位硬件故障。性能测试必不可少
用实际负载压测系统稳定性,记录平均延迟、最大抖动、丢帧率等指标。
结语:掌握AXI DMA,才算真正踏入高性能嵌入式开发的大门
AXI DMA 不只是一个IP核,更是一种思维方式的转变:
从“软件为中心”转向“软硬协同”。
当你学会把重复、耗时、确定性的任务交给硬件自动化执行时,你的系统才能真正做到:
- 更快
- 更稳
- 更省资源
无论是做智能相机、工业检测、车载视觉,还是5G边缘计算节点,高效的数据管道都是基石。而 AXI DMA 正是构建这条管道的关键组件。
如果你想动手实践,强烈推荐从 Xilinx 官方的“AXI DMA Loopback Test”工程入手,亲手连一次Block Design,跑一遍SDK代码,你会对“数据是如何自己走进内存的”有全新的认知。
如果你在调试过程中遇到了具体问题,比如“为什么我的DMA不触发中断?”或者“Scatter-Gather怎么配置BD链表?”,欢迎在评论区留言讨论。我们一起解决真问题,不做纸上谈兵。