FPGA 进阶 15:PCIe 入门使用——XDMA IP、BAR 空间、TLP 概念与 DMA 通路
本文是
fpga/进阶层第 19 篇。PCIe 是 FPGA 加速卡/采集卡与主机之间带宽最高的接口(Gen3 x8 单向理论近 8GB/s)。它协议栈很深,但 FPGA 工程师入门不需要自己写协议——Xilinx 的 XDMA IP 把硬核和 DMA 引擎打包,对外就是你已经熟悉的 AXI 接口。这篇讲第一次调 PCIe 必备的最小知识:TLP/BAR 是什么、主机怎么看到 FPGA、XDMA 数据通路怎么搭、软件怎么传数据、链路起不来怎么查。
本文要回答的问题
- PCIe 协议分几层?TLP 是什么?
- 主机怎么把 FPGA 枚举成设备?BAR 是什么、主机怎么访问 FPGA 寄存器?
- 为什么入门直接用 XDMA IP?它对外给什么接口?
- H2C/C2H、DMA 通路怎么搭?软件怎么收发?
- 链路训练失败、枚举不到、DMA 超时怎么排查?
一、PCIe 协议分层与 TLP
PCIe 是高速串行、点对点、分层协议(和并行的 PCI 完全不同):
| 层 | 职责 |
|---|---|
| 事务层 Transaction | 把请求打包成 TLP(Transaction Layer Packet):Memory Read/Write、配置读写、Completion(读的返回包);还有信用(credit)流控 |
| 数据链路层 Data Link | 给 TLP 加序列号、LCRC,做 ACK/NAK 与自动重传,保证可靠 |
| 物理层 Physical | 高速收发器(GTX/GTH/GTY)、8b/10b(Gen1/2)或 128b/130b(Gen3+)编码、差分对、链路训练 |
几个入门概念:
- Memory Read/Write TLP:最常用,主机读写设备 BAR 地址、或设备 DMA 读写主机内存,都封装成这种 TLP,携带地址、长度、数据;
- Completion TLP:读请求的返回数据(PCIe 读是「请求—完成」分离的,像 AXI 的读通道);
- 链路是 x1/x4/x8/x16 多条 lane(差分对)绑定,速率 Gen1 2.5GT/s、Gen2 5、Gen3 8、Gen4 16(每 lane)。
你不需要手写 TLP——XDMA/硬核替你组包,但理解「读=发请求 TLP + 收 Completion」能解释为什么 PCIe 读延迟高、要靠多 outstanding 摊薄(同 AXI outstanding)。
二、主机怎么看到 FPGA:枚举与 BAR
FPGA 上电后,主机把它当作一个 PCIe 设备枚举:
- 链路训练成功(LTSSM 进 L0)后,主机读设备的配置空间(Vendor ID、Device ID、Class、BAR 等);
- BAR(Base Address Register):设备声明「我需要 N 段地址空间、各多大」,主机给每段分配一个主机物理地址基址写回 BAR;
- 之后主机访问「BAR 基址 + 偏移」的物理地址,就变成 Memory TLP 发到 FPGA。
BAR 就是 FPGA 寄存器/存储在主机地址空间里的一个窗口。在 XDMA 设计里,BAR 读写被转成 AXI4-Lite 访问你 IP 的寄存器(偏移对应 AXI 地址),软件 mmap 后像指针一样读写:
// 主机侧(XDMA 驱动):控制 BAR 映射成 user 节点
int fd = open("/dev/xdma0_user", O_RDWR);
void *bar = mmap(NULL, MAP_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
*(volatile uint32_t*)(bar + 0x10) = 0x1; // 写 FPGA 寄存器(偏移0x10)
uint32_t v = *(volatile uint32_t*)(bar + 0x20); // 读 FPGA 状态三、XDMA IP:把协议栈和 DMA 都打包
Xilinx XDMA(DMA/Bridge Subsystem for PCI Express) 内部集成 PCIe 硬核、协议栈和 DMA 引擎,对外只暴露 AXI,是入门和大多数产品的选择:
主机 (Host)
PCIe Gen3 x8 │ TLP
┌───────▼────────┐
│ XDMA IP │
│ 硬核+协议栈+DMA │
└───┬───────┬────┘
AXI4-Lite(控制) AXI4/AXI-Stream(数据)
│ │
你的寄存器块 H2C/C2H 通道 → 你的流水 / DDR对外两类接口:
| 接口 | 方向 | 用途 |
|---|---|---|
| AXI4-Lite master(M_AXI_LITE) | XDMA→你 | 主机读写 BAR 时产生,访问你的控制/状态寄存器 |
| AXI4 master(M_AXI_BYPASS/M_AXI)或 AXI-Stream | XDMA↔你 | DMA 大数据搬运 |
四、H2C / C2H DMA 通路
- H2C(Host-to-Card):主机→FPGA。主机驱动给 DMA 描述符(主机内存源地址、长度),XDMA 主动发 Memory Read TLP 把数据从主机内存读来,送到 FPGA;
- C2H(Card-to-Host):FPGA→主机。XDMA 把 FPGA 数据用 Memory Write TLP 写进主机内存。
两种数据接法:
- AXI-Stream 模式(最简单,推荐入门):H2C 出一个 AXI4-Stream(TVALID/TDATA/TREADY)给你的处理流水,C2H 从你的流水收 Stream——天然 valid/ready 反压,适合传感器/采集/流式数据;
- AXI Memory-Map 模式:XDMA 出 AXI4 master,挂 SmartConnect 直接读写 FPGA 侧 DDR/BRAM,适合块数据落盘。
主机软件(XDMA 驱动把通道抽象成设备节点):
int h2c = open("/dev/xdma0_h2c_0", O_WRONLY);
int c2h = open("/dev/xdma0_c2h_0", O_RDONLY);
write(h2c, host_buf, len); // 启动一次 H2C DMA:主机内存 -> FPGA
read(c2h, host_buf, len); // 启动一次 C2H DMA:FPGA -> 主机内存
// Windows 下 XDMA 驱动提供等价的 .dll/句柄读写控制通路(寄存器)走 xdma0_user(BAR→AXI-Lite),数据通路走 h2c/c2h(DMA),这和 进阶 16 讲的「Lite 管控制、Full/Stream 管带宽」完全一致。
五、bring-up 顺序与排查
按 LTSSM 状态从下往上查(XDMA 自带 example design + 驱动测试程序,先跑官方 example 区分硬件/逻辑):
| 阶段 | 检查 | 不通过的表现/原因 |
|---|---|---|
| ① 物理层 | 100MHz 差分参考时钟、PERST# 复位、收发器管脚约束 | ltssm 卡在 Detect/Polling:时钟/管脚/金手指/线缆问题 |
| ② 链路训练 | 状态到 L0、宽度/速率(x8/Gen3) | 协商成 x1/Gen1:部分 lane 约束/SI 问题 |
| ③ 主机枚举 | lspci 能看到 Vendor/Device ID | L0 但枚举不到:配置空间/ID;试 echo 1 > /sys/bus/pci/rescan 或热复位 |
| ④ 驱动 | 出现 /dev/xdma0_* | 枚举到但无节点:XDMA 驱动未加载 |
| ⑤ 寄存器 | mmap 读写 BAR(AXI-Lite) | 读到全 F/全 0:AXI-Lite 译码/地址/复位 |
| ⑥ DMA | example 的 dma 测试 / 自己 loopback | DMA 超时:Stream 没给 valid/ready、描述符地址错、ID/outstanding |
常见坑:①参考时钟来源不对(板载 vs 金手指);②复位没释放或 PERST# 接错;③AXI-Stream 侧只给了 TVALID 没处理 TREADY 反压导致 DMA 等死;④DMA 用的主机内存没做一致性/ pinned 内存(驱动通常已处理,自己写驱动要注意)。
相关与延伸
前置/配套:AXI4 完整协议与互联(XDMA 对外就是 AXI,outstanding 提带宽同理)、AXI4-Stream 与 Lite、DDR MIG(DMA 块数据落点)、valid/ready 握手。 TLP 深水区、Scatter-Gather 描述符、多队列、QDMA、Gen4/5 与 SR-IOV 见专家层「PCIe 深水区(TLP·DMA)」「PCIe DMA 架构」规划篇;高速收发器本身见 LVDS 源同步 与专家层收发器篇。
一句话总结:PCIe 是分层串行协议(TLP/链路层/收发器),主机经枚举和 BAR 把 FPGA 映射成一段内存地址;入门直接用 XDMA IP——它把硬核、协议栈和 DMA 引擎打包,控制走 BAR→AXI4-Lite 寄存器、数据走 H2C/C2H 的 AXI-Stream/AXI4,主机驱动读写 /dev/xdma0_* 即可;bring-up 按「参考时钟/复位→ltssm 到 L0→lspci 枚举→驱动节点→BAR 读写→DMA loopback」逐层推进,先用官方 example design 区分硬件与逻辑。