
先把标题拆开看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 FPGAPCIe硬核是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个。放在网卡场景里这些资源意味着你不仅能跑通基础的MACDMA设计还有余量去塞卸载引擎、流表查询、甚至简单的加密处理。板卡上的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配置成BasicCustom模式速率设为10.3125GbpsPMA只做串并转换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传参pcirealloc解决的。第三个里程碑才是网口能收发数据。先跑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方向上的字节交错问题了下次见。