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以太网的有效数据率是100Gbps,1024bit乘以156.25MHz正好等于160Gbps的原始线速,去掉64B/66B编码的损耗后,正好落在100G的有效带宽上。
用户侧接口则是AXI4-Stream,这就有意思了。XGMII的1024bit位宽如果直接暴露给用户逻辑,会让用户侧的布线压力非常大,而且156MHz的时钟域对于大部分用户逻辑来说并不友好。所以verilog-ethernet在MAC内部做了位宽转换,把1024bit降到了512bit,时钟频率提升到322.265625MHz。512bit在UltraScale+系列的FPGA上是一个比较折中的位宽,既不会让布线太拥挤,也能保证FIFO的读写效率。
我这次用的板子是Xilinx UltraScale+系列的VU9P,GTM收发器跑100G(4路25G),FPGA逻辑侧的时钟正是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跑满100G
4.1 板级自检:先证明你的板子没有问题
上板测试的第一步,绝不是直接把电脑连到光口上就开始打流。而是要做几个基本的自检步骤,确认整条链路是完好的。
首先,检查GT的复位和时钟状态。Vivado的Hardware Manager里有一个IBERT(Integrated 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和IP,FPGA侧用一个假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打流:验证带宽和丢包率
电脑端跑iperf3,FPGA端则需要实现一个简单的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 up | GT参考时钟异常、光模块供电不足、光纤收发接反 | 检查时钟芯片输出、用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也只是位宽和通道数的问题而已。