前阵子帮客户做一套高速数据采集系统,FPGA 端要实时把 ADC 数据灌到 PC 上做处理。评估了几种 USB 3.0 方案之后,最后定了FPGA + CYUSB3014(EZ-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 数据总线上丢数据并拉低 SLWR,PCLK 上升沿采样,数据进入 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 | 接口时钟,通常 100MHz |
| SLCS# | 输出 | 片选,低有效 |
| 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 的桥接 demo,Slave FIFO 模式的所有基础代码都在,你要改的主要是:GPIF 状态机配置、DMA 通道参数、端点配置、线程与水位的映射关系。
固件主流程大致是:
- 调用 CyU3PDeviceInit 初始化芯片
- 调用 CyU3PKernelEntry 启动 RTOS 内核
- 创建 DMA 通道(P2U 和 U2P)
- 加载 GPIF 状态机配置(Slave FIFO 同步模式)
- 配置 USB 端点并连接 USB
- 启动 GPIF 引擎
每一步都有对应的 API,SDK 里也有完整注释,关键是理解每个参数的含义,尤其是 DMA 通道的 buffer 大小和 buffer count,这两个参数直接决定吞吐率。
3.2 端点、DMA 通道与线程配置
这是固件里最重要的部分。我们的数据通路是 FPGA 把数据写进 FX3,所以核心是 P2U(Peripheral to USB)通道,外部传入的数据先落入 GPIF II 的 FIFO,DMA 引擎再把数据搬到 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 0x81,PacketSize 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 快满了才通知 FPGA,FPGA 的写入状态机来不及停,就会丢数据;阈值设得太大,明明还有很多空间但 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 示例:
// 同步后的 FLAG:flag_a_sync // 状态机核心跳转 always @(posedge pclk or negedge rst_n) begin if (!rst_n) state <= IDLE; else begin case (state) IDLE: begin if (fifo_empty == 1'b0 && flag_a_sync == 1'b1) state <= WRITE; end WRITE: begin if (flag_a_sync == 1'b0) state <= IDLE; else if (fifo_empty == 1'b1) state <= IDLE; end endcase end end // 数据与写使能输出 always @(posedge pclk or negedge rst_n) begin if (!rst_n) begin slwr_n <= 1'b1; dq <= 32'h0; end else if (state == WRITE) begin dq <= fifo_dout; // 从缓存 FIFO 读出数据 slwr_n <= 1'b0; // 拉低写使能 end else begin slwr_n <= 1'b1; 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/s,USB 侧实际能吸收的不到 400MB/s,所以瓶颈在 USB 链路。338MB/s 大约是 400MB/s 理论最大 payload 的 84.5%,这个数字已经很健康了,说明 USB 协议开销和 FIFO 切换间隙已经压得比较低。
所以如果有人问"怎么才能测到 400MB/s 以上",答案很简单:换 USB 3.1/3.2 方案,或者用 PCIe。FX3 跑 USB 3.0,338MB/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 以上。