UDP網(wǎng)絡(luò)通信:從RGMII到ARP的完整實戰(zhàn)指南)
先問一句你手頭那個數(shù)據(jù)采集FPGA的項目最后是不是都卡在數(shù)據(jù)傳輸上串口太慢FIFO只能在板子里自嗨SD卡又沒法實時看波形。這一篇part.7我們來把網(wǎng)口這件事徹底做通——在FPGA里寫一個最精簡但能實際運行的UDP網(wǎng)絡(luò)通信模塊配上板載PHY芯片讓板卡和電腦之間通過網(wǎng)線實現(xiàn)雙向用戶數(shù)據(jù)收發(fā)。核心關(guān)鍵詞就是FPGA網(wǎng)絡(luò)通信設(shè)計不講大而全的協(xié)議棧只講從物理層到UDP打包這幾層最容易被新手卡住的地方。這個方案適用的人群很明確已經(jīng)會Verilog基礎(chǔ)語法、寫過狀態(tài)機和FIFO但還沒碰過以太網(wǎng)的進階學習者。別擔心eth看起來像一堆協(xié)議實際上FPGA里要做的只是按固定字節(jié)表把數(shù)據(jù)發(fā)出去再把收到的包按字節(jié)表拆開沒那么玄乎。1. 先搭好整體思路從物理層到UDP的完整鏈路1.1 為什么第一套協(xié)議棧選UDP而不是TCP一說要搞網(wǎng)絡(luò)通信很多人第一反應(yīng)是TCP。我勸你冷靜。TCP在軟件里很成熟但在FPGA里實現(xiàn)等于要自己用Verilog寫一整套狀態(tài)機來維護連接建立、序號確認、超時重傳、滑動窗口。光是連接狀態(tài)和重傳定時器就能把初學者的耐心耗光更別說出錯后的調(diào)試難度。UDP就不一樣無連接、無狀態(tài)、無重傳通信雙方直接往網(wǎng)絡(luò)上丟數(shù)據(jù)報就行。鏈路通不通、對方收沒收到全部由應(yīng)用層自己負責。恰恰是這種不靠譜給了FPGA用簡單寄存器邏輯實現(xiàn)網(wǎng)絡(luò)通信的可能。而且從實際項目看FPGA負責的往往是高速數(shù)據(jù)流——圖像采集、ADC采樣、過程控制這類場景全都是我發(fā)你收丟一個包在應(yīng)用層加個序號就能解決。所以不要覺得選UDP低級嵌入式界一直這么干這是性價比最高的方案。1.2 硬件平臺開發(fā)板上的PHY和網(wǎng)口做FPGA網(wǎng)絡(luò)通信硬件上最少需要三樣FPGA芯片、PHY芯片、RJ45座子通常帶網(wǎng)絡(luò)變壓器。目前主流開發(fā)板比如黑金、正點原子悟空、米聯(lián)客的板子多數(shù)都集成了千兆PHY常見型號有RTL8211、YT8531、88E1512。這些PHY基本都支持RGMII接口這也是FPGA最常用的連接方式。如果你的板子上沒有網(wǎng)口也可以單獨買一塊PHY小模塊用引腳和FPGA連起來。這時候要先確認兩件事PHY的地址一般由硬件引腳決定常見0x00或0x01以及PHY的工作模式RGMII千兆還是RMII百兆。多數(shù)開發(fā)板上PHY的RGMII/千兆模式通過硬件上下拉已經(jīng)固定好了不需要寫寄存器也能跑只用MDIO做可選配置。這一點后面會專門講。1.3 模塊劃分把協(xié)議棧拆成容易寫的小塊協(xié)議棧聽上去復(fù)雜實際上FPGA里面只做鏈路層網(wǎng)絡(luò)層傳輸層的子集。我用到的模塊劃分是這樣模塊功能所處時鐘域rgmii_tx / rgmii_rx處理RGMII物理層DDR收發(fā)TXC / RXCmac_frame_tx / mac_frame_rx組裝/解析以太網(wǎng)幀計算CRCTXC / RXCarp_module偵聽ARP請求構(gòu)造ARP應(yīng)答用戶時鐘域ip_udp_tx用戶數(shù)據(jù)封裝成IPUDP幀用戶時鐘域ip_udp_rx解析IPUDP頭提取payload用戶時鐘域async_fifo跨時鐘域緩存用戶數(shù)據(jù)雙口頂層把用戶邏輯產(chǎn)生的數(shù)據(jù)寫進發(fā)送FIFOip_udp_tx從FIFO讀出依次拼上UDP頭、IP頭、MAC頭、CRC交給rgmii_tx發(fā)出去。接收方向反著來rgmii_rx收到數(shù)據(jù)mac_frame_rx剝掉幀頭ip_udp_rx再把UDP payload提取出來寫進接收FIFO供用戶模塊讀取。這樣一拆你會發(fā)現(xiàn)每個模塊其實只干一件事比寫一個萬能網(wǎng)絡(luò)模塊容易十倍。2. 動手第一步RGMII接口和時鐘最容易被輕視的物理層2.1 RGMII的信號與時序RGMII全稱是Reduced Gigabit Media Independent Interface引腳非常少收發(fā)各只需要4根數(shù)據(jù)線加1根控制線。千兆以太網(wǎng)線速是1Gbps但數(shù)據(jù)線只有4根所以靠DDR技術(shù)解決時鐘頻率125MHz在時鐘上升沿傳低4位下降沿傳高4位等效每個時鐘周期傳8bit正好湊出千兆速度。信號名方向說明TXCFPGA→PHY發(fā)送時鐘125MHzTXD[3:0]FPGA→PHY上升沿發(fā)低4bit下降沿發(fā)高4bitTX_CTLFPGA→PHY上升沿TX_EN下降沿TX_ERRXCPHY→FPGA接收時鐘由PHY恢復(fù)RXD[3:0]PHY→FPGA上升沿采低4bit下降沿采高4bitRX_CTLPHY→FPGA上升沿RX_DV下降沿RX_ER這里最容易踩坑的是時鐘。發(fā)送時鐘TXC必須由FPGA側(cè)產(chǎn)生是精確的125MHz不能用邏輯自己分頻去驅(qū)動引腳必須走PLL/MMCM。很多開發(fā)板的PHY還有一根外接25MHz晶振那是給PHY自己用的和FPGA里的TXC不是一回事。2.2 發(fā)送方向ODDR控制數(shù)據(jù)和時鐘發(fā)送方向的本質(zhì)是在每個時鐘沿把對應(yīng)的4bit放到TXD上。在Xilinx FPGA里我一般直接用ODDR原語來完成DDR輸出而不是自己寫兩個always塊——后者綜合容易出問題。ODDR的原語調(diào)用大概這樣ODDR #(.DDR_CLK_EDGE(SAME_EDGE)) u_txd0 ( .Q (txd[0]), .D1 (tx_byte[0]), // 上升沿發(fā)出的bit .D2 (tx_byte[4]), // 下降沿發(fā)出的bit .C (clk_125m), .CE (1b1), .R (1b0), .S (1b0) );TX_CTL也是同樣的道理把發(fā)送使能tx_en接到D1、錯誤指示tx_er接到D2即可。TXC本身一般用MMCM生成125MHz后再通過一個ODDR把時鐘搬出來。需要注意TXC和TXD的相位關(guān)系必須對齊大多數(shù)PHY要求數(shù)據(jù)相對時鐘中心對齊FPGA端只要保證兩者來自同一個時鐘源、路徑長度接近普通板子都能正常工作。2.3 接收方向RXC時鐘域與異步FIFO接收方向是很多新手翻車的地方。RXC是PHY從數(shù)據(jù)中恢復(fù)出來的時鐘它和FPGA的系統(tǒng)時鐘完全是兩個不同的時鐘源。必須用RXC去采樣RXD和RX_CTL然后用異步FIFO把數(shù)據(jù)轉(zhuǎn)到用戶時鐘域絕不能用系統(tǒng)125MHz時鐘去直接采RXD那樣會偶爾采到亞穩(wěn)態(tài)出現(xiàn)幀偶爾丟一個字節(jié)這種極其難查的bug。接收側(cè)DDR解出8bit數(shù)據(jù)之后連同rx_valid一起寫進一個深度256的異步FIFO就夠緩存一個幀了。用戶邏輯從FIFO讀側(cè)再逐字節(jié)拿出來解析。這個異步FIFO是整個設(shè)計里最容易偷懶也最容易埋雷的地方建議直接用Xilinx的FIFO Generator IP設(shè)置獨立時鐘模式比手寫穩(wěn)得多。還有一種情況RGMII接收方向上PHY輸出的RXD和RXC之間會存在一定的走線延時如果板子布線不好或者PHY沒有開內(nèi)部延遲你可能會發(fā)現(xiàn)收到的首字節(jié)偶爾錯位。解決辦法有兩個改PHY寄存器的rx delay或者在FPGA里用IDELAY原語調(diào)整采樣點。多數(shù)開發(fā)板默認已經(jīng)開好如果遇到抓包首字節(jié)全是垃圾優(yōu)先去查PHY手冊里的delay寄存器。3. 寫入和解析以太網(wǎng)幀MAC邏輯與CRC32實現(xiàn)3.1 以太網(wǎng)幀格式只有四段需要操心以太網(wǎng)幀在鏈路上按順序是這么排的字段長度內(nèi)容說明前導(dǎo)碼7字節(jié)每個字節(jié)0x55幀起始定界符SFD1字節(jié)0xD5目的MAC6字節(jié)目標設(shè)備的MAC地址源MAC6字節(jié)本端MAC地址類型/長度2字節(jié)0x0800IPv40x0806ARP數(shù)據(jù)載荷46~1500字節(jié)上層協(xié)議數(shù)據(jù)FCS4字節(jié)CRC32校驗發(fā)送時前導(dǎo)碼和SFD用狀態(tài)機固定依次送出。目標MAC、源MAC、類型這些拼在幀頭。當載荷不足46字節(jié)時必須填充到46字節(jié)再算CRC這是以太網(wǎng)最小幀長64字節(jié)的要求否則對端網(wǎng)卡會直接丟棄。幀和幀之間還要留至少12字節(jié)的幀間隙IFGPC網(wǎng)卡才認。很多初學者發(fā)完一幀立刻發(fā)下一幀導(dǎo)致wireshark里看著連續(xù)實際上PC丟包率很高。3.2 發(fā)送狀態(tài)機的字段拼裝細節(jié)FPGA發(fā)送MAC幀通常用一個狀態(tài)機來完成逐字節(jié)輸出狀態(tài)機設(shè)計成IDLE → PREAMBLE → DA → SA → TYPE → PAYLOAD → FCS → IFG。每個狀態(tài)穩(wěn)定輸出一個時鐘周期的字節(jié)和valid信號。關(guān)鍵點在于PAYLOAD狀態(tài)的地址和長度控制。在用戶數(shù)據(jù)進入發(fā)送FIFO之前需要先把要發(fā)的UDP報文長度固定下來然后由發(fā)送狀態(tài)機從FIFO里讀多少字節(jié)、不足填充0都要在PAYLOAD狀態(tài)里用一個計數(shù)器精確控制。CRC要算整個DA到PAYLOAD的內(nèi)容所以常用實現(xiàn)是把所有待發(fā)字節(jié)一邊送給RGMII發(fā)送一邊同時送給CRC計算模塊到FCS狀態(tài)時把CRC結(jié)果送出去而不是先算好再發(fā)。3.3 CRC32多項式、初值和最容易錯的地方CRC32是FPGA網(wǎng)絡(luò)設(shè)計里最容易翻車的地方。標準還太容易搞混的點多項式是0x04C11DB7需要做位反射初值全1結(jié)果需要再異或全1。很多同學直接用軟件算法搬到Verilog結(jié)果發(fā)出去wireshark里的FCS怎么都對不上。說實話在FPGA里手寫并行CRC32對新手不友好我一開始也是靠抄表。你可以用兩種辦法之一用Xilinx的CRC Generator IP核選CRC-32、多項式0x04C11DB7、初值0xFFFFFFFF、輸出異或0xFFFFFFFF、按字節(jié)輸入直接生成并行CRC計算器。用查表法寫一個8位CRC表256項狀態(tài)機每處理一個字節(jié)查一次表1個時鐘處理1字節(jié)速度完全夠。這里提醒一個字節(jié)序的坑CRC結(jié)果的32位在寫入FCS字段時不是直接按計算值的最高字節(jié)在前發(fā)送而是要看鏈路層的位反轉(zhuǎn)。實戰(zhàn)中最快的驗證方法是發(fā)一個已知payload的包用wireshark看它期望的FCS是多少和你的CRC輸出比較。我第一次調(diào)就卡在這個字節(jié)序上折騰了兩天才發(fā)現(xiàn)結(jié)果是bit反射之后從低字節(jié)開始發(fā)送。4. 讓FPGA能被Ping通ARP協(xié)議的最簡實現(xiàn)4.1 為什么沒有ARP就發(fā)不出UDP包看到這里你可能急著寫UDP。別急先搞清一個事實如果要讓PC往FPGA發(fā)UDP包PC必須知道FPGA的MAC地址但如果PC不知道它第一個動作是發(fā)一個ARP廣播包詢問192.168.1.10的MAC地址是誰。FPGA如果不應(yīng)答ARPPC發(fā)的UDP包根本到不了板子。反過來FPGA要向PC發(fā)包也必須知道PC的MAC地址。最簡單辦法PC先ping一下FPGAFPGA從收到的ARP請求里學到對應(yīng)IP的MAC然后緩存起來或者干脆在代碼里人為配置PC的MAC不過這樣換臺電腦就麻煩了。所以先做ARP應(yīng)答讓FPGA能被ping通是一個標志性里程碑——代表物理層、MAC層、解析邏輯全部跑通。4.2 最小ARP應(yīng)答模塊的設(shè)計思路ARP請求幀的以太網(wǎng)類型是0x0806MAC層解包后會得到一個28字節(jié)的ARP報文。報文格式如下字段長度說明硬件類型2字節(jié)以太網(wǎng)1協(xié)議類型2字節(jié)IPv40x0800硬件地址長度1字節(jié)6協(xié)議地址長度1字節(jié)4操作碼2字節(jié)1請求2應(yīng)答發(fā)送方MAC6字節(jié)請求方MAC發(fā)送方IP4字節(jié)請求方IP目標MAC6字節(jié)請求時通常全0目標IP4字節(jié)本機IP最小ARP模塊要做的事接收端檢測到以太網(wǎng)類型0x0806且操作碼為1再比對目標IP是不是本端的IP是就當場構(gòu)造一個28字節(jié)的應(yīng)答包。應(yīng)答包里的目標MAC填請求方MAC目標IP填請求方IP源MAC填自己然后交給MAC發(fā)送模塊發(fā)出去。不需要緩存表不需要額外狀態(tài)機維護。4.3 MAC地址與IP配置參數(shù)化才能方便調(diào)本機MAC和本機IP建議做成模塊參數(shù)或者寄存器方便后續(xù)修改。測試環(huán)境我建議這么配PC靜態(tài)IP192.168.1.100掩碼255.255.255.0FPGA端IP192.168.1.10FPGA端MAC例如00:11:22:33:44:55只要不沖突就行兩塊設(shè)備直接用一根網(wǎng)線連不需要交換機加載完bit后Ping一下。如果通了說明FPGA的RGMII發(fā)送、MAC發(fā)送、ARP解析全通了這是一個非常值得慶祝的時刻。5. 真正的數(shù)據(jù)通路UDP封裝與回環(huán)收發(fā)設(shè)計5.1 IP頭和UDP頭怎么拼校驗和怎么算UDP報文封裝在IP包里IP包封裝在MAC幀里。寫給FPGA的發(fā)送狀態(tài)機時本質(zhì)就是按固定順序輸出以下字節(jié)序列MAC頭目的MAC源MAC0x0800→ IP頭20字節(jié) → UDP頭8字節(jié) → 用戶數(shù)據(jù)payload。IP頭字段如下字段長度常見值/說明版本/IHL1字節(jié)0x45IPv4頭長5個32位字DSCP/ECN1字節(jié)0x00總長度2字節(jié)IP頭20 UDP頭8 payload長度標識ID2字節(jié)每個包遞增即可小流量不用管Flags/分片偏移2字節(jié)0x0000不分片TTL1字節(jié)0x40或0x80協(xié)議1字節(jié)17UDP頭校驗和2字節(jié)只算IP頭源IP4字節(jié)FPGA的IP目的IP4字節(jié)PC的IPIP校驗和的算法很簡單把IP頭按16bit一組累加高位溢出回卷最后取反。用Verilog的函數(shù)實現(xiàn)大概是function [15:0] ip_checksum; input [159:0] hdr; // 20字節(jié)IP頭 integer i; reg [31:0] sum; begin sum 0; for (i 0; i 10; i i 1) sum sum hdr[i*16 : 16]; sum (sum 16hFFFF) (sum 16); sum (sum 16hFFFF) (sum 16); ip_checksum ~sum; end endfunctionUDP頭就4個字段源端口2字節(jié)、目的端口2字節(jié)、UDP長度2字節(jié)8payload長度、校驗和2字節(jié)。在IPv4里UDP校驗和可以填0表示接收方跳過校驗。初期千萬不要去寫UDP校驗和因為需要加偽頭參與計算非常容易錯先跑通再優(yōu)化。5.2 發(fā)送鏈路從用戶數(shù)據(jù)到上行幀頂層設(shè)計里用戶邏輯先把要發(fā)的數(shù)據(jù)寫入發(fā)送FIFOip_udp_tx模塊收到開始發(fā)送信號后進入打包狀態(tài)機。我習慣把發(fā)送長度做成寄存器可配置上位機可以通過另一條UDP命令設(shè)置payload長度默認1024字節(jié)。狀態(tài)機輸出順序MAC頭 → IP頭 → UDP頭 → payload → CRC。IP頭里的總長度參數(shù)需要在開始前就定好因為狀態(tài)機是一邊發(fā)一邊算CRC沒有回頭機會。標識ID字段可以每發(fā)一幀累加1PC端看到ID連續(xù)遞增基本能確認數(shù)據(jù)包的完整性。這里有個小技巧把FIFO的讀信號和MAC發(fā)送狀態(tài)機的字節(jié)流對齊。要讓FIFO在讀使能有效后的第一個時鐘周期就把數(shù)據(jù)送到MAC的數(shù)據(jù)線上這需要仔細設(shè)計FIFO讀時序。我用的是標準做法在PAYLOAD狀態(tài)FIFO讀使能拉高下一拍從FIFO輸出讀到的字節(jié)并同時計入CRC直到計數(shù)器到達配置長度。如果配置長度沒有填滿46字節(jié)MAC層的填充邏輯會把后面的填充字節(jié)補上。5.3 接收鏈路與回環(huán)模式接收鏈路是發(fā)送的鏡像。mac_frame_rx剝掉前導(dǎo)碼檢測到類型0x0800后把整個IP包交給ip_udp_rx。ip_udp_rx解析IP頭確認協(xié)議字段是17再解析UDP頭拿到端口和長度把payload寫進接收FIFO同時產(chǎn)生一個rx_pkt_end信號通知用戶邏輯。最直接驗證整條鏈路的方式是做回環(huán)頂層模塊把接收FIFO讀出的數(shù)據(jù)原封不動寫進發(fā)送FIFO。這樣PC發(fā)任何UDP包FPGA都會立刻原樣發(fā)回來現(xiàn)象就是PC發(fā)什么PC收什么。這一步能通就證明雙向通路全部OK后面替換成采集數(shù)據(jù)或命令解析邏輯就水到渠成。要注意接收和發(fā)送端口可以不一樣。比如PC往FPGA發(fā)5000端口FPGA從5001端口回環(huán)出去。Wireshark里能同時看到這兩個包回環(huán)源IP會顯示成FPGA的IP源MAC也會變成FPGA的MAC。6. 上板實測從Ping到UDP回環(huán)的調(diào)試流程6.1 鏈路Up之前的檢查順序如果插上網(wǎng)線后PC顯示網(wǎng)絡(luò)電纜被拔出或者Link燈不亮先別急著查FPGA代碼十有八九是PHY層面的問題。我一般按順序排查上電后檢查PHY的復(fù)位引腳是否釋放開發(fā)板一般由FPGA控制復(fù)位可能是低有效確認復(fù)位后過幾十毫秒再操作。用ILA抓PHY的中斷/狀態(tài)寄存器或者看板子上的Link LED是否亮起。確認PC網(wǎng)卡IP是靜態(tài)配置Windows里面把防火墻對文件和打印機共享之外的雙向通信都放行或者直接用調(diào)試工具。如果PHY已經(jīng)Link Up但ping不通問題就在MAC邏輯或ARP邏輯要用ILA逐級抓包。6.2 用ILA抓關(guān)鍵信號的經(jīng)驗ILA抓信號是調(diào)試RGMII的最有力手段但新手經(jīng)常抓手不對。RXD[3:0]是DDR信號直接抓原始的RXD是抓不出有效波形的要在IDDR轉(zhuǎn)換之后、根據(jù)RXC時鐘域提取出的8bit信號上抓。也就是說ILA的時鐘應(yīng)該選擇RXC域里恢復(fù)出來的采樣時鐘觸發(fā)條件可以設(shè)為SFD檢測到8hD5或者rx_valid拉高。如果你寫的是同步設(shè)計ILA時鐘選系統(tǒng)125MHz然后掛在async_fifo讀側(cè)也是可以的觸發(fā)信號選rx_pkt_end。先用同一次觸發(fā)把整條接收鏈路抓全從FIFO讀側(cè)往前對比能看到幀頭就是巨大勝利。還有個小坑ILA不能直接在RGMII的TXC/RXC引腳上抓因為引腳已經(jīng)不是普通邏輯信號了。要抓就抓原語之前的邏輯節(jié)點比如ODDR的D1/D2或者IDDR的Q1/Q2。6.3 Wireshark怎么確認協(xié)議正確性PC端驗證工具我推薦兩個wireshark抓包看協(xié)議細節(jié)網(wǎng)絡(luò)調(diào)試助手或者Python socket發(fā)送UDP數(shù)據(jù)。先用wireshark打開篩選udp。第一次做UDP設(shè)計時看到很多包顯示IP checksum offload或者udp checksum offload不用慌——這是網(wǎng)卡驅(qū)動的校驗和卸載功能不影響數(shù)據(jù)內(nèi)容本身。重點檢查這幾項PC發(fā)出的UDP包目的IP是不是FPGA的IP目的端口對不對。FPGA回環(huán)的包源IP是不是FPGA的IP源端口是不是設(shè)置的5001payload和PC發(fā)出的是不是一致。FCS錯誤是否很多。wireshark如果顯示少量CRC錯誤可能是PHY的rx delay沒調(diào)好需要去配置PHY寄存器如果錯誤出現(xiàn)在PC收到的FPGA包上那是FPGA發(fā)送CRC的問題。調(diào)通后的標準動作是用網(wǎng)絡(luò)調(diào)試助手往FPGA發(fā)一串內(nèi)容比如hello_fpga_01然后看收到的回環(huán)內(nèi)容是不是一字不差。能對上這個網(wǎng)絡(luò)通信模塊就算正式可用了。7. 踩過坑之后的經(jīng)驗和下一步方向7.1 時鐘約束與復(fù)位不處理好就會隨機出錯這個項目對時序約束要求很高尤其TXC的125MHz時鐘必須是約束過的生成時鐘不是隨便一個時鐘就能直接驅(qū)動ODDR輸出。在Vivado里如果MMCM的125MHz輸出沒有正確聲明create_generated_clock靜態(tài)時序分析會報錯但bit文件還是能生成上板以后就可能出現(xiàn)偶爾正常、偶爾卡死的狀況。RXC是PHY傳入的異步時鐘也要做set_input_delay約束。如果你對約束不熟至少要做到先把TXC所連接的MMCM輸出引腳約束到TXC引腳上讓工具自動布線時意識到這是一條時鐘輸出路徑。復(fù)位更要注意不要一上電就立即發(fā)UDP包。PHY芯片從上電到準備好通常要幾十毫秒建議在頂層做一個上電計數(shù)器延遲100ms后再開始發(fā)送和ARP應(yīng)答。另外MAC和用戶邏輯的復(fù)位要分開避免整個鏈路同時復(fù)位。7.2 PHY的MDIO配置不配也能跑配了更可控我遇到的開發(fā)板里約有一半默認就能跑RGMII千兆模式另一半需要寫MDIO寄存器。MDIO是兩根線的管理接口時鐘MDC和雙向數(shù)據(jù)MDIO用來讀寫PHY內(nèi)部寄存器。最常改的寄存器是0x00控制寄存器把ANEN自動協(xié)商使能置1。0x04/0x05狀態(tài)寄存器讀Link狀態(tài)。擴展RGMII delay寄存器不同PHY地址不同比如RTL8211的寄存器0x1E、0x1F用于選擇RX/TX時鐘延遲。如果板子的PHY默認沒有開啟RX時鐘延遲收數(shù)據(jù)會不定時丟字節(jié)。我的經(jīng)驗是先按默認配置跑通過wireshark的CRC統(tǒng)計判斷需要不需要改delay確定需要改的話再寫MDIO。你不需要在FPGA里寫一個復(fù)雜的MDIO控制器一個簡單的狀態(tài)機每次操作發(fā)32個周期管理幀即可。7.3 下一步可以怎么走光口、PCIe還是TCPUDP調(diào)通之后FPGA網(wǎng)絡(luò)通信就進入了一個真能用的狀態(tài)。如果你的下一個場景是更大帶寬的項目可以往兩個方向走一是光口收發(fā)核心是FPGA內(nèi)的高速收發(fā)器GTX/GTH配合SFP模塊實現(xiàn)萬兆級通信邏輯上跟RGMII類似但物理層和時鐘恢復(fù)都有更嚴格的要求二是PCIe通過DMA把大批量數(shù)據(jù)搬到主機內(nèi)存適合上位機需要千兆以上吞吐的場景但學習曲線陡得多。至于TCP如果真的需要一個TCP服務(wù)器我建議不要用純FPGA硬寫協(xié)議棧。更合理的方案是FPGA保持UDP數(shù)據(jù)通路把TCP協(xié)議棧交給旁邊的小型處理器比如Zynq的ARM核兩者之間用AXI總線互聯(lián)——這才是工程上最常見的做法。下次如果有人問你怎么讓FPGA上網(wǎng)你就可以從容地把這篇文章丟過去告訴他先去寫個能ping通的板子吧。