ARTICLE DETAIL

资讯详情

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

USB3.0与Artix-7 FPGA高速数据采集实战:方案设计到调试

USB3.0与Artix-7 FPGA高速数据采集实战:方案设计到调试 USB3.0 在 Xilinx Artix-7 上的高速数据采集项目应用在数据采集这个行当里待久了很多人都会撞上同一堵墙ADC 出来的数据量实在太大了FPGA 这边逻辑跑得飞快结果却卡死在上位机接口上。早几年大家习惯用 PCIe 或者千兆网但 PCIe 插槽不是每台机器都有千兆网实际能跑到的带宽又很有限遇到便携测试、车载测试或者电脑只留了一个 USB 口的场景非常被动。后来我把方案换成了 USB3.0 Xilinx Artix-7 的组合才算真正把“高速数据采集”这条路走通USB3.0 理论带宽 5Gbps实际批量传输能稳定跑到 400MB/s 以上足够覆盖 100MSPS 级别的 16 位 ADC也能应对 250MSPS 级别的 12 位 ADC。这篇就从头复盘一遍我在 Artix-7 平台上做 USB3.0 高速采集的完整过程从方案选型、硬件设计、FPGA 逻辑到 PC 端驱动与调试把真正能落地的细节和踩过的坑都摊开来讲。1. 项目需求与方案选型1.1 先算带宽你的数据到底有多大很多人一上来就想用 USB3.0但连自己的数据量都没算清楚结果要么带宽浪费要么根本不够用。我先给你一个最简单的估算公式数据速率MB/s 采样率MSPS× 位宽bit÷ 8。注意这是原始数据量还没算传输协议的开销。举个例子一个 16 位 ADC 跑 100MSPS那每秒就是 100M × 2 字节 200MB/s。USB3.0 在实际批量传输中刨掉包间隔、握手、CRC 之后大概能跑 380~430MB/s。也就是说单通道 100MSPS 的 16 位采集USB3.0 完全能接住而且还有富余。如果是双通道同时采那就是 400MB/s直接逼近极限这时候就得上 FPGA 内部做数据打包、降位宽、或者干脆降低采样率来换通道数。再看另一个常见场景12 位 ADC 跑 250MSPS原始数据是 375MB/s也还卡在 USB3.0 的能力范围内。但实际做项目时我不建议把 USB3.0 跑到 95% 以上因为系统一旦有轻微抖动、调度延迟、或者上位机存储不及时就会丢数据。所以我的习惯是留出 20% 以上的余量或者用 FPGA 内部 FIFO 做缓冲削峰。先把需求算清楚后面所有选型才有依据。1.2 为什么是 Artix-7 USB3.0而不是 Zynq 或 PCIe选 Artix-7 而不是 Zynq核心原因是成本和启动时间。Zynq 带双核 ARM做复杂交互确实方便但如果你只是需要 FPGA 做高速采集控制 接口桥接ARM 侧平时基本闲着还增加了一片 DDR 和启动配置的复杂度。Artix-7 作为纯 FPGA资源够用、功耗低、价格也更友好特别是 XC7A75T 和 XC7A200T 这两颗料在数据采集板卡里出镜率非常高。至于为什么不用 PCIe有几个现实问题一是用户现场不一定有 PCIe 插槽很多工控机和笔记本没有二是 PCIe 硬核在 Artix-7 里只有部分型号有而且要做 DMA 驱动开发量比 USB 大得多。USB3.0 是外部设备接口即插即用Windows、Linux、macOS 都有现成协议栈部署成本低非常多。但这里有个容易踩的坑Artix-7 的 GTX 高速收发器虽然最高能到 6.6Gbps理论上是能跑 USB3.0 的物理层 5Gbps但 USB3.0 的协议握手LFPS 握手、链路训练非常复杂纯用 GTX 逻辑去实现 USB3.0 Device 控制器工程量巨大性能还不稳定。所以工程上几乎没有人在做纯 FPGA 内嵌完整 USB3.0 协议栈普遍的做法是外接一颗 USB3.0 控制器芯片FPGA 只管把并行数据灌给它协议的事让控制器去处理。这也是我最终选择的路线。2. 硬件设计要点2.1 电源与上电时序Artix-7 的底线Artix-7 的电源设计比很多人想象中严格。核心供电 VCCINT 是 1.0VBRAM 供电 VCCBRAM 也是 1.0V辅助供电 VCCAUX 是 1.8VIO 供电 VCCO 则根据接口电平来定比如 Bank 接 USB3.0 控制器就配 3.3V 或 1.8V。关键点在于上电顺序VCCINT 必须先上电然后是 VCCBRAM再是 VCCAUX最后才是 VCCO。顺序反了轻则启动异常重则损坏器件。我在第一版设计里偷懒用了一颗多路输出 DC-DC靠软启动时间差来凑顺序结果经常出现配置失败甚至 JTAG 都连不上。后来老老实实用电源监控芯片 使能脚级联每路输出延迟 2ms 左右再使能下一路问题就消失了。强烈建议不要省这个监控电路如果实在不想加专用芯片至少要用 RC 延迟来拉开各路使能时序。另外一个容易忽略的点是 VCCINT 的纹波。Artix-7 内部逻辑规模跑起来后瞬时电流变化非常大1.0V 电源的纹波如果超过 30mV核心逻辑就有可能发生时序违例表现就是采集到的数据时不时出现一个错误字节。我实测下来用低 ESR 的钽电容 高频陶瓷电容组合去压纹波比单纯堆电容数量有效得多。2.2 时钟方案别让时钟拖垮 GTXArtix-7 的 GTX 需要一个高质量参考时钟一般是 100MHz 或 125MHz。这个时钟的抖动指标直接影响 GTX 能否稳定锁定、误码率多高。建议直接选用专用的可编程时钟芯片比如 Si5338 或 Si5340通过 I2C 配置输出频率而不是用手头随便抓来的通用晶振。我说一个真实翻车经历第一版为了省几块钱用了一颗普通 CMOS 晶振给 GTX 做参考时钟结果 GTX 偶尔能 lock偶尔 lock 不上跑上几分钟还可能出现 CRC 错误。后来用频谱仪一看那颗晶振的相位噪声在 100kHz 偏频处差了将近 10dB。换成 Si5338 之后所有问题不治而愈。时钟这东西在高速接口里就是基石建议直接一步到位。如果是配合 CYUSB3014也就是 FX3这种 USB3.0 控制器FPGA 侧还要和 FX3 的 PCLK 对齐。FX3 在 SlaveFIFO 同步模式下PCLK 可以由外部输入我习惯用 FPGA 给一个固定 100MHz 或 102.4MHz这样 FPGA 内部逻辑可以直接用 PCLK 域来驱动 SlaveFIFO 信号不需要再做跨时钟域转换少了很多麻烦。2.3 USB3.0 信号完整性与差分对布线USB3.0 跑在 5Gbps已经不是普通数字信号了Layout 必须按差分对要求来做。TX/RX 差分对要做 90Ω 差分阻抗控制对内等长误差尽量控制在 5mil 以内对间等长控制在 50mil 以内。USB 座子和控制器芯片之间走线要尽量短减少过孔数量最好做到 2 个过孔以内。另外要特别注意的是USB3.0 座子除了差分信号还有 VBUS 和 GND。VBUS 在接入瞬间会有很大的浪涌电流一定要加 ESD 保护器件比如 USBLC6-2SC6否则静电一旦打进来Controller 芯片和 FPGA 都可能一起带走。我在实验室就吃过这个亏插拔了几十次之后FX3 的 PHY 部分挂了查了几天才发现是 ESD 防护不到位。还有一个小细节USB3.0 的 RX 和 TX 在连接器上是交叉的。也就是说主机端的 TX 要接到设备端的 RX主机端的 RX 接到设备端的 TX。画原理图时特别容易一不留神把差分对接反结果就是枚举失败。建议画完原理图后在 PCB 里用网表核查工具跑一下连接关系确认交叉对应正确。2.4 FX3 接口电平与引脚规划我用的 USB3.0 控制器是 Cypress 的 FX3CYUSB3014它在 SlaveFIFO 模式下和 FPGA 直接连接信号包括 32bit 数据总线、A[1:0] 地址线、SLWR、SLRD、SLOE、PKTEND、FLAGA/B/C/D 状态标志。这里最容易犯的错是电平域不匹配。Artix-7 的 Bank 电压如果配置成 3.3V而 FX3 的 IO 电源是 1.8V两边直连轻则信号识别不了重则烧 IO。所以引脚规划前一定要先查两边的数据手册确认 IO 电平域一致。FX3 的 IO 电源可以支持 1.8V 或 3.3V但注意 3.3V 时它的 IO 速度会稍微慢一点而我们的场景里 PCLK 是 100MHz32bit 总线下理论带宽是 400MB/s用 3.3V 也能跑但如果追求极限速率建议用 1.8V。还要注意 FPGA 的引脚分配尽量把 SlaveFIFO 总线放在同一个 Bank并且选择支持 HRHigh Range而不是只有 HPHigh Performance的 Bank。Artix-7 的 HR Bank 支持 3.3VHP Bank 最高只支持 1.8V。如果用了 3.3V 电平却把信号约束在 HP Bank 上那就只能改电平了非常被动。3. FPGA 侧逻辑实现3.1 数据通路总览我最终采用的 FPGA 内部数据流是ADC 数据 - 双时钟 FIFO - DDR3 缓冲 - USB3.0 控制模块 - FX3 SlaveFIFO 接口 - PC。为什么要经过 DDR3因为 ADC 采样率不是恒定不变的可能一会儿 100MSPS 一会儿 10MSPS而 USB3.0 往 PC 传数据是一个持续的过程中间如果有瞬时尖峰FIFO 会被冲爆。加了 DDR3 之后数据可以暂存在 1GB 的缓存里USB 端按自己的节奏稳步搬走。双端口 FIFO 用的 Xilinx 自带 IP读时钟用 USB3.0 接口的 PCLK写时钟用 ADC 采样时钟跨时钟域的事交给 FIFO 做。要注意的是 FIFO 深度千万不要抠门我第一版只配了 8KB采样率一高就溢出。后面改成 256KB问题立刻消失。FIFO 的 almost_full 标志要接到采集控制逻辑里当 FIFO 快满时要么暂停 ADC 采样要么丢一个数据包的同时打一个标记让上位机知道这段时间的数据不连续。使用 DDR3 时MIG IP 的时钟域和用户逻辑之间要做一个异步 FIFO 衔接MIG 的用户接口跑 400MHz数据位宽是 64bit和 USB3.0 模块的 100MHz 32bit 位宽不在一个域上这个衔接如果做不好出现时序违例的几率非常高。我个人建议用 Xilinx 的 AXI4 协议封装 MIG 的用户接口虽然稍微多花一点时间但后面扩展其他功能模块时会方便很多。3.2 SlaveFIFO 写状态机FX3 的 SlaveFIFO 同步写模式核心控制信号是 SLWR 和 PKTEND。当 FPGA 要往 USB 端点写数据时先把数据放到 D[31:0] 总线上然后在 PCLK 的上升沿持续拉高 SLWRFX3 会在每个 SLWR 有效沿把总线上的数据打入 FIFO。写完一块固定长度的数据后再拉一个周期的 PKTEND告诉 FX3 这是一个包的结束可以提交给 USB 端点。我总结了一个三段式状态机的写法代码逻辑简单而且可读性强localparam IDLE 3d0; localparam WRITE 3d1; localparam PKTEND 3d2; reg [2:0] state; reg slwr_r; reg pktend_r; always (posedge pclk) begin if (!rst_n) begin state IDLE; slwr_r 1b0; pktend_r 1b0; end else begin case (state) IDLE: begin pktend_r 1b0; if (fifo_empty_n) begin state WRITE; slwr_r 1b1; end end WRITE: begin if (pkt_cnt PKT_LEN - 1) begin state PKTEND; slwr_r 1b0; pktend_r 1b1; end end PKTEND: begin pktend_r 1b0; state IDLE; end default: state IDLE; endcase end end这里特别要注意 PKTEND 和 SLWR 的配合。SLWR 必须先撤掉再拉 PKTEND不能在 SLWR 有效的同时发 PKTEND否则 FX3 的行为会变得不可预期。我第一版在这里吃过亏数据偶尔会多出一个 32bit 的尾包排查了很久才发现是这两个信号重叠了一个时钟周期。PKT_LEN 的选择也直接影响传输效率。USB 批量传输的最大包长是 1024 字节但为了减少 USB 包间隔带来的带宽损耗一次提交给 FX3 的数据包最好是大块连续数据比如 16KB 甚至 64KB。包太小比如每次只发 512 字节那 USB3.0 的利用率会惨不忍睹实测可能连 200MB/s 都跑不到。3.3 与 DDR3 缓冲的衔接如果只是数据直通不经过 DDR3那 USB3.0 模块只需要从 FIFO 读到数据然后往 FX3 灌即可。但一旦加入 DDR3就需要设计一个简单的 DMA 引擎。我用的是 AXI4 协议一个写通道负责把 ADC 数据写进 DDR3 的某个环形缓冲区一个读通道负责按顺序把缓冲区的数据读到 USB3.0 模块。环形缓冲区的设计有一个容易出错的地方读指针和写指针之间要留出空闲区域不能出现读追上写的情况。最简单的办法是在 FPGA 里维护一个占空计数器当还剩余多少缓存时停止读或写。实际代码里用格雷码跨时钟域传递指针也很常见但如果是同一个时钟域内直接比较指针就行没必要把简单问题复杂化。DMA 的突发长度burst length也直接影响带宽。AXI4 的突发长度可以到 256但在 400MHz 用户时钟下一次突发越大越能降低仲裁开销。我实测下来突发长度设置为 128512 字节时DDR3 的效率已经很高了再往上提升有限反而让时序收敛变得困难。3.4 仿真与板级调试技巧FPGA 逻辑写完我强烈建议先在 Vivado 里做行为仿真把 SlaveFIFO 时序验证清楚了再上板。不要觉得仿真浪费时间USB3.0 一旦接上出问题很难定位是 FPGA 时序问题还是 FX3 配置问题仿真至少能先把 FPGA 这一侧的行为锁死。板级调试时Vivado 的 ILA集成逻辑分析仪是必备工具。把 PCLK 作为采样时钟抓 SLWR、PKTEND、FLAGA、数据总线这些信号就能看到实际写入的过程是否和预期一致。最常见的场景FLAGA 拉低是 FX3 内部 FIFO 满了说明 FPGA 写入太快而 PC 端读取不及时这时候不是去改 FPGA而是先看上位机的读线程是否在正常工作。另外一个技巧是把 FXR 的 FLAGB 配置成水位线标志当 FIFO 低于某个阈值时拉高。调试的时候观察 FLAGB 的翻转频率如果频繁翻转说明 FX3 端在周期性饥饿传输效率低可以考虑提高上位机单次读请求的大小。这类问题在逻辑仿真里根本看不出来只有上板实测才能暴露。4. FX3 固件与 PC 端软件配合4.1 FX3 固件关键配置Cypress 的 FX3 SDK 提供了 SlaveFIFO 的示例工程但它给的例子默认是 Cypress 自家的控制中心软件使用我们要做的是按项目需求修改。重点配置三个地方DMA 通道、端点配置、SlaveFIFO 时序模式。DMA 通道建议用自动提交模式AUTO DMA这样 FPGA 侧只需要连续写数据FX3 会自动把缓冲区提交到 USB 端点不需要 FPGA 侧参与任何 DMA 描述符管理逻辑上会轻松很多。端点配置就是指定一个批量读端点比如 EP6大小 512 或 1024 字节方向是 OUT从 FPGA 到 PC。SlaveFIFO 时序模式里我选的是同步模式PCLK 100MHz数据宽度 32bit。FX3 固件里有一行CyU3PSetSlaveFifoConfig()里面有个isSynchronous参数必须设为CyTrue同时把clockLevel设成 0表示数据在 PCLK 上升沿采样。这些参数看起来不起眼但设错了就会出现数据错位一半的时间。还有一点值得提醒FX3 的固件里默认会做 USB 枚举设备名和 VID/PID 的定义如果想用 Cypress 官方驱动和上位机库建议保持默认 VID/PID 不变否则驱动签名和安装逻辑都要跟着改反而麻烦。4.2 PC 端 USB 读写与缓冲设计PC 端我用的也是 Cypress 提供的 CyUSB3.sys 驱动配合官方 API。核心目标只有一个把 USB3.0 的带宽跑到接近 400MB/s而这需要做异步批量传输和多 URB 排队。不要用同步读取那种简单方法因为它一次只发送一个读请求接收数据期间 USB 总线是空闲的带宽利用率非常低。我采用的方式是维护一个 16 个缓冲区的队列每个缓冲区 128KB先一次性提交 16 个异步读请求等某一个完成回调后把数据存到应用层然后马上重新提交这个缓冲区继续下一次读取。这样 USB 设备端永远有读请求在排队总线一直被占满实际带宽能轻松超过 350MB/s。上位机拿到数据后如果只是存盘建议用一个双缓冲环形队列读线程负责往队列里写存储线程负责从队列里往硬盘写中间不要有锁或者尽量用无锁队列。实测发现如果在读回调里直接做硬盘写入速度会从 380MB/s 掉到 200MB/s 左右原因就是磁盘 I/O 阻塞了 USB 读请求的重新提交。4.3 数据校验与统计调试阶段我强烈建议上位机加上包序统计和 CRC 校验。FPGA 每次打包时在包头写入一个自增包序号PC 端检测到序号跳变就知道中间有丢包马上停下来排查。这个方法帮我在早期抓到了一个非常隐蔽的丢包问题PC 在高速率下频繁触发内存分配导致读线程停顿了十几毫秒而 FPGA 侧等不到读请求FX3 FIFO 满后直接溢出了一段数据。解决思路是初始化时就将 16 个缓冲区全部分配好运行过程中只做移交、不重新分配彻底避免运行时内存分配开销。另外可以在 PC 端绘制实时速率曲线方便观察传输是否稳定。速度忽高忽低往往说明某个环节在间歇性阻塞这是 USB3.0 调优的重要信号。5. 常见问题与排查实录5.1 插上没反应或识别成 USB2.0这是 USB3.0 项目里最常见的问题没有之一。检查思路按顺序来先看 PC 端设备管理器识别到的是“USB 3.0 超高速”还是“USB 2.0 高速”如果是后者说明链路退化到了 USB2.0。这种情况大概率是 USB3.0 的差分信号对质量太差或者 ESD 器件电容过大导致信号质量下降。另一种常见原因是 U3 的 TDR 眼图没通过也就是接收端检测不到 USB3.0 的 LFPS 信号。这时候先别动软件用示波器看一下 USB 座子上的 TX/RX 差分信号是否正常如果信号幅度不足加大驱动电流或调低接收均衡器参数这些在 FX3 固件的 PHY 配置项里都有接口。5.2 速度卡在 100MB/s 左右我遇到过的几个原因按频率排序一是 FPGA 端数据包太小二是上位机只提交了少量读请求三是 FX3 的 DMA 缓冲区没有配置为多缓冲。先看图如果每次传输之间有明显空闲多半是上位机读请求不够多把缓冲池从 4 个提升到 16 个一般能解决问题。还有一个容易忽视的点开发机上有老旧的 USB 控制器驱动或者主板厂商在 UEFI 里把 USB3.0 的“USB Legacy Support”给开了这些都会导致实际跑在 USB2.0 模式。进 BIOS 关掉 Legacy USB Support速度往往立刻上来。5.3 FPGA 侧 FIFO 溢出误触发FPGA 内部的 FIFO 溢出提示有两种可能一种是真实溢出数据量太大缓存不够另一种是 almost_full 信号的时序错了。我调试时用 ILA 抓 almost_full发现它在信号链路上有毛刺导致误判。后来把 FIFO 的 almost_full 改成同步输出并在采集控制模块里做了两级同步处理误触发就消失了。如果你的 ADC 采样钟本身不稳定也会导致 FIFO 写侧有效信号出现毛刺建议在写使能前加一个寄存器打拍保证进入 FIFO 的写信号是干净的单周期脉冲。5.4 长时间运行掉速与发热系统连续跑几个小时之后速度从 380MB/s 掉到 200MB/s这是热问题。Artix-7 和 FX3 在高速传输时功耗都不低如果散热片没贴好核心温度升高会导致时序余量下降从而出现隐性错误和重传。可以借用 Artix-7 内部的 XADC 实时监测温度超过 85℃ 就触发风扇或者降速告警。另外长时间运行掉速还有一种可能是上位机内存碎片导致缓冲区分配失败读请求数量逐步减少。解决方法是程序启动时一次性分配固定缓冲池运行中不释放或者定期重启缓冲池。现象排查方向常见根因枚举为 USB2.0检查差分对走线、ESD 电容、PHY 配置信号质量差或 LFPS 握手失败速率只有 100MB/s 左右上位机读请求数量、缓存池大小读请求数量不足每次传输间隙过长长时间运行掉速监测温度、检查上位机内存碎片芯片过热或缓冲池分配失败偶发数据错字用 FPGA 内部 CRC 校验抓错误发生时序电源纹波偏大或跨时钟域处理不当回到项目本身这套方案我前后迭代了三版最大的体会是USB3.0 只是整条链路的一环真正决定系统上限的是 FPGA 内部缓冲、FX3 配置、上位机读请求三个环节之间的协同。任何一个环节掉链子最终的传输速率都会大打折扣。最后再分享一个调试小技巧在上位机里做一个 64bit 的接收计数器FPGA 侧同步发一个递增帧序号两边一旦对不上立刻能定位是丢包还是重复包省下的排查时间绝对值得。
返回列表