写在前面,这不是一篇“照着官方README装环境”的流水账。记录的是我最近一直啃的一件事:把开源100G网卡方案Corundum,从它默认支持的板卡上挪到Bittware VV4这块Arria 10板子上。整个移植目前只完成第一阶段,但也正因为只走完第一阶段,踩坑的细节都还热乎着,趁记忆新鲜写下来,给自己留档,也希望能给后来人省点时间。
先交代一下背景。Corundum是Alex Forencich维护的开源FPGA网卡实现,纯Verilog,核心是自研的PCIe DMA引擎加自研的以太网MAC,支持10G/25G/40G/50G/100G速率。它的好处是整条数据通路都是开源的,从MAC、PCS、DMA到PCIe队列管理,每一层都能打开看、能改、能重新综合。这点在现在尤其难得,因为厂商自带的100G网卡IP基本都是黑盒,可定制性差,许可证还卡得死死的。而Bittware VV4这块板子呢,Intel Arria 10 GX 1150加双QSFP28,板载PCIe Gen3 x8,天生就是干这个的料。
这篇文章是系列第一篇,重点不放在代码细节上,先把整体设计思路、板卡资源梳理、移植前准备和第一阶段踩到的坑讲清楚。后面几篇会分别展开DMA调通、收发器配置、驱动对接和性能测试。
1. 为什么非要用Corundum,而不是厂商IP
工程里选择方案的第一件事,就是搞清楚“为什么”。如果只是想把网络流量跑起来,那直接用Intel官方的例程改改,比移植Corundum省力得多。我选Corundum,是因为它解决的是我正在面对的一个实际问题:如何在FPGA上做一套可以按需修改的100G智能网卡。
1.1 这套开源网卡到底解决了什么问题
Corundum的完整数据面链路是这样的:主机侧通过PCIe发起DMA描述符,控制面用AXI-Lite寄存器组做配置,数据面用AXI-Stream传输,一直到以太网MAC、PCS,最后到高速收发器。中间的所有模块都在rtl目录下,自己清楚得很。
它自带的东西包括:
- 高性能DMA引擎,支持多队列,每个队列独立描述符环形缓冲区;
- 完整的多队列调度和中断合并,可以减轻主机CPU负载;
- 自研的10G/25G/40G/50G/100G以太网MAC,不依赖厂商MAC IP;
- PTP硬件时间戳、校验和卸载、RSS哈希等功能模块。
这里有个关键的移植友好点:Corundum把“厂商相关”和“厂商无关”的逻辑分得很开。厂商无关的,比如MAC、调度、DMA,这些是纯RTL,放到哪个FPGA平台上都一样。厂商相关的,主要集中在PCIe硬核接口、高速收发器、时钟和复位这几个地方。也就是说,把一块板卡上的Corundum移植到另一块板卡,核心工作不是改逻辑,而是换“底座”。
1.2 移植思路:不是从零开始,而是改板级
最开始我差点走弯路,想着从空工程开始,一个个把模块例化出来连起来。后来看了Corundum源码里的fpga/board目录才明白,官方早就把板卡相关的东西隔离了。每个板卡对应一个顶层模块,上面例化该板卡的PCIe硬核、收发器、时钟芯片,再往下接Core逻辑。当时的思路就变成了:
- 找到官方Arria 10参考板卡的顶层文件和约束文件;
- 对照Bittware VV4的原理图,把顶层里板卡相关的部分全部换掉;
- 保持Core逻辑不动,顶层的接口只做信号重映射和时钟复位适配;
- 先让PCIe能被系统枚举出来,再让网卡驱动挂上,再让100G链路up,最后才测DMA收发。
这个顺序非常重要。如果一开始就想着所有功能一起调,出了错根本没法定位。移植的本质不是改逻辑,而是做平台适配,改顶层就像换插座,里面的电路板还是一样。
2. Bittware VV4的硬件底子
方便后面讨论,先把Bittware VV4的关键资源盘一遍。这块板子不是什么新型号,但在原型验证里出镜率很高,网上资料也不少。板卡核心是Intel Arria 10 GX 1150,逻辑单元数量超过一百万,DSP和BRAM资源也不小,用于100G网卡数据通路绰绰有余。真正决定移植难度的,是它周围的几组信号。
2.1 Arria 10 GX 1150与PCIe Gen3 x8
Arria 10 GX 1150集成PCIe Gen3硬核,板卡金手指提供x8链路,理论带宽接近64Gbps(Gen3 x8单向)。这里面有一个容易被忽略的点:PCIe硬核的参考时钟来自金手指的REFCLK,通常是一对100MHz差分信号。PCIe硬核的复位是PERST#,这个信号由主板拉低再拉高,作为整个PCIe链路训练的开始条件。
做移植时,我在顶层里最关心的就是PCIe信号的命名和方向。Bittware VV4的原理图里,PCIe差分对、PERST#、CLKREQ#这些信号名字,和官方参考板卡不一定一致。这些必须一个一个对照修改,错一个脚,PCIe枚举就会失败。
2.2 双QSFP28与100G光模块链路
板载两只QSFP28 cage,这是最吸引我的地方。每只QSFP28提供4个高速收发器通道,在Arria 10 GX上可以配置成4x25.78125Gbps,正好组成一条100G以太网链路。也可以用分支线缆跑4x25G,这点后面调PCS时再细说。
QSFP28的问题在于它不只是高速线,还有一个管理接口。QSFP28的热插拔检测、模块存在检测(ModPrsL)、复位(ResetL)、低功耗模式(LPMode)、I2C接口,都需要FPGA这边有对应的GPIO和I2C控制器去操作。移植时最容易漏的就是这些低速管理信号,漏了一个,光模块就起不来。
2.3 时钟和复位的特殊之处
时钟是这次移植里最费心思的地方。VV4板上的时钟源和官方参考板完全不同。100G以太网MAC需要156.25MHz时钟,收发器需要对应速率的参考时钟,PCIe又需要100MHz参考时钟,DDR、QDR等存储器又各自有独立时钟。这些时钟的频率、相位、极性,甚至上电顺序,都会直接影响功能是否正常。
德州仪器SI5338这类可编程时钟芯片在VV4这类板卡上很常见,它靠I2C配置,默认配置不一定是100G以太网需要的频率。上电之后必须先把时钟芯片写好,再释放FPGA内部复位,否则后面的模块拿到一个错误频率的时钟,调试过程会非常痛苦。
2.4 板载资源映射列表
整理一张表,方便后面写代码时对照:
| 功能点 | VV4板卡资源 | Corundum中对应的接口 |
|---|---|---|
| PCIe | 金手指Gen3 x8,100MHz REFCLK,PERST# | pcie_*端口组 |
| 100G光口 | QSFP28 cage,4x25.78G收发器 | mii_*接口到100G MAC |
| 光模块管理 | I2C、ModPrsL、ResetL、LPMode | qsfp_*寄存器接口 |
| 时钟 | 可编程时钟芯片默认配置 | clk_*输入输出 |
| 复位 | 板上全局复位、PCIe PERST | rst_n输入 |
| 调试 | JTAG、UART、LED、按键 | fpga顶层调试端口 |
这里列出来的每一项,都会在顶层设计里变成一组具体的端口或寄存器,所以这张表就是我后面写RTL时的对照清单。
3. 移植前准备:环境、源码与架构梳理
很多移植项目死在“还没开始写代码就乱套了”这点上。环境不一致、源码版本不对、板卡参考设计没下载,最后编译报错都分不清是环境问题还是代码问题。所以我把准备阶段当成正事做,而不是随便装个软件就开干。
3.1 工具链选择与IP许可
Corundum官方仓库里带有Altera平台的参考设计,但用的Quartus版本可能和当前习惯版本不一样。我的建议是,尽量贴近官方参考设计当初维护的Quartus版本,或者至少保证工程升级时IP核能被正确迁移。Arria 10器件在Quartus Prime Pro和Standard里都有支持,但工程格式不一样。我当时在Pro和Standard之间犹豫了一下,最后选了Pro,理由是Arria 10硬核IP在Pro里维护得更勤。
许可证这块一定要提前确认好。Arria 10 GX 1150的器件支持有时候不在免费Lite许可证范围内,需要设备锁定许可证。这个如果没准备好,跑综合到一半才报license错误,才是真的浪费时间。
3.2 解读Corundum的源码结构
Corundum的rtl目录按功能分得很清楚:rtl/core是DMA、队列、调度等核心逻辑,rtl/eth是以太网MAC、PCS相关,rtl/pcie是PCIe相关适配,rtl/misc是各种辅助模块。这个目录结构是移植者最好的朋友,因为你能一眼看出哪些文件是纯逻辑、哪些文件是平台相关。
我花了不少时间读rtl/pcie里的A10相关代码。理由很简单:Arria 10的PCIe硬核接口和Xilinx的差别很大,AXI/ Avalon接口类型、时钟域、复位控制都不一样。Corundum在Altera平台上使用Avalon-ST接口与PCIe硬核交互,这需要理解IP核生成的接口签名,才能把信号对接到官方核心逻辑上。
3.3 找到官方参考设计并做差异对比
Corundum仓库fpga目录里,按厂商和板卡分了多个子目录。拿官方Arria 10板卡工程和VV4板卡对比,差异集中在几块:
- 引脚分配:参考设计的引脚名肯定对不上;
- 时钟/复位连接方式:参考设计里的时钟芯片型号不同,初始化和约束方式也不同;
- PCIe硬核IP配置:参考设计可能是x4或x8,要按VV4实际改成x8;
- 收发器配置:参考设计可能面向某个特定的QSFP cage引脚序。
这块对比越细,后面写顶层的时候就越顺。
3.4 如何定义第一阶段的验收标准
移植这种工作,没有验收标准就不知道自己做到哪一步了。我给第一阶段的定义是四步:
- Quartus工程编译通过,时序收敛,能正常生成bitstream;
- 板卡上电后,主机通过
lspci能看到FPGA网卡设备; - 加载Corundum的Linux驱动后,系统能识别出一个以太网接口;
- 把100G光模块连接好,接口状态从DOWN变UP。
这四步是不折不扣的“基础目标”。在没做到这些之前,我不碰任何性能调优。事实证明,这四步本身也不简单,光第一阶段的调试就花了我好些个完整晚上。
4. 核心适配工作
这一章是移植的主体,也是最容易出问题的部分。我按硬件数据流的方向来组织:先说顶层怎么写,然后说时钟和复位,再说PCIe和DMA对接,然后是QSFP28和100G MAC,最后是约束和时序。
4.1 写板卡顶层
Corundum的板卡顶层,本质上就是一个“把所有资源串联起来的壳”。我按VV4原理图重写顶层模块时,端口列表大概是这样的:
module vv4_corundum ( // PCIe input wire pcie_refclk_p, input wire pcie_refclk_n, input wire pcie_perst_n, input wire [7:0] pcie_rx_p, input wire [7:0] pcie_rx_n, output wire [7:0] pcie_tx_p, output wire [7:0] pcie_tx_n, // QSFP28 0 input wire [3:0] qsfp0_rx_p, input wire [3:0] qsfp0_rx_n, output wire [3:0] qsfp0_tx_p, output wire [3:0] qsfp0_tx_n, input wire qsfp0_modprst_l, output wire qsfp0_reset_l, output wire qsfp0_lpmode, // QSFP28 1 // ... // 时钟和复位 input wire clk_sma, // 调试 input wire uart_rx, output wire uart_tx );顶层里面做什么?首先是把PCIe硬核IP例化出来,把Avalon-ST转换成Corundum需要的接口;然后例化收发器,把高速串行信号分别接到两个QSFP28 cage;再把时钟芯片产生的各频点时钟连接到MAC和DMA逻辑;最后把GPIO连接到光模块管理信号。
这一层看起来没什么技术含量,但它决定了整个工程能否编译通过。我犯过一个低级错误:某个端口方向写反了,结果Quartus编译时报告了一大堆连接错误,排错花了半小时。后面学乖了,写顶层之前先把VV4的原理图放大到能看清每个引脚方向,甚至打印出来放桌上对照。
4.2 时钟芯片的配置
时钟是Arria 10板卡上最容易踩的坑。VV4板上那颗SI5338,我一开始以为默认配置就能用。结果无论如何,PCIe硬核始终训练不起来,用Signal Tap抓参考时钟,发现频率完全不对。这才意识到时钟芯片必须由FPGA在上电后主动通过I2C配置。
解决办法是在顶层里放一个I2C主机模块,上电后立刻执行一组配置序列,把SI5338输出频率切成需要的值,例如给收发器的156.25MHz和给PCIe硬核的核心时钟。这个序列并不是随便写的,要仔细看SI5338的数据手册和VV4的原理图,确认每个输出端口对应的频率、电压和使能位。
还有一个容易被忽略的细节:时钟芯片配置完成后,后续模块的复位释放必须滞后足够时间。如果复位的释放时序在时钟稳定之前,哪怕只早了几十微秒,硬核同样会初始化失败。
4.3 PCIe硬核与DMA对接
Arria 10的PCIe硬核和Xilinx的XDMA完全是两回事。Corundum在Arria 10上的做法是包了一层适配模块,把PCIe硬核的Avalon-ST接口转换成内部的AXI接口,然后再接DMA引擎。
我在前几次编译时,PCIe硬核的配置一直不对。重点是核对几项参数:
- 链路速率:Gen3;
- 链路宽度:x8;
- 参考时钟频率:100MHz;
- 接口类型:Avalon-ST还是Avalon-MM,要和Corundum顶层匹配;
- BAR配置:至少需要两个BAR,一个给控制寄存器,一个给DMA描述符和状态映射。
这里面最麻烦的是Avalon-ST的控制信号含义和AXI不太一样,比如ready、valid的时序。如果只对着官方参考板卡的PCIe模块抄,很容易在时钟域转换上出问题。调试时我用了一个最简单的办法:先把PCIe硬核单独拉出来,通过JTAG访问BAR空间的寄存器,能读能写后再接到Corundum核心逻辑上。
4.4 QSFP28收发器与100G MAC
100G以太网这部分的移植逻辑比较直接:Corundum自带100G MAC和PCS,但它不包含高速收发器。收发器需要由板卡提供,然后通过类似MII/XLGMII的接口与MAC连接。
在Arria 10上,这意味着要例化Native PHY IP或者Ethernet IP,把4个高速通道配置成25.78125Gbps,并打开正确的收发器PLL和时钟恢复。QSFP28 cage上的4对差分信号直接接收发器,但参考时钟和回环测试模式要额外处理。
我第一次上电之后,光模块的RX完全没有信号。后来用Signal Tap看收发器状态寄存器,发现RX不是没有信号,而是CDR没锁定。原因是我把收发器参考时钟引到了错误的时钟引脚。这种问题特别容易出,因为板上时钟分布很密集,引脚名字看起来都差不多,必须要对照原理图逐个引脚核实。
MAC和PCS的接口还有一个容易忽略的点:Corundum的MAC内部有独立的TX/RX时钟域,两侧异步FIFO是必需的。如果在顶层里直接跨时钟域连线,时序报告会显示晚期违规,而且运行时会出现随机丢包。
4.5 约束与关键时序
QSF文件是Altera系工程的灵魂。引脚分配、I/O标准、电流强度、差分对的命名,全都在这里控制。Arria 10的IO标准要特别注意,不能只看原理图上的网络名,还要看供电电压对应的IO标准。比如PCIe差分信号是PCIe标准,QSFP高速线是LVDS或PCML类。
除了引脚分配,还要定义时钟约束。我在SDC文件里需要定义:
- PCIe参考时钟100MHz约束;
- 收发器参考时钟156.25MHz约束;
- 所有异步复位的伪路径设置;
- 跨时钟域路径的时序例外,防止Quartus在客观上不需要收敛的路径上浪费努力。
时序收敛这件事,在Arria 10上比在Xilinx上更敏感。我第一次编译时,WNS是负数,大概有几十条违规路径,分布在PCIe用户时钟域到MAC时钟域的跨域逻辑上。后来加了几级同步器,又调整了约束中的分组,才把时序压到正数。这个阶段不要着急,编译一次要半个多小时,每次只改一点,才能积累有效的操作经验。
5. 板级调试与问题排查实录
这部分是这篇博文里我最想写的,因为踩坑的教训比成功经验值钱。我把第一阶段遇到的主要问题列出来,按现象、定位、解决三步走记下来。
5.1 PCIe枚举不到设备
现象:板卡上电,FPGA加载完成后,主机系统里lspci找不到任何新设备。
排查顺序:先用万用表确认金手指上PERST#信号是不是正常拉高;再确认REFCLK是否进入FPGA的专用时钟引脚;然后确认FPGA里PCIe硬核有没有正确复位。这三点如果都正常,再看是不是PCIe IP的配置和实际链路宽度不一致。
我遇到的问题是PERST#极性处理错了。主板在系统启动时会把PERST#拉低再拉高,但这个信号在板级上又被反了一次,顶层里又一反,最后硬核收到的复位一直是有效电平。解决方法是直接在顶层把极性对齐,不要依赖IP核内部的默认设置。
5.2 QSFP28读不到光模块信息
现象:系统能识别网卡,但使用ethtool或i2c工具读光模块的DDM信息时,返回全零或读不到。
原因基本是管理信号的问题。光模块的ModPrsL、ResetL、LPMode引脚逻辑没有配对。比如光模块的I2C地址需要用ModSelL选择,而很多QSFP28模块的ModSelL是低有效,如果顶层里接反了,I2C总线上的地址永远对不上。
排查时我直接用I2C总线扫描工具,把模块所有地址都扫一遍,看看有没有ACK。如果完全没有,说明管理信号或者I2C上拉电阻出了问题。这里补充一个经验:上电后一定先给ResetL一个完整的低脉冲,再释放,不然部分光模块会一直处于复位状态。
5.3 链路建立了但没有流量
现象:网络接口UP了,但ping不通,或者干脆没有任何收发包计数。
先看MAC层状态。用Signal Tap抓MAC的PCS状态信号,确认RX的CDR锁定了没有,PCS是否进入正常状态。再看是否开了流控或自动协商,100G通常不依赖自动协商,某些调试模式反而会卡住链路。
我遇到的情况是TX方向正常,RX方向数据到了PCS之后,校验和错。查下来是收发器的RX极性接反了。Arria 10的收发器可以动态配置极性翻转,但默认情况下必须保证物理连接正确。如果原理图上RX正负和FPGA引脚定义是反的,就需要在IP核里打开极性翻转选项,而不是去改PCB。
5.4 DMA数据不通
问题现象:驱动加载成功,接口也UP,但是用工具做收发测试时,DMA描述符状态一直pending,没有完成中断。
这个问题的排查相对复杂,我还在进行中。目前定位到的方向是:BAR地址映射、描述符基地址寄存器与驱动默认值的匹配关系、以及DMA引擎的时钟域与PCIe用户时钟域之间的同步问题。
这里要特别提一个排查思路:先不要碰复杂的数据收发路径,先把控制寄存器通路打通。比如通过devmem直接读写BAR空间里的寄存器,看能否正确读到FPGA的版本号和状态。如果控制层面通了,数据面还是不通,那问题就一定出在DMA引擎本身或驱动对应的队列初始化上。
6. 当前进展与后续安排
到目前为止,第一阶段里我能确认跑通的是:Quartus工程编译通过,PCIe链路能被主机识别,Linux驱动能加载出网卡接口,100G光模块能读到DDM信息,接口链路能够建立并保持在UP状态。数据面DMA收发还在继续调,这条线比较绕,涉及驱动、DMA描述符和时钟域三方面,预计会在第二篇里详细展开。
这里说一个实际体会:移植Corundum到VV4这么大的工作量,最怕的不是逻辑复杂,而是板级信息对不上。原理图、引脚约束、时钟芯片默认配置、光模块的I2C地址,任何一个和顶层里的假设不一致,都会表现为极其诡异的“软故障”——编译没错,上电就挂。
我的做法是打印一张板级信息清单,把VV4原理图上所有相关信号整理成表格,再对照Corundum官方参考工程的顶层信号整理成第二张表,然后一张一张做映射。这个工作很枯燥,但绝对正确。后面做DMA调试时,如果出现难以定位的问题,我大概率还是会回到这两张表上找答案。
下一篇我会重点写Corundum DMA引擎在Arria 10上的具体对接、Linux驱动的加载细节,以及我在数据面通路上遇到的问题。如果手里有类似板卡也想这么干的朋友,欢迎在评论里交换一下信息,排错这种事,一个人闷头搞太慢了。