ARTICLE DETAIL

资讯详情

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

FPGA与CY7C68013 Slave FIFO:USB2.0高速Bulk传输设计实战

FPGA与CY7C68013 Slave FIFO:USB2.0高速Bulk传输设计实战 简介面向FPGA工程师与嵌入式开发者的USB 2.0高速Bulk传输完整方案围绕Cypress CY68013在Slave FIFO模式下的实际应用展开。资源包共45个文件约385KB涵盖6个Verilog源码如usb2_high_controller.v、test_top.v、固件工程YJ.c、fw.c、dscr.a51及对应头文件、编译产物hex、iic、obj、lst以及Cy68013控制器说明书docx便于对照代码理解工程结构。已有719人学习。内容深入Bulk传输五大关键环节握手信号管理、FIFO数据缓冲、时序控制、CRC错误检测与恢复、协议转换FPGA侧代码与固件配合演示如何配置端点与中断、维护传输队列。配套说明书和测试例程可帮助快速集成验证时序与功能显著降低USB 2.0 Bulk传输的开发门槛。 这段时间调一块USB2.0高速采集板主控是FPGA和电脑之间走CY7C68013的Slave FIFO模式做Bulk传输。这套组合在数据采集、逻辑分析仪、工业相机这类项目里非常常见核心价值在于USB端点FIFO直接以并行总线形式暴露给FPGAFPGA像读写普通FIFO一样就能把数据灌进USB完全不占用68013内部8051核的精力。如果你正在纠结“FPGA怎么跟USB2.0芯片高效对接”或者已经被Slave FIFO的时序、固件寄存器绕晕这篇文章应该能帮你把整条链路理清楚。我默认你已经接触过Cypress/英飞凌的FX2LP系列至少知道CY7C68013有个8051内核也知道它有Slave FIFO和GPIF两种接口模式。下面我按“为什么选它—信号怎么接—固件怎么配—FPGA怎么写—实测踩坑”这条线展开全部是实际项目中验证过的东西。1. 为什么这个项目绕不开Slave FIFO选型逻辑与整体架构1.1 高速流式传输的核心矛盾USB协议开销与CPU干预先回到最根本的问题USB2.0 High-Speed的理论信令速率是480Mbps换算成字节是60MB/s但Bulk传输根本跑不满这个值。因为每个事务都有令牌包、握手包、帧间隔和SOF调度开销满速情况下一个微帧最多塞13个512字节的Bulk事务所以Bulk的理论极限大概是53.2MB/s实际稳定传输通常落在40~50MB/s之间。这意味着什么呢如果你让68013内部的8051核参与数据搬运每次端点FIFO满/空都触发中断然后再用CPU去读寄存器、搬内存、写端点CPU根本跟不上这个速率带宽直接掉到几MB/s。正确的做法是让数据流完全绕过CPUUSB SIE串行接口引擎负责把总线上的数据放进端点FIFO外部主控也就是FPGA直接读写这些FIFO。Slave FIFO模式就是干这个的。1.2 Slave FIFO与GPIF、FT2232H的取舍CY7C68013除了Slave FIFO还有GPIF模式。GPIF是让68013自己做主机去读外部设备适合主动抓取数据而Slave FIFO是把68013的端点FIFO变成从设备接口让FPGA掌握读写节奏。在采集系统里FPGA往往是数据的产生者它知道自己什么时候有数据、数据什么时候准备好了所以从设备模式更自然。我也对比过FTDI的FT2232H/FT232H方案。FT2232H的FIFO总线确实简单但它的引脚时序和68013的Slave FIFO并不完全一样而且在控制通道和自定义协议灵活性上68013的8051核可以顺带处理一些命令解析数据通道不用动。另外CY7C68013的资料和例程存量极大无论是Cypress官方AN4050还是开源项目fx2lafw都提供了可参考的工程。对于需要高速Bulk 自定义控制命令的项目这套方案的综合成本是偏低的。最终的系统架构是这样PC上位机通过Bulk IN端点EP2接收FPGA上传的数据通过Bulk OUT端点EP6下发配置命令或控制参数控制端点EP0负责少量枚举参数和调试信息。8051核跑一个极简固件只做端点初始化和外部命令转发数据面完全交给FPGA。2. Slave FIFO的管脚级“翻译”FPGA必须搞懂的一组信号与时序2.1 四个信号组数据总线、选通、状态标志、时钟Slave FIFO接口对FPGA来说就是一组异步/同步RAM接口但管脚命名偏硬件手册风第一次接触容易晕。拆开来看其实是四组信号信号组引脚方向作用数据总线FD[15:0]双向16位并行数据。8位模式下只用FD[7:0]地址选通FIFOADR[1:0]FPGA→FX2选择当前操作的端点FIFO00EP210EP6读/写选通SLRD、SLWR、SLOE、PKTENDFPGA→FX2SLRD拉低读一字节/字SLWR拉低写一字节/字SLOE是读输出使能PKTEND用于提交短包状态标志FLAGA、FLAGB、FLAGCFX2→FPGA可编程配置为FIFO空/满/可编程阈值标志一般低有效时钟IFCLK双向同步模式下的总线时钟可由FX2内部产生也可由FPGA外部输入设计时还有一个SLCS片选脚如果系统里只有一颗68013可以接地或由FPGA控制。我的建议是保留SLCS控制防止FPGA复位期间GPIO处于中间态导致误读写。2.2 同步读写时序上升沿采样控制信号要提早稳定Slave FIFO支持同步和异步两种模式高速场景必须用同步模式。同步模式下所有数据搬运都在IFCLK的上升沿完成。关键点是写FIFOFPGA→FX2FPGA先把数据放到FD总线然后把SLWR拉低IFCLK上升沿时FX2把FD上的数据写入当前FIFO。数据手册里对建立时间有要求通常要求数据在上升沿前至少几纳秒稳定保持时间几纳秒。读FIFOFX2→FPGA先把SLOE拉低让FX2把当前FIFO字放到FD总线然后拉低SLRDIFCLK上升沿读取数据并自动递增FIFO指针。实际写FPGA代码时一个很实用的技巧是用IFCLK的下降沿来更新SLWR、SLRD、FD这些输出信号。这样从下降沿到下一个上升沿之间有半个时钟周期让信号稳定建立时间余量非常充裕。如果所有信号都在上升沿更新组合逻辑一多时序就容易紧张。2.3 SLOE和PKTEND两个最容易忽略的引脚SLOE只影响读方向。很多第一次写读状态机的人只拉SLRD忘了SLOE结果FD总线始终是高阻读回来全是0xFF。记住读FIFO时SLOE和SLRD都要拉低而且SLOE要在SLRD之前有效给FX2留出驱动总线的时间。PKTEND是短包提交的关键。Bulk传输是按512字节的包为单位进行的AUTOIN模式下当FIFO里的数据达到512字节时FX2会自动提交成一个USB包。但如果最后一次数据不足512字节又想让主机端收到这个短包来结束传输FPGA就必须在写完最后一个数据后拉低PKTEND一拍强制提交当前FIFO中的剩余数据。如果你只做固定大小的块传输每次都恰好是512的整数倍这个脚可以不接但只要传输长度可能不是512的倍数PKTEND就是必需的。3. 固件侧配置一把寄存器组合出高速BULK通道3.1 TD_Init里必须配好的核心寄存器FX2固件的初始化逻辑很简单重点都在TD_Init函数里。以下是一份能跑Slave FIFO 16位同步Bulk传输的最小配置void TD_Init(void) { // 1. CPU时钟48MHz CPUCS 0x12; // 2. 配置IFCONFIG外部IFCLK、Slave FIFO、同步模式 IFCONFIG 0xE3; // 3. 复位所有端点FIFO FIFORESET 0x80; FIFORESET 0x02; // EP2 FIFORESET 0x04; // EP4 FIFORESET 0x06; // EP6 FIFORESET 0x08; // EP8 FIFORESET 0x00; // 4. 端点配置EP2 BULK IN 512字节 4重缓冲EP6 BULK OUT 512字节 4重缓冲 EP2CFG 0xA0; // VALID BULK IN 512 4x EP6CFG 0x48; // VALID BULK OUT 512 4x EP4CFG 0x80; // 禁用 EP8CFG 0x80; // 禁用 // 5. FIFO配置16位总线AUTOIN/AUTOOUT EP2FIFOCFG 0x0A; // AUTOIN WORDWIDE EP6FIFOCFG 0x12; // AUTOOUT WORDWIDE // 6. AUTOIN自动提交长度512字节 EP2AUTOINLENH 0x02; EP2AUTOINLENL 0x00; // 7. 允许OUT端点自动刷新到FIFO REVCTL 0x03; // 8. FLAG极性默认低有效FIFOPINPOLAR保持默认即可 FIFOPINPOLAR 0x00; }这里每个设置都有讲究。IFCONFIG0xE3表示使用外部IFCLK由FPGA板提供48MHz这也是我推荐的接法因为FPGA内部PLL可以精确控制时钟相位和占空比比依赖FX2内部时钟更可控。如果你不想额外引时钟给FX2也可以用IFCONFIG0xE2让FX2内部产生48MHz并输出到IFCLK引脚但这时FPGA侧必须把这个时钟当输入不能两边都驱动。EP2CFG0xA0和EP6CFG0x48的选择逻辑是BULK传输必须用512字节端点缓冲才有满速4重缓冲4x非常关键它能吸收USB主机调度抖动。如果只配成1x或2x缓冲一旦主机事务间隙稍大FIFO就会满FPGA只能暂停写入平均带宽立刻掉下来。3.2 AUTOIN的边界问题为什么固定块长比短包更省心AUTOIN模式本质上是一个硬件自动提交器每当FIFO中的数据量达到EP2AUTOINLENH/L设定的长度硬件自动把这段数据打包发送到USB总线。这个设计很聪明但有一个隐藏前提FPGA每次写入的数据量必须恰好是512的整数倍。如果FPGA某个时刻只写入了300字节AUTOIN不会触发数据就卡在FIFO里等后续数据凑满512。如果后续数据永远不来主机端就会一直挂起等待直到超时。所以我的经验是流式采集场景下上位机和FPGA约定一个固定的块大小比如每次上传16KB32个512字节包FPGA端缓冲采集数据到16KB后一次性写入AUTOIN每满512就自动提交整块写完后再检查余数有剩余就拉PKTEND。这样既保持了高吞吐又不会出现数据滞留。OUT方向也要注意AUTOOUT模式下主机发来的短包会正常进入FIFO但零长度包默认会被丢弃。如果你的上位机可能发送非512整数倍的命令帧FPGA侧要根据FIFO的空标志和接收长度自行判断数据边界不能默认每个包都是512字节。命令帧建议单独走EP0控制端点或者每次都补齐到512字节再发。3.3 枚举阶段与上位机驱动的配合固件里除了TD_Init还需要改USB描述符。设备描述符里的VID/PID如果是默认的0x04B4/0x8613可以让Cypress的CyUSB驱动直接识别如果改成自己的VID/PID就需要在驱动层面对应修改INF文件或用WinUSB。端点描述符必须声明为Bulk端点wMaxPacketSize填512。这个环节经常有人漏了改端点描述符导致上位机打开端点失败。调试阶段不建议一上来就烧EEPROM。可以把固件放在RAM里上电后先用Cypress的USB Control Center下载一次固件功能验证没问题后再烧录到板载EEPROM实现自举。这样固件改一版测一版不用反复插拔EEPROM开发效率高很多。4. FPGA状态机设计与实测带宽4.1 写方向EP2 IN状态机边缘触发加输出寄存FPGA侧最核心的是写状态机把采集数据灌进EP2 FIFO。我贴一段简化但思路完整的Verilog代码module fx2_slave_write #( parameter DATA_WIDTH 16 )( input wire ifclk, input wire rst_n, input wire tx_valid, input wire [DATA_WIDTH-1:0] tx_data, output reg tx_busy, output reg [1:0] fifoadr, output reg [DATA_WIDTH-1:0] fd, output reg slwr, input wire flag_full ); localparam IDLE 2d0; localparam CHECK_FULL 2d1; localparam WRITE_FIFO 2d2; reg [1:0] state; always (posedge ifclk or negedge rst_n) begin if (!rst_n) begin state IDLE; tx_busy 1b0; end else begin case (state) IDLE: begin tx_busy 1b1; if (tx_valid) state CHECK_FULL; end CHECK_FULL: begin // flag_full 低电平表示FIFO满 if (!flag_full) state WRITE_FIFO; end WRITE_FIFO: begin state IDLE; tx_busy 1b0; end default: state IDLE; endcase end end // 所有输出信号在IFCLK下降沿更新保证上升沿采样的建立时间 always (negedge ifclk or negedge rst_n) begin if (!rst_n) begin fd {DATA_WIDTH{1b0}}; slwr 1b1; fifoadr 2b00; // EP2 end else begin if (state WRITE_FIFO) begin fd tx_data; slwr 1b0; // 拉低写选通 end else begin slwr 1b1; // 空闲时保持高电平 end end end endmodule这个状态机的核心思想是先判断满标志不满就进入写周期在下降沿更新FD数据和SLWR所以数据会比上升沿提前半个时钟周期出现在总线上。fifoadr在初始化时固定为2‘b00选中EP2整个传输期间不要动它。4.2 读方向EP6 OUT状态机与总线方向切换读方向比写方向麻烦一点因为FD是双向总线FPGA不能一直驱动。读FIFO时FD由FX2驱动FPGA必须把FD引脚置为高阻然后才能去采样。一个可靠的处理顺序是把fifoadr切到EP62’b10等待一个时钟让FX2内部端点选择生效拉低SLOE再等待一个时钟让FX2把数据驱动到FD总线上拉低SLRD在IFCLK上升沿采样FD每个有效的读周期读取一个16位字不需要读时SLOE和SLRD都拉高并让FD恢复高阻。方向切换是实际项目里最容易出Bug的地方。如果你把EP2的写方向逻辑和EP6的读方向逻辑放在同一个模块里FD引脚会同时被两个逻辑驱动。正确做法是用一个输入输出方向控制信号来做三态控制例如高电平表示FPGA输出FD写方向低电平表示FPGA释放总线读方向。4.3 实测数据带宽瓶颈究竟在哪里我这边板子的实际配置是外部IFCLK 48MHz16位数据总线EP2配置为BULK IN 512字节4重缓冲AUTOIN自动提交。FPGA内部用一个计数器产生测试数据每512字节对齐上位机用CyUSB.NET的异步读接口持续读取64KB缓冲区。实测稳定速度在46~48MB/s之间偶尔能摸到50MB/s。这个数字已经接近USB2.0 Bulk的协议极限。如果你测出来只有20MB/s问题大概率出在以下几个地方FIFO缓冲深度不足1x缓冲根本扛不住主机调度抖动上位机每次读请求太小比如一次读512字节协议开销占了大部分FPGA状态机里插入多余等待周期明明FIFO不满还在等IFCLK频率太低比如用了10MHz总线速率本身就成了瓶颈。还有一个注意点是48MHz × 16位 96MB/s这是FPGA到FX2的总线带宽远大于USB链路的53MB/s所以总线不是瓶颈瓶颈在USB协议层。这个设计冗余是有意为之如果总线速率卡在50MB/s上下一旦FLAG标志的同步延迟导致偶尔停一拍平均带宽就会掉很多留出余量才能“跑满”USB。5. 调试期踩过的坑从带宽上不去到数据错位5.1 带宽上不去的排查链路从FIFO深度到主机侧调度我第一次把这套系统跑起来时带宽只有11MB/s离理论值差了一大截。排查过程很有代表性记录一下链路先用USBlyzer和Bus Hound抓包发现主机每微帧只发出几个Bulk IN事务远没有达到13个。这说明设备端一直在NAK主机的事务也就是端点FIFO经常是空的数据供不上。我立刻怀疑FPGA写状态机在SignalTap里抓FLAG和SLWR结果发现FLAG一直显示不满SLWR也一直在拉低FPGA侧数据确实在持续写入。问题转向固件。重新看EP2CFG发现开发板历程里用的是0x82也就是2x512缓冲而不是4重缓冲。改成0xA0后带宽从11MB/s提升到30多但离40MB/s还有距离。继续抓包看到每微帧能到11~12个事务了最终瓶颈变成了上位机读取节奏。把上位机的读超时调到50ms每次读取缓冲区提到64KB并开了2个异步读的交替提交带宽彻底稳定在46MB/s以上。这个案例说明带宽问题不能用“感觉”去猜要分层FPGA写没写入FIFO → FX2端点缓冲配置够不够深 → USB主机的调度是否被驱动和IO请求拖累。每一层都可能吃掉吞吐而且经常是三层一起拖后腿。5.2 偶发数据错位字节序和跨时钟域亚稳态系统跑起来之后紧接着发现数据偶发错位。采集的数据是16位灰度图像偶而出现整行像素高低字节交换或者连续几百个字节错位但后面又恢复正常。排查下来是三个原因叠加第一是字节序。FX2的16位总线FD[7:0]对应低字节FD[15:8]对应高字节。如果你的上位机按小端解析和我设计的格式不一致就会产生整段数据的高低字节交换。这个不算Bug是协议约定问题但在调试早期很容易误判为时序问题。第二个原因是跨时钟域。FPGA内部采集逻辑的时钟是100MHz而Slave FIFO接口是48MHz的IFCLK时钟域。FLAG满标志来自FX2本质上属于IFCLK时钟域FPGA如果直接用100MHz时钟采样FLAG偶尔会采到亚稳态导致状态机在FIFO满的时候继续写或者还有空间的时候误判为满。正确处理是把FLAG用两级寄存器同步后再用同步器虽然会引入几个时钟周期的延迟但能换来稳定性。我加的同步器是先用100MHz采样两拍再在状态机里使用同步后的信号。代价是判断FIFO满的时机延迟了约3个100MHz周期所以需要靠4重缓冲的余量来兜底。第三个问题是IFCLK本身的相位。如果FPGA内部逻辑用PLL生成的48MHz驱动状态机要确保这个PLL输出和FPGA提供给FX2的物理IFCLK引脚是同源且相位对齐。我是直接把PLL输出一路送到IFCLK引脚另一路作为内部逻辑时钟并且通过时序约束声明了两者之间的时钟关系保证Slave FIFO的建立保持时间在每次综合后都满足。5.3 用Bus Hound抓包定位主机侧调度问题当FPGA和固件侧都查不出问题时就要怀疑主机侧。Bus Hound可以在底层看到每个USB事务的时间戳、状态和数据长度。我遇到过一个诡异现象上位机调用一次读请求总线上一段时间没有任何事务像是“卡住”了。抓包发现主机发出若干Bulk IN事务后返回的全是NAK持续了几个毫秒。这说明FX2端点FIFO确实空了但FPGA明明在一刻不停地写。最终定位到问题出在FPGA和FX2之间的握手我用了FLAGB作为满标志但FLAGB默认配置的端点选择逻辑不对它只在fifoadr选中EP2时才反映EP2的状态而我初始化时fifoadr在EP6上所以FPGA看到的满标志一直是无效状态状态机认为FIFO永远满就不再写数据。改法很简单把FLAGB配置成EP2的满标志并在上电后立即把fifoadr切到EP2。这个坑提醒我FLAG的默认配置和实际工程对不上的概率很高设计前要把FX2里FLAG的配置寄存器FIFOFLAGSB和FIFOPINPOLAR看仔细别想当然。5.4 PKTEND的实战约定固定块长到底有多香前面反复提到固定块长这里把建议说得更具体在我的项目里上位机和FPGA约定每次DMA传输的最小单位是16KB。FPGA端有一个采集缓存满16KB后拉高写使能数据流进入Slave FIFO写状态机AUTOIN每凑满512字节自动发一个包16KB恰好对应32个BULK包。如果最终一帧数据不是16KB整数倍FPGA在最后一次写完后拉低PKTEND一拍把不足512字节的尾包提交出去。这样做的好处是尾包只有数据传输结束那一刻才需要处理绝大多数时间FPGA状态机根本不用关心PKTEND而AUTOIN模式又天然避免了CPU干预。如果你的采集数据本身是均匀连续流不需要分帧那就更省事连PKTEND都可以不接。如果反过来你选择了手动模式AUTOIN0FPGA每次写完都要自己拉PKTEND提交这会多一个状态而且每次提交的是一个USB包还是多个包不好控制带宽也会受一点影响。除非有特殊需求否则我建议优先AUTOIN。这套方案我陆陆续续做了好几年CY7C68013虽然是一颗老芯片但在USB2.0高速传输这个档位上配套资料、工具链和社区经验都非常成熟。真要把带宽跑满关键不在于芯片本身而在于固件缓冲深度、FPGA时序设计、上位机读取策略这三者是协同的。按上面的思路从底层往上逐层验证一般几个小时就能把链路调通。本文还有配套的精品资源点击获取
返回列表