☰
Corundum 100G NIC 移植到 Bittware VV4:Arria 10 平台工程搭建与调试实践
2026/9/26 2:05:47 网站建设 项目流程

先把标题拆开看:Corundum是个开源的100G NIC项目,Bittware VV4是块带Arria 10 FPGA的PCIe板卡,题眼在“移植”。也就是说要把一套原本为Xilinx平台设计的开源网卡逻辑,搬到Intel/Altera的板子上跑通。关注这类内容的人,基本是搞FPGA高速接口、网络卸载、或者自研智能网卡的工程师,手里大概率有一块VV4或者类似的Arria 10板卡,正琢磨怎么把现成的开源成果吃下来。

这个系列文章,我打算按工程推进的顺序来写。第一步先把平台差异和移植思路捋清楚,第二步再讲具体怎么改代码、怎么调时序、怎么把驱动跑起来。本文是第一篇,重点放在环境准备、硬件分析、框架选择和最初的工程搭建上。

1. 移植前的灵魂拷问:为什么要动Corundum这块蛋糕

Corundum这个项目在FPGA网卡圈子里算得上明星项目了。作者Alex Forencich把一套完整的100G NIC实现开源出来,包括MAC、PCIe DMA引擎、队列管理、中断控制,甚至还有精确时间同步(PTP)的雏形。最关键的是,这套东西不是玩具级别的demo,而是真正考虑了线速处理、多队列、高性能DMA的实用设计。用过Xilinx 7系列板卡跑过Corundum的人应该有体会,它的代码结构清晰,接口规范,拿来改成自己的定制网卡非常顺手。

但问题也出在这里——Corundum官方支持的平台基本是Xilinx系,从KC705到VCU118,人家用的是UltraScale+的Integrated Block for PCIe,收发器也是Xilinx的GTY/GTZ。你手里要是只有Bittware VV4这种Altera系板卡,直接拉代码编译,光是PCIe IP核和Transceiver IP就不是一个套路。VV4用的是Intel Arria 10 FPGA,PCIe硬核是Arria 10 Hard IP for PCIe,高速串行收发器是Arria 10 Transceiver Native PHY,这两个IP的接口时序、复位逻辑、寄存器配置和Xilinx完全不同。

我最早拿到VV4这块板子的时候,第一反应是想省事:能不能用Vivado直接建工程,把Corundum代码塞进去编译?答案显而易见:不行。光看顶层,Corundum的PCIe接口对接的是Xilinx的XDMA或硬核出来的AXI4接口,而Arria 10的PCIe Hard IP虽然也带AXI4接口,但握手时序、用户时钟频率、复位行为都有细微差别。这些差别在仿真里未必看得出来,一上板就跑飞。

所以这篇移植记录的核心思路,就是要解决三个层面的问题:一是工程框架怎么搭,二是PCIe和收发器这两个硬核相关的部分怎么替换,三是DMA和MAC逻辑哪些能沿用、哪些必须改。

2. 硬件底细:Bittware VV4到底给了我们什么

Bittware VV4是一块标准的半高半长PCIe Gen3 x8板卡,亮点是搭载了Arria 10 GX 660 FPGA。这颗芯片的资源对于跑100G NIC来说算是富余的:逻辑单元约66万,DSP Block有921个,M20K内存块有2136个,28Gbps收发器有48个。放在网卡场景里,这些资源意味着你不仅能跑通基础的MAC+DMA设计,还有余量去塞卸载引擎、流表查询、甚至简单的加密处理。

板卡上的PCIe接口支持Gen3 x8,理论带宽约64Gbps,跑100G网卡的数据通路略有点紧张,但实际用下来没太大问题,因为100G端口的线速虽然高,但PCIe侧只要不追求极致的RX/TX双向同时满负载,x8的Gen3足够应付大部分场景。如果以后要上200G,就得考虑换板卡或者用CXL了,这是后话。

存储接口方面,VV4集成了一组DDR4接口,容量看板载颗粒配置。Corundum本身支持用DDR做描述符缓存或者包缓冲,这一步移植时可以把DDR4控制器的PHY层替换成Arria 10的EMIF IP,再对接Corundum的AXI接口。这个我在后续文章的“存储通路”部分会细讲,这里先记住一点:VV4的DDR4时钟域和Xilinx板卡的默认频率不同,不能照抄Corundum默认参数。

板载时钟方案也需要关注。VV4的高精度时钟源为收发器提供参考时钟,需要检查参考时钟频率是否匹配100G MAC所要求的156.25MHz或161.1328125MHz。Corundum的100G MAC用的是XGMII接口,收发器需要配置成10GBASE-R或者自定义编码模式。Arria 10的Native PHY IP支持多种协议模板,我们要选对相应的配置或者采用自定义模式,这是后续调通链路的前提。

另外,VV4上有一组QSFP28笼子,这是光模块接口。你的QSFP28模块必须支持100G SR4/LR4或者有DSP的模块,否则收发器的CDR可能无法锁定。我第一次调试时用了手头一个40G的QSFP模块,结果RX侧死活不亮,换100G模块后秒亮,这个坑后面会详细说。

3. 移植总体框架:哪些能留,哪些必须换

拿到Corundum源码后,不要急着编译,先把目录结构摸一遍。Corundum的RTL代码主要分为几个模块:pcie的DMA引擎(mq_dma等)、MAC层(xge_mac等)、速率转换和接口适配逻辑、以及顶层例化。这套代码的设计意图是用一套逻辑适配不同的PHY和PCIe实现,所以在代码里你能看到很明显的接口抽象层。

我的做法是:把代码分成三类。

第一类是纯逻辑模块,比如队列管理、DMA描述符处理、中断合并、以及各种AXI跨时钟域处理。这些代码跟FPGA厂商无关,基本可以直接复用,只需要注意位宽匹配。Arria 10的AXI接口位宽通常可以配成512位,而Corundum的DMA数据通路也是512位为主——这个匹配很关键,因为如果位宽不一致,数据通路上就要插入转换逻辑,既增加延迟又容易出错。

第二类是厂商相关模块,主要是PCIe硬核封装和收发器PHY。这一部分必须替换成Arria 10的IP。Corundum的PCIe封装是一个叫pcie_ultrascale_plus的模块,里面内含Xilinx的硬核配置和复位逻辑。你需要重写为Altera的pcie_arria10模块,并把硬核的AXI4接口与Corundum的pcie_axil_master、pcie_axil_slave等模块正确对接。收发器这块,Corundum用xge_phy封装了GTY收发器,你要替换成Arria 10 Native PHY,并且要注意恢复时钟。Arria 10的Native PHY在10GBASE-R模式下,RX恢复时钟需要接到FPGA内部的时钟网络,再分频给XGMII接口使用,这和Xilinx的GTY的RXOUTCLK用法略有不同。

第三类是板级约束,包括引脚分配、时钟约束、差分对约束和时序例外。这部分必须从零开始写。Corundum的XDC文件全是Xilinx语法,SDC里有些写法类似,但很多约束命令不一样。比如Arria 10推荐用derive PLL clocks自动推导时钟关系,而不是手动create_generated_clock每一个PLL输出;Xilinx里用set_property PACKAGE_PIN来锁引脚,Quartus里用set_location_assignment。这些差异在移植初期就会遇到,需要边改边适应。

从整体框架上看,移植的思路是保留Corundum的逻辑核心,替换厂商IP封装,重写约束文件。听起来简单,实际操作中光是PCIe硬核的复位顺序就可能折腾一周。

4. 工程搭建:Quartus环境下的第一步实践

我是先在Ubuntu 20.04下用Quartus Prime Pro 21.3来做的,Arria 10是Pro/Standard都支持的器件,但建议用Pro版本,因为有些新IP特性只在Pro版里开放。工程搭建有两种方式:一种是直接在Quartus GUI里新建工程,一种是写TCL脚本全自动建工程。我推荐后者,理由很简单——移植过程中需要反复clean和rebuild,脚本化能省掉大量重复操作。

核心的工程文件至少包含这几部分:

第一,顶层文件。Corundum为每个板卡都写好了对应的顶层qsf/top,你需要新增一个bittware_vv4_top.v,例化Arria 10 PCIe硬核IP和Transceiver IP,同时保留Corundum的dma和mac模块。顶层文件是这个工程最关键的地方,因为它要协调所有接口,任何一根信号接错,仿真阶段不容易发现,上板就是灾难。

第二,时序约束文件(SDC)。除了时钟、复位、引脚约束外,还需要对PCIe接口加set_input_delay和set_output_delay约束,对收发器接口约束差分引脚对,并对异步时钟域设置set_false_path。一个容易忽略的是,Arria 10的收发器TX和RX时钟经常做成两种频率:一种和线速相关,另一种是固定用户时钟。Corundum内部MAC的XGMII接口时钟是156.25MHz,与Arria 10 Native PHY的用户时钟一致,这里需要确认quartus能识别出这个时钟域关系,否则时序收敛会很痛苦。

再说工程目录。我的习惯是把Corundum源码的rtl目录原样保留,另外建一个platform目录,里面放VV4相关代码和约束。这样做的好处是方便同步上游更新。Corundum项目迭代挺快的,如果你改乱了原始rtl代码,后续拉新版本会很痛苦。这块我吃过亏,刚开始图省事直接在rtl里改,碰到一次上游大更新,合并代码花了两个晚上。

5. 从仿真到上板:先把数据通路打通

工程搭建好之后,不要急着综合,我建议先做一次逻辑仿真。Corundum自带一套基于Verilator或Icarus的仿真环境,可以跑寄存器读写、DMA收发的基本testbench。把仿真环境里的板卡型号换成VV4后,先跑一遍pcie_reg测试,确保PCIe配置空间能够访问。Arria 10 Hard IP的配置空间默认参数和Xilinx有差异,比如厂商ID、设备ID、BAR大小,这些如果不对,驱动程序加载后找不到设备。

如果仿真通过了,再上板。上板调试我习惯分三步走:先用Quartus的Signal Tap抓PCIe的AXI接口信号,确认硬核能收到CPU发来的配置读写请求;再跑一个简单的DMA环回,让FPGA内部把TX数据环回到RX路径,验证DMA引擎的搬运逻辑没问题;最后才把QSFP28光模块接上,和外部设备对打流量。

这里尤其提醒一点:Arria 10的PCIe硬核有一个独立的复位管脚nPERST,还要检查板卡上PCIe的参考时钟是否可配置。如果参考时钟频率不是100MHz,硬核配置空间里的链路速率和宽度协商就会异常。VV4板卡的PCIe参考时钟是100MHz,这个很标准,一般不用动。

数据通路的第一个关键点在DMA描述符。Corundum的DMA引擎和驱动配合使用,驱动通过BAR空间读写描述符环形队列。当你从Xilinx平台换到Altera平台,BAR空间的映射大小和地址对齐方式可能会变,所以一定要确认Corundum驱动代码里读到的bar物理地址和硬核实际解码到的地址一致。我在调试时打印过lspci -v,发现BAR大小是1MB,而Corundum源码里默认的BAR是4MB,这就直接导致驱动访问出错。解决办法是在Quartus的PCIe硬核配置里把BAR大小改掉,或者改驱动里的地址掩码。

6. 收发器配置:从GTY到Arria 10 Native PHY

收发器是这次移植中最容易让人头疼的部分。Corundum的xge_mac模块对外是XGMII接口(64bit @156.25MHz),内部实现了一个64B/66B编解码器,收发器工作在自定义的64B/66B模式。Xilinx的GTY通过配置成Raw模式或者10G Base-R PCS模式来和MAC对接,然后连接到MAC层的XGMII接口。而Arria 10的Native PHY则提供了更多选项,你需要仔细选择。

我的配置经验是用Arria 10 Hard IP for 10GBASE-R或者直接自定义Native PHY。如果选择10GBASE-R模板,那么PHY内部就包含了PCS和PMA,输出恢复时钟和64bit的数据接口(顺序是XGMII的字节序)。要注意的是,Corundum的mac模块自带PCS功能,所以尽量不要让Native PHY重复做PCS,否则你的时序和字节序很可能对不上。

正确做法是:Native PHY配置成Basic(Custom)模式,速率设为10.3125Gbps,PMA只做串并转换,PCS功能关闭,从PMA直接输出64bit数据和时钟给Corundum的xge_mac。这个过程里你需要自己确认位序映射:Arria 10的收发器数据输出是按字(word)组织的,而Corundum的xge_mac是byte-lane模式,中间需要做一次位宽映射。这一步没有捷径,只能对着波形逐字节地核对。

还有一个坑是TX路径的相位对齐。Arria 10的Native PHY在TX端如果使用FPGA TX bit slip或者字节偏移功能,必须配置正确,否则发出去的包在接收端会全部错位。Corundum的源码里有一个tx_byte_align逻辑,但它是为Xilinx的收发器设计的,你需要在Arria 10里用serdes的rx_bitslip或analog reset来替代。调试这个问题的典型现象是:用抓包工具能看到链路层有大量CRC错误,但物理链路协商正常。

7. 时钟与复位:两个最容易翻车的细节

Arria 10对时钟和复位的要求比Xilinx更严格,这也是很多移植项目卡壳的地方。

时钟方面,Corundum的设计里通常有多个时钟域:PCIe用户时钟(通常是250MHz或500MHz)、MAC的XGMII时钟(156.25MHz)、DMA描述符处理时钟、以及跨时钟域FIFO两侧的异步时钟。在Xilinx下,这些时钟由MMCM/PLL生成,用户时钟是固定可配置的。在Arria 10下,PCIe硬核的时钟输出是固定的,一般可以配成250MHz或500MHz。你需要看Corundum的DMA引擎和AXI接口需要多少频率,然后选择合适的硬核时钟输出。如果硬核定死了500MHz,但Corundum逻辑的时序只收敛在350MHz以下,就得考虑在中间插入异步FIFO来降频。

复位方面,Arria 10的PCIe硬核给出了一个user_reset输出信号,但这个信号不是全局复位用的。我最开始直接把user_reset接到整个设计的所有复位端口,结果调度模块的状态机时不时出错,表现是丢包率约万分之一,很难抓。后来查资料才发现,Arria 10硬核的user_reset只是指示用户逻辑和PCIe事务层的接口复位,MAC层和DMA引擎需要用自己的本地复位同步逻辑。Corundum的代码里本来就有reset_sync模块,但前提是你要给它喂一个干净的异步复位源。我的方案是:把user_reset连到reset_sync的输入,再由reset_sync产生各个时钟域内的同步复位,经过cal_blk_clk和pcie_user_clk等时钟域处理后再分发。

如果你在Quartus里看到很多关于跨时钟域复位的时序违规报告,别急着加set_false_path,先检查复位信号是不是经过了正确的同步。Arria 10的全局复位网络资源有限,过多的复位信号扇出也会让时序变差。

8. 调试工具链:没有这几样,移植很难推进

调试FPGA网卡,工具链很重要。除了Quartus自带的Signal Tap和System Console之外,我还用了以下几样东西:第一是PCIe分析工具,比如lspci、setpci、以及Linux内核的pciutils工具集,用来验证硬核的配置空间枚举是否正常;第二是流量发生器,建议准备一个能打100G线速的测试端口,如果是做开发调试,可以用Spirent或者简单的iperf3配合DDPDK测试;第三是Wireshark配合一个管理口进行抓包分析,用于定位ARP、ICMP等基础报文的收发。

调试Corundum的DMA还有一个技巧:先用一个简单的寄存器读写验证BAR映射,再打开驱动里的大块DMA搬运测试,最后才考虑多队列和中断。中断调试坑比较多,尤其是Arria 10的中断控制器和Xilinx不同,MSI-X中断的配置需要额外注意。Corundum代码里的MSI-X处理逻辑是基于Xilinx的,如果你在Arria 10上跑,要检查硬核暴露的MSI-X表结构和Corundum的驱动是否一致性。我的经验是,如果MSI-X老是不触发,先用intx模式跑通,再把MSI-X打开,一步步验证。

开发过程中我记得有一个特别值得分享的排查过程:有一次100G端口在RX方向可以收包,但TX方向完全不通。驱动配置、DMA队列、MAC层都检查过,信号也都是对的。最后用Signal Tap抓数据发现,TX方向的AXI数据总线在写入前四个beats时正确,第五个beats开始数据全零。排查后发现是Arria 10的Native PHY在TX端有一个internal_fifo的almost_full信号,我没有连到回压逻辑上,导致PHY在上游数据速率不匹配时悄悄丢弃了数据。把almost_full信号引出来,接入到xge_mac的tx_axis_tready逻辑后,问题马上解决。

9. 第一个里程碑:从“编译过”到“链路通”

对于第一次移植来说,我的建议是先不要追求全部功能,设定三个里程碑。

第一个里程碑是工程能编译通过。Quartus的综合和布局布线是两回事,综合通过很容易,布局布线时序收敛很难。第一次编译通过后先看资源用量和时序报告,Arria 10的收发器资源有几十个,Corundum默认实例只用一个通道,资源肯定不紧张;但PCIe硬核和收发器的时钟网络必须保证连对,Quartus可能会警告你“no clock”或“clock not connected”,这些都得逐条解决。

第二个里程碑是PCIe枚举成功。上电后开机进入Linux,用lspci能看到你的VV4被识别为“Ethernet controller”设备。如果识别不到,最可能的原因就是硬核复位有问题或者差分引脚约束不对。Arria 10硬核的PERST信号是低电平有效,复位期间不能访问配置空间,否则会一直挂起。我遇到过一次特别诡异的现象:设备能识别,但调用驱动时kernel panic,后来排查发现是设备的中断资源分配和驱动冲突,通过kernel commandline传参pci=realloc解决的。

第三个里程碑才是网口能收发数据。先跑loopback模式,在MAC层把tx数据直接接到rx逻辑,验证DMA搬运没问题;再通过外部回环线或光模块交叉连接,跑单方向的流量。等这两个都通了,再尝试双向同传。

我自己踩过的风险点:直接上来就接100G光模块打流量,出了问题根本不知道是MAC层、DMA层还是光模块的问题,排错成本太高。循序渐进比较稳妥。

10. 关于后续计划的预告

这一篇把移植的整体框架、工程搭建和关键模块替换思路讲完了。下一篇重点讲PCIe硬核的复位时序和地址映射细节,并给出Arria 10硬核配置的完整清单,包括BAR大小、MSI-X配置、DMA描述符对齐要求等。再后面一篇会重点写Native PHY与Corundum MAC之间的字节序、位宽、恢复时钟处理,以及如何在Signal Tap里设置触发条件去定位CRC错误。

这个移植项目做下来,最大的体会是“开源代码不等于拿来即用”。Corundum代码质量很高,接口抽象和模块划分都算合理,但底层硬核的差异始终是移植成本的核心。你在Xilinx平台上积累的经验,换到Altera上不能生搬硬套,尤其是复位、时钟和字节序这三个坑,每一个都能耗掉你几天时间。我的建议是:开工前一定要把硬核手册和板卡原理图读明白,别急着写代码;Quartus的时序报告和Signal Tap是你最忠实的朋友,遇到问题先抓波形,不要瞎猜。

好吧,第一阶段的经验就分享到这里。接下来我要继续调TX方向上的字节交错问题了,下次见。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询