干了十年FPGA说实话开源网卡这个方向我盯了很久。今年项目需要一块能跑满100G的网卡手头正好有一块 Bittware VV4 的板子于是把开源NIC项目 Corundum 整体移植了过去。这篇是系列第一篇记录从板卡到手、资源盘点、工程移植到第一次把100G端口拉起来的过程。文章里涉及的具体管脚和工程路径都是我这块板子的实际情况别的板子可能要查原理图但排查思路和移植方法是通用的。1. 为什么选Corundum从商业网卡到开源FPGA网卡的迁移逻辑1.1 Corundum是什么能给你省下什么CorundumGitHub上alexforencich/corundum是目前FPGA开源网卡里完成度最高的项目不是某个学校实验室的玩具代码它把现代网卡需要的核心模块几乎都写全了PCIe的DMA引擎、多队列收发、RSS哈希分发、校验和卸载、PTP硬件时间戳以及10G/25G/100G以太网MAC的整合。你拿到手里的是一个能在FPGA上真正作为操作系统netdev工作的网卡而不是一个只能回环测试的demo。最直观的省事点在于DMA那一块。自己做网卡的话PCIe与DMA之间的一堆状态机足够耗掉两三个月而且很容易在边界条件下踩死。Corundum的多队列DMA已经相当成熟Linux下驱动配套齐全用户态还可以配合DPDK做数据面加速。对我们这种只想在100G网络上做定制数据处理的项目来说与其从零写不如在它基础上做裁剪和替换。1.2 Bittware VV4的硬件底子Bittware VV4这块板卡属于典型的UltraScale加速板卡阵容Virtex UltraScale级别FPGA、PCIe x16接口、支持100G的QSFP光口、板载多路可编程时钟。做100G网卡最关键的三样资源它都给了——足够多的GTY高速收发器、独立PCIe参考时钟、以及给光模块用的156.25MHz参考时钟。但是资源够和能跑网卡是两回事。板卡出厂一般只带DDR读写、逻辑测试这种基础工程要做网卡得自己把整套PCIe和SerDes路径搭起来。VV4的优势在于FPGA资源余量大PCIe x16跑Gen3带宽足够不会像小规模器件那样为了挤入口端队列而纠结LUT和BRAM的分配。它的麻烦点在于官方demo里没有网卡逻辑所有MGT管脚映射和时钟约束都得对照原理图重新填。1.3 移植的本质是把四类东西对齐第一版接触这个项目的人容易陷入我得从头学一遍网卡架构的误区。实际上移植工作的本质非常单纯RTL核心逻辑基本不动你要对齐的是四类工程层面的东西——器件型号、引脚约束、时钟约束、以及Xilinx IP的版本和配置。Corundum的设计刻意把板卡相关的部分收敛在顶层wrapper和约束文件里而把逻辑相关的部分放在fpga/core下面。FPGA型号从Vivado工程创建就开始定死引脚和时钟约束完全属于板卡特性IP则需要根据你选用的PCIe速率和MAC接口重新生成。说白了只要RTL里没有用到原板卡的私有资源移植就是一次换壳操作——但这次换壳里面藏了很多容易被时序工具和硬件手册坑到的细节。2. 动手移植前必须算清楚的几笔账2.1 时钟体系先画一棵完整的时钟树再写约束Corundum对时钟极其敏感因为它内部同时存在多个时钟域PCIe用户时钟、系统时钟、以太网MAC的收发时钟、以及GTY收发器需要的参考时钟。移植时第一件事就是画一棵时钟树明确每个时钟从哪里来、到哪里去、频率是多少。VV4板卡上通常会提供独立的PCIe参考时钟100MHz和可编程的以太网参考时钟。Corundum的100G路径建议使用156.25MHz参考时钟这是一个业界标准频率恰好对应4通道25G NRZ信号的PCS时钟基准。如果板卡上只有161.13MHz或者125MHz也不是完全不能跑但PCS层和SerDes的配置会复杂很多调试时会额外多出许多玄学问题。我在这次移植里画时钟树花了半天真正收益是后面综合时序报告出来的时候不慌每一类时序违例都能立刻定位到是哪个时钟域出了问题而不是在几十条路径里瞎猜。2.2 PCIe与SerDes通道映射别把高速信号接到错误的位置VV4这种大板卡上QSFP光口对应的GTY通道和PCIe x16用的GTY通道是分离的但它们在FPGA内部的Quad排列并不直观。移植前一定要做两件事第一打开板卡原理图把QSFP口每个lane对应的FPGA引脚和所在GTY Quad查清楚第二把PCIe x16对应的引脚和参考时钟输入引脚查清楚。这块如果填错综合阶段就会报大量placement conflict或pin not found错误严重时甚至会烧毁板卡上的高速接口。Corundum顶层接口里PCIe一侧是一组mgt_rxp/rxn和mgt_txp/txnMAC一侧是另一组类似的SerDes接口。实际移植时你在wrapper里把VV4的管脚和这些接口对接就行。举个例子我这边100G光口的四条收发起伏信号最终落到FPGA的GTY_GXB61_TRX这个Quad里面的四个通道上而PCIe Gen3 x16则跑在另一组GTY Quad上。这些映射必须老老实实手动确认一遍不能依赖Vivado自动布局因为自动布局不知道哪个Quad连接到了板卡光模块。2.3 软件侧准备版本和驱动同样决定成败Corundum对Vivado版本有隐性要求。我自己用的是Vivado 2022.2配合对应分支的Corundum版本官方README会标明推荐版本组合尽量不要跨太多大版本否则生成的IP核会自动升级升级后不一定能正常综合尤其是PCIe IP和CMAC IP这种复杂核跨版本升级基本等于重来。主机侧的驱动也需要提前准备好。Corundum的Linux驱动不是标准内核自带的需要单独编译加载。编译环境里要确认有make、gcc、linux-headers这些基础工具建议在加载前先关掉PCIe的ASPM节能否则链路可能因为省电协商而出现间歇性不识别这在很多服务器主板上尤其明显。软件侧的准备清单其实不长但每一步都会在后续排查中占用时间。我整理了一张表方便参考项目推荐做法说明Vivado版本按Corundum官方推荐选择跨版本IP升级可能直接失败主机系统Linux 5.x内核驱动模块编译依赖内核头文件PCIe节能内核参数pcie_aspmoff避免链路掉入低功耗状态导致不识别光模块选兼容性好的100G QSFP28部分模块的EEPROM内容和CMAC核协商有兼容问题2.4 版本选型不是越新越好Corundum的main分支每天都有提交但新提交往往带着新功能和新的未稳定状态。做移植一定要锁定一个稳定tag我在项目里锁定的就是一个在ZCU106和VCU118参考板上验证过的版本。为什么这么做因为Corundum的队列、DMA、时钟逻辑和beat版本之间有耦合你换一套新版可能改进了某条路径的性能却可能引入一个在特定板卡上才出现的回归问题。3. 实际移植过程从Git clone到第一次综合通过3.1 理解Corundum工程的目录组织Corundum的仓库结构比一般RTL项目复杂但对移植来说只需要关注几个目录fpga/core/corundum核心网卡逻辑顶层就在这里一般不需要改动。fpga/boards官方板卡约束和构建脚本参考价值很高。fpga/xilinx_ipXilinx IP核相关PCIe和Ethernet IP都在这里。fpga/build.py工程生成脚本用Python调用Vivado批处理模式创建工程。关键点是千万不要在core里做板卡相关的改动。当你的板卡因引脚约束需要调整时正确做法是在wrapper层做适配而不是去改DMA引擎的内部逻辑。否则下一次同步上游代码时每一次merge都是痛苦。3.2 创建顶层wrapper把VV4的管脚接进来我的工作方式是把官方某个板卡的顶层文件拷贝一份改成vv4_corundum_top然后逐一替换其中的引脚声明。Corundum顶层接口大致长这样module vv4_corundum_top ( input wire pcie_refclk_p, input wire pcie_refclk_n, input wire [15:0] pcie_rxp, input wire [15:0] pcie_rxn, output wire [15:0] pcie_txp, output wire [15:0] pcie_txn, input wire mac_refclk_p, input wire mac_refclk_n, input wire [3:0] qsfp_rxp, input wire [3:0] qsfp_rxn, output wire [3:0] qsfp_txp, output wire [3:0] qsfp_txn, input wire qsfp_modprs_l, output wire qsfp_reset_l );这里PCIe是x16一组四比特的SerDes是100G光口的四路25G lane。光模块的管理信号比如I2C和modprs具体看板卡设计Corundum核心逻辑不一定用到但有的板卡必须通过I2C初始化光模块才能让链路协商通过。如果板卡上接了光模块的I2C需要确认它默认是否已经有上拉、是否在上电后由I2C控制器自动配置。3.3 编写VV4专属XDC约束约束是移植工作的重头戏我把它分成三大块引脚约束、时钟约束、时序例外约束。引脚约束这步没太多技术含量纯粹是体力活。我是直接从VV4原理图里面把每个信号的名字和BGA封装脚对抄出来的。下面这种片段就是典型的MGT引脚约束# PCIe x16 MGT 通道约束示意实际以板卡原理图为准 set_property PACKAGE_PIN AJ4 [get_ports {pcie_rxp[0]}] set_property PACKAGE_PIN AK5 [get_ports {pcie_rxn[0]}] set_property PACKAGE_PIN AL2 [get_ports {pcie_txp[0]}] set_property PACKAGE_PIN AL1 [get_ports {pcie_txn[0]}] # 100G QSFP MGT 通道约束示意 set_property PACKAGE_PIN AM6 [get_ports {qsfp_rxp[0]}] set_property PACKAGE_PIN AN5 [get_ports {qsfp_rxn[0]}]时钟约束的一般写法是这样的create_clock -name pcie_ref -period 10.000 [get_ports pcie_refclk_p] create_clock -name mac_ref -period 6.400 [get_ports mac_refclk_p]PCIe参考时钟是标准100MHz所以周期10ns。MAC参考时钟如果是156.25MHz周期就是6.4ns。这两个时钟必须显式声明否则Vivado不知道它们的频率综合的时候很多时序路径会无法解析。时序例外约束常常是隐藏的坑。Corundum内部存在大量跨时钟域路径官方约束里会用set_clock_groups或者异步时钟组来声明这些跨域是安全的。如果官方XDC里没有覆盖所有跨域路径你又强行用时序工具去分析它们哪怕逻辑本身没问题时序收敛也会非常难看最终可能导致工具为了满足一条伪路径而把正确的布线改坏。3.4 综合实现阶段会遇到的几个高频报错第一次综合通过不是理所当然的绝大多数人第一次都会卡在这几个地方。第一个是GTY收发器site分配错误。报错信息大概是cannot place GTY instance at GTYE4_CHANNEL_X0Y...之类的。出现这个通常是你把MGT约束写到了不支持的通道或者Quad上需要回去对原理图。第二个是时钟约束冲突如果一个引脚既被物理约束又没被正确声明成时钟输入会报input clock buffer invalid或者类似的问题。第三个是参考时钟找不到常见原因是你在XDC里没有把光模块的参考时钟引脚连到CMAC核自己例化出的参考时钟输入或者引脚声明错了位置。这些问题我在这次移植里遇到了两轮第一轮是pin映射抄错了一个lane综合报placement错误第二轮是参考时钟在CMAC核里没有连接对实现阶段报时钟树错误。耐心对照原理图很快能找到问题。4. 上板调试从PCIe枚举到100G端口跑通4.1 第一次上电必须要看的三个信号工程生成比特流之后先不要急着插光模块跑流量。上板的第一步是确认板卡能配置成功第二步是确认PCIe链路能枚举出来。我通常看三个东西板卡的DONE信号、主机侧dmesg里有没有报PCIe错误、以及lspci -vvv的输出。如果DONE拉高说明FPGA配置完毕。如果dmesg里出现PCIe Bus Error之类的关键字优先排查PCIe参考时钟和复位时序。如果lspci里根本没有对应设备先检查是不是PCIe链路训练没有完成这通常和参考时钟质量、复位持续时间有关。这一步让我想起一个类似的通用问题很多USB网卡或者无线网卡在FreeBSD下会出现ugen2.2: realtek ... at usbus2但第一次开机不识别、需要重启才能用的现象本质就是设备在上电初始化时序里没有完成正确的link训练PCIe设备和USB设备在枚举机制上虽然实现完全不同但排查逻辑是一致的——先看物理层就绪再看上层枚举。Corundum搬新板卡前几次插上没设备是非常正常的核心在于复位于时序是否让PCIe硬核拿到了稳定的参考时钟。4.2 100G链路协商不是插上光模块就能通PCIe起来只是第一步100G端口能不能协商到100G速率是第二个大坎。Corundum在UltraScale平台上通常使用集成的100G CMAC硬核链路协商由CMAC核的PCS层负责。第一次上电时如果光模块没有正确的模块功耗和复位时序链路会一直停在DOWN状态。我调试时先用CMAC自带的内部回环模式测试PCS和GTY通道再看外部光模块链路。内部回环通过后可以确认FPGA内部的收发路径没问题外部回环不通过则有可能是光模块兼容性、连接器焊接或者SerDes端接的问题。区分内回环和外回环非常关键。这就像是把一条链路的两端接到同一个设备里看吞吐和你把网线插到真实交换机上测通断两者的诊断范围完全不同。我这次花了半天时间才确认问题不在CMAC本身而是光模块的EEPROM内容不完整导致模块无法进入100G模式换了一个光模块后链路立即协商成功。4.3 性能实测大文件下载和iperf打流链路状态变为UP之后就可以装驱动做收发包测试了。Corundum驱动加载后会出现一个标准的网卡接口用ip link show能看到。第一轮测试一定先从iperf3开始而不是直接scp大文件因为iperf能更精准地分离出网卡能力和内存/存储瓶颈。iperf3测试时注意几个参数多线程并发、窗口大小、还有MTU。100G链路下默认1500字节MTU会影响吞吐建议使用9000字节巨型帧。我这边单流iperf3大概跑到70Gbps左右四线并发能接近线速。不要一上来就怀疑网卡不行单流上不去有时候是CPU单核瓶颈给iperf进程绑核、调整网卡队列绑核吞吐数字会上来很快。至于很多人关心的100G大文件下载链接从网卡开发角度看大文件传输拼的是整条存储和协议栈链路而不是单靠100G网卡。先用打流工具确认网卡本身能顶到线速再去调上层排查逻辑才不会错。我通常的做法是拿本地两台机器直接对打两边都装Corundum网卡用TCP窗口开到足够大再把中断绑定到不同CPU核上。4.4 这次没有解决的问题留给第二篇东西虽然是跑通了但如果只看初见效果就宣布移植完成后面会吃大亏。我这边目前还有三个没有彻底解决的问题第一个是PCIe枚举偶发不稳定在部分服务器主板上第一次冷启动找不到设备需要重启一次才能识别和FreeBSD下某些USB无线网卡首次开机不识别很像我怀疑还是ASPM和复位时序的配合问题第二个是单队列TCP吞吐和官方数据还有差距我还没完全确认是驱动参数还是中断合并策略的问题第三个是光模块兼容性列表还没摸清手头能稳定跑100G的模块只有两款。这些其实比移植成功更有价值。一个网卡项目从能在板卡上点灯到能稳定承载业务中间隔着大量细节。下一篇我会重点写驱动侧的中断亲和性调整、多队列配置、以及PCIe偶发不识别问题的完整排查链路第二篇的篇幅不会比这篇短。自己经历过一遍Corundum的移植之后我的感受是开源网卡的完成度已经高到可以当作工程平台来用了但拿来就能跑这句口号千万别信。所有看起来不起眼的板卡差异最后都会落到时钟、引脚和复位时序上。这篇一把搭环境和首次跑通的路径走完了希望后面再做同类板卡的朋友能少走几个弯路。
