文章目录
- 前言
- 心得体会
- 一、UDP 协议介绍
- 二、UDP 数据报格式
- 三、UDP 数据发送测试
- 四、Verilog实现UDP 数据发送
- 1、IP 头部检验 IPchecksun 的计算
- 2、以太网报文的校验字段 FCS 的计算
- 3、以太网报文发送模块实现
- 五、以太网数据发送测试
- 六、仿真代码
- 七、仿真波形展示
- 八、上板测试
- 九、UDP 发送逻辑调试验证要点
前言
本章将讲解千兆以太网传输层 UDP 协议的相关内容。学习 UDP 层协议的内容,核心也是明确该协议的数据字段格式。在此基础上,理解其“不可靠、无连接”的传输特性。同时,结合前面章节的内容,进一步深化理解用户数据、UDP、IP、MAC 层的层层打包嵌套关系。
提示:以下是本篇文章正文内容,下面案例可供参考
心得体会
(1)UDP发送数据,无论是MAC地址,还是IP地址,或者UDP端口,其源端口都为开发板;
(2)在发送数据时不关心源端口(开发板)的MAC地址,还是IP地址,或者UDP端口,只关心目的地址,也就是说发送数据时可以将源端口MAC地址,IP地址,UDP端口全部置 0;
(3)验证时一定要弄清目的地址的MAC和P地址,
(4)本实验针对千兆网,注意查看电脑是否支持。
一、UDP 协议介绍
UDP 是 User Datagram Protocol 的简称, 中文名是用户数据报协议。它既是 OSI(Open
System Interconnection,开放式系统互联) 参考模型,又是 TCP/IP 参考模型中传输层的一种
无连接的协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768 是 UDP 的正式规范。
UDP 在 IP 报文的协议号是 17(即 0x11)。
UDP 协议全称是用户数据报协议,在网络中它与 TCP 协议一样用于处理数据包,是一种无连接的协议。在 OSI 模型中,UDP 协议处于 IP 协议的上一层。UDP 有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP 用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用 UDP 协议。UDP 协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但是即使是在今天UDP 仍然不失为一项非常实用和可行的网络传输层协议。与所熟知的 TCP(传输控制协议)协议一样,UDP 协议直接位于 IP(网际协议)协议的顶层。根据 OSI(开放系统互连)参考模型,UDP 和 TCP 都属于传输层协议。UDP 协议的主要作用是将网络数据流量压缩成数据包的形式。一个典型的数据包就是一个二进制数据的传输单位。每一个数据包的前 8 个字节用来包含报头信息,剩余字节则用来包含具体的传输数
据。
以太网 UDP 帧的用户数据是打包在 UDP 协议中,而 UDP 协议又是基于 IP 协议之上的,IP 协议又是走 MAC 层发送的,即从包含关系来说:MAC 帧中的数据段为 IP 数据报文,IP 报文中的数据段为 UDP 报文,UDP 报文中的数据段为用户希望传输的数据内容,如“Hello, welcome to FPGA!”。下图为使用 UDP 协议发送“Hello, welcome to FPGA!”数据的层层打包示意图。
其中,和以太网帧、IP 报文具有帧头一样,UDP 数据报也包含了一个 UDP 报头部分,与 UDP 协议相关的一些信息如端口号,数据包长度等会被打包进 UDP 报头中,然后再与需要传输的 UDP 报文数据一起,作为 IP 报文的数据段送往 IP 层发送。
二、UDP 数据报格式
UDP 协议使用端口号为不同的应用保留其各自的数据传输通道。UDP 和 TCP 协议正是采用这一机制实现对同一时刻内多项应用同时发送和接收数据的支持。数据发送一方(可以是客户端或服务器端)将 UDP 数据包通过源端口发送出去,而数据接收一方则通过目标端口接收数据。有的网络应用只能使用预先为其预留或注册的静态端口;而另外一些网络应用则可以使用未被注册的动态端口。因为 UDP 报头使用两个字节存放端口号,所以端口号的有效范围是从 0 到 65535。一般来说,大于 49151 的端口号都代表动态端口。
数据报的长度是指包括报头和数据部分在内的总字节数。因为报头的长度是固定的,所以该域主要被用来计算可变长度的数据部分(又称为数据负载)。数据报的最大长度根据操作环境的不同而各异。从理论上说,包含报头在内的数据报的最大长度为 65535 字节。不过,一些实际应用往往会限制数据报的大小,有时会降低到 8192 字节。UDP 协议使用报头中的校验值来保证数据的安全。校验值首先在数据发送方通过特殊的算法计算得出,在传递到接收方之后,还需要再重新计算。如果某个数据报在传输过程中被第三方篡改或者由于线路噪音等原因受到损坏,发送和接收方的校验计算值将不会相符,由此 UDP 协议可以检测是否出错。这与 TCP 协议是不同的,后者要求必须具有校验值。许多链路层协议都提供错误检查,包括流行的以太网协议,也许你想知道为什么 UDP 也要提供检查和校验。其原因是链路层以下的协议在源端和终端之间的某些通道可能不提供错误检测。虽然 UDP 提供有错误检测,但检测到错误时,UDP 不做错误校正,只是简单地把
损坏的消息段扔掉,或者给应用程序提供警告信息。下包为 UDP 报文的格式。
由于每次需要发送的数据都不相同,而且校验和内容在发送数据段之前就需要计算出来,不像 MAC 层是在所有数据都发送完成之后才发送 CRC 校验值,因此在 UDP 组包时,校验和值的计算是一个不太好处理的地方,即使通过专用的计算单元计算出校验和,在待发送数据输入完成到数据开始通过 UDP 包发送之间,也有一个计算校验和的时间段。所幸,UDP校验和在一些要求不太严格的地方,是可以忽略的。因此,在使用 Verilog 实现时,为了提升效率并节约 FPGA 资源,将校验和字段忽略。这样一来,UDP 数据包的组包就变得非常简单了。例如本机端口号为5000(0x1388),要给端口号为 6102(0x17D6)的目标主机发送“Hello, welcom to FPGA!”,数据为 22 个字节,UDP 首部 8 个字节,合计 30 个字节,则 UDP 数据段可以组包如下表所示:
三、UDP 数据发送测试
在了解以太网帧格式以及 UDP 报文协议格式后,先通过“小兵以太网测试仪”软件封装一个以太网 UDP 帧进行发送,然后通过“Wireshark”抓包软件对电脑网口的以太网报文进行抓取实验,加深对以太网 UDP 帧格式的熟悉。
1、打开小兵以太网测试仪软件。(下载链接)
链接:https://pan.baidu.com/s/1-JEef1o1ZYuUoJUFfRxb1A?pwd=l87s
提取码:l87s
2、点击左侧工具栏“新建流”,进入报文编辑界面,在常用报文中选择 UDP 格式报文。
3、修改以太网、IP 和 UDP 中各字段内容如下所示。
【注意:】修改数据时将以下√去掉,否则不能更改。
根据MAC头部长度14+IP头部长度20+UDP头部长度8+数据23= 65将数据改为65
4、配置好之后点击确定,在小兵以太网发包软件中就会出现刚刚配置的一条报文数据流。
5、打开 Wireshark 软件,如下图所示。
链接:https://pan.baidu.com/s/1_1hWBSG8izgbmpbCOGaCLA?pwd=dip9
提取码:dip9
6、选择需要抓包的网卡,由于我用的是笔记本,连接的是无线网,有线网口暂时没有连接网线,这里先选择抓取无线网口的数据包来做演示。读者根据自己的情况选取一个网口进行抓包测试。然后点击左上角的开始捕获按钮。
7、在小兵以太网发包工具上选择发包的网卡,这里设置成与上一步骤中 Wireshark 抓包的网卡。
8、勾选我们之前创建的一条数据报文流,然后点击发包按钮。发包过程中可随时进行暂停发包操作。
9、在小兵以太网测试仪发包过程中,Wireshark 会有显示抓取到小兵软件发过来的以太网报文。点击选择抓到的一条报文,在 Wireshark 窗口的下面会有显示具体的报文的内容,可以对比前面小兵软件配置的产生的报文内容,报文内容是相同的。
四、Verilog实现UDP 数据发送
首先根据前面协议的讲解,UDP 以太网帧里面包括 3 个校验字段,IP 报文头部检验字段、UDP 头部校验字段和以太网报文的校验字段 FCS。IP 头部检验 IPchecksun 与 IP 头部的数据有关,在 IP 头部数据改变(比如目的 IP 等数据改变)的时候,这个校验和也是需要重新进行计算的。UDP 头部校验是可选字段,该字段可以直接填充全零,表示不进行校验,考虑设计的简单性,后面的设计就直接忽略 UDP 头部检验,直接将该字段填充全零。以太网报文最后字段 FCS 与整个报文的数据都有关系,不管报文哪个数据发生变化,这个校验和 FCS 字段都需要重新进行计算。
1、IP 头部检验 IPchecksun 的计算
参考文章千兆以太网网络层 IP 协议介绍与 IP 校 验和算法实现
2、以太网报文的校验字段 FCS 的计算
参考文章CRC校验原理及实现
3、以太网报文发送模块实现
以太网 UDP 帧的格式在前面已经做了讲解,并且帧内各种校验字段的产生也做了实现,接下来是对整个以太网 UDP 帧的发送进行实现。UDP 帧根据格式包括前导码+帧界定符、以太网头部数据、IP 头部数据、UDP 头部数据、UDP 数据、FCS 数据。根据格式对UDP 帧发送的设计的代码如下:
`timescale 1ns / 1ps
//// Create Date: 2023/09/11 13:44:21
// Module Name: UDP_TX_ip
// Name : 小王在努力...
// Revision:Vivado 2018.3
// Revision 0.01 - File Created
// Additional Comments:
//
//module UDP_TX_ip #(//ip层参数parameter ip_ver = 4'd4, //ipv4ip_hdr_len = 4'd5, //ip首部长度ip_ttl = 8'd64, //生存周期ip_protocol = 8'd17, //UDP协议//mac层eth_type = 16'h0800 //ip协议)
(input clk_125m,input reset_n,//udp层input [15:0]udp_data_length,input [15:0]udp_src,input [15:0]udp_dest,//ip层input [31:0]ip_src,input [31:0]ip_dst,//mac层input [47:0]dst_mac,input [47:0]src_mac,//fifoinput wrclk,input rst,input [7:0]wrdata,input wrreq,output [11:0]wrusedw,output wr_rst_busy,output reg tx_done,//GMII协议output GMII_TX_ER,(*IOB = "TRUE"*)output reg GMII_TX_EN,output GMII_TX_CLK,(*IOB = "TRUE"*)output reg [7:0]GMII_TX_TXD);assign GMII_TX_CLK = clk_125m;assign GMII_TX_ER = 1'b0;//UDP 层参数parameter udp_checksum = 16'h00;wire [15:0]udp_length;assign udp_length = 16'd8 + udp_data_length;//ip层参数parameter ip_serve_type = 8'h00,ip_id = 16'h00,ip_rsv = 1'b0,ip_df = 1'b0,ip_mf = 1'b0,ip_frag_offset = 13'h0000;wire [15:0]ip_total_len;assign ip_total_len = 16'd20 + udp_length;//数据寄存参数reg [15:0]udp_data_length_reg;reg [15:0]udp_src_reg;reg [15:0]udp_dest_reg;reg [31:0]ip_src_reg;reg [31:0]ip_dst_reg;reg [47:0]dst_mac_reg;reg [47:0]src_mac_reg;//fif0参数reg fifo_rdreq;wire [7:0]fifo_rddata;wire [11:0]rdusedw;//tx_go状态机参数reg [1:0]ctrl_state;reg [27:0]delay_cnt;reg tx_go;//ip_check_sum参数wire [15:0]ip_check_sum;//crc32_d8参数wire [31:0]crc_result;reg crc_en;//状态机参数reg [2:0]cnt_preamble; reg [3:0]cnt_mac_header;reg [4:0]cnt_ip_header; reg [2:0]cnt_udp_header;reg [15:0]cnt_udp_message;reg [1:0]cnt_crc_check;reg [6:0]curr_state; //数据寄存用于对其crc_rereg [7:0]GMII_TXD_REG;reg [6:0]curr_state_reg;reg [1:0]cnt_crc_check_reg;reg GMII_TX_EN_REG;//例化iPeth_dcfifo eth_dcfifo (.rst(rst), // input wire rst.wr_clk(wrclk), // input wire wr_clk.rd_clk(GMII_TX_CLK), // input wire rd_clk.din(wrdata), // input wire [7 : 0] din.wr_en(wrreq), // input wire wr_en.rd_en(fifo_rdreq), // input wire rd_en.dout(fifo_rddata), // output wire [7 : 0] dout.full( ), // output wire full.empty( ), // output wire empty.rd_data_count(rdusedw), // output wire [11 : 0] rd_data_count.wr_data_count(wrusedw), // output wire [11 : 0] wr_data