
RoCE v2 这几年在数据中心和存储网络里越来越热原因很直接它把 RDMA 的内存直接访问能力搬到了标准以太网之上不用再为 InfiniBand 单独铺一套网络。但真到落地阶段很多做 FPGA 的工程师会卡在同一个地方——协议栈怎么落到硬件上尤其是 Xilinx 的 ERNIC IP 到底怎么用、PG332 文档里那些参数和接口该怎么理解。我自己前前后后折腾过几版基于 ERNIC 的 RoCE v2 加速方案从最初对着 PG332 一脸懵到后来能把 100G 链路跑通、把时延压到个位数微秒中间踩的坑不算少。这篇就把整个链路拆开讲清楚包括 ERNIC IP 的定位、PG332 的关键章节怎么读、RoCE v2 的报文处理逻辑、以及实际集成时那些文档里不会写的细节。1. 先搞清楚 ERNIC IP 到底解决什么问题1.1 从 CPU 做 RoCE 的瓶颈说起在纯软件方案里RoCE v2 的报文封装、ACK 处理、重传逻辑都跑在 CPU 上。用ibverbs或者厂商提供的用户态驱动确实能跑起来但问题在于每处理一个报文CPU 都要参与上下文切换、内存拷贝、doorbell 写寄存器。100G 线速下小包场景每秒上千万个报文CPU 根本扛不住时延抖动也大。我实测过一组数据用软件栈跑 100G RoCE64 字节小包场景下单核基本被打满端到端时延在 20 微秒以上而且抖动能到十几微秒。对于分布式存储、高频交易这类场景这个数字是不能接受的。ERNIC 的思路就是把整个 RoCE v2 的传输层逻辑下沉到 FPGA 里。CPU 只负责建链、下发工作请求WR剩下的报文组装、序列号管理、ACK 生成、重传判断全部由硬件完成。这样 CPU 占用率能降到个位数时延也能压到 3 到 5 微秒这个量级。1.2 ERNIC 在 Xilinx IP 体系里的位置Xilinx 的网络 IP 大致分几层最底层是 CMAC100G 以太网 MAC或者 GT 收发器往上是 PCS/PMA再往上是 MAC。ERNIC 是架在 MAC 之上的传输层 IP它不负责物理层和链路层只处理 RoCE v2 的传输层语义。这一点很关键很多人一开始以为 ERNIC 是个完整的网卡方案其实不是。它需要你外挂 CMAC 或者其它以太网 MACERNIC 通过 AXI-Stream 接口和 MAC 对接。PG332 文档里明确画了这张架构图ERNIC 的rx_axis和tx_axis直接连到 MAC 的对应接口上。ERNIC 内部主要包含几个模块QP 上下文管理、报文解析与生成、ACK 处理、重传缓冲、以及和主机侧对接的 AXI 接口。它支持 RC可靠连接和 UD不可靠数据报两种 QP 类型RoCE v2 场景下主要用 RC。1.3 为什么选 ERNIC 而不是自己写 RTL有人会问RoCE v2 协议也不算特别复杂为什么不自己写一套 RTL我的经验是自己写能跑通 demo但要做到稳定、可维护、支持多 QP、支持拥塞控制工作量远超预期。ERNIC 的价值在于它把那些容易出错的细节都封装好了PSN 回绕处理、ACK 合并、重传定时器、乱序处理、以及和 AXI 总线的握手时序。这些东西自己写光验证就要花几个月。而且 ERNIC 是经过 Xilinx 验证的在时序收敛和资源占用上有参考数据能省掉大量试错成本。当然 ERNIC 也不是万能的它有一些限制比如支持的 QP 数量、MTU 大小、以及不支持某些扩展特性。这些在 PG332 的 Feature Summary 章节里都有说明选型前一定要先看这一节。2. PG332 文档的正确打开方式2.1 别从第一页开始读PG332 有几百页如果从第一页顺着读很容易读到一半就迷失了。我的建议是分三步走第一步先看 Feature Summary 和 Architecture Overview搞清楚这个 IP 能做什么、不能做什么、内部有哪些模块。这一步大概花半小时目的是建立整体认知。第二步直接跳到 Port Descriptions 和 Clock and Reset 章节把接口信号和时钟域搞清楚。ERNIC 的接口比较多有 AXI-Lite 配置接口、AXI-Stream 数据接口、还有中断和状态信号。时钟域通常有core_clk、axis_clk、config_clk几个跨时钟域处理是集成时的重点。第三步再回头看 Design Flow 和 Example Design 章节结合 Vivado 里的 example design 一起看。Xilinx 的 example design 是理解 IP 用法最快的方式PG332 里的很多描述对照 example design 的代码一看就明白了。2.2 几个必须精读的章节PG332 里有几个章节是集成时的关键我列一下章节内容为什么重要Feature SummaryIP 支持的特性列表选型依据确认是否满足需求Architecture Overview内部模块框图理解数据流走向Port Descriptions所有接口信号定义顶层集成必看Clock and Reset时钟域和复位逻辑跨时钟域处理依据Register Space配置寄存器映射驱动开发依据Design Flow生成和配置流程Vivado 操作参考Example Design参考设计最快上手途径其中 Register Space 这一章特别容易被忽略但如果你要写驱动这一章就是字典。ERNIC 的 QP 上下文、门铃寄存器、中断状态寄存器都在这里定义。我建议把这一章的寄存器表格导出成 Excel开发时对照着看。2.3 文档里没写但实际会遇到的问题PG332 写得很规范但有些实际集成时的问题它不会提。比如ERNIC 的 AXI-Stream 接口在复位释放后的几个周期内tvalid可能会有毛刺需要在 MAC 侧做好过滤。配置接口的 AXI-Lite 写操作如果连续写同一个寄存器中间需要插入至少一个空闲周期否则可能丢配置。中断信号的极性在某些版本里是可配置的默认值和实际板级设计可能不一致需要确认。这些问题在文档里要么一笔带过要么完全没提但实际调试时都会遇到。我的做法是每次集成新版本 IP都先用 ILA 抓一遍复位释放后的接口时序确认没有异常再往下走。3. RoCE v2 报文在 ERNIC 里的处理链路3.1 发送路径从 WR 到以太网帧发送路径的起点是主机侧下发的工作请求WR。驱动把 WR 写到主机内存然后通过 doorbell 寄存器通知 ERNIC。ERNIC 收到 doorbell 后会去读取 WR 内容解析出目标 QP、操作类型、数据地址和长度。接下来 ERNIC 会从主机内存读取数据按照 RoCE v2 的格式组装报文。RoCE v2 的报文结构是以太网头 IP 头 UDP 头 IB BTH 扩展头 数据 ICRC。ERNIC 负责生成 BTH 和扩展头以及计算 ICRC。IP 头和 UDP 头通常由 MAC 或者外部的网络协议栈生成ERNIC 会把源目 IP、UDP 端口等信息通过侧带信号传给 MAC。这里有个细节RoCE v2 的 UDP 目的端口默认是 4791这个值在 ERNIC 里是可配置的。如果对端用了非标准端口需要在这里改。我遇到过对端设备用了自定义端口的情况当时排查了半天才发现是端口不匹配。报文组装完成后ERNIC 通过tx_axis接口把数据流推给 MAC。AXI-Stream 的握手遵循标准协议tvalid和tready同时为高时数据有效。ERNIC 在发送时会维护一个发送缓冲用于后续的重传。3.2 接收路径从以太网帧到完成队列接收路径反过来。MAC 收到以太网帧后通过rx_axis推给 ERNIC。ERNIC 首先解析 BTH提取 QP 号、PSN、操作码等信息。然后根据 QP 号查找 QP 上下文确认这个报文属于哪个连接。如果是预期内的报文ERNIC 会把数据写入主机内存并更新完成队列CQ。如果是乱序报文ERNIC 会先缓存等缺失的报文到达后再按序处理。如果是重复报文直接丢弃并发送 ACK。接收路径里最容易出问题的是 QP 上下文的查找。ERNIC 支持多 QP每个 QP 的上下文存在片上 RAM 里。如果 QP 数量多查找逻辑的时延会影响整体性能。PG332 里提到 QP 上下文查找是流水线化的但实际时延和 QP 数量有关QP 越多查找时延越大。3.3 ACK 与重传可靠传输的核心RoCE v2 的可靠性靠 ACK 和重传来保证。ERNIC 在收到报文后会根据配置决定是立即发 ACK 还是延迟合并 ACK。延迟合并能减少 ACK 数量降低网络负载但会增加发送端的重传判断时延。重传逻辑在发送侧。ERNIC 维护一个重传定时器如果超时还没收到 ACK就重传对应的报文。重传定时器的值需要根据网络 RTT 来配置配得太小会导致不必要的重传配得太大则故障恢复慢。我的经验是在数据中心内部网络里RTT 通常在几微秒到几十微秒重传定时器可以设在 100 微秒到 1 毫秒之间。PG332 里对 ACK 和重传的描述比较简略很多细节需要结合 IB 规范来看。如果你对 RoCE v2 的传输层语义不熟建议先补一下 IB 规范的传输层章节再回来看 ERNIC 的实现。4. Vivado 里集成 ERNIC 的完整流程4.1 IP 生成与参数配置在 Vivado 里生成 ERNIC IP第一步是选对版本。不同版本的 ERNIC 支持的器件和特性不一样比如某些版本只支持 UltraScale某些版本对 QP 数量有限制。选版本的原则是优先选和你的 Vivado 版本匹配的最新版但如果最新版有已知问题就退一个版本。参数配置里几个关键项QP 数量根据实际需求配配多了浪费资源配少了不够用。一般存储场景 64 到 256 个 QP 够用。MTURoCE v2 支持 256、512、1024、2048、4096 字节几种 MTU。MTU 越大协议开销越小但时延会略增。我一般选 1024 或 2048。AXI 数据位宽要和主机侧的总线位宽匹配常见的是 512 位。时钟频率core_clk的频率决定了处理能力100G 场景下一般要跑到 300MHz 以上。配置完成后Vivado 会生成 IP 核和配套的 example design。我强烈建议先把 example design 跑一遍仿真确认基本功能正常再开始自己的集成。4.2 顶层集成与时钟域处理顶层集成时ERNIC 需要和 CMAC、DDR 控制器、PCIe 等模块对接。这里最麻烦的是时钟域处理。ERNIC 通常有这几个时钟域core_clkERNIC 内部逻辑时钟axis_clkAXI-Stream 接口时钟通常和 MAC 的时钟同源config_clkAXI-Lite 配置接口时钟通常和 PCIe 或主机接口同源跨时钟域的信号需要做同步处理。AXI-Stream 接口如果跨时钟域需要用 FIFO 做缓冲。配置接口的跨时钟域相对简单因为配置操作不频繁用双触发器同步即可。我踩过的一个坑是core_clk和axis_clk的频率比没有选好导致 AXI-Stream 的吞吐量不够100G 链路只能跑到 60G 左右。后来把core_clk提到 350MHzaxis_clk用 300MHz才跑满线速。这个频率比在 PG332 里没有明确写需要根据实际数据流自己算。4.3 约束文件与时序收敛ERNIC 的时序约束比较复杂因为它涉及多个时钟域和高速接口。Vivado 的 IP 生成时会自动生成一部分约束但顶层集成时需要补充。关键约束包括时钟定义所有时钟域的周期、抖动、不确定性跨时钟域路径设置 false path 或 max delay输入输出延迟AXI-Stream 接口的 setup/hold伪路径复位同步器、配置寄存器的跨时钟域路径时序收敛的难点通常在core_clk域因为 ERNIC 内部逻辑比较密集。如果时序不收敛可以尝试降低core_clk频率、优化 QP 数量、或者用 Vivado 的物理优化选项。我遇到过core_clk跑到 350MHz 时 setup 违例的情况最后是通过减少 QP 数量从 256 降到 128加上调整布局约束解决的。所以 QP 数量不是越多越好要权衡资源和时序。5. 驱动侧对接与性能调优5.1 驱动需要做哪些事ERNIC 的驱动主要做三件事初始化配置、QP 管理、以及 WR/CQ 处理。初始化配置包括复位 ERNIC、配置 MAC 地址和 IP 地址、设置 QP 上下文基地址、使能中断。这些操作通过 AXI-Lite 接口完成寄存器地址在 PG332 的 Register Space 章节里有详细定义。QP 管理包括创建 QP、修改 QP 状态、销毁 QP。RoCE v2 的 QP 状态机有 RESET、INIT、RTR、RTS 几个状态驱动需要按照规范做状态迁移。ERNIC 的 QP 上下文存在片上 RAM 里驱动通过配置接口读写。WR/CQ 处理是数据面的核心。驱动把 WR 写到主机内存然后写 doorbell 寄存器通知 ERNIC。ERNIC 处理完后把完成信息写到 CQ并产生中断。驱动收到中断后读取 CQ通知上层应用。5.2 性能调优的几个抓手性能调优主要看几个指标吞吐量、时延、CPU 占用率。吞吐量上不去先查 AXI-Stream 的握手效率。如果tready经常为低说明下游 MAC 或者 DDR 带宽不够。可以用 ILA 抓tvalid和tready的占空比正常情况应该接近 100%。时延偏大先查中断处理路径。如果中断延迟大可以改用轮询模式或者用 MSI-X 中断并绑定到特定 CPU 核。另外QP 上下文的查找时延也会影响时延QP 数量多的时候尤其明显。CPU 占用率高通常是 doorbell 写太频繁。可以合并 WR一次 doorbell 触发多个 WR 的处理。ERNIC 支持 doorbell 合并具体配置在 PG332 里有说明。我实测下来优化到位的话100G 链路下 64 字节小包的端到端时延能到 4 微秒左右CPU 占用率在 5% 以下。这个数字比软件方案好一个数量级。5.3 常见问题与排查思路集成 ERNIC 时常见的问题我整理成表现象可能原因排查方法链路不通MAC 配置错误、时钟未锁定查 MAC 状态寄存器、测时钟报文丢失QP 上下文配置错误、MTU 不匹配抓 BTH、对比两端配置时延大中断延迟、QP 查找慢改轮询、减少 QP 数量吞吐低AXI-Stream 握手效率低抓 tvalid/tready 占空比重传频繁重传定时器太小、网络丢包调大定时器、查网络排查时建议从物理层往上查先确认链路 up再确认 MAC 收发包正常再确认 ERNIC 的 QP 状态最后查应用层。这样能快速定位问题在哪一层。6. 几个容易被忽略的实战细节6.1 ICRC 校验的坑RoCE v2 的报文末尾有 4 字节的 ICRC覆盖从 IP 头开始到数据结束的所有内容。ERNIC 在发送时会计算 ICRC接收时会校验。如果 ICRC 校验失败报文会被丢弃。问题在于ICRC 的计算依赖 IP 头里的某些字段比如 TTL、校验和。如果 MAC 或者外部协议栈在 ERNIC 之后修改了 IP 头ICRC 就会失效。我遇到过 MAC 自动填充 IP 校验和导致 ICRC 校验失败的情况最后是关掉了 MAC 的校验和卸载功能才解决。所以集成时一定要确认ERNIC 之后到物理层之间没有任何模块修改报文内容。如果有要么关掉那些功能要么把 ICRC 计算挪到修改之后。6.2 QP 上下文的内存分配ERNIC 的 QP 上下文存在片上 RAM 里但 WR 和 CQ 存在主机内存里。主机内存的分配需要驱动来做而且要做 DMA 映射。这里有个细节WR 和 CQ 的内存需要对齐到 64 字节或者 128 字节具体对齐要求看 PG332 的说明。如果不对齐ERNIC 读取时可能会出错。我一开始没注意对齐结果 WR 读取偶尔失败排查了很久才发现是地址没对齐。另外主机内存的物理地址需要传给 ERNIC这涉及到 IOMMU 配置。如果 IOMMU 没配好ERNIC 拿到的地址是虚拟地址DMA 会失败。这个在虚拟化环境里尤其要注意。6.3 中断合并的取舍ERNIC 支持中断合并就是把多个 CQ 事件合并成一个中断减少中断次数。中断合并能降低 CPU 占用率但会增加时延。我的经验是吞吐优先的场景开中断合并时延优先的场景关掉。如果开中断合并合并的阈值要调好太大时延高太小时 CPU 占用高。一般设 8 到 16 个事件合并一次比较合适。还有一个细节中断合并的定时器要和中断阈值配合。如果定时器设得太长即使事件数到了阈值也要等定时器到期才产生中断。这两个参数要一起调。6.4 多 QP 场景下的资源竞争多 QP 场景下多个 QP 会竞争 ERNIC 内部的资源比如发送缓冲、QP 上下文查找端口、以及 AXI 接口带宽。如果资源分配不均某些 QP 的性能会下降。ERNIC 内部有仲裁逻辑但仲裁策略是固定的不能配置。如果发现某个 QP 性能异常可以尝试调整 QP 的优先级或者把高优先级的 QP 分配到独立的资源上。我做过一个测试8 个 QP 同时跑总吞吐量比单个 QP 跑的时候低 15% 左右。这个损耗来自仲裁和资源竞争属于正常范围。如果损耗超过 30%就要查是不是资源配少了。7. 从 demo 到产品的距离把 ERNIC 的 example design 跑通只是第一步。从 demo 到能上线的产品中间还有不少工作。首先是稳定性验证。demo 通常只跑几分钟产品要跑几天甚至几个月。需要做长时间的压力测试观察有没有丢包、重传、QP 异常。我一般会跑 72 小时的压力测试期间监控各种计数器。其次是异常处理。网络里什么情况都可能发生链路闪断、对端重启、报文乱序、QP 状态异常。驱动和硬件都要能正确处理这些异常不能一异常就挂死。ERNIC 有一些错误状态寄存器驱动要定期读发现异常及时恢复。再就是性能一致性。demo 里性能好不代表产品里性能好。产品里可能有多个 QP、多种流量混合性能会波动。需要做混合流量的测试确认在各种场景下性能都达标。最后是可维护性。产品上线后要能监控、能诊断、能升级。ERNIC 的状态寄存器要暴露给监控系统日志要能定位问题。这些在 demo 阶段通常不考虑但产品阶段必须做。我个人在实际操作中的体会是ERNIC 这个 IP 本身是成熟的难点不在 IP 本身而在集成和调优。PG332 文档要反复看尤其是寄存器章节和接口章节。遇到问题先用 ILA 抓波形大部分问题都能从波形上看出来。性能调优要有耐心一个一个参数试不要指望一次到位。