目錄
開發環境
- 小梅哥AC6102開發板(內含RTL8211E千兆以太網phy芯片)
- quartus prime17.1
- win10
- 千兆網卡 這個需要電腦支持,不支持就玩不了了
- 本教程是根據小梅哥的百兆網arp教程改編過來的
GMII接口介紹
- gmii_tx_clk是由fpga輸出給phy的,注意方向,gmii_rx_clk則是phy輸出給fpga,所以我們系統時鐘就採用了gmii_rx_clk即可
- 由於系統時鐘採用了gmii_rx_clk,這個引腳並沒有連接到全局時鐘上,所以我們可以使用altclkctrl ip核,使該時鐘連接到全局時鐘上,即便這樣,系統時鐘到了125Mhz,頻率比較高,最好是要做一下時鐘的約束,我們只需要簡單了寫個clk的時序約束就可以了,告訴fpga我們的系統時鐘是125Mhz,然後他就會按這個頻率去優化時序的
- 綁定引腳時,可以寫個引腳的配置文件,但是要注意總線也是要一個一個綁定的,如gmii_tx_data[0],gmii_tx_data[1]…千萬不要寫成了gmii_tx_data0,gmii_tx_data1…
- 異步釋放,同步復位,避免復位引起亞穩態
//異步釋放,同步復位
always @(posedge clk or negedge s_rst_n)begin
if(s_rst_n==1'b0)begin
rst_n_r <= 0;
end
else begin
rst_n_r <= {rst_n_r[0],1'b1};
end
end
assign rst_n = rst_n_r[1];
- 以上是我自己遇到過的部分bug,如果實驗失敗了,首先要檢查時鐘是否有效,引腳是否綁定正確,時序是否違規。
以太網幀介紹
ARP幀介紹
ARP幀存在的目的
在網絡通訊時,源主機的應用程序知道目的主機的 IP 地址和端口號,卻不知道目的主機的硬件地址,
而數據包首先是被網卡接收到再去處理上層協議的,如果接收到的數據包的硬件地址與本機不符,則直接丟棄。
因此在通訊前必須獲得目的主機的硬件地址。 ARP 協議就起到這個作用。
ARP 幀工作原理
源主機發出 ARP 請求,詢問“IP 地址是 192.168.0.1 的主機的硬件地址是多少”,並將這
個請求廣播到本地網段(以太網幀首部的硬件地址填 FF:FF:FF:FF:FF:FF 表示廣播),目的主機
接收到廣播的 ARP 請求,發現其中的 IP 地址與本機相符,則發送一個 ARP 應答數據包給源
主機,將自己的硬件地址填寫在應答包中。
每臺主機都維護一個 ARP 緩存表,可以用 arp -a 命令查看。緩存表中的表項有過期時間
(一般爲 20 分鐘),如果 20 分鐘內沒有再次使用某個表項,則該表項失效,下次還要發 ARP
請求來獲得目的主機的硬件地址。想一想,爲什麼表項要有過期時間而不是一直有效?
ARP 數據報的格式如下圖所示(該圖出自[TCPIP]):
ARP數據報格式
使用以太網發包工具組ARP包
注意到源 MAC 地址、目的 MAC 地址在以太網首部和 ARP 請求中各出現一次,對於鏈路層爲以太網的情況
是多餘的,但如果鏈路層是其它類型的網絡則有可能是必要的。硬件類型指鏈路層網絡類型, 1 爲以太網,
協議類型指要轉換的地址類型, 0x0800 爲 IP 地址,後面兩個地址長度對於以太網地址和 IP 地址分別爲
6 和 4(字節), op 字段爲 1 表示 ARP 請求, op字段爲 2 表示 ARP 應答。
用wireshark軟件抓包
用CRC計算軟件計算CRC校驗值
手動計算CRC
完整的以太網數據包結尾是幀校驗(FCS), 即 CRC32 值, 由於整個過程中都沒有出現
CRC 校驗字段的內容,而我們使用 FPGA 發包需要計算 CRC 校驗字段, CRC 校驗字段的產生
可以使用軟件或硬件的形式產生,本節實驗,爲了降低難度, 使用 CRC 計算軟件手動計算出
數據報的 CRC 值, 因此使用專用 CRC 校驗工具對數據內容進行計算以得到校驗字:
CRC 校驗軟件計算校驗內容
第一次發的時候,我將arp中的des_mac默認爲00_00_00_00_00_00的,後面通過arp應答包,解析出des_mac的值後,crc就要做相應的更新,爲了方便,我是直接用crc計算校正值的,不同的電腦mac不一樣,所以需要自己計算一下。
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
crc_value <= 32'h967d1d69;
end
else begin
crc_value <= 32'h24495d2f; //這個值要根據你們自己電腦的mac來計算
end
end
自動計算CRC
自己看代碼研究一下吧,關鍵點是要時序對齊,注意下降沿採集的技巧(可提前半拍把crc送走,否則時序會出問題!!!),這個crc算法倒是不用過於深究,直接用就可以了。
/************************************************************************
* Author : Wen Chunyang
* Email : [email protected]
* Create time : 2018-11-19 22:27
* Last modified : 2018-11-19 22:27
* Filename : crc.v
* Description :
* *********************************************************************/
module crc(
input clk ,
input rst_n ,
input [ 7: 0] din ,
input en ,
input init ,
output reg [31: 0] crc
);
//======================================================================\
//************** Define Parameter and Internal Signals *****************
//======================================================================/
wire [ 7: 0] data ;
wire [31: 0] crc_next ;
//======================================================================\
//**************************** Main Code *******************************
//======================================================================/
assign data={din[0],din[1],din[2],din[3],din[4],din[5],din[6],din[7]};
assign crc_next[0] = crc[24] ^ crc[30] ^ data[0] ^ data[6];
assign crc_next[1] = crc[24] ^ crc[25] ^ crc[30] ^ crc[31] ^ data[0] ^ data[1] ^ data[6] ^ data[7];
assign crc_next[2] = crc[24] ^ crc[25] ^ crc[26] ^ crc[30] ^ crc[31] ^ data[0] ^ data[1] ^ data[2] ^ data[6] ^ data[7];
assign crc_next[3] = crc[25] ^ crc[26] ^ crc[27] ^ crc[31] ^ data[1] ^ data[2] ^ data[3] ^ data[7];
assign crc_next[4] = crc[24] ^ crc[26] ^ crc[27] ^ crc[28] ^ crc[30] ^ data[0] ^ data[2] ^ data[3] ^ data[4] ^ data[6];
assign crc_next[5] = crc[24] ^ crc[25] ^ crc[27] ^ crc[28] ^ crc[29] ^ crc[30] ^ crc[31] ^ data[0] ^ data[1] ^ data[3] ^ data[4] ^ data[5] ^ data[6] ^ data[7];
assign crc_next[6] = crc[25] ^ crc[26] ^ crc[28] ^ crc[29] ^ crc[30] ^ crc[31] ^ data[1] ^ data[2] ^ data[4] ^ data[5] ^ data[6] ^ data[7];
assign crc_next[7] = crc[24] ^ crc[26] ^ crc[27] ^ crc[29] ^ crc[31] ^ data[0] ^ data[2] ^ data[3] ^ data[5] ^ data[7];
assign crc_next[8] = crc[0] ^ crc[24] ^ crc[25] ^ crc[27] ^ crc[28] ^ data[0] ^ data[1] ^ data[3] ^ data[4];
assign crc_next[9] = crc[1] ^ crc[25] ^ crc[26] ^ crc[28] ^ crc[29] ^ data[1] ^ data[2] ^ data[4] ^ data[5];
assign crc_next[10] = crc[2] ^ crc[24] ^ crc[26] ^ crc[27] ^ crc[29] ^ data[0] ^ data[2] ^ data[3] ^ data[5];
assign crc_next[11] = crc[3] ^ crc[24] ^ crc[25] ^ crc[27] ^ crc[28] ^ data[0] ^ data[1] ^ data[3] ^ data[4];
assign crc_next[12] = crc[4] ^ crc[24] ^ crc[25] ^ crc[26] ^ crc[28] ^ crc[29] ^ crc[30] ^ data[0] ^ data[1] ^ data[2] ^ data[4] ^ data[5] ^ data[6];
assign crc_next[13] = crc[5] ^ crc[25] ^ crc[26] ^ crc[27] ^ crc[29] ^ crc[30] ^ crc[31] ^ data[1] ^ data[2] ^ data[3] ^ data[5] ^ data[6] ^ data[7];
assign crc_next[14] = crc[6] ^ crc[26] ^ crc[27] ^ crc[28] ^ crc[30] ^ crc[31] ^ data[2] ^ data[3] ^ data[4] ^ data[6] ^ data[7];
assign crc_next[15] = crc[7] ^ crc[27] ^ crc[28] ^ crc[29] ^ crc[31] ^ data[3] ^ data[4] ^ data[5] ^ data[7];
assign crc_next[16] = crc[8] ^ crc[24] ^ crc[28] ^ crc[29] ^ data[0] ^ data[4] ^ data[5];
assign crc_next[17] = crc[9] ^ crc[25] ^ crc[29] ^ crc[30] ^ data[1] ^ data[5] ^ data[6];
assign crc_next[18] = crc[10] ^ crc[26] ^ crc[30] ^ crc[31] ^ data[2] ^ data[6] ^ data[7];
assign crc_next[19] = crc[11] ^ crc[27] ^ crc[31] ^ data[3] ^ data[7];
assign crc_next[20] = crc[12] ^ crc[28] ^ data[4];
assign crc_next[21] = crc[13] ^ crc[29] ^ data[5];
assign crc_next[22] = crc[14] ^ crc[24] ^ data[0];
assign crc_next[23] = crc[15] ^ crc[24] ^ crc[25] ^ crc[30] ^ data[0] ^ data[1] ^ data[6];
assign crc_next[24] = crc[16] ^ crc[25] ^ crc[26] ^ crc[31] ^ data[1] ^ data[2] ^ data[7];
assign crc_next[25] = crc[17] ^ crc[26] ^ crc[27] ^ data[2] ^ data[3];
assign crc_next[26] = crc[18] ^ crc[24] ^ crc[27] ^ crc[28] ^ crc[30] ^ data[0] ^ data[3] ^ data[4] ^ data[6];
assign crc_next[27] = crc[19] ^ crc[25] ^ crc[28] ^ crc[29] ^ crc[31] ^ data[1] ^ data[4] ^ data[5] ^ data[7];
assign crc_next[28] = crc[20] ^ crc[26] ^ crc[29] ^ crc[30] ^ data[2] ^ data[5] ^ data[6];
assign crc_next[29] = crc[21] ^ crc[27] ^ crc[30] ^ crc[31] ^ data[3] ^ data[6] ^ data[7];
assign crc_next[30] = crc[22] ^ crc[28] ^ crc[31] ^ data[4] ^ data[7];
assign crc_next[31] = crc[23] ^ crc[29] ^ data[5];
//crc 這裏用下降沿採集,是爲了crc能提前半拍送到eth_mac_send模塊中,這個很重要
//否則crc高8位會發送出錯,自己可以修改代碼試試看
always @(negedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
crc <= {32{1'b1}};
end
else if(init)begin
crc <= {32{1'b1}};
end
else if(en)begin
crc <= crc_next;
end
end
endmodule
發送與接收模塊設計
發送模塊
- 首先是上電等待10ms,電壓穩定後,再去準備arp包,並且每隔1s發送一次arp包
/************************************************************************
* Author : Wen Chunyang
* Email : [email protected]
* Create time : 2018-11-16 22:20
* Last modified : 2018-11-16 22:20
* Filename : eth_send_test.v
* Description :
* *********************************************************************/
module eth_send(
input clk ,
input rst_n ,
//Eth
input [47: 0] des_mac ,
input update ,
output wire tx_trig ,
output wire [31: 0] crc ,
output reg [ 9: 0] wr_addr ,
output wire wr_clk ,
output reg wr_en ,
output reg [ 7: 0] wr_data
);
//======================================================================\
//************** Define Parameter and Internal Signals *****************
//======================================================================/
parameter DELAY_1S = 27'd125_000_000 ;
parameter DELAY_10MS = 25'd125_000_0 ;
//cnt0
reg [24: 0] cnt0 ;
wire add_cnt0 ;
wire end_cnt0 ;
//cnt
reg [27: 0] cnt ;
wire add_cnt ;
wire end_cnt ;
//cnt1
reg [ 9: 0] cnt1 ;
wire add_cnt1 ;
wire end_cnt1 ;
reg flag_wr ;
reg flag ;
reg [31: 0] crc_value ;
//======================================================================\
//**************************** Main Code *******************************
//======================================================================/
assign crc = {crc_value[7:0],crc_value[15:8],crc_value[23:16],crc_value[31:24]};
//crc_value 後續的會專門寫crc生成電路的,現在只是爲了簡單起見,比較剛剛入門
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
crc_value <= 32'h967D1D69;
end
else if(update)begin
crc_value <= 32'h24495D2F;
end
end
//cnt0 上電後等待10ms,使電平穩定後
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(add_cnt0)begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1;
end
end
assign add_cnt0 = flag;
assign end_cnt0 = add_cnt0 && cnt0 == DELAY_10MS-1;
//flag
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
flag <= 1'b1;
end
else if(end_cnt0)begin
flag <= 1'b0;
end
end
//cnt 1s發送一次
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = flag == 1'b0;
assign end_cnt = add_cnt && cnt == DELAY_1S-1;
assign tx_trig = end_cnt;
//cnt1
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(add_cnt1)begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
end
assign add_cnt1 = flag_wr;
assign end_cnt1 = add_cnt1 && cnt1 == 46-1;
//flag_wr
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
flag_wr <= 1'b0;
end
else if(end_cnt0 || update)begin
flag_wr <= 1'b1;
end
else if(end_cnt1)begin
flag_wr <= 1'b0;
end
end
//wr_en
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
wr_en <= 1'b0;
end
else begin
wr_en <= flag_wr;
end
end
//wr_addr
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
wr_addr <= 10'd0;
end
else begin
wr_addr <= cnt1;
end
end
//wr_clk
assign wr_clk = clk;
//wr_data
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
wr_data <= 8'h00;
end
else if(flag_wr)begin
case(cnt1)
//hdwr_type
00:wr_data <= 8'h00;
01:wr_data <= 8'h01;
//protocol_type
02:wr_data <= 8'h08;
03:wr_data <= 8'h00;
//hdwr_size
04:wr_data <= 8'h06;
//protocol_size
05:wr_data <= 8'h04;
//opcode
06:wr_data <= 8'h00;
07:wr_data <= 8'h01;
//sender_mac
08:wr_data <= 8'h00;
09:wr_data <= 8'h0a;
10:wr_data <= 8'h35;
11:wr_data <= 8'h01;
12:wr_data <= 8'hfe;
13:wr_data <= 8'hc0;
//sender_ip
14:wr_data <= 8'd192;
15:wr_data <= 8'd168;
16:wr_data <= 8'd0;
17:wr_data <= 8'd2;
//target_mac
18:wr_data <= des_mac[47:40];
19:wr_data <= des_mac[39:32];
20:wr_data <= des_mac[31:24];
21:wr_data <= des_mac[23:16];
22:wr_data <= des_mac[15: 8];
23:wr_data <= des_mac[ 7: 0];
//target_ip
24:wr_data <= 8'd192;
25:wr_data <= 8'd168;
26:wr_data <= 8'd0;
27:wr_data <= 8'd3;
//填充字段
28:wr_data <= 8'h00;
29:wr_data <= 8'h00;
30:wr_data <= 8'hff;
31:wr_data <= 8'hff;
32:wr_data <= 8'hff;
33:wr_data <= 8'hff;
34:wr_data <= 8'hff;
35:wr_data <= 8'hff;
36:wr_data <= 8'h00;
37:wr_data <= 8'h23;
38:wr_data <= 8'hcd;
39:wr_data <= 8'h76;
40:wr_data <= 8'h63;
41:wr_data <= 8'h1a;
42:wr_data <= 8'h08;
43:wr_data <= 8'h06;
44:wr_data <= 8'h00;
45:wr_data <= 8'h01;
default:begin
wr_data <= 8'h00;
end
endcase
end
end
endmodule
接收模塊
- 接收模塊設計的比較簡單,因爲pc發送的arp應答包大小是固定的,我設計了一個計數器用來計數,以此來定位,然後我解析的標準是前導碼正確,幀分界符正確,fpga mac地址正確,arp應答包,這些都滿足,我就把pc的mac存下來,並且更新到des_mac變量中,用於下次fpga發arp中的des_mac中。這個模塊其實也可以用狀態機來實現。後續的udp教程應該會使用狀態機來做哦。
/************************************************************************
* Author : Wen Chunyang
* Email : [email protected]
* Create time : 2018-11-18 16:37
* Last modified : 2018-11-18 16:37
* Filename : eth_receive.v
* Description :
* *********************************************************************/
module eth_receive(
input clk ,
input rst_n ,
//gmii
input gmii_rx_dv ,
input [ 7: 0] gmii_rx_data ,
input gmii_rx_er ,
output reg update ,
output reg [47: 0] des_mac
);
//======================================================================\
//************** Define Parameter and Internal Signals *****************
//======================================================================/
reg [ 6: 0] cnt ;
wire add_cnt ;
wire end_cnt ;
reg preamble_ok ;
reg frame_start ;
reg [47: 0] src_mac ;
reg [47: 0] des_mac_tmp ;
reg src_mac_ok ;
reg [15: 0] eth_type ;
reg is_arp ;
reg [15: 0] opcode ;
reg opcode_ack ;
//======================================================================\
//**************************** Main Code *******************************
//======================================================================/
//cnt
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
else begin
cnt <= 0;
end
end
assign add_cnt = gmii_rx_dv && (gmii_rx_er == 1'b0);
assign end_cnt = add_cnt && cnt == 72-1;
//preamble_ok
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
preamble_ok <= 1'd0;
end
else if(add_cnt && (cnt <= 7-1) && (gmii_rx_data == 8'h55))begin
preamble_ok <= 1'b1;
end
else begin
preamble_ok <= 1'b0;
end
end
//frame_start
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
frame_start <= 1'b0;
end
else if(preamble_ok && add_cnt && (cnt == 8-1) && (gmii_rx_data == 8'hd5))begin
frame_start <= 1'b1;
end
else if(end_cnt)begin
frame_start <= 1'b0;
end
end
//src_mac fpga的mac地址
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
src_mac <= 48'd0;
end
else if(frame_start && add_cnt && (cnt >= 9-1) && (cnt <= 14-1))begin
src_mac <= {src_mac[39:0],gmii_rx_data};
end
else if(end_cnt)begin
src_mac <= 48'd0;
end
end
//src_mac_ok
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
src_mac_ok <= 1'b0;
end
else if(add_cnt && (cnt == 15-1) && (src_mac == 48'h00_0a_35_01_fe_c0))begin
src_mac_ok <= 1'b1;
end
else if(end_cnt)begin
src_mac_ok <= 1'b0;
end
end
//eth_type
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
eth_type <= 16'd0;
end
else if(src_mac_ok && add_cnt && (cnt >= 21-1) && (cnt <= 22-1))begin
eth_type <= {eth_type[7:0],gmii_rx_data};
end
else if(end_cnt)begin
eth_type <= 16'd0;
end
end
//is_arp
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
is_arp <= 1'b0;
end
else if(src_mac_ok && add_cnt && (cnt == 23-1) && (eth_type == 16'h0806))begin
is_arp <= 1'b1;
end
else if(end_cnt)begin
is_arp <= 1'b0;
end
end
//opcode
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
opcode <= 16'd0;
end
else if(is_arp && add_cnt && (cnt >= 29-1) && (cnt <= 30-1))begin
opcode <= {opcode[7:0],gmii_rx_data};
end
else if(end_cnt)begin
opcode <= 16'd0;
end
end
//opcode_ack
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
opcode_ack <= 1'b0;
end
else if(add_cnt && (cnt == 31-1) && (opcode == 16'h0002))begin
opcode_ack <= 1'b1;
end
else if(end_cnt)begin
opcode_ack <= 1'b0;
end
end
//des_mac_tmp
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
des_mac_tmp <= 48'd0;
end
else if(add_cnt && (cnt >= 31-1) && (cnt <= 36-1))begin
des_mac_tmp <= {des_mac_tmp[39:0],gmii_rx_data};
end
end
//des_mac
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
des_mac <= 48'd0;
end
else if(opcode_ack && add_cnt && (cnt == 37-1))begin
des_mac <= des_mac_tmp;
end
end
//update
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
update <= 1'b0;
end
else if(opcode_ack)begin
update <= end_cnt;
end
end
endmodule
調試
發送
接收
自動CRC
wireshark
福利
爲了能及時回覆大家,現在獲取源碼方式如下:
微信掃描下面的二維碼關注【春哥筆記】公衆號,回覆“arp”即可Get源碼的獲取方式: