资讯动态

100G以太网UDP协议栈在FPGA上的移植与上板实战

发布时间:2026/9/11 15:33:43 来源:尧图企业网站定制
100G以太网在FPGA上跑UDP这事儿听起来挺唬人但真做起来其实就是一层窗户纸。前阵子我刚好把一个开源UDP协议栈从10G往100G上挪从看代码到上板跑通踩了一堆坑也摸出一些门道。写这篇东西就是想把整个移植上板的流程、我踩过的坑、以及最后怎么把问题一个个摁回去的完整记录一遍。不管你手里是Xilinx还是Intel的片子只要你准备碰100G UDP这文章应该能帮你省下一两周的瞎折腾时间。先说结论100G UDP移植上板本身不复杂复杂的是你对自己用的IP核、总线协议和时序收敛的理解深度。1. 为什么非要自己折腾100G UDP协议栈在开始聊技术细节前先说说为什么要干这件事。有人可能会说现成的闭源IP一大把花钱买了直接调不就行了这话没错但前提是你预算充足而且项目时间表宽松。但很多场景下你需要的其实是UDP这种无连接、低延迟的传输方式而不是动辄几十万授权费的完整TCP/IP卸载引擎。1.1 100G UDP的真实应用场景UDP在100G这个速率段的需求主要集中在几个领域高速数据采集示波器、雷达、软件无线电这类设备需要把ADC采到的大流量数据实时搬到上位机。UDP丢包重传机制简单硬件实现代价低满速率跑也不心疼。数据中心内部通信AI训练集群的参数同步、分布式存储的数据分发追求的是极低延迟和超高吞吐。TCP的拥塞控制在这种场景下反而是累赘。科研仪器互联粒子对撞机、射电望远镜阵列这类大型科学装置动辄几百路高速数据需要汇聚。开源方案意味着你可以精确控制每一路数据的时序甚至可以为了特殊需求魔改协议。在这些场景里UDP的不可靠反而成了优点我不需要你保证我一定收到我只需要你尽量快、尽量不丢地往我这边灌数据。剩下的纠错、排序、重传都交给应用层来处理。1.2 为什么选开源的移植方案闭源IP虽然省心但有几个绕不开的痛第一核心逻辑黑盒出了问题你只能提工单等回复第二定制化困难你想在发送路径上加个时间戳、或者在接收路径上过滤特定报文都得看人家脸色第三授权费用和版税机制对于很多预算敏感的项目来说确实不便宜。开源协议栈的好处就是透明可控。代码就在那儿你可以一行行地看看它的状态机怎么跳、FIFO怎么管理、校验和怎么计算。出了问题你理论上可以把问题定位到行号。而且从10G往100G挪大部分代码逻辑是通用的改的只是总线位宽和时钟频率。这活儿说白了就是一门手艺活学会了自己留着下次换个板子还能用。2. 移植前的准备工作与方案选型这块儿特别关键。我见过太多人上来就把代码down下来开始编译结果光IP核版本不一致就折腾了两天。准备工作做的越扎实后面调试越省事。2.1 硬件平台评估要跑100G先看看手头的FPGA够不够格。这里说的够格不只是逻辑资源够不够更关键的是高速收发器Transceiver的速率等级和数量。至少需要1个100G速率的硬核MAC虽然可以用软核通过逻辑去拼一个100G MAC但那样子资源和时序都会非常紧张。像Xilinx的UltraScale VU9P、VU13P或者Intel的Agilex系列基本都内置了硬核的100G MAC在Xilinx里叫CMAC在Intel里叫Hard IP for Ethernet。选用硬核还有个好处它帮你把PCS/PMA层的很多烦人细节都处理好了你只需要关心怎么把数据喂给它、怎么把数据从它那儿接走。检查可用的GTY/GTM收发器数量一个100G端口通常需要4路或8路高速收发器来组成一个通道组取决于你用的是CAUI-4还是CAUI-10。板子上可能有很多收发器但不是每一个都能跑到100G速率得查一下芯片手册看哪些Bank支持你要的速率。评估DDR带宽虽然UDP本身不存数据但如果你要做缓存、重排序或者流量整形DDR的带宽会成为一个瓶颈。100G线速意味着大约12.5GB/s的数据吞吐DDR4-2400单通道理论带宽也就19.2GB/s扣掉刷新和效率损耗你实际能用的带宽得精打细算。2.2 开源协议栈项目的挑选技巧市面上的开源UDP协议栈不少但能支持100G的其实凤毛麟角。国内比较有名的是那套从Open-NFPLus演化出来的项目国外的也有几套在GitHub上挂着。挑的时候重点看几个指标是否原生支持高总线位宽100G下如果使用512位的数据总线时钟频率大约是322MHz这是最常用的频率点如果你用256位总线频率就得跑到644MHz这对时序的要求就非常苛刻了。所以建议选原生支持512位总线的栈。是否有完整的RX/TX路径有些开源项目只是简单地把MAC包解出来丢给用户但UDP需要的头部解析、校验和计算、ARP处理、ICMP回显等这些细节它并不一定都实现了。选的时候要看代码里有没有完整的状态机。FIFO或缓存的设计是否成熟UDP接收端可能会有多个包同时到达如果只有一个FIFO很容易产生头阻塞。好的设计会为不同的端口号设置独立的缓存队列或者至少有一个足够大的暂存区避免丢包。我当时选的方案是基于一个在10G上已经验证过、架构比较干净的项目其总线接口恰好是通用的AXI4-Stream这让我后续对接CMAC IP核时省了很多事。2.3 开发环境的搭建要点Vivado版本的选择有时候比代码本身更影响心情。版本太老可能不支持你选的芯片型号版本太新又可能存在各种兼容性问题。我个人的建议是除非必须否则就用官方主推的稳定版本。对于100G设计Vivado 2020.2以上版本对UltraScale的支持都已经很成熟了。仿真工具方面如果只是做RTL级的功能仿真Vivado自带的XSim完全够用。但如果你要做时序仿真或者跑一些复杂的UVM环境那还是得回归ModelSim或QuestaSim。我自己是习惯先把逻辑用纯RTL仿真验证一遍再把IP核加进去做系统级仿真最后才上板。3. 整体架构设计与总线标准解析这一章是整个移植的核心讲清楚开源的协议栈和Xilinx CMAC之间是怎么连接起来的。如果你能把这个逻辑理清楚那你就不只是会移植而是真正理解这套系统了。3.1 AXI4-Stream总线在100G场景下的应用不管是什么协议栈最终和硬核MAC通信的一定是AXI4-Stream总线。AXI4-Stream是一种无地址的总线协议专门用来做高速数据流传输。它有四个最核心的信号tdata数据总线可以是8、64、128、256、512位等任意宽度。在100G场景下几乎都是512位。tkeep字节使能信号。它用来指示对应的tdata字节是否有效。因为一个以太网帧不一定是64字节的整数倍所以需要tkeep来标记最后一拍数据的有效字节数。tuser用户自定义信号。在以太网场景下通常用来传递错误标记、帧起始标记等。比如Xilinx的CMAC会在tuser里塞入一个指示当前帧是否有CRC错误的信息。tlast帧结束标志高电平表示一拍是当前帧的最后一拍。看到没这个总线协议里没有握手的ready/valid信号为什么它还能保证不丢数据其实它是有握手信号的只是在AXI4-Stream里被拆分成了TVALID和TREADY。TVALID拉高表示主设备Master这拍发送的数据是有效的TREADY拉高表示从设备Slave这拍可以接收数据。只有当TVALID和TREADY同时拉高时这一拍数据才算被成功传输。3.2 协议栈内部模块的层次拆解一个完整的UDP协议栈从上到下大概分成这么几层MAC层控制与状态负责管理CMAC IP核的初始化、复位时序、链路状态监测。例如检查链路是否建立PCS/PMA层是否完成同步、有没有远端错误。MAC帧解析与封装这时你拿到的数据是以太网帧。在这个模块里接收方向要剥离MAC头部目的MAC、源MAC、EtherType判断这是个IPv4帧、ARP帧还是其他类型的帧。发送方向则要负责填充MAC地址、EtherType并计算CRC校验码或者把这个活儿交给CMAC硬件。IP层处理对接收方向需要检查IP头部校验和、判断目的IP是不是本机、解析协议字段是UDP还是ICMP、处理分片重组逻辑。对发送方向则需要填写源/目的IP、计算IP头部校验和、处理MTU分片。UDP层处理对接收方向检查UDP校验和根据目的端口号将数据分发到不同的用户接口。对发送方向则根据用户指定的目的IP和端口封装UDP头计算校验和。ARP/ICMP模块这是让整个协议栈能够正常工作的“管理面”。ARP用于解析局域网内的IP地址和MAC地址的映射关系ICMP用于回应Ping请求方便你调试链路是否通。每一层之间大多数都用FIFO隔开这样可以隔离时钟域也可以防止模块间的相互干扰导致的时序问题。4. 核心移植过程实战记录接下来是真正的“搬砖”环节。我会按照实际的移植步骤把每一步干什么、为什么这么干以及我在这一步踩过的坑都记录下来。4.1 从10G到100G的代码改造细节如果你手里的源码是10G的那么恭喜你你至少不需要从零开始解决UDP协议的逻辑问题。但是10G和100G的区别可不仅仅是把时钟频率从156.25MHz变成322.265MHz这么简单。数据位宽的变更10G设计通常用64位数据总线跑到156.25MHz。100G设计推荐用512位数据总线跑322MHz。这不仅仅是把位宽翻几倍的问题。你想想一个最小以太网帧只有64字节加上前导码和FCS是84字节如果总线位宽是512位64字节那么一个最小帧在总线上只需要一拍就能传完。你要怎样在只有一拍的周期里完成FIFO读取、包头解析、甚至校验和计算这就对逻辑的并行化提出了很高的要求。时序状态机必须重新设计很多地方需要做成流水线。校验和计算的并行化UDP和IP头部校验和是16位反码求和。在10G下你可以把整个帧的数据串行地累加时间来得及。但在100G下数据是一个周期64字节地涌进来你要在同一个周期内完成对当前64字节数据的处理如果还用串行加法器那肯定完蛋。好的做法是用“加法器树”把64字节数据拆成16个32位数然后并行求和最后再合并进位。这部分逻辑是纯组合逻辑时序压力巨大通常需要插入流水寄存器。FIFO的深度调整因为总线上一个周期可以传输更多的数据所以你不再需要超深的FIFO来缓存过深的FIFO反而会增加延迟和资源消耗。但是因为每个包的到达间隔变短你需要确保FIFO能在极短的时间内处理完一个包而不会被下一个包给冲掉这就涉及到FIFO读写速率匹配的问题。建议使用Xilinx的原语或者IP核配置出独立时钟域的FIFO。4.2 与Xilinx CMAC IP核的对接过程CMAC是硬核它提供的是标准的AXI4-Stream接口。但它的用户侧总线数据位宽默认是512位。它的时钟可以配置为322.265MHz此时为512位总线或者644.53125MHz此时为256位总线。你需要将你的协议栈的用户侧接口接到这个总线上。复位时序CMAC对复位时序有严格要求。复位释放后它内部要完成GT收发器的校准、PCS/PMA通道的对齐等动作。你必须在设计中留够充足的时间等待CMAC的gt_rxuserrdy和gt_txuserrdy信号拉高后才能开始发送数据。用户侧状态信号CMAC提供了几个重要的状态信号。rx_axis_tuser里其实并不仅仅是帧错误标志在Xilinx的文档里它还被定义为携带了当前帧的字节长度、错误指示等信息。在对接时一定要仔细阅读IP手册里关于tuser信号位宽的定义我见过有人直接把tuser整段忽略掉结果丢包了还找不到原因。流控机制CMAC的接收侧可能会因为GT接收通道的背压而拉低rx_axis_tready。协议栈的接收FIFO要能够正确处理这种反压。同理发送侧如果用户逻辑持续不断地发数据但CMAC内部FIFO满了它会拉低tx_axis_tready你的发送逻辑必须能够暂停发送。4.3 时钟与复位策略的实现100G设计对时钟和复位的要求非常苛刻。我自己习惯这样组织时钟树一个独立的、高精度的差分时钟源作为参考时钟接到GT的参考时钟引脚频率为156.25MHz这是100G以太网的标准参考时钟频率。核心逻辑时钟用户侧使用CMAC输出的clk_tx_user和clk_rx_user这是由CMAC内部根据链路速率自动生成的稳定时钟。用户逻辑里尽量避免自己用PLL生成需要和GT参考时钟同源的时钟那样容易产生确定性延迟问题。在复位设计上千万别用全局异步复位然后把所有逻辑都扔进去。好的做法是使用同步复位并且要根据不同的时钟域做异步复位同步释放保证所有触发器在释放复位时是在同一个时钟沿上动作。4.4 地址与端口的映射关系UDP协议栈里最核心的东西其实就是一个查找表。你得告诉这个栈本机的IP是多少本机的MAC是多少哪个UDP端口的数据往哪个用户通道送。在移植的时候这部分通常是写死在RTL里的不够灵活运行时要改就得重新综合。我当时改成了一个小的RAM表通过微处理器接口比如AXI-Lite动态配置这样测试的时候就不用来回改代码重新编译了。这个设计调整虽然代码量不大但非常实用尤其是做长时间稳定性测试的时候需要反复切换测试端口和流量方向。5. 上板测试的完整流程与技巧代码移植完了仿真过了该上板了。这一步是真正考验人的地方。你可能会遇到仿真完全没问题的代码一上板就跑飞的情况这太常见了。上板测试的核心不仅仅是看灯亮不亮而是要看“线速”下稳不稳定。5.1 测试环境的搭建与检查清单硬件准备一台带有100G端口的服务器或者用两台100G交换机的端口环回、以及一块带有100G QSFP28端口的FPGA板卡。更重要的是你需要一个支持100G的光模块和一根配套的测试线缆有条件的话最好有一台示波器用来观察高速信号的眼图。软件准备在电脑上装好Wireshark用于抓包分析。如果你有Ixia或者思博伦的流量仪那更好没有的话就用我们后面要讲的软件方案替代。上板前先对着这个清单过一遍板卡的JTAG能连上吗FPGA配置成功了吗QSFP28光模块的供电和复位是否正常CMAC的GT参考时钟是否稳定可以通过ILA去抓取CMAC的状态寄存器来看。链路是否已经建立即cmac_link_status信号是否为15.2 Iperf3和Scapy的100G UDP打流测试方法在没有专用流量仪的情况下软件打流工具就成了首选。发送端测试在服务器上使用iperf3 -c FPGA_IP -u -b 0 -l 1400 -t 60其中-b 0表示不限带宽-l指定包长-t指定测试时间。需要注意的是100G的线速打流普通的CPU是远远扛不住的经常会出现所谓的“发送端瓶颈”即发不出去那么多包。你可以多开几个iperf3客户端进程每个进程绑定不同的CPU核心并且使用taskset命令把它们绑到不同的核上才能勉强打满100G。这个办法治标不治本但用来验证FPGA的接收能力足够了。双向测试与统计接收端FPGA这边通过内部的计数器分别统计收到的总包数、总字节数、错误包数。如果接收速率和发送速率能对得上并且错误包数为0那说明接收链路是通的。用Scapy构造特殊报文为了更精细地验证协议栈的逻辑比如验证ARP功能、验证非法校验和是否会被丢弃、验证分片包是否正确重组我会用Scapy直接从socket层发送自定义的报文。例如构造一个UDP校验和错误的包发给FPGA看它是否丢弃以及是否会上报错误中断。5.3 回环测试与吞吐量验证上板测试第一步通常是做个简单的环回测试。这个环回既可以直接在CMAC内部配置回环把发送数据原封不动地收回来也可以在外部光模块上通过光回环头来实现。回环的目的验证物理层链路正常。 直接在CMAC配置内部回环能看到数据从TX走到RX并且无误码说明GT串行收发器和PCS/PMA层没问题。这个测试完毕后我们要测真正意义上的逻辑环回——即FPGA收到UDP包后经过用户逻辑处理再原路发回去。这样测可以验证整个UDP协议栈的收发双通路是否都能正常工作。具体操作时我在协议栈内部加了一个简单的回环FIFO把接收到的UDP数据重新封装成发送包送回给上位机。上位机发100万个包能收到100万个包说明协议栈的数据通路是流畅的。5.4 通过Wireshark分析上行报文测试进入尾声我们要验证FPGA作为发送端发出的报文格式是否正确。用Wireshark在电脑上抓包一般重点看几项MAC层目的地址是否正确指向了电脑的网卡地址注意如果是发给网关的就要填网关MAC。EtherType字段是否正确。IP层源/目的IP、头的长度、标识符ID、标志位有没有问题。UDP层源/目的端口、长度字段、校验和是不是正常。在调试时还可以人为地让FPGA发送一些错误包设置UDP校验和为错误值然后用Wireshark抓包看看电脑上是否显示“Checksum Offload”之类的警告以此确认电脑侧的校验和计算行为。6. 测试结果分析如何判断链路真的“健康”测试不只是看功能通没通还要看它快不快稳不稳。这里整理了我在测试时关注的核心指标和分析方法。6.1 如何评估线速条件下的丢包率与误码率丢包率是很直观的指标。如果你用100G的流量去打瞬间收到的包数少于发送的包数那就算丢包了。丢包的原因分两类物理层误码由光模块、连接器、信号完整性引起。这会导致CRC校验失败从而被CMAC标记为错误帧。这类问题需要检查光模块、线缆、以及PCB走线甚至要跑一段时间的prbs测试来看误码率。逻辑层丢包接收路径上FIFO溢出、状态机误判、总线占用冲突等。这类问题通常要提高抓包时的观察精度使用ILA去抓取关键信号看看到底是哪一拍FIFO满了。误码率测试通常要用到一种叫PRBS伪随机二进制序列的测试报文。你发送一连串的伪随机数据在接收端用相同的多项式来校验生成的序列是否一致不一致的位即为误码位。如果长时间运行误码率为0说明物理层非常健康。6.2 接收错误包数的统计与故障定位FPGA内部要设计计数器分别统计各种错误类型“CRC错误帧计数”“帧长度错误计数”比如收到小于64字节的Runt包或大于1518字节的Jumbo包“IP校验和错误计数”“UDP校验和错误计数”如果在测试中发现CRC错误计数不为0那么大概率是物理层出问题了。你需检查光模块是不是插紧了光功率有没有异常线缆有没有损坏。如果CRC没问题但是UDP校验和错误计数上涨那就是协议栈里校验和计算的逻辑有bug需要回仿真里去找。有了这些计数器你能快速缩小问题范围而不是一头雾水。6.3 以太网数据包分析PCAP与Wireshark过滤规则对抓包文件PCAP的分析能力直接关系到你调试问题的效率。这里有几个常用的Wireshark过滤表达式能在排查时帮上大忙udp.port 5001 tcp.port 5201 ip.src 192.168.1.10 eth.src 00:0a:35:01:02:03应用这些过滤规则可以迅速从海量数据包中筛选出可疑目标。尤其是当你的FPGA在高速发送、电脑端在用多线程接收时通过PCAP分析能够直观看到每个线程绑定的端口流量是否均匀来判断负载均衡策略是否生效。7. 实际调试过程中遇到的几个“坑”这部分我记录几个典型的、容易让人卡壳的调试案例。希望对你有帮助。7.1 CMAC链路状态对不上的问题现象逻辑复位完成后CMAC的gt_rxuserrdy始终为0链路始终up不起来。排查过程我用ILA抓取了CMAC的gt_powergood、gt_tx_resetdone、gt_rx_resetdone信号。发现gt_powergood为1但gt_tx_resetdone一直不拉高。通过查阅芯片手册怀疑是GT参考时钟从初始化阶段就不正确。用示波器测量了板上的GT参考时钟晶振发现频率正确但幅度不够导致GT始终检测不到有效参考时钟。更换了板上的参考时钟电路后复位顺利通过。结论上板遇到链路问题先查高速收发器的参考时钟和复位信号。信号完整性问题切记要用仪表确认不能只看寄存器。7.2 RX路径FIFO溢出导致的随机丢包现象在打流时总会有固定比例的丢包比如发10万个包丢20个还都是随机的。排查过程这种随机丢包最烦人很难在仿真里复现。这时我在FPGA逻辑里置了一个标志位只要接收FIFO的almost_full信号拉高过就置位并向串口打印。跑了几分钟打流串口果然打印了这个标志位。确认是FIFO溢出导致的。原因分析回看代码发现我的接收模块使用的是“存储转发”模式即必须收到整个完整帧后才把数据发给上层逻辑。这样FIFO的深度就需要大于一个最大帧长可能由于Jumbo Frame支持达到9KB但实际配置的深度只有4KB。当一个超大帧到达时FIFO肯定会满从而拉低tready造成背压而CMAC一旦检测到反压就会丢弃或打标当前帧。解决办法把FIFO深度扩到16KB或者把逻辑改成“直通模式”边收边发问题解决。7.3 发送时钟与逻辑时钟不同源的时序问题现象时序收敛跑不过特别是从逻辑时钟域到CMAC发送时钟域的跨时钟域路径总是出现很长的布线延迟。分析这是因为我的逻辑时钟和CMAC发送时钟是完全异步的导致了跨时钟域的数据同步问题。虽然在功能上没错但在时序上增加了很大的不确定性。解决办法在逻辑和CMAC之间加入一个异步FIFO将数据从逻辑时钟域同步到CMAC用户时钟域。这样一来两边只需各自约束时钟域的路径即可跨时钟域的路径被FIFO隔断时序立刻收敛。8. 写在最后的总结与感悟100G UDP的移植测试本质上是一个“标准化接口”对接问题只要硬件平台选型正确、IP核配置准确、总线协议吃得透大部分问题都是可以系统性解决的。整个过程里最花时间的往往不是你写代码的过程而是在继承的旧代码和第三方代码之间反复确认它们之间的约定与假设。我个人在这次项目中最大的收获不是“能跑100G UDP”这个结果而是更加系统地理解了一个真实的100G以太网数据通路里从物理层GT、到数据链路层MAC、再到网络层IP和传输层UDP各层之间的协作方式以及如何用开源的力量来补齐商业IP短板。最后再分享一个小技巧UDP协议栈这类东西看起来复杂但真正核心的处理逻辑就那么几块多仿真、多上板、多踩坑慢慢你对整套数据流的直觉就建立起来了。下次哪怕要把这套东西移植到别人的板子上或者从100G换到400G你也能心里有数。毕竟底层逻辑万变不离其宗速率提升只是换了个更宽的马路而已。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价