ARTICLE DETAIL

资讯详情

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

FX3 SlaveFIFO调试实战:从硬件到固件的经验总结

FX3 SlaveFIFO调试实战:从硬件到固件的经验总结 这阵子在调试一块采集板主控端用的是Cypress现在应该说Infineon的EZ-USB FX3接口模式选了SlaveFIFO搭档是一颗中等规模FPGA。整套方案说白了就是FPGA采集ADC数据通过FX3的USB 3.0口灌给PC端上位机。整个链路从硬件设计到固件、FPGA逻辑再到上位机读写前前后后折腾了不少时间。过程中踩的坑、翻的车、总结出来的可用经验我觉得比标准文档里写的东西更实在这里整理出来希望对正在做或者准备做FX3 SlaveFIFO调试的朋友有点帮助。先说清楚这篇总结的适用范围。如果你手里是CYUSB3014或者其他FX3系列芯片想把GPIF II接口配置成SlaveFIFO模式和FPGA/单片机/ARM对接那这篇文章几乎每个章节都能对号入座。如果你只是想用FX3自带的USB启动、UART这类功能那SlaveFIFO相关的部分可以跳过但硬件设计和调试思路的部分也能有参考。下面这五千多字我自己写完都觉得有点长但每一节都是实际调试过程里积累出来的不是从数据手册里搬出来的空话。1. SlaveFIFO方案到底是怎么回事1.1 为什么很多高速采集板都选FX3先聊两句选型逻辑不然你也不知道为什么要在FX3上死磕。USB 3.0的5Gbps理论带宽看着很宽但实际可用吞吐率受限于协议开销、端点buffer、驱动栈和硬件实现能稳定跑到350MB/s以上已经算不错了。FX3在这类应用里几乎是绕不开的选择一方面它内置USB 3.0 PHY和协议栈另一方面它那颗ARM9核跑着固件可以灵活配置GPIF II接口去对接各种外部总线。相比用FPGA直接实现USB 3.0协议栈的方案FX3加FPGA的组合开发难度和成本都要低不少。SlaveFIFO这个词听起来抽象拆开就很好理解。Slave代表FX3在总线上是“从”的角色FIFO代表外部主机看到的是两个深度可控的FIFO一个用于上行外设到USB一个用于下行USB到外设。外部主机——也就是FPGA——通过一组并行信号线往里写数据或者从里面读数据FX3负责把FIFO里的数据搬运到USB端点上。外部主机完全不用关心USB协议细节只需要遵守一套类FIFO的读写时序就行。这种架构的最大好处是解耦。FPGA侧不涉及USB协议USB侧不关心数据是怎么生成的两边各自干各自的事。调试时也可以分头定位FPGA这边抓总线波形PC那边看端点数问题出在哪一层很快就能圈定。1.2 数据流里到底有哪些角色整套系统的数据流大概是这样的传感器或者ADC输出的数据进入FPGAFPGA对数据做预处理、缓存然后通过GPIF接口写入FX3的DMA bufferFX3再把buffer里的数据打包成USB bulk传输送上PC。反过来PC下发控制命令或者配置数据时数据会通过USB OUT端点进入FX3FX3写到下行FIFOFPGA再从总线上把数据读走。这里最容易让人晕的是FX3内部的DMA架构。FX3内部有多个DMA通道每个通道连接一个外设接口比如GPIF II和一个USB端点。数据从GPIF II接口进入后会通过DMA自动搬运到USB端点的buffer里。这个过程虽然在固件里只是几个API的事情但它决定了整个链路的带宽上限也决定了调试时你会遇到什么样的问题。我在第四节会详细展开固件配置的细节这里先记住一句话slaveFIFO模式下FPGA看到的FIFO深度和流控状态本质上是由DMA通道的buffer大小和固件里设置的flag阈值决定的。另外FX3的GPIF II接口在设计上支持16位或32位总线宽度常见配置是32位总线配合5位地址线A0~A3加上一些控制位。具体用什么宽度取决于你对吞吐率的要求也取决于FPGA那边的资源开销。32位模式单次读写能传4字节理论上在100MHz时钟下就能跑到400MB/s的峰值实际打八折也有320MB/s对于绝大多数高速采集场景够用了。1.3 什么场景适合、什么场景不适合别把SlaveFIFO当成万金油它也有明显的适用边界。最适合它的场景是“PC和FPGA之间有大量连续数据要双向交换”典型代表就是软件无线电、高速数据采集卡、逻辑分析仪的前端、工业相机采集。这类应用里数据一旦开始传输就自然地持续流动FIFO机制和流控标志能很好匹配。不太适合的场景有两个特征。一是数据量很小但要求极低延迟比如控制类命令走了SlaveFIFO反而被USB bulk传输的调度延迟拖累这种应该用FX3的另外一套机制或者直接换USB HID这类低延迟方式。二是要求FPGA主动发起传输的场景。SlaveFIFO是外部主机主动读写的模式FX3自己不会向外部总线“推”数据虽然有GPIF事件和中断可以通知外部但本质还是靠外部不断轮询flag状态。如果应用链路要求FX3主动上报数据你可能得考虑FX3的ProgMode或者把主从关系反过来。选型阶段把这些边界想清楚比后期在固件和FPGA逻辑里拆东墙补西墙要省力得多。2. 硬件连接与信号完整性设计2.1 关键引脚分配与含义FX3的GPIF II口是一组复用引脚硬件上把它接到FPGA的通用IO上就行。实际项目中我用的信号大致有这些信号名方向FPGA视角作用IFCLK输入GPIF接口时钟可选择内部产生或外部输入SLCS#输出片选拉低表示本设备被选中SLWR#输出写使能上升沿写入数据SLRD#输出读使能上升沿读取数据SLOE#输出输出使能拉低时数据总线由FX3驱动PKTEND#输出包结束信号用于提交短包FLAGA/B/C/D输入FIFO状态标志可配置为可编程水位标志A[1:0]输出地址线选择FIFO或命令/状态寄存器DQ[31:0]双向数据总线这里要特别注意FLAG的极性问题。FX3的FLAG在默认配置下通常是低有效但这不是绝对的——固件里可以通过寄存器配置翻转极性。FPGA侧写逻辑时如果不知道配的是高有效还是低有效调试时就会出现“flag明明说没数据但读出来全是有效数据”或者反过来“flag说有数据读出来全是0”的诡异现象。我建议在项目一开始就约定好统一的极性和时序定义写成一个硬件接口文档两边对照着来能少掉很多无效沟通。还有一个容易忽略的点是PKTEND信号。Bulk传输里PC端read通常按请求长度读取如果FPGA这一侧要传的数据量不是USB bulk包大小比如512字节的整数倍最后那个不满一包的数据就必须靠PKTEND来“封口”。不拉PKTEND的话PC端会一直等数据直到超时表现出来就是上位机读卡死。这个问题几乎每个做SlaveFIFO的人都会遇到一次遇到之后就再也不会忘了。2.2 电平、电源与时钟处理FX3 GPIO的IO电压是可配置的GPIF II接口建议工作在1.8V到3.3V之间。如果FPGA侧的bank电压和FX3不一致就必须加电平转换。不要图省事把两个不同电平的bank直接连在一起轻则信号不稳定重则烧芯片。我调试时FPGA正好有3.3V的bank就干脆配成3.3V电平均省了转换芯片。如果你的FPGA用1.8V bank记得在固件里把GPIO对应的电压配置也改成1.8V否则电平判断会出问题。说到供电就得多啰嗦两句。FX3对电源敏感核心电压1.2V通常由内部LDO产生但如果输入电源纹波大高速USB传输会间歇性出现设备掉线、枚举失败、批量传输返回错误等怪毛病。实测中我发现劣质USB线带来的压降比想象中大得多线一长插上后芯片工作电压可能已经不在规格范围内了。如果调试中遇到“拔插一下又好一阵”的情况先怀疑电源再怀疑固件。时钟方面FX3内部PLL可以产生GPIF接口时钟也可以由外部输入时钟。SlaveFIFO模式下我习惯用FX3内部产生时钟然后在固件里把GPIF时钟频率设好FPGA通过IFCLK引脚直接采样。这样少一个外部时钟源两边只对上频率就行。默认100MHz是常见选择FPGA侧逻辑比较好做时序收敛也不容易因为过高的接口频率导致布局布线困难。有一点很多人忽视如果选择内部时钟模式IFCLK引脚在FPGA侧看到的实际上是一个由FX3输出驱动的时钟FPGA应该把它当成输入时钟用它来同步SLWR/SLRD以及数据总线。不要搞出一个外部晶振再往IFCLK上灌那是另一个时钟模式的做法。2.3 硬件设计里的几个容易翻车的细节第一个是数据总线的方向控制。DQ[31:0]是双向总线FPGA侧如果没有用IOBUF原语正确管理三态就会出现总线争用具体表现是数据时而正确时而错误而且错误模式没什么规律。一定要保证SLOE有效期间FPGA把数据总线释放成高阻SLOE无效时由FPGA驱动这个切换时机要严格对照时序图来写状态机。第二个是引脚分配不冲突。FX3的GPIF II引脚和UART、I2C、SPI、USB启动配置引脚是有部分复用的。如果固件里同时启用了UART打印日志而UART引脚恰好和GPIF的数据线复用那么每次串口打印都会把总线状态搞乱调试时你会看到FLAG状态正常但数据全是垃圾。建议硬件设计阶段就把GPIF专用引脚隔离出来不要和其他外设共用。第三个是信号完整性的粗浅处理。GPIF总线如果走线过长或者经过连接器建议在FPGA侧串联22到33欧姆的电阻抑制过冲。FX3和FPGA之间属于同步并行总线信号边沿太差会直接影响时序裕量。实测中在数据线上串了33欧姆电阻后原来偶发的数据位错误基本消失了。这个属于低成本高收益的操作值得做。3. 固件侧的关键配置3.1 理解GPIF状态机和DMA通道的关系FX3固件里最容易让人困惑的部分就是GPIF配置和DMA通道的关系。GPIF II是一个可编程状态机通过一组称为“GPIF State Machine”的寄存器描述符来定义外部总线上的读写时序。FX3 SDK里带了一个叫做GPIF II Designer的可视化工具你可以在里面画状态跳转图、定义每个状态下信号的电平然后生成C代码。SlaveFIFO模式下固件的工作是把两个层面的东西打通GPIF状态机负责在外部总线上搬运数据DMA通道负责把GPIF拿到的数据送到USB。两者之间通过socket连接GPIF侧是P端口生产端USB侧是U端口消费端。固件里设置DMA通道的buffer大小、数量、传输模式自动还是手动就决定了FIFO的深度和flag跳变的时机。一旦理解了这层关系你就能自己推导很多行为。比如为什么FLAGA可编程标志通常配置成“FIFO可写入”信号在某个时间段内一直无效可能是因为DMA通道里所有buffer都被USB占用了生产者没有空闲buffer可用。这时候你去查USB端点的状态大概率是PC端read请求处理不及时或者根本没发。3.2 固件初始化顺序和关键配置项FX3固件开发基于SDK我用的版本是1.3.3IDE是Eclipse基础上的那个开发环境。初始化的大致顺序是初始化硬件CyU3PDeviceInit配置时钟、IO电压。初始化缓存CyU3PMemInit分配DMA用的buffer。使能USBCyU3PUsbStart注册USB事件回调。加载GPIF配置CyU3PGpifLoadApi加载GPIF II Designer生成的配置。设置GPIF接口CyU3PGpifStart启动状态机设置flag极性等参数。创建DMA通道CyU3PDmaChannelCreate配置buffer数量和大小。启动DMA通道CyU3PDmaChannelSetXfer。连接USB端点保存USB事件回调等在DeviceConnected事件里调用CyU3PDmaChannelSetupBuffers之类的API把DMA通道和端点绑定。这里最关键的参数是DMA buffer的大小和数量。FX3内部有256KB的SRAM可用作buffer。如果每个buffer设成16KB那最多能分配16个。buffer越小flag翻转越频繁FPGA侧等待空位的概率越高buffer越大占用的SRAM越多但连续传输的稳定性更好。实际项目中我用了32个8KB的buffer对于100MB/s级别的持续传输已经很充裕。如果你追求极高吞吐建议buffer数量适当多些每个buffer大小不要小于4KB否则DMA中断和端点切换的开销会吃掉不少有效带宽。固件里另一个容易出问题的点是传输模式。SlaveFIFO模式下通常使用MANUAL模式也就是外部主机每提交一个buffer的数据后FX3才会触发事件把数据送USB。AUTO模式虽然省事但FIFO水位控制和flag信号的行为会和MANUAL不太一样。我调试时曾经为了省事开了AUTO结果发现FLAGA的有效时机和预期完全不同FPGA侧状态机经常等到超时。后来换回MANUAL一切恢复正常。固件代码片段简化CyU3PReturnStatus_t status; CyU3PGpifConfig_t gpifConfig; gpifConfig.interface0 CY_U3P_GPIF_SYNC_MASTER; gpifConfig.clock 100000000; // 100MHz内部时钟 status CyU3PGpifLoadApi(gpifConfig); CyU3PDmaChannelConfig_t dmaConfig; dmaConfig.size 8192; // 8KB per buffer dmaConfig.count 32; // 32 buffers dmaConfig.prodSckId CY_U3P_PIB_SOCKET_0; dmaConfig.consSckId CY_U3P_UIB_SOCKET_0; dmaConfig.prodHeader 0; dmaConfig.prodFooter 0; dmaConfig.consHeader 0; dmaConfig.dmaMode CY_U3P_DMA_MODE_MANUAL; status CyU3PDmaChannelCreate(dmaHandle, CY_U3P_DMA_TYPE_MANUAL, dmaConfig);这段代码里的prodSckId和consSckId是经常配错的地方。上行数据链路FPGA写、USB读的生产端是GPIF接口PIB消费端是USB接口UIB。下行链路则反过来。如果这两个sockeet配反了数据根本就不会往USB端点流但固件还不会报错调试起来相当头疼。3.3 用串口日志辅助固件状态检查固件里加串口打印是非常有效的调试手段。FX3的UART在启动阶段就能用你可以把初始化过程的每一步状态都打印出来看是哪一步返回了失败。固件运行过程中也可以在DMA事件回调里打印一些统计信息比如每秒钟产生了多少个buffer、USB端收到多少byte。串口调试助手这边就能直接看到这些实时日志配合上位机的读写测试结果可以快速定位问题是在固件层、FPGA层还是PC端应用层。我习惯在固件里维护几个全局计数器比如g_upBufferCount上行DMA通道累计提交的buffer数。g_usbBytesSentUSB IN端点累计发送的字节数。g_dmaEventCountDMA事件回调触发的次数。然后在某个定时器里每隔一秒往UART上打印一次。如果FPGA侧一直在写但g_upBufferCount不动说明GPIF接口压根没收到数据问题在FPGA和FX3总线那一层。如果g_upBufferCount在涨但g_usbBytesSent不动说明问题在USB端点和DMA通道的连接上。用串口打印做这类二分定位比瞎猜高效得多。串口调试助手的波特率一般配成115200或者921600都行注意和固件初始化的CyU3PUartSetBaudRate保持一致就行。4. FPGA侧读写状态机设计与实现4.1 写数据通路设计FPGA到PCFPGA侧写状态机的核心任务就是监控FLAGA一旦发现FIFO可写就把数据放到总线上拉一个有效的SLWR脉冲。说起来简单实际设计里有几个细节决定了写通路能不能稳定干活。先说等待机制。SlaveFIFO接口要求SLWR高电平有效期间数据要稳定也就是说数据必须在SLWR变高之前就放到总线上。FPGA里最简单的做法是先等待FLAGA有效然后把数据打到DQ总线上经过一拍延迟后拉高SLWR保持一拍后拉低同时准备下一笔数据。这个过程用Verilog写的话就是典型的有限状态机localparam IDLE 2d0; localparam ASSERT_DATA 2d1; localparam ASSERT_WR 2d2; localparam RELEASE_WR 2d3; always (posedge ifclk or negedge rst_n) begin if (!rst_n) begin state IDLE; slwr_n 1b1; dq_out 32b0; end else begin case (state) IDLE: begin if (flaga 1b0) begin // flag低有效表示可写 dq_out data_from_internal_fifo; state ASSERT_WR; end end ASSERT_WR: begin slwr_n 1b0; // 拉低写使能上升沿写入 state RELEASE_WR; end RELEASE_WR: begin slwr_n 1b1; state IDLE; end default: state IDLE; endcase end end注意上面的代码用了一个假设FLAGA低有效。如果你在固件里把flag配成了高有效这个判断就要反过来。实际调试中我建议把flag的极性约束条件写在一个宏定义里万一后面改配置FPGA侧只改一个宏就行。第二个关键点是连续写和burst操作。如果你每一拍数据之间都插入IDLE等待FLAGA的判断那么有效带宽会损失不少。更好的做法是检测到FLAGA有效后连续写若干个数据比如4个或者8个然后再回IDLE重新检查flag。这个连续长度要跟DMA buffer的大小匹配别超过水线太多否则一次写太多直接把FIFO写穿。实测中我设了每次burst写16拍也就是64字节然后再查一次flag吞吐率比一拍一查高了30%以上。第三个关键点是短包提交。FPGA内部的数据通常按帧或者按包组织最后一包数据经常不是512的整数倍。这种情况下写完最后一个数据之后需要拉低PKTEND一拍告诉FX3“这个数据包结束”。不这么做PC端的read操作会一直等待直到超时如果你的上位机没有设计超时重试整个采集流程就会卡死。对付这个问题我的习惯是数据包逻辑层里专门加一个“包结束”控制位FPGA写状态机一看到这位置1就在写完本次数据后自动产生一个PKTEND脉冲。4.2 读数据通路设计PC到FPGA读通路是PC下发数据到FPGA的方向过程跟写通路镜像对称但有个额外的信号要处理好SLOE。SLOE拉低时FX3的数据总线被驱动出来FPGA才能读到有效数据。SLOE拉高时总线是高阻FPGA必须把总线释放掉否则会有总线冲突的风险。读状态机的主要流程是等待FLAGB配置成“FIFO非空”或者“可读标志”一旦有效拉低SLOE和SLRD在一个时钟边沿把数据采进来然后释放SLRD再判断下一笔是否继续。SLOE在burst读的过程中可以一直保持低直到本次burst结束再拉高这样省去每次读都重新开SLOE的时间。这里有一个经常踩的坑SLOE和SLRD的时序配合。FX3数据手册上有明确要求SLOE拉低到数据稳定需要一定的建立时间SLRD上升沿采样数据时要保证数据已经有效。FPGA逻辑上如果你用同一个always块同时控制SLOE和SLRD很容易出现SLOE和SLRD同时变化导致采样到的数据是总线还没稳定的状态。我的解决办法是让SLOE比SLRD提前一拍动作也就是在进入READ状态时先拉低SLOE等到下一拍再拉低SLRD去采样。这样虽然损失了一点吞吐率但数据稳定性大幅改善。读方向的数据缓冲同样重要。FPGA从GPIF总线上拿到的数据是连续流但内部模块比如配置寄存器组往往需要按字节使能、按命令字解析。我一般在读通路后面挂一个小FIFO先把GPIF总线数据缓存下来然后由内部逻辑按自己节奏处理。这个小FIFO同时起到跨时钟域隔离的作用因为GPIF时钟和FPGA内部逻辑时钟不一定同源贸然直接处理会引入亚稳态和时序收敛问题。4.3 跨时钟域与缓冲设计要点如果你FPGA内部逻辑跑的是另一个时钟比如200MHz而GPIF接口工作在100MHz那两边数据交互就涉及跨时钟域。最稳妥的方式就是FIFO。上行方向内部模块把数据写入异步FIFOGPIF写状态机从FIFO读数据再搬到FX3。下行方向则是GPIF读状态机把数据写入另一个FIFO内部模块自己读走。异步FIFO设计在FPGA工程里是基本功很多IP核可以直接生成不要自己手搓除非你想在高深的时序问题上多花几个通宵。选FIFO深度时至少要大于GPIF最多连续读写的burst长度我一般设成1KB到4KB具体看应用。如果FIFO深度太小GPIF状态机会频繁被“空”或“满”信号卡住吞吐率上不去太大又浪费FPGA内部的block RAM资源所以要平衡。跨时钟域还有一个容易被忽略的点状态标志本身的同步。FLAG信号从FX3过来相对于IFCLK到底是同源还是不同源不同项目不一样。如果FLAG不是由IFCLK直接产生的最好在FPGA里用两级触发器同步后再进状态机。“打两拍”虽然简单但确实能避免很多随机性的逻辑错误——省掉它会出现的故障往往特别难排查因为错误出现频率低、时机无规律。5. 调试过程中的问题速查与避坑思路5.1 枚举正常但数据不流动怎么办这个现象是SlaveFIFO调试中出现频率最高的设备能够在PC端正常枚举设备管理器里能看得到或者Cypress的USB Control Center能识别芯片但上位机read或write的时候数据完全不动或者返回超时。按照排查优先级我会依次做下面几件事看一下固件串口日志确认DMA通道是否真的创建并启动了。如果CyU3PDmaChannelCreate返回成功但CyU3PDmaChannelSetXfer没被调用那DMA通道处于未启动状态flag信号根本不会正常翻转。Socket号和端点号是否匹配也在这里一并确认。检查flag极性和FPGA测的状态机判断是否一致。用逻辑分析仪直接抓FLAGA和FLAGB的波形如果在启动DMA通道之后flag根本没变化那大概率是固件配置问题如果flag在变化但跟预期方向相反那就是极性配反了。把DMA数据通路打一个回环测试。最简单的方法是让固件不依赖GPIF而是用一个CyU3PDmaChannelWrite之类的API向DMA通道里塞测试数据然后PC端去read。如果能read到数据说明USB端点和DMA通道没问题问题锁定在GPIF接口和FPGA逻辑。如果read不到那就要回头查DMA通道配置和端点连接。检查PC端读写API的缓冲区和请求长度是不是合理。有些上位机程序里用了一个很小的buffer去read大量数据比如每次只读4KB然后在上位机循环里拼命调用read这种用法的表现是数据传输极慢但没完全卡死跟完全无数据还是有区别的。如果连read返回值都是0或者错误那再往下查协议栈层。5.2 数据错位、丢数与时序问题数据出现错位或者偶发丢数是最让人头疼的问题因为复现不稳定、定位困难。这类问题通常有两种根源。第一种是时序边沿采样问题。GPIF总线上数据、控制信号、时钟之间的关系非常严格比如SLWR上升沿采样数据数据必须在建立时间之前就有效。如果你的FPGA状态机把数据信号和SLWR同一拍给出那么实际时序上可能就差了一点点建立时间综合布线后在这个频率下可能刚好能满足要求但温度、电压一变就出问题。解决方法是把数据比控制信号早半拍到一拍给出给时序多留裕量。我在4.1的代码里就是先打数据再拉写使能就是这个道理。第二种是总线三态控制问题。FPGA代码里如果没有正确管理双向IO的高阻态在SLOE有效期间FPGA还在驱动数据总线就可能和FX3的输出打架造成数据位错误。这个问题最典型的特征是直接读FLAG正常、单独看某个信号正常、一旦真正跑burst传输数据就开始花。解决办法是检查IOBUF的使用确保SLOE有效期间FPGA数据总线是高阻只有SLOE无效时FPGA才驱动数据。第三种是DMA buffer大小和burst长度不匹配。FPGA侧burst了一次很大的数据量超出了单个DMA buffer容量而FX3在MANUAL模式下可能会等buffer满了才送USB导致FPGA侧认为FIFO已满而停止写入而PC端迟迟等不到数据。这种表现既有丢数又有卡顿。解决思路是把FPGA侧burst长度控制在一个buffer容量之内比如buffer 8KB就每次burst不超过8KB的数据量或者干脆把burst长度减到256字节配合flag可编程水线反而更稳定。5.3 吞吐率上不去的调优手段如果数据能通、看起来也能读写但速度就是达不到预期比如理论能跑350MB/s实测只有150MB/s那就要逐个排查瓶颈。先说FPGA侧。写状态机如果每次写一拍都要回到IDLE重新检测flag那等待flag变化的周期会占用大量时钟吞吐率自然上不去。解决办法就是改成burst写每次连续写8到16拍减少flag轮询的频率。另外GPIF接口时钟能不能提高也很关键如果固件配置里时钟只有50MHz那无论FPGA多努力都只能在50MHz下传输可以考虑改到100MHz。再看固件侧。DMA buffer小会导致中断频繁CPU忙于处理DMA事件而无法及时提交新的buffer。在MANUAL模式每个buffer满了之后CPU需要处理事件处理时间没优化好的话吞吐率会被严重拉低。我试过把buffer从4KB增到16KB后上行吞吐率从180MB/s提升到了280MB/s效果立竿见影。如果你不想频繁在CPU事件回调里做太多事情可以考虑适当增大buffer数量并减少回调里的日志操作。最后说PC端。Cypress提供了CyUSB.NET驱动库上位机read请求的buffer如果太小比如每次只申请4KB协议栈里每处理一次都需要切换内核态和用户态开销非常大。我建议每次read至少申请1MB然后用一个循环里多次读取的方式填充应用层缓冲。同样的逻辑也适用于write。还有一个容易被忽略的点是Bulk传输模式下USB控制器下发请求后必须等端点有数据或者超时才返回如果你用同步read去读一个大包而数据源只是每秒才发几个小包那大量的时间会浪费在等待超时上。这种情况适合用异步或者多线程方式做读操作。5.4 调试工具组合拳逻辑分析仪、串口、上位机三管齐下最后说说调试工具的使用思路这部分的经验对“快速定位问题”帮助最大比盲目改代码高效太多了。逻辑分析仪建议买一个采样率至少200MHz、通道数16路以上的不用太贵但一定得支持长时间深存储采集。调试时把IFCLK、SLWR、SLRD、SLOE、FLAGA、FLAGB、PKTEND、数据总线低位几根信号都挂上然后触发条件设成SLWR下降沿或者FLAG变化。一帧波形拉出来你就能看到FPGA到底有没有按照时序去读写也能看到FLAG和实际读写之间差了多长时间。很多固定时序错误靠波形一眼就能发现比如SLWR脉冲宽度不够、数据建立时间太紧、burst中间有不该有的空闲状态等。串口日志这块前面讲了固件里加计数器、打印状态是整个系统里最灵活的观测点。因为FPGA波形只能看到GPIF总线层面USB端点的状态它看不到这时候就得靠固件日志来补充。我习惯把固件的UART日志做成开关式的平时关闭需要调试时打开避免日志打印本身干扰DMA传输性能。上位机端建议自己写一个简单的吞吐率测试工具用CyUSB.NET的CyUSBDevice类枚举设备找到对应的bulk端点循环收发每秒钟打印一次MB/s。别只用Cypress自带的USB Control Center那个工具适合做控制传输和简单读写测试但它的吞吐率统计粒度太粗而且不支持灵活的buffer大小配置。自己写一个小工具几十行C#代码就够了但对抗这种性能问题的帮助非常大。另外调试时手边放一个串口调试助手特别有用尤其当你的PC上位机还没开发完或者你想快速验证固件某个模块逻辑时直接用串口助手发命令、收统计数据完全不依赖主上位机程序。很多时候就是靠这个“土办法”先把固件到DMA到USB这一整段验证通了再回头完善应用层。最后再说一点个人感受这套方案调试下来最大的体会是SlaveFIFO本身不复杂复杂的是它横跨FPGA、固件、USB驱动、上位机四个层面每一个层面都可能出问题而且问题表象经常互相掩盖。调试时如果只是盯着某一层反复折腾效率会很低。正确心态是把整条链路拆成几个可独立验证的段逐段打通。先用固件回环测试排除USB段再用逻辑分析仪确认GPIF时序最后才把FPGA内部逻辑挂进来全链路联调。每一步都确认清楚了再做下一步就不会出现那种“改了一堆东西但问题还在”的绝望局面。我早期犯过的最大错误是FPGA侧burst长度和FX3的buffer大小没对齐导致数据在低速时正常、高速时偶发丢包整整排了两天才通过波形加固件日志的组合锁到原因。所以这个项目做完之后我特意把这种跨层参数匹配的检查列成了checklist每次新项目启动时先过一遍省了非常多的时间。希望这篇总结也能帮你少走一段弯路。最后再分享一个可以扩展的方向如果你后续需要支持多通道并行采集或者数据率超过GPIF接口带宽可以考虑把FX3配置成两个DMA通道对接到USB的多个Bulk端点配合FPGA侧的多路FIFO调度。吞吐率能再上一个台阶不过那就是另一个debug故事了。先把SlaveFIFO这条基本功练好很多高级玩法都是在这个基础上叠加的。
返回列表