免軟核:W5500硬件協(xié)議棧驅(qū)動設(shè)計與實測)
簡介這是一份面向FPGA開發(fā)者的W5500驅(qū)動實現(xiàn)與范例工程定位于解決W5500與FPGA之間SPI接口設(shè)計、狀態(tài)機驅(qū)動及高速數(shù)據(jù)收發(fā)等實際問題。W5500內(nèi)置硬件TCP/IP協(xié)議棧配合FPGA可達到3.5M字節(jié)/秒的實測傳輸速度接口簡潔易用適用于工業(yè)控制、物聯(lián)網(wǎng)設(shè)備、遠程監(jiān)控等需要實時網(wǎng)絡(luò)通信的場景。壓縮包大小為21.55MB內(nèi)含可直接下載使用的應(yīng)用示例與驅(qū)動代碼開發(fā)者可據(jù)此快速完成初始化配置、SPI時序控制、中斷處理及數(shù)據(jù)收發(fā)功能的移植或二次開發(fā)。已有1212人學習下載工程結(jié)構(gòu)清晰適合具備一定FPGA基礎(chǔ)、希望快速集成以太網(wǎng)通信能力的開發(fā)者參考。 搞FPGA以太網(wǎng)這個需求最怕的不是寫代碼而是協(xié)議棧那一大堆狀態(tài)。之前項目里遇到網(wǎng)絡(luò)功能我第一反應(yīng)是掛軟核跑lwIP后來換用W5500這顆芯片才發(fā)現(xiàn)很多場景下根本不用那么折騰。硬件協(xié)議棧把TCP/IP全部固化在芯片內(nèi)部FPGA這邊只需要通過SPI接口讀寫寄存器相當于把網(wǎng)絡(luò)協(xié)議棧簡化成一個“讀寫外部寄存器”的活。我在實際工程里用它跑下載實測速度穩(wěn)定在3.5M字節(jié)/秒左右也就是大概28Mbps的有效吞吐做遠程配置、數(shù)據(jù)采集、固件升級這類應(yīng)用完全夠用。這篇文章就圍繞這個項目梳理一下為什么選W5500而不是軟核方案、SPI協(xié)議里有哪些關(guān)鍵寄存器、FPGA驅(qū)動模塊怎么設(shè)計、3.5M字節(jié)/秒是怎么測出來的以及下載下來的驅(qū)動怎樣才能做到“直接用”。如果你是第一次在FPGA上接網(wǎng)絡(luò)功能這篇文章能省你不少查手冊的時間。1. 選型邏輯FPGA配W5500而不是CPU軟核跑lwIP1.1 硬件協(xié)議棧把網(wǎng)絡(luò)功能變成了寄存器訪問FPGA上實現(xiàn)以太網(wǎng)其實有三條路可以走。第一是用內(nèi)嵌MAC加外部PHY自己寫或者移植TCP/IP協(xié)議棧靈活度最高但工作量非常大要處理ARP、IP分片、TCP重傳、超時管理這些細節(jié)。第二是在FPGA里跑MicroBlaze或者Nios II軟核再在軟核上跑lwIP協(xié)議棧好處是代碼生態(tài)成熟壞處是軟件調(diào)試鏈路長而且軟核和緩存會占用不少片內(nèi)資源。第三就是今天說的W5500方案芯片內(nèi)部把MAC、PHY、TCP/IP協(xié)議棧全部做成了硬件邏輯FPGA只負責通過SPI發(fā)命令和搬運數(shù)據(jù)。從FPGA開發(fā)者的視角看W5500就是一個SPI從設(shè)備你不需要懂TCP的擁塞控制不需要處理ACK重傳只要按順序操作寄存器協(xié)議棧內(nèi)部會自動把連接狀態(tài)維護好。這個思路很像在生活中請了一個專門處理信件往來的文員你只需要把信投進郵箱剩下寄信、回執(zhí)、催件都是他搞定。對FPGA工程師來說這種“把復雜協(xié)議藏起來”的做法特別解壓因為時序和狀態(tài)機才是我們擅長的事情。1.2 這套組合最適合什么樣的項目我實際用下來覺得這套方案最適合下面幾類場景。一類是吞吐需求在幾十Mbps以內(nèi)就夠用的工業(yè)數(shù)據(jù)采集和遠程監(jiān)控設(shè)備這類設(shè)備往往既要聯(lián)網(wǎng)又不能把FPGA資源都花在協(xié)議棧上。另一類是FPGA里已經(jīng)跑了很多信號處理或者圖像算法邏輯剩余資源緊張根本塞不下一個完整的CPU子系統(tǒng)。還有一類是項目排期緊希望一兩周內(nèi)就能把網(wǎng)絡(luò)功能穩(wěn)定接起來前期不想花太多時間在協(xié)議棧上。反過來它也有明顯的邊界。如果項目要求萬兆或者至少千兆線速轉(zhuǎn)發(fā)W5500完全不夠看如果需要同時維護幾十個TCP連接或者要處理非標準私有協(xié)議硬件協(xié)議棧的靈活性也會成為限制。W5500最多提供4個Socket每個Socket的收發(fā)緩沖是可配置的但協(xié)議棧是固化的想改TCP窗口策略或者加私有選項幾乎不可能。選型時候要有這個預期別拿它和通用處理器去比。2. W5500 SPI協(xié)議要點三段式幀與關(guān)鍵寄存器2.1 三段式SPI幀結(jié)構(gòu)W5500的SPI每次完整訪問都分三個階段。地址階段是16bit最高位表示讀還是寫接著3bit選擇寄存器區(qū)域低12bit是寄存器偏移地址。區(qū)域選擇可以指向通用寄存器、Socket0到Socket3的寄存器塊以及對應(yīng)的收發(fā)數(shù)據(jù)緩沖區(qū)。控制階段是8bit主要用低3位來表示讀操作的數(shù)據(jù)長度這也是W5500支持連續(xù)讀的關(guān)鍵如果是連續(xù)傳輸幾十字節(jié)這個控制字段能告訴芯片接下來要讀多長。數(shù)據(jù)階段就是真正要讀寫的那些數(shù)據(jù)字節(jié)。SPI模式要用模式0或者模式3實際工程里我習慣配成CPOL0、CPHA0。片選信號在整次訪問期間必須保持低電平訪問結(jié)束再拉高。有些調(diào)不通的板子問題就出在CS時序上CS和SCK相對關(guān)系不對讀出來的數(shù)據(jù)全是0xFF。建議上板之前先用邏輯分析儀抓一下CS、SCK、MOSI、MISO四根線確認波形和手冊一致再往下寫驅(qū)動邏輯。2.2 初始化、TCP連接、收發(fā)數(shù)據(jù)要關(guān)注哪些寄存器以TCP客戶端為例最小操作序列大概是這樣的。先寫MR寄存器配置芯片做軟復位然后延時一段時間等待芯片穩(wěn)定。接著設(shè)置Sn_MR把當前Socket配置成TCP模式往Sn_CR寫OPEN命令。再之后寫目標IP地址和端口發(fā)CONNECT命令輪詢Sn_SR直到進入SOCK_ESTABLISHED狀態(tài)。發(fā)送數(shù)據(jù)的時候先讀Sn_TX_FSR確認發(fā)送緩沖剩余空間把用戶數(shù)據(jù)連續(xù)寫到Sn_TX_BUF區(qū)域更新Sn_TX_WR寫指針再往Sn_CR發(fā)SEND命令。接收數(shù)據(jù)時先讀Sn_RX_RSR看接收緩沖有多少字節(jié)有數(shù)據(jù)就連續(xù)讀Sn_RX_BUF讀完更新Sn_RX_RD讀指針最后發(fā)RECV命令釋放緩沖。這個流程里最容易出錯的是指針更新Sn_TX_WR、Sn_RX_RD這類指針最好連續(xù)寫兩次讓芯片內(nèi)部正確鎖存邊界否則會在高負載下偶發(fā)丟包。3. FPGA驅(qū)動模塊設(shè)計SPI狀態(tài)機與對外接口3.1 SPI主機狀態(tài)機的實現(xiàn)思路驅(qū)動模塊的底層需要實現(xiàn)一個SPI主機它負責把地址、控制、數(shù)據(jù)三個階段按位串行送出去。我用移位寄存器加計數(shù)器來做一個發(fā)送移位寄存器、一個接收移位寄存器、一個bit計數(shù)器主狀態(tài)機在IDLE、ADDR、CTRL、DATA、DONE之間跳轉(zhuǎn)。ADDR階段把16bit地址逐位移出CTRL階段送控制字節(jié)DATA階段如果是寫則將數(shù)據(jù)逐位移出如果是讀則從MISO采樣并拼成字節(jié)。網(wǎng)上很多驅(qū)動喜歡把狀態(tài)機做得特別復雜用多個嵌套的條件判斷。我按字節(jié)邊界精簡狀態(tài)后整個狀態(tài)機非常干凈。關(guān)鍵點在于SPI是位流但寄存器是字節(jié)對齊的所以每次狀態(tài)切換必須發(fā)生在字節(jié)邊界上不能用半字節(jié)判斷。這里用計數(shù)器對8取模到0的時候再切換時序上不容易出氣泡。3.2 對外接口寫地址、給數(shù)據(jù)、看結(jié)果驅(qū)動模塊設(shè)計完以后頂層接口我刻意做得非??酥品殖闪巳M。配置類接口就是一組簡單的讀寫握手cfg_valid、cfg_addr、cfg_wdata、cfg_rdata、cfg_done用戶想讀寫W5500任意寄存器直接給地址給數(shù)據(jù)就行。發(fā)送類接口用了類似AXI-Stream的風格有tx_valid、tx_ready、tx_data和tx_last整包數(shù)據(jù)輸入模塊內(nèi)部在包尾自動觸發(fā)SEND命令。接收類接口同樣有rx_valid、rx_ready、rx_data、rx_lastW5500收到完整數(shù)據(jù)包后驅(qū)動會把數(shù)據(jù)流水一樣輸出。我給上層用戶留了一組狀態(tài)信號鏈路建立后會拉高link_upTCP連接成功后會拉高socket_established。用這三個接口用戶完全不需要了解W5500的寄存器映射、SPI幀格式和Socket模式只要在連接建立后往發(fā)送接口喂數(shù)據(jù)有數(shù)據(jù)進來接收接口就會給valid脈沖需要流控就拉低ready。很多拿到驅(qū)動的人第一反應(yīng)是“這就完事了”但確實不需要更多東西接口做得直接使用門檻才低。4. 實測3.5M字節(jié)/秒測法、限速分析與提速4.1 測試環(huán)境和測法測試環(huán)境是Kintex-7開發(fā)板加一塊W5500模塊PC用網(wǎng)線直連模塊的RJ45座PC端跑iperf3或者直接通過HTTP下載文件。FPGA側(cè)SPI時鐘最開始只配了20MHz實測下載速度只有1.2M字節(jié)/秒左右。后來把SPI時鐘分頻系數(shù)調(diào)小提升到50MHz之后實測速度穩(wěn)定在3.5M字節(jié)/秒。這個數(shù)字對應(yīng)約28Mbps有效吞吐雖然和千兆網(wǎng)完全不是一個量級但用來做現(xiàn)場數(shù)據(jù)采集和遠程運維已經(jīng)很夠用。測量下載速度比ping更有參考價值因為下載是連續(xù)的大數(shù)據(jù)包能同時壓測SPI總線、內(nèi)部緩沖和協(xié)議棧處理能力。ping只能說明連接通斷測不出吞吐天花板。建議大家在項目驗收時也用iperf這類工具壓一壓收發(fā)通路這樣對整個系統(tǒng)的性能上限會更有數(shù)。4.2 為什么上限在這里以及還能怎么擠理論上SPI時鐘50MHz下字節(jié)速率是6.25M字節(jié)/秒實測3.5M字節(jié)/秒意味著有效負載只占56%左右其余開銷主要在三個地方。第一個是寄存器讀寫都要額外傳2字節(jié)地址加1字節(jié)控制如果頻繁單字節(jié)讀寫有效吞吐會非常低。第二個是W5500內(nèi)部Socket緩沖操作有同步等待寫指針更新后芯片內(nèi)部需要時間完成狀態(tài)切換。第三個是收發(fā)共用同一根SPI總線半雙工機制決定了不能一邊收一邊發(fā)。實測中最有效的優(yōu)化是減少小幀讀寫頻率盡量使用連續(xù)塊傳輸。讀Sn_RX_BUF的時候一次把整包數(shù)據(jù)讀完而不是分多次小幀讀寫Sn_TX_BUF的時候把整包數(shù)據(jù)積攢在一起連續(xù)寫幾十甚至幾百字節(jié)再發(fā)SEND命令。同樣條件下連續(xù)讀256字節(jié)比逐字節(jié)讀吞吐提升接近一倍這個差距在高速采集項目中非常明顯。5. 調(diào)試中踩過的三個坑與最終定位5.1 復位時序和初始化順序第一個坑是上電后立刻配置寄存器會偶發(fā)失敗。W5500內(nèi)部PHY上電需要穩(wěn)定時間芯片還要做內(nèi)部校準這個期間SPI雖然能訪問但寄存器的配置可能不生效。后來在驅(qū)動初始化狀態(tài)機里加了嚴格時序先拉低RST引腳保持至少50us釋放后再延時等待芯片內(nèi)部穩(wěn)定最后才開始寫MR軟復位并執(zhí)行后續(xù)配置。加了這一個狀態(tài)之后實測上電成功率從大概70%提升到了接近100%。這個坑在仿真階段幾乎發(fā)現(xiàn)不了因為仿真模型不會模擬PHY上電時序只有在真機上電時才暴露。建議拿到開發(fā)板先寫一個最簡單的寄存器回讀測試確認版本寄存器能讀到正確值再繼續(xù)配置Socket否則問題排查時很難分清是時序問題還是協(xié)議問題。5.2 接收緩沖環(huán)形回卷和指針更新第二個坑出現(xiàn)在接收方向。W5500的接收緩沖本質(zhì)是環(huán)形緩沖區(qū)如果Sn_RX_RSR指示的數(shù)據(jù)量跨越了緩沖末尾直接線性讀取會讀到回卷邊界之外數(shù)據(jù)就錯位了。正確處理是每次接收前先根據(jù)當前讀指針計算到緩沖末尾還剩多少空間再和Sn_RX_RSR取最小值分段讀取讀完后統(tǒng)一更新Sn_RX_RD指針并執(zhí)行RECV命令。我一開始圖省事直接按Sn_RX_RSR長度連續(xù)讀結(jié)果在數(shù)據(jù)量大時經(jīng)常出現(xiàn)半個包亂碼。后來抓了W5500的輸出波形才發(fā)現(xiàn)是環(huán)形回卷導致的問題。這個坑在普通TCP連接測試中不一定觸發(fā)只有長時間高負載跑數(shù)據(jù)時才會暴露排查起來比較隱蔽。5.3 中斷標志位的清除方式第三個坑是中斷標志的清除方式。W5500的中斷寄存器是寫1清除但有些Socket的中斷標志位如果清除不干凈會導致后續(xù)中斷不觸發(fā)。我們遇到過TCP連接建立后只能收到第一包數(shù)據(jù)之后就完全沒有中斷的情況最后定位到是清中斷時用了整字節(jié)覆蓋把不該清的狀態(tài)位也寫掉了。正確做法是讀-改-寫只把當前需要處理的中斷位寫1其他位保持原值。這個坑特別容易出現(xiàn)在復用了別人代碼的情況下大家在處理中斷標志位時都要養(yǎng)成讀-改-寫的習慣別直接賦值一個固定常量。中斷標志處理錯誤不會立刻暴露通常在連續(xù)收發(fā)一段時間后才會讓協(xié)議??ㄋ蓝ㄎ怀杀竞芨摺?. 下載即可用的移植方式與擴展建議6.1 引腳約束和時鐘分頻配置下載驅(qū)動源碼后第一步是核對W5500模塊的引腳。大多數(shù)W5500模塊是3.3V電平FPGA端IO要對應(yīng)配置成LVCMOS33。如果開發(fā)板IO是2.5V或者1.8V需要加電平轉(zhuǎn)換或選擇兼容的Bank。復位信號要連接到模塊的RST引腳片選、SCK、MOSI、MISO四根線對應(yīng)連接。部分模塊還帶中斷引腳用來告訴主機有事件發(fā)生如果不想輪詢中斷標志可以把這個引腳接到FPGA的輸入中斷上。時鐘分頻參數(shù)SPI_CLK_DIV決定SPI實際速率。外部輸入時鐘如果是50MHz建議先配一個比較大的分頻系數(shù)讓SPI時鐘穩(wěn)定工作在10MHz到20MHz先把鏈路調(diào)通再逐步提高頻率。不要一上來就直接跑到W5500支持的上限真機走線、電平轉(zhuǎn)換都會影響時序留點余量更穩(wěn)。6.2 頂層例化與驗證路徑驅(qū)動模塊的例化非常直接下面是一個最小化的示例w5500_driver #( .SPI_CLK_DIV(4) ) u_w5500 ( .clk (clk_50m), .rst_n (rst_n), .spi_cs_n (w5500_cs_n), .spi_sck (w5500_sck), .spi_mosi (w5500_mosi), .spi_miso (w5500_miso), .rst_out (w5500_rst), .link_up (link_up), .socket_established (conn_flag), .tx_valid (user_tx_valid), .tx_ready (user_tx_ready), .tx_data (user_tx_data), .tx_last (user_tx_last), .rx_valid (user_rx_valid), .rx_ready (user_rx_ready), .rx_data (user_rx_data), .rx_last (user_rx_last) );接好例化之后建議先做一次簡單的回環(huán)自測在FPGA內(nèi)部把接收FIFO的數(shù)據(jù)再回填到發(fā)送接口板上跑起來后PC端用TCP工具發(fā)送一段數(shù)據(jù)如果FPGA能把數(shù)據(jù)原樣回傳說明底層通路已經(jīng)通了一半。然后再跑iperf或者大文件下載驗證持續(xù)吞吐。這樣分階段驗證出錯時能快速縮小范圍。6.3 后續(xù)擴展的幾個方向驅(qū)動本身做得很精簡后續(xù)要擴展也不難。想做多路通信可以繼續(xù)開放其他Socket四個Socket都能獨立工作。想把驅(qū)動掛到復雜系統(tǒng)里可以把發(fā)送接收接口前面加一個FIFO做速率匹配避免用戶邏輯偶爾忙不過來時丟包。如果項目里處理器是ZYNQ也可以把底層的SPI主接口換成AXI接口方便ARM端遠程訪問。整體來看這套W5500的FPGA驅(qū)動方案最大的價值是把網(wǎng)絡(luò)協(xié)議棧的復雜度屏蔽掉了留下的就是很標準的讀寫、收發(fā)、狀態(tài)檢測接口。實測3.5M字節(jié)/秒的速度在工業(yè)場景里完全夠用接口簡潔到接上就能跑這也是為什么我們在項目里把網(wǎng)絡(luò)功能固定成這個方案的原因。如果你也在給FPGA找網(wǎng)絡(luò)方案建議按這個思路先跑通一個最小系統(tǒng)再按實際需要擴展。本文還有配套的精品資源點擊獲取