
记得去年接了个高速数据采集的项目板子上一颗ADC跑到2GSPS数据用32对LVDS拼出来满负载率算下来接近5Gbps。甲方提的需求就一句数据通过USB3.0传到电脑。我当时试过两条路找带USB3.0的ARM主控来跑上行带宽实测撑死400MB/s再加上系统调度、协议栈和DMA的中断开销实际能稳定跑的数据率远不满意又想用PCIe转USB3.0的桥接芯片结果发现桥接方案既要预留PCIe端点资源整个链路的延迟和灵活度也都不行。最后定下来的是纯FPGA方案用Xilinx UltraScale系列FPGA把GT/GTY高速收发器直接配置成USB3.0物理层配合USB3.0设备控制器IP和自研DMA引擎做成了一个干净利落的USB3.0 Device设备。这篇分享主要面向做高速数据采集、仪器仪表、图像采集这类需要USB3.0上行链路的同行我把原理图设计的几个关键决策、IP集成与代码结构、以及调试期三个最折磨人的坑完整讲一遍不少结论可以直接抄作业。1. 项目缘起与选型逻辑为什么USB3.0设备端要用UltraScale来实现1.1 真实需求高速数据需要一个大口径的上行通道这个项目的核心业务是数据采集。板上ADC采样率2GSPS位宽12bit即使做了实时降采样主数据流也有接近1GB/s的速率。甲方要的是连续数据流不能掉点不能压缩任何处理器介入做格式转换都会成为瓶颈。这种情况下最直接的办法就是让数据从FPGA内部直接进入USB链路中间不做任何CPU参与。传统MCU带USB3.0的方案在这个场景下很快就被否掉了ARM自带的USB3.0控制器虽然理论能到5Gbps但实测读方向稳定带宽普遍在350MB/s到400MB/s之间写方向更差加上驱动中断处理想连续跑满根本不现实。再者高速采集项目通常需要对数据进行实时滤波、抽取、格式封装这一层算法如果在FPGA里做可以零成本流水线化如果交给处理器做带宽又掉一截。所以纯FPGA的方案几乎是唯一选择。1.2 UltraScale系列的优势点GT收发器与逻辑资源选型阶段我对比过Zynq-7000、Kintex-7和UltraScale三个方向最终选了UltraScale。核心原因有两个。第一高速收发器资源。UltraScale的GTHKintex级别和GTYVirtex级别收发器单通道线速率覆盖5Gbps非常轻松。配置为USB3.0 Gen1物理层时线速率5.0Gbps编码方案是8b/10b有效数据带宽4Gbps也就是理论最大500MB/s。收发器内置的PLL、CDR、接收检测等硬件模块刚好是USB3.0物理层需要的功能组合不需要额外的外部芯片。第二逻辑资源密度。USB3.0的链路层和协议层不管用官方IP还是第三方IP本身就要占用不少LUT和BRAM再加上自研DMA引擎和用户数据通路一块中等规模的Kintex UltraScale比如KU025就够用了不用上Virtex级别。平台高速收发器USB3.0路径逻辑资源参考LUT开发难度Zynq-7000GTX 6.6Gbps收发器软核IP30K~50K中PS/PL协同复杂Kintex-7GTX 12.5Gbps收发器软核IP30K~50K中芯片较老Kintex UltraScaleGTH 16.3Gbps收发器软核IP20K~40K低工具链成熟Virtex UltraScaleGTY 32.75Gbps收发器软核IP20K~40K低成本高有人会问为什么不用Zynq UltraScale MPSoC那个芯片自带的USB3.0控制器处理起来不是更省事吗省事是真的省事但有两个问题一是MPSoC的USB3.0控制器受限于PS侧驱动和协议栈仍然绕不开CPU参与二来我们的数据是边到边的持续流最好直接以AXI-Stream形式在FPGA逻辑里传递MPSoC的USB控制器需要经过PS侧的DMA和驱动比起纯逻辑控制延迟和确定性都差一截。另外成本上MPSoC比同等逻辑规模的纯FPGA贵一个档次所以这个项目最终敲定了Kintex UltraScale。1.3 最终方案的三层结构整个系统分三层最底层是GT收发器负责把USB3.0的串行bit流解析成并行数据中间是USB3.0设备控制器IP完成链路训练、协议解析、端点管理最上层是自研DMA引擎和用户数据通路负责把端点收到的数据搬到用户逻辑或者反过来把用户数据喂给端点。后面几章就围绕这三层展开。2. 原理图设计的核心决策物理层通道怎么搭2.1 先理清一个大方向外挂PHY芯片还是直接复用GT收发器USB3.0设备的物理层有两条路。方案A是外挂一颗USB3.0 PHY芯片比如TI的TUSB1310或赛普拉斯CYUSB3014里的PHY部分FPGA只写逻辑层。PHY芯片自带完整的LFPS检测、RX终端匹配、时钟恢复等物理层功能和USB3.0规范完全贴合开发风险最低但一颗PHY芯片的价格通常在三五十元以上封装不小给PCB布局带来压力而且多了一颗芯片就多了调试和故障点。方案B是直接把FPGA的GT收发器当作USB3.0的物理层来用。GT本来就是一个高速串行收发器5Gbps线速率对它来说不算事差分输入输出、交流耦合、8b/10b编解码、RX终端检测这些硬件模块都有。但GT不是天生为USB3.0设计的它不认USB3.0的LFPS低频周期信号握手时序所以必须在外围逻辑里补一层LFPS检测和发生控制。好在Xilinx的USB3 IPVivado里叫usb3支持UltraScale系列把这部分封装好了它对GT做了一层适配逻辑把LFPS检测、发送、超时处理都做了用户看到的接口就是标准的USB3.0链路层接口。我当时选的是方案B。算一笔账外挂PHY芯片物料成本高、布线面积大GT方案多花的是IP集成和调试时间而这两项我预先有心理准备。省掉的PHY芯片钱直接换一台更好的调试工具值。2.2 参考时钟、AC耦合、电源与LFPS通道的关键点原理图层面有四个细节决定成败。第一参考时钟。GT收发器需要一个干净的100MHz差分参考时钟用于PLL锁定和串并转换。USB3.0对参考时钟的频率精度要求是±300ppm以内但抖动要求很严格。我在项目里一开始用了普通的有源晶振加FPGA普通IO转发给GT结果链路训练时有时无后面调试章会详细讲。正确的做法是用板上高精度100MHz差分晶振或者用低抖动时钟芯片的差分输出直接接到GT的专用参考时钟引脚MGTREFCLK上不要用普通逻辑时钟绕路。第二AC耦合电容。GT的TXP/TXN和RXP/RXN虽然是CML信号但USB3.0标准要求链路两端做交流耦合耦合电容一般选0.1uF到0.33uF。我用的0.22uF 0402封装放在连接器一侧。电容本身要用高频性能好的X7R如果有空间用C0G更好不要用Y5V那种电容在高频下容值掉得厉害直接影响信号质量和低频LFPS传输。第三电源。GT收发器的模拟电源和端接电源UltraScale里对应MGTRAVCC和MGTRAVTT必须干净。MGTRAVCC要独立的低噪声LDO供电MGTRAVTT可以稍微松一点但也要和数字电源分开。板上如果同时有DDR、以太网这些大动态功耗的电路MGT电源一定要做物理隔离中间加磁珠或者用独立的电源芯片更好。我见过太多板子因为MGT电源没隔离GT眼图一片糊的情况。第四LFPS通道。LFPS信号是USB3.0物理层用来做低速握手用的频率大约在10MHz到50MHz之间。GT收发器本身不直接处理LFPSXilinx的USB3 IP内部会在GT的接收数据路径上做特殊处理对低频信号进行检测。这部分是IP核的事原理图上不需要额外引脚但我强烈建议你在板上留一组或者两组普通IO把IP核里的LFPS检测信号、TxDetectBusy这些内部状态引出来接个小LED或者测试点调试链表路时会救命的。2.3 USB2.0通路为什么少不了一个ULPI PHY很多第一次做USB3.0设备的人容易忽略一个问题USB3.0规范要求设备端必须保留一套USB2.0物理层用于初始枚举、兼容识别和一些链路管理功能。也就是说即使你的USB3.0链路做得再好USB2.0通道不工作主机也认不出你的设备。FPGA的普通IO不是USB2.0的物理层不能直接拿来做D/D-需要一颗外部ULPI接口的USB2.0 PHY芯片。我用的是Microchip的USB3320C工作在HS模式接口是标准的ULPI8位双向数据总线加一根60MHz参考时钟控制信号包括方向、片选、地址或专用控制线。USB3320和FPGA之间是纯数字接口连接非常简单常见连法如下// ULPI接口连接示意 assign ulpi_data_oe ulpi_dir ? 1b0 : ulpi_nxt; // 数据方向控制 always (posedge ulpi_clk) begin if (!ulpi_dir) begin // 写PHY命令/状态字节地址/数据组合写入 end else begin // 读PHY采集数据总线上的状态/数据 end end原理图的重点在于USB3320的DP/DM引脚直接连到USB3.0连接器的USB2.0引脚D/D-上48MHz晶振接在PHY旁边独立晶振比FPGA提供更稳VBUS检测脚建议连到FPGA的普通IO作为设备插入和供电状态的判断依据。2.4 容易被忽略的板级细节USB3.0连接器我用的标准Type-B设备端通常配B口SuperSpeed信号脚是两对差分线走线一定要控制好。三个板级细节最容易出问题差分阻抗USB3.0的SuperSpeed差分对建议控制在85Ω差分阻抗很多PCB叠层默认就按这个值算实际在85~95Ω范围内影响不大但线宽线距要和其他差分对统一。千万别让差分对跨过分割的电源平面否则参考平面断裂阻抗突变信号反射会很严重。长度匹配两对差分对内部P/N要匹配在5mil以内两对之间的长度差异最好控制在±5mm以内保证链路的时序余量。ESD保护连接器端必须加USB3.0专用ESD保护器件比如TPD4E05U06这类超低结电容的型号。注意千万不能用普通USB2.0用的TVS管结电容太大会把5Gbps信号直接压扁。另外VBUS检测、ID脚、连接器外壳接地这三个看似不起眼的点都影响设备的插拔识别和热插拔安全别嫌麻烦全部引到FPGA IO上做处理。3. 代码实现从IP配置到DMA通路的完整链路3.1 用哪个USB3.0控制器IP选型与配置FPGA实现USB3.0设备端控制器IP是核心。可选方案有Xilinx官方带在Vivado里的USB3 IP仅支持Device模式也有PLDA、Northwest Logic等第三方IP。第三方IP功能更完整比如支持Host模式、OTG、更灵活的端点配置但授权费用不低。这个项目只要Device模式、两个BULK端点、一个控制端点官方IP足够。Vivado里的USB3 IP配置界面看起来选项不多但有几个关键点必须仔细端点配置要定义端点0用于控制传输再定义至少一个BULK IN端点设备到主机和一个BULK OUT端点主机到设备。每个端点的Max Packet Size在USB3.0下固定是1024字节这个参数对应IP内部FIFO的写入策略不要改小。FIFO深度官方IP的端点FIFO深度配置直接影响连续吞吐。默认值往往偏小大块传输时会频繁反压。我们项目里把每个端点的FIFO加深到能装下16个Max Packet也就是16KB左右。DMA模式官方IP带了一个基础DMA控制器但对高速连续流来说性能不够所以我们的做法是用IP提供的AXI接口外接自研DMA引擎。Vivado版本不同IP界面有差异如果你是第一次用建议照着IP例化向导的默认值生成一个最简工程先跑通再逐步加东西。别一开始就想把性能优化到位链路能不能起来是第一优先级。3.2 GT收发器初始化不是配置完就能用IPCORE生成后GT不是上电就能工作的需要按照特定时序完成初始化。GT的初始化主要包括PLL锁定、TX复位、RX复位、接收终端检测几个阶段。官方IP内部会生成这些初始化逻辑但用户逻辑必须在之后等待IP返回的link_up信号置有效才能开始端点收发和DMA调度。下面是我在顶层模块里写的链路初始化监控状态机简化版localparam IDLE 3d0, RESET_WAIT 3d1, LINK_WAIT 3d2, RUN 3d3, ERROR_CT 3d4; reg [2:0] state, next_state; reg [23:0] timer; always (posedge clk) begin if (usr_reset) begin state IDLE; timer 24d0; end else begin case (state) IDLE: begin usb3_reset 1b1; state RESET_WAIT; end RESET_WAIT: begin usb3_reset 1b0; timer 24d0; state LINK_WAIT; end LINK_WAIT: begin if (link_up) state RUN; else if (timer TIMEOUT_10MS) begin usb3_reset 1b1; state ERROR_CT; end else timer timer 1b1; end RUN: begin if (!link_up) state IDLE; // 链路掉线自动重训 end ERROR_CT: begin if (timer 24d2000) state IDLE; else timer timer 1b1; end endcase end end这段代码的核心作用上电后拉复位释放复位后给IP最多10ms等待link_up如果超时说明链路训练有问题就进错误处理重新再来。实际调试中这个10ms超时值要按硬件的实际训练时间来调整我第一次用5ms结果有时能起来有时起不来后来改成10ms并且加了复位去抖就好了。3.3 LTSSM链路状态机设备端要处理哪些状态转换USB3.0的物理层状态机叫LTSSMLink Training and Status State Machine主机和设备两端要协同完成训练。LTSSM有一长串状态但对设备端来说重点是下面几个Rx.Detect设备端在这里发送接收检测信号确认对端是否存在。Polling两侧交换训练序列TS1/TS2协商线速率和对齐参数。Configuration确认链路宽度USB3.0是单通道所以就是1-lane。U0正常工作状态数据在这里传输。U1/U2/U3低功耗状态主机可以随时把链路降功耗。Recovery链路上出现位错误时两端重新训练回到U0。这些状态转换官方IP自动实现但应用层必须能感知。你可能会问如果应用层不知道链路进了低功耗状态会发生什么最常见的情况是主机因为一段时间没有大数据传输自动把链路降到了U1/U2而我们的DMA引擎还在试图往端点里推数据结果数据全被反压软件收到一堆超时错误。解决思路是在应用层用寄存器跟踪link_up和低功耗状态位当链路不在U0时主动暂停DMA的传输调度等链路回到U0再恢复。3.4 DMA描述符与数据通路整个数据通路的性能核心在自研DMA引擎。描述符是主机和FPGA之间交换传输任务的载体我采用了MMIO映射的方式主机侧驱动通过BAR基址寄存器空间访问寄存器组同时通过AXI接口读写DMA描述符和数据缓冲区。描述符结构如下typedef struct { uint32_t next; // 指向下一个描述符的物理地址0表示链表结束 uint32_t buf_addr; // 数据缓冲区物理地址要求128字节对齐 uint32_t length; // 本次传输的字节数 uint32_t control; // bit0: 传输方向(0读/写), bit1: 完成中断使能, ... } usb_dma_desc;DMA引擎的工作逻辑分四步主机驱动通过MMIO写入描述符链表的首地址和端点编号。FPGA端DMA引擎通过AXI4读总线把描述符读进来解析buf_addr和length。数据搬运如果是IN端点设备到主机DMA从buf_addr读数据写入USB3 IP的端点FIFO如果是OUT端点主机到设备DMA从端点FIFO读数据写入buf_addr。搬运完成后DMA引擎更新描述符里的状态字并触发中断通知主机驱动处理下一个描述符。性能上最关键的配置是两个burst长度和描述符深度。burst长度我用的是256拍即每次AXI读操作连续发256个时钟周期描述符深度从默认16个扩展到128个。后面调试章会有实测对比这两个参数对吞吐量的影响是数量级的。3.5 端点0的标准请求处理USB3.0的枚举期间主机通过端点0发送标准请求GET_DESCRIPTOR、SET_ADDRESS、SET_CONFIGURATION、SET_INTERFACE这些。官方IP的端点0逻辑已经处理了大部分标准请求但设备描述符、配置描述符、字符串描述符这些内容是用户逻辑需要准备的。我用了IP提供的描述符RAM接口把设备描述符、配置描述符、BOS描述符USB3.0必须有里面声明了SuperSpeed能力和配置、端点描述符依次存放在IP的RAM地址区间里按USB规范组织好。注意两个细节一是设备描述符和BOS描述符里的bcdUSB版本要写0x0300二是配置描述符里的bMaxPacketSize0字段要填9表示64字节端点描述符的wMaxPacketSize字段要填0x0400即1024字节。如果这些值填错主机可能枚举不完整或者枚举成功后端点的实际吞吐异常。4. 调试实录三个最折磨人的坑4.1 坑一连接电脑完全没有反应问题锁定在GT参考时钟第一个版本的板子回来满怀期待地把USB3.0线一插电脑一点反应都没有。用USB2.0线插上去能正常枚举说明USB2.0通路没问题问题锁定在USB3.0的SuperSpeed链路上。这时候要按层排查。先确认GT复位完成了没有。把IP里的gt_powergood信号引到LED上上电后灯亮了说明收发器供电正常。再用IBERTXilinx的集成误码率测试工具跑一个纯GT通道测试把GT配置成回环模式结果发现误码率高得离谱基本没法同步。问题范围缩小到物理层。用高速示波器看TXP差分信号发现信号眼图很差幅度也不对。这时候才回想起原理图的问题当时的GT参考时钟是从一个普通有源晶振出来经过FPGA内部逻辑转接到GT的参考时钟输入引脚上的。GT作为高速收发器对参考时钟的抖动极其敏感普通晶振加逻辑转发的方案产生的时钟抖动太大导致GT内部的PLL和CDR无法锁定到正确的频率。后来改在板上加了一颗低抖动100MHz差分晶振直接连到GT的MGTREFCLK专用引脚再测试IBERT误码率直接降到10的负15次方以下。插上USB3.0线设备秒认。这个坑的教训很直接GT参考时钟一定要走专用时钟引脚一定要用低抖动的时钟源不要在GT时钟路径上放任何可编程逻辑。很多人以为GT对参考时钟的要求和普通逻辑时钟差不多其实差的不是频率精度而是长期抖动和相位噪声。普通逻辑时钟路径上的PLL和布线延迟会把这些指标搞得很糟糕导致GT内部CDR失锁。另外如果是多路GT同时跑参考时钟的fanout也要规划好一个MGTREFCLK引脚最多接几个GT是有讲究的真不够用需要外接时钟缓冲芯片。4.2 坑二能枚举但大包一传就掉线Max Packet Size与FIFO深度的组合坑第二个坑更隐蔽。设备能枚举lsusb能看到VID/PID读取设备描述符也没问题但一执行大数据块传输比如从上位机发一个1MB的BULK写传输刚开始几十KB就掉线dmesg报disconnect。奇怪的是小数据量的控制传输完全正常。用usbmon在上位机抓URB发现连BULK传输的包大小就是1024字节但传输计划里每个URB包含的包数非常多一旦某个包的响应超时链路就进入Recovery然后主机把设备断开。排查后发现是两点叠加一是USB3 IP的BULK端点FIFO深度配小了当IN端点FIFO满时IP不会自动缓冲更多数据导致端点向主机回NAK而主机侧驱动的超时逻辑没做好就断开了二是自研DMA缓冲区地址做了32字节对齐但USB3.0协议栈内部要求DMA缓冲区按端点Max Packet Size1024字节边界对齐效率最高否则IP内部会做额外的组装和拆分增加延迟。解决办法把IP端点的FIFO深度调大到16KBDMA缓冲区统一用大页内存并做1024字节对齐传输立即稳定下来。这个坑的本质是端到端缓冲深度不匹配任何一端的缓冲不够表现都不是速度慢而是断链。排查的时候如果不抓协议层的数据单看硬件波形根本看不出问题所以建议在调试环境里先装好usbmon或者直接接一台协议分析仪把错误现象和数据包对应起来效率高得多。4.3 坑三带宽卡在200MB/s上不去DMA调度成为瓶颈链路稳定了但最初版本的性能测试结果让人想砸键盘设备到主机IN方向读取带宽只有180MB/s主机到设备OUT方向只剩120MB/s。离USB3.0理论500MB/s差得太远。用usbmon观察URB发现上位机每次发起的URB长度只有96KB而且DMA引擎每完成一个描述符都会产生一次中断。中断频率高中断处理又在主机侧占用了大量CPU导致主机无法及时下发新的URB。usbmon的统计显示每两个URB之间的间隔高达几十微秒这个空档时间被白白浪费了。定位到两个因素一是上位机驱动里的URB请求长度太小改到512KB二是FPGA侧DMA描述符深度太小只有16个导致DMA引擎频繁耗尽描述符后停等主机下发新任务。把描述符深度扩展到128同时把AXI burst长度从64拍提高到256拍IN方向带宽从180MB/s涨到386MB/sOUT方向从120MB/s涨到342MB/s。这个386MB/s对USB3.0 Gen1来说已经接近单BULK端点的实际极限了因为USB3.0的协议开销、8b/10b编码开销都在那里能达到理论值的77%左右算是不错的成绩。配置项原始值优化后带宽变化IN方向URB请求长度96KB512KB40%DMA描述符深度1612870%AXI burst长度64拍256拍45%总带宽180MB/s386MB/s提升约2.1倍这个坑的启示是USB3.0链路吞吐是一个端到端的系统问题主机的URB策略、FPGA的DMA调度、AXI总线效率三个环节必须同时优化单改哪一环都没用。5. 实测结果与可以直接抄作业的经验清单5.1 性能实测数据最终的测试环境Kintex UltraScale KU025Vivado 2020.2PC是AMD Ryzen 7 B550主板驱动用的libusb异步传输模式。以下是持续传输1分钟的稳定数据不是瞬时峰值。方向稳定带宽占理论比例500MB/s备注IN设备到主机386MB/s77%单BULK端点零丢包OUT主机到设备342MB/s68%单BULK端点零丢包环回INOUT同时读320MB/s 写230MB/s-双向同时跑总带宽约450MB/s上面这个表是基于libusb异步传输模式跑出来的结果如果你用同步模式带宽会掉到300MB/s以下因为每次同步传输等待完成的时间开销很大。Windows侧的结果会稍微低一点主要是驱动栈的URB调度策略不一样但总体也能跑在360MB/s左右。测试时要注意把系统的USB选择性暂停关闭否则Windows会在空闲时把设备切到低功耗导致过一会儿掉线。5.2 项目复盘从头做这个项目我的5条建议GT参考时钟必须专用、低抖动不要走FPGA内部逻辑转发。这条已经验证过太多次直接把它当作最优先级的硬件设计约束。USB3.0的AC耦合电容选0.1uF到0.33uF位置靠连接器ESD器件一定要用USB3专用低电容型号别拿USB2.0的顺手顶上。原理图阶段就把LFPS检测、link_up、gt_powergood这些状态引到板上的LED或测试点能少踩一半调试坑。很多底层信号在布线阶段不加测试点等出了故障只能飞线痛苦程度完全不是一个量级。上位机驱动和FPGA固件要一起设计URB长度、描述符深度、burst长度这三个参数务必放在同一个系统里通盘考虑。协议栈设计时就把它们做成可配置的寄存器避免每次调整都改固件重综合。调试预算里一定要有协议分析仪或者至少是usbmonWireshark组合。USB3.0链路的调试纯靠插上看有没有反应根本不够数据包层面的现象和硬件时序往往是两码事。5.3 一点后续的想法这个项目只使用了官方IP支持的Device模式如果哪天产品需要把板卡切换到Host模式比如从FPGA去读一个U盘就得换第三方IP或者自己写LTSSM了工作量会明显上升。另外如果数据率需求超过USB3.0 Gen1可以考虑上USB3.2 Gen210GbpsUltraScale的GTY收发器跑到10Gbps毫无压力但USB3.2的协议复杂度又上一档USB2.0通路、LFPS、链路训练这些都要重新捋一遍。这个方向我还没实际量产暂时不展开。最后再分享一点点实操时的感受USB3.0设备端用FPGA实现难度不在协议理论而在把物理层、链路层、DMA调度这三层整合成一个能协同工作的系统。官方IP和GT收发器把大部分脏活累活都封装好了真正决定项目成败的往往是参考时钟、FIFO深度、对齐方式这些不起眼的参数。希望这篇帖子能帮你少走几个弯路也欢迎做过类似项目的同行一起交流。