一、設計思路
-
FPGA實現MAC層(數據鏈路層)的功能並連接到RTL8211物理層(PHY)芯片實現以太網數據的發送
-
使用GMII接口
時鍾是125MHz,一次發8bit數據
8bit * 125M = 1000Mbit
所以叫做千兆以太網 -
RTL8211時序
來一個時鍾上升沿就發一個字節的數據 -
數據鏈路層(MAC幀協議)發送過程
- 前同步碼 0x55,發七次
- 幀開始符 0xD5
- 目的MAC地址(6字節)
- 源MAC地址(6字節)
- 類型:0x800 使用IP上層協議,代表下面發送的數據是IP數據報
- 數據
- IP報文首部
- IP版本 0x4 IPv4
- 首部長度
- 服務類型
- 總長度
- 分段標識
- 保留位
- DF
- MF
- 段偏移
- 生存周期TTL
- 上層協議 0x11 UDP
- 報文校驗和
- 源IP地址
- 目的IP地址
- 可選字段(0字節不需要)
- IP報文數據(UDP報文)
- UDP報文首部
- 16位源端口號
- 16位目的端口號
- 16位UDP長度
- 16位UDP校驗和(可以直接置0)
- UDP報文數據
- UDP報文首部
- CRC校驗
- IP報文首部
-
IP報文校驗和
將IP報文首部,從前到后拼成一個一個的2字節的數據相加(其中報文校驗和置0)
然后將所加的32bit數據的高16位於低16位相加,直到高16bit為0
將16bit取反,則為校驗和 -
CRC校驗
CRC的計算方法:https://www.bilibili.com/video/BV1V4411Z7VA
這里使用一個網站,自動生成CRC verilog的函數代碼:https://www.easics.com/crctool/
雖然生成了函數,但是具體使用函數還是要做更改的
注意:CRC校驗是從發送目的MAC地址開始校驗的
-
IP首部長度
單位是/32bit(4字節)
如果沒有可選自動,首部長度就是 5(行) -
IP總長度
總長度 = IP首部長度 + IP報文數據長度
單位是/字節 -
MAC幀協議的數據和填充
數據和填充那個位置,至少要46個字節,如果要發送的UDP報文數據加起來沒有46字節的話,就要在后面填0x00直到有46個字節為止
IP報文頭部(20字節)+ UDP報文(8字節)+ 發送的數據 >= 46
發送的數據 >= 18字節 -
FPGA狀態機實現
每一個狀態都配套一個計數器
二、以太網GMIII發送代碼實現
module eth_udp_tx_gmii(
clk ,
rst_n ,
tx_en ,
tx_done ,
dst_mac ,
src_mac ,
dst_ip ,
src_ip ,
dst_port ,
src_port ,
gmii_clk ,
data_len ,
data_vld ,
gmii_en ,
gmii_tx ,
data_in
);
localparam MAC_W = 48;
localparam IP_W = 32;
localparam PORT_W = 16;
localparam DATA_LEN_W = 16;
localparam GMII_W = 8;
localparam PREAMBLE = 8'h55; //前導碼
localparam SFD = 8'hD5; //幀開始符
localparam ETH_TYPE = 16'h0800;//上層協議類型,IP協議
localparam IP_VER = 4'h4; //IPV4
localparam IP_HDR_LEN = 4'h5; //首部長度有5個32bit
localparam IP_TOS = 8'h00; //服務類型,未用
localparam IP_HED_LEN = 20; //IP頭部長度
localparam UDP_HED_LEN = 8; //UDP頭部長度
localparam IP_ID = 16'h0000;//分段標識
localparam IP_RSV = 1'b0; //保留
localparam IP_DF = 1'b0; //是否分段
localparam IP_MF = 1'b0; //是否有后續分段
localparam IP_FRAG_OFFSET = 13'h0; //段偏移(以8字節為單位)
localparam IP_TTL = 8'h40; //生存周期,能經過40個路由器
localparam IP_PROTOCOL = 8'h11; //上層協議(UDP)
localparam MIN_DATA = 46; //UDP數據最小長度
localparam DATA_W = 8;
//狀態機參數
localparam IDLE = 7'b0000001;
localparam TX_ETH_HED = 7'b0000010;
localparam TX_IP_HED = 7'b0000100;
localparam TX_UDP_HED = 7'b0001000;
localparam TX_UDP_DATA = 7'b0010000;
localparam TX_FILL_DATA = 7'b0100000;
localparam TX_CRC = 7'b1000000;
localparam STATE_W = 7;
//計數器參數
localparam ETH_HED_W = 5;
localparam ETH_HED_N = 22;
localparam IP_HED_W = 5;
localparam IP_HED_N = 20;
localparam UDP_HED_N = 8;
localparam UDP_HED_W = 4;
localparam CNT_DATA_W = 11;
localparam FILL_W = 6;
localparam CNT_CRC_W = 3;
localparam CNT_CRC_N = 4;
//模塊參數
localparam CHEC_W = 16;
localparam CRC_OUT_W = 32;
input clk;
input rst_n;
input tx_en; //發送使能
input [MAC_W-1:0] dst_mac; //目的Mac地址
input [MAC_W-1:0] src_mac; //源Mac地址
input [IP_W-1:0] dst_ip; //目的ip地址
input [IP_W-1:0] src_ip; //源ip地址
input [PORT_W-1:0] dst_port; //目的端口
input [PORT_W-1:0] src_port; //源端口
input [DATA_LEN_W-1:0] data_len; //發送數據長度
input [DATA_W-1:0] data_in; //輸入數據
output gmii_clk; //以太網接口時鍾
output gmii_en; //以太網使能
output [GMII_W-1:0] gmii_tx; //以太網數據
output tx_done; //發送完成
output data_vld; //數據有效標志信號
wire gmii_clk; //以太網接口時鍾
reg gmii_en; //以太網使能
reg [GMII_W-1:0] gmii_tx; //以太網數據
reg tx_done; //發送完成
reg data_vld; //數據有效標志信號
//狀態機變量
reg [STATE_W-1:0] state_c;
reg [STATE_W-1:0] state_n;
wire idle2tx_eth_hed;
wire tx_eth_hed2tx_ip_hed;
wire tx_ip_hed2tx_udp_hed;
wire tx_udp_hed2tx_udp_data;
wire tx_udp_data2tx_fill_data;
wire tx_udp_data2tx_crc;
wire tx_fill_data2tx_crc;
wire tx_crc2idle;
//計數器變量
reg [ETH_HED_W-1:0] cnt_eth_hed;
wire add_cnt_eht_hed;
wire end_cnt_eth_hed;
reg [IP_HED_W-1:0] cnt_ip_hed;
wire add_cnt_ip_hed;
wire end_cnt_ip_hed;
reg [UDP_HED_W-1:0] cnt_udp_hed;
wire add_cnt_udp_hed;
wire end_cnt_udp_hed;
reg [CNT_DATA_W-1:0] cnt_data;
wire add_cnt_data;
wire end_cnt_data;
reg [FILL_W-1:0] cnt_fill;
wire add_cnt_fill;
wire end_cnt_fill;
reg [CNT_CRC_W-1:0] cnt_crc;
wire add_cnt_crc;
wire end_cnt_crc;
//中間變量
reg tx_flag;
reg [MAC_W-1:0] dst_mac_tmp; //目的Mac地址
reg [MAC_W-1:0] src_mac_tmp; //源Mac地址
reg [IP_W-1:0] dst_ip_tmp; //目的ip地址
reg [IP_W-1:0] src_ip_tmp; //源ip地址
reg [PORT_W-1:0] dst_port_tmp; //目的端口
reg [PORT_W-1:0] src_port_tmp; //源端口
reg [DATA_LEN_W-1:0] data_len_tmp; //發送數據長度
reg [DATA_LEN_W-1:0] ip_total_len; //IP總長度
reg [DATA_LEN_W-1:0] udp_total_len; //UDP總長度
wire [16-1:0] udp_check_sum; //UDP校驗和
reg [DATA_W-1:0] data_out; //以太網數據
reg tx_nocrc_flag;
//模塊變量
wire [CHEC_W-1:0] check_sum; //IP報頭校驗和
reg sum_en;
reg crc_init;
reg crc_en;
wire [CRC_OUT_W-1:0] crc_out;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
state_c <= IDLE;
else
state_c <= state_n;
end
always @(*)begin
case(state_c)
IDLE:begin
if(idle2tx_eth_hed)
state_n = TX_ETH_HED;
else
state_n = state_c;
end
TX_ETH_HED:begin
if(tx_eth_hed2tx_ip_hed)
state_n = TX_IP_HED;
else
state_n = state_c;
end
TX_IP_HED:begin
if(tx_ip_hed2tx_udp_hed)
state_n = TX_UDP_HED;
else
state_n = state_c;
end
TX_UDP_HED:begin
if(tx_udp_hed2tx_udp_data)
state_n = TX_UDP_DATA;
else
state_n = state_c;
end
TX_UDP_DATA:begin
if(tx_udp_data2tx_fill_data)
state_n = TX_FILL_DATA;
else if(tx_udp_data2tx_crc)
state_n = TX_CRC;
else
state_n = state_c;
end
TX_FILL_DATA:begin
if(tx_fill_data2tx_crc)
state_n = TX_CRC;
else
state_n = state_c;
end
TX_CRC:begin
if(tx_crc2idle)
state_n = IDLE;
else
state_n = state_c;
end
default:state_n = IDLE;
endcase
end
assign idle2tx_eth_hed = state_c == IDLE && tx_en;
assign tx_eth_hed2tx_ip_hed = state_c == TX_ETH_HED && end_cnt_eth_hed;
assign tx_ip_hed2tx_udp_hed = state_c == TX_IP_HED && end_cnt_ip_hed;
assign tx_udp_hed2tx_udp_data = state_c == TX_UDP_HED && end_cnt_udp_hed;
assign tx_udp_data2tx_fill_data = state_c == TX_UDP_DATA && end_cnt_data && (data_len_tmp > 0 && data_len_tmp < (MIN_DATA - IP_HED_LEN - UDP_HED_LEN));
assign tx_udp_data2tx_crc = state_c == TX_UDP_DATA && end_cnt_data && data_len_tmp >= (MIN_DATA - IP_HED_LEN - UDP_HED_LEN);
assign tx_fill_data2tx_crc = state_c == TX_FILL_DATA && end_cnt_fill;
assign tx_crc2idle = state_c == TX_CRC && end_cnt_crc;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data_out <= 0;
else if(state_c == TX_ETH_HED)begin
case(cnt_eth_hed)
0,1,2,3,4,5,6:data_out <= PREAMBLE;
7 :data_out <= SFD;
8 :data_out <= dst_mac_tmp[47:40];
9 :data_out <= dst_mac_tmp[39:32];
10:data_out <= dst_mac_tmp[31:24];
11:data_out <= dst_mac_tmp[23:16];
12:data_out <= dst_mac_tmp[15:8];
13:data_out <= dst_mac_tmp[7:0];
14:data_out <= src_mac_tmp[47:40];
15:data_out <= src_mac_tmp[39:32];
16:data_out <= src_mac_tmp[31:24];
17:data_out <= src_mac_tmp[23:16];
18:data_out <= src_mac_tmp[15:8];
19:data_out <= src_mac_tmp[7:0];
20:data_out <= ETH_TYPE[15:8];
21:data_out <= ETH_TYPE[7:0];
default:data_out <= 8'h00;
endcase
end
else if(state_c == TX_IP_HED)begin
case(cnt_ip_hed)
0 :data_out <= {IP_VER,IP_HDR_LEN};
1 :data_out <= IP_TOS;
2 :data_out <= ip_total_len[15:8];
3 :data_out <= ip_total_len[7:0];
4 :data_out <= IP_ID[15:8];
5 :data_out <= IP_ID[7:0];
6 :data_out <= {IP_RSV,IP_DF,IP_MF,IP_FRAG_OFFSET[12:8]};
7 :data_out <= IP_FRAG_OFFSET[7:0];
8 :data_out <= IP_TTL;
9 :data_out <= IP_PROTOCOL;
10:data_out <= check_sum[15:8];
11:data_out <= check_sum[7:0];
12:data_out <= src_ip_tmp[31:24];
13:data_out <= src_ip_tmp[23:16];
14:data_out <= src_ip_tmp[15:8];
15:data_out <= src_ip_tmp[7:0];
16:data_out <= dst_ip_tmp[31:24];
17:data_out <= dst_ip_tmp[23:16];
18:data_out <= dst_ip_tmp[15:8];
19:data_out <= dst_ip_tmp[7:0];
default:data_out <= 8'h00;
endcase
end
else if(state_c == TX_UDP_HED)begin
case(cnt_udp_hed)
0:data_out <= src_port_tmp[15:8];
1:data_out <= src_port_tmp[7:0];
2:data_out <= dst_port_tmp[15:8];
3:data_out <= dst_port_tmp[7:0];
4:data_out <= udp_total_len[15:8];
5:data_out <= udp_total_len[7:0];
6:data_out <= udp_check_sum[15:8];
7:data_out <= udp_check_sum[7:0];
default:data_out <= 8'h00;
endcase
end
else if(state_c == TX_UDP_DATA)begin
data_out <= data_in;
end
else if(state_c == TX_FILL_DATA)begin
data_out <= 8'h00;
end
else if(state_c == TX_CRC)begin
data_out <= 0;
end
end
//以太網頭部計數器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_eth_hed <= 0;
else if(add_cnt_eht_hed)begin
if(end_cnt_eth_hed)
cnt_eth_hed <= 0;
else
cnt_eth_hed <= cnt_eth_hed + 1'b1;
end
end
assign add_cnt_eht_hed = state_c == TX_ETH_HED;
assign end_cnt_eth_hed = add_cnt_eht_hed && cnt_eth_hed == ETH_HED_N - 1;
//IP頭計數器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_ip_hed <= 0;
else if(add_cnt_ip_hed)begin
if(end_cnt_ip_hed)
cnt_ip_hed <= 0;
else
cnt_ip_hed <= cnt_ip_hed + 1;
end
end
assign add_cnt_ip_hed = state_c == TX_IP_HED;
assign end_cnt_ip_hed = add_cnt_ip_hed && cnt_ip_hed == IP_HED_N - 1;
//udp頭部計數器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_udp_hed <= 0;
end
else if(add_cnt_udp_hed)begin
if(end_cnt_udp_hed)
cnt_udp_hed <= 0;
else
cnt_udp_hed <= cnt_udp_hed + 1;
end
end
assign add_cnt_udp_hed = state_c == TX_UDP_HED;
assign end_cnt_udp_hed = add_cnt_udp_hed && cnt_udp_hed == UDP_HED_N - 1;
//UDP數據計數器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_data <= 0;
end
else if(add_cnt_data)begin
if(end_cnt_data)
cnt_data <= 0;
else
cnt_data <= cnt_data + 1;
end
end
assign add_cnt_data = state_c == TX_UDP_DATA;
assign end_cnt_data = add_cnt_data && cnt_data == data_len_tmp - 1;
//填充計數器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_fill <= 0;
end
else if(add_cnt_fill)begin
if(end_cnt_fill)
cnt_fill <= 0;
else
cnt_fill <= cnt_fill + 1;
end
end
assign add_cnt_fill = state_c == TX_FILL_DATA;
assign end_cnt_fill = add_cnt_fill && cnt_fill == (MIN_DATA - IP_HED_LEN - UDP_HED_LEN - data_len_tmp) - 1;
//CRC計數器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_crc <= 0;
end
else if(add_cnt_crc)begin
if(end_cnt_crc)
cnt_crc <= 0;
else
cnt_crc <= cnt_crc + 1;
end
end
assign add_cnt_crc = state_c == TX_CRC && !tx_nocrc_flag;
assign end_cnt_crc = add_cnt_crc && cnt_crc == CNT_CRC_N - 1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_flag <= 0;
else if(tx_en)
tx_flag <= 1;
end
//裝載寫入數據
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dst_mac_tmp <= 0;
src_mac_tmp <= 0;
dst_ip_tmp <= 0;
src_ip_tmp <= 0;
dst_port_tmp <= 0;
src_port_tmp <= 0;
data_len_tmp <= 0;
end
else if(tx_en && (!tx_flag))begin
dst_mac_tmp <= dst_mac;
src_mac_tmp <= src_mac;
dst_ip_tmp <= dst_ip;
src_ip_tmp <= src_ip;
dst_port_tmp <= dst_port;
src_port_tmp <= src_port;
data_len_tmp <= data_len;
end
end
//IP總長度,20字節的IP首部+8字節UDP首部+UDP數據長度
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
ip_total_len <= 0;
else if(state_c == TX_ETH_HED && (cnt_eth_hed == 0 && add_cnt_eht_hed))
ip_total_len = IP_HED_LEN + UDP_HED_LEN + data_len_tmp;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
udp_total_len <= 0;
else if(state_c == TX_ETH_HED && (cnt_eth_hed == 0 && add_cnt_eht_hed))
udp_total_len <= UDP_HED_LEN + data_len_tmp;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
sum_en <= 0;
else if(state_c == TX_ETH_HED && end_cnt_eth_hed)
sum_en <= 1;
else
sum_en <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
crc_init <= 0;
else if(tx_en && (!tx_flag))
crc_init <= 1;
else
crc_init <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
crc_en <= 0;
else if(state_c == TX_ETH_HED)begin
if(cnt_eth_hed == 9 - 1 && add_cnt_eht_hed)
crc_en <= 1;
end
else if(state_c == TX_IP_HED || state_c == TX_UDP_HED || state_c == TX_UDP_DATA || state_c == TX_FILL_DATA)
crc_en <= 1;
else
crc_en <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
gmii_tx <= 0;
else if(tx_nocrc_flag)
gmii_tx <= data_out;
else if(state_c == TX_CRC)begin
case(cnt_crc)
0:gmii_tx <= crc_out[7:0];
1:gmii_tx <= crc_out[15:8];
2:gmii_tx <= crc_out[23:16];
3:gmii_tx <= crc_out[31:24];
default:gmii_tx <= 8'h00;
endcase
end
else if(tx_done)
gmii_tx <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_nocrc_flag <= 0;
else if(state_c == TX_ETH_HED || state_c == TX_IP_HED || state_c == TX_UDP_HED || state_c == TX_UDP_DATA || state_c == TX_FILL_DATA)
tx_nocrc_flag <= 1;
else
tx_nocrc_flag <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_done <= 0;
else if(end_cnt_crc)
tx_done <= 1;
else
tx_done <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
data_vld <= 0;
else if(end_cnt_udp_hed)
data_vld <= 1;
else if(end_cnt_data)
data_vld <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
gmii_en <= 0;
else if(cnt_eth_hed == 1 && add_cnt_eht_hed)
gmii_en <= 1;
else if(tx_done)
gmii_en <= 0;
end
assign gmii_clk = clk;
assign udp_check_sum = 0;
ip_checksum ip_checksum(
.clk (clk),
.rst_n (rst_n),
.sum_en (sum_en), //使能
.ver (IP_VER), //ip版本
.hdr_len (IP_HDR_LEN), //首部長度(單位,字節)
.tos (IP_TOS), //服務類型
.total_len (ip_total_len), //總長度
.id (IP_ID), //分段標識
.rsv (IP_RSV), //未用
.df (IP_DF), //DF
.mf (IP_MF), //MF
.frag_offset (IP_FRAG_OFFSET), //段偏移
.ttl (IP_TTL), //生存周期TTL(單位,秒)
.protocal (IP_PROTOCOL), //上層協議
.check_sum (check_sum), //報頭校驗和
.src_ip (src_ip_tmp), //源ip地址
.dst_ip (dst_ip_tmp) //目的ip地址
);
crc32_d8 crc32_d8(
.clk (clk),
.rst_n (rst_n),
.data (data_out),
.crc_init (crc_init),
.crc_en (crc_en),
.crc_out (crc_out)
);
endmodule
三、IP校驗和模塊
module ip_checksum(
clk ,
rst_n ,
sum_en , //使能
ver , //ip版本
hdr_len , //首部長度(單位,字節)
tos , //服務類型
total_len , //總長度
id , //分段標識
rsv , //未用
df , //DF
mf , //MF
frag_offset , //段偏移
ttl , //生存周期TTL(單位,秒)
protocal , //上層協議
check_sum , //報頭校驗和
src_ip , //源ip地址
dst_ip //目的ip地址
);
localparam VER_W = 4;
localparam HDR_W = 4;
localparam TOS_W = 8;
localparam TOT_W = 16;
localparam ID_W = 16;
localparam FRAG_W = 13;
localparam TTL_W = 8;
localparam PTOC_W = 8;
localparam CHEC_W = 16;
localparam SR_W = 32;
localparam DS_W = 32;
localparam SUM_W = 32;
localparam ACC_W = 17;
input clk;
input rst_n;
input sum_en;
input [VER_W-1:0] ver;
input [HDR_W-1:0] hdr_len;
input [TOS_W-1:0] tos;
input [TOT_W-1:0] total_len;
input [ID_W-1:0] id;
input rsv;
input df;
input mf;
input [FRAG_W-1:0] frag_offset;
input [TTL_W-1:0] ttl;
input [PTOC_W-1:0] protocal;
input [SR_W-1:0] src_ip;
input [DS_W-1:0] dst_ip;
output [CHEC_W-1:0] check_sum;
reg [CHEC_W-1:0] check_sum;
//中間變量
reg [SUM_W-1:0] sum;
reg [ACC_W-1:0] acc_high_low;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
sum <= 0;
else if(sum_en)
sum <= {ver,hdr_len,tos} + total_len + id + {rsv,df,mf,frag_offset} + {ttl,protocal} + src_ip[31:16] + src_ip[15:0] + dst_ip[31:16] + dst_ip[15:0];
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
acc_high_low <= 0;
else
acc_high_low <= sum[15:0] + sum[31:16];
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
check_sum <= 0;
else
check_sum <= ~(acc_high_low[15:0] + acc_high_low[16]);
end
endmodule
四、CRC校驗模塊
////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 1999-2008 Easics NV.
// This source file may be used and distributed without restriction
// provided that this copyright statement is not removed from the file
// and that any derivative work contains the original copyright notice
// and the associated disclaimer.
//
// THIS SOURCE FILE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS
// OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
// WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//
// Purpose : synthesizable CRC function
// * polynomial: x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + 1
// * data width: 8
//
// Info : tools@easics.be
// http://www.easics.com
////////////////////////////////////////////////////////////////////////////////
module crc32_d8(
clk ,
rst_n ,
data ,
crc_init ,
crc_en ,
crc_out
);
localparam DATA_W = 8;
localparam OUT_W = 32;
input clk;
input rst_n;
input [DATA_W-1:0] data;
input crc_init;
input crc_en;
output [OUT_W-1:0] crc_out;
wire [OUT_W-1:0] crc_out;
//中間變量
wire [DATA_W-1:0] data_in;
reg [OUT_W-1:0] crc_out_inv;
//生成data反轉電路
generate
genvar i;
for(i = 0;i < DATA_W;i = i + 1)begin
assign data_in[i] = data[(DATA_W-1) - i];
end
endgenerate
//生成crc反轉電路
generate
genvar j;
for(j = 0;j < OUT_W;j = j + 1)begin
assign crc_out[j] = ~crc_out_inv[(OUT_W-1) - j];
end
endgenerate
//輸出CRC,初始化CRC為FFFF_FFFF,使能輸出CRC
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
crc_out_inv <= 0;
else if(crc_init)
crc_out_inv <= 32'hffff_ffff;
else if(crc_en)
crc_out_inv <= nextCRC32_D8(data_in,crc_out_inv);
end
// polynomial: x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + 1
// data width: 8
// convention: the first serial bit is D[7]
function [31:0] nextCRC32_D8;
input [7:0] Data;
input [31:0] crc;
reg [7:0] d;
reg [31:0] c;
reg [31:0] newcrc;
begin
d = Data;
c = crc;
newcrc[0] = d[6] ^ d[0] ^ c[24] ^ c[30];
newcrc[1] = d[7] ^ d[6] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[30] ^ c[31];
newcrc[2] = d[7] ^ d[6] ^ d[2] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[26] ^ c[30] ^ c[31];
newcrc[3] = d[7] ^ d[3] ^ d[2] ^ d[1] ^ c[25] ^ c[26] ^ c[27] ^ c[31];
newcrc[4] = d[6] ^ d[4] ^ d[3] ^ d[2] ^ d[0] ^ c[24] ^ c[26] ^ c[27] ^ c[28] ^ c[30];
newcrc[5] = d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[27] ^ c[28] ^ c[29] ^ c[30] ^ c[31];
newcrc[6] = d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[2] ^ d[1] ^ c[25] ^ c[26] ^ c[28] ^ c[29] ^ c[30] ^ c[31];
newcrc[7] = d[7] ^ d[5] ^ d[3] ^ d[2] ^ d[0] ^ c[24] ^ c[26] ^ c[27] ^ c[29] ^ c[31];
newcrc[8] = d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[0] ^ c[24] ^ c[25] ^ c[27] ^ c[28];
newcrc[9] = d[5] ^ d[4] ^ d[2] ^ d[1] ^ c[1] ^ c[25] ^ c[26] ^ c[28] ^ c[29];
newcrc[10] = d[5] ^ d[3] ^ d[2] ^ d[0] ^ c[2] ^ c[24] ^ c[26] ^ c[27] ^ c[29];
newcrc[11] = d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[3] ^ c[24] ^ c[25] ^ c[27] ^ c[28];
newcrc[12] = d[6] ^ d[5] ^ d[4] ^ d[2] ^ d[1] ^ d[0] ^ c[4] ^ c[24] ^ c[25] ^ c[26] ^ c[28] ^ c[29] ^ c[30];
newcrc[13] = d[7] ^ d[6] ^ d[5] ^ d[3] ^ d[2] ^ d[1] ^ c[5] ^ c[25] ^ c[26] ^ c[27] ^ c[29] ^ c[30] ^ c[31];
newcrc[14] = d[7] ^ d[6] ^ d[4] ^ d[3] ^ d[2] ^ c[6] ^ c[26] ^ c[27] ^ c[28] ^ c[30] ^ c[31];
newcrc[15] = d[7] ^ d[5] ^ d[4] ^ d[3] ^ c[7] ^ c[27] ^ c[28] ^ c[29] ^ c[31];
newcrc[16] = d[5] ^ d[4] ^ d[0] ^ c[8] ^ c[24] ^ c[28] ^ c[29];
newcrc[17] = d[6] ^ d[5] ^ d[1] ^ c[9] ^ c[25] ^ c[29] ^ c[30];
newcrc[18] = d[7] ^ d[6] ^ d[2] ^ c[10] ^ c[26] ^ c[30] ^ c[31];
newcrc[19] = d[7] ^ d[3] ^ c[11] ^ c[27] ^ c[31];
newcrc[20] = d[4] ^ c[12] ^ c[28];
newcrc[21] = d[5] ^ c[13] ^ c[29];
newcrc[22] = d[0] ^ c[14] ^ c[24];
newcrc[23] = d[6] ^ d[1] ^ d[0] ^ c[15] ^ c[24] ^ c[25] ^ c[30];
newcrc[24] = d[7] ^ d[2] ^ d[1] ^ c[16] ^ c[25] ^ c[26] ^ c[31];
newcrc[25] = d[3] ^ d[2] ^ c[17] ^ c[26] ^ c[27];
newcrc[26] = d[6] ^ d[4] ^ d[3] ^ d[0] ^ c[18] ^ c[24] ^ c[27] ^ c[28] ^ c[30];
newcrc[27] = d[7] ^ d[5] ^ d[4] ^ d[1] ^ c[19] ^ c[25] ^ c[28] ^ c[29] ^ c[31];
newcrc[28] = d[6] ^ d[5] ^ d[2] ^ c[20] ^ c[26] ^ c[29] ^ c[30];
newcrc[29] = d[7] ^ d[6] ^ d[3] ^ c[21] ^ c[27] ^ c[30] ^ c[31];
newcrc[30] = d[7] ^ d[4] ^ c[22] ^ c[28] ^ c[31];
newcrc[31] = d[5] ^ c[23] ^ c[29];
nextCRC32_D8 = newcrc;
end
endfunction
endmodule
五、仿真驗證
`timescale 1ns / 1ns
module eth_tb();
localparam MAC_W = 48;
localparam IP_W = 32;
localparam PORT_W = 16;
localparam DATA_LEN_W = 16;
localparam GMII_W = 8;
localparam DATA_W = 8;
parameter CYCLE = 20;
reg clk;
reg rst_n;
reg tx_en; //發送使能
reg [MAC_W-1:0] dst_mac; //目的Mac地址
reg [MAC_W-1:0] src_mac; //源Mac地址
reg [IP_W-1:0] dst_ip; //目的ip地址
reg [IP_W-1:0] src_ip; //源ip地址
reg [PORT_W-1:0] dst_port; //目的端口
reg [PORT_W-1:0] src_port; //源端口
reg [DATA_LEN_W-1:0] data_len; //發送數據長度
reg [DATA_W-1:0] data_in; //輸入數據
wire gmii_clk; //以太網接口時鍾
wire gmii_en; //以太網使能
wire [GMII_W-1:0] gmii_tx; //以太網數據
wire tx_done; //發送完成
wire data_vld; //數據有效標志信號
eth_udp_tx_gmii eth_udp_tx_gmii(
.clk (clk),
.rst_n (rst_n),
.tx_en (tx_en),
.tx_done (tx_done),
.dst_mac (dst_mac),
.src_mac (src_mac),
.dst_ip (dst_ip),
.src_ip (src_ip),
.dst_port (dst_port),
.src_port (src_port),
.gmii_clk (gmii_clk),
.data_len (data_len),
.data_vld (data_vld),
.gmii_en (gmii_en),
.gmii_tx (gmii_tx),
.data_in (data_in)
);
initial clk = 1;
always #(CYCLE/2) clk = ~clk;
initial begin
rst_n = 1;
#3;
rst_n = 0;
#(3*CYCLE)
rst_n = 1;
end
initial begin
dst_mac = 0;
#(10*CYCLE)
dst_mac = 48'hA1_6F_5B_12_01_F8;
end
initial begin
src_mac = 0;
#(10*CYCLE)
src_mac = 48'h00_0a_35_01_fe_c0;
end
initial begin
dst_ip = 0;
#(10*CYCLE)
dst_ip = 32'hc0_a8_00_02;
end
initial begin
src_ip = 0;
#(10*CYCLE)
src_ip = 32'hc0_a8_00_03;
end
initial begin
dst_port = 0;
#(10*CYCLE)
dst_port = 16'd8080;
end
initial begin
src_port = 0;
#(10*CYCLE)
src_port = 16'd1010;
end
initial begin
data_len = 0;
#(10*CYCLE)
data_len = 10;
end
reg [4-1:0] cnt_data_in;
wire add_cnt_data_in;
wire end_cnt_data_in;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_data_in <= 0;
end
else if(add_cnt_data_in)begin
if(end_cnt_data_in)
cnt_data_in <= 0;
else
cnt_data_in <= cnt_data_in + 1;
end
end
assign add_cnt_data_in = data_vld;
assign end_cnt_data_in = add_cnt_data_in && cnt_data_in == 11 - 1;
always @(*)begin
case (cnt_data_in)
0 :data_in = "X";
1 :data_in = "I";
2 :data_in = "L";
3 :data_in = "I";
4 :data_in = "N";
5 :data_in = "X";
6 :data_in = " ";
7 :data_in = "F";
8 :data_in = "P";
9 :data_in = "G";
10:data_in = "A";
default: data_in = 8'h00;
endcase
end
initial begin
@(posedge tx_done)
#200;
$stop;
end
initial begin
#1;
tx_en = 0;
#(15*CYCLE)
tx_en = 1;
#(CYCLE)
tx_en = 0;
end
endmodule
六、板級驗證
module eth_udp_tx_gmii_test(
clk ,
rst_n ,
led ,
eth_rst_n,
gmii_clk ,
gmii_en ,
gmii_tx
);
//延遲0.5ms
parameter DELAY_N = 625_00000;
input clk;
input rst_n;
output led;
output eth_rst_n;
output gmii_clk ;
output gmii_en ;
output [8-1:0] gmii_tx;
wire led;
wire eth_rst_n;
wire gmii_clk ;
wire gmii_en ;
wire [8-1:0] gmii_tx;
wire locked;
wire clk125M;
wire data_vld;
reg [8-1:0] data_in;
reg tx_en;
//PLL穩定后,開始工作
assign eth_rst_n = locked;
assign led = locked;
pll pll(
.clk_out1(clk125M),
.resetn(rst_n),
.locked(locked),
.clk_in1(clk)
);
eth_udp_tx_gmii eth_udp_tx_gmii(
.clk (clk125M),
.rst_n (eth_rst_n),
.tx_en (tx_en),
.tx_done (tx_done),
.dst_mac (48'h10_1E_1F_12_11_18),
.src_mac (48'h00_0a_35_01_fe_c0),
.dst_ip (32'hc0_a8_00_03),
.src_ip (32'hc0_a8_00_02),
.dst_port (16'd6000),
.src_port (16'd5000),
.gmii_clk (gmii_clk),
.data_len (23),
.data_vld (data_vld),
.gmii_en (gmii_en),
.gmii_tx (gmii_tx),
.data_in (data_in)
);
reg [26-1:0] cnt_delay;
wire add_cnt_delay;
wire end_cnt_delay;
always @(posedge clk125M or negedge rst_n)begin
if(!rst_n)begin
cnt_delay <= 0;
end
else if(add_cnt_delay)begin
if(end_cnt_delay)
cnt_delay <= 0;
else
cnt_delay <= cnt_delay + 1;
end
end
assign add_cnt_delay = eth_rst_n;
assign end_cnt_delay = add_cnt_delay && cnt_delay == DELAY_N - 1 ;
always @(posedge clk125M or negedge rst_n)begin
if(!rst_n)
tx_en <= 0;
else if(end_cnt_delay)
tx_en <= 1;
else
tx_en <= 0;
end
reg [5-1:0] cnt_data_in;
wire add_cnt_data_in;
wire end_cnt_data_in;
always @(posedge clk125M or negedge rst_n)begin
if(!rst_n)begin
cnt_data_in <= 0;
end
else if(add_cnt_data_in)begin
if(end_cnt_data_in)
cnt_data_in <= 0;
else
cnt_data_in <= cnt_data_in + 1;
end
end
assign add_cnt_data_in = data_vld;
assign end_cnt_data_in = add_cnt_data_in && cnt_data_in == 23 - 1;
always @(*)begin
case (cnt_data_in)
16'd0 : data_in = "H";
16'd1 : data_in = "e";
16'd2 : data_in = "l";
16'd3 : data_in = "l";
16'd4 : data_in = "o";
16'd5 : data_in = ",";
16'd6 : data_in = " ";
16'd7 : data_in = "w";
16'd8 : data_in = "e";
16'd9 : data_in = "l";
16'd10 : data_in = "c";
16'd11 : data_in = "o";
16'd12 : data_in = "m";
16'd13 : data_in = "e";
16'd14 : data_in = " ";
16'd15 : data_in = "t";
16'd16 : data_in = "o";
16'd17 : data_in = " ";
16'd18 : data_in = "F";
16'd19 : data_in = "P";
16'd20 : data_in = "G";
16'd21 : data_in = "A";
16'd22 : data_in = "!";
default:data_in = 8'h00;
endcase
end
endmodule
七、問題記錄
在板級驗證過程中,抓取的包出現異常
原因是FPGA開發板連接的物理層芯片引腳沒有使用,但是FPGA的未用引腳是高電平和低電平影響了芯片的正常工作
解決方法是:添加一條約束,將未用引腳置為高阻態
set_property BITSTREAM.CONFIG.UNUSEDPIN Pullnone [current_design]
在時序分析過程中,發現時序不符合要求
因此,需要將輸出通過約束加到IOB上
具體方法有:
參考鏈接:https://blog.csdn.net/alangaixiaoxiao/article/details/106140715
將寄存器放入IOB中的方法:
1.verilog代碼中
(IOB = “TRUE”) output reg [3:0] LED;
2.XDC文件中
set_property IOB TRUE [get_ports {REMOTE_FIFO_din[15]}]
(IOB = "TRUE") output gmii_en; //以太網使能
(IOB = "TRUE") output [GMII_W-1:0] gmii_tx; //以太網數據