(1)状态设计:
- ST_IDLE :空闲状态
- ST_PREAMBLE :前导码+帧起始界定符
- ST_ETH_HEAD :以太网帧头:目标MAC地址+源MAC地址+类型/长度
- ST_IP_HEAD :IP首部
- ST_UDP_HEAD :UDP首部(8个字节)
- ST_RX_DATA :接收数据状态
- ST_RX_DONE :接收完成状态
(2)状态转移图:
(3)以太网帧头:目标MAC地址(6个字节)+源MAC地址(6个字节)+ 类型/长度(2个字节)(08 00 表示基于IP协议) 目标MAC地址需要使用一个寄存器寄存起来。
(4)IP首部:目的 IP 地址在首部的第 17 至 20字节,需要使用一个寄存器寄存起来。
比如第一个字节为45,则IP地址长度对应5左移两位(010100),即长度为20
(5)UDP首部:第五个字节和第六个字节包含了UDP长度信息,例如:0028(40),有效数据长度等于UDP长度-首部长度(8个字节)
(6)UDP接收模块代码:
//UDP接收模块设计
module udp_rx(
input wire gmii_rxc ,
input wire reset_n ,
input wire gmii_rx_dv , //GMII输入数据有效信号
input wire [7:0] gmii_rxd , //GMII输入数据
output reg [31:0] rec_data , //以太网接收后转化为32位的数据
output reg rec_en , //以太网接收的数据使能信号
output reg rec_pkt_done , //以太网单包数据接收完成信号
output reg [15:0] rec_byte_num //以太网接收的有效数据字节数
);
/*--------------参数定义,parameter全局变量,可以在上层进行修改,而localparam则是局部变量,仅在该模块使用-----------*/
//开发板MAC地址(48位)
parameter BOARO_MAC = 48'hff_ff_ff_ff_ff_ff ;
//开发板IP地址(32位)
parameter BOARO_IP = {8'd0,8'd0,8'd0,8'd0} ;
//状态机状态定义 独热吗编码
localparam ST_IDLE = 7'b000_0001 ; //空闲状态,等待接收前导码
localparam ST_PREAMBLE = 7'b000_0010 ; //接收前导码和帧起始界定符状态
localparam ST_ETH_HEAD = 7'b000_0100 ; //接收以太网帧头状态
localparam ST_IP_HEAD = 7'b000_1000 ; //接收IP首部状态
localparam ST_UDP_HEAD = 7'b001_0000 ; //接收UDP首部
localparam ST_RX_DATA = 7'b010_0000 ; //接收有效数据状态
localparam ST_RX_DONE = 7'b100_0000 ; //接收完成状态
//以太网协议类型定义 IP协议
localparam ETH_TYPE = 16'h0800 ;
/*--------------寄存器变量定义-----*/
reg [6:0] cur_state ; //当前状态
reg [6:0] next_state ; //下一个状态
reg sw_en ; //下一个状态跳转标志信号
reg error_en ; //接收错误标志信号
reg [4:0] cnt_byte ; //字节计数器
reg [47:0] des_mac ; //目标MAC地址,在本模块中及开发板MAC地址
reg [15:0] eth_type ; //以太网类型
reg [31:0] des_ip ; //目标IP地址,在本模块中及开发板IP地址
reg [5:0] ip_len ; //IP首部长度
reg [15:0] udp_len ; //UDP长度
reg [15:0] cnt_data ; //数据长度计算
reg [15:0] data_len ; //UDP有效数据长度
reg [1:0] cnt_rec_data ; //8位转32位计数器
/*--------------三段式状态机编写主程序--------*/
//第一段:时序逻辑描述状态转移
always@(posedge gmii_rxc or negedge reset_n)begin
if(!reset_n)
cur_state <= ST_IDLE ;
else
cur_state <= next_state ;
end
//第二段:组合逻辑判断状态机转移条件
always@(*)begin
next_state = ST_IDLE ;
case(cur_state)
ST_IDLE :
if(sw_en)
next_state = ST_PREAMBLE;
else
next_state = ST_IDLE;
ST_PREAMBLE :
if(sw_en)
next_state = ST_ETH_HEAD;
else if(error_en)
next_state = ST_RX_DONE;
else
next_state = ST_PREAMBLE;
ST_ETH_HEAD :
if(sw_en)
next_state = ST_IP_HEAD;
else if(error_en)
next_state = ST_RX_DONE;
else
next_state = ST_ETH_HEAD;
ST_IP_HEAD :
if(sw_en)
next_state = ST_UDP_HEAD;
else if(error_en)
next_state = ST_RX_DONE;
else
next_state = ST_IP_HEAD;
ST_UDP_HEAD :
if(sw_en)
next_state = ST_RX_DATA;
else
next_state = ST_UDP_HEAD;
ST_RX_DATA :
if(sw_en)
next_state = ST_RX_DONE;
else
next_state = ST_RX_DATA;
ST_RX_DONE :
if(sw_en)
next_state = ST_IDLE;
else
next_state = ST_RX_DONE;
default:next_state = ST_IDLE ;
endcase
end
//第三段:时序逻辑描述寄存器变量和状态输出
always@(posedge gmii_rxc or negedge reset_n)begin
if(!reset_n)begin
sw_en <= 1'd0;
error_en <= 1'd0;
cnt_byte <= 5'd0;
des_mac <= 48'd0;
eth_type <= 16'd0;
des_ip <= 32'd0;
ip_len <= 6'd0;
udp_len <= 16'd0;
cnt_data <= 16'd0;
data_len <= 16'd0;
cnt_rec_data <= 2'd0;
rec_data <= 32'd0;
rec_en <= 1'd0;
rec_pkt_done <= 1'd0;
rec_byte_num <= 16'd0;
end
else begin
sw_en <= 1'd0;
error_en <= 1'd0;
rec_en <= 1'd0;
rec_pkt_done <= 1'd0;
case(next_state)
ST_IDLE :begin //GMII输入有效信号拉高,接收到0x55,跳转至下一个状态
if((gmii_rx_dv == 1'd1) && (gmii_rxd == 8'h55))
sw_en <= 1'd1;
end
ST_PREAMBLE :begin
if(gmii_rx_dv == 1'd1)begin
cnt_byte <= cnt_byte + 5'd1;
if((cnt_byte < 5'd6) && (gmii_rxd != 8'h55)) //如果正确,0,1,2,3,4,5 再加上空闲跳转到该状态的一个0x55,总共7个0x55
error_en <= 1'd1;
else if(cnt_byte == 5'd6)begin
cnt_byte <= 5'd0;
if(gmii_rxd == 8'hd5) //一个0xd5
sw_en <= 1'd1;
else
error_en <= 1'd1;
end
end
end
ST_ETH_HEAD :begin
if(gmii_rx_dv == 1'd1)begin
cnt_byte <= cnt_byte + 5'd1;
if(cnt_byte < 5'd6) //0,1,2,3,4,5 6个8位数据,并凑出一个48位的MAC地址
des_mac <= {des_mac[39:0],gmii_rxd};
else if(cnt_byte == 5'd12) //6,7,8,9,10,11 6个8位数据,是源MAC地址,12,13位为协议类型
eth_type[15:8] <= gmii_rxd;
else if(cnt_byte == 5'd13)begin
eth_type[7:0] <= gmii_rxd;
cnt_byte <= 5'd0;
if(((des_mac == BOARO_MAC) || (des_mac == 48'hff_ff_ff_ff_ff_ff))
&& eth_type[15:8] == ETH_TYPE[15:8]
&& gmii_rxd == ETH_TYPE[7:0])
sw_en <= 1'd1;
else
error_en <= 1'd1;
end
end
end
ST_IP_HEAD :begin
if(gmii_rx_dv == 1'd1)begin
cnt_byte <= cnt_byte + 5'd1;
if(cnt_byte == 5'd0) //IP首部第一个字节的后四位左移2位 代表IP首部的长度
ip_len <= {gmii_rxd[3:0],2'd0};
else if((cnt_byte >= 5'd16) && (cnt_byte <= 5'd18)) //IP首部的后四个字节组成目标IP地址
des_ip <= {des_ip[23:0],gmii_rxd};
else if(cnt_byte == 5'd19)begin
des_ip <= {des_ip[23:0],gmii_rxd};
if((des_ip[23:0] == BOARO_IP[31:8])
&&(gmii_rxd == BOARO_IP[7:0]))
if(cnt_byte == ip_len - 1'b1)begin
sw_en <= 1'd1;
cnt_byte <= 5'd0;
end
else begin
error_en <= 1'd1;
cnt_byte <= 5'd0;
end
end
else if(cnt_byte == ip_len - 1'b1)begin
sw_en <= 1'd1;
cnt_byte <= 5'd0;
end
end
end
/*------UDP首部(8个字节):第五个字节和第六个字节包含了UDP长度信息,
例如:0028(40),有效数据长度等于UDP长度-首部长度(8个字节)----*/
ST_UDP_HEAD :begin
if(gmii_rx_dv == 1'd1)begin
cnt_byte <= cnt_byte + 5'd1;
if(cnt_byte == 5'd4)
udp_len[15:8] <= gmii_rxd;
else if(cnt_byte == 5'd5)
udp_len[7:0] <= gmii_rxd;
else if(cnt_byte == 5'd7)begin
data_len <= udp_len - 16'd8;
sw_en <= 1'd1;
cnt_byte <= 5'd0;
end
end
end
ST_RX_DATA :begin
if(gmii_rx_dv == 1'd1)begin
cnt_data <= cnt_data + 16'd1;
cnt_rec_data <= cnt_rec_data + 2'd1;
if(cnt_data == data_len - 16'd1)begin
sw_en <= 1'd1;
cnt_data <= 16'd0;
cnt_rec_data <= 2'd0;
rec_pkt_done <= 1'd1;
rec_en <= 1'd1;
rec_byte_num <= data_len;
end
if(cnt_rec_data == 2'd0)
rec_data[31:24] <= gmii_rxd;
else if(cnt_rec_data == 2'd1)
rec_data[23:16] <= gmii_rxd;
else if(cnt_rec_data == 2'd2)
rec_data[15:8] <= gmii_rxd;
else if(cnt_rec_data == 2'd3)begin
rec_en <= 1'd1;
rec_data[7:0] <= gmii_rxd;
end
end
end
ST_RX_DONE :begin //单包数据接收完成
if((gmii_rx_dv == 1'd0) && (sw_en == 1'd0))
sw_en <= 1'd1;
end
default: ;
endcase
end
end
endmodule
(7)仿真代码:
`timescale 1ns / 1ps
module tb_udp_rx;
reg gmii_rxc ;
reg reset_n ;
reg start_flag ;
reg gmii_rx_dv ;
reg [7:0] cnt ;
reg [7:0] data_mem[85:0] ;
wire [7:0] gmii_rxd ;
wire [31:0] rec_data ;
wire rec_en ;
wire rec_pkt_done ;
wire [15:0] rec_byte_num ;
initial gmii_rxc = 1'd1;
always #10 gmii_rxc = ~gmii_rxc;
initial begin
reset_n <= 1'd0;
start_flag <= 1'd0;
#200
reset_n <= 1'd1;
#200
start_flag <= 1'd1;
#40
start_flag <= 1'd0;
#3000
$stop;
end
initial $readmemh("D:/FPGA/35_eth_udp_loop/vivado_prj/project_1.srcs/sim_1/new/data.txt",data_mem);
assign gmii_rxd = (gmii_rx_dv == 1'd1)? data_mem[cnt] : 8'd0;
always@(negedge gmii_rxc or negedge reset_n)
if(!reset_n)
cnt <= 1'd0;
else if(gmii_rx_dv == 1'd1)
cnt <= cnt + 1'b1;
else
cnt <= cnt;
always@(negedge gmii_rxc or negedge reset_n)
if(!reset_n)
gmii_rx_dv <= 1'd0;
else if(cnt == 8'd85) //8 + 14 + 20 + 8 + 32 + 4 = 86
gmii_rx_dv <= 1'd0;
else if(start_flag)
gmii_rx_dv <= 1'd1;
else
gmii_rx_dv <= gmii_rx_dv;
defparam udp_rx_inst.BOARO_MAC = 48'h12_34_56_78_9a_bc;
defparam udp_rx_inst.BOARO_IP = 32'ha9_fe_01_17 ;
udp_rx udp_rx_inst(
.gmii_rxc (gmii_rxc ),
.reset_n (reset_n ),
.gmii_rx_dv (gmii_rx_dv ),
.gmii_rxd (gmii_rxd ),
.rec_data (rec_data ),
.rec_en (rec_en ),
.rec_pkt_done (rec_pkt_done ),
.rec_byte_num (rec_byte_num )
);
endmodule
(8)仿真波形: