據(jù)傳輸:基于CYUSB3014的USB 3.0 Slave FIFO設(shè)計(jì)實(shí)戰(zhàn))
前陣子幫客戶做一套高速數(shù)據(jù)采集系統(tǒng)FPGA 端要實(shí)時(shí)把 ADC 數(shù)據(jù)灌到 PC 上做處理。評(píng)估了幾種 USB 3.0 方案之后最后定了FPGA CYUSB3014EZ-USB FX3的 Slave FIFO 模式。調(diào)通之后用 Streamer 和自寫上位機(jī)分別測(cè)速持續(xù)傳輸穩(wěn)定在338MB/s這個(gè)數(shù)字在 USB 3.0 bulk 傳輸里已經(jīng)非常接近鏈路極限了。整個(gè)過(guò)程踩了不少坑固件側(cè)、FPGA 側(cè)、主機(jī)側(cè)都有很多問(wèn)題不是看 datasheet 就能立刻想明白的。這篇文章把完整的設(shè)計(jì)思路、固件與源碼要點(diǎn)、實(shí)測(cè)數(shù)據(jù)和避坑記錄整理出來(lái)給正在做或者準(zhǔn)備做 FPGA 高速數(shù)據(jù)傳輸?shù)呐笥岩粋€(gè)參考。如果你也是用 FPGA 做數(shù)據(jù)采集、圖像傳輸、軟件無(wú)線電這類需要大帶寬上位的項(xiàng)目這篇內(nèi)容應(yīng)該能幫你省下至少兩周的調(diào)板時(shí)間。文章會(huì)講清楚為什么選 FX3、Slave FIFO 模式到底怎么工作、固件里哪些配置必須改、FPGA 側(cè)狀態(tài)機(jī)怎么寫不會(huì)出錯(cuò)還有 338MB/s 這個(gè)數(shù)字是怎么來(lái)的、還能不能更高。1. 方案選型與整體設(shè)計(jì)思路1.1 為什么是 CYUSB3014先說(shuō)選型。FPGA 和 PC 之間高速傳輸常見(jiàn)的路子就那幾條PCIe、千兆/萬(wàn)兆以太網(wǎng)、USB 3.0。PCIe 帶寬最好但 FPGA 要帶 PCIe 硬核PC 端還要寫驅(qū)動(dòng)工程量大。以太網(wǎng)方案比較通用但協(xié)議棧和 FPGA 邏輯資源開銷不小TCP 吞吐率調(diào)優(yōu)也是無(wú)底洞。USB 3.0 最大的優(yōu)勢(shì)是上位機(jī)生態(tài)成熟——Windows 下有 CyUSB3.sys 驅(qū)動(dòng)Linux 下有現(xiàn)成的內(nèi)核驅(qū)動(dòng)不需要自己碰驅(qū)動(dòng)開發(fā)的深水區(qū)。USB 3.0 橋接芯片里市面上主要就是 Cypress現(xiàn)在是 Infineon的 FX3 和 FTDI 的 FT60x。FT60x 用起來(lái)簡(jiǎn)單FIFO 接口好接但它的文檔和社區(qū)積累不如 FX3而且 FT60x 的 USB 協(xié)議層是把數(shù)據(jù)包封裝好之后直接往主機(jī)發(fā)的靈活性差一些。FX3 內(nèi)置了一顆 200MHz 的 ARM926EJ-S 核可以跑固件GPIF II 接口可編程既能做 Slave FIFO 又能做 Master端點(diǎn)、DMA 通道、Buffer 大小全都可以按需配置。說(shuō)白了FT60x 是一個(gè)定死的橋FX3 是一個(gè)半定制的橋后者給了你足夠的調(diào)整空間去優(yōu)化吞吐率。另外 FX3 的參考資料非常多官方有 AN75779 這個(gè)經(jīng)典的 Slave FIFO 示例工程網(wǎng)上也有大量中文資料。做硬件最怕的就是芯片資料少、出了問(wèn)題沒(méi)地方查FX3 在這方面的優(yōu)勢(shì)是實(shí)打?qū)嵉摹?.2 Slave FIFO 模式到底是怎么工作的FX3 的 GPIF II 是一個(gè)可編程狀態(tài)機(jī)接口可以配置成 Master 模式由 FX3 主動(dòng)產(chǎn)生讀寫時(shí)序去訪問(wèn)外部器件也可以配置成 Slave FIFO 模式由外部器件當(dāng)主控FX3 的 FIFO 就是一個(gè)被動(dòng)的存儲(chǔ)池。我們項(xiàng)目里用的就是 Slave FIFO 同步模式。FPGA 作為主控向外提供 100MHz 的 PCLK 給 FX3同時(shí)控制片選、讀寫使能、數(shù)據(jù)總線FX3 通過(guò) FLAGA/FLAGB 這些水位標(biāo)志告訴 FPGA 現(xiàn)在能不能寫/讀。數(shù)據(jù)流是單向大帶寬的FPGA 內(nèi)部邏輯把數(shù)據(jù)寫入緩存 FIFO然后寫狀態(tài)機(jī)檢測(cè) FX3 的 FLAGA若水位允許就往 32bit 數(shù)據(jù)總線上丟數(shù)據(jù)并拉低 SLWRPCLK 上升沿采樣數(shù)據(jù)進(jìn)入 FX3 的 DMA 緩沖固件再把緩沖搬運(yùn)到 USB 端點(diǎn)最終通過(guò) USB 3.0 送到主機(jī)。這里有個(gè)常見(jiàn)誤區(qū)很多人以為 Slave FIFO 模式下 FX3 就是一塊內(nèi)存直接映射到 USB其實(shí)不是。FX3 內(nèi)部有 DMA 通道和 USB 端點(diǎn)數(shù)據(jù)是肩并肩搬運(yùn)的GPIF II 寫入 FIFO 的速度和 USB 上傳速度互相制約水往低處流慢的那一方?jīng)Q定實(shí)際速率。所以要讓 FPGA 側(cè)接口帶寬遠(yuǎn)大于 USB 側(cè)才能讓 USB 變成瓶頸我們配置的 32bit 100MHz 理論帶寬是 400MB/s比 USB 3.0 實(shí)際有效帶寬高出一截這樣 USB 鏈路才是天花板。2. 硬件板卡設(shè)計(jì)與接口要點(diǎn)2.1 最小系統(tǒng)與啟動(dòng)方式FX3 的最小系統(tǒng)說(shuō)簡(jiǎn)單也簡(jiǎn)單說(shuō)麻煩也麻煩。供電方面它需要 1.2V 內(nèi)核電壓、1.8V/2.5V/3.3V 的 I/O 電壓自己內(nèi)部還有 USB PHY 的模擬供電。我見(jiàn)過(guò)不少板子死在供電上——上電順序不對(duì)導(dǎo)致枚舉不穩(wěn)定或者 VIO 電壓和 FPGA bank 電壓不匹配導(dǎo)致接口時(shí)序異常。建議 VIO 和 FPGA 側(cè) bank 電壓保持一致比如都用 3.3V這樣不用加電平轉(zhuǎn)換芯片少一層延遲時(shí)序也更好控。啟動(dòng)方式要特別注意。FX3 可以從 USB 啟動(dòng)、I2C EEPROM 啟動(dòng)、或者 SPI Flash 啟動(dòng)。開發(fā)調(diào)試階段用 USB 啟動(dòng)最方便也就是每次上電后通過(guò) USB Control Center 手動(dòng)下載固件。但是一旦進(jìn)入聯(lián)調(diào)階段如果不想每次斷電后都重新下載就要在板上放一顆 I2C EEPROM比如 24LC128 或 24LC256把固件燒進(jìn)去。這個(gè)點(diǎn)坑過(guò)很多人EEPROM 里固件版本和當(dāng)前工程不匹配板子上電后枚舉出來(lái)一個(gè)老固件然后你對(duì)新固件的修改完全不生效還以為是代碼問(wèn)題。所以建議 Solid State 版的燒錄和版本管理從一開始就規(guī)范化我在工程里會(huì)寫一個(gè)版本號(hào)常量枚舉后通過(guò)廠商字符串讀出來(lái)上位機(jī)也能顯示避免這種烏龍。2.2 關(guān)鍵信號(hào)與時(shí)序裕量Slave FIFO 同步模式下 FPGA 和 FX3 之間的關(guān)鍵信號(hào)不多列一下信號(hào)方向相對(duì) FPGA功能PCLK輸出給 FX3接口時(shí)鐘通常 100MHzSLCS#輸出片選低有效SLWR#輸出寫使能低有效SLRD#輸出讀使能低有效SLOE#輸出輸出使能讀數(shù)據(jù)時(shí)使用PKTEND#輸出短包結(jié)束信號(hào)A[1:0]輸出線程選擇地址DQ[31:0]雙向數(shù)據(jù)總線FLAGA/FLAGB輸入FIFO 狀態(tài)標(biāo)志這里的時(shí)序關(guān)系是高速接口的關(guān)鍵。PCLK 是 100MHz一個(gè)周期 10ns。FX3 手冊(cè)里對(duì) DQ 相對(duì) PCLK 的建立時(shí)間和保持時(shí)間都有明確要求不同批次芯片也許有細(xì)微差異但大致在幾個(gè)納秒量級(jí)。FPGA 側(cè)約束要跟上不然即使功能正確時(shí)序裕量不足也可能在溫度變化或不同板卡間隨機(jī)出錯(cuò)。我的建議是在 XDC/FDC 中顯式約束 PCLK并用 set_input_delay、set_output_delay 約束數(shù)據(jù)總線與 PCLK 的相對(duì)關(guān)系。你不需要把每個(gè)信號(hào)的 delay 卡到幾個(gè)皮秒但至少要讓 Vivado/Quartus 知道 DQ 是同步于 PCLK 的否則工具默認(rèn)按異步路徑處理布局布線出來(lái)的結(jié)果很隨機(jī)。2.3 PCB 布線經(jīng)驗(yàn)PCB 布線是容易忽略但決定成敗的一環(huán)。FPGA 與 FX3 之間的數(shù)據(jù)總線 DQ[31:0] 要盡量等長(zhǎng)組內(nèi)偏差控制在 ±5mil 以內(nèi)PCLK 到 FX3 的走線要比數(shù)據(jù)線短一些因?yàn)?FPGA 輸出的時(shí)鐘沿和數(shù)據(jù)的相對(duì)關(guān)系受內(nèi)部延遲影響PCB 上留出余量有利于時(shí)序收斂。USB 3.0 的 TX/RX 差分對(duì)要做 90Ω 差分阻抗控制走線遠(yuǎn)離其他高速信號(hào)連接器附近的參考平面不能挖斷。我的經(jīng)驗(yàn)是第一版 PCB 如果條件允許把 FLAG 信號(hào)和 DQ 信號(hào)都引出測(cè)試點(diǎn)方便示波器或邏輯分析儀測(cè)量。調(diào)試高速接口時(shí)能直接看到波形比什么都強(qiáng)。另外 FX3 的 USB 3.0 引腳和 USB 2.0 引腳需要同時(shí)接出來(lái)因?yàn)?FX3 啟動(dòng)時(shí)先靠 USB 2.0 枚舉再協(xié)商到 SuperSpeed。只接了 USB 3.0 的線沒(méi)接 USB 2.0板子會(huì)一直枚舉失敗。3. FX3 固件側(cè)配置與實(shí)現(xiàn)3.1 從官方示例工程改起FX3 固件開發(fā)強(qiáng)烈建議直接從 SDK 里的 cyfxgpiftousb 示例工程開始而不是從零搭。這個(gè)工程本身就是 GPIF II 到 USB 的橋接 demoSlave FIFO 模式的所有基礎(chǔ)代碼都在你要改的主要是GPIF 狀態(tài)機(jī)配置、DMA 通道參數(shù)、端點(diǎn)配置、線程與水位的映射關(guān)系。固件主流程大致是調(diào)用 CyU3PDeviceInit 初始化芯片調(diào)用 CyU3PKernelEntry 啟動(dòng) RTOS 內(nèi)核創(chuàng)建 DMA 通道P2U 和 U2P加載 GPIF 狀態(tài)機(jī)配置Slave FIFO 同步模式配置 USB 端點(diǎn)并連接 USB啟動(dòng) GPIF 引擎每一步都有對(duì)應(yīng)的 APISDK 里也有完整注釋關(guān)鍵是理解每個(gè)參數(shù)的含義尤其是 DMA 通道的 buffer 大小和 buffer count這兩個(gè)參數(shù)直接決定吞吐率。3.2 端點(diǎn)、DMA 通道與線程配置這是固件里最重要的部分。我們的數(shù)據(jù)通路是 FPGA 把數(shù)據(jù)寫進(jìn) FX3所以核心是 P2UPeripheral to USB通道外部傳入的數(shù)據(jù)先落入 GPIF II 的 FIFODMA 引擎再把數(shù)據(jù)搬到 USB 端點(diǎn)。USB 側(cè)我用了一個(gè) Bulk IN 端點(diǎn) 0x81以及一個(gè) Bulk OUT 端點(diǎn) 0x01用來(lái)接收上位機(jī)下發(fā)的控制命令。創(chuàng)建一個(gè)可靠的 DMA 通道核心參數(shù)有幾個(gè)傳輸方向、事件類型、buffer 大小、buffer count。示例代碼里常見(jiàn)的是 16KB buffer、8 個(gè) buffer。我實(shí)際測(cè)試下來(lái)buffer count 太少會(huì)導(dǎo)致 DMA 搬完數(shù)據(jù)后 CPU 介入太頻繁速率上不去。后來(lái)把 buffer count 加大到 16加上合理的水位配置吞吐率明顯改善。FX3 內(nèi)部是 ARM 核跑 RTOS 的DMA 中斷太多會(huì)擠掉主循環(huán)的時(shí)間相當(dāng)于拖慢吞吐。端點(diǎn)配置也不復(fù)雜但有一點(diǎn)要注意Bulk 端點(diǎn)的最大包大小要設(shè)成 1024 字節(jié)這是 USB 3.0 超速 bulk 的標(biāo)準(zhǔn)。如果固件里忘了改還是默認(rèn)的 512 字節(jié)那帶寬會(huì)直接砍半。下面是固件初始化中關(guān)鍵部分的一個(gè)簡(jiǎn)化示例/* DMA 通道創(chuàng)建P2U自動(dòng)模式從 GPIF 到 USB */ CyU3PDmaChannelCreate(glChHandleSlFifoP2U, CY_U3P_DMA_TYPE_MANUAL, dmaConfig); /* USB 端點(diǎn)配置Bulk IN 0x81PacketSize 1024 */ CyU3PUsbSetEpConfig(0x81, CY_U3P_USB_EP_BULK, 1024); CyU3PUsbSetEpConfig(0x01, CY_U3P_USB_EP_BULK, 1024); /* 啟動(dòng) GPIF 引擎加載 Slave FIFO 狀態(tài)機(jī)表 */ CyU3PGpifLoad(CyFxGpifConfig); CyU3PGpifStart();到底用自動(dòng)模式還是手動(dòng)模式取決于是否需要 CPU 參與數(shù)據(jù)搬運(yùn)。純高速流式傳輸用自動(dòng)模式即可CPU 不碰數(shù)據(jù)DMA 直接搬如果需要做協(xié)議解析、包格式轉(zhuǎn)換才需要手動(dòng)模式讓 CPU 介入。3.3 水位配置與水印標(biāo)志FLAG 信號(hào)是 FPGA 判斷能不能寫的依據(jù)它背后靠的是水位Watermark機(jī)制。FX3 的每個(gè)線程都有自己的水位配置當(dāng) FIFO 剩余空間大于某個(gè)閾值時(shí)FLAG 就有效表示可以寫小于閾值時(shí)FLAG 無(wú)效表示不要再寫了否則數(shù)據(jù)可能溢出。水印閾值設(shè)得太小FIFO 快滿了才通知 FPGAFPGA 的寫入狀態(tài)機(jī)來(lái)不及停就會(huì)丟數(shù)據(jù)閾值設(shè)得太大明明還有很多空間但 FLAG 已經(jīng)無(wú)效利用率上不去吞吐率受損。我一開始用默認(rèn)閾值實(shí)測(cè)只有 200MB/s 左右后來(lái)把 P2U 方向的水印適當(dāng)調(diào)低給 FIFO多留一點(diǎn)緩沖速率馬上上來(lái)了。具體數(shù)值和你的 DMA buffer 配置有關(guān)需要實(shí)測(cè)微調(diào)但這里記住一個(gè)原則寫方向的水印要保證 FIFO 里有足夠的空間余量讓 FPGA 從檢測(cè)到 FLAG 無(wú)效到停止寫入這段時(shí)間里數(shù)據(jù)不會(huì)溢出。3.4 固件側(cè)的幾個(gè)坑先講 PKTEND。在 Slave FIFO 模式下如果傳輸?shù)氖沁B續(xù)大流量數(shù)據(jù)FPGA 側(cè)需要周期性地拉一下 PKTEND#告訴 FX3這一段數(shù)據(jù)結(jié)束了發(fā)給主機(jī)吧。不拉 PKTEND 的后果是數(shù)據(jù)一直攢在 FX3 內(nèi)部主機(jī)收不到表現(xiàn)就是上位機(jī)讀到的數(shù)據(jù)卡住或者吞吐率忽高忽低。很多人在跑短包測(cè)試時(shí)沒(méi)發(fā)現(xiàn)問(wèn)題一旦跑持續(xù)大流量就掉鏈子多半是這個(gè)原因。再講 EEPROM 啟動(dòng)的坑。上文提過(guò)固件版本管理這里再補(bǔ)充一點(diǎn)EEPROM 里的固件如果和當(dāng)前硬件不匹配比如 GPIO 配置不同輕則功能異常重則設(shè)備枚舉都過(guò)不了。而且 USB 啟動(dòng)和 EEPROM 啟動(dòng)的優(yōu)先級(jí)是 USB 優(yōu)先還是 EEPROM 優(yōu)先和 boot 引腳配置有關(guān)設(shè)計(jì)時(shí)要明確你要的啟動(dòng)方式別讓兩種方式互相干擾。固件側(cè)調(diào)優(yōu)經(jīng)驗(yàn)一句話總結(jié)先把默認(rèn)示例跑通再一項(xiàng)一項(xiàng)改參數(shù)每次只改一個(gè)變量記錄吞吐率變化不要一次改一堆參數(shù)然后不知道是誰(shuí)起作用。4. FPGA 側(cè)邏輯實(shí)現(xiàn)4.1 模塊劃分與數(shù)據(jù)通路FPGA 側(cè)的邏輯我是按模塊劃分的避免把所有邏輯堆在頂層。主要分為時(shí)鐘管理模塊、數(shù)據(jù)源模塊ADC 或 DDR 讀出、寫通道狀態(tài)機(jī)、讀通道狀態(tài)機(jī)、以及跨時(shí)鐘域緩沖 FIFO。數(shù)據(jù)通路的頂層邏輯是這樣的數(shù)據(jù)源以較高速率把數(shù)據(jù)寫入一個(gè)異步 FIFO這個(gè) FIFO 隔離了源時(shí)鐘域和 GPIF 時(shí)鐘域?qū)憼顟B(tài)機(jī)輪詢 FX3 的 FLAGA當(dāng) FIFO 非空且 FX3 允許寫入時(shí)從 FIFO 讀數(shù)據(jù)并驅(qū)動(dòng) DQ 總線和 SLWR讀狀態(tài)機(jī)相對(duì)簡(jiǎn)單主要處理上位機(jī)通過(guò) OUT 端點(diǎn)下發(fā)的命令流用 SLOE 和 SLRD 從 FX3 讀回?cái)?shù)據(jù)。使用異步 FIFO 這個(gè)點(diǎn)非常重要。FPGA 內(nèi)部邏輯可能跑在 200MHz 甚至更高而 GPIF 接口是 100MHz兩個(gè)時(shí)鐘域如果不做隔離數(shù)據(jù)采樣很容易出問(wèn)題。跨時(shí)鐘域不是靠大概對(duì)齊就能解決的一定要用 FIFO 或至少雙寄存器同步。4.2 寫通道狀態(tài)機(jī)寫狀態(tài)機(jī)是 FPGA 側(cè)的核心。它要做的事情是檢測(cè) FLAGA 有效然后把 32bit 數(shù)據(jù)放到 DQ 總線上拉低 SLWR等待一個(gè) PCLK 上升沿完成寫入重復(fù)直到 FIFO 空或者 FLAGA 無(wú)效。有一個(gè)細(xì)節(jié)必須注意FLAGA 是異步信號(hào)進(jìn)入 FPGA 內(nèi)部要先做兩級(jí)寄存器同步否則可能采到亞穩(wěn)態(tài)狀態(tài)機(jī)誤判。我在代碼里專門做了同步模塊同步后還要考慮到同步延遲——從 FLAGA 有效到 FPGA 真正采樣中間有幾個(gè)時(shí)鐘周期的延遲所以要保證 FX3 返回 FLAG 無(wú)效時(shí)水位的余量能覆蓋這個(gè)延遲。寫狀態(tài)機(jī)的簡(jiǎn)化 Verilog 示例// 同步后的 FLAGflag_a_sync // 狀態(tài)機(jī)核心跳轉(zhuǎn) always (posedge pclk or negedge rst_n) begin if (!rst_n) state IDLE; else begin case (state) IDLE: begin if (fifo_empty 1b0 flag_a_sync 1b1) state WRITE; end WRITE: begin if (flag_a_sync 1b0) state IDLE; else if (fifo_empty 1b1) state IDLE; end endcase end end // 數(shù)據(jù)與寫使能輸出 always (posedge pclk or negedge rst_n) begin if (!rst_n) begin slwr_n 1b1; dq 32h0; end else if (state WRITE) begin dq fifo_dout; // 從緩存 FIFO 讀出數(shù)據(jù) slwr_n 1b0; // 拉低寫使能 end else begin slwr_n 1b1; end end這段邏輯看著簡(jiǎn)單實(shí)際調(diào)試時(shí)最折磨人的是時(shí)序收斂。DQ 總線在 PCLK 上升沿之前要穩(wěn)定SLWR 也要保證和 DQ 滿足 FX3 的建立保持時(shí)間。如果你的設(shè)計(jì)在仿真里一切正常上板就隨機(jī)出錯(cuò)十有八九是時(shí)序約束沒(méi)做好。4.3 讀通道與回環(huán)驗(yàn)證讀通道用來(lái)接收上位機(jī)的命令或者做回環(huán)驗(yàn)證。它的邏輯比寫通道簡(jiǎn)單檢測(cè) FLAGB 是否表示有數(shù)據(jù)可讀拉低 SLRD#讀取 DQ 總線。需要注意的是 SLOE# 信號(hào)在 Slave FIFO 模式下讀數(shù)據(jù)時(shí)分兩種情況——如果 SLOE 有效DQ 總線由 FX3 驅(qū)動(dòng)如果 SLOE 無(wú)效DQ 總線可能處于高阻。所以讀通道要同時(shí)控制 SLOE 和 SLRD不能只拉一個(gè)。建議從最開始就做一個(gè)回環(huán)測(cè)試FPGA 從 FX3 讀回?cái)?shù)據(jù)然后原樣寫回。這樣固件和 FPGA 的讀寫通道可以分開驗(yàn)證哪邊有問(wèn)題一目了然。調(diào)試完回環(huán)通路了再接入真實(shí)數(shù)據(jù)源。4.4 時(shí)序約束實(shí)戰(zhàn)FPGA 側(cè)時(shí)序約束是整個(gè)工程能否穩(wěn)定跑滿速率的隱藏決定因素。我以 Vivado 為例說(shuō)一下做法create_clock -period 10.000 -name pclk [get_ports pclk] # 輸入延遲FX3 的 FLAG 信號(hào)相對(duì) PCLK set_input_delay -clock pclk -max 5.0 [get_ports {flag_a flag_b}] set_input_delay -clock pclk -min 1.0 [get_ports {flag_a flag_b}] # 輸出延遲DQ、SLWR 等相對(duì) PCLK set_output_delay -clock pclk -max 6.0 [get_ports {dq[*] slwr_n slrd_n}] set_output_delay -clock pclk -min 1.0 [get_ports {dq[*] slwr_n slrd_n}]這里的具體 delay 值僅供參考要根據(jù) FX3 手冊(cè)的時(shí)序參數(shù)計(jì)算。如果你的工程里沒(méi)有類似約束工具會(huì)默認(rèn)把 FLAG 當(dāng)成異步輸入布局布線時(shí)不會(huì)保證它滿足時(shí)序上板出錯(cuò)概率很高。我見(jiàn)過(guò)太多人仿真沒(méi)問(wèn)題但上板隨機(jī)錯(cuò)最后發(fā)現(xiàn)約束文件幾乎是空的。5. 實(shí)測(cè)數(shù)據(jù)、性能分析與優(yōu)化5.1 338MB/s 是怎么測(cè)出來(lái)的實(shí)測(cè)環(huán)節(jié)我用兩種方法互相驗(yàn)證。第一種是 Cypress SDK 自帶的 Streamer 上位機(jī)它支持 FX3 的 bulk 傳輸測(cè)速簡(jiǎn)單粗暴界面直接顯示吞吐率。第二種是自己寫了一個(gè) C# 上位機(jī)基于 CyUSB.NET 庫(kù)開一個(gè) 4MB 的接收緩沖循環(huán)調(diào)用異步讀接口統(tǒng)計(jì)每秒收到的字節(jié)數(shù)。測(cè)試條件是FPGA 側(cè)用計(jì)數(shù)器產(chǎn)生偽隨機(jī)數(shù)據(jù)流DDR 讀出的數(shù)據(jù)也可以反正不關(guān)心內(nèi)容只關(guān)心速度。上位機(jī)持續(xù)讀取 60 秒統(tǒng)計(jì)總字節(jié)數(shù)換算成平均速率。多次測(cè)試取平均值最終結(jié)果是 338MB/s。測(cè)試模式實(shí)測(cè)速率說(shuō)明默認(rèn)配置DMA buffer 8 個(gè)約 200MB/s水印不匹配DMA 中斷頻繁加大 buffer調(diào)整水印約 300MB/s明顯改善最終調(diào)優(yōu)主機(jī)端大緩沖338MB/s接近 USB 3.0 bulk 極限主機(jī)端緩沖 64KB約 260MB/s緩沖區(qū)太小丟速嚴(yán)重看到 200MB/s 到 338MB/s 的差距了吧這幾個(gè)變量——DMA 通道 buffer count、水印閾值、主機(jī)端接收緩沖——任何一個(gè)不匹配都會(huì)讓你誤以為方案不行其實(shí)是參數(shù)沒(méi)調(diào)到位。5.2 338MB/s 離 USB 3.0 的理論極限還有多遠(yuǎn)來(lái)算一筆賬。USB 3.0 的物理層線速率是 5Gbps但因?yàn)椴捎昧?8b/10b 編碼實(shí)際有效數(shù)據(jù)速率是 4Gbps也就是 500MB/s。這是物理層的上限。但是 bulk 傳輸在協(xié)議層有開銷每個(gè)包有包頭、CRC、握手還有包與包之間的間隔。實(shí)際能達(dá)到的最大 payload 帶寬通常只有 400MB/s 左右這還是在最理想的情況下。如果你用 USB 分析儀抓包會(huì)看到總線上的數(shù)據(jù)并不會(huì) 100% 時(shí)間都在傳數(shù)據(jù)總有一些 idle 間隔。再回到接口側(cè)FPGA 與 FX3 之間是 32bit 100MHz理論帶寬 400MB/s。FPGA 側(cè)供給的上限是 400MB/sUSB 側(cè)實(shí)際能吸收的不到 400MB/s所以瓶頸在 USB 鏈路。338MB/s 大約是 400MB/s 理論最大 payload 的 84.5%這個(gè)數(shù)字已經(jīng)很健康了說(shuō)明 USB 協(xié)議開銷和 FIFO 切換間隙已經(jīng)壓得比較低。所以如果有人問(wèn)怎么才能測(cè)到 400MB/s 以上答案很簡(jiǎn)單換 USB 3.1/3.2 方案或者用 PCIe。FX3 跑 USB 3.0338MB/s 已經(jīng)接近它的天花板。5.3 影響吞吐率的幾個(gè)隱形因素先講主機(jī)端。很多人把注意力全部放在 FPGA 和 FX3 上忽略了上位機(jī)代碼。如果你用同步方式讀 USB每次讀 4KB讀完再發(fā)起下一次讀取中間的開銷非常大吞吐率能掉到 200MB/s 以下。要跑滿速率必須用異步讀并且緩沖區(qū)要足夠大至少 4MB。原理很簡(jiǎn)單USB 控制器需要足夠的緩沖來(lái)容忍系統(tǒng)的調(diào)度延遲緩沖太小系統(tǒng)一卡USB 總線就空等了。固件線程優(yōu)先級(jí)也有影響。FX3 的 RTOS 里USB 中斷線程和 DMA 事件線程的優(yōu)先級(jí)如果設(shè)置不當(dāng)數(shù)據(jù)搬運(yùn)鏈條上某一環(huán)會(huì)周期性阻塞。我試過(guò)把 DMA 事件線程優(yōu)先級(jí)調(diào)低速率暴跌調(diào)回合適優(yōu)先級(jí)后恢復(fù)正常。這類問(wèn)題沒(méi)有統(tǒng)一標(biāo)準(zhǔn)要結(jié)合自己的數(shù)據(jù)流來(lái)驗(yàn)證。還有一個(gè)容易被忽略的因素是數(shù)據(jù)對(duì)齊。Bulk 傳輸對(duì)包大小敏感如果 FPGA 側(cè)送入 FX3 的數(shù)據(jù)總是湊不滿 1024 字節(jié)的整數(shù)倍就會(huì)頻繁產(chǎn)生短包短包每一次都需要 PKTEND 和額外的總線處理長(zhǎng)期跑下來(lái)吞吐率損失不小。所以設(shè)計(jì)數(shù)據(jù)源時(shí)盡量保證單次寫入的數(shù)據(jù)量是 1024 字節(jié)的整數(shù)倍。6. 常見(jiàn)問(wèn)題排查與避坑速查6.1 問(wèn)題速查表癥狀可能原因解決方案設(shè)備無(wú)法枚舉USB 2.0 線沒(méi)接、EEPROM 固件損壞、供電異常先測(cè) USB 2.0 枚舉再排查供電枚舉成功但上位機(jī)打不開設(shè)備驅(qū)動(dòng)沖突或驅(qū)動(dòng)版本不對(duì)卸載舊驅(qū)動(dòng)重裝 CyUSB3.sys速率只有 100-200MB/s主機(jī)緩沖太小、DMA 配置不當(dāng)調(diào)整主機(jī)緩沖到 4MB 以上加大 DMA buffer count跑幾分鐘后速率驟降上位機(jī)內(nèi)存碎片或緩存回收不及時(shí)改用異步讀反復(fù)使用預(yù)分配緩沖數(shù)據(jù)內(nèi)容錯(cuò)位、出現(xiàn)亂碼字節(jié)序不對(duì)或數(shù)據(jù)位寬映射錯(cuò)誤檢查 DQ 總線高低字節(jié)映射32bit 打包順序FLAG 信號(hào)誤觸發(fā)導(dǎo)致丟數(shù)跨時(shí)鐘域亞穩(wěn)態(tài)未處理增加兩級(jí)同步寄存器檢查水位余量短包模式正常連續(xù)流卡死PKTEND 沒(méi)周期性拉低在 FPGA 邏輯中按包周期拉低 PKTEND#這張表我每次做新項(xiàng)目都會(huì)打開看一遍。很多問(wèn)題看起來(lái)是硬件問(wèn)題實(shí)際是軟件配置問(wèn)題很多問(wèn)題看起來(lái)是 FPGA 問(wèn)題實(shí)際是上位機(jī)問(wèn)題。調(diào)試時(shí)不要急著懷疑一個(gè)方向按數(shù)據(jù)流從源頭到終點(diǎn)一級(jí)一級(jí)排查。6.2 一次卡在 200MB/s的排查實(shí)錄這個(gè)案例挺典型。有一次我把代碼從舊工程移植到新工程速率從 338MB/s 掉到 200MB/s而且怎么調(diào) DMA 參數(shù)都沒(méi)用。當(dāng)時(shí)排查步驟是第一步先用 Streamer 測(cè)速確認(rèn)掉速現(xiàn)象在官方工具上也存在排除上位機(jī)因素。第二步用示波器看 FPGA 和 FX3 之間的 FLAGA 和 SLWR發(fā)現(xiàn) SLWR 的有效占空比不到 50%說(shuō)明 FPGA 經(jīng)常處于想寫但 FX3 不接收的狀態(tài)問(wèn)題在 FX3 側(cè)水位配置。第三步查固件里 DMA 通道配置發(fā)現(xiàn)新工程里我復(fù)制了一份舊代碼但 DMA buffer count 被初始化成了 4和之前調(diào)優(yōu)后的 16 不一致。改回 16 之后速率立刻恢復(fù)到 330MB/s 以上。這個(gè)案例的教訓(xùn)是參數(shù)不是多就好或者少就好而是要和數(shù)據(jù)流匹配。舊工程的配置在新工程里不一定適用每次粘貼代碼都要認(rèn)真核對(duì)每個(gè)參數(shù)。后來(lái)我在工程里加了一個(gè)配置頭文件把 DMA buffer、水印、端點(diǎn)大小全部集中管理再也沒(méi)出過(guò)這類問(wèn)題。6.3 調(diào)試工具組合拳最后分享一套調(diào)試工具用法是我在聯(lián)調(diào)時(shí)固定使用的組合。第一個(gè)是 USB 分析儀式的抓包工具。Windows 下可以用 Wireshark 加 USBPcap插上 USB 3.0 設(shè)備后就可以抓取總線上的 URBs雖然看不到每個(gè)包的物理層細(xì)節(jié)但足夠分析吞吐率瓶頸和錯(cuò)誤包。第二個(gè)是邏輯分析儀或示波器。耐心等觸發(fā)條件抓 FLAGA、SLWR、DQ 之間的關(guān)系看看是 FLAG 一直無(wú)效還是有效但 FPGA 不寫。第三個(gè)是 Cypress 的 USB Control Center方便查看端點(diǎn)信息、廠商描述符以及手動(dòng)下載固件。第四個(gè)是 UsbTreeView 這類 USB 設(shè)備查看工具排查枚舉失敗問(wèn)題。我的調(diào)試順序一般是先用 UsbTreeView 確認(rèn)枚舉正常再用 Control Center 下載固件并確認(rèn)版本然后用 Streamer 測(cè)速測(cè)速不達(dá)標(biāo)就上示波器抓接口時(shí)序接口時(shí)序沒(méi)問(wèn)題就上 Wireshark 抓總線包。按這個(gè)順序走完一般都能定位到問(wèn)題所在。我個(gè)人在實(shí)際操作中的體會(huì)是FPGA 和 FX3 聯(lián)調(diào)最需要的不是靈光一現(xiàn)而是系統(tǒng)性的排查順序和耐心的參數(shù)微調(diào)。338MB/s 這個(gè)數(shù)字不是一次就調(diào)出來(lái)的中間經(jīng)歷了從 200 到 300 再到 338 的逐步提升每一步改動(dòng)都很小但每個(gè)改動(dòng)背后都有明確的依據(jù)。這套方案后續(xù)還可以繼續(xù)擴(kuò)展比如把 FPGA 側(cè)搬到 PCIe 上做更高帶寬或者在 FX3 上做多通道采集但 USB 3.0 這個(gè)門檻上FX3 的 Slave FIFO 模式基本是繞不開的經(jīng)典路數(shù)。希望這篇文章能幫你少踩幾個(gè)我踩過(guò)的坑一次就跑到 300MB/s 以上。