ARTICLE DETAIL

资讯详情

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

FPGA高速数据传输:基于CYUSB3014的USB 3.0 Slave FIFO设计实战

FPGA高速数据传输:基于CYUSB3014的USB 3.0 Slave FIFO设计实战 前阵子帮客户做一套高速数据采集系统FPGA 端要实时把 ADC 数据灌到 PC 上做处理。评估了几种 USB 3.0 方案之后最后定了FPGA CYUSB3014EZ-USB FX3的 Slave FIFO 模式。调通之后用 Streamer 和自写上位机分别测速持续传输稳定在338MB/s这个数字在 USB 3.0 bulk 传输里已经非常接近链路极限了。整个过程踩了不少坑固件侧、FPGA 侧、主机侧都有很多问题不是看 datasheet 就能立刻想明白的。这篇文章把完整的设计思路、固件与源码要点、实测数据和避坑记录整理出来给正在做或者准备做 FPGA 高速数据传输的朋友一个参考。如果你也是用 FPGA 做数据采集、图像传输、软件无线电这类需要大带宽上位的项目这篇内容应该能帮你省下至少两周的调板时间。文章会讲清楚为什么选 FX3、Slave FIFO 模式到底怎么工作、固件里哪些配置必须改、FPGA 侧状态机怎么写不会出错还有 338MB/s 这个数字是怎么来的、还能不能更高。1. 方案选型与整体设计思路1.1 为什么是 CYUSB3014先说选型。FPGA 和 PC 之间高速传输常见的路子就那几条PCIe、千兆/万兆以太网、USB 3.0。PCIe 带宽最好但 FPGA 要带 PCIe 硬核PC 端还要写驱动工程量大。以太网方案比较通用但协议栈和 FPGA 逻辑资源开销不小TCP 吞吐率调优也是无底洞。USB 3.0 最大的优势是上位机生态成熟——Windows 下有 CyUSB3.sys 驱动Linux 下有现成的内核驱动不需要自己碰驱动开发的深水区。USB 3.0 桥接芯片里市面上主要就是 Cypress现在是 Infineon的 FX3 和 FTDI 的 FT60x。FT60x 用起来简单FIFO 接口好接但它的文档和社区积累不如 FX3而且 FT60x 的 USB 协议层是把数据包封装好之后直接往主机发的灵活性差一些。FX3 内置了一颗 200MHz 的 ARM926EJ-S 核可以跑固件GPIF II 接口可编程既能做 Slave FIFO 又能做 Master端点、DMA 通道、Buffer 大小全都可以按需配置。说白了FT60x 是一个定死的桥FX3 是一个半定制的桥后者给了你足够的调整空间去优化吞吐率。另外 FX3 的参考资料非常多官方有 AN75779 这个经典的 Slave FIFO 示例工程网上也有大量中文资料。做硬件最怕的就是芯片资料少、出了问题没地方查FX3 在这方面的优势是实打实的。1.2 Slave FIFO 模式到底是怎么工作的FX3 的 GPIF II 是一个可编程状态机接口可以配置成 Master 模式由 FX3 主动产生读写时序去访问外部器件也可以配置成 Slave FIFO 模式由外部器件当主控FX3 的 FIFO 就是一个被动的存储池。我们项目里用的就是 Slave FIFO 同步模式。FPGA 作为主控向外提供 100MHz 的 PCLK 给 FX3同时控制片选、读写使能、数据总线FX3 通过 FLAGA/FLAGB 这些水位标志告诉 FPGA 现在能不能写/读。数据流是单向大带宽的FPGA 内部逻辑把数据写入缓存 FIFO然后写状态机检测 FX3 的 FLAGA若水位允许就往 32bit 数据总线上丢数据并拉低 SLWRPCLK 上升沿采样数据进入 FX3 的 DMA 缓冲固件再把缓冲搬运到 USB 端点最终通过 USB 3.0 送到主机。这里有个常见误区很多人以为 Slave FIFO 模式下 FX3 就是一块内存直接映射到 USB其实不是。FX3 内部有 DMA 通道和 USB 端点数据是肩并肩搬运的GPIF II 写入 FIFO 的速度和 USB 上传速度互相制约水往低处流慢的那一方决定实际速率。所以要让 FPGA 侧接口带宽远大于 USB 侧才能让 USB 变成瓶颈我们配置的 32bit 100MHz 理论带宽是 400MB/s比 USB 3.0 实际有效带宽高出一截这样 USB 链路才是天花板。2. 硬件板卡设计与接口要点2.1 最小系统与启动方式FX3 的最小系统说简单也简单说麻烦也麻烦。供电方面它需要 1.2V 内核电压、1.8V/2.5V/3.3V 的 I/O 电压自己内部还有 USB PHY 的模拟供电。我见过不少板子死在供电上——上电顺序不对导致枚举不稳定或者 VIO 电压和 FPGA bank 电压不匹配导致接口时序异常。建议 VIO 和 FPGA 侧 bank 电压保持一致比如都用 3.3V这样不用加电平转换芯片少一层延迟时序也更好控。启动方式要特别注意。FX3 可以从 USB 启动、I2C EEPROM 启动、或者 SPI Flash 启动。开发调试阶段用 USB 启动最方便也就是每次上电后通过 USB Control Center 手动下载固件。但是一旦进入联调阶段如果不想每次断电后都重新下载就要在板上放一颗 I2C EEPROM比如 24LC128 或 24LC256把固件烧进去。这个点坑过很多人EEPROM 里固件版本和当前工程不匹配板子上电后枚举出来一个老固件然后你对新固件的修改完全不生效还以为是代码问题。所以建议 Solid State 版的烧录和版本管理从一开始就规范化我在工程里会写一个版本号常量枚举后通过厂商字符串读出来上位机也能显示避免这种乌龙。2.2 关键信号与时序裕量Slave FIFO 同步模式下 FPGA 和 FX3 之间的关键信号不多列一下信号方向相对 FPGA功能PCLK输出给 FX3接口时钟通常 100MHzSLCS#输出片选低有效SLWR#输出写使能低有效SLRD#输出读使能低有效SLOE#输出输出使能读数据时使用PKTEND#输出短包结束信号A[1:0]输出线程选择地址DQ[31:0]双向数据总线FLAGA/FLAGB输入FIFO 状态标志这里的时序关系是高速接口的关键。PCLK 是 100MHz一个周期 10ns。FX3 手册里对 DQ 相对 PCLK 的建立时间和保持时间都有明确要求不同批次芯片也许有细微差异但大致在几个纳秒量级。FPGA 侧约束要跟上不然即使功能正确时序裕量不足也可能在温度变化或不同板卡间随机出错。我的建议是在 XDC/FDC 中显式约束 PCLK并用 set_input_delay、set_output_delay 约束数据总线与 PCLK 的相对关系。你不需要把每个信号的 delay 卡到几个皮秒但至少要让 Vivado/Quartus 知道 DQ 是同步于 PCLK 的否则工具默认按异步路径处理布局布线出来的结果很随机。2.3 PCB 布线经验PCB 布线是容易忽略但决定成败的一环。FPGA 与 FX3 之间的数据总线 DQ[31:0] 要尽量等长组内偏差控制在 ±5mil 以内PCLK 到 FX3 的走线要比数据线短一些因为 FPGA 输出的时钟沿和数据的相对关系受内部延迟影响PCB 上留出余量有利于时序收敛。USB 3.0 的 TX/RX 差分对要做 90Ω 差分阻抗控制走线远离其他高速信号连接器附近的参考平面不能挖断。我的经验是第一版 PCB 如果条件允许把 FLAG 信号和 DQ 信号都引出测试点方便示波器或逻辑分析仪测量。调试高速接口时能直接看到波形比什么都强。另外 FX3 的 USB 3.0 引脚和 USB 2.0 引脚需要同时接出来因为 FX3 启动时先靠 USB 2.0 枚举再协商到 SuperSpeed。只接了 USB 3.0 的线没接 USB 2.0板子会一直枚举失败。3. FX3 固件侧配置与实现3.1 从官方示例工程改起FX3 固件开发强烈建议直接从 SDK 里的 cyfxgpiftousb 示例工程开始而不是从零搭。这个工程本身就是 GPIF II 到 USB 的桥接 demoSlave FIFO 模式的所有基础代码都在你要改的主要是GPIF 状态机配置、DMA 通道参数、端点配置、线程与水位的映射关系。固件主流程大致是调用 CyU3PDeviceInit 初始化芯片调用 CyU3PKernelEntry 启动 RTOS 内核创建 DMA 通道P2U 和 U2P加载 GPIF 状态机配置Slave FIFO 同步模式配置 USB 端点并连接 USB启动 GPIF 引擎每一步都有对应的 APISDK 里也有完整注释关键是理解每个参数的含义尤其是 DMA 通道的 buffer 大小和 buffer count这两个参数直接决定吞吐率。3.2 端点、DMA 通道与线程配置这是固件里最重要的部分。我们的数据通路是 FPGA 把数据写进 FX3所以核心是 P2UPeripheral to USB通道外部传入的数据先落入 GPIF II 的 FIFODMA 引擎再把数据搬到 USB 端点。USB 侧我用了一个 Bulk IN 端点 0x81以及一个 Bulk OUT 端点 0x01用来接收上位机下发的控制命令。创建一个可靠的 DMA 通道核心参数有几个传输方向、事件类型、buffer 大小、buffer count。示例代码里常见的是 16KB buffer、8 个 buffer。我实际测试下来buffer count 太少会导致 DMA 搬完数据后 CPU 介入太频繁速率上不去。后来把 buffer count 加大到 16加上合理的水位配置吞吐率明显改善。FX3 内部是 ARM 核跑 RTOS 的DMA 中断太多会挤掉主循环的时间相当于拖慢吞吐。端点配置也不复杂但有一点要注意Bulk 端点的最大包大小要设成 1024 字节这是 USB 3.0 超速 bulk 的标准。如果固件里忘了改还是默认的 512 字节那带宽会直接砍半。下面是固件初始化中关键部分的一个简化示例/* DMA 通道创建P2U自动模式从 GPIF 到 USB */ CyU3PDmaChannelCreate(glChHandleSlFifoP2U, CY_U3P_DMA_TYPE_MANUAL, dmaConfig); /* USB 端点配置Bulk IN 0x81PacketSize 1024 */ CyU3PUsbSetEpConfig(0x81, CY_U3P_USB_EP_BULK, 1024); CyU3PUsbSetEpConfig(0x01, CY_U3P_USB_EP_BULK, 1024); /* 启动 GPIF 引擎加载 Slave FIFO 状态机表 */ CyU3PGpifLoad(CyFxGpifConfig); CyU3PGpifStart();到底用自动模式还是手动模式取决于是否需要 CPU 参与数据搬运。纯高速流式传输用自动模式即可CPU 不碰数据DMA 直接搬如果需要做协议解析、包格式转换才需要手动模式让 CPU 介入。3.3 水位配置与水印标志FLAG 信号是 FPGA 判断能不能写的依据它背后靠的是水位Watermark机制。FX3 的每个线程都有自己的水位配置当 FIFO 剩余空间大于某个阈值时FLAG 就有效表示可以写小于阈值时FLAG 无效表示不要再写了否则数据可能溢出。水印阈值设得太小FIFO 快满了才通知 FPGAFPGA 的写入状态机来不及停就会丢数据阈值设得太大明明还有很多空间但 FLAG 已经无效利用率上不去吞吐率受损。我一开始用默认阈值实测只有 200MB/s 左右后来把 P2U 方向的水印适当调低给 FIFO多留一点缓冲速率马上上来了。具体数值和你的 DMA buffer 配置有关需要实测微调但这里记住一个原则写方向的水印要保证 FIFO 里有足够的空间余量让 FPGA 从检测到 FLAG 无效到停止写入这段时间里数据不会溢出。3.4 固件侧的几个坑先讲 PKTEND。在 Slave FIFO 模式下如果传输的是连续大流量数据FPGA 侧需要周期性地拉一下 PKTEND#告诉 FX3这一段数据结束了发给主机吧。不拉 PKTEND 的后果是数据一直攒在 FX3 内部主机收不到表现就是上位机读到的数据卡住或者吞吐率忽高忽低。很多人在跑短包测试时没发现问题一旦跑持续大流量就掉链子多半是这个原因。再讲 EEPROM 启动的坑。上文提过固件版本管理这里再补充一点EEPROM 里的固件如果和当前硬件不匹配比如 GPIO 配置不同轻则功能异常重则设备枚举都过不了。而且 USB 启动和 EEPROM 启动的优先级是 USB 优先还是 EEPROM 优先和 boot 引脚配置有关设计时要明确你要的启动方式别让两种方式互相干扰。固件侧调优经验一句话总结先把默认示例跑通再一项一项改参数每次只改一个变量记录吞吐率变化不要一次改一堆参数然后不知道是谁起作用。4. FPGA 侧逻辑实现4.1 模块划分与数据通路FPGA 侧的逻辑我是按模块划分的避免把所有逻辑堆在顶层。主要分为时钟管理模块、数据源模块ADC 或 DDR 读出、写通道状态机、读通道状态机、以及跨时钟域缓冲 FIFO。数据通路的顶层逻辑是这样的数据源以较高速率把数据写入一个异步 FIFO这个 FIFO 隔离了源时钟域和 GPIF 时钟域写状态机轮询 FX3 的 FLAGA当 FIFO 非空且 FX3 允许写入时从 FIFO 读数据并驱动 DQ 总线和 SLWR读状态机相对简单主要处理上位机通过 OUT 端点下发的命令流用 SLOE 和 SLRD 从 FX3 读回数据。使用异步 FIFO 这个点非常重要。FPGA 内部逻辑可能跑在 200MHz 甚至更高而 GPIF 接口是 100MHz两个时钟域如果不做隔离数据采样很容易出问题。跨时钟域不是靠大概对齐就能解决的一定要用 FIFO 或至少双寄存器同步。4.2 写通道状态机写状态机是 FPGA 侧的核心。它要做的事情是检测 FLAGA 有效然后把 32bit 数据放到 DQ 总线上拉低 SLWR等待一个 PCLK 上升沿完成写入重复直到 FIFO 空或者 FLAGA 无效。有一个细节必须注意FLAGA 是异步信号进入 FPGA 内部要先做两级寄存器同步否则可能采到亚稳态状态机误判。我在代码里专门做了同步模块同步后还要考虑到同步延迟——从 FLAGA 有效到 FPGA 真正采样中间有几个时钟周期的延迟所以要保证 FX3 返回 FLAG 无效时水位的余量能覆盖这个延迟。写状态机的简化 Verilog 示例// 同步后的 FLAGflag_a_sync // 状态机核心跳转 always (posedge pclk or negedge rst_n) begin if (!rst_n) state IDLE; else begin case (state) IDLE: begin if (fifo_empty 1b0 flag_a_sync 1b1) state WRITE; end WRITE: begin if (flag_a_sync 1b0) state IDLE; else if (fifo_empty 1b1) state IDLE; end endcase end end // 数据与写使能输出 always (posedge pclk or negedge rst_n) begin if (!rst_n) begin slwr_n 1b1; dq 32h0; end else if (state WRITE) begin dq fifo_dout; // 从缓存 FIFO 读出数据 slwr_n 1b0; // 拉低写使能 end else begin slwr_n 1b1; end end这段逻辑看着简单实际调试时最折磨人的是时序收敛。DQ 总线在 PCLK 上升沿之前要稳定SLWR 也要保证和 DQ 满足 FX3 的建立保持时间。如果你的设计在仿真里一切正常上板就随机出错十有八九是时序约束没做好。4.3 读通道与回环验证读通道用来接收上位机的命令或者做回环验证。它的逻辑比写通道简单检测 FLAGB 是否表示有数据可读拉低 SLRD#读取 DQ 总线。需要注意的是 SLOE# 信号在 Slave FIFO 模式下读数据时分两种情况——如果 SLOE 有效DQ 总线由 FX3 驱动如果 SLOE 无效DQ 总线可能处于高阻。所以读通道要同时控制 SLOE 和 SLRD不能只拉一个。建议从最开始就做一个回环测试FPGA 从 FX3 读回数据然后原样写回。这样固件和 FPGA 的读写通道可以分开验证哪边有问题一目了然。调试完回环通路了再接入真实数据源。4.4 时序约束实战FPGA 侧时序约束是整个工程能否稳定跑满速率的隐藏决定因素。我以 Vivado 为例说一下做法create_clock -period 10.000 -name pclk [get_ports pclk] # 输入延迟FX3 的 FLAG 信号相对 PCLK set_input_delay -clock pclk -max 5.0 [get_ports {flag_a flag_b}] set_input_delay -clock pclk -min 1.0 [get_ports {flag_a flag_b}] # 输出延迟DQ、SLWR 等相对 PCLK set_output_delay -clock pclk -max 6.0 [get_ports {dq[*] slwr_n slrd_n}] set_output_delay -clock pclk -min 1.0 [get_ports {dq[*] slwr_n slrd_n}]这里的具体 delay 值仅供参考要根据 FX3 手册的时序参数计算。如果你的工程里没有类似约束工具会默认把 FLAG 当成异步输入布局布线时不会保证它满足时序上板出错概率很高。我见过太多人仿真没问题但上板随机错最后发现约束文件几乎是空的。5. 实测数据、性能分析与优化5.1 338MB/s 是怎么测出来的实测环节我用两种方法互相验证。第一种是 Cypress SDK 自带的 Streamer 上位机它支持 FX3 的 bulk 传输测速简单粗暴界面直接显示吞吐率。第二种是自己写了一个 C# 上位机基于 CyUSB.NET 库开一个 4MB 的接收缓冲循环调用异步读接口统计每秒收到的字节数。测试条件是FPGA 侧用计数器产生伪随机数据流DDR 读出的数据也可以反正不关心内容只关心速度。上位机持续读取 60 秒统计总字节数换算成平均速率。多次测试取平均值最终结果是 338MB/s。测试模式实测速率说明默认配置DMA buffer 8 个约 200MB/s水印不匹配DMA 中断频繁加大 buffer调整水印约 300MB/s明显改善最终调优主机端大缓冲338MB/s接近 USB 3.0 bulk 极限主机端缓冲 64KB约 260MB/s缓冲区太小丢速严重看到 200MB/s 到 338MB/s 的差距了吧这几个变量——DMA 通道 buffer count、水印阈值、主机端接收缓冲——任何一个不匹配都会让你误以为方案不行其实是参数没调到位。5.2 338MB/s 离 USB 3.0 的理论极限还有多远来算一笔账。USB 3.0 的物理层线速率是 5Gbps但因为采用了 8b/10b 编码实际有效数据速率是 4Gbps也就是 500MB/s。这是物理层的上限。但是 bulk 传输在协议层有开销每个包有包头、CRC、握手还有包与包之间的间隔。实际能达到的最大 payload 带宽通常只有 400MB/s 左右这还是在最理想的情况下。如果你用 USB 分析仪抓包会看到总线上的数据并不会 100% 时间都在传数据总有一些 idle 间隔。再回到接口侧FPGA 与 FX3 之间是 32bit 100MHz理论带宽 400MB/s。FPGA 侧供给的上限是 400MB/sUSB 侧实际能吸收的不到 400MB/s所以瓶颈在 USB 链路。338MB/s 大约是 400MB/s 理论最大 payload 的 84.5%这个数字已经很健康了说明 USB 协议开销和 FIFO 切换间隙已经压得比较低。所以如果有人问怎么才能测到 400MB/s 以上答案很简单换 USB 3.1/3.2 方案或者用 PCIe。FX3 跑 USB 3.0338MB/s 已经接近它的天花板。5.3 影响吞吐率的几个隐形因素先讲主机端。很多人把注意力全部放在 FPGA 和 FX3 上忽略了上位机代码。如果你用同步方式读 USB每次读 4KB读完再发起下一次读取中间的开销非常大吞吐率能掉到 200MB/s 以下。要跑满速率必须用异步读并且缓冲区要足够大至少 4MB。原理很简单USB 控制器需要足够的缓冲来容忍系统的调度延迟缓冲太小系统一卡USB 总线就空等了。固件线程优先级也有影响。FX3 的 RTOS 里USB 中断线程和 DMA 事件线程的优先级如果设置不当数据搬运链条上某一环会周期性阻塞。我试过把 DMA 事件线程优先级调低速率暴跌调回合适优先级后恢复正常。这类问题没有统一标准要结合自己的数据流来验证。还有一个容易被忽略的因素是数据对齐。Bulk 传输对包大小敏感如果 FPGA 侧送入 FX3 的数据总是凑不满 1024 字节的整数倍就会频繁产生短包短包每一次都需要 PKTEND 和额外的总线处理长期跑下来吞吐率损失不小。所以设计数据源时尽量保证单次写入的数据量是 1024 字节的整数倍。6. 常见问题排查与避坑速查6.1 问题速查表症状可能原因解决方案设备无法枚举USB 2.0 线没接、EEPROM 固件损坏、供电异常先测 USB 2.0 枚举再排查供电枚举成功但上位机打不开设备驱动冲突或驱动版本不对卸载旧驱动重装 CyUSB3.sys速率只有 100-200MB/s主机缓冲太小、DMA 配置不当调整主机缓冲到 4MB 以上加大 DMA buffer count跑几分钟后速率骤降上位机内存碎片或缓存回收不及时改用异步读反复使用预分配缓冲数据内容错位、出现乱码字节序不对或数据位宽映射错误检查 DQ 总线高低字节映射32bit 打包顺序FLAG 信号误触发导致丢数跨时钟域亚稳态未处理增加两级同步寄存器检查水位余量短包模式正常连续流卡死PKTEND 没周期性拉低在 FPGA 逻辑中按包周期拉低 PKTEND#这张表我每次做新项目都会打开看一遍。很多问题看起来是硬件问题实际是软件配置问题很多问题看起来是 FPGA 问题实际是上位机问题。调试时不要急着怀疑一个方向按数据流从源头到终点一级一级排查。6.2 一次卡在 200MB/s的排查实录这个案例挺典型。有一次我把代码从旧工程移植到新工程速率从 338MB/s 掉到 200MB/s而且怎么调 DMA 参数都没用。当时排查步骤是第一步先用 Streamer 测速确认掉速现象在官方工具上也存在排除上位机因素。第二步用示波器看 FPGA 和 FX3 之间的 FLAGA 和 SLWR发现 SLWR 的有效占空比不到 50%说明 FPGA 经常处于想写但 FX3 不接收的状态问题在 FX3 侧水位配置。第三步查固件里 DMA 通道配置发现新工程里我复制了一份旧代码但 DMA buffer count 被初始化成了 4和之前调优后的 16 不一致。改回 16 之后速率立刻恢复到 330MB/s 以上。这个案例的教训是参数不是多就好或者少就好而是要和数据流匹配。旧工程的配置在新工程里不一定适用每次粘贴代码都要认真核对每个参数。后来我在工程里加了一个配置头文件把 DMA buffer、水印、端点大小全部集中管理再也没出过这类问题。6.3 调试工具组合拳最后分享一套调试工具用法是我在联调时固定使用的组合。第一个是 USB 分析仪式的抓包工具。Windows 下可以用 Wireshark 加 USBPcap插上 USB 3.0 设备后就可以抓取总线上的 URBs虽然看不到每个包的物理层细节但足够分析吞吐率瓶颈和错误包。第二个是逻辑分析仪或示波器。耐心等触发条件抓 FLAGA、SLWR、DQ 之间的关系看看是 FLAG 一直无效还是有效但 FPGA 不写。第三个是 Cypress 的 USB Control Center方便查看端点信息、厂商描述符以及手动下载固件。第四个是 UsbTreeView 这类 USB 设备查看工具排查枚举失败问题。我的调试顺序一般是先用 UsbTreeView 确认枚举正常再用 Control Center 下载固件并确认版本然后用 Streamer 测速测速不达标就上示波器抓接口时序接口时序没问题就上 Wireshark 抓总线包。按这个顺序走完一般都能定位到问题所在。我个人在实际操作中的体会是FPGA 和 FX3 联调最需要的不是灵光一现而是系统性的排查顺序和耐心的参数微调。338MB/s 这个数字不是一次就调出来的中间经历了从 200 到 300 再到 338 的逐步提升每一步改动都很小但每个改动背后都有明确的依据。这套方案后续还可以继续扩展比如把 FPGA 侧搬到 PCIe 上做更高带宽或者在 FX3 上做多通道采集但 USB 3.0 这个门槛上FX3 的 Slave FIFO 模式基本是绕不开的经典路数。希望这篇文章能帮你少踩几个我踩过的坑一次就跑到 300MB/s 以上。
返回列表