udp點對點通信(正點原子FPGA連載 第二十六章以太網UDP測試實驗)
1)摘自【正點原子】領航者 ZYNQ 之FPGA開發指南
2)實驗平臺:正點原子領航者ZYNQ開發板
3)平臺購買地址:https://item.taobao.com/item.htm?&id=606160108761
4)全套實驗源碼+手冊+視頻下載:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
5)對正點原子FPGA感興趣的同學可以加群討論:876744900
6)正點原子資料更新和新品發布,請加正點原子公眾號:正點原子
關注方法:微信→添加好友→公眾號→輸入:正點原子
第二十六章以太網UDP測試實驗
UDP是一種面向無連接的傳輸層協議,屬于TCP/IP協議簇的一種。UDP具有消耗資源少、通信效率高等優點,通常用來傳輸音頻、視頻等對實時性要求高的場合。本章我們來學習如何通過領航者ZYNQ開發板實現UDP通信的功能。
本章分為以下幾個章節:
2626.1簡介
26.2實驗任務
26.3硬件設計
26.4程序設計
26.5下載驗證
26.1簡介
UDP概述
UDP(User Datagram Protocol),即用戶數據報協議,是一種面向無連接的傳輸層協議。無連接是指在傳輸數據時,數據的發送端和接收端不建立邏輯連接。簡單來說,當一臺計算機向另外一臺計算機發送數據時,發送端不會確認接收端是否存在,就會發出數據,同樣接收端在收到數據時,也不會向發送端反饋是否收到數據。由于使用UDP協議消耗資源小,通信效率高,所以通常都會用于音頻、視頻和普通數據的傳輸(如視頻會議等)都會采用UDP協議進行傳輸,這種情況即使偶爾丟失一兩個數據包,也不會對接收結果產生太大影響。
UDP和TCP是傳輸層中非常重要的兩個協議,位于OSI(Open System Interconnection,開放式系統互聯)參考模型中的第四層(傳輸層),是一種無連接的傳輸層協議,提供面向事務的簡單不可靠信息傳送服務,位于IP協議層(網絡層)之上。OSI將計算機網絡體系結構分為七層:物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層和應用層,OSI參考模型如下圖所示。
圖 26.1.1 OSI參考模型
以太網UDP傳輸單包數據的格式由圖 26.1.2所示。從圖中可以看出,以太網的數據包就是對各層協議的逐層封裝來實現數據的傳輸。用戶數據打包在UDP協議中,UDP協議又是基于IP協議之上的,IP協議又是走MAC層發送的,即從包含關系來說:MAC幀中的數據段為IP數據報,IP報文中的數據段為UDP報文,UDP報文中的數據段為用戶希望傳輸的數據內容。接下來我們逐個來向大家介紹不同層的數據格式。
圖 26.1.2 以太網UDP傳輸數據包格式
其中以太網的幀格式在“以太網ARP測試實驗”中已經向大家作了詳細的介紹,如果對以太網幀格式不熟悉的話,可以參考“以太網ARP測試實驗”。IP協議(互聯網分組交換協議)是TCP/IP協議簇中非常重要的一個協議,下面我們來熟悉下IP協議。
IP協議
IP協議是TCP/IP協議簇中的核心協議,也是TCP/IP協議的載體,IP協議規定了數據傳輸時的基本單元和格式。從前面介紹的圖 26.1.2中可以看出,IP協議位于以太網MAC幀格式的數據段,IP協議內容由IP首部和數據字段組成。所有的TCP、UDP及ICMP數據都以IP數據報格式傳輸,IP數據包格式如圖 26.1.3所示。
圖 26.1.3 IP數據報格式
前20個字節和緊跟其后的可選字段是IP數據報的首部,前20個字節是固定的,后面可選字段是可有可無的,首部的每一行以32位(4個字節)為單位。
版本:4位IP版本號(Version),這個值設置為二進制的0100時表示IPv4,設置為0110時表示IPv6,目前使用比較多的IP協議版本號是4。
首部長度:4位首部長度(IHL,Internet Header Length),表示IP首部一共有多少個32位(4個字節)。在沒有可選字段時,IP首部長度為20個字節,因此首部長度的值為5。
服務類型:8位服務類型(TOS,Type of service),該字段被劃分成兩個子字段:3位優先級字段(現在已經基本忽略掉了)和4位TOS字段,最后一位固定為0。服務類型為0時表示一般服務。
總長度:16位IP數據報總長度(Total Length),包括IP首部和IP數據部分,以字節為單位。我們利用IP首部長度和IP數據報總長度,就可以知道IP數據報中數據內容的起始位置和長度。由于該字段長16bit,所以IP數據報最長可達65535字節。盡管理論上可以傳輸長達65535字節的IP數據報,但實際上還要考慮網絡的最大承載能力等因素。
標識字段:16位標識(Identification)字段,用來標識主機發送的每一份數據報。通常每發送一份報文它的值就會加1。
標志字段:3位標志(Flags)字段,第1位為保留位;第2位表示禁止分片(1表示不分片 0:允許分片);第3位標識更多分片(除了數據報的最后一個分片外,其它分片都為1)。
片偏移:13位片偏移(Fragment Offset),在接收方進行數據報重組時用來標識分片的順序。
生存時間:8位生存時間字段,TTL(Time To Live)域防止丟失的數據包在無休止的傳播,一般被設置為64或者128。
協議:8位協議(Protocol)類型,表示此數據報所攜帶上層數據使用的協議類型,ICMP為1,TCP為6,UDP為17。
首部校驗和:16位首部校驗和(Header Checksum),該字段只校驗數據報的首部,不包含數據部分;校驗IP數據報頭部是否被破壞、篡改和丟失等。
源IP地址:32位源IP地址(Source Address),即發送端的IP地址,如192.168.1.123。
目的IP地址:32位目的IP地址(Destination Address),即接收端的IP地址,如192.168.1.102。
可選字段:是數據報中的一個可變長度的可選信息,選項字段以32bit為界,不足時插入值為0的填充字節,保證IP首部始終是32bit的整數倍。
以上內容是對IP首部格式的詳細闡述,還需要補充的內容是IP首部校驗和的計算方法,其計算步驟如下:
將16位檢驗和字段置為0,然后將IP首部按照16位分成多個單元;
對各個單元采用反碼加法運算(即高位溢出位會加到低位,通常的補碼運算是直接丟掉溢出的高位);
此時仍然可能出現進位的情況,將得到的和再次分成高16位和低16位進行累加;
最后將得到的和的反碼填入校驗和字段。
例如,我們使用IP協議發送一個IP數據報總長度為50個字節(有效數據為30個字節)的數據包,發送端IP地址為192.168.1.123,接收端IP地址為192.168.102,則IP首部數據如下:
圖 26.1.4 IP首部數據
按照上述提到的 IP 首部校驗和的方法計算 IP 首部校驗和,即:
0x4500 + 0x0032 + 0x0000 + 0x4000 + 0x4011 + 0x0000(計算時強制置0) + 0xc0a8 + 0x017b + 0xc0a8 + 0x0166 = 0x24974
0x0002 + 0x4974 = 0x4976
0x0000 + 0x4976 = 0x4976(此種情況并未出現進位)
check_sum = ~0x4976(按位取反) = 0xb689
到此為止IP協議內容已經介紹完了,我們從前面介紹的圖 26.1.2錯誤!未找到引用源。可以知道,UDP的首部和數據位于IP協議的數據段。既然已經有IP協議了,為什么還需要UDP協議呢?為什么我們選擇的是UDP還不是傳輸更可靠的TCP呢?帶著這些疑問我們繼續往下看。
UDP協議
首先回答為什么還需要UDP協議?事實上數據是可以直接封裝在IP協議里而不使用TCP、UDP或者其它上層協議的。然而在網絡傳輸中同一IP服務器需要提供各種不同的服務,各種不同的服務類型是使用端口號來區分的,例如用于瀏覽網頁服務的80端口,用于FTP(文件傳輸協議)服務的21端口等。TCP和UDP都使用兩個字節的端口號,理論上可以表示的范圍為0~65535,足夠滿足各種不同的服務類型。
然后是為什么不選擇傳輸更可靠的TCP協議,而是UDP協議呢?TCP協議與UDP協議作為傳輸層最常用的兩種傳輸協議,這兩種協議都是使用IP作為網絡層協議進行傳輸。下面是TCP協議與UDP協議的區別:
1、TCP協議面向連接,是流傳輸協議,通過連接發送數據,而UDP協議傳輸不需要連接,是數據報協議;
2、TCP為可靠傳輸協議,而UDP為不可靠傳輸協議。即TCP協議可以保證數據的完整和有序,而UDP不能保證;
3、UDP由于不需要連接,故傳輸速度比TCP快,且占用資源比TCP少;
4、應用場合:TCP協議常用在對數據文件完整性較高的一些場景中,如文件傳輸等。UDP常用于對通訊速度有較高要求或者傳輸數據較少時,比如對速度要求較高的視頻直播和傳輸數據較少的QQ等。
首先可以肯定的告訴大家,使用FPGA實現TCP協議是完全沒有問題的,但是,FPGA發展到現在,卻鮮有成功商用的RTL級的TCP協議設計,大部分以太網傳輸都是基于比較簡單的UDP協議。TCP協議設計之初是根據軟件靈活性設計的,如果使用硬件邏輯實現,工程量會十分巨大,而且功能和性能無法得到保證,因此,TCP協議設計并不適合使用硬件邏輯實現。UDP協議是一種不可靠傳輸,發送方只負責數據發送出去,而不管接收方是否正確的接收。在很多場合,是可以接受這種潛在的不可靠性的,例如視頻實時傳輸顯示等。
UDP數據格式如圖 26.1.5所示:
圖 26.1.5 UDP數據格式
UDP首部共8個字節,同IP首部一樣,也是一行以32位(4個字節)為單位。
源端口號:16位發送端端口號,用于區分不同服務的端口,端口號的范圍從0到65535。
目的端口號:16位接收端端口號。
UDP長度:16位UDP長度,包含UDP首部長度+數據長度,單位是字節(byte)。
UDP校驗和:16位UDP校驗和。UDP計算校驗和的方法和計算IP數據報首部校驗和的方法相似,但不同的是IP數據報的校驗和只檢驗IP數據報的首部,而UDP校驗和包含三個部分:UDP偽首部,UDP首部和UDP的數據部分。偽首部的數據是從IP數據報頭和UDP數據報頭獲取的,包括源IP地址,目的IP地址,協議類型和UDP長度,其目的是讓UDP兩次檢查數據是否已經正確到達目的地,只是單純為了做校驗用的。在大多數使用場景中接收端并不檢測UDP校驗和,因此這里不做過多介紹。
以太網的幀格式、IP數據報協議以及UDP協議到這里已經全部介紹完了,關于用戶數據、UDP、IP、MAC四個報文的關系如下圖所示:
圖 26.1.6 以太網包數據格式
用戶數據打包在UDP協議中,UDP協議又是基于IP協議之上的,IP協議又是走MAC層發送的,即從包含關系來說:MAC幀中的數據段為IP數據報,IP報文中的數據段為UDP報文,UDP報文中的數據段為用戶希望傳輸的數據內容。現在再回過頭看圖 26.1.6的內容就非常容易理解了。
26.2實驗任務
本節實驗任務是上位機通過網口調試助手發送數據給FPGA,FPGA通過PL端以太網接口接收數據并將接收到的數據發送給上位機,完成以太網UDP數據的環回。
26.3硬件設計
PL端千兆以太網接口部分的硬件設計原理及本實驗中各端口信號的管腳分配,和“以太網ARP測試實驗”完全相同,請參考“以太網ARP讀寫測試實驗”中的硬件設計部分。
26.4程序設計
圖 26.4.1是根據本章實驗任務畫出的系統框圖。和“以太網ARP測試實驗”相比,將ARP控制模塊替換成了以太網控制模塊,并增加了一個同步FIFO和UDP頂層模塊。本次實驗雖然實現的是UDP通信,但保留了ARP頂層模塊,這是由于上位機應用程序只知道接收端的目的IP地址和端口號,卻不知道接收端的MAC地址,因此這里通過ARP協議來獲取接收端的MAC地址,否則需要在發送端手動綁定接收端MAC地址,而手動綁定的方法較為繁瑣,因此這里保留了ARP協議。
本次實驗同時實現了ARP協議和UDP協議,GMII接收側的引腳同時連接至ARP頂層模塊和UDP頂層模塊,這個兩個模塊會分別根據ARP協議和UDP協議解析數據。而GMII發送側引腳只能和ARP頂層模塊和UDP頂層模塊的其中一個連接,因此以太網控制模塊會根據當前接收到的協議類型,選擇切換GMII發送側引腳和ARP頂層模塊或者UDP頂層模塊連接。除此之外,以太網控制模塊根據輸入的ARP接收的類型,控制ARP頂層模塊返回ARP應答信號。以太網單次會接收到大量數據,因此本次實驗需要一個FIFO模塊用來緩存數據,由于本次實驗所使用的GMII接收時鐘和GMII發送時鐘實際上為同一個時鐘,因此這里使用的是同步FIFO。
圖 26.4.1 以太網UDP測試系統框圖
系統時鐘經過PLL時鐘模塊后,輸出200Mhz的時鐘,用于IDELAYCTRL原語的參考時鐘;GMII TO RGMII模塊負責將雙沿(DDR)數據和單沿(SDR)數據之間的轉換;ARP頂層模塊解析ARP請求命令,并返回開發板的MAC地址;以太網控制模塊根據輸入的ARP接收完成信號類型,控制ARP頂層模塊返回ARP應答信號,并根據當前接收到的協議類型,選擇切換ARP頂層模塊和UDP頂層模塊的GMII發送側引腳;UDP頂層模塊實現了以太網UDP數據包的接收、發送以及CRC校驗的功能。同步FIFO模塊是由Vivado軟件自帶的FIFO IP核生成的,FIFO的大小為2048個32bit,為了能夠滿足單包數據量較大的情況(盡管通常情況下,以太網幀有效數據不超過1500個字節),所以FIFO的深度最好設置的大一點,這里把深度設置為2048,寬度為32位。
各模塊端口及信號連接如下圖所示:
圖 26.4.2 頂層模塊原理圖
由上圖可知,FPGA頂層模塊例化了以下六個模塊,PLL時鐘模塊(clk_wiz)、GMII TO RGMII模塊(gmii_to_rgmii)、ARP頂層模塊(arp)、UDP頂層模塊(udp)、同步FIFO模塊(sync_fifo_2048x32b)和以太網控制模塊(eth_ctrl),實現了各模塊之間的數據交互。
其中GMII TO RGMII(gmii_to_rgmii)模塊和ARP頂層模塊(arp)在“以太網ARP測試實驗”中已經向大家作了詳細的介紹,如果大家對這部分內容不熟悉的話,可以參考“以太網ARP測試實驗”。
本章我們重點介紹UDP頂層模塊(udp)。
UDP頂層模塊實現了整個以太網幀格式與UDP協議的功能,其模塊端口及信號連接如下圖所示:
圖 26.4.3 UDP模塊原理圖
由上圖可知,UDP頂層模塊例化了UDP接收模塊(udp_rx)、UDP發送模塊(udp_tx)和CRC校驗模塊(crc32_d8)。
UDP接收模塊(udp_rx):UDP接收模塊較為簡單,因為我們不需要對數據做IP首部校驗也不需要做CRC循環冗余校驗,只需要判斷目的MAC地址與開發板MAC地址、目的IP地址與開發板IP地址是否一致即可。接收模塊的解析順序是:前導碼+幀起始界定符→以太網幀頭→IP首部→UDP首部→UDP數據(有效數據)→接收結束。IP數據報一般以32bit為單位,為了和IP數據報格式保持一致,所以要把8位數據轉成32位數據,因此接收模塊實際上是完成了8位數據轉32位數據的功能。
UDP發送模塊(udp_tx):UDP發送模塊和接收模塊比較類似,但是多了IP首部校驗和和CRC循環冗余校驗的計算。CRC的校驗并不是在發送模塊完成,而是在CRC校驗模塊(crc32_d4)里完成的。發送模塊的發送順序是前導碼+幀起始界定符→以太網幀頭→IP首部→UDP首部→UDP數據(有效數據)→CRC校驗。輸入的有效數據為32位數據,GMII接口為8位數據接口,因此發送模塊實際上完成的是32位數據轉8位數據的功能。
CRC校驗模塊(crc32_d4):CRC校驗模塊是對UDP發送模塊的數據(不包括前導碼和幀起始界定符)做校驗,把校驗結果值拼在以太網幀格式的FCS字段,如果CRC校驗值計算錯誤或者沒有的話,那么電腦網卡會直接丟棄該幀導致收不到數據(有些網卡是可以設置不做校驗的)。CRC32校驗在FPGA實現的原理是LFSR(Linear Feedback Shift Register,線性反饋移位寄存器),其思想是各個寄存器儲存著上一次CRC32運算的結果,寄存器的輸出即為CRC32的值。
其中CRC校驗模塊和ARP模塊例化的校驗模塊完全相同,這里我們重點介紹UDP接收模塊和UDP發送模塊。
UDP接收模塊按照UDP的數據格式解析數據,并實現將8位用戶數據轉成32位數據的功能。由UDP的數據格式可知,解析UDP數據很適合使用狀態機來實現,下圖為UDP接收模塊的狀態跳轉圖。
圖 26.4.4 UDP接收模塊的狀態跳轉圖
接收模塊使用三段式狀態機來解析以太網包,從上圖可以比較直觀的看到每個狀態實現的功能以及跳轉到下一個狀態的條件。這里需要注意的一點是,在中間狀態如前導碼錯誤、MAC地址錯誤以及IP地址錯誤時跳轉到st_rx_end狀態而不是跳轉到st_idle狀態。因為中間狀態在解析到數據錯誤時,單包數據的接收還沒有結束,如果此時跳轉到st_idle狀態會誤把有效數據當成前導碼來解析,所以狀態跳轉到st_rx_end。而eth_rxdv信號為0時,單包數據才算接收結束,所以st_rx_end跳轉到st_idle的條件是eth_rxdv=0,準備接收下一包數據。因為代碼較長,只粘貼了第三段狀態機的接收數據狀態和接收結束狀態源代碼,代碼如下:
241 st_rx_data : begin
242 //接收數據,轉換成32bit
243 if(gmii_rx_dv) begin
244 data_cnt <= data_cnt + 16'd1;
245 rec_en_cnt <= rec_en_cnt + 2'd1;
246 if(data_cnt == data_byte_num - 16'd1) begin
247 skip_en <= 1'b1; //有效數據接收完成
248 data_cnt <= 16'd0;
249 rec_en_cnt <= 2'd0;
250 rec_pkt_done <= 1'b1;
251 rec_en <= 1'b1;
252 rec_byte_num <= data_byte_num;
253 end
254 //先收到的數據放在了rec_data的高位,所以當數據不是4的倍數時,
255 //低位數據為無效數據,可根據有效字節數來判斷(rec_byte_num)
256 if(rec_en_cnt == 2'd0)
257 rec_data[31:24] <= gmii_rxd;
258 else if(rec_en_cnt == 2'd1)
259 rec_data[23:16] <= gmii_rxd;
260 else if(rec_en_cnt == 2'd2)
261 rec_data[15:8] <= gmii_rxd;
262 else if(rec_en_cnt==2'd3) begin
263 rec_en <= 1'b1;
264 rec_data[7:0] <= gmii_rxd;
265 end
266 end
267 end
268 st_rx_end : begin //單包數據接收完成
269 if(gmii_rx_dv == 1'b0 && skip_en == 1'b0)
270 skip_en <= 1'b1;
271 end
程序中的st_rx_data狀態表示接收UDP的有效數據,在接收完有效數據后,拉高rec_pkt_done(單包有效數據接收完成)信號,如程序中第250行代碼所示。
圖 26.4.5為接收過程中ILA采集的波形圖,上位機通過網口調試助手發送http://www.openedv.com(十六進制為:68 74 74 70 3A 2F 2F 77 77 77 2E 6F 70 65 6E 65 64 76 2E 63 6F 6D),圖中gmii_rx_dv和gmii_rxd為GMII接口的接收有效信號和數據,skip_en為狀態機的跳轉信號。每次單包數據接收完成都會產生rec_pkt_done信號,rec_en和rec_data為收到的數據有效信號和32位數據。
圖 26.4.5 UDP接收采集的ILA波形圖
UDP發送模塊按照UDP的數據格式發送數據,并將32位用戶數據轉成8位數據的功能,也就是接收模塊的逆過程。同樣也非常適合使用狀態機來完成發送數據的功能,狀態跳轉圖如下圖所示:
圖 26.4.6 UDP發送模塊的狀態跳轉圖
發送模塊和接收模塊有很多相似之處,同樣使用三段式狀態機來發送以太網包,從上圖可以比較直觀的看到每個狀態實現的功能以及跳轉到下一個狀態的條件。
發送模塊的代碼中定義了數組來存儲以太網的幀頭、IP首部以及UDP的首部,在復位時初始化數組的值,部分源代碼如下。
69 reg [7:0] preamble[7:0] ; //前導碼
70 reg [7:0] eth_head[13:0] ; //以太網首部
71 reg [31:0] ip_head[6:0] ; //IP首部 + UDP首部
省略部分代碼……
209 //初始化數組
210 //前導碼 7個8'h55 + 1個8'hd5
211 preamble[0] <= 8'h55;
212 preamble[1] <= 8'h55;
213 preamble[2] <= 8'h55;
214 preamble[3] <= 8'h55;
215 preamble[4] <= 8'h55;
216 preamble[5] <= 8'h55;
217 preamble[6] <= 8'h55;
218 preamble[7] <= 8'hd5;
219 //目的MAC地址
220 eth_head[0] <= DES_MAC[47:40];
221 eth_head[1] <= DES_MAC[39:32];
222 eth_head[2] <= DES_MAC[31:24];
223 eth_head[3] <= DES_MAC[23:16];
224 eth_head[4] <= DES_MAC[15:8];
225 eth_head[5] <= DES_MAC[7:0];
226 //源MAC地址
227 eth_head[6] <= BOARD_MAC[47:40];
228 eth_head[7] <= BOARD_MAC[39:32];
229 eth_head[8] <= BOARD_MAC[31:24];
230 eth_head[9] <= BOARD_MAC[23:16];
231 eth_head[10] <= BOARD_MAC[15:8];
232 eth_head[11] <= BOARD_MAC[7:0];
233 //以太網類型
234 eth_head[12] <= ETH_TYPE[15:8];
235 eth_head[13] <= ETH_TYPE[7:0];
236 end
以上代碼在復位時對數組進行初始化。
244 st_idle : begin
245 if(trig_tx_en) begin
246 skip_en <= 1'b1;
247 //版本號:4 首部長度:5(單位:32bit,20byte/4=5)
248 ip_head[0] <= {8'h45,8'h00,total_num};
249 //16位標識,每次發送累加1
250 ip_head[1][31:16] <= ip_head[1][31:16] + 1'b1;
251 //bit[15:13]: 010表示不分片
252 ip_head[1][15:0] <= 16'h4000;
253 //協議:17(udp)
254 ip_head[2] <= {8'h40,8'd17,16'h0};
255 //源IP地址
256 ip_head[3] <= BOARD_IP;
257 //目的IP地址
258 if(des_ip != 32'd0)
259 ip_head[4] <= des_ip;
260 else
261 ip_head[4] <= DES_IP;
262 //16位源端口號:1234 16位目的端口號:1234
263 ip_head[5] <= {16'd1234,16'd1234};
264 //16位udp長度,16位udp校驗和
265 ip_head[6] <= {udp_num,16'h0000};
266 //更新MAC地址
267 if(des_mac != 48'b0) begin
268 //目的MAC地址
269 eth_head[0] <= des_mac[47:40];
270 eth_head[1] <= des_mac[39:32];
271 eth_head[2] <= des_mac[31:24];
272 eth_head[3] <= des_mac[23:16];
273 eth_head[4] <= des_mac[15:8];
274 eth_head[5] <= des_mac[7:0];
275 end
276 end
277 end
在程序的第248行至265行代碼,為IP首部數組進行賦值。
344 st_tx_data : begin //發送數據
345 crc_en <= 1'b1;
346 gmii_tx_en <= 1'b1;
347 tx_bit_sel <= tx_bit_sel + 3'd1;
348 if(data_cnt < tx_data_num - 16'd1)
349 data_cnt <= data_cnt + 16'd1;
350 else if(data_cnt == tx_data_num - 16'd1)begin
351 //如果發送的有效數據少于18個字節,在后面填補充位
352 //補充的值為最后一次發送的有效數據
353 gmii_txd <= 8'd0;
354 if(data_cnt + real_add_cnt < real_tx_data_num - 16'd1)
355 real_add_cnt <= real_add_cnt + 5'd1;
356 else begin
357 skip_en <= 1'b1;
358 data_cnt <= 16'd0;
359 real_add_cnt <= 5'd0;
360 tx_bit_sel <= 3'd0;
361 end
362 end
363 if(tx_bit_sel == 1'b0)
364 gmii_txd <= tx_data[31:24];
365 else if(tx_bit_sel == 3'd1)
366 gmii_txd <= tx_data[23:16];
367 else if(tx_bit_sel == 3'd2) begin
368 gmii_txd <= tx_data[15:8];
369 if(data_cnt != tx_data_num - 16'd1)
370 tx_req <= 1'b1;
371 end
372 else if(tx_bit_sel == 3'd3)
373 gmii_txd <= tx_data[7:0];
374 end
程序第344行至374行代碼為發送UDP數據段的狀態。我們前面講過以太網幀格式的數據部分最少是46個字節,去掉IP首部字節和UDP首部字節后,有效數據至少為18個字節,程序設計中已經考慮到這種情況,當發送的有效數據少于18個字節時,會在有效數據后面發送補充位,填充的數據為0。
375 st_crc : begin //發送CRC校驗值
376 gmii_tx_en <= 1'b1;
377 tx_bit_sel <= tx_bit_sel + 3'd1;
378 if(tx_bit_sel == 3'd0)
379 gmii_txd <= {~crc_next[0], ~crc_next[1], ~crc_next[2],~crc_next[3],
380 ~crc_next[4], ~crc_next[5], ~crc_next[6],~crc_next[7]};
381 else if(tx_bit_sel == 3'd1)
382 gmii_txd <= {~crc_data[16],~crc_data[17], ~crc_data[18],~crc_data[19],
383 ~crc_data[20],~crc_data[21],~crc_data[22],~crc_data[23]};
384 else if(tx_bit_sel == 3'd2) begin
385 gmii_txd <= {~crc_data[8], ~crc_data[9], ~crc_data[10],~crc_data[11],
386 ~crc_data[12], ~crc_data[13], ~crc_data[14],~crc_data[15]};
387 end
388 else if(tx_bit_sel == 3'd3) begin
389 gmii_txd <= {~crc_data[0], ~crc_data[1], ~crc_data[2],~crc_data[3],
390 ~crc_data[4], ~crc_data[5], ~crc_data[6],~crc_data[7]};
391 tx_done_t <= 1'b1;
392 skip_en <= 1'b1;
393 end
394 end
程序的第375行至394行代碼為發送CRC校驗值狀態,發送模塊的CRC校驗是由crc32_d4模塊完成的,發送模塊將輸入的crc的計算結果每4位高低位互換,按位取反發送出去。
圖 26.4.7為發送過程中Vivado抓取的波形圖,圖中tx_start_en作為開始發送的啟動信號,eth_tx_en和eth_tx_data即為GMII接口的發送接口。在開始發送以太網幀頭時crc_en拉高,開始CRC校驗的計算,在將要發送有效數據時拉高tx_req(發送數據請求)信號,tx_data即為待發送的有效數據,在所有數據發送完成后輸出tx_done(發送完成)信號和crc_clr(CRC校驗值復位)信號。
圖 26.4.7 UDP發送采集的ILA波形圖
以太網控制模塊的代碼如下:
1 module eth_ctrl(
2 input clk , //系統時鐘
3 input rst_n , //系統復位信號,低電平有效
4 //ARP相關端口信號
5 input arp_rx_done, //ARP接收完成信號
6 input arp_rx_type, //ARP接收類型 0:請求 1:應答
7 output arp_tx_en, //ARP發送使能信號
8 output arp_tx_type, //ARP發送類型 0:請求 1:應答
9 input arp_tx_done, //ARP發送完成信號
10 input arp_gmii_tx_en,//ARP GMII輸出數據有效信號
11 input [7:0] arp_gmii_txd, //ARP GMII輸出數據
12 //UDP相關端口信號
13 input udp_gmii_tx_en,//UDP GMII輸出數據有效信號
14 input [7:0] udp_gmii_txd, //UDP GMII輸出數據
15 //GMII發送引腳
16 output gmii_tx_en, //GMII輸出數據有效信號
17 output [7:0] gmii_txd //UDP GMII輸出數據
18 );
19
20 //reg define
21 reg protocol_sw; //協議切換信號
22
23 //*****************************************************
24 //** main code
25 //*****************************************************
26
27 assign arp_tx_en = arp_rx_done && (arp_rx_type == 1'b0);
28 assign arp_tx_type = 1'b1; //ARP發送類型固定為ARP應答
29 assign gmii_tx_en = protocol_sw ? udp_gmii_tx_en : arp_gmii_tx_en;
30 assign gmii_txd = protocol_sw ? udp_gmii_txd : arp_gmii_txd;
31
32 //根據ARP發送使能/完成信號,切換GMII引腳
33 always @(posedge clk or negedge rst_n) begin
34 if(!rst_n)
35 protocol_sw <= 1'b1;
36 else if(arp_tx_en)
37 protocol_sw <= 1'b0;
38 else if(arp_tx_done)
39 protocol_sw <= 1'b1;
40 end
41
42 endmodule
以太網控制模塊的代碼較簡單,如果輸入的arp_rx_done(ARP接收完成信號)為高電平,且arp_rx_type為低電平(ARP接收類型為請求)時,表示接收到ARP請求數據包,此時將arp_rx_done賦值給arp_tx_en信號,并拉高arp_tx_type信號,表示控制ARP模塊返回應答數據包,如程序中第27行和28行代碼所示。
另外,程序中的第32行至40行代碼根據ARP發送使能(完成)信號,控制protocol_sw信號的高低電平,用于選擇切換GMII發送引腳的連接方式。當protocol_sw等于1時,GMII發送引腳和UDP GMII發送引腳相連,否則和ARP GMII發送引腳相連,如程序中第29行第30行代碼所示。
26.5下載驗證
將下載器一端連接電腦,另一端與開發板上的JTAG下載口連接,將網線一端連接開發板的PL網口(GE_PL),另一端連接電腦的網口,接下來連接電源線,并打開開發板的電源開關。GE_PL網口的位置如下圖所示。
圖 26.5.1 GE_PL網口位置
點擊Vivado左側“Flow Navigator”窗口最下面的“Open Hardware Manager”,此時Vivado軟件識別到下載器,點擊“Hardware”窗口中“Program Device”下載程序,在彈出的界面中選擇“Program”下載程序。
程序下載完成后,PHY芯片會和電腦網卡進行通信(自協商),如果程序下載正確并且硬件連接無誤的話,我們點擊電腦右下角的網絡圖標,會看到本地連接剛開始顯示的是正在識別,一段時間之后顯示未識別的網絡,打開方式如下圖所示(WIN7和WIN10操作可能存在差異,但基本相同)。
圖 26.5.2 點擊網絡圖標
接下來就可以使用網口調試助手進行通信了,該工具位于開發板所隨附的資料“6_軟件資料/1_軟件/網口調試助手”目錄下(打開網口調試助手前,開發板必須硬件連接正確并且程序下載完成)。網口調試助手打開界面如圖 26.5.3所示:
圖 26.5.3 網口調試助手界面
打開網口調試助手后,協議類型選擇:UDP;本地主機地址選擇:本地連接的IP地址(在這里是192.168.1.102);本地主機端口號:1234;設置完成后點擊【打開】按鈕。如下圖所示:
圖 26.5.4 網口調試助手打開界面
遠程主機選擇:192.168.1.10 : 1234 (開發板的IP地址和端口號),在這里本機主端口號和遠程主機端口號都為1234,見ip_send模塊,源代碼如下所示:
[7:0]
262 //16位源端口號:1234 16位目的端口號:1234
263 ip_head[5] <= {16'd1234,16'd1234};
網口調試助手打開后,在發送文本框中輸入數據“http://www.openedv.com”并點擊發送,如下圖所示:
圖 26.5.5 網口調試助手收發數據界面
可以看到網口調試助手中接收到數據“http://www.openedv.com”,接收到的數據與發送的數據一致。
接下來通過Wireshark軟件抓取網口的數據包,界面如下圖所示:
圖 26.5.6 wireshark打開界面
雙擊上圖所示的以太網或者先選中以太網,再點擊上方紅框選中的藍色按鈕,即可開始抓取本地連接的數據包,抓取界面如下圖所示:
圖 26.5.7 wireshark以太網打開界面
從上圖可以看到,已經抓取到其它應用程序使用以太網發送的數據包,但是這些數據包并不是開發板發送的數據包,我們這個時候重新在網口調試助手中點擊“發送”按鈕,可以看到Wireshark軟件中抓取的數據,如下圖所示。
圖 26.5.8 wireshark以太網抓取到的數據包
上圖中第39行是上位機發送的ARP請求數據包,第40行是開發板返回的ARP應答數據包,第41行是上位機發送的UDP數據包,第42行是開發板返回的UDP數據包。雙擊開發板返回的數據包,可以看到開發板發送的詳細數據,如下圖所示:
圖 26.5.9 Wireshark抓取到的詳細數據
由上圖可知,源IP地址(開發板IP地址)為192.168.1.10,目的IP地址(電腦IP地址)為192.168.1.102,源端口號和目的端口號都是1234。上圖中下方紅框為開發板發送的16進制數據(去掉前導碼、SFD和CRC值),可以看到,UDP的用戶數據段對應的ASIC碼為“http://www.openedv.com”。