前面我們實現了FPGA板卡接收以太網的數據,但是里面的數據比較亂,而且可能出現無效幀,即便是有效幀,也不是所有數據都是我們要的,必須對數據進行篩選。本篇博客詳細記錄一下以太網數據的校驗和篩選。
一、數據的校驗和篩選
根據本次工程的情況,我們按照下表來進行設計:
1、包有效校驗
如上圖藍色部分即是我們的包有效校驗區,包發送數據過來,而剛好藍色位置的 5byte(40bit)數據和標准值一樣,那么就可以認定該包為有效包。
//========================================================================== //== 包有效校驗(udp、port源、port目的) //========================================================================== always @(posedge clk) begin if(rst) begin pkg_value <= 40'd0; end else if(PHY_rx_cnt==31 || (PHY_rx_cnt>=42 && PHY_rx_cnt<=45)) begin pkg_value <= {pkg_value[31:0],PHY_rxd}; end end always @(posedge clk) begin if(rst) begin pkg_vld <= 1'b0; end else if(PHY_rx_neg && pkg_value==40'h11_04d2_007b) begin pkg_vld <= 1'b1; end else begin pkg_vld <= 1'b0; end end
2、CRC校驗
CRC校驗是為了證明一個數據包是否出錯,多數為 8 位或 32 位,本次采用 32 位的CRC校驗,但是記住,只能用於驗證數據是否出錯,而不能對錯誤進行糾正。此外 CRC 校驗是去掉了幀首部的 8 byte 數據后的校驗。如下所示是32位的CRC校驗模塊,注意校驗和解校驗的電路默認初始狀態都是 32‘hffffffff,即全1狀態。千兆以太網的解校驗結果為 32’hc704dd7b。可以用例化的方式對該模塊進行使用,而實際 CRC 校驗的科學原理則略微高深,此處不做講解。
always @(posedge sclk) begin if(rst) begin crc32_value <= 32'hFFFFFFFF; end else if(crc_en) begin crc32_value[ 0] <= c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[ 1] <= c[25]^c[31]^d[ 0]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[ 2] <= c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[ 3] <= c[27]^d[ 4]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[ 4] <= c[28]^d[ 3]^c[27]^d[ 4]^c[26]^d[ 5]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[ 5] <= c[29]^d[ 2]^c[28]^d[ 3]^c[27]^d[ 4]^c[25]^c[31]^d[ 0]^d[ 6]^c[24]^c[30]^d[1]^d[7]; crc32_value[ 6] <= c[30]^d[ 1]^c[29]^d[ 2]^c[28]^d[ 3]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[ 7] <= c[31]^d[ 0]^c[29]^d[ 2]^c[27]^d[ 4]^c[26]^d[ 5]^c[24]^d[ 7]; crc32_value[ 8] <= c[ 0]^c[28]^d[ 3]^c[27]^d[ 4]^c[25]^d[ 6]^c[24]^d[ 7]; crc32_value[ 9] <= c[ 1]^c[29]^d[ 2]^c[28]^d[ 3]^c[26]^d[ 5]^c[25]^d[ 6]; crc32_value[10] <= c[ 2]^c[29]^d[ 2]^c[27]^d[ 4]^c[26]^d[ 5]^c[24]^d[ 7]; crc32_value[11] <= c[ 3]^c[28]^d[ 3]^c[27]^d[ 4]^c[25]^d[ 6]^c[24]^d[ 7]; crc32_value[12] <= c[ 4]^c[29]^d[ 2]^c[28]^d[ 3]^c[26]^d[ 5]^c[25]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[13] <= c[ 5]^c[30]^d[ 1]^c[29]^d[ 2]^c[27]^d[ 4]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[14] <= c[ 6]^c[31]^d[ 0]^c[30]^d[ 1]^c[28]^d[ 3]^c[27]^d[ 4]^c[26]^d[5]; crc32_value[15] <= c[ 7]^c[31]^d[ 0]^c[29]^d[ 2]^c[28]^d[ 3]^c[27]^d[ 4]; crc32_value[16] <= c[ 8]^c[29]^d[ 2]^c[28]^d[ 3]^c[24]^d[ 7]; crc32_value[17] <= c[ 9]^c[30]^d[ 1]^c[29]^d[ 2]^c[25]^d[ 6]; crc32_value[18] <= c[10]^c[31]^d[ 0]^c[30]^d[ 1]^c[26]^d[ 5]; crc32_value[19] <= c[11]^c[31]^d[ 0]^c[27]^d[ 4]; crc32_value[20] <= c[12]^c[28]^d[ 3]; crc32_value[21] <= c[13]^c[29]^d[ 2]; crc32_value[22] <= c[14]^c[24]^d[ 7]; crc32_value[23] <= c[15]^c[25]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[24] <= c[16]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[25] <= c[17]^c[27]^d[ 4]^c[26]^d[ 5]; crc32_value[26] <= c[18]^c[28]^d[ 3]^c[27]^d[ 4]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[27] <= c[19]^c[29]^d[ 2]^c[28]^d[ 3]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[28] <= c[20]^c[30]^d[ 1]^c[29]^d[ 2]^c[26]^d[ 5]; crc32_value[29] <= c[21]^c[31]^d[ 0]^c[30]^d[ 1]^c[27]^d[ 4]; crc32_value[30] <= c[22]^c[31]^d[ 0]^c[28]^d[ 3]; crc32_value[31] <= c[23]^c[29]^d[ 2]; end else begin crc32_value <= 32'hFFFFFFFF; end end
3、數據的篩選
由上圖可知,只有中間紅色部分的 “用戶數據”才是我們真正需要的,因此最終輸出結果還需要進行一番篩選,去頭去尾即可,這也沒什么難的。
4、8bit轉16bit
以太網傳過來的是雙沿 4bit 數據,轉換后是 8bit,而圖像是 RGB565 格式,是 16bit 的,因此還需要進行一下 8bit 轉 16bit 的設計。
//========================================================================== //== 8bit轉16bit //========================================================================== always @(posedge clk) begin if(rst) byte_cnt <= 0; else if(add_byte_rx_cnt) begin if(end_byte_rx_cnt) byte_cnt <= 0; else byte_cnt <= byte_cnt + 1; end end assign add_byte_rx_cnt = frx_vld; assign end_byte_rx_cnt = add_byte_rx_cnt && byte_cnt== 2-1; always @(posedge clk) begin if(rst) begin PHY_data <= 0; end else if(add_byte_rx_cnt) begin PHY_data <= {PHY_data[7:0],frx_data}; end end always @(posedge clk) begin if (rst == 1'b1) begin PHY_vld <= 1'b0; end else if(end_byte_rx_cnt) begin PHY_vld <= 1'b1; end else begin PHY_vld <= 1'b0; end end
二、代碼設計
上面已經貼了部分代碼了,難道這里我要全貼代碼嗎?不,貼代碼沒有意義,重要的是懂內部的含義。我們以 rx_filter 來命名此模塊,rx_filter 的輸入是網口數據經過前一講中轉換后的 8bit 數據值和對應使能,輸出則是經過校驗和篩選后的數據和使能。
上面說的 包有效校驗 和 CRC校驗,代碼本身都不難,用個計數器數進來的數據使能,然后對那些關鍵節點進行比較即可。但是那頭數據邊來這頭又要邊校驗,很可能時序出錯,因此有必要建立一個 data_fifo 先緩存住過來的數據,然后進行包有效校驗和 CRC 校驗。data_fifo 的深度可以深一點,例如8192,這樣就能容納多幀了。校驗完了后是需要判斷是否丟棄該包的,因此還需要另一個 status_fifo 對校驗信息進行存儲,同時只要 status_fifo 由空變成不空了,說明該包校驗信息寫入了,即該包校驗結束,那就直接設計 status_fifo 的讀使能讓信息數據出來,並用一個寄存器鎖存住后面用。 status_fifo 的讀使能有了后,data_fifo 的讀使能也要立馬設計出來,免得新來的各種包不斷進入 data_fifo,那不得撐爆了。注意 data_fifo 的讀使能持續時間應該和進來時的數據使能一樣長,所以前面計數時不能光計數了,還得把一個完整包的長度存起來。怎么存?直接將長度寄存住后 和 包有效校驗、CRC校驗拼接一起寫給 status_fifo 就行了,后面 status_fifo 讀出來這些數據就能為我們所用了。現在 data_fifo 不斷的寫數據讀數據,status_fifo 也不斷的寫信息數據讀信息數據,下一步我們就能利用讀出來的信息數據,判斷里面的的信息(即包有效校驗和CRC校驗)是不是真的OK,如果是真的OK,那就將 data_fifo 的讀數據和讀使能進行數據剔除,留下中間“用戶數據”部分再傳出去,如果不OK就不管了,讓那個 data_fifo 的讀使能和讀數據繼續工作,但我們不使用它,相當於丟棄了。示意圖如下所示:
波形圖如下所示,結合上面所說,理解了下面的波形圖那本模塊就沒問題了。
三、波形
申請一個 ila 觀察數據,可以讓 Matab 發送一段 0-255 的數據,查看我們的包有無問題,波形圖(部分名字進行了修改)如下所示:
四、千兆以太網 + DDR3 + HDMI 顯示
將千兆以太網和 DDR3、HDMI結合,即可實現 以太網 + DDR3 + HDMI 顯示了,尤其注意輸出端口、時鍾連線和引腳約束,工程結構如下所示:
發送端在PC,利用 matlab 將一段影片轉換為圖像數據,圖像數據格式為 RGB565,圖像轉換為一個個以太網的包,再利用 matlab 發送到網口,FPGA板卡接收以太網數據,經過 PHY_top 的轉換,再在 DDR3_ctrl 中緩存,最后通過 HDMI_top 模塊將視頻輸出到顯示屏上。
HDMI 的設計中沒有考慮聲音的輸出,因此 matlab 那也沒有轉換聲音,最終顯示的視頻是無聲的,實驗結果如下所示:
參考資料:威三學院FPGA教程