ARTICLE DETAIL

资讯详情

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

FPGA自研USB3.0 Device控制器全流程:从PHY选型到Linux驱动调通

FPGA自研USB3.0 Device控制器全流程:从PHY选型到Linux驱动调通 这活儿我熟。去年给一套图像采集板卡做高速上行接口板子上正好是一颗Xilinx UltraScale FPGA方案选型的时候在“外挂USB3.0控制器芯片”和“FPGA自己实现USB3.0 DEVICE”之间纠结了很久。当时市面上现成的USB3.0桥接芯片比如FX3确实能快速出活但客户要求的是纯FPGA逻辑可控、低延迟、可定制端点的传输通道最终我们走了“FPGA内部实现USB3.0 Device Controller 外置PIPE接口PHY芯片 Linux侧Gadget/驱动配置”这条完整自研路线。整个过程从原理图到驱动调通踩了不少坑也整理出了一套可以复用的全流程方法。这篇就把从硬件设计到Linux软件配置的关键节点逐一拆开讲适合手里有UltraScale或同类FPGA平台、正在规划USB3.0 Device功能或者想评估“FPGA直接做USB3.0设备”是否靠谱的工程师参考。1. 为什么要在UltraScale上自己实现USB3.0 DEVICE三条技术路线的选型对比很多同学拿到“USB3.0 Device”的需求第一反应是找一颗USB控制器芯片。确实这条路最快但放到FPGA平台上它并不总是最优解。在动手画原理图之前一定要先把方案路线定清楚否则后面全部白做。1.1 三条路怎么选现成控制器、FPGAPHY、SoC集成我把实际项目里最常遇到的三种实现路径列个表方便对照实现路径典型方案开发周期灵活性推荐场景外置USB3.0控制器桥接Cypress CYUSB3014FX3、Fresco等短2~4周低受限于芯片固定端点/协议快速出数据通路不追求定制化FPGA外置USB3.0 PHYUltraScale FPGA PIPE3接口PHY芯片中长2~3个月高控制器在自己手里需要自定义端点、低延迟、与FPGA内部逻辑深度耦合Zynq UltraScale MPSoC PS侧USB3.0Zynq UltraScale自带DWC3控制器PHY短配置为主中PS侧固定但可灵活配置Gadget已经有ARM跑Linux不想碰FPGA高速逻辑我这次要讲的是第二条路FPGA 外置USB3.0 PHY。它看起来最折腾但收益也最明显。数据通路完全控制在FPGA内部逻辑里端点个数、端点类型、缓冲深度、DMA搬运方式都可以按项目需求定制。比如图像采集场景我可以在FPGA里给一个BULK IN端点配上16KB的专用FIFO再用AXI-DMA直接从DDR搬运这种定制能力是任何现成桥接芯片都给不了的。1.2 UltraScale平台上做USB3.0 DEVICE的底牌GTX、BRAM、DMA先看一眼Xilinx UltraScale这颗FPGA能提供什么。USB3.0 SuperSpeedGen1的线速率是5Gbps物理层用8b/10b编码有效数据带宽理论约500MB/s。FPGA侧的GTX/GTY高速收发器完全可以作为USB3.0 PHY使用很多参考设计也这么干——但我要先说清楚用GTX做USB3.0 PHY意味着你要自己做LFPS检测、电气空闲检测、Rx.Detect这些物理层状态机工作量非常大。除非是量级很大、对BOM成本极度敏感的成熟产品否则我建议老老实实外置一颗USB3.0 PHY芯片把GTX资源留给其他高速接口PCIe、SRIO。外置PHY通过PIPE3接口与FPGA交互PIPE3是并行的数据宽度8/16/32bit典型时钟125MHz/250MHz接到FPGA的HPHigh PerformanceIO bank上时序约束做好就行。这对FPGA内部逻辑的要求是实现USB3.0协议层LTSSM状态机、事务层、链路层、端点管理然后为上层提供一个类似DMA寄存器的接口。FPGA内部的BRAM可以作为端点FIFOUltraScale的UltraRAM容量更大适合做大缓冲。DMA引擎可以自己写也可以复用Xilinx的AXI-DMA IP把USB端点缓冲和DDR之间的搬运交给DMACPU几乎不用干预这才是高性能传输的正确姿势。选型结论如果你要做的产品是“FPGA是核心处理器USB3.0只是它的一个外设接口”那“FPGA 外置PIPE3 PHY 自研控制器IP”是认真做产品的路线如果只是为了demo或快速交付可以跳过本篇剩下的内容直接买FX3开发板。2. 原理图设计PHY芯片选型、电源树与关键信号处理定了FPGA自行实现Device Controller的路线之后原理图阶段有四个地方需要重点守住PHY芯片选型、参考时钟与复位、电源设计、以及PIPE接口信号完整性与Type-C相关逻辑。这四块任何一个出问题后面软件调试都会变成玄学。2.1 PHY怎么选不是所有USB3.0 PHY都带PIPE接口标题里提到了“usb3.0芯片”这个热词这里详细说一说。很多号称“USB3.0芯片”的器件其实是完整的控制器ControllerPHY比如CYUSB3014它的GPIF接口是给FPGA做数据搬运用的但USB协议栈已经被芯片接管了这跟我们要做的“FPGA自己实现控制器”是两码事。我们需要的是纯PHYPhysical Layer芯片它只负责把PIPE接口的数字信号转成USB3.0线上的高速差分信号。选型时重点看三件事接口协议是PIPE3还是ULPI。PIPE3对应USB3.0 SuperSpeedULPI对应USB2.0 High-Speed。如果产品只做USB2.0用ULPI PHY比如USB3300/USB3320会简单得多但5Gbps速度就别想了。做USB3.0设备老老实实找PIPE3 PHY。PHY是否带USB2.0 DP/DM通道。USB3.0设备为了兼容USB2.0主机SS PHY芯片往往内部还带一个完整的USB2.0 PHY对外引出DP/DM引脚。这个DP/DM可以单独接FPGA的普通IO通过ULPI或自定义逻辑实现USB2.0响应也可以直接不接有些产品的USB2.0兼容性不重要。我建议引脚资源够的话还是接上毕竟现在很多主机还是USB2.0口。PIPE接口的数据宽度与时钟频率。常见的是16bit 250MHzDDR模式或8bit 125MHzSDR模式。FPGA的IO速率能不能满足要提前查UltraScale HP bank的IO支持。UltraScale的HP IO在1.8V下支持到很高速率16bit PIPE跑250MHz问题不大但时序约束一定要做对。市面常见的PIPE3 PHY芯片有TI的TUSB1310A/TUSB1320、Diodes等具体型号要按器件供货、成本、封装、参考时钟要求综合选。不要只看最高速率USB3.0 Gen1的5Gbps对PHY来说是成熟技术重点是看它支持的参考时钟频率和电源需求。2.2 时钟、电源与复位最容易在原理图阶段埋雷的三个点先说话USB3.0 PHY的参考时钟抖动要求非常严格这是原理图阶段最容易翻车的地方。很多PHY要求参考时钟的峰峰值抖动在几十皮秒以内直接用板上的普通晶振往往不够。最稳妥的做法是给PHY独立配一颗低抖动有源晶振或时钟缓冲器频率按要求选择常见24MHz、25MHz、100MHz时钟走线远离开关电源和高速数字总线必要时加屏蔽地孔。电源树设计上典型USB3.0 PHY需要1.0V核心电压电流可能到几百mA看具体型号1.8V模拟电压给PLL和模拟前端3.3V数字IO电压给PIPE接口FPGA侧对应VCCINT 0.85V/0.9VFPGA核心VCCAUX 1.8V辅助电压PLL等VCCO 1.8VPIPE接口所在bank的IO电压PHY核心供电和FPGA核心供电最好分两路LDO/DCDC至少加磁珠隔离防止PHY高速翻转时的电源噪声串到FPGA。电源纹波在USB3.0这种5Gbps高速链路里直接影响眼图宁可用贵的低纹波LDO也不要在电源上省成本。复位设计更敏感。PHY的复位信号要在FPGA配置完成之后再释放否则PHY会进入错误状态。简单做法是用FPGA的IO在配置后拉高给PHY复位解除或者用专用复位芯片加延时。我踩过一次坑PHY在FPGA之前上电复位释放得太早结果PHY没等到稳定时钟就开始PLL锁定导致USB链路一直训练失败最后加了RC延时电路才解决。2.3 关键信号与PCB布局差分阻抗、走线长度以及C口OTG的实现PIPE接口虽然有16bit数据但它是并行总线不是高速串行对阻抗的宽容度比差分线稍高但走线仍要控制等长。USB3.0的差分对SSTX_P/N、SSRX_P/N则必须按90欧姆差分阻抗设计走线尽量短远离时钟和电源。DP/DM这对USB2.0差分线按90欧姆差分阻抗走。这里提一下热词“fpga布局和布线区别是什么”布局是决定信号质量的上限布线只是去实现它。USB3.0 PHY的位置一定要靠近连接器FPGA到PHY之间的PIPE数据线虽然可以长一点但等长要控制在±50mil以内时钟线单独走并加包地。关于“实现usb3.0 c口otg需要哪些芯片”如果产品用Type-C接口还要支持OTG/DRP那除了USB3.0 PHY之外还需要CC逻辑芯片检测C口插入方向、Rp/Rd电阻典型如TUSB321/TPS65982等VBUS开关控制芯片用于OTG时切换host/device供电方向VCONN电源控制用于给线缆里面的emark芯片供电如果只是Device设备比如U盘/采集卡插到主机上最简单的方案是用Type-C口但只做Device模式CC1/CC2通过5.1kΩ下拉电阻到地不检测方向直接强制定位为Device。大部分自供电的USB设备这么做就够了。但如果是做双角色设备DRP就要上CC逻辑芯片。这块在原理图阶段要提前定后面改板子非常痛苦。3. FPGA内部逻辑设计把PHY变成一门能读写的“外设”硬件定型之后FPGA内部逻辑就是项目的技术核心。你要在FPGA里实现的不只是“能枚举成功”而是“能稳定跑满带宽”。这一章节我拆成三块PIPE接口的时序对齐、USB3.0协议状态机LTSSM与事务层、以及DMA缓冲设计。3.1 PIPE接口与协议层FPGA和PHY之间的握手规则PIPE3接口的复杂度在于它不只是简单数据收发还包含链路训练需要的带外信号。FPGA通过PIPE接口向PHY发送控制命令PHY通过状态信号回馈链路当前状态。你需要实现的是USB3.0规范里的LTSSMLink Training and Status State Machine它是整个USB3.0协议最核心的状态机。LTSSM的状态包括Rx.Detect检测对端是否插入、Polling训练预编码、U0正常工作、U1/U2低功耗、U3挂起、Recovery从错误中恢复等。FPGA内部必须有一个清晰的状态机实现这些跳转并且在U0状态下正确收发数据包、完成8b/10b编解码这个工作PHY已经做了FPGA只需要在协议层处理加扰/解扰注意USB3.0的8b/10b之前还有加扰需要LSFR和CRC校验。这部分代码量很大如果从零手写一个熟练工程师要一到两个月。更现实的方案是购买第三方USB3.0 Device Controller IP核Synopsys DWC3是业界标准但通常授权给ASIC/SoCFPGA上比较少用开源RTL实现比如一些FPGA开源社区有USB3.0 Device控制器项目但要仔细评估成熟度参考Xilinx官方或第三方提供的USB3.0 PHY相关例程在此基础上改协议层我个人的经验是如果项目周期紧先评估第三方IP不要试图从LTSSM手写。IP的授权费可能不便宜但比起你花两三个月调试链路训练IP费用是值得的。如果是学习目的那可以手写LTSSM但要做好长期Debug的心理准备。3.2 端点、DMA与缓冲传输性能的三块基石USB3.0支持控制端点EP0、批量端点BULK、中断端点INTERRUPT和同步端点ISOCHRONOUS。做高速数据传输主力通常是BULK端点。在FPGA内部每个端点对应一块缓冲存储区可以是BRAM或UltraRAM带宽要求高就做成双端口、多缓冲Ping-pong。比如做一个发送方向DEVICE到HOST的BULK IN端点FPGA逻辑要支持主机发IN Token时从端点FIFO读取数据并按USB包格式返回主机发ACK后清空已发送缓冲区继续从DMA填入新数据如果FIFO为空返回NRDY或NAK等待DMA填充这看起来简单但性能瓶颈往往出在FIFO深度和DMA搬运效率上。USB3.0单BULK端点的理论带宽约400MB/s考虑到协议开销如果端点FIFO只有2KB主机连续发IN Token后FPGA很快就NAK了吞吐量惨不忍睹。建议至少配置16KB~64KB的端点FIFO再结合DMA queue让DMA连续从DDR搬运数据到FIFOFPGA只管和USB主机握手。DMA设计建议用AXI4接口数据通路是DDR ← AXI-DMA ← 端点FIFO ← USB协议层。Xilinx官方的AXI-DMA IP可以直接用也支持多描述符环Buffer Descriptor Table这样软件可以提前准备好多个BufferDMA自动轮询CPU零参与。3.3 时序约束与复位处理亚稳态问题要从小处堵FPGA内部逻辑跑USB3.0协议时钟一般由PHY提供的PIPE时钟比如250MHz驱动FDMA和协议逻辑分别用各自的时钟域异步FIFO做跨时钟域处理。这里要特别重视时序约束在Vivado里把PIPE接口输入的时钟set_input_delay把所有跨时钟域的异步FIFO加上false pathDRC要清零。很多人忽略这一点导致综合后时序余量只有负零点几纳秒板子上偶尔枚举失败、传输随机丢数这就是典型症状。复位信号亚稳态也是高频坑对应热词“fpga复位信号亚稳态”。PIPE接口的复位释放要和PHY时钟对齐建议使用同步复位电路把外部复位信号打两拍同步到PIPE时钟域再把同步后的复位用于所有状态机。我见过有人直接用异步复位导致LTSSM在不同状态间跳乱链路训练随机失败。复位设计在整个FPGA设计里看着不起眼但直接决定可靠性务必用同步复位或经过同步的异步复位。4. Linux驱动配置让设备在系统里“活”起来FPGA硬件和逻辑都搞定后就差Linux侧了。这里的“Linux侧”实际有两个角色一是设备端FPGA设备自身或它所连接的嵌入式处理器如何配置成USB设备功能二是主机端通常是x86 Linux或ARM Linux电脑如何正确识别、加载驱动并完成数据读写。很多教程只讲其中一边实际项目两边都要通。4.1 设备端Gadget配置把FPGA声明成一个“USB外设”如果你的设备端是一个带Linux处理器比如Zynq MPSoC的ARM或FPGA内部用MicroBlaze硬跑Linux那么设备自身的Linux内核里会有一个USB Device Controller驱动它对外注册为一个UDCUSB Device Controller然后通过Gadget框架配置成具体设备类型。在Linux 4.x/5.x内核上最常用的配置方式是通过configfs。比如要把设备配置成一个虚拟串口g_serial流程是modprobe libcomposite mount -t configfs none /sys/kernel/config cd /sys/kernel/config/usb_gadget mkdir g1 cd g1 echo 0x1d6b idVendor echo 0x0104 idProduct mkdir functions/acm.usb0 mkdir configs/c.1 ln -s functions/acm.usb0 configs/c.1/ echo udc-name UDC其中udc-name指的是设备树里注册的UDC设备名。如果是FPGA自研控制器Linux驱动里需要实现struct usb_gadget_driver和struct usb_ep_ops把FPGA的端点注册成usb_ep批量和控制端点分别实现对应的queue操作。这块代码不算复杂但要把FPGA寄存器映射、中断处理、DMA描述符处理都写好才能跑通g_serial或g_mass_storage。如果你用的是Zynq UltraScale MPSoCPS侧自带DWC3控制器那Linux内核已经支持得很完善只需要在设备树里正确描述PHY和控制器节点然后启动后加载对应的gadget function即可。这也是为什么很多产品最终会选择Zynq MPSoC而不是纯FPGA来做带USB3.0的智能设备——Linux侧代码省一大半。4.2 主机端设备树与驱动绑定写在设备树里的硬件描述如果设备端FPGA没有跑Linux而是作为一个纯USB外设接入主机那么主机端的Linux要能正确枚举并驱动它。标准USB设备通过描述符Device Descriptor、Configuration Descriptor、Interface Descriptor、Endpoint Descriptor被主机识别主机侧的内核根据idVendor、idProduct以及接口类去匹配驱动。对于自研的FPGA USB3.0设备最常用的做法是把它定义成一个Vendor Specific设备bDeviceClass0xFF然后在Linux主机上写一个用户态libusb程序或者内核驱动。libusb方案上手极快libusb_device_handle *dev libusb_open_device_with_vid_pid(NULL, 0x1d6b, 0x0104); libusb_claim_interface(dev, 0); libusb_bulk_transfer(dev, 0x81, buf, len, actual, 1000); // BULK IN libusb_bulk_transfer(dev, 0x02, buf, len, actual, 1000); // BULK OUT如果希望Linux识别为某种标准设备比如U盘、CDC-ECM网卡那需要在FPGA固件里实现对应的标准类描述符。我的建议是调试阶段用Vendor Specific设备libusb最简单后期产品定型后再考虑是否符合USB-IF的类规范。设备树侧如果FPGA是通过PCIe接入主机Linux那么不会有设备树由PCIe枚举机制自动加载驱动如果FPGA挂在嵌入式主机的某种总线比如LocalBus或AXI上那设备树里需要添加自定义平台设备节点并在驱动里通过platform_get_resource拿到寄存器地址和中断号。4.3 枚举调试与性能验证从dmesg到dd测试的完整链路每次板卡上电先跑一串命令确认USB链路状态lsusb -t lsusb -v # 查看设备描述符 dmesg | tail -50 # 查看内核枚举日志 cat /sys/kernel/debug/usb/devices如果是Vendor Specific设备lsusb -v可以看到bcdUSB比如0x0300表示USB3.0、端点地址、最大包长等信息。如果这些信息看起来正常说明枚举已经收尾接下来用libusb或内核驱动做BULK回环测试。回环测试是验证性能的最佳手段FPGA把BULK OUT接收到的数据原样通过BULK IN发回主机写一段数据再读回来比对同时统计吞吐。这时候如果发现吞吐只有几十MB/s通常不是USB链路问题而是FPGA端点FIFO太小频繁NAKDMA描述符没有连续填充数据“供不上”libusb的buffer size太小系统调用开销太高主机端平台节能导致USB控制器降频我在实测中把libusb的async批量传输buffer调到32个、每个8KB吞吐可以从300MB/s提升到接近450MB/s。这里也对应一个热词“usb3.0 inf”Windows下需要一个自定义INF文件来描述Vendor Specific设备并安装驱动程序Linux下不需要INF但内核驱动或libusb规则需要自己管理设备节点权限。5. 调试实战枚举失败、传输异常与性能不达标的坑所有项目到最后都会进入“怎么都不通”的阶段。USB3.0链路一旦出问题现象千奇百怪有的插上没反应有的枚举到一半设备消失有的能认出设备但一传输就掉线。我把自己调试中踩过的几个典型问题按排查链路写出来照着查比瞎试快得多。5.1 枚举失败先确认Link层还是协议层的问题故障现象设备插入Linux主机dmesg完全没有任何输出lsusb也看不到设备。排查第一件事是看PHY链路有没有起来。手边有示波器/逻辑分析仪的话先量PHY的PHYSTATUS或RX_ELECIDLE信号确认PHY检测到了对端主机的LFPS信号。如果Rx.Detect都不过说明差分线没接对、电源/时钟有问题或者是PHY复位没有释放。量到PHY状态正常但主机仍无响应下一步看LTSSM是否进入了Polling。这时候FPGA内部的调试工具——ILA集成逻辑分析仪就派上用场了。把LTSSM状态寄存器、主要的PIPE接口控制信号都采下来。我在一次项目里就发现LTSSM卡在Polling.LFPS原因是PHY与FPGA之间的PIPE时钟相位不一致导致数据采样出错。最后通过调整PIPE接口的input delay约束把时序余量从-0.2ns拉到0.3ns问题解决。如果链路层已经进了U0主机还是没有信息那就是协议层/枚举层问题。用usbmon或者抓包工具看主机是否发出SETUP请求、FPGA是否返回了Device Descriptor。很多自研控制器会在设备描述符的bMaxPacketSize0上栽跟头——USB3.0要求控制端点EP0最大包长固定为512字节bMaxPacketSize00x09表示512字节注意编码方式写错会导致主机直接放弃枚举。5.2 大数据吞吐时的性能瓶颈在FPGA与驱动两侧分别查故障现象小包传输正常但持续大流量传输时吞吐骤降或者传输一会儿就报错。先看NAK率。主机发IN Token时FPGA如果端点FIFO没数据就返回NAK这在调试阶段是正常的但如果NAK率超过20%吞吐必然上不去。在FPGA逻辑里加一个NAK计数器连续传输后读出来看看。NAK高的原因一般是DMA搬运跟不上解决办法是增大DMA burst长度、增加端点的双重/四重缓冲Multi-buffering、或者把FIFO从BRAM换到UltraRAM。UltraScale的UltraRAM容量大做64KB端点FIFO很合适但要留意UltraRAM的读延迟和多端口限制规划好地址映射。主机侧的urb缓冲大小同样关键。在libusb里如果用同步接口每次传输都要等完成再提交下一个吞吐最多到100MB/s量级改成Async接口一次提交多个in-flight URBs才能跑满USB3.0。Linux内核驱动里也可以通过增大sg或ring长度来提升效率。故障现象变为“传输一会儿后设备掉线”大概率是数据包CRC错误或协议层超时。这种情况优先检查电源纹波、参考时钟抖动、PIPE走线质量高速链路对信号完整性极敏感。也可能是FPGA内部DMA地址越界写到了非法内存导致数据通路崩溃。我用ILA抓过DMA的读地址总线发现描述符链在边界处跳变错误原因是BD table的地址未按64位对齐低地址位被忽略导致读取到错误的描述符。5.3 这类项目最重要的文档清单与验收清单最后分享一个文档经验这种FPGAUSB3.0Linux的项目团队协作最容易出问题的不是代码而是命名和接口定义不一致。我会在项目刚启动时维护两份清单寄存器清单FPGA侧所有控制寄存器、状态寄存器、DMA描述符字段务必在Excel或Markdown表里统一编号。Linux驱动开发同事拿到后直接对着写寄存器读写代码不用反复找FPGA工程师口头确认。端点规划表哪个端点号是BULK IN、哪个是BULK OUT、最大包长、期望的FIFO深度、传输优先级全部列清楚。USB协议里端点地址一旦硬件定死后期软件很难改所以原理图阶段就要定。验收清单则包括冷启动、热插拔各50次枚举成功率100%Windows/Linux双系统枚举正常如果目标只有Linux至少确认USB2.0兼容性满负载持续传输24小时无掉线、无CRC错误吞吐实测值达到设计指标例如BULK单向≥400MB/s这套验收标准我基本每个项目都会跑能拦住80%交付后的返修问题。最后再补一句个人体会FPGA上做USB3.0 Device最吃功夫的不是硬件也不是Linux而是Debug高速链路时的那种耐心。每次看到LTSSM卡在某个状态不要急着改代码先列出可能原因用仪器和逻辑分析仪尽可能获取现场证据再动手。把本文提到的几个方向都排查一遍大部分问题都会浮出水面。等第一次看到lsusb -t里出现自己的设备传输速度跑到接近400MB/s的时候前面熬的夜都值了。
返回列表