FPGA与上位机高速通信:CY7C68013A slave FIFO模式实战解析

FPGA与上位机高速通信:CY7C68013A slave FIFO模式实战解析 简介USB2.0芯片cy68013在Slave FIFO模式下实现高速Bulk传输的完整参考方案集成FPGA控制器工程与固件源码面向FPGA开发者和USB嵌入式系统工程师USB2.0的Bulk传输适合非实时大块数据交互Slave FIFO模式让FPGA作为外部主控管理数据流适用于数据采集、文件存储、工业控制等大吞吐场景。资源共45个文件以Verilog源码、固件C代码、汇编启动配置、编译生成的hex/iic镜像及DOCX使用说明为主整体约385KB。已有719人下载学习验证了方案在实际工程中的参考价值。工程包含usb2_high_controller顶层模块、FIFO缓冲、时序控制、握手信号管理、CRC错误检测及重传逻辑固件端配置端点、中断和Bulk传输队列并附带test_top等仿真测试模块便于功能验证。随附说明书和测试例程能帮助开发者快速理解Slave FIFO模式下的Bulk传输机制并可直接移植参考实现缩短开发周期。 搞FPGA和上位机之间的高速数据交换CY7C68013A的slave FIFO模式是绕不开的经典方案。这颗芯片俗称FX2LP很多人习惯把型号简写成cy68013它内置USB 2.0高速收发器能把一条批量传输bulk通道变成一组FPGA可以直接读写的FIFO接口。配合FPGA和固件做一条数据通路稳定跑35MB/s以上没什么问题非常适合图像传感采集、高速ADC回传、逻辑分析仪这类中速率场景。这篇记录我从固件配置、FPGA读写状态机到上位机调优的全过程也把踩过的坑一并整理出来给打算走这条路的工程师做个参考。用到这套方案的场景普遍有个共性FPGA一侧数据速率在几MB/s到几十MB/s之间既要保证USB2.0带宽跑满又不能把8051拖进来当数据搬运工。slave FIFO模式正好就是干这个的。FPGA端看到的就是一个FIFOFLAG满、空信号都拉出来读写时序用状态机做掉固件只管上电初始化数据在硬件层面自动流转。这也是为什么很多采集类设备明明用的USB2.0却能流畅地把几十MB/s的数据送到PC端。后面我会按固件、FPGA、上位机三个环节展开每部分都给出寄存器、时序和实测细节也会讲到联调时最常遇到的几个故障点。无论你是刚接触FX2LP的初学者还是已经调通但传输速度不理想的老手这套流程都可以直接照着走一遍。1. 方案选型与整体架构为什么是slave FIFO1.1 常见USB桥接方案的横向比较在做FPGA到PC的高速链路时市面可选项不算少FTDI的FT232H/FT2232H、赛普拉斯后来推出的FX3CYUSB3014、以及各种内置USB的MCU。我最终选择CY7C68013A最直接的原因是它在USB 2.0时代把接口做到极致简单开发资料几乎无限价格还便宜。方案最高速率接口方式开发难度适用场景CY7C68013A (FX2LP)USB 2.0实际35~40MB/sslave FIFO/GPIFFPGA直接读写低资料极多中速率采集、控制传输FT232HUSB 2.0实际10~15MB/s左右245同步FIFO/SPI/MPSI等低小批量、简单数据传输CYUSB3014 (FX3)USB 3.0实际300MB/sGPIF IIFPGA需适配复杂时序较高高速图像、存储设备STM32H7内置USBUSB 2.0高速但受制于CPU和内部总线自定义协议中低速控制为主FPGA走FMC可扩展FT232H看起来简单但它的FIFO接口在高速模式下时序没有FX2LP那么直接而且实际吞吐往往跑不到USB2.0上限。至于FX3性能确实强但GPIF II时序状态多初学者上手周期明显长。CY7C68013A是8/16位数据总线、几个标志位、一根写选通一根读选通本质上就是一个可以挂到USB总线的SRAM式FIFO。对FPGA工程师来说这是最友好的交互方式。1.2 slave FIFO模式的工作原理FX2LP内部有一个增强型8051核心但它和端点FIFO之间的数据通路有三种典型方式GPIF可编程接口引擎、CPU直接读写FIFO、slave FIFO。在slave FIFO模式下端点FIFO的控制权完全交给外部主控也就是FPGA。8051只在初始化阶段配置好寄存器数据搬运不经过它所以不会成为瓶颈。数据流向是这样的FPGA要上传数据时在IFCLK时钟沿把数据写到FD总线上同时拉低SLWR数据进入端点FIFOFX2的USB引擎检测到缓冲区满后自动打包为512字节批量传输包发向主机。主机下发数据的方向则反过来USB引擎先把数据填入端点FIFOFPGA通过SLRD和FLAG空标志把数据读走。这个“自动打包提交”的能力在FX2里叫AUTOIN/AUTOUT配置好后相当于把USB协议细节隐藏掉了。整个通路中需要FPGA和固件配合的点有三个第一总线宽度选择8位还是16位直接决定同一时钟下数据速率第二FIFO标志FLAG的极性定义必须和FPGA逻辑一致第三短包和不满包处理依赖PKTEND信号这个很容易被忽略。后面两章我会逐一细说。另外稍微提一句同步模式与异步模式的选择。slave FIFO本身支持同步和异步两种工作方式但实际工程里几乎都用同步因为异步方式需要用外部信号选通数据时序约束更麻烦带宽上限也明显低。同步模式只要抓住IFCLK沿和FLAG状态状态机写起来非常直观所以下面的配置和代码都以同步模式为准。2. 固件侧配置让芯片进入正确的slave FIFO状态2.1 固件框架与关键寄存器FX2LP的固件推荐用Cypress原厂SDK里的框架来写主要包含fw.c主循环和重枚举、dscr.a51描述符表、periph.c用户初始化。在slave FIFO模式下periph.c的TD_Init里配置几个关键寄存器就够了TD_Poll基本可以留空因为数据流转不需要8051参与。void TD_Init(void) { // 时钟、总线模式内部48MHz同步slave FIFO IFCONFIG 0xE3; // EP2作为OUTEP6作为IN都是批量传输512字节、4重缓冲 EP2CFG 0xA0; EP6CFG 0xE0; // 复位端点FIFO顺序不能乱 FIFORESET 0x80; SYNCDELAY; FIFORESET 0x02; SYNCDELAY; FIFORESET 0x06; SYNCDELAY; FIFORESET 0x00; SYNCDELAY; // 端点FIFO寄存器沿用官方模板配置数据总线宽度和自动传输位 EP2FIFOCFG 0x01; EP6FIFOCFG 0x09; }IFCONFIG为0xE3时芯片输出内部48MHz时钟到IFCLK引脚工作于同步slave FIFO。这个时钟可以直接引到FPGA作为逻辑主时钟能省掉额外晶振同时也让FLAG信号的时序天然对齐。EP2和EP6的方向分配也很常规EP2做OUT主机写、FPGA读EP6做INFPGA写、主机读这样一上一下互不干扰。FIFORESET的复位顺序必须先NAK所有端点再逐一复位对应FIFO最后恢复正常跳过任何一步都可能导致枚举后端点假死。EP2FIFOCFG和EP6FIFOCFG的取值我直接沿用官方模板不同SDK版本会有一点差异重点是确认数据总线宽度和AUTOIN/AUTOUT相关位与你的硬件一致。2.2 固件里容易被忽略的三个细节第一个是FLAG极性问题。slave FIFO的FLAGB/FLAGC默认极性和你想象中的“满为高、空为高”不一定一样强烈建议在初始化里通过FIFOPINPOLAR寄存器把极性明确设成高有效并在FPGA代码里按同一个约定判断。否则FPGA端读到的满标志是反的轻则性能下降重则覆盖FIFO造成数据错位。第二个是短包处理。AUTOIN模式下当前缓冲被写满512字节以后USB引擎才自动提交如果一次数据量不是512的整数倍最后一包会一直赖在FIFO里不出去。这时候FPGA需要拉一拍PKTEND把没满的缓冲强制提交。很多第一次调试的朋友发现“前面的数据都收到了最后一段丢了”多半就是这个原因。第三个是描述符和驱动匹配。官方框架里的描述符默认带厂商IDVID/PID和字符串如果上位机用Cypress官方驱动最好固件与驱动包版本匹配如果要用libusb通常用Zadig把驱动替换成WinUSB这时描述符里的接口类和端点地址必须和实际一致。我在Win10 64位系统上试过直接用Zadig换WinUSB比折腾官方签名驱动省事得多。开发阶段固件通过USB线下载到RAM即可量产再考虑烧EEPROM调试周期会短很多。3. FPGA侧读写时序从状态机到带宽3.1 同步slave FIFO的接口信号在同步模式下FPGA与CY7C68013A之间的信号并不复杂核心是IFCLK、FD、FIFOADR、SLWR、SLRD、PKTEND和FLAG。数据总线可以配8位或16位开发板默认多是16位下面的时序均以16位为准。信号方向以FPGA视角描述。信号方向作用IFCLK输入48MHz同步时钟建议直接用FX2的IFCLK输出FD[15:0]双向数据总线写时输出读时输入FIFOADR[1:0]输出选择端点00EP210EP6SLWR输出写选通低有效SLRD输出读选通低有效PKTEND输出强制提交当前缓冲低有效FLAGC / FLAGB输入满/空标志按初始化配置有一点很关键尽量让FPGA逻辑完全跑在IFCLK这个时钟域下而不是自己从别的PLL分出一个时钟再和FX2对接。因为满标志和空标志本身也和IFCLK同步大家共用一个时钟源就省去了一堆跨时钟域判断。假如IFCLK必须由FPGA侧产生并送给FX2那就需要额外约束时钟质量基本是用内部PLL生成48MHz复杂度会上升不推荐第一版就这么干。3.2 写通道FPGA向PC上传数据写通道对应EP6IN端点FPGA把数据塞进端点的思路就是只要FIFO没满就在IFCLK上升沿拉低SLWR并放上数据。但实际工程里不建议每个时钟周期都盯着标志位去判断因为还要处理不满包提交和多拍连续写。我习惯用一个四状态状态机来做核心逻辑如下。reg [1:0] state; reg slwr_r; reg pktend_r; reg [15:0] fd_r; always (posedge ifclk or negedge rst_n) begin if (!rst_n) begin state IDLE; slwr_r 1b1; pktend_r 1b1; end else begin case (state) IDLE: begin if (wr_req !flag_c_full) begin fd_r fifo_out_data; slwr_r 1b0; state WRITE; end else if (pkt_req) begin pktend_r 1b0; state PEND; end end WRITE: begin slwr_r 1b1; if (last_beat) begin pktend_r 1b0; // 短包或完成本次传输 state PEND; end else if (!flag_c_full) begin fd_r fifo_out_data; slwr_r 1b0; // 继续写 end else begin state IDLE; end end PEND: begin pktend_r 1b1; state IDLE; end endcase end end这个状态机的关键在于SLWR不能长期拉低写个没完而是在每个IFCLK上升沿判断下一次是否还能继续写。因为FX2的FLAG输出略晚于时钟沿读到的“非满”状态其实对应下一拍所以状态机里通常会把SLWR看作“预拉低一拍”的写使能。实测下来如果不做这种一拍冗余很容易出现满标志边界上覆盖数据的问题。还有一个细节FIFOADR选择EP6之后地址切换需要等待建立时间通常建议切换后留2个IFCLK周期再开始读写。地址线上的毛刺会让FX2误选到EP2/EP8现象就是数据跑到别的端点上主机端表现为设备无响应。第一版调试时可以先把FIFOADR固定为EP6只做上传通路跑通后再加动态切换。提示初次调试时建议先用逻辑分析仪或ILA抓IFCLK、FLAGC、SLWR三根线确认满标志翻转和SLWR拉低之间没有明显毛刺再继续下一步。3.3 读通道PC向FPGA下发数据读通道使用的是EP2OUT端点FPGA在FLAGB为空之前持续发起读操作。同步模式的读时序和写类似但要额外注意SLRD使能后数据总线上数据有效的时刻比读选通滞后实际采样时多等一个周期更安全。IDLE: begin if (rd_req !flag_b_empty) begin slrd_r 1b0; state READ; end end READ: begin fifo_wr_valid 1b1; fifo_wr_data fd; // 数据在下一拍采样 slrd_r 1b1; state IDLE; end读方向的丢数据问题大多不是因为SLRD时序而是内部FIFO深度不够。主机一秒钟发几十MB数据过来FPGA处理速度稍慢内部缓冲就被填满。FPGA的逻辑如果还在按固定循环去读肯定会丢。建议在接口和用户逻辑之间加一个异步FIFO深度至少2048×16bit适配两个时钟域同时把异步FIFO的空/满信号反馈给用户逻辑这样PC端发起的大量数据可以被暂时缓存而不是硬塞给后级。3.4 FPGA内部缓冲和反压设计再往上一层想如果FPGA自身是连续采集比如ADC一直在采样而USB总线会因为上位机调度出现间歇性停顿那么采集端不能直接跟USB接口FIFO联动。我在这一版设计里把内部异步FIFO做成了“采集侧写、发送侧读”的缓冲池。采集侧只要内部FIFO没满就一直写满了就暂停采集逻辑发送侧则受USB满标志控制USB端FIFO满时暂停读内部FIFO。这个缓冲池同时解决了两个问题一是隔离了采集时钟和IFCLK的相位关系二是把USB瞬时的速度快慢平滑掉。实测里FPGA内部FIFO深度用16K×16bit在40MB/s速率下可以扛住上位机约2ms的调度卡顿足够避免数据丢失。如果后级还要接DDR3缓存思路完全一样把异步FIFO替换成DDR写控制模块即可。4. 上位机驱动与联调中的关键经验4.1 驱动和库的选型理论上FX2LP官方提供CyUSB.sys和CyUSB.dll配套的CyBulkStream示例也能直接测速度但官方驱动在Win10/11上经常有签名问题而且CyUSB库的接口风格偏老。我后来改用libusb配合WinUSB驱动Zadig一键替换开发体验好很多。// libusb批量读EP6的简化示例 libusb_bulk_transfer(dev_handle, 0x86, buf, 65536, actual, 1000);libusb里批量读端点地址是0x86对应EP6 IN写EP2是0x02。对纯数据搬运项目一次请求建议至少16KB以上64KB到256KB效果最好。请求太小的时候USB控制器不断切换传输描述符总线实际利用率上不去速度差一倍很正常。4.2 实测吞吐与ZLP、URB的关系我在这套平台上实测过几次单纯用同步API读64KB缓冲速度大概能到32~34MB/s改成异步模式、同时排2~4个512KB的URB后能稳定在38MB/s左右接近USB 2.0高速批量传输的协议上限。影响速度的因素从主机侧看主要有三个请求大小、同时挂起的URB数量、是否频繁出现短包。请求太小会明显降低有效载荷比例URB太少总线会出现空闲等待短包触发的ZLP处理会增加额外握手如果FPGA发出来的数据包总是512字节整数倍主机侧要留意端点字节计数。站在FPGA侧想跑高速的关键则是尽量让SLWR连续拉低、持续把128字节以上的突发数据灌进FIFO而不是每个周期都判断标志再写一拍。连续写和判断式写跑出来的速度能差出10MB/s以上。联调阶段我一般会先做一次链路自检在FPGA里放一个递增计数器不断往EP6写数据上位机按递增规律校验。这个办法能一次性暴露时序、缓冲、ZLP处理等多个问题。如果递增序列不连续先查FPGA侧波形如果序列连续但速度不达标再回头调上位机请求和URB参数。4.3 常见问题速查表联调过程中遇到最多的问题我整理成了一张速查表每一个都是实际项目里踩过的坑。现象可能原因排查/解决办法枚举正常但读不到数据AUTOIN未开、FIFO未复位、FLAG极性反检查EP6FIFOCFG用Bus Hound确认是否收到IN端点数据用ChipScope观察FLAGC和SLWR速度只有1~2MB/sUSB工作在全速12Mbps模式主机请求太小固件里8051处理数据检查设备描述符bcdUSB改用异步大块请求确认AUTOIN模式数据错位或丢块FIFO满时强写地址切换未等待内部缓冲溢出FPGA侧加一拍冗余判断A0A1切换后延时2拍加大内部FIFO并做反压跑一段时间挂死上位机停止读取导致USB端FIFO溢出上位机保证持续读FPGA检测溢出后清空FIFO重新同步Windows端驱动总是失败官方驱动签名问题用Zadig换WinUSB配合libusb使用排查时建议用一个逻辑分析仪或ILA抓FPGA侧的关键波形尤其要抓IFCLK、FLAGC和SLWR三者的相位关系。主机侧用Bus Hound或USBlyzer看端点传输是否正常能快速区分是USB协议层问题还是FPGA逻辑问题。两个工具同时用基本能把问题缩小到单一模块。5. 写在最后一点个人体会这套方案里固件和FPGA单独看都不算难难的是联调时对FLAG、时序和PC端传输策略的整体理解。我第一次调通用了接近一周后来把问题拆开才明白固件只管端点和复位FPGA只按约定读FLAG上位机只管大块并发传输每个环节都独立验证后再组合起来速度自然就上去了。如果后续你发现USB 2.0的40MB/s不够用可以顺着这套经验迁移到FX3CYUSB3014的GPIF II或者FT601这类USB 3.0 FIFO芯片架构思路几乎一致只是端点和时序信号更多、要求更高。先把这个中速率方案做到心里有数再去碰高速率接口会顺畅很多。本文还有配套的精品资源点击获取