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的拓扑结构:

CPU Root Complex (RC) PCIe Switch Endpoint (FPGA) Endpoint (GPU) Endpoint (NVMe) x8 Gen3 x16 Gen4 x4 Gen3

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核配置步骤

  1. 打开Vivado IP Catalog,搜索"XDMA"
  2. 选择PCIe配置:Gen3 x8或x16,根据你的板卡来
  3. 配置DMA通道:一般选2个H2C(主机到卡)和2个C2H(卡到主机)
  4. 描述符引擎:开启"Descriptor Bypass"模式,减少延迟
  5. 中断配置:选择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