
1. 为什么要在 FPGA 上折腾 RoCE v2做高性能网络的人都有一个共同的痛CPU 越来越快但网络协议栈的处理开销始终是瓶颈。一个 100Gbps 的链路如果走传统 TCP/IP 内核协议栈光是数据拷贝和中断处理就能把好几个物理核吃满应用层真正拿到的有效带宽可能连一半都不到。RoCE v2RDMA over Converged Ethernet version 2就是冲着这个痛点来的——它把 RDMA 的内存语义直接架在 UDP/IP 之上让网卡硬件完成报文封装、可靠传输和内存直接访问CPU 基本不参与数据搬运。那为什么要在 FPGA 上做这件事原因很直接Xilinx 的 ERNIC IP 就是一颗可以烧进 FPGA 的“RDMA 网卡内核”。你不需要去买昂贵的商用 RDMA 网卡只要手里有一块带高速收发器的 Xilinx 卡比如 Alveo 系列或者带 CMAC 的 UltraScale 器件配合 ERNIC IP就能自己搭出一个支持 RoCE v2 的 100G 网络加速端点。这对做金融低延迟、分布式存储、HPC 互联、甚至自研智能网卡的团队来说价值非常大——你拿到的是完全可控的硬件数据通路而不是一个黑盒。这篇文章面向的是有 FPGA 基础、想切入高速网络加速的开发者。我会围绕 Xilinx ERNIC IP 和 PG332 文档把 RoCE v2 的硬件实现路径拆开讲清楚从整体架构怎么搭、IP 怎么配、关键接口怎么接到实际调试中会踩的坑。PG332 是 Xilinx 官方给 ERNIC 的产品指南文档号 PG332很多人第一次翻会觉得它写得“点到为止”很多细节要自己补我下面会把这些补全。2. ERNIC IP 的整体架构与设计思路拆解2.1 ERNIC 到底在 FPGA 里扮演什么角色先把概念理清楚。ERNIC 全称是 Ethernet RDMA NIC它不是一颗独立的芯片而是一套可以综合进 FPGA 的 IP 核。它的定位是在 FPGA 内部实现一个符合 RoCE v2 规范的 RDMA 端点对外通过以太网 MAC 收发报文对内通过 AXI 接口和用户逻辑或主机交互。你可以把它理解成“把一张 RDMA 网卡塞进了 FPGA 的 fabric 里”。传统的 RDMA 网卡比如那些商用卡内部也是类似的逻辑一个以太网 MAC/PCS、一个报文解析与封装引擎、一套队列管理QP、一套内存翻译MR/MTT机制。ERNIC 把这些东西做成了可配置的 IP你负责提供 MAC、时钟、内存和上层驱动。从 PG332 的描述看ERNIC 的核心模块大致分几块以太网接口层对接 CMAC 或其它 MAC、RoCE v2 报文处理引擎负责 BTH、RETH 等头部封装解析、传输层状态机处理 QP 状态、序列号、ACK/NAK、DMA 引擎读写主机或 DDR 内存、以及配置与管理接口通过 AXI-Lite 访问寄存器。这几块协同工作才能完成一次完整的 RDMA 写或读操作。2.2 为什么选 ERNIC 而不是自己从零写有人会问RoCE v2 协议我也懂为什么不自己用 Verilog 撸一个答案在于工程量。RoCE v2 看起来只是“UDP 里塞个 BTH”但真正难的是可靠传输那一套序列号管理、重传、拥塞控制、QP 状态迁移、内存保护、乱序处理。这些逻辑的状态机极其复杂自己写一版能跑通的没个一年半载下不来而且很难保证和标准协议栈互通。ERNIC 的价值就在于它把这些“脏活累活”都封装好了而且经过了 Xilinx 的验证能和主流 RDMA 生态比如标准 verbs 接口的驱动对接。你只需要关注怎么把 MAC 接上、怎么分配内存、怎么配 QP。这是典型的“站在巨人肩膀上”的思路。当然代价是你要接受它的接口约束和资源占用但对绝大多数项目来说这个交换是划算的。2.3 一个典型的系统框图长什么样我实际搭过的系统大致是这样分层的最底层是 FPGA 的 GT 收发器比如 GTY往外接光模块GT 之上是 CMAC100G 以太网 MAC负责 PCS/PMA 和 MAC 层CMAC 的 AXI-Stream 接口接到 ERNIC 的以太网侧ERNIC 的内存侧通过 AXI 接到 DDR 控制器或者直接接到用户逻辑的 BRAM控制侧通过 AXI-Lite 接到 MicroBlaze 或者主机的 PCIe BAR。这里有个关键点ERNIC 对内存的访问是走 AXI 的所以你的内存子系统带宽必须够。100Gbps 线速下双向流量意味着内存侧要能扛住 200Gbps 以上的读写DDR4 一般够用但如果你用 BRAM 做小缓存就得仔细算容量和带宽了。这个后面讲参数计算时会细说。3. PG332 文档解读与核心配置要点3.1 PG332 里最该先看的几节PG332 篇幅不短但真正决定你能不能跑起来的其实就那么几节。我的建议是先看“Overview”把架构图吃透再看“Core Interfaces”搞清楚每个 AXI 接口的方向和位宽然后重点啃“Configuration”那一章最后对照“Example Design”看官方怎么接的。很多人一上来就翻寄存器手册结果被几百个寄存器劝退。其实寄存器是最后调的时候才需要的前期你只要把 IP 配好、接口接对大部分功能就能跑。PG332 的寄存器章节更像是“字典”用到哪个查哪个不用通读。3.2 关键配置参数怎么选ERNIC 的配置里有几个参数直接决定资源占用和功能配置项含义选型建议线速支持 10G/25G/40G/100G按你的 GT 和 CMAC 能力选100G 资源占用最大QP 数量支持的队列对数量按并发连接数定一般 256~1024 够用越多 BRAM 占用越高MTU最大传输单元RoCE v2 常用 1024 或 4096要和交换机一致内存接口位宽AXI 数据位宽512bit 是常见选择匹配 DDR 效率是否使能 DCQCN拥塞控制无损网络建议开普通环境可关这里重点说 MTU。RoCE v2 的 MTU 必须端到端一致包括交换机。如果你 FPGA 侧配 4096交换机配 1024那大包会被分片或者丢弃性能直接崩。我踩过一次坑实验室交换机默认 MTU 1500我 IP 里配了 4096结果小包能通、大包全丢查了两天才发现是 MTU 不匹配。所以配之前先确认整条链路的 MTU。3.3 时钟与复位设计ERNIC 涉及多个时钟域GT 的收发时钟、MAC 的 AXI-Stream 时钟、内存侧的 AXI 时钟、控制侧的 AXI-Lite 时钟。这些时钟之间的跨域处理是 IP 内部做的但你要保证每个时钟的频率和相位关系符合 PG332 的要求。复位方面ERNIC 有一个全局复位和若干分模块复位。我的经验是上电后先复位 GT 和 CMAC等链路 up看 CMAC 的 status 寄存器再释放 ERNIC 的复位。如果顺序反了ERNIC 可能在链路没起来的时候就开始发报文导致状态机卡死。这个顺序在 PG332 里没有特别强调但实际调试中很关键。4. RoCE v2 报文处理与实操实现4.1 一次 RDMA 写操作的完整流程要理解 ERNIC 怎么工作最好的办法是跟一遍 RDMA Write 的流程。假设主机 A 要往主机 B 的内存里写数据主机 A 的驱动把数据准备好告诉 ERNIC 的 QP“把这批数据发到 B 的某个虚拟地址”。ERNIC 从内存读出数据封装成 RoCE v2 报文外层是以太网头 IP 头 UDP 头目的端口 4791内层是 BTHBase Transport Header RETHRDMA Extended Transport Header 数据。报文经 CMAC 发出穿过交换机到达主机 B。主机 B 的 ERNIC 收到报文解析 BTH 拿到 QP 号解析 RETH 拿到虚拟地址通过内存翻译表MTT把虚拟地址转成物理地址直接把数据 DMA 进内存。B 回一个 ACKA 收到后确认完成。整个过程 CPU 只在第 1 步参与后面全是硬件干的。这就是 RDMA 的威力。ERNIC 在 FPGA 里实现的就是第 2 步和第 4 步的硬件逻辑。4.2 报文封装的硬件实现细节ERNIC 内部对报文的封装是流水线式的。以太网头、IP 头、UDP 头这些字段大部分是固定的除了 checksum 和长度可以预先算好放在寄存器里。BTH 和 RETH 则要根据每次操作动态生成。这里有个细节UDP checksum。RoCE v2 要求 UDP checksum 有效但很多实现为了性能会把它算成 0IPv4 下允许。ERNIC 支持硬件计算 checksum但会消耗一些逻辑资源。如果你的网络环境对 checksum 校验严格就必须开如果是可控的内网可以关掉省资源。我一般在内网测试时关掉上生产再开。另一个细节是 PSNPacket Sequence Number。每个 QP 的每个报文都有递增的 PSN接收侧靠它检测丢包和乱序。ERNIC 内部有 PSN 生成和校验逻辑你不需要手动管但要保证 QP 初始化时 PSN 从 0 开始且两端一致。4.3 内存翻译表MTT的配置RDMA 的核心之一是“零拷贝”而零拷贝的前提是网卡能直接把数据写进应用的内存。这需要一张虚拟地址到物理地址的映射表也就是 MTT。ERNIC 通过 MTT 把 RETH 里的虚拟地址翻译成物理地址然后发起 DMA。MTT 的配置是通过驱动完成的。在 FPGA 场景下如果你没有标准驱动就需要自己写一段逻辑来填 MTT。PG332 里给了 MTT 的格式每个 entry 包含物理页基址和权限位。我的做法是在初始化阶段把应用要用的内存区域按页比如 4KB注册进 MTT每个页一个 entry。这样 ERNIC 收到报文后用虚拟地址的高位索引 MTT低位作为页内偏移就能算出物理地址。这里要注意 MTT 的大小。如果你注册 1GB 内存按 4KB 页算就是 26 万个 entry每个 entry 假设 8 字节就是 2MB 的 MTT。这块内存要放在 ERNIC 能快速访问的地方一般放 DDR 里但访问延迟会影响性能。所以有些实现会用大页2MB把 entry 数量降下来。5. 实操过程从零搭一个 ERNIC 工程5.1 环境准备与 IP 获取先说环境。你需要 Vivado建议 2021.2 或更新老版本对 ERNIC 支持不全一块带 GTY 的 Xilinx 开发板Alveo U250/U280 或者自制的 UltraScale 板卡以及一个支持 RoCE v2 的交换机或者两台机器直连。ERNIC IP 不是免费随便下的它属于 Xilinx 的以太网 IP 家族需要 license。如果你有 Vivado 的完整授权一般能在 IP Catalog 里搜到。搜不到的话去 Xilinx 官网下载 PG332 对应的 IP 包手动加到 IP repository 里。5.2 搭建 Block Design我习惯用 Block Design 来搭直观且不容易出错。步骤大致是新建工程选对器件型号。添加 CMAC IP配置成 100G接口选 AXI-Stream。添加 ERNIC IP线速选 100GQP 数量按需MTU 设 1024。添加 DDR4 控制器配置成 AXI 接口位宽 512bit。用 AXI-Stream 把 CMAC 和 ERNIC 的以太网侧连起来。用 AXI 把 ERNIC 的内存侧接到 DDR 控制器。加一个 MicroBlaze 或者 Zynq PS通过 AXI-Lite 接 ERNIC 的控制接口。连时钟和复位注意前面说的复位顺序。这里有个容易错的地方CMAC 和 ERNIC 之间的 AXI-Stream 位宽要匹配。CMAC 100G 一般是 512bit 322MHz 左右ERNIC 的以太网侧也是类似位宽但具体要查 PG332 的接口表。如果位宽不一致要加位宽转换器但转换器会引入延迟尽量让两边一致。5.3 约束文件与时序收敛高速设计里约束是命根子。ERNIC 涉及 100G 的 AXI-Stream时钟频率高时序很紧。你需要给 GT 的参考时钟加 create_clock。给 CMAC 和 ERNIC 的 AXI-Stream 时钟加 create_clock并设置正确的频率。跨时钟域的信号加 set_false_path 或 set_max_delay具体看 PG332 的建议。内存侧 AXI 加 output delay 约束匹配 DDR 控制器的要求。我实测下来ERNIC 在 UltraScale 上跑 100G时序收敛的难点主要在 AXI-Stream 那一段。如果时序过不去可以试着降低一点频率比如从 322MHz 降到 300MHz或者优化布局约束。Vivado 的 implementation strategy 选 Performance_Explore 通常有帮助。5.4 上板调试与链路建立综合实现生成 bitstream 后下载到板子。第一步是看 CMAC 的链路状态读 CMAC 的 status 寄存器确认 link up 且速率协商正确。如果 link 没起来先查光模块和光纤再看 GT 的复位和时钟。链路 up 之后配置 ERNIC 的 QP。通过 AXI-Lite 写寄存器设置 QP 状态为 INIT配置目的 IP、MAC、QPN、PSN。然后迁移到 RTRReady to Receive再迁移到 RTSReady to Send。每一步的状态迁移都有对应的寄存器操作PG332 里有详细说明。状态迁移完成后就可以试着发一个 RDMA Write 了。我一般先用一个小数据量比如 64 字节测试用 ILA 抓 AXI-Stream 上的报文看 BTH 和 RETH 是否正确。确认无误后再加大数据量测带宽。6. 常见问题与排查技巧实录6.1 链路起不来怎么办这是最常见的问题。排查顺序看 GT 的复位是否释放参考时钟是否有。看光模块是否被识别TX_DISABLE 是否拉低。看 CMAC 的 link status 寄存器如果是 0检查 PCS 层。如果两台机器直连确认一端是 master 一端是 slave或者都配成 auto-negotiation。我遇到过一次光模块是好的但 GT 的参考时钟频率设错了应该是 156.25MHz我设成了 125MHz结果链路死活起不来。所以时钟一定要对着板卡手册确认。6.2 报文发出去了但收不到如果链路 up 了报文也发了但对端收不到先查这几个目的 MAC 和 IP 是否配对。UDP 目的端口是否是 4791RoCE v2 的标准端口。交换机是否开了 RoCE v2 支持有些交换机默认不开。MTU 是否一致。我踩过的坑是交换机没配 PFCPriority Flow Control。RoCE v2 在无损网络里依赖 PFC 来保证不丢包如果交换机没开 PFC大流量下丢包会导致重传性能暴跌甚至连接断开。所以生产环境一定要配 PFC 和 ECN。6.3 性能上不去怎么调如果功能通了但带宽只有几 Gbps排查方向现象可能原因排查方法带宽低且 CPU 占用高走了内核协议栈确认用的是 RDMA verbs 接口带宽低但 CPU 低PCIe 或内存带宽瓶颈测 DDR 带宽和 PCIe 带宽带宽波动大拥塞控制没开开 DCQCN配 ECN小包性能差报文处理延迟高优化流水线减少跨时钟域我实测过一个案例DDR 带宽够但 AXI 位宽只有 256bit结果内存侧成了瓶颈100G 只能跑到 60G。后来把 AXI 位宽改成 512bit立刻跑满。所以内存接口位宽一定要算够。6.4 独家避坑清单复位顺序先 GT/CMAC后 ERNIC反了会卡死。MTU 一致端到端都要确认包括交换机。PFC/ECN无损网络必配否则大流量丢包。AXI 位宽内存侧至少 512bit否则带宽不够。时钟约束AXI-Stream 时钟要仔细约束时序过不去先降频。MTT 大小大页能减少 entry 数量降低访问延迟。UDP checksum内网可关生产要开。7. 一些实操心得与扩展思路做 ERNIC 这段时间我最大的体会是FPGA 网络加速的门槛不在写 RTL而在“系统集成”。ERNIC IP 本身很成熟但你把它和 CMAC、DDR、PCIe、驱动串起来中间任何一个环节出问题现象都是“不通”或“慢”排查起来很费劲。所以我的建议是先用官方 Example Design 跑通再一点点改成自己的架构不要一上来就大改。另外ERNIC 只是 RoCE v2 的一个实现如果你要做更定制化的东西比如在 RDMA 基础上加自己的协议头或者做智能网卡的 offload可以在 ERNIC 的用户接口上挂自己的逻辑。PG332 里提到了用户可扩展的接口这块空间很大值得深挖。最后分享一个小技巧调试 RDMA 时Wireshark 是神器。只要交换机支持端口镜像把流量镜像出来用 Wireshark 解 RoCE v2 报文BTH、RETH、PSN 一目了然。比对着 ILA 抓波形猜要快得多。我很多问题都是靠 Wireshark 定位的强烈推荐。