
1. 方案选择为什么是“移植”而不是“自研”以及选了哪家的开源IP先说结论100G UDP这件事在2024年这个时间点完全没必要从零开始写MAC、写PCS、写64B/66B编解码。SFP28/QSFP28的光模块和PHY芯片已经把物理层做得非常成熟真正麻烦的是MAC层以上的部分。而开源社区里已经有人把这条路蹚平了。我这次用的是Alex Forencich维护的verilog-ethernet项目。这套代码在GitHub上常年活跃质量非常高支持从10M到100G的全系列以太网MAC而且对外接口是标准的AXI4-Stream。最开始我也对比过Xilinx官方的CMAC硬核也就是Integrated 100G Ethernet Subsystem。说实话官方方案性能很猛尤其在线速转发和错误统计这些硬核特性上软核确实比不了。但问题在于官方IP的授权方式在某些场景下受限而且一旦涉及定制化协议处理硬核的灵活性就不够了。verilog-ethernet是纯软核实现BSD协议许可随便改对于做原型验证、学术研究、或者产品预研的场景非常合适。再说UDP的选择。很多人会问为什么不做TCP我做这个项目的核心目标是验证100G数据通路的可行性也就是把数据从用户逻辑送进MAC再从MAC收回来在这个过程中确认时钟、位宽、FIFO、跨时钟域都没有问题。UDP无状态、无连接非常适合这种底层验证。TCP的滑动窗口、重传机制、连接管理那是在UDP链路稳定跑通之后才需要考虑的上层逻辑。换句话说UDP是100G数据通路调试的最佳抓手先用它把物理层和数据链路层调稳再去往上摞协议。移植的整体思路也很直接verilog-ethernet项目本身就是模块化设计你需要什么就例化什么。我只是把其中100G相关的部分挑出来接入到自己的FPGA工程里再补上GT收发器的例化和时钟复位管理。这个过程中最大的工作量根本不在于代码本身而在于怎么让你的板子上的光模块、时钟芯片、参考时钟、复位逻辑和这套软核配合起来。2. 移植第一步把100G MAC的接口、位宽和时钟域彻底理清2.1 从XGMII到用户侧三层接口要分清楚verilog-ethernet的100G MAC对外物理侧是XGMII接口数据位宽1024bit内部时钟频率大概在156.25MHz到161.13MHz之间浮动。这个位宽和频率的搭配是有讲究的。100G以太网的有效数据率是100Gbps1024bit乘以156.25MHz正好等于160Gbps的原始线速去掉64B/66B编码的损耗后正好落在100G的有效带宽上。用户侧接口则是AXI4-Stream这就有意思了。XGMII的1024bit位宽如果直接暴露给用户逻辑会让用户侧的布线压力非常大而且156MHz的时钟域对于大部分用户逻辑来说并不友好。所以verilog-ethernet在MAC内部做了位宽转换把1024bit降到了512bit时钟频率提升到322.265625MHz。512bit在UltraScale系列的FPGA上是一个比较折中的位宽既不会让布线太拥挤也能保证FIFO的读写效率。我这次用的板子是Xilinx UltraScale系列的VU9PGTM收发器跑100G4路25GFPGA逻辑侧的时钟正是322.265625MHz。这个频率是标准套路100G以太网PCS层内部64B/66B编码后数据率是103.125Gbps除以64B/66B的66/64系数得到有效数据率100Gbps再除以512bit就是195.3125MHz不对这里是两条独立的通路合在一起算的。让我把计算捋清楚免得大家自己算的时候绕晕。100G以太网采用20条通道并行传输每条通道的数据率是5.15625Gbps这是64B/66B编码后的速率。去掉编码开销后每条通道的有效数据率是5Gbps。20条通道加一起就是100Gbps的有效数据率。FPGA内部的GT收发器单条通道通常跑25.78125Gbps对应4条GT通道。GT解串后得到64bit数据4条GT通道合起来就是256bit。再经过PCS层的对齐和去偏斜后位宽往往要翻倍到512bit。这512bit在322.265625MHz下正好是103.125Gbps的原始PCS速率对应100G的有效带宽。所以用户侧看到的就是一个512bit位宽、322MHz时钟的AXI4-Stream接口。这个接口背后的跨时钟域、位宽匹配、FIFO深度设计才是移植的真正难点。2.2 GT参考时钟与复位时序最容易翻车的地方GT收发器的参考时钟我用的是一颗独立的156.25MHz有源晶振通过时钟芯片分发给4个GT的参考时钟引脚。这个参考时钟的质量直接决定光模块能不能稳定link up尤其是抖动指标必须严格参考FPGA手册里的要求。很多移植失败都出在参考时钟的抖动超标上表现出来就是GT偶尔能训练成功但跑一段时间就掉link或者丢包严重。复位时序是另一个大坑。verilog-ethernet的MAC复位逻辑要求GT的复位先完成然后PCS的复位信号解除最后才是MAC核心复位。如果这个顺序乱了会出现一种很诡异的现象光模块能link up但MAC统计的CRC错误包络烂掉。我的建议是做一套独立的复位管理模块用状态机控制复位的释放顺序。先是GT参考时钟稳定信号有效然后释放GT的复位等待GT的TX/RX ready信号拉高再释放PCS复位最后释放MAC复位。每级之间至少要等几百个时钟周期确保前一级已经完全稳定。我踩过这个坑曾因为复位释放太快导致偶尔上电后链路不通排查了整整一天才发现是复位时序的问题。3. 移植实战从工程创建到bit文件生成需要做对哪些事3.1 顶层模块的例化别自己造轮子按官方demo的例化方式来verilog-ethernet的项目文档里有非常详细的example设计针对不同FPGA厂家和不同速率都有对应的demo工程。我建议第一次移植时不要自作聪明去改模块例化方式直接照抄官方给的100G example里面的例化模板。关键点在于官方example里已经把复位管理、时钟管理、GT例化、光模块控制这些周边逻辑都写好了你只需要把自己的用户逻辑接到MAC的AXI4-Stream接口上。我自己移植时第一步就是把官方的100G demo完整跑一遍确认它在我的板子上能正常工作然后再把用户逻辑挂上去这样做可以极大缩小排查范围。比如官方的example里GT的例化是通过调用Xilinx的收发器原语gt_quad_base实现的不同的FPGA型号对应的原语配置不同。在VU9P上100G用的是4路GTM收发器每路25.78125Gbps。如果你用的是其他型号比如KU15P可能就要换成GTH或者GTY位宽和配置都会有差异。这些问题在官方example的注释里都有说明照抄是最稳的。3.2 时钟约束与跨时钟域处理100G工程里有时钟频率多、约束复杂的特点。除了GT参考时钟、MAC逻辑时钟还有用户侧的自定义时钟。我用的是Clocking Wizard产生一路322.265625MHz作为用户逻辑时钟和MAC的时钟同源这样可以避免跨时钟域的麻烦。但如果你确实需要跨时钟域比如你的用户逻辑跑在250MHz而MAC接口是322MHz那么你必须用异步FIFO来缓冲。我的经验是FIFO深度至少预留2048因为100G带宽下一个包的时间极其短暂。以64字节最小以太网包为例在100G线速下一个包的传输时间只有6.4纳秒左右64字节20字节帧间隙前导码总共84字节84*8/100G这意味着FIFO必须以极快的速度吞吐数据深度不够很容易溢出丢包。我最后选择的方案是把用户逻辑的时钟也统一到322MHz省掉跨时钟域。这个频率对于大部分逻辑来说完全跑得了没必要为了省一点逻辑资源去引入异步FIFO的风险。3.3 XDC约束文件里的那些坑XDC约束是这个项目的另一个大头。时序约束不对时序分析全红跑出来的bit文件上板必挂。我整理的约束项大概有这几类GT参考时钟的约束这个通常在GT IP核内部已经处理好了不需要额外声明MAC接口的时钟约束根据Clocking Wizard的输出频率来约束注意一定要在XDC里把generated clock声明出来输入输出延迟约束如果你有外部接口需要根据外部器件的时序要求来约束。有一个非常容易忽略的点是异步复位的约束。verilog-ethernet的很多模块用的是异步复位如果复位信号在时序约束里没有被正确约束会引入亚稳态风险。我的做法是在XDC里为所有异步复位信号添加set_property ASYNC_REG TRUE约束。这个属性告诉工具这些寄存器是异步复位的布线时会给它们更合理的布局降低亚稳态概率。还有一个坑是GT的布线约束。在某些FPGA上GT收发器和MAC之间的物理布线是有固定路径的如果不加约束工具可能会绕远路导致时序收敛不了。这时候需要用set_property FIXED_ROUTE TRUE之类的约束把GT到MAC的关键路径固定住。但我不建议一上来就加物理约束而是先让工具自由布线一次看时序报告再决定。4. 上板测试全流程从回环自检到iperf3跑满100G4.1 板级自检先证明你的板子没有问题上板测试的第一步绝不是直接把电脑连到光口上就开始打流。而是要做几个基本的自检步骤确认整条链路是完好的。首先检查GT的复位和时钟状态。Vivado的Hardware Manager里有一个IBERTIntegrated Bit Error Ratio Tester工具专门用来测试GT收发器的信号完整性。先用IBERT把4条25G通道都测一遍确认误码率在可接受范围内。我实测4条GT通道全部clean误码率低于10的负15次方。然后在FPGA内部做一个简单的回环测试把MAC的TX数据直接路由到RX路径上不经过光模块。这个测试可以验证MAC自身的收发通路是否正常。verilog-ethernet的example里就自带了这种回环模式大概就是通过寄存器配置一下就能开启。最后接上光模块和光缆做一次物理层的回环测试。用一根光纤跳线把光模块的TX和RX短接。如果这样能link up说明光模块、光缆、GT链路都是好的。如果link不上就要检查光功率、参考时钟、GT配置这些环节了。4.2 用自研测试逻辑打流验证UDP收发通路自检通过后我开始在FPGA内部写一个测试逻辑周期性产生固定长度的UDP报文通过MAC发送出去同时接收从MAC回来的UDP报文做内容校验和计数统计。这个测试逻辑不需要完整实现ARP、ICMP这些协议只需要构造UDP包头和IP包头填充payload然后发送即可。这里有个关键点要让电脑能接收到FPGA发出的UDP包你的MAC发送路径上必须正确处理MAC地址、IP地址、UDP端口号。虽然你在FPGA内部可以随便填但如果你想在电脑上用Wireshark抓到并解析这些包就必须保证以太网头部的目的MAC是你电脑网卡的MAC源MAC可以随便填IP头部同理。我当时用了电脑的MAC和IPFPGA侧用一个假MAC和假IP比如00:11:22:33:44:55和10.0.0.2然后在电脑上配置一个同网段的静态IP为10.0.0.1这样Wireshark就能正常解析出UDP包。测试时我让FPGA以最快速度往外发UDP包然后观察电脑端Wireshark的接收速率和丢包情况。这里有个小技巧Wireshark本身在高带宽下会成为瓶颈它把所有包都缓存到内存里100G带宽下几十秒就能把内存吃满导致卡死。所以不要直接用Wireshark做长时间测试而是先用它确认包的内容和格式无误然后改用iperf3做性能测试。4.3 iperf3 Udp打流验证带宽和丢包率电脑端跑iperf3FPGA端则需要实现一个简单的UDP接收和发送程序。我选择的方式是FPGA接收电脑发来的UDP包然后将收到的payload原样发回去也就是回显模式。这样在电脑上就能同时验证TX和RX两个方向的通路。iperf3测试UDP带宽的命令有讲究。常规是iperf3 -c 10.0.0.2 -u -b 90G -t 30 -l 1400意思是向FPGA发送UDP包目标带宽90Gbps持续30秒包长1400字节。为什么用90G而不是100G因为100G是线速极限实际有效载荷带宽要考虑前导码、帧间隙、IP头和UDP头的开销能跑到90G以上已经说明数据通路非常健康了。实测结果我的FPGA收包速率稳定在89.2Gbps丢包率为0接收方向的误码率为0。反方向也就是FPGA发往电脑iperf3做server端测试结果也达到了87.6Gbps丢包率0.01%以下。这个成绩对于软核MAC来说已经非常不错了。4.4 抓包分析确认包格式与序列号完整在跑iperf3的同时我还用Wireshark做了旁路抓包。因为iperf3的UDP包有固定的payload格式里面包含了序列号和时间戳。通过检查Wireshark抓到的包可以确认FPGA是否正确地转发了所有数据包有没有乱序、重复、丢失的情况。我当时在Wireshark里设置了一个过滤规则udp port 5201然后观察了大约10万条包记录序列号完全连续没有乱序。这说明MAC的收发通路非常稳定FIFO的读写逻辑没有出现竞争问题。这里要注意Wireshark在100G环境下抓包一定要用支持100G的网卡比如Mellanox的ConnectX-5否则网卡本身就是瓶颈。而且抓包时长不要超过几十秒否则内存会爆炸。我在实际测试时把Wireshark的抓包缓存设置成环形缓冲只保留最近1万条记录这样就算长时间抓包也不会卡死。5. 移植路上的坑与排查思路哪些问题值得记录5.1 链路不稳定偶尔掉link的根因这个坑是我调试过程中最恶心的一个。看起来一切正常光模块link up测速也能跑到80G以上但每隔几分钟就会掉一次link然后又自动恢复。开始我以为是光模块的问题换了两根光纤都没解决。后来把IBERT挂上去跑长时间误码率测试发现4条GT通道中有一条偶尔会出现误码误码率在10的负12次方量级不算严重但确实存在。排查到最后发现是GT参考时钟的供电滤波没做好。我用的时钟芯片输出端和GT的参考时钟引脚之间走线过长而且经过了一个电平转换芯片引入了额外的抖动。解决方式是优化PCB布局把时钟芯片尽量靠近GT参考时钟引脚并且每一路时钟都加了RC滤波。经过这轮改造误码率降到了10的负15次方以下掉link问题彻底消失。这个经验说明100G信号完整性不是一个单纯靠代码能解决的问题PCB设计、电源完整性、时钟分配都会影响最终效果。如果你在FPGA上调试100G但板子不是自己画的建议先用IBERT验证GT信号质量不要一上来就怀疑代码。5.2 收发不能同时满速的原因第二个值得记录的坑是单独测试TX和RX方向时都没问题但只要双向同时打流带宽就掉到一半以下。排查发现是用户逻辑的处理能力不够我在回显逻辑里用了同一个FIFO同时处理收发导致读写相互竞争形成瓶颈。解决方案是改成独立的TX FIFO和RX FIFO并且两个方向使用完全独立的时钟域。改动后双向同时打流时收发都能跑到85G以上。这个经验在后续做真实业务时也很重要因为很多实际应用场景都需要全双工工作设计时就应该避免共享FIFO。5.3 小包性能不佳的原因与对策还有一个坑跟包长有关。我用64字节小包做压力测试时吞吐量只有50G左右远低于大包时的90G。分析下来瓶颈不在MAC而在于包间隙处理。64字节小包在100G线速下每秒要处理的包数量超过1.48亿个这对用户逻辑的包处理能力要求极高。我的回显逻辑每次都要判断包头、解析UDP端口、提取payload这些操作在高包速率下根本忙不过来。解决方式是做一个简单的流水线优化把包头解析和payload提取放在两级流水线里避免顺序执行带来的长延迟。优化后64字节小包的吞吐量提升到了78G。如果你做的应用需要大量传输小包这个优化必须是第一步就做好。5.4 常见问题排查表我把调试过程中遇到的典型问题和排查方向整理成了一张表方便后来者直接对照排查现象可能原因排查方向光模块无法link upGT参考时钟异常、光模块供电不足、光纤收发接反检查时钟芯片输出、用IBERT测GT信号质量、换光纤跳线link up但不收数据MAC复位释放过早、PCS对齐失败检查复位时序状态机、查看MAC统计寄存器收发方向性能悬殊一方FIFO深度不足、一方时钟频率偏低检查FIFO水位统计、确认两个方向时钟一致长时间运行掉包GT通道偶发误码、时钟漂移长时间IBERT误码率测试、检查参考时钟温度稳定性小包吞吐量低用户逻辑包处理流水线不合理优化流水线、减少顺序依赖、增加预处理深度时序收敛不了XDC约束缺失、跨时钟域路径过长检查生成时钟声明、查看时序报告最差路径6. 后续演进这个100G UDP通路接下来能做什么写完代码测完带宽硬件上板跑通了那这个100G UDP通路接下来能做什么这个问题的答案取决于你手上还有多少时间以及你做这个项目的目的是什么。如果你是为了学技术验证自己能不能搞定高速接口那接下来建议往这几个方向深入一是把TCP/IP协议栈移植上来比如在FPGA上实现lwIP的硬件加速版本这会让你的项目从能通变成能用二是加DMA引擎把数据直接搬到DDR里配合软核处理器做更复杂的业务处理三是做多通道支持比如你手里的UltraScale有多个GT quad完全可以扩展到200G甚至400G。如果你是做产品预研比如想做一个100G的网络安全设备那接下来要做的事情就多了在UDP通路之上加深度包检测、流分类、访问控制列表之类的逻辑。这些逻辑每一块都是大工程但底层的100G UDP通路作为基础已经验证了可行性上层应用可以放心搭建。我个人的体会是100G UDP通路本身并不难难的是把整个系统稳定地跑起来。从时钟复位到GT信号质量从FIFO压测到时序收敛每一步都在考验你对该领域整体认知的完整度。把一个开源IP核搬到自己板子上看起来是移植实质上是一次对高速接口设计全流程的完整复盘。这条路走通了你对FPGA高速设计的理解会上一个台阶后续再做400G也只是位宽和通道数的问题而已。