Zynq或MPSOC的同学,第一个被问爆的问题一定是:"ARM这边和FPGA那边,数据到底怎么传过去?" 今天用一条公路体系,把ARM-FPGA通信讲透。
> 核心结论: 控制寄存器走AXI-Lite(μs级),批量数据走AXI4+DMA(MB/s级),低速外设挂UART/SPI(kbps级)。90%的初学者错误是把所有数据塞进GPIO——那是拿自行车跑高速。
AXI是ARM与FPGA fabric之间最核心的数据通道。AXI4支持64/128/256/512位数据宽度,理论带宽数GB/s。典型场景:图像帧搬运、FIFO高吞吐读写、DMA传输。AXI-Lite是其简化版(32位地址+32位数据),专用于寄存器读写,握手简单,一次事务不到1μs。
FPGA侧只做传感器采集、LED控制这类低速功能时,挂一条UART(115200~921600 bps)或SPI就够。优点是代码量小、调试直观;缺点是带宽天花板低,不适合搬大块数据。
几根GPIO加硬件mailbox握手,适合传几字节控制命令。Zynq的PS-PL mailbox(pl_ps_irq)就属于这类。
| 场景 | ❌ 错误 | ✅ 正确 | 原因 |
|---|---|---|---|
| 传一帧1080P图像 | GPIO逐bit拉 | AXI4 + DMA | GPIO耗时ms级,DMA微秒级 |
| 读写FPGA状态寄存器 | 每次触发中断+GPIO翻转 | AXI-Lite mmap读写 | 一次事务<1μs,CPU不阻塞 |
| ARM 100 MHz / FPGA 200 MHz跨域 | 直接连线采样 | 两级FF同步 + 格雷码FIFO | 防亚稳态传播 |
module uart_rx #(parameter CLK_FREQ=100_000_000, BAUD=115_200)(
input clk, rst, rx,
output reg [7:0] rx_data, rx_done
);
localparam DIV = CLK_FREQ / BAUD;
reg [16:0] cnt; reg [3:0] bit_cnt; reg [7:0] shift;
always @(posedge clk or posedge rst) begin
if (rst) begin cnt<=0; bit_cnt<=0; shift<=0; rx_data<=0; rx_done= DIV-1) begin
cnt <= 0;
if (bit_cnt == 0) shift <= rx;
else if (bit_cnt<=7) shift <= {shift[6:0], rx};
if (bit_cnt == 8) begin rx_data <= shift; rx_done <= 1; end
bit_cnt <= bit_cnt + 1;
end
else cnt <= cnt + 1;
end
end
endmodule第一次做Zynq项目,很多人把所有功能往GPIO上堆:"FPGA算完了,ARM用GPIO读不就行了?" 能跑,但帧率一上去CPU就卡死。正确思路是先定数据量级再选接口:
< 1 KB → AXI-Lite / GPIO
1 KB ~ 几十 MB → AXI4 + DMA
需周期性同步 → 异步FIFO + 时钟域交叉
先画数据流图,再选接口,比闷头写代码省至少两周。
用Vitis的Xil_Out32 / Xil_In32,地址由Block Design里AXI-Lite的基地址决定。FPGA侧寄存器模块用简单always块即可,保持32位宽一致。两边"对暗号"的就是那个地址。
会。跨时钟域必须加两级触发器同步(防亚稳态),数据通路用异步FIFO(格雷码指针)。单独跑一拍也许没事,但上电瞬间亚稳态传播概率不可忽略。
把ARM-FPGA通信想成公路体系——高速路(AXI)、省道(UART/SPI)、电话(GPIO/mailbox),选对路就成功了一半。
思考题: 如果你的项目需要ARM每1 ms从FPGA读一次256字节数据,你会选AXI-Lite轮询、DMA中断、还是OBC?为什么?
每个人卡住的地方不一样,有人是跨时钟域没搞清,有人是AXI握手时序对不上。与其自己翻手册踩坑,不如让我们帮你诊断当前进度——点击右下角客服窗口,1对1聊聊你适合哪条学习路径。
未经许可,禁止转载!