ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于PG2L100H的PCIe光纤加速卡实战:从LTSSM调试到万兆链路跑通

基于PG2L100H的PCIe光纤加速卡实战:从LTSSM调试到万兆链路跑通 拿到紫光同创PG2L100H这块板子之后我第一个想法不是点亮LED而是把它做成一张真正能塞进服务器、走PCIe又能出光的加速卡。原因很简单FPGA做高速光纤通信其实已经很常见但国产FPGA里能同时把PCIe硬核和高速收发器做齐还愿意把开发板做成标准卡形态的确实不多。这篇内容就完整记录我跑通“PCIe主机 - PG2L100H - SFP光模块 - 光纤 - 对端FPGA”这条链路的全过程包括PCIe的LTSSM状态机怎么调、枚举过程怎么排错、光纤链路怎么配以及中间踩过的各种坑。适合手里有同款板子、或者正准备选国产FPGA做光通信/PCIe加速卡的同学参考。1. 板卡和芯片摸底PG2L100H到底适合干什么1.1 从型号看芯片定位PG2L100H这个型号按我对紫光同创产品线的理解可以拆开看PG开头是Pango系列2代产品L代表逻辑型器件100说明逻辑规模在中等档位H后缀大概率对应带高速收发器的版本。这颗芯片最值钱的地方不在于逻辑资源有多少而在于它把PCIe硬核和SerDes高速收发器集成到了一起。实际搞过FPGA开发的人都知道逻辑资源再多要是没有高速串行收发器想接SFP光模块就是一句空话。以前国产FPGA做光通信要么外接PHY芯片要么用并行LVDS硬凑带宽上不去PCB布线还特别痛苦。PG2L100H这类器件出来之后情况完全不一样了——单芯片就能覆盖万兆光模块的收发需求而且自带PCIe硬核数据进了FPGA之后可以直接通过PCIe上主机不需要中间再挂一颗PCIe转接芯片。1.2 高速收发器和PCIe硬核资源盘点我手上这块板子的核心资源大致是这几个方面高速收发器SerDes通道芯片集成了多路高速收发器标称单通道速率覆盖1G到10G档位。这个速率范围很关键因为它正好覆盖10G光模块的线速率10.3125Gbps也覆盖常用的PCIe Gen2/Gen3速率。如果你用过Xilinx的GTX/GTH这套东西的定位是类似的核心由PMA物理介质接入层和PCS物理编码子层组成内部包含串化器/解串器、CDR时钟恢复、均衡器、编解码和弹性缓冲。PCIe硬核支持Endpoint和Root Complex两种工作模式常见配置x1/x2/x4/x8协议版本支持到Gen2/Gen3。做PCIe采集卡、加速卡时把它配置成Endpoint插到服务器主板上做特定背板交换场景时也可以配置成Root Complex去管理下游设备。DDR控制器和PCIe配合起来FPGA可以把光纤进来的数据先缓存到DDR里再通过DMA批量搬到主机内存避免零碎小包频繁打断CPU。普通逻辑资源中等规模做协议解析、DMA描述符管理、数据包处理足够用了。如果做复杂的网络卸载引擎资源会偏紧但做一版能用的原型验证完全没问题。1.3 开发板的硬件形态和外设观察开发板本身的设计更接近一张标准半高半长PCIe卡而不是那种面包板式的评估板。这是我很满意的一点。板上的关键外设大概包括SFP光模块笼子支持10G光模块或DAC高速线缆。PCIe金手指x4/x8物理接口能从PCIe插槽取电并和主机通信。DDR内存颗粒FPGA侧通过硬核控制器访问。JTAG下载口、UART调试口、LED按键等常规外设。时钟系统板上带有PCIe 100MHz参考时钟源和高速收发器参考时钟晶振。这里我特别提醒一句拿到板子先别急着上电调光纤先把板子插到电脑PCIe槽里什么都不接确认BIOS能通过PCIe枚举认出这个设备。这一步通过之后再谈光纤链路。否则后面出了问题很难分清是PCIe配置的问题还是光路的问题。2. 光纤链路的第一步光模块、参考时钟和信号完整性2.1 光模块选型和接口定义光模块选择上大家常用的就是SFP封装的10G模块。多模短距离场景选SR模块配合850nm多模光纤几百米内没问题单模长距离选LR模块配合1310nm单模光纤可以到10公里。实际开发调试我建议优先用多模SR模块加短跳线成本低坏了换起来也不心疼。SFP模块的接口逻辑其实不复杂核心是两组高速差分信号发送差分对从FPGA高速收发器的TX端连接到光模块的发送引脚。接收差分对从光模块接收引脚连接到FPGA高速收发器的RX端。除了高速数据线还有I2C管理接口用来读模块的EEPROM信息型号、光功率、温度等、LOS信号用来指示接收光功率是否达标、TX_DISABLE用来关闭发射。上板调试时I2C读取模块光功率这个功能非常有用。如果LOS一直拉高多半是光纤没插好或者对端没有发光链路没建立。2.2 参考时钟设计高速收发器和PCIe对时钟的要求完全不同很多第一次做高速光通信的人会栽在参考时钟上。PCIe接口和高速收发器用的参考时钟是两套东西不能混用。PCIe的参考时钟是100MHz差分时钟用来训练链路并作为收发器的位宽转换基准。它的抖动指标要求非常严格板上通常用专用PCIe时钟芯片产生。高速收发器做光纤通信时参考时钟频率取决于你想跑的线速率和编码方式。例如如果跑10G Base-R这种应用收发器内部PLL锁定在156.25MHz的参考时钟上经过内部倍频和分频之后产生10.3125Gbps的串行比特率。如果跑XAUI或者Aurora可能用156.25MHz或125MHz。这里有个容易忽略的细节PCIe的100MHz参考时钟和光纤用的收发器参考时钟必须在FPGA内部走不同的时钟引脚分别接到对应的IP核时钟输入端不能拿PCIe的100MHz去做光纤收发器的参考时钟也不能反过来。两者的频点和抖动要求完全不同硬凑的话会导致CDR锁定失败错误率居高不下。2.3 高速链路的信号完整性检查清单如果是用厂商提供的官方开发板PCB布线已经帮你做好了但上板前还是建议做一轮检查光模块的TX/RX差分对是否连接到FPGA高速收发器的专用引脚而不是普通IO。高速差分信号路径上是否有AC耦合电容。SFP模块的相关标准要求在链路中放置AC耦合电容很多开发板上已经集成。差分阻抗是否匹配SFP的高速信号线一般按100欧差分阻抗设计。空闲未使用的收发器通道是否做了合理处理不要悬空乱接避免引入噪声。有人会问这些检查做完之后怎么验证物理层通不通答案是先跑误码率测试IBERT而不是直接跑上层协议。用工具链里的高速收发器误码测试功能把收发器配置成回环模式TX发出伪随机序列RX接收并比对。如果误码率在1e-12以下说明物理层是健康的这时再往上跑协议就有底气了。3. PCIe配置实战从IP例化到LTSSM进入L03.1 PCIe IP的生成与例化在紫光同创的开发环境里新建工程、选好PG2L100H芯片之后第一件事就是生成PCIe IP。我用的配置大致是Endpoint模式x4通道Gen2速率使能MSI中断使能两个32位BAR空间一个用作寄存器控制一个用作数据搬运。如果不太清楚自己需要几个BAR先按这个配置来后面按需调整。IP生成之后需要把关键端口例化到顶层模块参考时钟输入引脚接板上100MHz差分时钟。复位信号接PCIe的PERST复位或者用逻辑控制一个上电复位。收发器差分对PCIe TX/RX lane分别接金手指对应的差分引脚。AXI接口PCIe IP内部通常把事务层转换成AXI或类似的内部总线接口用于和用户逻辑交互。这里有个新手容易忽略的问题PCIe IP的复位时序和时钟稳定性。PCIe要求在参考时钟稳定之后、再释放复位而且复位释放后要等待一段时间让内部PLL锁定。如果复位时序做乱了LTSSM状态机会一直卡在Detect状态环回枚举永远过不了。3.2 LTSSM状态机排查枚举失败的关键地图PCIe链路训练状态机LTSSM是理解PCIe排错最核心的知识点没有之一。每次枚举失败第一步就是查当前LTSSM状态停在哪个阶段。LTSSM的主要状态包括Detect检测对端是否有设备链路两端是否都准备好。Polling发送训练序列TS1/TS2尝试建立位同步和块同步。Configuration协商最终链路宽度和速率处理lane翻转。L0正常工作状态可以传输TLP报文。Recovery、L1、L2等链路恢复和低功耗状态。对调试来说最关键的是Polling和Configuration这两个阶段的子状态。简单理解Polling阶段在解决“我能不能和对面说话”Configuration阶段在解决“我们用几根线、什么速度说话”。如果卡在Polling大概率是物理层问题——参考时钟没起振、差分线接反、光模块没插好如果是通过光模块做PCIe链路这种情况也常见。如果卡在Configuration通常是通道数和速率协商不一致或者lane翻转到确认处理有问题。我习惯的做法是在FPGA内部把LTSSM状态寄存器通过硬件调试工具实时抓出来然后和主机端lspci的结果对照看。主机BIOS开始枚举时状态机会从Detect一路跑到L0整个过程在毫秒级。如果没进L0看它停在哪个状态排错范围一下就缩小了。3.3 枚举过程BIOS/操作系统如何发现你的FPGA卡PCIe枚举时主机会做以下几件事读取设备厂商ID和设备ID确认设备类型读取Class Code判断设备属于什么类型网络控制器、存储控制器、还是处理加速器给每个BAR空间分配物理地址分配中断资源。PG2L100H的PCIe硬核在IP配置时可以指定厂商ID和设备ID。如果用的是默认配置可能需要改成自己的值否则lspci显示出来是一个陌生ID。改完重新生成bit文件下载之后重新扫描PCIe总线就能看到设备了。Linux下常用命令lspci -tv看设备树lspci -vvv看设备能力集和当前链路状态。看到LinkSta里的LnkSta字段显示Speed 5GT/s、Width x4说明链路已经跑到Gen2 x4。如果Speed显示2.5GT/s说明协商到了Gen1通常是因为对端或板卡只支持Gen1或者训练时无法稳定跑Gen2。这时候可以在IP配置里强制只跑Gen1先让链路稳定再逐步提高。3.4 用DMA搬数据链路通了只是开始PCIe链路起来之后还只是拿到了一个能通信的管道。真正干活靠的是DMA。PG2L100H这套环境里官方提供了类似Xilinx XDMA的DMA控制器方案支持描述符环形队列、MSI中断、多通道DMA等。如果你之前用过Xilinx的XDMA这套设计思路几乎是平移过来的。核心流程是这样的主机的驱动在内存里申请一块缓冲区把物理地址和长度填入描述符FPGA侧DMA控制器从描述符队列里读取任务发起内存读或写完成后通过MSI中断通知主机。跑通之后你可以在Linux下对/dev设备做读写数据会从主机内存搬进FPGA、再从FPGA光纤口出去反过来也行。至此一个真正的PCIe光纤加速卡雏形就出来了。4. 光纤通信协议栈用现成IP还是自己写协议4.1 优先用官方高速串行协议IP光纤链路物理层调通之后接下来要决定数据怎么在链路上封装。如果你的应用不需要和别的设备互通只是为了两块FPGA之间高速传数据最省事的是用官方提供的传输层参考设计类似Xilinx Aurora那样的协议支持64B/66B编码、通道绑定、流控、热插拔等。用现成协议的优势很明显对齐、时钟补偿、差错处理这些底层细节都已经处理好了你只需要关心如何把用户数据包从A点搬到B点。我第一次做光通信时就是自己写对齐和时钟补偿结果在长时间大数据量测试时频繁出现通道失步排查了很久才发现是对端芯片时钟频率有微小偏差导致缓冲区溢出。后续的协议栈开发我没再重复造轮子的习惯能拿现成IP解决的问题坚决不自己写底层。4.2 自定义简化协议的帧格式设计如果因为特殊需求必须自己定义协议例如你要传输非标准数据格式或者要做极低延迟的数据通路建议遵循几个设计原则帧头要有固定特征字段用于接收端的帧同步识别。净荷长度要明确写在帧头里接收端才能知道一帧数据在什么位置结束。帧尾放CRC校验至少32位CRC防止数据出错被上层静默吞掉。空闲帧IDLE要周期性发送一方面维持链路同步状态另一方面做时钟补偿。我常用的简化帧长是64字节到1024字节可变帧头用4字节0xAAAAAAAA加2字节长度加2字节序号帧尾4字节CRC。接收端先用帧头完成同步然后按长度字段提取净荷做CRC校验再送入后续FIFO。这个方案在调试时非常直观逻辑分析仪一抓就知道哪一步出问题。4.3 跨时钟域的坑为什么不能直接把数据塞给收发器自定义协议最容易翻车的点是跨时钟域处理。高速收发器发送端和接收端各自有独立的用户时钟发送端时钟来自本地PLL接收端时钟是接收数据恢复出来的时钟。两块板卡之间不存在共同时钟源所以两边数据速率会有微小的频率偏差。如果不做处理长时间传输后接收FIFO必然溢出或下溢表现为链路偶尔丢包、错误突发。处理方案有两个一是用弹性缓冲配合IDLE字符做时钟补偿发送端每隔一定时间插入一个可删除的IDLE空字符接收端检测到缓冲区水位偏高时删除一个水位偏低时补一个二是在更上层做基于帧的重传机制但那是万不得已的办法。我自己的项目里是两者配合物理层弹性缓冲解决比特级偏差上层协议用序号和超时重传解决偶然丢帧。单纯依赖某一层长期跑都不太稳妥。5. 整链路联调Linux下识别设备到满带宽传输5.1 Ubuntu下让系统识别FPGA设备板卡插上主机后开机到Ubuntu系统先用lspci确认设备存在lspci -tv lspci -vvv -s 01:00.0如果系统没有识别到设备先确认PCIe物理链路是否稳定。最常见的两个原因一是金手指没插到位二是PCIe参考时钟没起振。这两点排查完之后再考虑FPGA内部逻辑问题。系统识别之后可以选择用UIO、VFIO或者官方驱动做后续开发。我调试阶段最喜欢用UIO框架内核加载uio_pci_generic模块把设备绑定到UIO驱动用户态直接mmap BAR空间读写寄存器非常方便。VFIO功能更完整但配置复杂适合正式驱动开发时再用。5.2 驱动层发起DMA读写用UIO框架时用户态程序可以这样操作打开设备文件mmap映射BAR空间读取寄存器。申请一块物理内存对齐的内存获取物理地址。把物理地址和长度写入DMA描述符寄存器。启动DMA等待中断或轮询完成标志。实际测试中我会在主机侧准备一块64KB的缓冲区填入递增序号下发到FPGA后FPGA接收完原样回传主机校验数据是否一致。这个简单回环测试能同时验证光纤收发链路和PCIe DMA链路排查问题非常好用。5.3 带宽实测和瓶颈分析链路全通之后我做了实际的带宽测试。PCIe Gen2 x4的理论带宽是2GB/s而万兆光纤的有效数据速率大约在1.25GB/s考虑编码开销后更低。实测下来整条链路能稳定跑到900MB/s到1.1GB/s接近万兆上限。瓶颈主要在光链路这一侧PCIe带宽是富余的。如果以后要上25G光模块就需要升级PCIe到Gen3 x8或者更宽否则PCIe反而会成为瓶颈。这点在做方案选型时要想清楚别光纤速率提上去了PCIe通道反而卡脖子。6. 调试工具链和我的排错经验6.1 工具链自带的在线逻辑分析仪国产FPGA工具链和Xilinx的Vivado相比生态和成熟度肯定有差距但基本调试工具还是齐全的。PDS里的在线逻辑分析仪类似ChipScope/ILA可以在FPGA内部预设探针信号触发条件设置命中之后把波形抓出来看。我的调试流程一般是先抓收发器侧的关键信号确认对齐完成、CRC错误计数不涨再抓用户接口侧信号确认数据有效信号和数据是否对齐最后抓DMA描述符状态确认PCIe侧任务是否正常完成。一层一层往上查绝不跳步。6.2 几个让我记忆深刻的排错过程第一个坑是PCIe枚举失败。第一次上板时系统完全识别不到设备。我先用硬件调试工具看LTSSM状态发现一直停在Detect状态。检查参考时钟发现板上PCIe时钟芯片没有输出原来是时钟芯片的使能引脚没有接对默认拉低导致时钟不工作。改完时钟配置后状态机正常跑到了L0。第二个坑是光纤链路偶尔丢包。物理层误码率测试是过的但上层协议一跑大数据就丢包。后来用逻辑分析仪抓接收侧数据发现是接收FIFO溢出——链路速率匹配没做好。我在发送端加入了以IDLE字符为基础的时钟补偿机制后问题消失。第三个坑是DMA描述符内存地址的不对齐问题。CPU分配的内存如果没做物理连续和地址对齐描述符里的地址跨页就会导致DMA读到错误数据。解决方法是在驱动里用内核API分配物理连续、按页对齐的内存。6.3 一些通用经验总结上电顺序很重要。PCIe设备要求先有参考时钟再释放PERST复位复位释放后不能立刻请求配置访问要等内部初始化完成。做光模块调试时先用模块自环和短光纤排除光纤本身的问题再连远距离设备。DDR控制器初始化时序要严格按照手册来初始化失败会导致DMA搬运数据时随机出错。国产FPGA工具链和Xilinx有差异遇到编译报错多看看官方文档很多是工程配置问题而不是代码问题。跨时钟域是高速系统最大的坑所有跨接的地方都要用异步FIFO或握手处理不要抱有侥幸心理。在我实际做完一版PCIe光纤卡之后最大的体会是这个项目的难点不在于某个单一模块而在于PCIe、光纤、DDR、DMA这些子系统之间的协同。每个子系统单独验证都正常但串在一起时任何一个环节的时序、复位和时钟处理不到位整条链路就会出各种奇怪问题。调试时一定要按照物理层、链路层、事务层一层一层往上验证不要直接跳到上层应用去找原因。先把LTSSM跑进L0再把误码率测到1e-12以下再跑DMA回环最后才谈性能和业务逻辑。这个顺序走下来所有问题都会变得可控。
返回列表