一,引言
前文链接:FPGA - 以太网UDP通信(一)
FPGA - 以太网UDP通信(二)
在以上文章中介绍了以太网简介,以太网UDP通信硬件结构,以及PHY芯片RGMII接口-GMII接口转换逻辑,以及数据链路层(MAC层)接受发送逻辑。接下来介绍UDP通信IP层接受发送逻辑。
二,以太网UDP通信结构框图
在 FPGA - 以太网UDP通信(二)中画出了以太网UDP通信简易结构框图,在mac_layer中使用双fifo来处理跨时钟域处理,并且在mac层的MAC帧格式中:(类型/长度 2byte 小于1536表示长度,大于1536表示类型 arp:16'h0806 , ip: 16'h0800 )所以在设计中预留arp接口。
优化后的结构框图如下:
三,IP层数据帧
IP数据报格式如下图所示,IP 数据报文由首部(称为报头)和数据两部分组成。首部的前一部分是固定长度,共 20 字节(如图所示前五行为IP首部),是所有 IP 数据报必须具有的。在首部的固定部分的后面是一些可选字段,其长度是可变的。
IP 协议首部详解
---------------------------------------------------IP首部 20byte---------------------------------------------------
版本 + 首部长度 1byte
- 版本:占4位,指的是IP协议的版本,通信双方的版本必须一致,当前主流版本是4,即IPv4,也有IPv6
- 首部长度:占4位,最大数值为15,表示的是IP首部的长度,单位是“32位字”(4个字节),也就是IP首部最大长度为60字节
版本ipv4 : 4'h4 ,首部长度 : 4'h5
服务类型 1byte
一般为8'h0
总长度 2byte
(ip首部长度 + ip数据包长度)占16位,最大数值为65535,表示的是IP数据报总长度(IP首部+IP数据) (在前边介绍数据链路层的时候,也提到过一个长度。对于数据链路层的长度,称之为MTU,一般为1500字节。而IP数据报的最大长度有65535个字节,比MTU要大。如果真正传输的时候,如果出现这种情况,数据链路层会对IP数据报进行分片,也就是将一个较长的IP数据报拆分成多个数据帧来进行传输)
标识 2byte
复位给0,发完一包数据自加1
标记 + 分段偏移 2byte
- 标记:3bit。最高位保留为0;中间位是否开启分段,0不开启,1开启;最低位表示是否存在下一个分段,0表示为最后一个分段,1表示还存在下一个分段。一般默认为3’b010
分段偏移:表示第0段,第1段..... - 片偏移:前边有提到,如果IP数据报的长度过长,会进行IP报文的分片,把一个IP报文拆分成多个数据帧进行数据链路层的传输。因此,如果拆分的话,就需要使用片偏移来记录当前的数据帧,保存的第几个偏移的IP数据
生存时间(TTL) 1byte
表明IP数据报文在网络中的寿命,每经过一个设备(不管是路由器还是计算机),TTL减一,当TTL=0时,网络设备必须丢弃该报文(它解决的就是,当网络报文找不到终点的时候,避免网络报文在网络中无限的传输,以消耗带宽)
win系统默认为8‘h80
协议 1byte
表明IP数据所携带的具体数据是什么协议的(如TCP、UDP等)
udp : 8'd17 ,tcp : 8'd6 , icmp : 8'd1
首部校验和 2byte
校验IP首部是否有出错(接收方接收到IP首部之后也会进行校验,如果有错,则直接丢弃)
源ip地址 4byte
发送IP数据报的网络设备的IP地址
目的ip地址 4byte
IP数据报要到达的目的网络设备的IP地址
---------------------------------------------------------------------------------------------------------------------------
IP 首部校验和算法
首部检验和字段是根据IP首部计算的检验和码,不对首部后面的数据进行计算 。其计算方法为:
1. 将校验和字段置为0, 然后将IP包头按16比特分成多个单元,如包头长度不是16比特的倍数,则用 0 比特填充到 16 比特的倍数;
2. 对各个单元采用反码加法运算(即高位溢出位会加到低位 通常的补码运算是直接丢掉溢出的高位)将得到的和的反码填入校验和字段;
四,IP层代码设计
首先,在上面优化后的结构框图中可以看到,在mac_receiv输出的数据经过校验,数据是输入到arp还是ip层,因此要设计一个mac_to_arp_ip模块。
其次,ip层的接受和发送逻辑和mac层的接受发送逻辑类似,并且在mac层已经完成了跨时钟域转换,所以只需要在ip_receive中要完成数据解包校验,在ip_send中要完成数据组包,就完成了整个ip_layer的设计。
mac_to_arp_ip模块
`timescale 1ns / 1ps
module mac_to_arp_ip(
input clk,
input reset,
/*-------mac_receive模块交互的信号-------------*/
input mac_rx_data_vld ,
input mac_rx_data_last ,
input [7:0] mac_rx_data ,
input [15:0] mac_rx_frame_type,
/*-------ip_receive模块交互的信号----------------*/
output reg ip_rx_data_vld ,
output reg ip_rx_data_last ,
output reg [7:0] ip_rx_data ,
/*-------arp相关的的信号--------------------------*/
output reg arp_rx_data_vld ,
output reg arp_rx_data_last ,
output reg [7:0] arp_rx_data
);
localparam ARP_TYPE = 16'h0806;
localparam IP_TYPE = 16'h0800;
always @(posedge clk) begin
if (mac_rx_frame_type == IP_TYPE) begin
ip_rx_data_vld <= mac_rx_data_vld;
ip_rx_data_last <= mac_rx_data_last;
ip_rx_data <= mac_rx_data;
end
else begin
ip_rx_data_vld <= 0;
ip_rx_data_last <= 0;
ip_rx_data <= 0;
end
end
always @(posedge clk) begin
if (mac_rx_frame_type == ARP_TYPE) begin
arp_rx_data_vld <= mac_rx_data_vld;
arp_rx_data_last <= mac_rx_data_last;
arp_rx_data <= mac_rx_data;
end
else begin
arp_rx_data_vld <= 0;
arp_rx_data_last <= 0;
arp_rx_data <= 0;
end
end
endmodule
ip_receive模块
`timescale 1ns / 1ps
module ip_receive #(
parameter LOCAL_IP_ADDR = 32'h0
)(
input clk,
input reset,
/*-------mac_to_arp_ip模块交互的信号------------ */
input ip_rx_data_vld ,
input ip_rx_data_last ,
input [7:0] ip_rx_data ,
/*-------udp_receive模块交互的信号------------ */
output reg udp_rx_data_vld ,
output reg udp_rx_data_last ,
output reg [7:0] udp_rx_data ,
output reg [15:0] udp_rx_length ,
/*-------icmp的信号----------------------------- */
output reg icmp_rx_data_vld ,
output reg icmp_rx_data_last ,
output reg [7:0] icmp_rx_data ,
output reg [15:0] icmp_rx_length
);
localparam UDP_TYPE = 8'd17;
localparam ICMP_TYPE = 8'd1;
reg [10:0] rx_cnt;
reg [31:0] rx_target_ip;
reg [7:0] ip_protocol;
reg [15:0] total_length;
/*------------------------------------------*\
cnt
\*------------------------------------------*/
always @(posedge clk) begin
if (reset)
rx_cnt <= 0;
else if (ip_rx_data_vld)
rx_cnt <= rx_cnt + 1;
else
rx_cnt <= 0;
end
/*------------------------------------------*\
获取总长度、协议类型、目的IP地址
\*------------------------------------------*/
always @(posedge clk) begin
if (rx_cnt == 2 || rx_cnt == 3)
total_length <= {total_length[7:0],ip_rx_data};
else
total_length <= total_length;
end
always @(posedge clk) begin
if (rx_cnt == 9)
ip_protocol <= ip_rx_data;
else
ip_protocol <= ip_protocol;
end
always @(posedge clk) begin
if (rx_cnt > 15 && rx_cnt < 20)
rx_target_ip <= {rx_target_ip[23:0],ip_rx_data};
else
rx_target_ip <= rx_target_ip;
end
/*------------------------------------------*\
输出UDP
\*------------------------------------------*/
always @(posedge clk) begin
if (ip_protocol == UDP_TYPE && rx_target_ip == LOCAL_IP_ADDR && rx_cnt >= 20) begin
udp_rx_data_vld <= ip_rx_data_vld;
udp_rx_data_last <= ip_rx_data_last;
udp_rx_data <= ip_rx_data;
udp_rx_length <= total_length - 20;
end
else begin
udp_rx_data_vld <= 0;
udp_rx_data_last <= 0;
udp_rx_data <= 0;
udp_rx_length <= 0;
end
end
/*------------------------------------------*\
输出ICMP
\*------------------------------------------*/
always @(posedge clk) begin
if (ip_protocol == ICMP_TYPE && rx_target_ip == LOCAL_IP_ADDR && rx_cnt >= 20) begin
icmp_rx_data_vld <= ip_rx_data_vld;
icmp_rx_data_last <= ip_rx_data_last;
icmp_rx_data <= ip_rx_data;
icmp_rx_length <= total_length - 20;
end
else begin
icmp_rx_data_vld <= 0;
icmp_rx_data_last <= 0;
icmp_rx_data <= 0;
icmp_rx_length <= 0;
end
end
endmodule
ip_send模块
`timescale 1ns / 1ps
module ip_send #(
parameter LOCAL_IP_ADDR = 32'h0,
parameter TARGET_IP_ADDR = 32'h0
)(
input clk,
input reset,
/*-------udp_send模块交互的信号------------------*/
input udp_tx_data_vld ,
input udp_tx_data_last ,
input [7:0] udp_tx_data ,
input [15:0] udp_tx_length ,
/*-------mac_send模块交互的信号------------------*/
output reg ip_tx_data_vld ,
output reg ip_tx_data_last ,
output reg [15:0] ip_tx_length ,
output reg [7:0 ] ip_tx_data
);
reg [10:0] tx_cnt;
wire [7:0] udp_tx_data_delay; //udp_tx_data打20拍
reg [15:0] package_id; //标识
reg [15:0] ip_head_chack;
reg [31:0] add0;
reg [31:0] add1;
reg [31:0] add2;
reg [31:0] chack_sum;
/*------------------------------------------*\
锁存length
\*------------------------------------------*/
always @(posedge clk) begin
if (udp_tx_data_vld)
ip_tx_length <= udp_tx_length + 20;
else
ip_tx_length <= ip_tx_length;
end
/*------------------------------------------*\
tx_cnt
\*------------------------------------------*/
always @(posedge clk) begin
if (reset)
tx_cnt <= 0;
else if (tx_cnt == ip_tx_length - 1)
tx_cnt <= 0;
else if (udp_tx_data_vld || tx_cnt != 0)
tx_cnt <= tx_cnt + 1;
else
tx_cnt <= tx_cnt;
end
/*------------------------------------------*\
组包:IP头部 + IP有效数据
\*------------------------------------------*/
always @(posedge clk) begin
if (reset)
ip_tx_data <= 0;
else begin
case(tx_cnt)
0 : ip_tx_data <= {4'h4,4'h5}; //版本加首部长度
1 : ip_tx_data <= 0; //服务类型为0
2 : ip_tx_data <= ip_tx_length[15:8]; //总长度
3 : ip_tx_data <= ip_tx_length[7:0] ;
4 : ip_tx_data <= package_id[15:8]; //标识
5 : ip_tx_data <= package_id[7:0];
6 : ip_tx_data <= {3'b010,5'h0}; //标记 + 分段偏移
7 : ip_tx_data <= 8'h0;
8 : ip_tx_data <= 8'h80; //固定值,win系统固定位128 = 8‘h80
9 : ip_tx_data <= 8'd17; //udp
10 : ip_tx_data <= ip_head_chack[15:8]; //ip首部校验
11 : ip_tx_data <= ip_head_chack[7:0];
12 : ip_tx_data <= LOCAL_IP_ADDR[31:24];
13 : ip_tx_data <= LOCAL_IP_ADDR[23:16];
14 : ip_tx_data <= LOCAL_IP_ADDR[15:8];
15 : ip_tx_data <= LOCAL_IP_ADDR[7:0];
16 : ip_tx_data <= TARGET_IP_ADDR[31:24];
17 : ip_tx_data <= TARGET_IP_ADDR[23:16];
18 : ip_tx_data <= TARGET_IP_ADDR[15:8];
19 : ip_tx_data <= TARGET_IP_ADDR[7:0];
default : ip_tx_data <= udp_tx_data_delay;
endcase
end
end
always @(posedge clk) begin
if (tx_cnt == ip_tx_length - 1)
ip_tx_data_last <= 1'b1;
else
ip_tx_data_last <= 1'b0;
end
always @(posedge clk) begin
if (ip_tx_data_last)
ip_tx_data_vld <= 1'b0;
else if (udp_tx_data_vld)
ip_tx_data_vld <= 1'b1;
else
ip_tx_data_vld <= ip_tx_data_vld;
end
/*------------------------------------------*\
标识
\*------------------------------------------*/
always @(posedge clk) begin
if (reset)
package_id <= 0;
else if (ip_tx_data_last)
package_id <= package_id + 1;
else
package_id <= package_id;
end
/*------------------------------------------*\
计算IP首部校验和
\*------------------------------------------*/
always @(posedge clk) begin // 2个clk
add0 <= 16'h4500 + ip_tx_length + package_id;
add1 <= 16'h4000 + {8'h80,8'd17} + LOCAL_IP_ADDR[31:16];
add2 <= LOCAL_IP_ADDR[15:0] + TARGET_IP_ADDR[31:16] + TARGET_IP_ADDR[15:0];
chack_sum <= add0 + add1 + add2;
end
always @(posedge clk) begin
if (reset)
ip_head_chack <= 0;
else if (tx_cnt == 5)
ip_head_chack <= chack_sum[31:16] + chack_sum[15:0];
else if (tx_cnt == 6)
ip_head_chack <= ~ip_head_chack;
else
ip_head_chack <= ip_head_chack;
end
/*------------------------------------------*\
打拍
\*------------------------------------------*/
//注意 : 如果A的值为19,udp_tx_data_delay打拍为20拍
c_shift_ram_0 ip_delay (
.A(19), // input wire [5 : 0] A
.D(udp_tx_data), // input wire [7 : 0] D
.CLK(clk), // input wire CLK
.Q(udp_tx_data_delay) // output wire [7 : 0] Q
);
endmodule
顶层设计
ip层分别实现了接收和发送两部分,将两部分例化封装顶层ip_layer:
`timescale 1ns / 1ps
module ip_layer #(
parameter LOCAL_IP_ADDR = 32'h0,
parameter TARGET_IP_ADDR = 32'h0
)(
input app_tx_clk ,
input app_rx_clk ,
input app_rx_reset ,
input app_tx_reset ,
input ip_rx_data_vld ,
input ip_rx_data_last ,
input [7:0] ip_rx_data ,
output ip_tx_data_vld ,
output ip_tx_data_last ,
output [15:0] ip_tx_length ,
output [7:0 ] ip_tx_data ,
output udp_rx_data_vld ,
output udp_rx_data_last ,
output [7:0] udp_rx_data ,
output [15:0] udp_rx_length ,
input udp_tx_data_vld ,
input udp_tx_data_last ,
input [7:0] udp_tx_data ,
input [15:0] udp_tx_length ,
output icmp_rx_data_vld ,
output icmp_rx_data_last ,
output [7:0] icmp_rx_data ,
output [15:0] icmp_rx_length
);
ip_receive #(
.LOCAL_IP_ADDR(LOCAL_IP_ADDR)
) ip_receive (
.clk (app_rx_clk),
.reset (app_rx_reset),
.ip_rx_data_vld (ip_rx_data_vld),
.ip_rx_data_last (ip_rx_data_last),
.ip_rx_data (ip_rx_data),
.udp_rx_data_vld (udp_rx_data_vld),
.udp_rx_data_last (udp_rx_data_last),
.udp_rx_data (udp_rx_data),
.udp_rx_length (udp_rx_length),
.icmp_rx_data_vld (icmp_rx_data_vld),
.icmp_rx_data_last (icmp_rx_data_last),
.icmp_rx_data (icmp_rx_data),
.icmp_rx_length (icmp_rx_length)
);
ip_send #(
.LOCAL_IP_ADDR(LOCAL_IP_ADDR),
.TARGET_IP_ADDR(TARGET_IP_ADDR)
) ip_send (
.clk (app_tx_clk),
.reset (app_tx_reset),
.udp_tx_data_vld (udp_tx_data_vld),
.udp_tx_data_last (udp_tx_data_last),
.udp_tx_data (udp_tx_data),
.udp_tx_length (udp_tx_length),
.ip_tx_data_vld (ip_tx_data_vld),
.ip_tx_data_last (ip_tx_data_last),
.ip_tx_length (ip_tx_length),
.ip_tx_data (ip_tx_data)
);
endmodule
五,总结
至此,我们完成了IP层的发送与接受。同时,也已经了解了IP协议首部的具体内容,IP数据包就是紧跟在IP协议首部后面的 。 然后, IP的数据部分也还并不是单纯的用户数据, 我们在网络应用时 ,还需要将我们的用户数据 进一步打包到比IP协议更上一 层 的协议中, 再通过 IP 协议发送。
接下来,在下一篇博客中将会实现udp层的接收与发送。