干这行久了你会发现,100G RDMA 这个词几乎已经和高性能网络绑定了。不管你是做存储加速、AI 训练集群,还是搞数据中心网络卸载,最终都要面对同一个问题:怎么把 100G RDMA 这条链路真正跑起来。对 FPGA 工程师来说,这既是机会也是挑战——机会在于 FPGA 可以把协议栈彻底打开,按你的需求改;挑战在于 RDMA 涉及 PCIe、DMA、队列管理、无损网络一堆东西,任何一个环节掉链子,整个环境就起不来。
这篇文章就围绕“用 Xilinx FPGA 搭建 100G RDMA 测试环境”这条主线,从 Vivado 里的 IP 配置开始讲,一直到 PC 端通过 RDMA 网卡和 FPGA 真正互通、跑出带宽数据为止。适合手里有 UltraScale+ 系列板卡、想快速拥有一套可控的 100G RDMA 验证平台的开发者,也适合准备做 RoCE v2 协议学习、FPGA 网络卸载设计的同学。整个过程我不会只贴配置截图,而是把每一步背后的原理、踩过的坑、排查的思路一起写清楚。
1. 先想明白:这套环境到底在做什么
1.1 RDMA 的三种形态,为什么 FPGA 盯上了 RoCE v2
RDMA 的全称是 Remote Direct Memory Access,核心思想就是让数据从一台机器的内存直接搬到另一台机器的内存,过程中 CPU 只负责下发命令,不参与数据搬运。这个能力在高性能计算、分布式存储里极其值钱,因为 CPU 介入越少,延迟越低、带宽越容易打满。
RDMA 的物理实现有三种主流路线:InfiniBand、RoCE(RDMA over Converged Ethernet)和 iWARP。InfiniBand 是专用网络,性能最好,但交换机、网卡都贵得离谱,而且和普通以太网生态不互通。iWARP 走 TCP/IP 协议栈,兼容性最好,但 TCP 的重传和确认机制拖累了极致性能。RoCE v2 是一种让 RDMA 直接跑在以太网协议栈上的方案,它把 RDMA 报文封装进 UDP/IP 里,既能享受以太网的普及度,又能拿到接近 InfiniBand 的性能。对 FPGA 来说,RoCE v2 是唯一能自己动手把协议栈完全打开的路线,因为报文格式、队列管理、重传逻辑都是你在 RTL 里面写的,可控性在三种方案里是最高的。
1.2 搭建这个测试环境的真实价值
很多人会问,测试 100G RDMA,直接买一张 Mellanox 网卡插上不就完了,为什么非要折腾 FPGA?答案不是性能问题,而是可控性和可定制性。商用 RDMA 网卡是一颗封闭的黑盒,你能操作的只有驱动和配置接口,协议细节、拥塞控制、流控策略都是厂商固件说了算。FPGA 方案把这些全打开了,你可以插入自己的拥塞控制算法、修改重传策略、观测每一层协议的时序统计,甚至把 RDMA 引擎和你的业务逻辑集成到同一个芯片里。
这套测试环境真正解决的是下面这些实际需求:一是验证你自己写的 RoCE v2 协议栈功能对不对、性能能达到什么水平;二是在没有完整 InfiniBand 交换机的情况下,用最普通的以太网线缆和设备互通;三是给存储或者 AI 加速项目提供一个可以压测的 RDMA 数据通路。整个系统可以拆成两个大块:FPGA 板卡负责把 RDMA 报文从 PCIe 搬到光口,PC 端用支持 RoCE 的 100G 网卡作为对端,两边通过光模块直连或者经过一层交换机互相对打。
1.3 FPGA 方案和专用网卡的取舍
在动手之前,有必要把 FPGA 和专用网卡的差异摊开看,这样才能理解后面每一步配置的出发点。
| 维度 | 商用 RDMA 网卡 | FPGA 方案 |
|---|---|---|
| 协议栈 | 固件固定,无法修改 | RTL 完全开放,可深度定制 |
| 延迟 | 极低,但受固件处理路径限制 | 可自行优化,通常 1~3 微秒量级 |
| 开发周期 | 即插即用,只需软件配置 | 数月到一年不等,取决于经验 |
| 功耗与成本 | 单卡功耗压力小,但功能锁定 | 单板功耗高,但功能可无限扩展 |
| 适用范围 | 标准的存储、计算集群 | 协议研究、网络卸载、异构加速 |
我个人的建议是:如果你只想要一个“能用的 RDMA 网络”,直接上商用卡;如果你想研究协议、做定制化网络设备或者把 RDMA 能力嵌入自己的加速卡中,FPGA 路径值得投入。下面的实操内容全部基于 FPGA 路径。
2. 硬件平台与 Vivado 工程搭建的关键选择
2.1 板卡、光模块、线缆怎么选
FPGA 板卡要跑 100G RDMA,芯片至少要到 UltraScale+ 这一代,因为 100G 需要的 GTY/GTM 高速收发器、PCIe Gen3x16 硬核、和足够规模的逻辑资源,在 7 系列上会比较吃力。我这边测试用到的是 VCU118,核心芯片是 VU9P,正好覆盖 PCIe Gen3x16 和 100G 光口,是很多原型验证板的选择。如果你手头是 Alveo U200/U250 这类自带 PCIe 和 QSFP28 的加速卡,也可以走通同样的流程,只是 Alveo 卡已经有成熟的 XDMA 骨架,你只需要加 RDMA 引擎和以太网 MAC 部分。
光模块选择上,最常见的组合是 QSFP28 SR4 光模块加 MPO 光纤,或者直接上 QSFP28 DAC 高速铜缆。这里有个非常实际的建议:如果两台设备在同一个机柜里、距离不超过三米,优先用 DAC 铜缆而不是光模块。DAC 少了一对光电转换环节,出现光功率问题的概率低很多,调试阶段能省下大量时间。如果非要用光模块,一定注意发射端和接收端的标签方向,QSFP28 里面四个通道的 Tx/Rx 方向不能搞反,否则链路状态会一直在 Training 状态里转圈。
2.2 时钟与复位规划的完整思路
高速链路设计,时钟是最容易翻车的地方。100G 以太网的线速率是对应四个 25.78125Gbps 的通道,所以 GT reference clock 必须给 156.25MHz,这个时钟源通常来自板上的可编程时钟芯片,在 Vivado 工程里通过 IBUFDS_GTE4 原语进来。PCIe 部分则是单独一路 100MHz 参考时钟,由主板 PCIe 插槽提供。我见过很多人把这两路时钟混为一谈,结果 100G 链路 Ping 不通,折腾半天发现是 GT 参考时钟频率不对。
复位时序这里值得多说两句。CMAC IP 的复位顺序有严格要求,一般是先复位 GT 和 PCS,等 rx_pma_ready、tx_pma_ready 拉高之后再释放 MAC 主复位,最后释放应用侧复位。在代码里我习惯把复位做成异步复位同步释放,避免复位信号撤掉那一刻出现亚稳态,这也是 FPGA 开发里“对 reg 打几拍”这个经典做法的重要场景。RDMA 引擎和 DMA 部分的复位则建议和 PCIe 的 perst# 信号关联起来,保证主机上下电、驱动重载时 FPGA 侧能跟着做一次干净的重置。
2.3 时钟与复位规划速查表
| 时钟/复位 | 来源 | 频率/极性 | 用途 | 关键注意事项 |
|---|---|---|---|---|
| GT 参考时钟 | 板上可编程时钟 | 156.25MHz 差分 | 100G 收发器 | 必须干净,抖动超标会直接导致误码 |
| PCIe 参考时钟 | 主板 PCIe 槽 | 100MHz 差分 | PCIe 硬核与 DMA | 通常由主板提供,无需额外生成 |
| 系统时钟 | 板上晶振 | 100MHz 单端 | 应用逻辑、寄存器 | 与 GT参考时钟分开,避免干扰 |
| PCIe perst# | 主板复位信号 | 低有效 | 全链路复位 | 连接 FPGA 全局复位逻辑 |
3. 100G MAC 与 PCIe IP 配置,最容易踩坑的两个 IP
3.1 100G CMAC IP 配置要点
Vivado 里用于 100G 以太网的 IP 是 CMAC(100G Ethernet Subsystem),它的核心角色是完成 MAC 层和 PCS/PMA 层的工作,对外连接 GTY 收发器,对内提供 AXI4-Stream 接口给上层协议。配置这个 IP 有几个关键点需要注意。
线速率默认是 103.125Gbps,对应四个通道各 25.78125Gbps。接口宽度如果选 512-bit 的 AXI4-Stream,用户时钟是 322.265625MHz;如果选 256-bit,用户时钟是 644.53125MHz。这个用户时钟会直接影响后面 RDMA 引擎的时序收敛,建议根据你 FPGA 上逻辑的紧张程度来选。我这边用 512-bit 接口,时序压力小,逻辑频率要求低,对设计的稳定性更有帮助。
Flow Control 选项在测试阶段建议先关闭。RoCE v2 在实际数据中心环境里依赖 PFC(Priority Flow Control)实现无损网络,但那是整个网络交换机都要配合的事情。如果只是两台设备直连测试,先在 IP 内部关掉 flow control,后面有需要再手动配置 PFC。还有一个重点是“Include Shared Logic”选项。CMAC 的 GT 参考时钟、复位逻辑可以选择放在 IP 内部还是外部。我的习惯是如果整个工程只用一个 100G MAC,直接勾选在 core 内部,配置简单;如果工程里未来会加第二个 100G MAC,或者要和别的 GT 共享参考时钟,选 external 并把 shared logic 放在顶层,这样多个 core 能共用一套 GT 参考时钟和复位控制。
3.2 PCIe IP 与地址空间规划
PCIe 侧我用的是 XDMA(DMA/Bridge Subsystem for PCIe),它同时提供了 PCIe 硬核和 DMA 引擎,RDMA 引擎的寄存器可以通过 AXI-Lite 接口映射到主机地址空间,数据通路则走 AXI4 接口。XDMA 配置里最关键的是 DMA 接口的选择,有两种模式,AXI Memory Mapped 和 AXI Streaming。
AXI Memory Mapped 模式对 RDMA 来说更直接:主机侧送出来的是一个带地址的读请求或者写请求,FPGA 内部可以通过地址译码判断这是发给 QP 上下文、CQ 还是一个普通的数据缓冲。AXI Streaming 模式则适合数据流式的场景,但 RDMA 本质上需要随机访问内存地址,所以 Memory Mapped 更合适。BAR 空间方面,我通常把 BAR0 定义为 64KB 的 AXI-Lite 寄存器空间,用于 RDMA 状态机控制;BAR2 作为大块的数据缓冲映射,用于 DMA 访问。中断用 MSI-X,RDMA 的完成事件通过 MSI-X 上报给主机驱动,中断数量按队列对数来分配。
3.3 从 bit 文件到链路 UP 的关键步骤
配置好两个 IP 之后,完整的流程是:综合、布局布线、生成 bit 文件、下载到板卡、验证链路状态。这里面最容易出问题的环节是链路状态检查。bit 文件下载完成后,先不要急着跑协议,先在 Vivado 里把 CMAC 的状态信号拉出来看,rx_pcs_ready、tx_pcs_ready 必须是高电平,rx_pma_ready_done 也要置位。如果这些信号不齐,问题基本锁定在光模块、线缆或者 GT 参考时钟上。
这时候可以用 IBERT 做一次高速收发器的完整性测试,验证 GT 通道的误码率。IBERT 是 Xilinx 自带的高速串行链路测试工具,它能绕过 MAC 和所有上层逻辑,直接在 GT 通道里打伪随机码并统计误码。我每次换光模块或者新板卡到货后的第一件事就是跑 IBERT,链路干净了再进上层调试,这个顺序能帮你把“物理层问题”和“协议层问题”彻底分开。
4. RDMA 协议栈与数据通路联调
4.1 QP 状态机与握手过程
RDMA 的核心抽象是 QP(Queue Pair),一个 QP 由一条发送队列和一条接收队列组成。在 RoCE v2 实现里,QP 的状态机有四个主要状态:RESET、INIT、RTR(Ready to Receive)和 RTS(Ready to Send)。连接的建立过程就是在两端之间交换 QP 编号、GID、LID 等连接信息,然后让两端各自把状态推进到 RTS。
FPGA 作为 RDMA 设备时,必须处理一个角色问题:FPGA 侧是主动发起连接,还是被动接受连接。测试阶段我更推荐让 PC 端作为主动方,用 perftest 工具发起连接请求,FPGA 侧作为被动方监听。原因很简单,perftest 的握手逻辑是现成的,FPGA 只需要正确响应,这样能减少 FPGA 侧的协议代码复杂度。等到基本流程跑通,再反过来让 FPGA 主动发起连接,验证你的协议栈在主动路径上也没问题。QP 切换到 RTR 和 RTS 状态时,FPGA 侧需要完成几件事:初始化发送和接收队列、配置好 DMA 描述符、把指向接收缓冲区的地址写入相关寄存器。任何一步漏掉,都会表现为对方发数据发不过来,或者收到数据但没人接手。
4.2 回环自测:内部回环与外部回环
在正式和 PC 对接之前,一定要做回环自测。回环有两种:内部回环和外部回环。内部回环是直接在 CMAC IP 内部把发送数据引回接收通路,不经过物理光口,用来验证 MAC 和上层协议逻辑。外部回环是插一个 QSFP28 环回模块,或者在 DAC 线缆另一头接一个光模块环回环,让数据真正走一遍收发器。
实际操作时,我的习惯是分两级验证:先让 RDMA 引擎在内部回环模式下发送数据,确认 DMA 描述符、QP 状态、报文封装这几层逻辑正确,然后再用外部回环验证 GT 收发器和光模块链路。这里有个经验之谈:如果内部回环能通但外部回环不通,优先怀疑 GT 通道的极性、参考时钟的分配,而不是怀疑上层协议;如果外部回环能通但接 PC 不通,那问题就在 PC 端配置或者线缆连接上。回环测试通过之后,才能保证整个数据流从主机内存到 FPGA、再从 FPGA 光口出去的路径是完整的。
4.3 Go-Back-N 重传机制的现实影响
FPGA 实现 RoCE v2 时,重传机制大多是 Go-Back-N,原因很简单:实现复杂度低,不需要像选择性重传那样维护复杂的缓存管理和按序重排逻辑。Go-Back-N 的代价是丢一个包,发送端会把从这个包之后的所有包全部重传一遍,瞬时带宽会掉得很明显。
这套行为放到测试环境里,意味着你必须控制两条硬条件:一是物理层的误码率要足够低,二是网络不能有拥塞丢包。直连场景下,只要光模块和线缆没问题,满足这两条基本没压力。但如果你后续接入了交换机,或者开启了 PFC 却没有正确配置优先级映射,丢包事件引爆 Go-Back-N 重传之后,你看到的带宽数字会非常难看。所以我建议在测试日志里把重传计数加进去,一旦出现带宽异常,先看重传次数,这是定位问题最快的手段。
5. PC 端互联配置与带宽测试
5.1 Linux 侧配置 100G 网卡与 IP 地址
PC 端我用的是 Mellanox ConnectX-5 双口 100G 网卡,驱动是 mlx5_core。系统起来之后,先用 lspci 确认网卡被识别,然后加载驱动,接着配置 IP。直连拓扑比较推荐:FPGA 和 PC 网卡各自配一个静态 IP 地址,放在同一个子网里。比如 FPGA 侧配 10.0.0.1/24,PC 侧配 10.0.0.2/24。
具体在 Linux 下的操作是下面几条命令:
# 确认网卡接口名,通常是 enp3s0f0 或类似 ip link show # 配置 IP 并启动接口 ip addr add 10.0.0.2/24 dev enp3s0f0 ip link set enp3s0f0 up # 确认 RDMA 设备已经注册 rdma link show有一个细节值得注意:RoCE v2 是承载在 UDP/IP 上的,先用普通 ping 验证基本的 IP 连通性,能省掉后面一大部分排查时间。但 ping 之前要把 MTU 调整好,100G 网络建议把 MTU 改成 9000,避免在 1500 字节默认 MTU 下出现分片问问题。分片问题在 RDMA 里尤其麻烦,RDMA 报文通常很大,一旦触发 IP 分片,不少网卡和 FPGA 实现并不支持分片重组,直接丢包。
5.2 RDMA 子网与 GID 配置检查
RoCE v2 报文的寻址依赖 GID,而 GID 是从网卡的 IP 地址派生出来的。Mellanox 网卡驱动加载之后会自动给每个端口生成 GID,但 GID index 不一定是 0,你需要确认实际使用的 GID index 是多少。检查方法:
# 查看当前端口所有 GID show_gids # 或者用 rdma 命令 rdma link show mlx5_0FPGA 侧也要配置相应的 IP 地址和 GID index。这里最常见的坑是:PC 网卡配置了多个 IP,驱动自动生成多个 GID,但 RDMA 客户端工具默认用了错误的 GID index,导致 QP 握手失败。解决办法是显式指定 GID index,比如 perftest 里的 -x 参数。FPGA 侧的 GID 同样要和你写入的源 IP 对应起来,两边 GID 对不上,连 QP 状态都推进不到 RTS。
5.3 跑通 perftest 并解读结果
perftest 是 RDMA 开发中最常用的带宽/延迟测试工具包,它提供 ib_write_bw、ib_read_bw、ib_send_bw 等一系列命令。测试之前,PC 端先启动服务端(通常用 -s 参数监听),再在另一台机器启动客户端发起流量。由于这里是 PC 和 FPGA 互联,服务端和客户端的角色都只有 PC 这一侧,FPGA 扮演的角色由你写的协议栈决定。
# 服务端,等待连接 ib_write_bw -d mlx5_0 -x 3 --report_gbits # 客户端,连到 FPGA 侧,假设 FPG A 的 IP 是 10.0.0.1 ib_write_bw -d mlx5_0 -x 3 --report_gbits 10.0.0.1输出结果里重点看两个数字:BW(带宽)和 Latency(延迟)。直连 100G 网络的预期带宽应该在 90Gbps 以上,因为 Ethernet 帧头、前导码、流控开销会吃掉一部分理论带宽。延迟方面,小包(比如 2 字节)的写操作延迟应该在几微秒量级,如果延迟飙升到几百微秒,大概率是某个中间环节在做软件轮询,或者 DMA 描述符回收不及时。
我也遇到过一种情况:ib_write_bw 显示带宽只有 20Gbps,不是网络链路问题,而是 PCIe 链路没跑满。检查 PC 端的 lspci -vvv,确认当前 PCIe 链路工作在 Gen3 x16 还是 Gen3 x8。FPGA 板卡插在 x16 插槽里不等于实际协商出 x16,我见过板卡插进 x8 插槽,物理接口看着是 x16 的,实际协商结果却大打折扣。
6. 常见问题与排错实录
6.1 链路起不来的五个原因
100G 链路起不来是出现频率最高的故障,也是最耗费时间的。我遇到的绝大多数情况可以归纳为五类:GT 参考时钟没给对、光模块方向插反、DAC 线缆与模块速率不匹配、CMAC 的复位时序不满足、PCIe 链路未协商成功。
参考时钟问题最常见的表现是 GT 的 txpma_ready 一直拉不高,用 ILA 抓信号会发现 PLL 处于 unlock 状态。光模块方向问题在 MPO 光纤连接时特别常见,MPO 光纤带极性,换一根或者翻一个方向往往就解决了。DAC 线缆的问题出现在早期 25G 和 100G 混用的场景,注意看线缆标签上的 rate 标识。PCIe 链路未协商成功的排查要去 BIOS 和 Linux 层确认,FPGA 侧则检查 perst# 复位是否正常释放。
强烈建议每排查一步都做记录,把信号名、状态值、时间点写下来。高速链路问题经常互相嵌套,有记录才能回溯。
6.2 PCIe 枚举失败的排查
FPGA 作为 PCIe 设备时,如果主机 BIOS 里根本枚举不到设备,先别急着怀疑 PCIe IP 配置。最应该查的是复位信号:perst# 有没有拉高、FPGA 的 PCIe 硬核有没有成功初始化。Xilinx 的 PCIe IP 初始化需要几个条件,参考时钟稳定、perst# 释放、配置逻辑加载完毕,任何一个条件不满足都会让枚举失败。
另一个容易被忽视的点是 PCIe 差分对极性。在布局布线时,如果原理图把差分对的 P/N 接反了,PCIe 链路也能协商成功,但稳定性很差,容易在高负载时掉链路。Xilinx 的 PCIe IP 内部自带极性检测和反转功能,但有时候需要手动打开相关选项。遇到高负载掉链路的诡异问题,先怀疑极性。
6.3 常见问题速查表
| 现象 | 优先检查项 | 处理思路 |
|---|---|---|
| link 一直 Training | GT 参考时钟、光模块方向 | 先查 156.25MHz 是否上板,再换 DAC 或换光纤方向 |
| MAC 能 up 但 ping 不通 | MAC 地址配置、ARP 处理 | 确认 FPGA 侧 MAC 和 IP 匹配,PC 侧 arp 表是否有响应 |
| QP 连接失败 | GID index、QP 信息交换 | 用 -x 指定 GID index,核对交换的 QPN、LID 字段 |
| RDMA 带宽远低于预期 | PCIe 链路速率、描述符深度 | lspci -vvv 检查 PCIe 协商状态,加大 DMA 描述符深度 |
| 传输过程中大量重传 | PFC 配置、物理层误码 | 直连不需 PFC,先跑 IBERT 排除物理层误码 |
RDMA 握手失败时,PC 端 dmesg 里会出现 mlx5 相关的报错信息,大部分时候错误信息会直接告诉你是 QP 状态不匹配还是 GID 配置不对。FPGA 侧则要靠逻辑分析仪抓 QP 状态机的跳转条件,定位是哪个字段没对上。这个联动排查的过程很锻炼人,但逻辑清晰的话,半天内能解决大部分问题。
6.4 一个小技巧:用 10G 网卡做预联调
再分享一个很实用的技巧。如果你觉得第一次就把 100G 物理链路调到干净状态有压力,可以先在 PC 端插一张 10G 网卡,配合 FPGA 板卡上的 SFP+ 口做预联调。用 10G 链路把 RDMA 协议栈的 QP 建立、报文封装、DMA 通路全部验证一遍,再切换到 100G。10G 链路的物理层问题少很多,调试速度会快得多。协议栈逻辑是共用的,切换成 100G 之后,只剩 GT 通道和时序需要重新验证。这个思路在某些项目里能省下整周的调试时间。
这套环境的下一步扩展
把上面这套基础环境完整跑通之后,你会发现一个可用的 100G RDMA 测试平台已经立住了。我最后想说的是,这个平台的价值不止于“能 ping 通”或者“能跑出带宽”,它本质上是一个可编程的高性能网络研究底座。后续可以扩展的方向包括:往 RDMA 引擎里加入自定义的拥塞控制算法,对比默认策略在不同流量模型下的表现;把 DMA 路径从单队列扩展成多队列,验证 QoS 优先级调度的效果;甚至可以把 FPGA 的 100G 网络接口和自研存储控制器集成起来,做一个真正的 NVMe over RDMA 卸载方案。
我个人在实际操作中的体会是,搭建这套环境的过程比结果更有价值。你会被迫把 PCIe、DMA、以太网 MAC、RDMA 协议、Linux 网络栈这些知识彻底串起来,很多之前只看文档不理解的点,都会在一次次的合不上时序、链路起不来、带宽上不去中被真正想明白。如果这篇文章能帮你少跑几次实验室、少熬几个通宵,那就值了。