☰
基于Xilinx UltraScale+的100G ROCE V2 RDMA实战解析
2026/10/7 12:10:18 网站建设 项目流程

Xilinx FPGA 100G RDMA这套东西,我去年在一款Xilinx UltraScale+板卡上完整跑通过一次,控制面全部走AXI Lite寄存器,数据面是自己写的ROCE V2协议逻辑,最终实现和商用RDMA网卡互通。这篇文章就把整个项目的选型思路、架构设计、AXI Lite配置细节、ROCE V2协议实现要点和调试过程一起整理出来。

如果你正在做类似的事情——准备用FPGA做100G接入、想绕过商用网卡的限制自己控制RDMA协议行为,或者刚入手ROCE V2但被协议栈和IP配置卡住,那这篇文章值得你花点时间看完。

1. 项目背景与整体设计思路

1.1 为什么要在FPGA上做100G RDMA

先说说项目背景。当时我们做的是存储卸载卡,需要在网卡侧直接支持RDMA读写,让后端存储节点通过100G ROCE V2网络高速访问前端内存。之所以不买现成的Mellanox网卡,是因为数据路径中间还夹着自研的加解密、重删、地址转换逻辑,这些必须跟网络入口强耦合,插在网卡后面就不成立了。

FPGA在这个场景下的优势是数据通路可定制。你可以把接收到的RDMA报文在FPGA内部解析后直接送入自己的加速流水线,而不是先到主机内存再绕一圈。另一个优势是和存储控制器的DDR控制器、PCIe DMA、自定义DMA都能无缝集成。但代价也很明显:ROCE V2是一种依赖可靠传输的协议,有QP状态机、PSN确认、重传、拥塞控制这些机制,全用逻辑去实现,工作量不小。

如果只是做原型验证,选一块带100G光口、有DDR4资源、逻辑规模足够的Xilinx UltraScale+板卡就够了。我们用的是VCU118级别的板子,VU9P芯片,带双路100G光口和四组DDR4,跑100G ROCE V2没有瓶颈。

1.2 协议选型:ROCE V2凭什么胜出

InfiniBand、ROCE V1、ROCE V2、iWARP,这几种协议都支持RDMA语义,但落地场景差别很大。InfiniBand要专用交换机,成本高;iWARP基于TCP,对丢包健壮性强,但协议栈重、延迟高;ROCE V1直接用MAC地址做寻址,只能在二层网络跑,很难跨子网。

ROCE V2把RDMA报文封装在UDP/IP里,保留了InfiniBand的BTH头部,数据面直接建立在以太网上。这意味着可以用现有的以太网交换机和网管体系,同时还能享受RDMA的零拷贝、内核旁路和低延迟。默认使用UDP端口4791,交换机不需要特殊配置也能转发,只是要保证无损或不丢包才稳定。也正是因为这一点,很多高性能存储和AI训练集群都选择了ROCE V2。

对于FPGA实现来说,ROCE V2的报文格式清晰,头部解析和构造全是固定偏移,非常适合用流水线做。相比之下iWARP的TCP流管理在FPGA里要实现完整可靠传输,复杂度要高一个数量级。

1.3 整体架构与数据通路设计

我们的系统控制面与数据面是彻底分离的。控制面由主机或管理CPU通过AXI Lite总线向FPGA写寄存器,配置MAC地址、IP地址、UDP端口、QP上下文、MR表项。数据面完全靠硬件逻辑跑,不经过CPU。

TX方向的流程是:用户逻辑产生RDMA写请求,读本地DDR里的数据,然后按配置好的QP上下文和MR表项构造RDMA报文,加上BTH、UDP、IP、MAC头,送进CMAC(100G Ethernet Subsystem)发出。RX方向是反过来:CMAC收到报文后,先做以太网、IP、UDP解析,再解析BTH,查到对应的QP上下文,执行内存读写操作,最后按需生成ACK或者读取响应报文。

这套架构里最关键的两个模块就是AXI Lite控制通路和ROCE V2协议引擎。前者决定配置是否可靠,后者决定通信是否符合RDMA语义并能和标准网卡互通。下面分章节详细拆解。

2. 硬件选型与IP配置

2.1 FPGA与板卡选择

Xilinx官方生态里,涉及100G网络的IP主要是CMAC、XDMA、Interlaken、AXI Ethernet等。做ROCE V2的话,选择逻辑资源充裕、高速收发器多的UltraScale+系列比较稳,比如VU9P、VU11P、VU13P这些。VU9P有超过250万个逻辑单元,高速收发器支持100G,DDR4控制器带宽也够,留给协议逻辑余量很大。

你要特别注意板卡的光模块形态。100G光模块有QSFP28,也有一分四的QSFP到SFP28分支模式。我们用的是双QSFP28接口,配合100G SR4模块和MPO光纤,连接到对端的ConnectX-6网卡。光模块的兼容性也要提前确认,有些模块锁TWI操作,或者寄存器映射异常,会导致link up不了,这些坑后面调试部分细说。

2.2 100G CMAC IP关键参数

在Vivado里,Xilinx的100G Ethernet Subsystem(也就是常说的CMAC)是必经之路。它负责PCS/PMA、MAC、流控、FEC这些底层功能,用户侧拿到的是一个AXI4-Stream接口。IP配置时几个关键点:

  • Line Rate选择100G,接口数据位宽选择512bit还是1024bit。512bit运行在322.265625MHz,1024bit运行在161.1328125MHz。大多数逻辑在322MHz时序能收敛,我们选512bit。
  • FEC选项根据对端和光纤链路长度确定。短距离SR4光模块在机柜内互联,可以先不开FEC调试;距离超过几百米建议开RS-FEC,容错能力完全不同。
  • 时钟源选择“GT Reference Clock”,输入156.25MHz,CMAC会自动完成时钟生成。
  • 统计和状态接口建议打开,调试时看帧计数、CRC错误会方便很多。

这里有一个非常重要的原则:打开CMAC的“core reset”后,不要急着收发数据,必须等待gtpowergood、gt_rxresetdone、tx_resetdone这些信号全部拉高,才能真正工作。很多刚开始接触100G的人在这里栽跟头,以为复位信号给一下就结束了。

2.3 XDMA与其他基础IP

控制通路如果走PCIe,就再加一个XDMA IP,用XDMA的AXI Lite从接口映射寄存器空间。我们的场景比较特殊,控制面只需要AXI Lite,因此直接用AXI Lite Interconnect把寄存器总线引出来,既简单又不会有XDMA带来的额外复杂度。

另外还要根据应用需要配一个DDR4 MIG控制器,用于存放注册内存区域的数据。ROCE V2的写请求最终要落到物理内存,读请求要从物理内存取数据。MIG的接口带宽要足够,至少是一读一写两个AXI端口,否则数据通路会成为瓶颈。

AXI Lite Interconnect的地址映射配置要特别小心,这是最容易出错但又最基础的一步。每个从端口分配一段地址,主机侧写0xA0010000,就必须让Interconnect能把这段地址路由到对应的从端口。我们最初因为地址段重叠,导致程序写寄存器时数据写进去了但读出来全是0,排查了大半天。

3. AXI Lite配置通路:把控制面做稳

3.1 寄存器规划:一张表管理所有配置

AXI Lite配置通路是整个系统的“方向盘”,我们需要把驱动或者测试脚本要写的所有寄存器列出来,设计成一张清晰的寄存器映射表。以下是我们实际使用的寄存器布局,可以直接参考。

偏移宽度名称功能
0x0032CTRLbit0:软复位;bit1:ROCE引擎使能;bit2:回环模式
0x0432MAC_LOW本端MAC地址低32位
0x0832MAC_HIGH本端MAC地址高16位
0x0C32IP_ADDR本端IPv4地址
0x1032UDP_PORT本端UDP端口,默认4791
0x1432PEER_MAC_LOW对端MAC地址低32位
0x1832PEER_MAC_HIGH对端MAC地址高16位
0x1C32PEER_IP对端IPv4地址
0x2032PEER_UDP_PORT对端UDP端口
0x40 - 0x7F32×16QP_TABLE每个QP的上下文,包括QP号、状态、PSN、对端QP号
0x80 - 0xFF32×32MR_TABLE内存注册表项,包括物理地址、长度、R_Key、L_Key
0x1000 - 0x1FFF32×NSTAT_BASE各种统计计数器和状态寄存器,只读

QP表项的核心字段包括:本地QP号(24bit)、对端QP号(24bit)、发送PSN、期望接收PSN、QP状态(RTS/RTR/ERR)、对端IP和UDP端口。MR表项则要记录虚拟地址、物理地址(通常是FPGA本地DDR地址)、长度、访问权限、键值。

在设计寄存器表时,我强烈建议把每个表项都做成独立的连续地址空间,而不是浓缩到一个寄存器里用位段去塞。位段虽然省地址,但驱动侧解析麻烦,硬件逻辑也容易在读写时出错。地址空间在FPGA里又不值钱,宁可多留空。

3.2 AXI Lite时序与常见坑

AXI Lite虽然是“Lite”,但握手信号一个都不能少。写事务是AW通道和W通道同时发,然后等B通道返回;读事务是AR通道发出地址后,等R通道返回数据。

// 一个简单的AXI Lite写寄存器状态机示例 always @(posedge clk) begin case (state) IDLE: begin if (wr_start) begin awaddr <= wr_addr; wdata <= wr_data; state <= W_ISSUE; end end W_ISSUE: begin awvalid <= 1'b1; wvalid <= 1'b1; state <= WAIT_BRESP; end WAIT_BRESP: begin if (bvalid && bresp == 2'b00) begin awvalid <= 1'b0; wvalid <= 1'b0; state <= IDLE; end end endcase end

实际工程中我遇到过几个坑。第一个是AXI Interconnect的地址位宽,如果主机侧总线位宽是64位,AXI Lite从端口是32位,地址的低位对齐处理容易出错。第二个是写寄存器的握手顺序,有些IP要求AW和W同时有效,分开拉高可能导致事务卡死。第三是寄存器表项的原子性,比如一个QP上下文要用连续4个寄存器表示,主机侧如果一边写一边有数据包进来,逻辑读到一半的状态,就可能构造出错误的报文。解决办法是加一个“shadow寄存器组”,先全部写入临时寄存器,确认完整后再用一个commit寄存器一次性更新到工作寄存器。

3.3 上电初始化流程

AXI Lite本身不会自动初始化,必须由外部主动写寄存器。我们的上电顺序是:

  1. 拉低CTRL的软复位位,让协议引擎处于复位状态。
  2. 配置本端和对端的MAC、IP、UDP端口。
  3. 写QP_TABLE,把所有要用的QP上下文准备好,此时QP状态先设为Reset。
  4. 写MR_TABLE,把DDR里可用内存区域注册好,分配R_Key和L_Key。
  5. 全部配置完之后,再写CTRL,清除软复位,同时置位ROCE引擎使能。

这里有一个经验:GT和CMAC的复位配置完成后,一定要等一段时间,再开始配置上层协议。光模块的CDR锁定、PCS同步都需要时间,通常要等几十毫秒到几百毫秒不等。如果一上电就立刻使能协议引擎,很可能因为CMAC还没就绪导致首包丢失。

4. ROCE V2核心逻辑实现

4.1 报文格式与头部构造

ROCE V2报文外层和普通UDP报文长得很像,只是在UDP头之后增加了RDMA头部。整体结构是:

  • 以太网头(14字节,可选VLAN)
  • IPv4头(20字节),IP协议号填17(UDP)
  • UDP头(8字节),目的端口固定4791
  • BTH头(12字节),这是RDMA的核心信息
  • 载荷数据(SEND数据或RDMA读写的有效负载)
  • ICRC(4字节),覆盖从UDP头到载荷的CRC校验

BTH里的关键字段包括操作码(OpCode),比如SEND、RDMA WRITE、RDMA READ请求、RDMA READ响应、ACK等;PKey是分区键,两端要一致;目的QP号是24位;PSN是24位分组序列号,用于可靠传输的确认和重传。

构造发送报文时,我们的流水线是先查QP上下文拿到对端MAC、IP、端口和目的QP号,再查MR表拿到本地物理地址和长度,然后依次拼接以太网头、IP头、UDP头、BTH,最后把数据从DDR搬进发送FIFO。这个过程中IP头里的总长度和校验和是关键,UDP的checksum在ROCE V2里大多数网卡不校验,但我们还是会算上,因为有些交换机的内置诊断会报checksum错误。

接收解析时,第一步判断目的MAC是否是本端;第二步判断IP和UDP端口;第三步检查BTH里的目的QP号是否存在且处于可接收状态;第四步校验PSN是否连续。全部通过后才把载荷送到DMA引擎。

4.2 QP管理与PSN滑动窗口

QP(Queue Pair)可以理解成一个双向通信的端点,每对通信双方各持有一个QP。我们的设计里QP上下文存储在Block RAM或URAM中,用QP号做索引。QP状态至少要实现Reset、Initialize、RTR、RTS、SQ Error这几个,不过实际调试时只要保证RTR和RTS的切换正确就够了。

PSN的管理是可靠连接(RC)的核心。发送方每发一个数据包,PSN加一,接收方收到一个包后,校验PSN是否为期望值。如果连续,就返回一个ACK包,里面携带期望收到的新PSN。如果PSN不连续,说明中间丢了包,接收方应该返回NACK,发送方则要从重传缓冲区中把丢掉的包重新发一遍。

重传缓冲设计是这个项目里最让硬件工程师头疼的部分。协议要求RC模式下数据可靠到达,所以每个发出的包都要保留在缓冲区里,直到收到对应的ACK才能释放。缓冲区的大小取决于带宽延迟积:100G链路,假设RTT是2微秒,那么窗口内最多有100Gbps乘以2us等于200Kbit,也就是大约25KB的数据。考虑到重传发生的时序和队列深度,我们实际给每个QP分配了4MB重传缓冲,这样才能保证在深队列场景下不丢数据。

实际做下来,最简单的实现是每个QP维护一个PSN环形队列,发送完成后把数据写进重传RAM,收到ACK后根据PSN排出对应的条目。这样可以做到精确释放。

4.3 内存访问与地址转换

ROCE V2的RDMA操作都基于内存区域(MR)和键值。远端发起RDMA WRITE时,报文中携带目的端的R_Key和虚拟地址,目的端需要根据这两个信息找到对应的物理地址,把数据写入。

在FPGA实现里,我们没有操作系统和真实MMU,所以地址转换逻辑是自己做的。每个MR表项记录:起始虚拟地址、长度、物理地址(FPGA本地DDR基地址)、R_Key、L_Key、访问权限。收到写请求后,硬件用报文里的目的虚拟地址和R_Key去查表,校验虚拟地址是否落在注册区间内,再通过基址偏移换算成物理地址。

这里有个容易忽略的问题:R_Key本身有校验机制。标准RDMA网卡会用R_Key中的高8位作为“内存窗口索引”,而我们还额外要求R_Key匹配表项才能执行读写。如果校验不严,任何知道IP和端口的节点都可以读你的内存,在存储场景下这是不可接受的。

5. 100G数据通路与性能调优

5.1 从CMAC到用户逻辑的接口

CMAC的用户侧接口是AXI4-Stream。在512bit位宽模式下,每个时钟周期可以传输64字节,322MHz下刚好接近100G线速。接口信号中除了tdata、tvalid、tready、tkeep外,tuser也很重要,用来标识帧的开始和结束。

数据通路设计上要保证每个时钟周期都能接受一个完整的字,否则就会掉带宽。我们的TX流水线做了两级缓冲:第一级是发送描述符队列,第二级是数据FIFO。启动发送时先查描述符,拿到对应的DDR地址、长度、目标QP信息,然后发起DMA读,数据流进FIFO后再按帧组织好送进CMAC。

对于RX方向,CMAC输出的报文直接进解析模块。解析模块第一拍判断帧类型,第二拍剥离头部,第三拍提取载荷地址信息并触发DMA写。为避免背压,DMA写接口的ready信号必须保持足够长的综合表现,否则CMAC的FIFO满了之后会拉低tready,导致RX暂停。

5.2 时钟复位与FEC

100G CMAC的复位设计比普通MAC严格得多。Xilinx推荐的做法是用GT复位控制器统一管理GT的复位,然后等待各resetdone信号就绪。我之前图省事,直接在系统复位里拉一下PCS复位就完事,结果link status起来了但抓包全是错误,检查发现是GT TX没有从复位中恢复。

FEC的问题是很多项目的隐藏雷。如果你的对端设备开启了RS-FEC,而FPGA侧的CMAC没有开启,会出现link能up但误码率极高的现象。反过来你开了FEC而对方没开,也会导致各种CRC错误。建议100G互联场景,两端统一配置,长距离链路优先开RS-FEC(544,514)。

FEC还有一个副作用是增加延迟,RS-FEC的编解码处理要几微秒。对于延迟极度敏感的场景,比如多级交换网络里的同步操作,可以评估是否值得为了抗误码牺牲这部分延迟。我们在同机房VCSEL短距离场景,最终选择不开FEC,延迟能低一个档位。

5.3 性能瓶颈与优化手段

从最终测试结果看,单路100G ROCE V2的RDMA写带宽可以跑到线速的90%以上。我们实测无FEC、SR4光模块、对端ConnectX-6网卡,RDMA写带宽大约91Gbps,FPGA到FPGA之间甚至能接近线速。

性能卡点主要在三处:DMA读带宽、重传缓冲的RAM带宽、以及头部分析流水线的阻塞。

DMA读带宽要注意MIG控制器的bank管理和突发长度。二选一:要么用AXI4接口配置成最大突发256拍,要么拆成多个通道并行访问不同的DDR rank。我们在VU9P上用了两个DDR4控制器,一个专门给写请求的数据搬运用,另一个给读请求的数据搬运用,避免读写仲裁互相拖累。

重传缓冲用URAM实现,URAM容量大且排列密集,带宽比BRAM差一点,但做好两端口乒乓访问后不是瓶颈。真正的问题在于PSN窗口管理逻辑:确认包回来时,如果同时有大量排队的重传块要释放,必须用FIFO做异步处理。我们在一次测试中因为释放逻辑处理不过来,导致新发送的包被等待队列堵住,带宽掉到70%。后来改成“释放请求进FIFO,后台逐条处理,发送不等待释放完成”的方式,问题才解决。

头部分析的流水线则要注意一次处理一整个64字节字。如果每个时钟周期都能完成一个帧的头部解析,那么理论吞吐就能跟上。我们实际上把头部解析拆成了四拍流水,每拍只做简单的比较和提取,最终能稳定跑到322MHz。

6. 调试经验与常见问题排查

6.1 从Loopback到端到端的调试路径

100G ROCE V2开发,最忌讳一亮机就做端到端测试,那样一旦失败根本不知道问题出在哪层。我们采用分层的调试路径:

  1. CMAC内部回环:在CMAC IP内部把TX回环到RX,用ILA抓接口信号,确认MAC侧数据通路完整。
  2. 光模块远端回环:用一根MPO回环线连接光模块的TX和RX,测试SerDes和光模块通路的稳定性。
  3. ICMP/UDP测试:在CMAC上层写一个简单的UDP echo模块,主机用ping或者UDP包去测,确认IP/UDP层工作正常。
  4. ROCE V2互通:对端换成RDMA网卡,用perftest工具发起读写,FPGA侧用ILA抓BTH解析结果。

每一步都要有明确的判定标准。比如CMAC回环测试,至少要跑满10秒钟不出CRC错误,才敢继续往下走。光模块回环更是要测半小时以上,因为小概率误码需要长时间才能暴露。

6.2 高频问题速查表

现象可能原因解决方案
端口物理link up,但CMAC侧无收发统计CMAC或GT复位未完全释放,光模块firmware异常检查gtpowergood、gt_rxresetdone,按顺序执行GT复位
抓到大量错误帧或CRC错误FEC配置不匹配,光纤或模块质量差排查两端FEC配置,更换模块/线缆验证
ROCE连接建不起来,对端设备报timeoutUDP端口不是4791,QP上下文里对端QP号错误核对UDP端口和BTH中目的QP号,用ILA抓发出去的头部
寄存器写不进去或读回来不对AXI Interconnect地址映射配置错误,数据位宽不匹配检查地址段映射,确认32bit对齐,用ILA抓AXI时序
RDMA写带宽远低于预期DMA读冲突、重传缓冲释放阻塞、PSN窗口太小分别测DMA裸带宽,增加重传缓冲和QP窗口
随机偶发丢包队列深度不足,RX路径背压处理不好增加FIFO深度,优化tready信号生成逻辑

调试工具上,ILA的采样深度一定要拉高。数据位宽512bit,采样深度至少32768,否则抓几个包就被填满了,看不到完整的事件序列。另外,我强烈建议在ROCE引擎里加一组硬件计数器,专门统计发送包数、接收包数、ACK数、NACK数、重传次数,这些计数器要比任何在线逻辑分析仪都好用,因为测试跑几小时后看计数器就能定位问题趋势。

6.3 实测数据与心得

整套系统最终稳定运行后,我们对端用ConnectX-6做RDMA写和读测试。无FEC、SR4模块、房间内短距离光纤条件下,RDMA Write带宽约91Gbps,RDMA Read带宽约85Gbps。往返延迟在1.5微秒左右,其中包含了FPGA的逻辑处理和网卡的协议开销。FPGA到FPGA的直连测试里,延迟能降到0.9微秒附近。

这个项目做下来,我最大的体会是:ROCE V2的协议逻辑本身并不算难,真正难的是把100G通路上的每一个环节都调稳。链路层一个错误的FEC配置,数据面一个不合理的背压,控制面一个不仔细的地址映射,都会让整体性能崩盘。

另外一个值得分享的技巧是寄存器配置的回归测试。我们在主机侧写了一套Python脚本,每次修改完逻辑后,先通过AXI Lite把所有寄存器值读出来和预期比对,再跑一轮perftest。这套回归测试在后续迭代中救了我很多次,因为改了协议解析逻辑后,经常出现某些寄存器配置被连带破坏的情况,没有自动化比对根本发现不了。

如果你也在做类似的项目,建议先从点对点静态配置打通,再考虑CM握手自动建链;先保证无错跑通,再谈拥塞控制。ROCE V2里的ECN和CNP处理,可以等基础收发稳定之后再加,否则调试复杂度会呈几何级增长。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询