1. 项目价值Corundum是什么为什么要在国产FPGA上做1.1 一个完全开放的高性能网卡参考设计做FPGA网络加速的同行应该都听过Corundum。这个开源项目几乎是目前最完整的开源网卡设计方案不是一块现成的PCIe网卡而是一整套可以直接编译、综合、下载到FPGA里的源代码工程覆盖了PCIe DMA、多队列收发、校验和卸载、10G/25G以太网控制器甚至PTP时间同步。我最初是在Xilinx平台上用它的当时主要做网络数据采集和线速丢包统计跑起来之后最大的感受是终于不用再被商业网卡IP的黑盒牵着走了。Corundum的工作方式可以通俗理解成把FPGA做成一张“半定制”的网卡主机侧加载一个Linux驱动后会看到标准eth0这样的网络接口。真正的数据流是主机CPU通过描述符告诉FPGA“把收到的包放到哪块内存里”FPGA通过DMA直接把报文写进主机内存CPU再通过NAPI轮询或中断拿到报文。这样路径上的大部分工作都不经过CPU逐包搬运所以单端口轻松跑满10G线速并不夸张。Corundum的核心组件大致可以拆成三块PCIe接口与DMA引擎、数据通路接收解析、发送调度、校验和、Hash、物理侧MAC/PCS/PMA。三块之间用AXI-Stream连接结构非常清晰。它对社区的价值不在于某个单一功能有多惊艳而在于把你做一块网卡涉及到的几乎所有模块都开放出来了后续做DPU、RDMA网卡预研、网络测量设备都能在这个底子上改。1.2 国产FPGA平台面临的现实问题把Corundum搬到国产FPGA上最直接的矛盾不是“能不能实现逻辑”而是“生态怎么兼容”。Corundum原生工程大量依赖Xilinx的IP核比如PCIe硬核、10G/25G Ethernet Subsystem、MIG内存接口、MMCM/PLL时钟管理甚至XDC约束文件里的一些时钟写法。国产FPGA如果要换工具链、换芯片型号这些IP全部得替换成厂商自己的版本。替换不是说照猫画虎例化一下就完事接口时序、复位极性、时钟频率、AXI地址对齐都可能不一样。另一个现实问题是国产FPGA里同时带高速serdes、PCIe硬核、足够块RAM和逻辑资源的型号本来就比Xilinx/Intel的选择少很多而且集中在各家偏高端的产品线。做这种项目我不建议拿入门级资源去硬扛。我们当时选型的原则是逻辑资源要比单端口实现所需余量多出至少30%PCIe硬核最好支持Gen3 x8板卡上最好还有DDR颗粒或比较充裕的SRAM做包缓冲。资源不够的话后续四端口扩展基本不用想。提示这里的“国产FPGA”并不特指某一款型号。实操中只需要抓住三个硬指标有高速串行收发器、有可用的PCIe硬核、逻辑资源至少200K LUT级别。满足这三项Corundum移植才有可行性。2. 硬件方案与架构选择从单端口开始的地基工程2.1 板卡资源评估与选型先算一笔资源账。Corundum在Xilinx Artix-7 200T这类芯片上跑一个10G端口大概消耗40K到50K LUT、50K左右FFBRAM用量则在几百Kb到几Mb之间具体取决于你开的队列数量和包缓冲深度。如果再算上PCIe硬核、MAC/PCS、时钟网络整个工程接近100K LUT级别。这个量级放到国产FPGA里并不算小尤其是当你想上四个端口时资源会翻数倍。我们当时先做单端口验证选的是某个带PCIe Gen3 x8和4个SFP口的国产FPGA板卡。板卡上FPGA的逻辑单元在300K LUT左右同时拥有大约10Mb BRAM外部还挂了一颗DDR3。这个配置对单端口很充裕但对四端口来说BRAM仍然紧紧巴巴因为每个端口至少要有若干MB的收发缓冲才能扛住PCIe和以太网之间的速率差异。如果板卡上再没有DDR那四个端口同时满载时丢包基本是必然的。所以我的建议是不要只盯着“能不能综合通过”要看你手里的板卡有没有给四端口留下足够缓冲。缓冲不足的话后面所有性能优化都是在为硬件底子打补丁事倍功半。如果你手头已有四口光口板卡但FPGA资源只有150K LUT那我的经验是四个10G口可以跑通但小包线速很难看64字节包性能大概率不达标。2.2 把Corundum从Xilinx工程改造成国产平台工程移植的第一步是先在厂商自己的开发环境里建一个空工程把板卡上的PCIe链路和以太网PHY分别跑通。这一步千万不能省。我见过不少团队一上来就把Corundum整套代码丢进国产工具里综合结果报错几百条根本分不清是代码问题还是IP缺失问题。正确做法是先快跑一个厂商自带的PCIe DMA例程确认板卡能被主机识别再用厂商的以太网例程让SFP光口能起来。两个基础链路都通了再开始搬Corundum。接下来是替换IP。Corundum里用PCIe硬核的地方主要是DMA描述符访问和MMIO寄存器访问代码里封装了AXI-Lite和AXI DMA接口。国产IP的AXI总线位宽和outstanding能力可能和Xilinx不一样这时候不用着急改Corundum主逻辑先把厂商IP封装成同样的axi接口就行。另一个大头是以太网MAC/PCS/PMACorundum里对MAC侧信号做了统一封装通常是标准的XGMII或自定义的axis接口需要把厂商IP的数据通路信号对到Corundum内部信号上。这一步最繁琐因为不同厂商对“rx_axis_tvalid、rx_axis_tdata”这类信号的时序定义会有细微差别。时钟和复位也要单独处理。Corundum在Xilinx工程里用MMCM/PLL生成PCIe时钟、以太网时钟、逻辑时钟而国产FPGA的时钟IP在锁定标志、复位时序、输出相位上都有差异。我们当时的做法是所有跨时钟域都显式加异步FIFO不复用Corundum里某些隐性的同步假设。改完之后先用空转测试验证每个端口的回环再逐步加入DMA通路。整个单端口移植我们用了大概两周真正写逻辑的时间不长时间都花在对IP手册和调节时序上了。3. 单端口性能调优先让一块10G网卡满载3.1 网卡侧的关键模块与参数移植跑通只是开始单端口能不能跑满线速完全要看调优是否到位。网卡侧第一个要调的是DMA描述符环。Corundum的驱动和固件配合时主机内存里会维护一个环形描述符队列FPGA通过读取描述符知道“收到的包写到哪”写完后再通过门铃机制通知主机。描述符环太浅流量一上来就会出现生产者等消费者的现象吞吐直接掉下去。我们单端口测试时就发现默认256个描述符的队列在64字节小包场景下只能跑到五六成线速把描述符环深度调到1024之后小包PPS明显提升。第二个关键参数是PCIe的MRRS和MPS。MRRS指最大读请求大小MPS指最大有效负载大小。默认配置下PCIe链路为了兼容各种设备往往会比较保守但我们做网卡是典型的高吞吐DMA场景需要把MRRS调到512字节、MPS调到256字节减少DMA读请求的拆包次数。这个参数可以写在驱动加载时的配置脚本里也可以在BIOS里改具体看平台。改完后同一个队列的DMA效率能差出好几个百分点。第三个是接收侧的包缓冲。10G以太网线速是10.3125Gbps瞬间突发流量很可能超过PCIe的搬运能力此时必须有足够FIFO或DDR吸收突发。包缓冲越大网络侧的丢包越少但代价是DMA描述符周转延迟变高。实际操作中我们一般把单个端口接收FIFO配到1MB以上配合描述符环深度一起调找到一个当前平台下的平衡点。3.2 主机侧驱动与内存调优网卡改完还要看主机侧。Corundum驱动的核心是队列管理和中断处理主机侧如果配置不当再好的DMA引擎也白搭。首先要做的是CPU绑核和中断亲和性。Linux下每个MSI-X中断都会触发NAPI调度如果中断在所有CPU核心上乱跳缓存命中率会非常低。我们当时用irqbalance关掉改成手动把队列中断绑定到固定物理核同时把处理报文的用户态程序也绑到同一NUMA节点上的核心效果非常明显。内存方面建议给网卡DMA区域预留大页内存。普通4K页在内核和用户态之间搬运时会产生额外映射开销而2MB大页能显著降低TLB miss。可以用echo 2048 /proc/sys/vm/nr_hugepages预留内存再通过DPDK或其他用户态框架映射。即使你暂时不用DPDK只是用内核协议栈大页对提升DMA内存分配的连续性也有帮助。另外如果开发机开了IOMMUDMA性能会掉得很明显因为每次DMA都需要IOMMU页表转换。我们做性能压测时会把内核参数设为iommupt将IOMMU设置为直通模式能明显降低延迟。但这属于测试环境的取舍生产环境要根据实际安全需求权衡。3.3 实测方法与性能基线单端口性能怎么测也要讲究。第一轮用iperf3跑TCP大流量看流控和中断处理是否正常iperf3 -c 10.0.0.2 -t 60 -P 8-P 8是关键单线程iperf3很容易把CPU跑成瓶颈测不出网卡真实上限。第二轮用Linux内核自带的pktgen打UDP小包这个工具有点绕但非常有效modprobe pktgen # 在 /proc/net/pktgen/ 下配置发送网卡、包大小、速率和目的IP小包场景下10G端口的理论线速大约是14.88Mpps。我们最终通过调整描述符环、MRRS、大页和中断绑核把单端口64字节包从最初的8Mpps左右提升到了14.5Mpps以上接近线速。这个结果才算给后面四端口优化打了个像样的底子。注意单端口测出的“线速”只是这块板卡的基本功。真正考验架构设计的是四个端口同时满速率打流时共享的PCIe链路、内存总线、DMA仲裁还能不能稳得住。4. 从单端口到四端口架构重构与多队列设计4.1 四端口系统整体规划与时钟域划分四端口不是简单把单端口代码复制四份。硬件上四个SFP光口就是四条独立的10G链路每条链路都有独立的串行收发器、独立的恢复时钟和独立的MAC状态机。如果只是套模板例化四次最常见的问题就是DMA访存冲突、四个端口争抢PCIe带宽、中断风暴甚至一个端口复位把另外三个端口也带崩。正确做法是把四端口当作一个“四入口单出口”的互联系统来设计。时钟域方面PCIe的时钟来自主板槽位四个以太网端口的时钟来自各自的SFP恢复时钟所以FPGA内部天然存在至少5个异步时钟域。跨时钟域数据全部走异步FIFO复位也要分开。我们当时的复位顺序是PCIe复位后先复位DMA子系统再复位配置总线最后分别复位四个端口MAC。每个复位都有超时检测防止某个光口没插模块时一直卡住整个系统。端口仲裁是另一个关键点。四路DMA写请求最终都要进同一个PCIe链路所以必须在DMA引擎入口做仲裁。我们采用的方案是加权公平调度每个端口有一个权重默认四端口权重相同但保留一个紧急通道给控制报文。仲裁粒度也很重要不能等一个端口把一整包写完才切到下一个端口那样会造成端口间严重的延迟抖动应该按DMA突发长度轮流调度。4.2 端口缓冲与共享资源分配共享缓冲池的设计决定了四端口在突发流量下的表现。如果每个端口独立分配固定大小的FIFO那么总缓冲利用效率会很低因为平时某些端口可能很空闲另一些端口却在猛灌。我们参考了商用交换芯片的做法把单端口FIFO改成共享缓冲池加小幅预留每个端口预留25%的私有缓冲剩下75%归公共池哪个端口突发大就先从公共池借用。这样不会因为某一个端口完全占死资源而饿死其他端口。缓冲池的调度算法也需要配套。我们在缓存管理里给每个端口设置了上限防止某一个端口突发时把整个缓冲池耗尽。同时在出口侧做反压当某个端口的DMA队列拥塞时对应MAC的流控信号要能及时生效。实测下来共享缓冲池的四端口丢包率比独立FIFO方案在打满状态下能低一个数量级。4.3 驱动与DMA通道的扩展FPGA里的DMA通道数决定了主机侧能看到的队列数。四端口设计里既要保证每个端口至少能独享一个DMA通道还要支持多队列RSS不然Linux内核里多核收包时会抢同一个锁。Corundum原生的多队列机制比较完整但四端口化之后要确认每个端口各自维护独立的描述符环同时所有描述符环最终共用一个PCIe DMA引擎。我们在这个阶段踩过一个大坑四个端口使用同一个MSI-X中断向量导致中断频率翻倍后CPU处理不过来。后来我们在RTL里为每个端口的每个队列分配独立MSI-X中断同时Linux驱动侧给每个中断绑不同的CPU核端口0的队列绑CPU0-3端口1的队列绑CPU4-7依此类推。改完后四端口同时跑流时CPU之间不再相互干扰整体PPS也上去了。驱动扩展还有一个细节每个端口需要一个独立的net_device结构体但底层DMA资源可以由同一套代码管理。在Linux驱动里做多端口注册时不要把全局变量当成单实例否则第二个端口注册时会把第一个端口的寄存器地址覆盖掉。这个报错通常很隐蔽不仔细看会以为是时序问题。5. 四端口性能优化实践线速的最后一公里5.1 瓶颈定位PCIe带宽、DMA效率还是包缓冲四端口性能优化第一步是定位瓶颈而不是盲目调参。我们先把四个端口全部接到测试仪的四个口上分别做单向收、单向发、双向收发三组测试。单向收四口同时10G总接收流量约40Gbps实际数据约为5GB/sPCIe Gen3 x8的理论有效带宽约7.88GB/s所以单向收的瓶颈不在PCIe链路。但如果是全双工同时跑收发合计80Gbps也就是约10GB/sPCIe Gen3 x8就不够了必然出现带宽收敛。这里要特别强调一个计算不要只看以太网标称速率。10G光口线速是10.3125Gbps去掉前导码和帧间隙MAC有效带宽约10Gbps四个口单向就是40Gbps约5GB/s。PCIe Gen3每个lane单向约985MB/s有效带宽x8约7.88GB/s。所以四口单向接收用Gen3 x8有余量但双向线速必须上Gen3 x16或Gen4 x8。我们手头板卡是Gen3 x8最后就只能以“单向四口线速”为优化目标双向场景做降级处理。内存侧也可能成为瓶颈。PCIe DMA写主机内存如果内存控制器本身带宽不够或者DMA写到了远端NUMA节点性能下降很厉害。可以用numactl --hardware看CPU和内存拓扑把网卡和业务进程尽量放在同一个NUMA节点。5.2 优化配置表与实施步骤四端口压测时我们整理了一份配置表每项都经过实际验证。这里列出来供参考优化项推荐配置调整目的描述符环深度每队列1024减少小包场景下DMA等待PCIe MRRS512 Byte减少DMA读请求拆分PCIe MPS256 Byte匹配网卡DMA载荷每端口队列数4个RX队列 4个TX队列支持多核并行收包MSI-X中断每队列独立中断避免中断风暴CPU亲和性队列中断绑定固定核心提升缓存命中接收侧大页2048个2MB页降低TLB miss共享缓冲池每端口预留25% 公共池75%提高缓冲利用率实施顺序建议从硬件到软件先确认PCIe链路工作在Gen3 x8用lspci -vvv查看LnkSta再调整FPGA固件中的描述符环深度和共享缓冲池然后改驱动加载参数和中断亲和性最后才用pktgen和iperf3做压力验证。跳着来容易出问题比如先软件后硬件最后发现瓶颈其实在PCIe链路就没有意义了。5.3 验证结果与效果对照我们在四端口单向接收场景下最终让每个端口都跑到了接近线速。64字节小包单向总PPS大约稳定在58Mpps左右理论值是4乘14.88Mpps约59.5Mpps已经比较接近了。换成1024字节大包后总吞吐约40Gbps没有丢包。这个结果说明四个端口的DMA仲裁、共享缓冲和PCIe带宽分配达到了一个比较好的平衡。双向全双工场景就没这么乐观因为PCIe Gen3 x8物理带宽摆在那实际总吞吐大概在55Gbps到60Gbps之间。这个结果我们认为是合理的毕竟架构上选择了x8链路。如果想做到双向全双工80Gbps线速必须换支持PCIe Gen3 x16或Gen4 x8的板卡同时DMA引擎也要做更宽位的读写并行这不是靠软件调优能解决的。所以做这类项目提前评估好场景最重要。如果业务只关心“四口同时接收”Gen3 x8够用如果要求严格全双工线速别在选型阶段省钱。6. 避坑指南移植和调优过程中最常踩的坑6.1 国产FPGA工具链的兼容性坑国产FPGA的工具链和Vivado在SystemVerilog支持度上有不少差异。Corundum代码大量使用SystemVerilog的interface、结构体、带副作用的赋值这些在Vivado里能顺利综合但在部分国产工具里可能直接报语法错或综合出意想不到的结果。我们的处理方式是保留一份“可综合子集”的代码风格规范遇到工具不支持的语法就改成普通module端口信号不要在interface上死磕。另外国产工具对未连接信号、悬空端口管的比较松这在仿真时看不出问题但上板后可能变成不定态需要格外注意。时序收敛也是一大坑。四端口工程规模变大后布局布线时间明显变长时序不收敛时不要急着加流水线先看关键路径出现在哪个模块。我们遇到过一块时序问题其实是在共享缓冲池的读指针跨时钟域逻辑上而不是在以太网MAC里。把读指针改成格雷码同步后时序立刻变好。注意工程换工具链后之前Vivado里的XDC约束不能直接复用。国产工具的时钟约束、引脚约束、区域约束写法都不同尤其是SFP引脚的电平标准和RX/TX差分对约束错一个整块板都起不来。6.2 多端口稳定性问题四端口跑起来之后最容易遇到的问题是“偶尔丢包”。这种丢包往往不是线速测出来的而是在长时间混合流量下发生的。第一次排查我们会看ethtool -S统计里的rx_missed、rx_dropped如果这些计数在增长大概率是FPGA内部缓冲不足或DMA描述符更新不及时。如果统计计数不增长但应用层丢包那问题可能出在驱动NAPI调度比如中断没有及时触发。还有一种隐藏问题四个SFP端口的光模块散热和供电。FPGA高速串行收发器对电源纹波非常敏感四端口同时高速工作时板卡局部温度升高会导致serdes误码率上升表现形式就是偶发的CRC错误和链路重训练。我们当时在背板加了风扇导风罩把光模块温度压到65度以下后这个问题基本消失。硬件稳定性问题常常被软件手段误诊非常值得注意。多端口之间还会出现“抢带宽”现象。某个端口开启大流量时另一个端口延迟明显变大。这个主要靠仲裁权重解决但也要确认共享缓冲池的上限是否生效。如果某个端口能无限借用公共池其他端口必然饿死。我们后来在每个端口的入口都加了流量整形超过阈值就触发反压端口间的公平性立刻改善。6.3 性能测试中容易被误导的坑用pktgen和iperf3跑四端口有几个测试配置错误会直接导致结果虚低。第一四路打流的源IP和目的IP不能相同否则接收端RSS哈希会把所有流分到同一个队列多核优势发挥不出来。第二小包测试要用随机源MAC或随机IP让哈希足够分散不要所有包都一样。第三iperf3默认单线程不指定-P参数时测出来的结果大概率只是CPU协议栈的瓶颈不是网卡真实上限。另外用测试仪打流时四个端口之间如果有相同的VLAN或相同MAC地址交换设备可能会做MAC学习导致部分流量被丢弃。我们吃过一次亏四口打流性能上不去查了半天发现是测试仪端口之间默认共享MAC地址表流量被错误转发到了一个没收发包的端口。如果你看到的性能数字忽高忽低先不要怀疑FPGA逻辑先确认测试仪和主机之间的协商速率、流控模式、光模块是否都能跑到10G。很多国产板卡的光模块兼容性一般换个知名品牌的模块后误码率能降一个数量级性能自然就上来了。收尾做这个项目最大的体会是Corundum给了你一个很好的起点但“能跑”和“跑满”之间隔着大量系统级的调优工作尤其是国产FPGA平台生态差异会把很多隐性坑放大。四端口看似是资源翻倍实际上是从单链路变成共享链路PCIe带宽、内存访问、中断处理、缓冲仲裁全都挤在一起任何一个环节短板都会扯后腿。如果让我重新选型做一次我会在一开始就把PCIe链路余量留足至少Gen3 x16同时优先选带DDR的板卡。这个余量后面加TCP校验卸载、报文时间戳、额外抓包逻辑的时候会帮上大忙。Corundum本身很灵活但灵活意味着需要你对每个模块足够熟悉愿意花时间一点点调。折腾过这一轮之后再看开源网卡和商用网卡之间的差距你会非常清楚哪些是硬件能力决定的哪些是软件和设计细节决定的。