以太网UDP帧发包设计
RTL8211 是一片 10M/100M/1000M 自适应以太网收发器,提供 MII/GMII/RGMII 接口的
MAC 连接。
下图为 RTL8211 与 FPGA 的连接关系。
以太网MAC层(媒体介入控制层)接口介绍
MII 接口
PHY是物理接口收发器,它实现物理层。包括MII/GMII(介质独立接口)子层、PCS(物理编码子层)、PMA(物理介质附加)子层、PMD(物理介质相关)子层、MDI子层。
MAC 侧向 PHY 侧传输数据的时序图如下。发送数据信号 TXD 和发送使能信号需要在时钟上升沿保持稳定。
PHY 侧向 MAC 侧传输数据(也就是 MAC 侧接收 PHY 侧传过来的数据)的时序图如下。 PHY 芯片传出(也就是 MAC 接收)数据信号 RXD 和数据有效信号在时钟上升沿保持稳定。在 MAC 接收数据时,需要根据传过来数据信号的时序特点进行正确的
接收。
GMII 接口
从波形图可以看出,发送数据信号 TXD 和发送使能信号需要在时钟 GTX_CLK 的上升沿保持稳定;同样的 PHY 芯片传出(也就是 MAC 接收)数据信号 RXD 和数据有效信号在时钟RX_CLK 上升沿保持稳定。在 MAC 发送/接收数据时,需要满足这些时序要求才能让 PHY 正确收到数据和正确接收到 PHY 传过来数据。
RGMII 接口
RGMII 接口为了保持 1000Mbps 的传输速率不变, RGMII 接口在时钟的上升沿和下降沿都采样数据。在参考时钟的上升沿发送 GMII 接口中的 TXD[3:0]/RXD[3:0],在参考时钟的下降沿发送 GMII 接口中的 TXD[7:4]/RXD[7:4]。 RGMII 同时也兼容 100Mbps 和 10Mbps 两种速率,此时参考时钟速率分别为 25MHz 和 2.5MHz。 TX_EN 信号线上传送 TX_EN 和 TX_ER 两种信息,在 TX_CLK 的上升沿发送TX_EN,下降沿发送 TX_ER; 同样的, RX_DV 信号线上也传送 RX_DV 和RX_ER 两种信息,在 RX_CLK 的上升沿传输 RX_DV,下降沿传输 RX_ER。
以太网(MAC) 帧协议介绍
以太网技术的正式标准是 IEEE 802.3 标准,它规定了在以太网中传输的数据帧结构,如下图所示。
在物理层上看,一个完整的以太网帧有 7 个字段,事实上,前两个字段并不能算是真正意义上的以太网数据帧,它们是以太网在物理层上发送以太网数据时添加上去的。
为了实现底层数据的正确阐述,物理层使用 7 个字节前同步码(0 和 1 交替的 56 位(55-55-55-55-55-55-55))实现物理层帧输入/输出同步;使用 1 个字节的 SFD(帧首定界符,固定为 10101011)标识帧的开始。
上图中剩下的 5 个字段是真正的以太网数据,其中包含了目的地址和源地址,它们都是 6 字节长度(通常每个网卡都有 1 个 6 个字节 MAC 地址,以在以太网中唯一地标识自己)。网卡接收数据时,通过将目的地址字段和自身的 MAC 地址做比较,判断是否
接收该数据包。通常,将这里的 6 字节目的地址按照下面的格式来书写,如: 00-01-02-03-04-05。这 6 个字节在以太网中是按照从左到右(先发该字段的高字节后发字段的低字节)的顺序发送的,同时对每个字节来说,最先发送的是最低位 bit0,最后是最高位 bit7。
在以太网帧中,目的 MAC 地址可以分为三类:单播地址、多播地址和广播地址。单播地址通常与一个具体网卡的 MAC 地址相对应,它要求第一个字节的 bit0(即最先发出去的位)必须是 0;多播地址则要求第一个字节的 bit0 为 1,这样,在网络中多播地址不会与任何网卡的 MAC 相同,多播数据可以被很多个网卡同时接收;广播地址的所有 48 位全为 1(即 FF-FF-FF-FF-FF-FF),同一局域网中的所有网卡可以接收广播数据包。
UDP 协议介绍
UDP 是 User Datagram Protocol 的简称, 中文名是用户数据报协议,是 OSI(Open SystemInterconnection,开放式系统互联)参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务, IETF RFC 768 是 UDP 的正式规范。
UDP 协议的主要作用是将网络数据流量压缩成数据包的形式。一个典型的数据包就是一个二进制数据的传输单位。每一个数据包的前 8 个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。
IP 数据报格式
IP 是 TCP/IP 协议族中最核心的协议,所有的 TCP、 UDP、 ICMP、 IGMP 数据都以 IP 数据报的格式传输。 IP 仅提供尽力而为的传输服务,如果发生某种错误, IP 会丢失该数据,然后发送 ICMP 消息给信源端。另外, IP 数据报可以不按发送顺序接受。
IP 数据报的格式如下, IP 数据报的长度/类型段的数值为 0x0800,数据和填充段包括IP 头部数据和 IP 数据两个部分。
首部的每一行是一个以 32bit 为单位的数据,最高位在左边,为 0bit,最低位在右边,为 31bit。
首部长度是指首部占 32bit 字(4 字节) 的数目,因为 4 位的最大值为 15,因此首部最长为 60(4*15=60) 字节,也即是说选项部分的最大值为 40 字节,不够 4 的倍数,要用 0 填充,使数据部分的起始地址为 4 的倍数。
UDP 数据报格式
UDP 数据报分为首部和用户数据部分,整个 UDP 数据报作为 IP 数据报的数据部分封装在 IP 数据报中, UDP 报文的以太网帧结构如图所示。
UDP 报文包括 UDP 报文首部数据和 UDP 报文数据(数据负载),其中 UDP 首部有 8 个字节,由 4 个字段构成,每个字段都是两个字节。
例如本机端口号为 5000(0x1388), 要给端口号为 6000(0x1770)的目标主机发送“Hello, welcome to FPGA!”,数据为 23 个字节, UDP 首部 8 个字节,合计 31 个字节,则 UDP 报文组包如下。
以太网 UDP 帧发包接口 FPGA 实现
UDP 以太网帧里面包括 3 个校验字段, IP 报文头部检验字段、UDP 头部校验字段和以太网报文的校验字段 FCS。 IP 头部检验 IPchecksun 与 IP 头部的数据有关,在 IP 头部数据改变(比如目的 IP 等数据改变)的时候,这个校验和也是需要重新进行计算的。 UDP 头部校验是可选字段, 该字段可以直接填充全零,表示不进行校验,考虑设计的简单性,后面的设计就直接忽略 UDP 头部检验,直接将该字段填充全零。以太网报文最后字段 FCS 与整个报文的数据都有关系,不管报文哪个数据发生变化,这个校验和 FCS 字段都需要重新进行计算。
IP 头部检验 IPchecksun 的计算
IP 头部检验 IPchecksun 仅与 IP 头部的数据有关。
发送端发送数据时具体 checksum 计算步骤如下。
- 把校验和字段(check sum) 置为 0;
- 对 IP 头部中的每 16bit 进行二进制求和;
- 如果和的高 16bit 不为 0,则将和的高 16bit 和低 16bit 反复相加,直到和的高 16bit为 0,从而获得一个 16bit 的值;
- 将该 16bit 的值取反, 即为最终计算的 check sum 值, 存入校验和字段。
以上面数据举例对 check sum 进行计算, 我们使用 IP 协议发送一个数据长度为 31 个字节的数据包,发送端 IP 为 192.168.0.2, 接收端 IP 为 192.168.0.3。 根据 check sum 计算的步骤,对需要计算的 check sum 字段置为 0,然后以每 16bit 进行求和, 即:
0x4500 + 0x0033 + 0x0000 + 0x0000 + 0x4011 + 0x0000(check sum 计算时置为 0) +0xc0a8 + 0x0002 + 0xc0a8 + 0x0003 = 0x20699
上面计算的结果 0x20699 的高 16bit 不为 0,将高 16bit 和低 16bit 相加,计算如下。
0x0002 + 0x0699 = 0x069b
上面计算结果的高 16bit 为 0,进行步骤的第 4 步,则 check sum 最终计算的值为
checksum = ~0x069b = 0xf964
module ip_checksum(
input clk ,
input reset_n ,
input cal_en ,
input [3:0] IP_ver ,
input [3:0] IP_hdr_len ,
input [7:0] IP_tos ,
input [15:0] IP_total_len ,
input [15:0] IP_id ,
input IP_rsv ,
input IP_df ,
input IP_mf ,
input [12:0] IP_frag_offset ,
input [7:0] IP_ttl ,
input [7:0] IP_protocol ,
input [31:0] src_ip ,
input [31:0] dst_ip ,
output [15:0] checksum
);reg [31:0]suma;wire [16:0]sumb;wire [15:0]sumc;always@(posedge clk or negedge reset_n)if(!reset_n)suma <= 32'd0;else if(cal_en)suma <= {IP_ver,IP_hdr_len,IP_tos}+IP_total_len+IP_id+{IP_rsv,IP_df,IP_mf,IP_frag_offset}+{IP_ttl,IP_protocol}+src_ip[31:16]+src_ip[15:0]+dst_ip[31:16]+dst_ip[15:0];elsesuma <= suma;assign sumb = suma[31:16]+suma[15:0];assign sumc = sumb[16]+sumb[15:0];assign checksum = ~sumc;
endmodule
该模块仿真测试的 testbench 代码如下。
`timescale 1ns/1ns
`define CLK_PERIOD 8
module ip_checksum_tb();reg clk ;reg reset_n ;reg cal_en ;reg [15:0] IP_total_len ;reg [31:0] src_ip ;reg [31:0] dst_ip ;wire [15:0] checksum ;initial clk = 1;always#(`CLK_PERIOD/2)clk = ~clk;initial beginreset_n = 1'b0;cal_en = 1'b0;IP_total_len = 16'd0;src_ip = 32'hc0a80002;dst_ip = 32'hc0a80003;#(`CLK_PERIOD*20 + 1 );reset_n = 1'b1;#(`CLK_PERIOD*50);cal_en = 1'b1;IP_total_len = 16'h33;src_ip = 32'hc0a80002;dst_ip = 32'hc0a80003;#(`CLK_PERIOD)cal_en = 1'b0;#200;cal_en = 1'b1;IP_total_len = 16'h44;src_ip = 32'hc0a80004;dst_ip = 32'hc0a80005;#(`CLK_PERIOD)cal_en = 1'b0;#200;$stop;
endip_checksum ip_checksum(.clk(clk),.reset_n(reset_n),.cal_en(cal_en),.IP_ver(4'h4),.IP_hdr_len(4'h5),.IP_tos(8'h00),.IP_total_len(IP_total_len),.IP_id(16'h0000),.IP_rsv(1'b0),.IP_df(1'b0),.IP_mf(1'b0),.IP_frag_offset(13'h0000),.IP_ttl(8'h40),.IP_protocol(8'h11),.src_ip(src_ip),.dst_ip(dst_ip),.checksum(checksum)
);endmodule
以太网报文的校验字段 FCS 的计算
以太网报文校验字段 FCS 采用的是 CRC32 计算,关于 CRC 计算的 Verilog 代码实现已经做的很成熟,网上也有可直接生成 CRC 计算 Verilog 代码的网站。
以太网报文发送模块实现
UDP 帧根据格式包括前导码+帧界定符、以太网头部数据、 IP 头部数据、 UDP 头部数据、 UDP 数据、 FCS 数据。根据格式对 UDP 帧发送的设计的状态机转移图如下。