做100G网络这块有一段时间了,最近把一个开源的UDP协议栈移植到了Xilinx UltraScale+平台上,跑通了100Gbps的线速收发。从拿到开源代码到上板测出满速,中间踩了不少坑,也梳理出不少关键点。今天把这套移植上板测试的完整过程整理出来,包括方案选型、工程搭建、代码适配、打流验证、问题排查,希望能给正在搞100G以太网和FPGA的朋友一些参考,尤其是那些准备拿开源UDP方案快速起步的同学,这篇文章应该能帮你少走不少弯路。
这个需求在数据中心、高性能计算、高速数据采集、信号处理板卡互联这些场景里特别常见,一块FPGA要把高速数据从板卡上搬出去,或者从网络里收进来,UDP是最实用、最少开销的协议选择。很多自研交换机、网络加速卡、金融行情加速、雷达信号处理板卡,底层跑的都是这套东西。对于刚接触高速以太网的工程师来说,开源UDP协议栈加100G CMAC核是一个门槛相对低、验证充分的起点。接下来我直接按整个项目的推进顺序来写,从设计思路到最后的实测定型,尽量把细节都交代清楚。
1. 项目整体设计与思路拆解
1.1 100G UDP协议栈移植的本质是什么
很多朋友一听“100G UDP”,第一反应是拿MicroBlaze或者Zynq软核去跑LwIP,但这条路在100G带宽下根本走不通。通用处理器跑协议栈,单核能跑到千兆线速已经很吃力了,到了10G就必须多队列加硬件卸载,100G基本是纯硬件电路的活儿。所以我们这颗板卡上所有的UDP收发逻辑都是用Verilog在可编程逻辑里写死或者用IP核生成的,整个数据路径上没有一个CPU参与。
那“移植”到底是移什么?简单说,把开源的UDP协议栈代码拿过来,接到商用或开源的100G以太网MAC上,再配上板卡自己的用户逻辑接口。具体拆开是这样几块:
- 物理层:100G光模块加高速SerDes,这部分由Xilinx集成在以太网IP核里,我们基本不碰物理信号。
- 数据链路层:100G以太网MAC,负责成帧、CRC校验、流控、前导码处理。UltraScale+上叫CMAC,是硬核,不需要额外付费。
- 网络层和传输层:UDP/IP协议栈,负责组IP包头、算校验和、做ARP、ICMP回应、IP分片重组这些逻辑。这部分是开源代码的核心,也是移植工作量的集中地。
- 用户接口:协议栈和用户逻辑之间的数据通道,通常是AXI-Stream,一端连MAC,一端连用户应用。
整条链路的数据流是用户逻辑产生数据,封装成UDP包发出去;收到的网络包拆掉以太网和IP头,把载荷交给用户逻辑处理。
1.2 为什么选择开源方案,省了什么又费了什么
选开源方案核心原因是时间成本。商用UDP协议栈IP一般按带宽计费,100G级别的授权费不低,而且商务流程、NDA、评估授权往往要几周时间。开源方案随手就能拿到完整的RTL代码,GitHub上拉下来直接看逻辑,心里踏实。另一个原因是可定制性,很多商用IP是网表或者加密代码,出了问题只能找原厂FAE,开源代码你能自己加探针、加统计、改接口时序,这对后面调板子特别重要。
但那句老话得说在前面,开源不等于免费。省了授权费,费的是集成和调试的精力。你拿到的开源代码是针对某种MAC接口、某个时钟域、某种位宽写的,你的平台上CMAC核的接口位宽可能是512bit,开源代码可能只适配了64bit或256bit,整个AXI数据通路的时序、跨时钟域处理、FIFO深度都得重新审视和调整。更别说上板之后遇到丢包、校验错误、时序收敛问题,调试难度远高于仿真。
我选的方案是Alex Forencich的开源verilog-ethernet项目,这个在业内认可度很高,代码风格干净、结构清晰、文档也还凑合。它包含以太网MAC、UDP协议栈、ARP、ICMP、CRC等等一堆模块,最重要的是它提供了一个独立成型的udp_ip_stack顶层模块,接口是标准AXI-Stream,做适配非常方便。另外我B站上还看到同济子豪兄的开源机器鸭那类项目,用的是国产开发板做的网络通信,虽然速率没有100G那么夸张,但整个开源思路和学习路径是相通的,对刚入手的朋友可以参考参考。
1.3 目标架构:从MAC到用户的完整数据通路
在动代码之前,先把整条数据通路画清楚,这一步决定了后面所有接口的定义和时序约束。我的目标架构是这样的:
- 100G QSFP28光模块,四路25G SerDes接进FPGA;
- CMAC硬核,配置成100G模式,数据位宽512bit,用户时钟322.265625MHz;
- CMAC的AXI-Stream接口接到udp_ip_stack顶层;
- UDP协议栈输出用户侧的AXI-Stream,接到DMA模块或者自定义的用户逻辑;
- 用户逻辑负责产生测试数据和消费数据,以及统计收发帧数、字节数。
这里有一个关键点:CMAC的收发时钟和用户逻辑的时钟可能不在同一个时钟域,UDP协议栈内部也会重新打拍同步,所以跨时钟域的处理是移植里最容易出问题的地方。我在协议栈和CMAC之间加了异步FIFO,在协议栈和用户逻辑之间也加了异步FIFO,确保两边的时钟域隔离,同时用FIFO的almost_full信号反压用户逻辑。
为什么UDP能跑线速而TCP不行,这个也得提前说清楚。UDP协议栈是无连接的,不需要维护连接状态、不需要序号确认、不需要窗口管理和重传,所以状态机非常简单,每个包的处理延迟是固定的流水线级数。TCP协议栈在FPGA里做得好的也有,但复杂度高一个数量级,要维护每条连接的收发状态、定时器、重传缓冲区,100G线速TCP基本需要多连接并行处理。在高速板卡互联场景里,可靠传输通常由上层应用自己处理,或者靠链路层的流控,所以UDP是绝对主流的选择。
2. 硬件平台与工具链准备
2.1 开发板选型:UltraScale+是100G的底线
不是说其他平台不能做,而是UltraScale+这个级别做100G最顺手。主要是两个原因:一是内置CMAC硬核,二是SerDes速率和数量足够。如果你用的是Kintex-7这代器件,100G需要外接PHY芯片或者用软核MAC加GTY,不仅时序压力大,而且逻辑资源占用非常夸张,几乎没有实用价值。到了UltraScale+这一代,100G MAC直接在芯片内部,GTY SerDes也支持100G速率,一片VU9P就能搞定4个100G端口。
选型的时候重点看这几项资源:
- 逻辑资源:LUT和FF至少要到30万以上,UDP协议栈加用户逻辑再加缓存控制,大概能吃掉一两万LUT,留足余量给后续扩展。
- BRAM/URAM:跨时钟域FIFO和包缓存是BRAM大户,尤其100G带宽下FIFO深度不能太小,不然瞬间拥塞就丢包。
- 高速收发器:QSFP28端口对应的GTY通道数量要够。
- 硬核MAC:UltraScale+上叫CMAC,VU9P这种大器件自带多个CMAC实例。
- DDR控制器:如果要在板卡上做大缓存或者协议处理,DDR4的控制权也要规划好。
我用的是一块自研板卡,VU9P主芯片,板载两个QSFP28光口,一个DDR4 SODIMM插槽,整板供电和时钟设计都比较完善。这块板子最大的好处是CMAC的参考时钟走的是专用的低抖动时钟树,对100G的SerDes眼图影响很大,换板子一定要确认这一点。
2.2 Vivado工程搭建:版本、IP配置和约束
Vivado 2022.2,这是当前比较稳定的版本,对UltraScale+支持成熟。工程结构上我是按RTL、IP、约束、仿真四块分开放的,这样多人协作和后续版本管理都方便。
CMAC的配置有几个关键项,得仔细说:
- Line Rate:选100G,接口位宽选512bit,这时用户时钟是322.265625MHz,这是一个派生时钟,必须用PLL生成,不能直接在约束里乱写。
- 数据接口选AXI-Stream不带TUSER,还是带TUSER,会影响后面协议栈适配。我习惯带TUSER,因为TUSER里可以带CRC结果和错误标记,方便调试。
- CRC处理和Preamble PassThrough这些选项,按CMAC的默认设置来,协议栈代码自己会重新处理校验。
- 流控选项:100G下建议开启Priority-based Flow Control或者至少让CMAC的RS-FEC处于开启状态。这里有个坑,如果光模块链路质量不够好,丢包可能是物理层误码导致的,和协议栈一点关系都没有,所以RS-FEC能开就开。
约束文件方面,除了时钟约束,最需要注意的是异步FIFO的时钟域约束。Vivado会自动推断CDC(Clock Domain Crossing)路径,但建议手动设置set_clock_groups -asynchronous,把CMAC时钟域、用户逻辑时钟域、DDR时钟域分开,避免时序分析报出大量跨时钟域违例。我见过很多新手的工程就是没设这组约束,Vivado时序报告里全是红的FIFO跨时钟路径,根本没法收敛。
工具链还有一个小建议:仿真和上板分开建工程,不要用一个工程又仿又跑。仿真要用仿真专用的IP配置(比如CMAC会变成behavioral model),上板要用综合后的IP配置,两套混在一起很容易出髒版本。我是直接复制工程文件夹,仿真和上板各用各的,省了很多麻烦。
3. 开源UDP协议栈移植实操
3.1 开源IP结构解析:先摸清每一级模块
从GitHub拉下来的代码目录结构清晰度很重要,我用的verilog-ethernet项目顶层是rtl/udp_ip_stack.v,里面例化了UDP、IP、ARP、ICMP这些子模块。刚开始不要急着改代码,先把每个模块的接口信号和用途搞清楚。我习惯把每个模块的例化关系画成树状图,标清楚每个端口的来源和去向,这样后面改一处地方能快速评估影响。
我的分析顺序是:
- 先从接口上看,udp_ip_stack对外有哪些端口,哪些是接MAC的,哪些是接用户逻辑的;
- 然后看内部数据流,发送方向用户数据怎么打包成IP包,接收方向网络包怎么解包;
- 最后看配置和管理接口,比如MAC地址、IP地址、ARP表、校验和控制怎么配置。
这个项目里udp_ip_stack对外接口大致分为三类:
- 接MAC的AXI-Stream收发接口,发送和接收各一组;
- 接用户逻辑的AXI-Stream收发接口,发送和接收各一组;
- 配置端口,包括本机MAC地址、本机IP地址、子网掩码、网关IP、ARP缓存操作等。
接口信号名字很规范,mac_tx_、mac_rx_、udp_tx_*、udp_rx_*这样的命名一眼就能认出来。需要注意的是,AXI-Stream的握手信号tvalid/tready/tlast/tdata/tkeep这套是标准接口,但不同版本vaild和ready的时序策略可能有细微差别,比如是否允许在tlast之后立即开始下一包。
3.2 移植过程中的核心改造点
接入CMAC的AXI-Stream位宽匹配是第一个要解决的问题。开源ip_stack通常用的是64bit数据位宽,而CMAC是512bit,位宽不一致导致整个接口适配层必须改。
我采用的方案是在中间加一个位宽转换模块,把512bit转成64bit(或者反过来),配一个异步FIFO做时钟域隔离。这个转换模块的难点是tkeep和tlast的处理,跨位宽转换时tlast的位置会变,tkeep也要跟着调整,写不好就会把包长度弄错。我实测下来,如果只是纯位宽转换不加FIFO,逻辑资源会省一点,但时序很难收敛,尤其是512bit到64bit这种大跨度转换,组合逻辑太长,跑到322MHz基本不可能。所以老老实实加FIFO,用两级流水换时序收敛。
第二个改造点是用户侧接口的协议适配。开源项目的用户侧接口是AXI-Stream read和write分离的,每个方向都有自己的tdest/tuser这些信号。我的用户逻辑是DMA写的,数据格式是按descriptor组织的,必须做一个小的转换桥,把DMA的通道映射到UDP协议栈的AXI-Stream上。这里我多花了一些时间,因为DMA的burst长度和UDP包长不是对齐的,需要在发送方向把DMA burst拆成多个UDP包,接收方向把UDP包拼成DMA burst。
第三个改造点是出端口逻辑。ip_stack顶层默认只有一组MAC接口,但我的板卡有2个QSFP28光口,要做端口选择。我的做法是在MAC接口层做一个简单的交叉开关(crossbar),根据目的MAC或者用户指定的端口号把数据路由到对应的CMAC。这个功能虽然小,但涉及收发包的两条路径,状态机写起来要特别小心,不然容易丢包或者错序。
第四个需要注意的点是ARP和ICMP。100G UDP能跑通,但你在局域网里和PC通信,PC首先要发ARP请求解析MAC地址,UDP协议栈必须正确回应ARP,不然数据包根本发不过去。开源代码里ARP模块是有的,但需要对一下它的接口和你的配置方式。ICMP回应用于ping测试,建议保留,排查链路不通时特别有用,一个ping通了就说明物理层、MAC层、IP层基本没问题。
3.3 关键代码和参数设置示例
这里记录一段我当时最常改的参数配置,方便大家参考。在本机MAC和IP初始化部分,开源工程通常用一个parameter或者寄存器来配置。我用的是寄存器方式,上电时由软核写入:
// 配置本机MAC地址 00:11:22:33:44:55 assign local_mac[47:0] = 48'h001122334455; // 配置本机IP地址 192.168.1.10 assign local_ip[31:0] = 32'hc0a8010a; // 配置子网掩码 255.255.255.0 assign subnet_mask[31:0] = 32'hffffff00; // 配置网关IP 192.168.1.1 assign gateway_ip[31:0] = 32'hc0a80101;发送一个UDP包的用户逻辑侧控制信号大致是这样的格式:
// 用户侧发送UDP包 assign udp_tx_tvalid = user_send_valid; assign udp_tx_tdata = user_send_data; assign udp_tx_tkeep = user_send_keep; assign udp_tx_tlast = user_send_last; assign udp_tx_tdest = 8'h01; // 目标端口选择 assign udp_tx_tuser = user_send_user;目标端口的选择要看协议栈的端口定义,我用的这个工程是通过tdest来区分不同的目标IP和端口,需要在代码里自己维护一张端口映射表。这个设计用起来很方便,用户逻辑只需要设置一个tdest值,协议栈内部就会自动查表封装对应的目标MAC、目标IP、源端口、目的端口。
4. 上板验证与性能测试
4.1 上板前的准备:时钟约束和ILA调试
上板不是代码编译过了就直接跑的,尤其是100G这种高速接口,物理层的准备必不可少。我先用IBERT(集成误码率测试工具)验证光模块和SerDes链路,确保误码率为零或者极低。IBERT通过JTAG或者PCIe接口访问GTY收发器,直接做PRBS测试,几分钟就能确认物理层是否可靠。这一步非常关键,很多时候链路速率跑不上去并不是逻辑问题,而是SerDes参数不对,IBERT能帮你排除掉这个变量。
逻辑上板之前,我习惯在关键节点插入ILA(集成逻辑分析仪)探针:
- CMAC到协议栈的发送路径上,观察tvalid/tready/tlast/tdata的时序;
- 协议栈到用户逻辑的接收路径上,观察包头的字段解析是否正确;
- ARP请求处理的状态机,观察是否有效响应。
ILA的采样深度根据FPGA BRAM资源来定,100G速率下采样深度太小根本抓不到完整的包,我一般采样深度设成65536,采样时钟用对应接口的时钟。有一点要注意,ILA本身会占用大量布线资源,插入之后对时序收敛可能有影响,所以上板验证时先用一个低占用版本,等确认功能正常后再去掉ILA做完整时序收敛。
4.2 iperf3 UDP打流测试:从千兆到100G逐级验证
平台准备好之后,先用小带宽测试,再用大带宽测试。这一步我用iperf3在服务器和FPGA之间打UDP流,服务器装的是Mellanox ConnectX-5 100G网卡,FPGA侧用ILA统计收发包数量。
iperf3的命令大概是:
# 服务器作为接收端 iperf3 -s -p 5201 -i 1 # FPGA侧通过PC端发起UDP打流 # 这里注意带宽参数,先从小带宽开始 iperf3 -c 192.168.1.10 -u -b 100M -p 5201 -l 1400 -t 30 iperf3 -c 192.168.1.10 -u -b 1G -p 5201 -l 1400 -t 30 iperf3 -c 192.168.1.10 -u -b 10G -p 5201 -l 1400 -t 30 iperf3 -c 192.168.1.10 -u -b 50G -p 5201 -l 1400 -t 30测试下来发现,小带宽下丢包率都是0,一到50G以上就开始在服务器侧看到接收丢包。一开始我怀疑是FPGA协议栈问题,后来排查发现是服务器侧的UDP接收缓冲区不够大。Linux默认的UDP接收缓冲对百Gbps来说太小了,需要调大系统参数:
sudo sysctl -w net.core.rmem_max=67108864 sudo sysctl -w net.core.rmem_default=67108864 sudo sysctl -w net.core.wmem_max=67108864 sudo sysctl -w net.core.wmem_default=67108864调整之后,50G掉包的现象基本消失。这里也提醒一下,用iperf3做100G打流测试,服务器的网卡设置也很关键。如果网卡的RSS、GRO、LRO这些卸载功能没有正确配置,也会造成接收端CPU处理不过来而丢包,即使缓冲区调大了也没用。我当时的做法是关闭GRO和LRO:
sudo ethtool -K enp3s0f0 gro off sudo ethtool -K enp3s0f0 lro off最终测到接近满速,FPGA转发方向的线速数据率达到98.5Gbps,按1500字节帧长计算,pps大概是800万左右。这个速度意味着每秒钟处理800万个UDP包,CMAC和协议栈之间的握手效率至关重要,任何一个慢下来都会成为瓶颈。
4.3 Wireshark抓包验证:协议栈行为分析
除了打流,还要用Wireshark在PC侧抓包,确认协议栈封装的包格式完全正确。抓包以后主要看几个点:
- 以太网头:目的MAC、源MAC是否正确;
- IP头:版本、IHL、总长度、协议字段(应为17即UDP)、校验和是否有效;
- UDP头:源端口、目的端口、长度、校验和(UDP校验和可以为零,具体看实现);
- ARP交互:PC发ARP请求,FPGA是否正确回ARP响应;
- ICMP:PC ping FPGA,是否能正常回ICMP echo reply。
Wireshark的显示过滤器可以这样用:
udp arp icmp ip.addr == 192.168.1.10有一次抓包发现UDP校验和总是被Wireshark标记为错误的,找了半天发现不是协议栈问题,而是网卡的UDP checksum offload在作怪。Mellanox网卡默认开启了UDP checksum offload,抓包的时候显示的是网卡写入的伪校验值,和实际线上的包不一样。关掉网卡的checksum offload再看就正常了:
sudo ethtool -K enp3s0f0 tx-checksum-ip-generic off sudo ethtool -K enp3s0f0 rx-checksumming off这个坑非常经典,提醒大家抓包验证协议栈时,一定要关掉网卡的硬件校验和卸载,否则会得到很多误报。
5. 常见问题与排查技巧实录
5.1 一张问题速查表,先对着查一遍
| 现象 | 排查方向 | 解决建议 |
|---|---|---|
| 端口link up但打流无流量 | 物理层/CMAC配置 | 先用IBERT测试SerDes,再用CMAC自带计数器看收发包数量 |
| 小带宽正常大带宽丢包 | 接收端缓冲区/AXI流控 | 调大UDP收发缓冲,检查FIFO的almost_full反压是否生效 |
| PC ping不通FPGA | ARP/ICMP/Routing | 抓包看ARP是否响应,确认本机MAC/IP配置是否正确 |
| Wireshark报UDP校验和错误 | 网卡checksum offload | 关闭网卡硬件校验卸载,重新抓包 |
| 时序收敛不过 | 跨时钟域FIFO/ILA占用 | 加set_clock_groups异步约束,去掉或减小ILA采样深度 |
| 100G线速跑不满 | 数据路径瓶颈 | 分析CMAC tready拉低的占比,检查位宽转换和FIFO深度 |
| 丢包是周期性或者随机的 | 光模块误码/SERDES参数 | 用IBERT长时间PRBS测试,调整TX emphasis参数 |
5.2 时序收敛不了是常态,心态和方法都要调整
100G工程的时序收敛是出了名的痛苦。主要压力是CMAC用户时钟322.265625MHz,这个速率下任何一条关键路径的组合逻辑都不能太长,否则直接红。我的经验是按模块分别约束、分组收敛,不要指望一次place and route全部通过。
常见的几个优化手段:
- 在数据路径上插入流水寄存器,用面积换时序。比如位宽转换逻辑,拆成多级流水,每级只做一小段组合逻辑。
- 不要用纯组合逻辑做复杂的包头解析,改成每个周期只处理一个字段,分多个周期完成。虽然增加了延迟,但时序好很多。
- 使用同步FIFO而不是异步FIFO来分担时序压力,跨时钟域只在少数几个固定点做。
- 关键路径上避免使用高扇出的控制信号,比如复位信号,要用同步复位配合全局复位树,并且尽量让复位信号的扇出降低。
- 复杂状态机拆成多个小状态机,每个状态机只负责一个简单功能,避免一个大状态机组合逻辑链太长。
我在做位宽转换模块的时候,第一次跑综合到322MHz直接时序违规,关键路径长度超出0.6ns。后来把位宽转换拆成多级流水,每级位宽依次递减,比如512→256→128→64,每一级之间插寄存器,时序瞬间就收敛了。所以遇到时序过不去,优先考虑加流水,不要一上来就调布局布线选项。
5.3 丢包问题的系统性排查思路
丢包是最头疼的问题,因为可能的原因太多。我总结了一套排查顺序,从物理层往应用层查:
第一步,物理层。用IBERT做PRBS测试,长时间跑(至少10分钟),如果误码率高,说明是光模块、光纤或者SerDes参数问题。这一步不过,后面查什么都是白搭。
第二步,MAC层。看CMAC的统计计数器,Xilinx的CMAC核自带一组收发统计寄存器,可以查CRC错误帧数、超长帧数、超短帧数、溢出丢弃数。如果CRC错误帧很多,大概率还是物理层误码,而不是协议栈问题。
第三步,协议栈内部。在ILA里观察UDP协议栈的输入和输出端口,看tvalid/tready的握手情况。如果tready长期为低,说明下游堵了;如果tvalid和tready都高但数据没出来,说明协议栈内部状态机卡住了。
第四步,用户逻辑。看用户侧FIFO的almost_empty/almost_full信号,确认用户侧是不是消费不过来。
丢包时序上还有一个小细节:偶尔丢一两个包,可能是CMAC接口的tkeep处理有误,导致包长计算错误,协议栈把帧当成错误帧丢弃。这种问题概率不大,但一旦出现,排查起来极其费劲,需要仔细核对tkeep在每个周期的取值是否正确。
5.4 100G UDP的几个流传误区
- “UDP丢包是因为UDP不可靠”——这句话在软件协议栈里是对的,但在FPGA硬件协议栈里不一定。FPGA里UDP不收发丢包主要是FIFO溢出和物理层误码,只要反压机制做得好,UDP在硬件上也可以做到非常可靠。
- “100G必须要用RDMA”——RDMA是infiniBand和RoCE那套东西,和UDP是不同的技术路线。很多场景不想用RDMA,就是因为不想引入管理面和控制面的复杂度,纯UDP简单直接,反而更好维护。
- “开源UDP协议栈性能不如商用IP”——我实测下来的结论是,开源方案在纯数据路径性能上并不输给商用IP,区别主要在可管理性、诊断能力、多队列支持这些辅助功能上。如果你的应用只需要简单的UDP透明转发,开源方案完全够用。
- “板卡逻辑时钟越快越好”——不是。322MHz的CMAC用户时钟是固定的,用户侧逻辑如果跑不到这个频率,可以用异步FIFO隔离,用户侧跑自己的低频率时钟,只要FIFO深度和读写速率匹配就行,强行让用户逻辑也跑到322MHz反而得不偿失。
6. 后续扩展方向与经验总结
6.1 从UDP到更高价值的应用
100G UDP跑通只是第一步,真正值钱的是在这个基础上叠加业务。我目前正在做的是把UDP协议栈和自定义DMA引擎对接,实现一个完整的100G网络数据加速卡。具体来说,CPU只需要配置DMA描述符,数据搬运全靠FPGA完成,网卡带宽完全不吃CPU。这个架构非常适合金融行情加速、HPC数据交换、数据采集存储一体机这些场景,UDP只是基础通道,业务逻辑才是核心。
另外一个可以扩展的方向是多端口汇聚。一块FPGA如果有4个100G端口,可以在UDP协议栈之上做负载均衡、链路聚合、环网保护这些功能。开源项目里已经有了很多以太网交换相关的模块,比如verilog-ethernet里就有交换机核心和去重模块,可以在这个基础上做更复杂的多端口应用。
6.2 踩坑几年后的一些实在建议
最后再分享几点个人体会。开源代码拿下来先看仿真,不要直接上板。我见过太多人就是不做仿真,一上板遇到问题就抓瞎。仿真跑通了,至少证明代码本身逻辑没问题,剩下的只是集成问题。我用Vivado自带的xsim跑仿真,写了一个简单的testbench模拟MAC侧和用户侧的数据收发,大概跑了几万个包,确认协议栈行为正确之后才上板,节省了大量调试时间。
上板之前把所有的初始化配置固化死,尽量减少上板后的变量。比如本机MAC、本机IP、UDP端口映射表,这些在上板之前全部写成参数或者初始化逻辑,不要在调试时候靠软件去改,这样出了问题原因更可控。
多做自动化的数据校验,不要只用iperf3的丢包率来判断。我在FPGA里做了一个简单的CRC校验计数器,用户逻辑在收包时会对UDP载荷做累加校验,然后和发送端的累加值对比,一旦不一致立刻打标记。这样即使iperf3显示不丢包,也能发现有没有数据被意外改坏的情况。
100G UDP的移植上板,说起来就是“接口适配加FIFO、时序收敛加验证”这十几个字,但实际做起来每一个环节都需要仔细打磨。希望这篇记录能帮大家少填一些我踩过的坑,尤其是那些正准备从千兆跳到100G的朋友。如果你也在做类似的方案,欢迎多交流。