3、CPU与FPGA通信基础:PCIe协议简介、AXI总线协议、DMA传输原理、Xilinx DMA IP核配置
做异构计算,说白了就是让CPU和FPGA好好说话。CPU是老大,负责调度;FPGA是打手,负责干活。但这两兄弟怎么传数据?这就是本章要聊的核心。
我个人习惯把通信分成三层来看:物理层(PCIe)、协议层(AXI)、传输层(DMA)。你把这层搞明白了,后面写代码心里就有底了。
3.1 PCIe协议简介
PCIe,全称Peripheral Component Interconnect Express。嗯,名字很长,但你只需要记住:它是目前CPU和FPGA之间最快、最通用的总线。
为什么选PCIe?我刚开始做项目时也纠结过,后来发现没得选。你想啊,千兆以太网延迟太高,USB带宽不够,只有PCIe能同时满足高带宽和低延迟。我见过一个高频交易项目,CPU到FPGA的延迟必须控制在1微秒以内,不用PCIe根本做不到。
3.1.1 拓扑结构
PCIe是点对点的串行总线。什么意思?就是每个设备都有自己的专用通道,不像老式PCI那样大家抢一条总线。
关键概念:
- Root Complex(RC):CPU端的根节点,负责管理所有PCIe设备
- Endpoint(EP):终端设备,比如你的FPGA卡
- Switch:交换机,用于扩展多个设备
- Lane(通道):一条差分信号对,x1、x4、x8、x16表示通道数
我画了一张图,帮你理解PCIe的拓扑结构:
3.1.2 传输层级
PCIe协议分三层:事务层、数据链路层、物理层。你不需要背,但要知道每层干啥的。
| 层级 | 功能 | 核心概念 |
|---|---|---|
| 事务层 | 处理读写请求、完成包 | TLP(事务层包)、Memory/IO/Config空间 |
| 数据链路层 | 保证可靠传输、重传机制 | DLLP、ACK/NAK、CRC校验 |
| 物理层 | 串并转换、电气特性 | 8b/10b编码(Gen1/2)、128b/130b编码(Gen3+) |
避坑指南:我曾经在Gen2和Gen3混用的系统上踩过坑。FPGA卡是Gen3的,但插槽只支持Gen2,结果协商降级到Gen2,带宽直接砍半。所以设计时一定要确认好PCIe版本和通道数。
3.2 AXI总线协议
AXI是ARM公司搞的,现在已经是Xilinx FPGA内部的事实标准。说白了,FPGA内部各个模块之间怎么连?靠AXI。
你想想看,PCIe是FPGA和CPU之间的高速公路,那AXI就是FPGA内部的毛细血管。数据从PCIe进来后,要通过AXI总线分发到各个处理模块。
3.2.1 AXI4、AXI4-Lite、AXI4-Stream
AXI有三个变种,别搞混了:
- AXI4(Full):完整版,支持突发传输,适合大数据块搬运
- AXI4-Lite:精简版,不支持突发,适合寄存器配置
- AXI4-Stream:流式版,没有地址,适合数据流处理
我一般这样用:配置寄存器用AXI-Lite,搬数据用AXI-Full,处理流数据用AXI-Stream。各司其职,别混着用。
3.2.2 握手信号
AXI的核心是握手协议。VALID和READY信号,缺一不可。
// AXI握手时序示例
// 发送方拉高VALID,接收方拉高READY
// 当VALID和READY同时为高时,数据传输发生
always @(posedge clk) begin
if (reset) begin
valid <= 1'b0;
data <= 32'h0;
end else begin
// 当ready为高时,发送下一个数据
if (ready) begin
valid <= 1'b1;
data <= next_data;
end
end
end
注意:我曾经见过一个同事,VALID信号一直拉高不释放,结果接收方还没准备好,数据就丢了。记住:VALID不能依赖READY,但READY可以依赖VALID。这是AXI协议的铁律。
3.3 DMA传输原理
DMA,Direct Memory Access。说白了,就是让FPGA直接读写CPU的内存,不用CPU插手。
为什么需要DMA?你想想看,如果没有DMA,CPU得一条一条指令地搬数据。一次PCIe读写,CPU要处理中断、上下文切换,延迟几十微秒就没了。而DMA可以在纳秒级别完成一次传输。
3.3.1 传统DMA vs 现代DMA
| 特性 | 传统DMA | 现代DMA(如XDMA) |
|---|---|---|
| 描述符管理 | CPU手动配置 | 硬件自动抓取 |
| 传输粒度 | 固定大小 | 可变大小、链表 |
| 中断频率 | 每次传输都中断 | 批量完成中断 |
| 适用场景 | 简单数据搬运 | 高性能、低延迟交易 |
我做过一个低延迟交易系统,用的就是现代DMA。CPU只需要准备好描述符链表,FPGA自己会去抓取,然后直接往内存里写数据。CPU全程不用管,延迟从10微秒降到了500纳秒。
3.3.2 描述符链表
描述符链表是DMA的核心。每个描述符包含:源地址、目的地址、传输长度、下一个描述符指针。
// DMA描述符结构(C语言定义)
typedef struct {
uint64_t src_addr; // 源地址(FPGA侧)
uint64_t dst_addr; // 目的地址(内存侧)
uint32_t length; // 传输长度(字节)
uint32_t control; // 控制位(中断使能、方向等)
uint64_t next_desc; // 下一个描述符指针
} dma_descriptor_t;
个人经验:描述符链表一定要放在连续物理内存中。我刚开始用虚拟地址,结果DMA抓取时地址翻译出错,数据全乱了。后来改用dma_alloc_coherent分配连续内存,问题解决。
3.4 Xilinx DMA IP核配置
Xilinx提供了几个DMA IP核,最常用的是XDMA和AXI DMA。我个人推荐XDMA,因为它集成了PCIe控制器和DMA引擎,省事。
3.4.1 XDMA IP核配置步骤
- 打开Vivado IP Catalog,搜索"XDMA"
- 选择PCIe配置:Gen3 x8或x16,根据你的板卡来
- 配置DMA通道:一般选2个H2C(主机到卡)和2个C2H(卡到主机)
- 描述符引擎:开启"Descriptor Bypass"模式,减少延迟
- 中断配置:选择MSI-X,支持多队列中断
关键参数:
- PCIe ID:Vendor ID(0x10EE for Xilinx)、Device ID(自定义)
- BAR空间:一般配置64位非预取,大小4KB到1MB
- DMA缓冲:对齐到4KB边界,否则性能下降
3.4.2 驱动与软件接口
XDMA自带Linux驱动,你只需要加载模块就行:
# 加载XDMA驱动
sudo insmod xdma.ko
# 查看设备
lspci -d 10ee:
# 测试DMA传输
sudo ./dma_test -r -d /dev/xdma0_c2h_0 -s 4096 -a 0x10000000
嗯,这里要注意:驱动默认使用UIO(用户空间I/O),如果你需要更低延迟,可以考虑用VFIO或者自己写内核模块。我一般用UIO就够了,除非延迟要求特别苛刻。
避坑指南:我曾经在配置XDMA时,忘记设置BAR空间的地址范围。结果驱动加载后,读写BAR空间直接导致系统崩溃。记住:BAR空间必须和FPGA内部地址映射一致,否则会触发PCIe致命错误。
好了,PCIe、AXI、DMA这三个基础概念讲完了。你把这些搞明白,CPU和FPGA通信这块就算入门了。下一章我们聊聊具体的硬件设计流程,到时候我会拿一个实际项目来拆解。
本章小结:
- PCIe是CPU和FPGA之间的物理通道,理解拓扑和层级是关键
- AXI是FPGA内部总线,三种变种各有用途
- DMA让FPGA直接访问内存,是现代高性能系统的基石
- XDMA IP核配置要关注PCIe参数、DMA通道和中断方式
公众号:蓝海资料掘金营,微信deep3321