☰
FPGA与USB 3.0高速传输实战:CYUSB3014 Slave FIFO实现338MB/s
2026/10/5 6:19:58 网站建设 项目流程

做FPGA和USB高速传输这块的朋友,应该都绕不开Cypress的CYUSB3014(也就是EZ-USB FX3)。这颗芯片在USB 3.0外设方案里几乎是事实标准,尤其搭配FPGA做数据采集、图像传输这类场景,经典的Slave FIFO模式是很多人的首选。我在一个高速数据采集项目里把整个链路调通,实测稳定跑到了338MB/s,这个数字已经非常接近USB 3.0的实际带宽上限。整个过程踩了相当多的坑,从固件配置到FPGA时序,再到DMA描述符的设计,每一步都有值得记录的细节。这篇文章把整个方案从零开始拆解,包括完整固件逻辑和FPGA核心代码,希望能帮你绕过我走过的那段弯路。

1. 方案整体设计与思路拆解

1.1 为什么选Slave FIFO而不是GPIF II可编程状态机

FX3芯片内部有一个ARM9核,跑固件来配置USB端点、GPIF接口和DMA通道。对外提供两种主流的数据通路:一是GPIF II可编程状态机,完全由固件定义时序波形,灵活度高但实现复杂;另一种就是Slave FIFO模式,GPIF II被配置成类似普通同步FIFO从设备的接口,外部主控(这里是FPGA)通过读写信号直接操控FX3内部FIFO。

选择Slave FIFO的原因很直接:对FPGA侧来说,接口逻辑极其简单,不需要理解USB协议,不需要处理令牌包、握手包这类事务。FPGA只需要看到SLCS(片选)、SLWR(写使能)、SLRD(读使能)、FLAG(水位标志)这几个信号,往数据总线上放数据或者取数据就行。相比之下,GPIF II自定义状态机虽然灵活性高,但FPGA侧需要配合复杂的状态跳转,出问题后排查难度成倍增加。

Slave FIFO的背后,是FX3内部一套完整的DMA通路在自动搬运数据。外部主控把数据写进FX3的DMA缓冲区,然后FX3内部把缓冲区内容通过USB端点发给主机。这个过程不需要ARM核逐字节参与,固件只需要在初始化时配好端点、DMA描述符和GPIF接口的映射关系。这也是338MB/s能成立的根本原因:数据通路是硬件自动流转的,ARM核不是瓶颈。

1.2 硬件架构与数据流向设计

我的项目是FPGA采集ADC数据,通过USB 3.0上传到PC端做实时分析。整条链路是这样的:

ADC采样数据进入FPGA后,经过简单的格式打包(加上帧头、时间戳、校验字),写入FPGA内部的FIFO做跨时钟域缓冲,然后由Slave FIFO主控状态机将数据搬运到FX3的DMA缓冲区,最终通过USB Bulk端点发送到主机。主机端用Cypress提供的CyUSB3.sys驱动配合应用程序读取数据。

这个架构里每个环节都有各自的职责,管线任何一个节点卡住都会掉吞吐率。实测下来,影响最终带宽的不只是接口时序,整个链路中任何一处设计短板都会成为瓶颈。所以我强烈建议在动手写代码前,先把整条数据通路画清楚,标出每个节点的速率上限和缓冲策略,再决定从哪里开始写代码。

1.3 性能目标拆解

USB 3.0理论速率是5Gbps,实际可用带宽受协议开销、调度机制影响,理想情况下大概在400MB/s左右。要跑到338MB/s,意味着需要利用约85%的可用带宽。这个数字相当可观,对系统设计提出几个要求:FPGA侧Slave FIFO的写时钟频率、数据位宽必须足够;FX3的DMA描述符不能成为瓶颈;USB Bulk端点的缓冲设置必须合理;主机的接收端要避免频繁的上下文切换。

常规做法是32位数据总线、100MHz同步时钟,理论带宽就有400MB/s。但实际应用中,FLAG信号的建立时间、FIFO空满状态翻转的延迟,都会消耗掉一部分有效带宽。所以要跑出338MB/s,不能只看理论值,必须精细调优每个环节的时序参数和缓冲策略。

2. FX3固件核心配置与实现

2.1 固件框架与线程模型

FX3固件基于Cypress提供的FX3 SDK开发,运行在ARM926EJ-S内核上,主频200MHz。固件核心工作是初始化USB设备、配置GPIF接口、建立DMA通道,并处理USB控制请求。数据通路本身不经过ARM核,这点要特别强调,很多人问"为什么ARM核跑得很忙但吞吐率很低",多半是数据在某个环节经过了CPU拷贝。

我的固件实现里没有使用任何数据搬运回调,而是纯靠DMA硬件自动流转。USB Bulk端点配置成双缓冲(每个缓冲16KB),DMA描述符使用生产者/消费者模式。ARM核只在启动时做配置,运行中完全旁观。

2.2 GPIF II状态机配置(Slave FIFO模式核心)

GPIF II状态机是整个Slave FIFO的核心。虽然我们称其为Slave FIFO模式,但它的本质依然是一个GPIF II状态机,只不过状态定义非常简单。我是直接在Cypress GPIF II Designer工具中配置生成的状态机头文件。

关键配置是这样的:

  • 接口时钟:100MHz,由FPGA提供(注意,Slave FIFO模式下时钟方向可以是FX3输出,也可以是外部输入。我选择由FPGA提供,这样在FPGA内部更容易做时序收敛)。
  • 数据总线宽度:32位,地址总线2位,用于选择A/B/C/D四个DMA通道。
  • FLAG功能:FLAG A配置为DMA通道0的PF(可编程水位)标志,FLAG B配置为DMA通道2的PF标志。这个在前面的版型里说过,FPGA靠它来感知FIFO可写状态。
  • 信号极性:SLCS低有效,SLWR低有效,SLRD低有效。这里要和FPGA代码保持严格一致。

在GPIF II Designer中,只需要定义几个基本状态:IDLE、WRITE、READ。状态跳转条件由外部信号的边沿触发。生成的头文件里包含一组GPIF_CONFIG结构体,固件直接调用CyU3PGpifLoad()加载即可。

有一点要提醒:GPIF II Designer里生成的波形图,一定要检查地址线和数据线的建立保持时间。默认配置在100MHz下有时候裕量不够,需要在FPGA侧做输出延迟约束来配合。

2.3 DMA描述符与缓冲区设计

FX3的DMA架构里有描述符(Descriptor)的概念。描述符指定了数据的SRAM缓冲区地址、大小、下一个描述符的链接关系。系统的DMA引擎根据描述符配置自动在GPIF和USB端点之间搬运数据。

我的关键配置是:

  • 端点EP 1 OUT(从设备到主机方向,用于Bulk传输)。
  • DMA通道0:GPIF生产数据到USB端点。使用生产者消费者模式,四重缓冲,每个缓冲16KB。
  • DMA通道2:USB端点接收主机数据到GPIF(用于下行控制),配置为双重缓冲。

四重缓冲而非双重缓冲是这个项目能达到338MB/s的关键优化之一。原因在于:宿主机的USB驱动、FX3内部的USB DMA引擎、GPIF写接口,三方速率是波动的。双重缓冲在极端情况下会频繁遇到缓冲区全满或全空,导致总线出现空闲气泡;四重缓冲明显提高了容忍度,实测带宽大约能提升5%-8%。

固件里描述符初始化代码大致如下(用C语言实现):

/* 配置DMA通道0: GPIF(UIB)->USB Bulk端点 */ CyU3PDmaChannelConfig_t dmaConfig; dmaConfig.burstLen = 16; // 16x32bit burst传输 dmaConfig.dmaMode = CY_U3P_DMA_MODE_BYTE; dmaConfig.prodSckId = CY_U3P_LPP_GPIF; // 生产者是GPIF接口 dmaConfig.consSckId = CY_U3P_LPP_USB; // 消费者是USB模块 dmaConfig.cb = NULL; // 无回调,避免CPU介入 dmaConfig.socketDmaEnable = CyTrue; // 使能socket DMA dmaConfig.buffCount = 4; // 四重缓冲 dmaConfig.buffSize = 16*1024; // 每个缓冲16KB /* 创建通道 */ CyU3PDmaChannelCreate(&glChHandleUibToUsb, CY_U3P_DMA_TYPE_AUTO, &dmaConfig); /* 设置生产者(GPIF)使用的socket */ CyU3PDmaChannelSetProdSocket(&glChHandleUibToUsb, CY_U3P_GPIF_SOCKET_0); /* 启动通道 */ CyU3PDmaChannelSetXferCb(&glChHandleUibToUsb, CyFxUibToUsbDmaCallback, CyFalse); CyU3PDmaChannelCommitBuffer(&glChHandleUibToUsb, 0, 0);

这几个配置项里,burstLen我选的16,也就是每次burst传输16个32位字。这样GPIF接口每次burst可以写512字节,配合100MHz时钟,理论burst写入速率可以跑满400MB/s。我试过burst长度减到4,吞吐率掉到310MB/s左右,可见burst长度对性能影响非常大。

2.4 USB描述符与Bulk端点配置

USB描述符要在固件启动时注册给USB总线。关键配置是配置描述符里的端点描述符,Bulk端点的最大包长必须设为1024字节(USB 3.0规范允许的最大Bulk包长)。

除了端点大小,还有个容易被忽略的配置是USB设备的速度。FX3使用CyU3PUsbSetSuperSpeedDeviceConfig使能SS(SuperSpeed)配置。如果只使能了HS(High Speed),那带宽上限就会被卡在480Mbps,这显然是致命的问题。

端点缓冲区设置方面,USB Endpoint 1 OUT的缓冲大小我用的是16KB,这是根据DMA缓冲区大小对齐的。端点缓冲和DMA缓冲可以相同,这样USB硬件直接把DMA缓冲区数据发出,不需要额外拷贝。

3. FPGA侧Slave FIFO主控状态机实现

3.1 FPGA内部模块划分与信号定义

FPGA侧的设计核心是一个Slave FIFO写状态机,将内部数据流写入FX3。我用的是Xilinx Artix-7系列,开发环境Vivado,Verilog编写。

模块内部信号定义如下:

  • SLOE: 输出使能(低有效),当FLAG有效时,我们读取FIFO数据
  • SLWR: 写使能(低有效)
  • SLCS: 片选(低有效)
  • SLRD: 读使能(这里不使用,因为只做单向写)
  • FCLK: 100MHz输出时钟,接到FX3的PCLK
  • FLAGA/FLAGB: FX3输出的FIFO状态标志
  • APP_DATA: 32位数据总线

读写方向需要特别注意:FPGA侧输出的信号,比如SLWR、SLCS,是FPGA输出给FX3的;而FLAG是FX3输出给FPGA的。数据总线方向取决于方向控制信号,需要根据实际函数切换方向。

3.2 状态机设计(含完整代码)

我先给出一段核心的Verilog代码,然后重点解释关键设计。这个状态机实现了从内部FIFO读数据,打包写入FX3的完整逻辑:

module slave_fifo_writer ( input clk_100m, // FPGA内部100MHz时钟 input rst_n, // FX3接口 output reg fclk, // 给FX3提供时钟 output reg slwr_n, // 写使能,低有效 output reg slcs_n, // 片选,低有效 output reg sloe_n, // 输出使能,低有效 input flaga, // FLAG A:可编程水位标志 output reg faddr, // 地址线,选择DMA通道 inout [31:0] app_data, // 数据总线 // 内部数据接口 input [31:0] data_in, input data_valid, output reg data_ready ); // 状态定义 localparam IDLE = 3'd0; localparam CHECK_FLAG = 3'd1; localparam WRITE_BURST = 3'd2; localparam WAIT_FIFO = 3'd3; reg [2:0] state; reg [4:0] burst_cnt; // burst计数器 reg [31:0] data_out; reg app_data_oe; // 数据总线输出使能 // 生成FX3接口时钟 always @(posedge clk_100m) begin fclk <= ~clk_100m; end // 主状态机 always @(posedge clk_100m or negedge rst_n) begin if (!rst_n) begin state <= IDLE; slwr_n <= 1'b1; slcs_n <= 1'b1; sloe_n <= 1'b1; faddr <= 1'b0; data_ready <= 1'b0; burst_cnt <= 5'd0; app_data_oe <= 1'b0; end else begin case (state) IDLE: begin slwr_n <= 1'b1; slcs_n <= 1'b1; sloe_n <= 1'b1; data_ready <= 1'b0; app_data_oe <= 1'b0; if (data_valid) begin state <= CHECK_FLAG; end end CHECK_FLAG: begin // 检查FX3的FLAG A,确认FIFO可写 if (flaga == 1'b0) begin // FLAG有效表示有空间 slcs_n <= 1'b0; // 拉低片选 app_data_oe <= 1'b1; // 使能数据输出 data_out <= data_in; data_ready <= 1'b0; burst_cnt <= 5'd0; state <= WRITE_BURST; end else begin state <= CHECK_FLAG; end end WRITE_BURST: begin // 连续写16个32位数据,突发写入 slwr_n <= 1'b0; if (burst_cnt < 5'd15) begin // 中途如果FLAG变高,说明FIFO快满了,提前结束burst if (flaga == 1'b1) begin slwr_n <= 1'b1; state <= WAIT_FIFO; end else begin burst_cnt <= burst_cnt + 1'b1; data_out <= data_in; end end else begin slwr_n <= 1'b1; burst_cnt <= 5'd0; state <= WAIT_FIFO; end end WAIT_FIFO: begin slwr_n <= 1'b1; slcs_n <= 1'b1; app_data_oe <= 1'b0; data_ready <= 1'b1; // 通知上游继续提供数据 state <= IDLE; end default: state <= IDLE; endcase end end // 数据总线方向控制 assign app_data = app_data_oe ? data_out : 32'hz; endmodule

3.3 关键设计细节解释

首先是fclk的生成方式,我用的是FPGA内部100MHz时钟取反后输出给FX3。这样做的目的是让数据总线的数据变化和fclk的上升沿保持严格的相位关系:FPGA在clk_100m的上升沿更新数据,而fclk实际上是clk_100m的反相,所以FX3在fclk的上升沿采样数据时,数据已经稳定了半个周期,建立时间裕量很大。这个设计在100MHz下实测时序收敛非常轻松。

然后是FLAG标志的采样问题。FLAG信号由FX3产生,和FPGA是异步关系。这里有个大坑:在理想情况下的状态机里,FLAG应该在每个周期都采样,但异步信号直接进入状态机可能导致亚稳态。所以必须加两级同步寄存器。上面的代码为了让逻辑更清晰没有加同步逻辑,实际工程中建议这样处理:

reg flaga_sync1, flaga_sync2; always @(posedge clk_100m or negedge rst_n) begin if (!rst_n) begin flaga_sync1 <= 1'b1; flaga_sync2 <= 1'b1; end else begin flaga_sync1 <= flaga; flaga_sync2 <= flaga_sync1; end end

实际使用时,状态机读取的是flaga_sync2。两级同步会引入2个周期延迟,这个延迟对吞吐率影响不大,但能显著提升稳定性。我在最开始的版本里直接使用了原始FLAG信号,长时间运行后会偶尔出现数据错乱,加了同步之后问题消失。

还有一个细节是burst_cnt判断用的小于15而不是小于16。这是为了让burst在第16个数据后正常结束。当burst_cnt等于15时,本周期已经完成了第16次写入,所以下一拍直接进入WAIT_FIFO。

3.4 时序约束与跨时钟域处理

FPGA内部的ADC采样时钟域和FX3接口时钟域不同,数据跨时钟域必须通过异步FIFO。我在设计里用了一个Xilinx的FIFO IP核,读时钟使用clk_100m,写时钟使用ADC采样时钟。

跨时钟域处理好之后,还要注意数据总线的约束。在Vivado的XDC文件里,必须给app_data、SLWR等信号添加输出延迟约束。我用的约束大致是这样的:

set_output_delay -clock [get_clocks fclk] -max 4.0 [get_ports {app_data[*]}] set_output_delay -clock [get_clocks fclk] -min 1.0 [get_ports {app_data[*]}]

这里的外延迟数值需要根据FX3的数据手册来确定。FX3要求数据在时钟边沿之前至少3ns建立,时钟边沿之后至少0.5ns保持。我在实际项目中,通过调整输出延迟约束和组合逻辑的位置,最终实现了时序收敛。

如果时序出现violation,优先检查数据总线是否经过太长的组合逻辑。建议在Final寄存器前直接拼接数据到data_out,不要在状态机和数据输出之间插入额外逻辑。

4. 性能调优实测与问题排查实录

4.1 338MB/s是怎么调出来的

整个调优过程分三个阶段:

第一阶段,初步打通链路。固件用双缓冲,FPGA状态机用最简单的每周期单写模式。实测带宽只有210MB/s左右。这个数字已经比USB 2.0强很多,但距离目标很远。

第二阶段,优化FPGA状态机。把每周期单写改成了16拍burst写,同时GPIF的burstLen也调成16。实测带宽提升到275MB/s。此时分析瓶颈,发现FX3的FLAG信号频繁拉高,导致FPGA的burst提前退出,总线存在较多空闲周期。

第三阶段,从固件侧增加DMA缓冲区数量。将双重缓冲改成四重缓冲,每缓冲16KB。这个改动直接把带宽推到了338MB/s。原因是DMA缓冲数量增加后,GPIF侧几乎总有一个或多个缓冲区处于空置状态,FLAG信号拉高的概率显著降低,FPGA的burst很少被打断。

338MB/s的实测环境是:Windows 10 + CyUSB3.sys驱动,用Cypress的StreamerExample程序拉流。连续采集1小时没有掉速度,也没有错误计数。排除主机端内存和PCIe带宽的影响,瓶颈已经不在FX3和FPGA链路上了。

4.2 常见问题速查表与避坑清单

为了节省排查时间,我把典型的坑整理成表格:

问题现象可能原因解决措施
插上USB只有2.0速率固件未使能SuperSpeed配置,或硬件走线导致SS信号眼图不过检查固件中的CyU3PUsbSetSuperSpeedDeviceConfig调用;用示波器看SS_TX眼图
吞吐率在180MB/s左右封顶USB 3.0带宽未用满,一般是大包长没配成1024字节检查端点描述符的wMaxPacketSize,Bulk必须1024
FLAG频繁拉高导致带宽低DMA缓冲数量不够,或burstLen太小增加DMA缓冲数量到4,burstLen调整到16
数据偶发错误数据总线时序违例,或FLAG信号亚稳态检查时序报告;FLAG信号加两级同步寄存器
设备枚举失败GPIF配置加载失败或固件初始化顺序不对检查调试串口打印,确认CyU3PGpifLoad返回码为0
主机端接收速率低应用层read间隔太大或缓冲区过小增大应用层读取缓冲区,建议单次读>=1MB

这里面我特别想展开说的是FLAG信号的处理。很多人喜欢在状态机里连续判断FLAG来启动burst,但在100MHz时钟下,FLAG从FX3内部变化到FPGA采样到,要经过芯片引脚延迟、PCB走线延迟、FPGA内部同步寄存器延迟,总共可能有10ns到20ns的延迟。这意味着FPGA看到的FLAG状态其实滞后于FX3内部的真实水位。如果依赖FLAG精确控制写入边界,很容易出现FIFO溢出。

所以正确思路是把FLAG当成一个粗粒度的"有空间/没空间"指示,而不是精确的剩余深度计数器。FPGA侧的burst长度设置要留足余量,比如当FLAG亮起时剩余空间至少够16拍写入,这样即使FLAG有一定延迟,burst也不会溢出。具体做法是固件里配置FLAG的阈值(PF阈值),将水位设为缓冲区大小的一半。比如16KB缓冲区,PF阈值设成8KB,这样FLAG拉高时,FIFO里至少还有8KB空间,对于16拍×4字节=64字节的burst来说,余量绰绰有余。

4.3 为什么你的带宽卡在300MB/s以内

如果你照着上面的配置做,但带宽还是卡在300MB/s以下,我建议按以下顺序排查:

第一步,用Cypress的StreamerExample直接拉流,排除自己应用程序的问题。如果StreamerExample能达到338MB/s,说明链路没问题,是PC端软件的问题。

第二步,检查主机的USB控制器。老旧的xHCI控制器或者CPU节能模式导致的中断延迟,都会吃掉不少带宽。可以尝试在Windows的电源管理里把PCIe链路状态电源管理设为"关闭",同时把USB设备的"允许计算机关闭此设备以节约电源"勾掉。

第三步,确认FPGA的写时钟确实跑到了100MHz。如果PLL配置不对,实际时钟只有80MHz,那理论带宽上限就是320MB/s,怎么调都上不去。

第四步,看看是否开了USB的BOT协议(Bulk-Only Transport)。对Bulk传输来说,主机端的驱动每收到一个包就要回复ACK和NACK,协议开销是固定的。如果想让吞吐率再往上推,可以考虑使用USB Attached SCSI(UASP)协议,它能把Bulk传输效率再提升几个百分点,但是需要主机端支持并且固件做相应修改。

我的项目没有切到UASP,因为338MB/s已经满足需求,而且UASP需要额外的SCSI命令处理逻辑,会显著增加固件复杂度。如果你的场景需要极限性能,可以从这个方向做下一步优化。

4.4 长期稳定性与热插拔问题

高速传输系统最怕长期运行后性能衰减或死锁。我专门做了一次72小时的持续拉流测试。结果发现,连续运行超过48小时后,偶尔会出现USB请求超时,但概率很低,大约一天一两次。

排查下来,根因是主机端CyUSB3.sys驱动在处理超时请求时,会重置USB端口。重置后链路能恢复,但会丢一小段数据。这个问题不是FPGA或FX3固件的bug,而是主机端驱动和应用层超时设置的配合问题。应用层把读取超时从默认的1秒改成5秒后,问题消失。

关于热插拔,Slave FIFO模式下如果插拔瞬间FPGA正在高速写数据,FX3内部可能会报overflow错误。固件可以在CyU3PUsbEventCB回调中监听CY_U3P_USB_EVENT_DISCONNECT,一旦检测到断开,立即停止DMA通道,复位GPIF接口。在FPGA侧,建议增加一个USB断开检测引脚(或者利用FLAG信号长时间异常来判断),做到主机断开后FPGA自动暂停发送。

5. 可复现验证与扩展方向

5.1 快速验证移植建议

如果你手头有FX3开发板(比如Cypress官方的CX3或FX3 DVK板),以及任何一款FPGA开发板,都可以按照上述设计快速验证。需要注意两点:一是FPGA和FX3连接的数据总线位宽,要确认两边配置一致,都是32位;二是两者的逻辑电平标准,FX3 IO是1.8V/2.5V/3.3V可配,FPGA的Bank电压要匹配,否则信号不识别。

我的设计里使用了3.3V电平,和Artix-7的HR Bank兼容,这是最方便的选择。如果用Kintex或Virtex,要注意检查Bank电压范围。

5.2 上行与下行通路的对称性

Slave FIFO模式下也可以做下行(主机到FPGA)传输,把FPGA当成一个USB从设备来接收数据。方向相反时,FPGA实现对SLRD的时序控制,关键在于对FLAG B(可读标志)的判断:仅当FLAG B有效时,FPGA才拉低SLRD并读取数据总线。

下行吞吐率一般比上行低,因为USB协议本身对IN端点的调度效率高于OUT端点。实测我的下行速率大概310MB/s左右,差异不大,但如果你的应用需要双向同时高带宽,就要考虑FX3的DMA通道共享带宽的问题了。双向同时传输时总带宽约380MB/s,单一方向只能获得一部分。

我在项目里用到了下行通道传输配置参数,数据量很小,所以没有遇到瓶颈。如果你对双向方案感兴趣,可以按同样的思路把下行状态机加上,测试下实际数据。

5.3 从338MB/s到极限带宽的探索

338MB/s这个数字在标准Bulk传输下已经算是很好的成绩。如果还想继续往上推,可以考虑两个方向:

一是把GPIF接口时钟从100MHz提到133MHz。数据位宽32位不变的情况下,理论带宽可达532MB/s,USB 3.0的瓶颈马上变成主控端。但FX3的GPIF接口最高支持100MHz(在数据手册里写的是100MHz),超频使用风险很高。

二是优化主机端软件。跳过操作系统驱动,使用UIO或VFIO直接操作xHCI控制器,可以省掉一部分驱动层拷贝和中断处理开销。Linux下可以做到360MB/s以上,但Windows下很难绕过签名驱动的限制。

回到实际工程,338MB/s对于绝大多数数据采集和图像传输场景已经绰绰有余,与其追求极限数字,不如把精力放在系统的稳定性和易用性上。

5.4 从项目复盘中学到的三个重要经验

第一,DMA缓冲数量比预想中更重要。最初我觉得双缓冲就够用了,毕竟每个缓冲有16KB,GPIF接口消耗完一个,USB引擎应该早就发走了。但实际情况是,USB总线上的调度延迟、主机驱动的响应时间,很多时候比预期大得多。四重缓冲让FX3内部的"蓄水池"足够深,有效吸收了链路两端的速率波动。

第二,FPGA和FX3之间的时序调试,不要全靠时序分析报告,一定要用示波器实测。报告的约束是理论计算,但实际信号经过PCB走线、过孔、连接器后,边沿会变差。我花了大量时间优化代码逻辑之后,一次示波器测试才发现,数据线和时钟线的走线长度差导致了约0.8ns的skew,重新约束后马上就好转了。

第三,固件里的调试串口打印不能留到正式版本里。我在调优阶段用UART打印DMA事件的计数,结果打印逻辑本身干扰了DMA操作,导致带宽一直上不去。后来把所有打印全部注释掉,带宽立刻提升了将近20MB/s。这个坑值得后来人引以为戒。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询