25、FPGA与ARM协同设计:Zynq架构简介、AXI总线通信、Linux驱动开发、FPGA加速示例
说实话,FPGA和ARM协同设计这块,是我这些年做项目时感触最深的部分。以前我们做嵌入式系统,要么纯ARM跑软件,要么纯FPGA做逻辑,各有各的局限。直到Zynq这种异构架构出现,才真正把两者的优势捏在了一起。
我个人习惯把这种协同设计叫做「软硬兼施」——ARM负责控制、调度、网络协议栈这些串行任务,FPGA负责图像处理、信号滤波、加密解密这些并行计算。你想想看,这不就是取长补短吗?
25.1 Zynq架构简介
Zynq不是简单的「FPGA里塞了个ARM」,它是一颗真正的异构SoC。内部包含两大部分:PS(Processing System) 和 PL(Programmable Logic)。
| 组成部分 | 内容 | 我的理解 |
|---|---|---|
| PS(处理系统) | 双核Cortex-A9、DDR控制器、外设接口 | 相当于一颗完整的ARM芯片 |
| PL(可编程逻辑) | FPGA逻辑单元、DSP切片、BRAM | 就是咱们熟悉的FPGA部分 |
| 互联总线 | AXI总线、EMIO、中断控制器 | PS和PL之间的高速公路 |
我在项目中遇到过一个问题:有人把Zynq当成普通FPGA用,只用了PL部分,PS完全闲置。这其实很浪费。Zynq的精髓就在于PS和PL协同工作,ARM跑Linux系统,FPGA做硬件加速,两边通过AXI总线高速通信。
核心要点:Zynq的启动流程是先启动PS,PS初始化DDR和时钟后,再配置PL。所以PL的bit文件可以存在SD卡或QSPI Flash里,由ARM加载。
25.2 AXI总线通信
AXI总线是PS和PL之间的桥梁。说白了,就是ARM和FPGA互相读写数据的通道。Zynq里主要用到三种AXI接口:
- AXI_HP(高性能端口):直接连到DDR控制器,带宽最高,适合大数据量传输。我一般用它做视频数据搬运。
- AXI_GP(通用端口):带宽较低,适合配置寄存器、控制状态。比如ARM给FPGA发个启动信号。
- AXI_ACP(加速一致性端口):可以保持ARM和FPGA的缓存一致性,这个比较高级,我用的不多。
嗯,这里要注意:AXI总线的时序协议比较复杂。如果你自己写AXI Slave逻辑,建议先用Vivado的IP Integrator搭个简单的测试,再用ILA抓波形看看握手信号对不对。
我的小技巧:刚开始学AXI通信时,别急着写复杂逻辑。先做一个简单的「ARM写数据到FPGA寄存器,FPGA读回来再返回」的loopback测试。这个通了,后面就好办了。
25.3 Linux驱动开发
ARM上跑Linux,FPGA作为外设,那就得写驱动。Zynq的Linux驱动开发,我总结下来就三种方式:
- UIO(Userspace I/O):最简单,把FPGA的寄存器映射到用户空间,应用程序直接读写。适合控制类场景。
- 字符设备驱动:标准Linux驱动模型,支持open/read/write/ioctl。适合数据流传输。
- DMA驱动:配合AXI DMA IP核,实现高速数据搬运。这个稍微复杂,但性能最好。
我曾经踩过一个坑:用UIO方式映射了FPGA的AXI_GP端口,结果发现读写速度只有几MB/s。后来查资料才知道,UIO没有缓存一致性处理,每次读写都要flush cache。换成DMA方式后,速度直接飙到几百MB/s。
避坑指南:写驱动时,一定要确认FPGA的地址映射是否正确。我见过有人把0x43C00000写成了0x43C00001,结果读写全乱套。用devmem命令先测一下物理地址,再写驱动。
25.4 FPGA加速示例
光说不练假把式。我拿一个实际项目举例:图像边缘检测的硬件加速。
纯ARM做Sobel边缘检测,处理一帧640x480的图像大概要30ms。用FPGA做流水线加速后,只需要2ms。为什么这么快?因为FPGA可以同时处理多个像素,而且没有CPU的指令取指、分支预测这些开销。
具体实现思路是这样的:
- ARM通过AXI_HP把图像数据写入DDR
- FPGA的Sobel加速IP通过AXI_HP读取DDR中的图像
- FPGA内部做3x3卷积、梯度计算、阈值判断
- 结果写回DDR的另一块区域
- ARM读取结果,显示或保存
// 简单的Sobel加速IP接口定义(Verilog)
module sobel_accelerator (
input wire clk,
input wire rst_n,
// AXI Slave接口(ARM配置寄存器)
input wire [31:0] s_axi_awaddr,
input wire s_axi_awvalid,
// AXI Master接口(读写DDR)
output wire [31:0] m_axi_araddr,
input wire [31:0] m_axi_rdata,
// 控制寄存器
input wire start,
output reg done
);
// 内部流水线处理...
endmodule
你看,ARM端只需要调用一个ioctl命令,告诉FPGA「开始处理」,然后等待中断信号。FPGA自己就把活干完了。这就是协同设计的精髓——ARM做调度,FPGA做苦力。
性能对比:同样的Sobel算法,ARM纯软件30ms,FPGA硬件加速2ms,加速比15倍。而且FPGA功耗只增加了不到0.5W。这就是为什么现在很多边缘计算设备都用Zynq的原因。
最后说一句:FPGA与ARM协同设计,不是简单的「把代码分成两份」。你得从系统层面考虑,哪些任务适合ARM跑,哪些适合FPGA跑。我个人的经验是:控制流、协议栈、文件系统给ARM;数据流、矩阵运算、信号处理给FPGA。这样分工,才能发挥Zynq的真正实力。