拿到一块带 QSFP28 光口的 UltraScale+ 板卡时,我的第一反应是先把 100G UDP 链路跑起来。开源协议栈那么多,理论上 clone 下来加个顶层就能用,但真正做“移植 + 上板测试”的时候才发现,100G 这条路上到处都是 25G 时代没遇到过的坑。从 CMAC 的 license、参考时钟、FEC 配置,到 AXI-Stream 位宽和时钟频率的计算,再到 iperf3 打流时看到的丢包率,每一步都在逼着你去把协议栈底下的硬件细节翻出来。
这篇就完整记录一下我做“开源 100G FPGA UDP 移植上板测试”的过程,包括为什么选开源方案、移植时改了什么、上板怎么分层次验证、打流数据怎么解读,以及那几个差点让我怀疑人生的坑。
1. 为什么我先选了开源UDP协议栈而不是从零手写
1.1 100G UDP栈的隐藏复杂度
UDP 本身只是一个很薄的层,往上计算就是“IP 包拆开、端口匹配、校验和检查”这么简单。但问题从来不在 UDP 本身,而在它底下那一整套物理链路。
100G 以太网链路从外到内大概是这个结构:QSFP28 光模块/铜缆 → PMA(SerDes)→ PCS(64B/66B 编码)→ RS-FEC(544/514)→ MAC 层 → 用户侧 AXI-Stream 接口。这个栈在 10G 时代可以用软核逻辑实现,到了 100G 如果不用 FPGA 厂商的硬核,光靠 LUT 去做 PCS 和 FEC,资源开销极其夸张,时序大概率也收敛不了。
就算厂商硬核帮我们解决了 MAC/PCS/FEC,后面还有一堆事要做:ARP 模块要能回以太网请求,ICMP 要能响应 ping,IP 层要处理简单的收发,UDP 层要把端口和 payload 剥出来。接着还有跨时钟域处理、发送侧流量控制、接收侧背压。
真从零写一遍,编码量并不是最大的问题,最大的问题在验证。你写一个 10G UDP 协议栈,仿真几个月都不一定能覆盖所有边界情况;写 100G 的更是要命。所以对于“先把链路跑起来”这种目标,开源协议栈是性价比最高的选择。
1.2 主流开源方案对比与选型依据
我主要对比过两个开源方案,分别是基于独立 MAC/UDP 模块拼起来的方案,以及把 DMA、PCIe、UDP 等做成一套完整系统的方案。
从可移植性角度,独立模块拼装的方案更符合我的需求。原因很简单:它不强制绑定某个 PCIe 交互框架,我可以只把 UDP offload 相关的模块拿出来用,用户侧直接对接 AXI-Stream。这类模块通常由以太网 MAC、发送/接收 FIFO、ARP 缓存、ICMP、UDP 接收/发送等部分组成,结构清楚,代码风格也相对干净。
完整系统方案虽然功能更全,100G MAC 和 DMA 都已经替你接好了,但其复杂度也高得多。它默认你的使用场景是“网卡卸载”,如果你只是想把 UDP 数据引到 FPGA 内部逻辑里做处理,这套框架反而显得笨重,因为你要先搞明白它的 DMA 描述符、PCIe BAR 映射,然后才能开始你的业务。
所以我的结论是:核心诉求是“把 100G UDP 跑起来、能收发自定义数据”,优先选模块解耦清晰、接口友好的开源方案。如果你的目标是做一个完整的网卡卸载引擎,那再考虑集成度高的那套框架。
1.3 开源方案也有“水土不服”
但开源方案不是拿来就能跑,尤其是 100G 这个速率。最明显的“水土不服”有几点:
- 大部分开源 UDP 栈默认的验证环境是 10G/25G,MAC 层用的是软核或简单封装。到了 100G,需要先适配厂商的硬核 CMAC。
- 100G 用户侧数据位宽是 512bit,时钟接近 200MHz,跟 10G/25G 时代的 64bit/256bit 接口完全不同。开源代码里如果硬编码了某个位宽,即使功能正确,接口也会对不上。
- 不同板卡的参考时钟来源、GT 位置、复位信号极性都不完全一样,直接拿通用代码上板,大概率会在链路训练和复位顺序上卡壳。
所以“移植”这个动作,真正花时间的部分不是改逻辑,而是搞清楚开源代码的接口约束,再把板卡特有的硬件细节填进去。
2. 移植前必须搞懂的硬件细节:100G链路不是简单加宽
2.1 硬件平台选型和测试环境
这次使用的是一块带 QSFP28 接口的 UltraScale+ FPGA 板卡,具体型号不细说,但它用的 100G 方案很典型:4 路 25G SerDes 组成 100GBASE-R,外部参考时钟为 156.25MHz,MAC 用的是 Xilinx 集成 100G 以太网硬核(CMAC)。
配套测试环境也很简单:
- PC 端配了一块 100G 网卡(Mellanox ConnectX 系列),用来和 FPGA 对打流量。
- 一条 QSFP28 直连铜缆(DAC),短距离测试比光模块稳定,少了光功率问题,排查链路更容易。
- Wireshark 抓包,主要用来确认 ARP、ICMP、UDP 报文结构是否正确。
- iperf3 做 UDP 打流,看吞吐、丢包、抖动数据。
DAC 铜缆是我个人比较推荐的上板首选,原因后面会细说,这里先记住:上板测试第一步,如果环境允许,优先用短距离 DAC,而不是光纤 + 光模块。光模块一旦出现光功率异常、los 告警,问题域会扩大很多。
2.2 从 10G/25G 到 100G 的四个关键差异
很多做 FPGA 的同学对 10G/25G 已经很熟了,上来就按老经验操作 100G,结果各种不收敛。四个差异最明显:
第一,编码和 FEC 机制。100GBASE-R 使用 64B/66B 编码,线路速率不是 100G,而是约 103.125Gbps。PMA 层为了纠错,通常还要开启 RS-FEC(544,514),这会引入额外的延迟和码字开销。FEC 开没开、两端是否一致,直接决定链路能不能 up。
第二,用户侧数据位宽和时钟完全不同。10G 通常是 64bit @ 156.25MHz,25G 通常 64bit @ 390.625MHz,而 100G 常用 512bit @ 195.3125MHz。位宽变了,开源代码里所有 AXI-Stream 总线的处理逻辑都要重新核对。
第三,MAC 实现方式不同。10G/25G 软核 MAC 很常见,100G 再软的核,资源消耗和时序收敛都很难受。几乎所有 100G 方案都走硬核 CMAC,而硬核的配置、复位时序、状态寄存器都是厂商相关的,不是简单“例化一个 MAC”就结束。
第四,时序收敛难度指数级上升。512bit 总线在 195MHz 下跑,意味着每个时钟周期要处理 64 字节,稍微多一级组合逻辑延迟,时序就红了。因此跨时钟域处理、FIFO 深度、流水线级数都要重新评估。
2.3 CMAC、GT 位置、参考时钟、license 这一套“四件套”
使用 Xilinx 的 100G CMAC,首先要有对应 IP 的 license。这个问题不说技术,但确实会卡人。如果你没有授权,CMAC 可能只能跑仿真,无法完成 bitstream 生成或上板。提前确认 license 是第一步。
其次是 GT 位置。100G 通常占用一个 QSFP28 对应的 4 个 GTY Transceiver,位置是固定的。在 XDC 约束里要把 GT 的 location 和参考时钟的引脚绑对,否则链路根本没法起来。
参考时钟方面,常见的是 156.25MHz 外部差分时钟输入到 GTREFCLK。这里要注意:部分板卡上同一个参考时钟会同时给多路 GT 使用,如果之前被别的项目占用过,要在约束里确保属性一致,不能出现一个 GT group 混用不同参考时钟的情况。
最后是复位时序。CMAC 的复位比软核复杂得多,通常有 tx_rst、rx_rst、gt_rst 以及 core 内部的复位状态机。上板时不能简单地拉一下全局复位就完事,而是要看 CMAC 的 status 寄存器确认 tx/rx 是否各自完成 reset 并进入 ready 状态。
2.4 用户侧时钟频率的计算逻辑
很多第一次接触 100G 的同学会问:为什么用户侧时钟是 195.3125MHz?为什么不是 100MHz 或者 250MHz?
其实算一下就清楚了。以太网用户接口承载的是有效数据速率,100G 就是 100Gbps。当总线位宽为 512bit 时,时钟频率 = 100Gbps / 512bit = 195.3125MHz。如果选择 256bit 位宽,频率就要翻倍到 390.625MHz,这对时序来说压力更大。
所以绝大多数 100G 设计会选 512bit @ 195.3125MHz。这个数字也意味着,用户逻辑每个有效时钟周期要消化 64 字节数据。以后无论你在哪个模块遇到瓶颈,优先思考的都是“我能否在一个周期内处理 64 字节”,而不是盯着某个具体的延时看。
3. 移植过程:代码适配、接口封装和时钟修正
3.1 开源代码结构怎么快速看
拿到开源工程后,不要急着把所有文件一股脑添加进 Vivado。我习惯先看 README 和目录结构,搞清楚哪些是 core、哪些是 example design、哪些是 testbench。以常见 UDP offload 工程为例,核心文件通常包括:以太网 MAC 模块、发送/接收 FIFO、ARP 缓存模块、ICMP 模块、UDP 接收/发送引擎,以及连接这些模块的顶层。
我第一步是先找 example design 里的 demo_top,它展示了模块之间如何连接。然后带着两个问题去看代码:一是用户数据走哪个接口,二是数据有效信号和位宽是多少。把这两个问题搞清楚,再去做移植就有的放矢。
3.2 本地 MAC、IP、端口怎么改
开源代码里通常默认给了一个 MAC 和 IP,比如 00:11:22:33:44:55 和 192.168.1.128 这类。上板第一件事就是改成你实际环境里的地址。
我建议做成参数化配置,而不是直接在代码里写死。比如在顶层定义LOCAL_MAC、LOCAL_IP、LOCAL_PORT这几个 parameter,ARP 模块、UDP 模块都引用这组参数。这样做的好处是后期调试时,想换一个 IP 地址,只需改顶层参数,重新综合编译很快就出结果。
UDP 端口方面,如果只是简单测试,固定一个端口即可。但要注意:100G 线速下,接收侧如果只按一个端口过滤,进来的所有其他端口报文都会被丢弃。要做统计计数,方便判断是否收到了“目标端口之外”的报文,这会成为排查问题的重要线索。
3.3 时钟、复位和跨时钟域:最容易翻车的区域
移植中改动最大的不是逻辑代码,而是时钟和复位。开源工程通常提供一个 156.25MHz 的用户时钟(从参考时钟分频/倍频来),但 100G CMAC 用户侧时钟是独立的、由硬核输出的 195.3125MHz。这个时钟才是 AXI-Stream 接口的工作时钟,绝对不能随便用一个 PLL 生成的时钟替代。
我当时把这些信号理成了三类:
- gt_ref_clk:156.25MHz,提供给 GT 的参考时钟,来自板卡晶振。
- core_clk:CMAC 内部逻辑工作时钟,由 IP 内部自动管理。
- user_clk:CMAC 用户侧 AXI-Stream 接口时钟,195.3125MHz,由 IP 输出,供用户逻辑使用。
复位逻辑也要分开。CMAC 的 tx/rx 复位各自独立,开源 UDP 栈本身还有一个用户逻辑复位。上板时我给它们设计了一个固定的复位顺序:先释放 GT 相关复位,等待 CMAC 内部 status 拉高,再释放用户逻辑复位。这个顺序错一步,容易出现“看起来复位都给了,但就是收不到数据”的诡异现象。
3.4 把 UDP 栈封装成可复用的 AXI-Stream 模块
调试阶段直接改开源 top 没问题,但要做到“移植后可复用”,我建议在外面再包一层自己的顶层,把 UDP 栈整体封装成一个对用户友好的 AXI-Stream 从/主接口模块。
我的封装思路:
- 发送侧提供
s_axis_tdata/s_axis_tvalid/s_axis_tready/s_axis_tlast/s_axis_tkeep,用户逻辑只要往这个接口写数据,封装模块会负责加 MAC 头、IP 头、UDP 头、校验和,然后发送出去。 - 接收侧提供
m_axis_tdata/m_axis_tvalid/m_axis_tlast/m_axis_tkeep,同时输出接收到的源 MAC、源 IP、源端口,以及报文长度。
封装之后的好处非常直接:后面做其他项目想用 100G UDP,不用再跟开源代码里的 ARP/ICMP 细节纠缠,直接拉接口就行。
3.5 综合和布局布线的资源评估
做完代码适配,先跑一遍综合看资源。100G UDP 协议栈本身逻辑资源占用不大,但加上发送侧 FIFO、接收侧统计模块后,LUT 大概几千,BRAM 会多消耗一些。更大的挑战是时序收敛。
我用的约束很简单,但很关键:
- CMAC 相关引脚(GT location、REFCLK)必须按板卡原理图严格约束。
- 对 user_clk 时钟组做
set_clock_groups,避免跨时钟域的路径被错误分析。 - 对 AXIS 接口的关键路径加 multicycle 约束时一定要谨慎,宁可紧一点,不要放松到影响功能。
编译完成后,如果时序还有少量 violation,优先检查是不是组合逻辑跨位宽拼接导致的长路径,通常在数据拼包/解包的地方做流水线切分就能解决。
4. 上板实测:从内部回环到 PC 对通的分层验证流程
4.1 第一层:CMAC 内部回环,先验证逻辑主体
上板第一步我不建议直接插线对 PC,那样一旦出问题,分不清是 FPGA 逻辑问题还是链路问题。我的做法是先开 CMAC 的内部回环(near-end loopback),把 TX 数据从 CMAC 内部直接送回 RX 端。
这时 FPGA 内部产生一个 UDP 测试报文源(比如固定计数器值),经过 UDP 栈发送,再由 CMAC 回环回 RX 方向,通过 UDP 接收侧把数据读出并做 CRC 比对。如果比对一致,说明从用户逻辑到 CMAC 发送、再从 CMAC 接收到用户逻辑这条通路在 FPGA 内部是通的。
这个阶段基本可以验证:时钟配置对不对、复位时序对不对、UDP 栈能不能正常组包和拆包。还有一个隐藏收益:它能暴露 AXI-Stream 上 tkeep/tlast 处理不当导致的长度错位问题。内部回环通了,才说明逻辑主体没问题,可以去碰真实链路。
4.2 第二层:DAC 铜缆直连,验证链路训练和 FEC
内部回环通过后,插上 QSFP28 DAC 铜缆,可以是 FPGA 直接连 PC 网卡,也可以先做外部回环(把 TX 和 RX 用 DAC 短接)。
如果条件允许,我建议先做外部回环:一根 DAC 线缆把 FPGA 的 TX 和 RX 自己连起来,这样不依赖 PC 网卡配置,先验证物理层和链路训练是否正常。看 CMAC 或 GT 的 link status 寄存器,确认 link up。
如果 link 起不来,90% 的情况是 FEC 配置不一致。100G 下两端必须协商好 RS-FEC 是否启用。把 CMAC 的 RS-FEC 打开,再检查 GT 的 eyescan 或 BER 寄存器,基本能定位问题。
之后再把 DAC 连到 PC 网卡。这里有个很多人忽略的点:PC 网卡默认可能开了自适应协商,而 FPGA 侧 CMAC 通常不跑 autoneg(长距离一般不跑),需要手动把 PC 网卡固定在 100G 全双工、对应的 FEC 模式,不要留 auto。
4.3 第三层:ARP 和 ICMP 通联,确认 UDP 栈能接入局域网
物理链路 up 之后,我第一次在半开玩笑地跟同事说:“终于看到 Ethernet cable connected 了,接下来才是真正的网络层测试。”
先给 PC 网卡配一个静态 IP,比如 192.168.1.1,FPGA 侧设 192.168.1.10。然后从 PC ping FPGA。如果 ping 不通,先查 ARP,PC 上敲arp -d清缓存后重新 ping。
ping 通说明 ARP 模块和 ICMP 模块工作正常。这个意义很大,因为 ARP 通说明 FPGA 能正确解析广播帧并回复单播帧,ICMP 通说明 IP 层收发的路由逻辑没问题。这两个都通了,再去做 UDP 数据收发,成功率会高很多。
如果在 ping 这一步失败,暂时不要急着查 UDP 逻辑,先拿 Wireshark 在 PC 侧抓包。看有没有发出 ARP 请求、有没有收到 ARP 回复;有请求无回复,先查 FPGA 的 ARP 缓存模块和以太网 RX 通路;回复了但 PC 不认,大概率是源 MAC 地址填错了。
4.4 Wireshark 抓包确认 UDP 报文格式
ping 通之后,从 FPGA 周期性向 PC 的指定端口发送 UDP 测试帧,同时在 PC 上用 Wireshark 抓包,过滤条件设udp.port == 目标端口。
看三个东西:
- 源 MAC 是否等于 FPGA 配置的 MAC。
- 源 IP 是否等于 FPGA 配置的 IP。
- UDP payload 内容是否和 FPGA 内部产生的数据一致。
我第一次抓包时发现的典型问题是:MAC 地址和 IP 地址都对了,但 UDP 校验和显示错误。这是因为 FPGA 侧发送时没做 checksum offload,直接把 checksum 字段填 0 或算错了。虽然 UDP over IPv4 允许 checksum 为 0,但 PC 网卡如果开启了 checksum 校验卸载,Wireshark 会把它标成错误,某些驱动性能还会受影响。所以最好在 FPGA 侧把 UDP checksum 正确计算出来。
5. 用 iperf3 打流:吞吐、丢包和“看起来通了”的假象
5.1 打流环境与关键参数
通了 ARP、ICMP、UDP 单向数据之后,就可以上 iperf3 做压力测试了。
PC 作为服务端,命令是:
iperf3 -s -u -p 5001 -i 1FPGA 侧没有现成的 iperf3,得自己写一个简单的打流模块,往目标 IP:端口发送连续 UDP 报文,payload 长度建议设置为 1472 字节(1500 MTU 减去 IPv4 头部 20 字节和 UDP 头部 8 字节),或者干脆开启 Jumbo Frame,用 8972 字节 payload。
如果反过来,PC 向 FPGA 打流,可以在 PC 上执行:
iperf3 -u -c 192.168.1.10 -p 5001 -b 100G -t 10 -l 1472-b 100G告诉 iperf3 用 100Gbps 作为目标带宽来发包,-l 1472指定 UDP payload 长度。
5.2 看懂打流报告里的两个关键数字
iperf3 的 UDP 测试报告会直接给出 Bandwidth、Jitter、Lost/Total Datagrams。很多人只盯着 Bandwidth,但实际系统调优时,Lost Datagrams 比带宽更值得关注。
如果丢包率为 0,但 bandwidth 只有 40Gbps,说明不是链路不够,而是发包侧没有打满,或者接收侧消费能力不够,形成了隐式背压。
如果丢包率很高,但 bandwidth 显示接近 100Gbps,说明链路本身能承载,但某个中间环节在丢包。这个环节可能是 FPGA 接收侧 FIFO 溢出,也可能是 PC 网卡驱动来不及处理,还可能是 UDP 校验和错误导致的静默丢弃。
还有一个小指标:Jitter。UDP 是尽力而为的协议,100G 线速下 jitter 如果异常大,说明发送侧发包节奏不均匀,可能是 FPGA 端打包逻辑存在突发间隙,也可能是中断合并策略导致接收突发不稳定。
5.3 吞吐跑不满时,排查优先顺序
如果测试时吞吐远低于 100G,我建议按以下顺序排查:
- 先确认是单流还是多流。单流 UDP 很容易受限于 PC 网卡的中断处理和单核性能,多流才能打满。FPGA 侧可以同时构造多个不同源端口的 UDP 流,PC 端用
-P 4或几个并行 iperf3 进程。 - 再查 MTU 和 Jumbo Frame。默认 1500 字节 MTU,吞吐很难跑满 100G。开启 9000 字节 Jumbo Frame 后,单位时间内要处理的中断和报文数量会大幅下降。
- 查 UDP 校验和是否由硬件卸载。PC 网卡如果没开 checksum offload,10G 都吃力,100G 更是直接成为瓶颈。确认网卡关闭或开启对应卸载选项。
- 查 FPGA 侧发送间隙。观察 tvalid/tready 握手是否每拍都有效,如果频繁拉低,说明发送侧数据源跟不上。
- 最后才看物理层,比如 FEC 的纠错统计、链路 BER。物理层问题通常伴随 CRC error,链路层计数会先暴露出来。
5.4 “看起来通了”和“真正可靠”是两码事
我见过不少同事,UDP 能收到几个包就宣布“通了”。但 100G 项目里,“能收到包”和“线速可靠”差得很远。
一个非常隐蔽的问题是接收侧背压。FPGA 接收 FIFO 满的时候会拉低 tready,这本身是正常反压。但 UDP 没有重传机制,如果 FIFO 深度不够,面对任意一段突发流量,丢包就不可避免。你的业务逻辑是否允许这种丢包,必须在设计时就定好,而不是靠运气。
另一个是 ARP 缓存老化。长期运行测试中,如果 PC 侧 IP 地址变更或缓存老化,FPGA 侧 ARP 表项可能更新不及时,导致数据包发出去了但 PC 收不到。这种问题从 FPGA 侧看链路正常,从 PC 侧看完全静默,很费时间。解决办法是 UPD 发送模块每次发包前检查 ARP 表项年龄,必要时主动重新请求一次 ARP。
6. 翻车记录:这次移植中踩过的坑和最终稳定配置
6.1 link up 但收不到任何包,问题出在复位顺序
上板第一次连 PC 时出现过一次很典型的故障:CMAC 状态寄存器显示 tx/rx 都是 ready,link 状态也是 up,但 PC 上 ping FPGA 完全没有回复。
通过 ILA 抓 CMAC 的 rx_axis 总线,发现根本没有报文进来。当时我第一个怀疑 MAC 地址过滤,但检查了 CMAC 的地址过滤寄存器之后发现默认是全透传。后来才发现问题在复位顺序:FPGA 整体复位时,CMAC 的 rx 部分先恢复了,但用户逻辑复位后立刻清掉了 CMAC 还没准备好的 rx 路径上的 FIFO 指针,导致状态错乱。
解决方法是把用户逻辑复位信号改为由 CMAC 的rx_axis_aresetn和tx_axis_aresetn做逻辑与之后产生,并额外加足够的延迟。改完之后不仅 ping 通了,长期打流也没有再复现这个问题。
6.2 两端 FEC 配置不一致导致 link 反复抖动
外部回环测试时出现过 link up 几秒又掉、反复抖动的问题。当时 DAC 线缆很短,理论上信号质量没问题,于是把怀疑重点放到了 FEC 上。
确认后发现问题确实出在 CMAC 的 RS-FEC 配置和 PC 网卡不一致:FPGA 侧开了 RS-FEC,PC 网卡强制的是 no FEC。两端不匹配时,物理层上信号能“勉强同步”,但错误率极高,链路层表现为反复 up/down。
解决方案很简单:把 PC 网卡驱动配置成对应的 RS-FEC 模式,或者关闭 FPGA 侧的 RS-FEC。这里要特别提醒,不同网卡驱动下 FEC 模式的名称不一样,有的叫rs-fec,有的叫linkx-fec,要仔细对着驱动支持的列表选。
6.3 UDP 校验和导致的抓包解析异常
第一次从 FPGA 发包到 PC,Wireshark 里看到的 UDP checksum 显示错误,PC 上自定义接收程序偶尔能收到数据,但 iperf3 打流时丢包率极高。
抓包分析发现 FPGA 发送的 UDP checksum 字段全零,而 PC 网卡开启了 RX checksum offload,于是驱动层面把相关报文直接判为错误丢弃,只有一部分恰好没被卸载机制处理的报文到达了应用层。
解决方法是修改 UDP 发送模块,将 checksum 启用并正确计算。IPv4 UDP 虽然允许 checksum 为 0,但在 100G 网卡卸载链路上实际就是要填正确值,不然只能被动接受丢包。
6.4 踩完坑之后的稳定配置清单
整个调通之后,我把配置固定成下面这份,作为后续项目的默认起点:
| 项目 | 配置 | 说明 |
|---|---|---|
| 线缆 | QSFP28 DAC 短距离铜缆 | 比光模块少一个变量 |
| PC 网卡速率 | 固定 100G 全双工 | 不跑自适应协商 |
| FEC | 两端统一 RS-FEC,或同时 no FEC | 必须两端一致 |
| 用户侧时钟 | CMAC 输出的 195.3125MHz,512bit | 不额外生成 |
| 复位 | CMAC 复位完成后再释放用户复位 | 顺序严格 |
| UDP checksum | 正确计算并填充 | 别依赖全零 |
| MTU | 测试时开启 Jumbo Frame 9000 | 提升吞吐 |
| 接收 FIFO | 深度 4KB 到 8KB 起步 | 视业务突发量调整 |
如果你也是刚开始做 100G UDP 上板,这份清单可以直接当 check list 用,能省不少排查时间。
最后说一个我这次操作中很受用的小细节:在调试阶段,不要把源 MAC、IP、端口这些参数直接写死在逻辑里,而是做成寄存器项,或者至少做成顶层参数。因为我反复在同一个 bitstream 上改地址、换端口、调 FEC 模式,如果每次都要重新综合,光编译等待时间就能让人崩溃。做成可配置之后,很多测试只是改一下寄存器值的事,逻辑一次综合可以用到整个调试验证结束。
100G UDP 在 FPGA 上跑起来,本质上不是“能不能写个 UDP 栈”的问题,而是“物理层、MAC 层、用户逻辑三者能不能完美协同”的问题。开源方案把功能逻辑给了你,但平台上那一堆硬件细节,还是得自己一脚一脚踩过去。