基于FPGA的以太网设计(六)
前面分别实现了ARP协议和ICMP协议,但这俩协议都不能进行数据的传输,如果想要用以太网传输数据的话还需要实现UDP协议或者TCP协议,关于二者的差别主要包括以下几点:
1.连接性
TCP是面向连接的协议,它在传输数据之前需要建立连接(三次握手),并在数据传输完成后关闭连接(四次挥手)。这种连接是全双工的,即数据可以在两个方向上同时传输。
UDP则是无连接的协议,它不需要建立或关闭连接,发送方可以直接发送数据报,接收方也可以随时接收数据报。
TCP提供可靠的传输服务。它使用确认和重传机制来确保数据正确、完整、有序地到达目的地。TCP还通过流量控制和拥塞控制机制来防止网络过载。
UDP则提供不可靠的传输服务。它不检查数据包的顺序、错误或重传。如果数据包在传输过程中丢失或损坏,UDP不会采取任何补救措施。因此,UDP通常用于对实时性要求较高、但对数据可靠性要求不高的应用(如视频流、音频流、实时游戏等)
TCP的头部开销相对较大,包含更多的控制信息,如序列号、确认号、窗口大小等。
UDP的头部开销较小,只包含必要的字段,如源端口、目的端口、长度和校验和。
由于TCP需要建立连接、确认数据、处理重传等,所以其传输效率相对较低。但这也使得TCP在需要可靠传输的场景下表现更好。
UDP则不需要这些额外的步骤,因为其传输效率更高。但这也意味着UDP在传输过程中可能会丢失数据。
TCP通常用于需要可靠传输的场景,如文件存储、电子邮件、远程登录等。
UDP则常用于对实时性要求较高、但对数据可靠性要求不高的场景,如视频流、音频流、DNS查询、VoIP(网络电话)等。
TCP具有流量控制和拥塞控制机制,可以根据网络状况动态调整发送速率,以防止网络拥塞和丢包。
UDP则没有这些机制,它只负责将数据报从源端发送到目的端,则不关心网络状况和数据传输质量。
TCP通常用于一对一的通信,即一个TCP连接只能有一个发送方和一个接收方。
UDP则支持一对多、多对一和多对多的通信模式,可以实现广播和组播功能。
总而言之,UDP的优点是协议简单,传输速度快,不用进行握手,一般用于进行视频数据的传输。
UDP协议结构图如下所示:
UDP首部组成如下:
UDP首部主要包括源端口号、目的端口号、UDP总长度和校验和。
源端口号:2个字节的发送端端口号,用于区分不同的发送端口。
目的端口号:2个字节的接收端端口号。
UDP长度:UDP首部和数据段的长度,单位字节,对于接收方来说该长度其实作用不大,因为UDP数据段的长度可以通过IP首部的总长度和IP首部长度计算出来。
UDP校验和:计算方式与IP首部校验和一致,需要对UDP伪首部、UDP首部、UDP数据进行校验。伪首部包括源IP地址、目的IP地址、协议类型、UDP长度。
UDP数据组成如下图所示:
代码实现如下所示:
发送部分:
`timescale 1ns / 1ps
module udp_tx(
input clk , //系统时钟
input rst_n , //系统复位
//input
input tx_start_en , //发送使能信号
input [15:0] tx_byte_num , //发送有效字节数
input [7:0] tx_data , //发送数据
input [47:0] des_mac , //源MAC地址
input [31:0] des_ip , //源ip地址
input [31:0] crc_data , //CRC校验数据
input [7:0] crc_next , //CRC下次校验数据
//ooutput
output reg tx_done , //发送结束信号
output reg tx_req , //发送请求信号
output reg gmii_tx_en , //GMII发送使能信号
output reg [7:0] gmii_txd , //GMII发送数据
output reg crc_en , //CRC校验使能
output reg crc_clr //CRC清零信号
);
//开发板MAC地址 00-11-22-33-44-55
parameter BOARD_MAC = 48'h00_11_22_33_44_55;
//开发板IP地址 192.168.1.123
parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd123};
//目的MAC地址 ff_ff_ff_ff_ff_ff
parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff;
//目的IP地址 192.168.1.102
parameter DES_IP = {8'd192,8'd168,8'd1,8'd102};
localparam IDLE = 7'b000_0001 ; //初始化状态
localparam CHECK_SUM = 7'b000_0010 ; //校验和
localparam PREAMBLE = 7'b000_0100 ; //发送前导码+SFD
localparam ETH_HEAD = 7'b000_1000 ; //发送以太网帧头
localparam IP_HEAD = 7'b001_0000 ; //发送ip首部
localparam TX_DATA = 7'b010_0000 ; //发送数据
localparam CRC = 7'b100_0000 ; //CRC校验
localparam ETH_TYPE = 16'h0800 ; //以太网协议类型 IP协议
localparam MIN_DATA_NUM= 16'd18 ; //以太网数据最小46个字节,IP首部20个字节+UDP首部8个字节,所以数据至少46-20-8=18个字节
reg [6:0] cur_state ; //现态
reg [6:0] next_state ; //次态
reg state_en ; //状态跳转使能
reg [7:0] preamble[7:0] ; //前导码+帧起始界定符
reg [7:0] eth_head[13:0] ; //以太网帧头
reg [31:0] ip_head[6:0] ; //ip首部
reg [4:0] cnt ; //数据发送计数器
reg start_en_r0 ; //暂存发送使能信号
reg start_en_r1 ;
reg start_en_r2 ;
reg [15:0] tx_data_num ; //发送有效数据字节个数
reg [15:0] total_num ; //总字节个数
reg trig_tx_en ; //发送使能触发信号
reg [15:0] udp_num ; //udp字节数
reg [31:0] check_buffer ; //校验和
reg [1:0] tx_bit_sel ; //
reg [15:0] data_cnt ; //ip首部发送数据计数器
reg tx_done_t ; //发送结束信号寄存器
reg [4:0] real_add_cnt ; //实际多发字节数
wire posedge_start ; //开始发送信号上升沿
wire [15:0] real_tx_data_num; //实际发送字节数据
assign posedge_start = (~start_en_r2) && start_en_r1;
assign real_tx_data_num = (tx_data_num >= MIN_DATA_NUM) ? tx_data_num : MIN_DATA_NUM;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
start_en_r0 <= 1'b0;
start_en_r1 <= 1'b0;
start_en_r2 <= 1'b0;
end
else begin
start_en_r0 <= tx_start_en;
start_en_r1 <= start_en_r0;
start_en_r2 <= start_en_r1;
end
end
//寄存数据有效字节
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_data_num <= 16'd0;
total_num <= 16'd0;
udp_num <= 16'd0;
end
else begin
if(posedge_start && (cur_state == IDLE))begin
tx_data_num <= tx_byte_num;
total_num <= tx_byte_num + 16'd28;
udp_num <= tx_byte_num + 16'd8;
end
else;
end
end
//发送触发信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
trig_tx_en <= 1'b0;
else
trig_tx_en <= posedge_start;
end
//三段式状态机第一段
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cur_state <= IDLE;
else
cur_state <= next_state;
end
//三段式状态第二段
always @(*) begin
next_state = IDLE;
case (cur_state)
IDLE:begin
if(state_en)
next_state = CHECK_SUM;
else
next_state = IDLE;
end
CHECK_SUM:begin
if(state_en)
next_state = PREAMBLE;
else
next_state = CHECK_SUM;
end
PREAMBLE:begin
if(state_en)
next_state = ETH_HEAD;
else
next_state = PREAMBLE;
end
ETH_HEAD:begin
if(state_en)
next_state = IP_HEAD;
else
next_state = ETH_HEAD;
end
IP_HEAD:begin
if(state_en)
next_state = TX_DATA;
else
next_state = IP_HEAD;
end
TX_DATA:begin
if(state_en)
next_state = CRC;
else
next_state = TX_DATA;
end
CRC:begin
if(state_en)
next_state = IDLE;
else
next_state = CRC;
end
default: next_state = IDLE;
endcase
end
//三段式状态机第三段
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt <= 5'd0;
check_buffer <= 32'd0;
state_en <= 1'b0;
ip_head[1][31:16] <= 16'd0;
tx_bit_sel <= 2'd0;
crc_en <= 1'b0;
gmii_tx_en <= 1'b0;
gmii_txd <= 8'd0;
tx_req <= 1'b0;
tx_done_t <= 1'b0;
data_cnt <= 16'd0;
real_add_cnt <= 5'd0;
//初始化数组
//前导码+帧起始界定符
preamble[0] <= 8'h55;
preamble[1] <= 8'h55;
preamble[2] <= 8'h55;
preamble[3] <= 8'h55;
preamble[4] <= 8'h55;
preamble[5] <= 8'h55;
preamble[6] <= 8'h55;
preamble[7] <= 8'hd5;
//目的MAC地址
eth_head[0] <= DES_MAC[47:40];
eth_head[1] <= DES_MAC[39:32];
eth_head[2] <= DES_MAC[31:24];
eth_head[3] <= DES_MAC[23:16];
eth_head[4] <= DES_MAC[15:8];
eth_head[5] <= DES_MAC[7:0];
//源MAC地址
eth_head[6] <= BOARD_MAC[47:40];
eth_head[7] <= BOARD_MAC[39:32];
eth_head[8] <= BOARD_MAC[31:24];
eth_head[9] <= BOARD_MAC[23:16];
eth_head[10] <= BOARD_MAC[15:8];
eth_head[11] <= BOARD_MAC[7:0];
//以太网类型
eth_head[12] <= ETH_TYPE[15:8];
eth_head[13] <= ETH_TYPE[7:0];
end
else begin
crc_en <= 1'b0;
gmii_tx_en <= 1'b0;
tx_done_t <= 1'b0;
state_en <= 1'b0;
case (next_state)
IDLE:begin
if(trig_tx_en)begin
state_en <= 1'b1;
//版本号:4 首部长度:5
ip_head[0] <= {8'h45,8'h00,total_num};
//16位标识,每次发送累加一
ip_head[1][31:16] <= ip_head[1][31:16] + 1'b1;
//
ip_head[1][15:0] <= 16'h4000;
//表示udp协议
ip_head[2] <= {8'h40,8'd17,16'h0};
//源ip地址
ip_head[3] <= BOARD_IP;
//目的ip地址
if(des_ip != 32'd0)
ip_head[4] <= des_ip;
else
ip_head[4] <= DES_IP;
//udp端口号
ip_head[5] <= {16'd1234,16'd1234};
//udp校验长度与udp校验和
ip_head[6] <= {udp_num,16'h0000};
if(des_mac != 48'd0)begin
eth_head[0] <= des_mac[47:40];
eth_head[1] <= des_mac[39:32];
eth_head[2] <= des_mac[31:24];
eth_head[3] <= des_mac[23:16];
eth_head[4] <= des_mac[15:8];
eth_head[5] <= des_mac[7:0];
end
else;
end
else;
end
CHECK_SUM:begin
cnt <= cnt + 1'b1;
if(cnt == 5'd0)
check_buffer <= ip_head[0][31:16] + ip_head[0][15:0] +
ip_head[1][31:16] + ip_head[1][15:0] +
ip_head[2][31:16] + ip_head[2][15:0] +
ip_head[3][31:16] + ip_head[3][15:0] +
ip_head[4][31:16] + ip_head[4][15:0];
else if(cnt == 5'd1)
check_buffer <= check_buffer[31:16] + check_buffer[15:0];
else if(cnt == 5'd2)
check_buffer <= check_buffer[31:16] + check_buffer[15:0];
else if(cnt == 5'd3)begin
cnt <= 5'd0;
ip_head[2][15:0] <= ~check_buffer[15:0];
state_en <= 1'b1;
end
else;
end
PREAMBLE:begin
gmii_tx_en <= 1'b1;
gmii_txd <= preamble[cnt];
if(cnt == 5'd7)begin
cnt <= 5'd0;
state_en <= 1'b1;
end
else
cnt <= cnt + 1'b1;
end
ETH_HEAD:begin
gmii_tx_en <= 1'b1;
gmii_txd <= eth_head[cnt];
crc_en <= 1'b1;
if(cnt == 5'd13)begin
cnt <= 5'd0;
state_en <= 1'b1;
end
else
cnt <= cnt + 1'b1;
end
IP_HEAD:begin
tx_bit_sel <= tx_bit_sel + 1'b1;
gmii_tx_en <= 1'b1;
crc_en <= 1'b1;
if(tx_bit_sel == 2'd0)
gmii_txd <= ip_head[cnt][31:24];
else if(tx_bit_sel == 2'd1)
gmii_txd <= ip_head[cnt][23:16];
else if(tx_bit_sel == 2'd2)begin
gmii_txd <= ip_head[cnt][15:8];
if(cnt == 5'd6)
tx_req <= 1'b1;//提前读请求数据
else;
end
else if(tx_bit_sel == 2'd3)begin
gmii_txd <= ip_head[cnt][7:0];
if(cnt == 5'd6)begin
cnt <= 5'd0;
state_en <= 1'b1;
end
else
cnt <= cnt + 1'b1;
end
end
TX_DATA:begin
crc_en <= 1'b1;
gmii_tx_en <= 1'b1;
tx_bit_sel <= tx_bit_sel + 1'b1;
gmii_txd <= tx_data;
if(data_cnt < tx_data_num - 1'b1)
data_cnt <= data_cnt + 1'b1;
else if(data_cnt == tx_data_num - 1'b1)
if(data_cnt + real_add_cnt < real_tx_data_num - 1'b1)
real_add_cnt <= real_add_cnt + 1'b1;//发送有效数据字节少于18位则在后面补充最后一次发送的有效数据
else begin
state_en <= 1'b1;
real_add_cnt <= 16'd0;
data_cnt <= 16'd0;
tx_bit_sel <= 2'd0;
end
else;
if(data_cnt == tx_data_num - 16'd2)
tx_req <= 1'b0;
else;
end
CRC:begin
gmii_tx_en <= 1'b1;
tx_bit_sel <= tx_bit_sel + 1'b1;
tx_req <= 1'b0;
if(tx_bit_sel == 3'd0)
gmii_txd <= {~crc_next[0], ~crc_next[1], ~crc_next[2],~crc_next[3],
~crc_next[4], ~crc_next[5], ~crc_next[6],~crc_next[7]};
else if(tx_bit_sel == 3'd1)
gmii_txd <= {~crc_data[16], ~crc_data[17], ~crc_data[18],~crc_data[19],
~crc_data[20], ~crc_data[21], ~crc_data[22],~crc_data[23]};
else if(tx_bit_sel == 3'd2) begin
gmii_txd <= {~crc_data[8], ~crc_data[9], ~crc_data[10],~crc_data[11],
~crc_data[12], ~crc_data[13], ~crc_data[14],~crc_data[15]};
end
else if(tx_bit_sel == 3'd3) begin
gmii_txd <= {~crc_data[0], ~crc_data[1], ~crc_data[2],~crc_data[3],
~crc_data[4], ~crc_data[5], ~crc_data[6],~crc_data[7]};
tx_done_t <= 1'b1;
state_en <= 1'b1;
end
else ;
end
default:;
endcase
end
end
//发送完成信号与CRC清零信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_done <= 1'b0;
crc_clr <= 1'b0;
end
else begin
tx_done <= tx_done_t;
crc_clr <= tx_done_t;
end
end
endmodule
仿真结果:
检测到发送开始信号上升沿,开始发送前导码和帧起始界定符。
数据发送部分。
接收部分:
`timescale 1ns / 1ps
module udp_rx(
input clk , //系统时钟
input rst_n , //系统复位
//input
input gmii_rx_dv , //接收数据有效
input [7:0] gmii_rxd , //接收数据
output reg rec_pkt_done, //以太网接收单包数据完成
output reg rec_en , //接收使能信号
output reg [7:0] rec_data ,
output reg [15:0] rec_byte_num //以太网接收有效字节数
);
parameter BOARD_MAC = 48'h00_11_22_33_44_55 ; //开发板MAC地址 00-11-22-33-44-55
parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd10} ; //开发板IP地址 192.168.1.10
localparam IDLE = 7'b000_0001 ; //空闲状态
localparam PREAMBLE = 7'b000_0010 ; //前导码+帧起始界定符
localparam ETH_HEAD = 7'b000_0100 ; //接收以太网帧头
localparam IP_HEAD = 7'b000_1000 ; //接收ip首部
localparam UDP_HEAD = 7'b001_0000 ; //接收udp首部
localparam RX_DATA = 7'b010_0000 ; //接收数据
localparam RX_DONE = 7'b100_0000 ; //接收结束
localparam ETH_TYPE = 16'h0800 ; //以太网协议类型 IP协议
localparam UDP_TYPE = 8'd17 ; //UDP协议
reg [6:0] cur_state ; //现态
reg [6:0] next_state ; //次态
reg [4:0] cnt ; //接收数据计数器
reg state_en ; //状态跳转使能
reg error_flag ; //错误指示信号
reg [47:0] des_mac ; //目的MAC地址
reg [31:0] des_ip ; //目的ip地址
reg [15:0] eth_type ; //以太网类型
reg [5:0] ip_head_byte_num ; //ip首部字节数
reg [15:0] udp_byte_num ; //UDP长度
reg [15:0] data_byte_num ; //数据长度
reg [15:0] data_cnt ; //ip首部发送计数
//三段式状态机第一段
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cur_state <= IDLE;
else
cur_state <= next_state;
end
//三段式状态机第二段
always @(*) begin
next_state = IDLE;
case (cur_state)
IDLE:begin
if(state_en)
next_state = PREAMBLE;
else
next_state = IDLE;
end
PREAMBLE:begin
if(state_en)
next_state = ETH_HEAD;
else if(error_flag)
next_state = RX_DONE;
else
next_state = PREAMBLE;
end
ETH_HEAD:begin
if(state_en)
next_state = IP_HEAD;
else if(error_flag)
next_state = RX_DONE;
else
next_state = ETH_HEAD;
end
IP_HEAD:begin
if(state_en)
next_state = UDP_HEAD;
else if(error_flag)
next_state = RX_DONE;
else
next_state = IP_HEAD;
end
UDP_HEAD:begin
if(state_en)
next_state = RX_DATA;
else
next_state = UDP_HEAD;
end
RX_DATA:begin
if(state_en)
next_state = RX_DONE;
else
next_state = RX_DATA;
end
RX_DONE:begin
if(state_en)
next_state = IDLE;
else
next_state = RX_DONE;
end
default: next_state = IDLE;
endcase
end
//三段式状态机第三段
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
state_en <= 1'b0;
error_flag <= 1'b0;
cnt <= 5'd0;
des_mac <= 48'd0;
des_ip <= 32'd0;
eth_type <= 16'd0;
ip_head_byte_num <= 6'd0;
udp_byte_num <= 16'd0;
data_byte_num <= 16'd0;
data_cnt <= 16'd0;
rec_data <= 32'd0;
rec_pkt_done <= 1'b0;
rec_en <= 1'b0;
rec_byte_num <= 16'd0;
end
else begin
state_en <= 1'b0;
rec_pkt_done <= 1'b0;
error_flag <= 1'b0;
case (next_state)
IDLE:begin
if(gmii_rx_dv && (gmii_rxd == 8'h55))
state_en <= 1'b1;
else;
end
PREAMBLE:begin
if(gmii_rx_dv)begin
cnt <= cnt + 1'b1;
if((cnt < 5'd6) && (gmii_rxd != 8'h55))
error_flag <= 1'b1;
else if(cnt == 5'd6)begin
cnt <= 5'd0;
if(gmii_rxd != 8'hd5)
error_flag <= 1'b1;
else
state_en <= 1'b1;
end
else;
end
end
ETH_HEAD:begin
if(gmii_rx_dv)begin
cnt <= cnt + 1'b1;
if(cnt < 5'd6)
des_mac <= {des_mac[39:0],gmii_rxd};
else if(cnt == 5'd12)
eth_type[15:8] <= gmii_rxd;
else if(cnt == 5'd13)begin
eth_type[7:0] <= gmii_rxd;
cnt <= 5'd0;
//判断目的ip地址与开发板的ip地址是否相同或者为广播地址
if(((des_mac == BOARD_MAC) || (des_mac == 48'hff_ff_ff_ff_ff_ff))
&& (eth_type[15:8] == ETH_TYPE[15:8]) && (gmii_rxd == ETH_TYPE[7:0]))
state_en <= 1'b1;
else
error_flag <= 1'b1;
end
else;
end
else;
end
IP_HEAD:begin
if(gmii_rx_dv)begin
cnt <= cnt + 1'b1;
if(cnt == 5'd0)
ip_head_byte_num <= {gmii_rxd[3:0],2'b00};
else if(cnt == 5'd9)
if(gmii_rxd != UDP_TYPE)begin
error_flag <= 1'b1;
cnt <= 5'd0;
end
else;
else if((cnt >= 5'd16) && (cnt <= 5'd18))
des_ip <= {des_ip[23:0],gmii_rxd};
else if(cnt == 5'd19)begin
des_ip <= {des_ip[23:0],gmii_rxd};
if((des_ip[23:0] == BOARD_IP[31:8]) && (gmii_rxd == BOARD_IP[7:0]))begin
cnt <= 5'd0;
state_en <= 1'b1;
end
else begin
cnt <= 5'd0;
error_flag <= 1'b1;
end
end
else;
end
else;
end
UDP_HEAD:begin
if(gmii_rx_dv)begin
cnt <= cnt + 1'b1;
if(cnt == 5'd4)
udp_byte_num[15:8] <= gmii_rxd;
else if(cnt == 5'd5)
udp_byte_num[7:0] <= gmii_rxd;//解析UDP字节长度
else if(cnt == 5'd7)begin
data_byte_num <= udp_byte_num - 16'd8;//有效数据字节长度,UDP首部长度为8所以减去8
state_en <= 1'b1;
cnt <= 5'd0;
end
else;
end
else;
end
RX_DATA:begin
if(gmii_rx_dv)begin
data_cnt <= data_cnt + 1'b1;
rec_data <= gmii_rxd;
rec_en <= 1'b1;
if(data_cnt == data_byte_num - 16'd1)begin//有效字节接收完成
state_en <= 1'b1;
rec_pkt_done <= 1'b1;
data_cnt <= 16'd0;
rec_byte_num <= data_byte_num;
end
else;
end
else;
end
RX_DONE:begin
rec_en <= 1'b0;
if((gmii_rx_dv == 1'b0) && (state_en == 1'b0))
state_en <= 1'b1;
else;
end
default:;
endcase
end
end
endmodule
仿真结果:
上板测试:
基于FPGA的UDP实现(包含源工程文件)_fpga udp-CSDN博客
原文地址:https://blog.csdn.net/qq_69315815/article/details/143082810
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!