FPGA——以太网MAC层数据发送协议实现及验证
一、设计思路
-
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; //以太网数据