本帖最后由 正点原子 于 2021-1-23 15:36 编辑
1)实验平台:正点原子达芬奇FPGA开发板
2)购买链接:https://detail.tmall.com/item.htm?id=624335496505
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_dafenqi.html
4) 正点原子官方B站:https://space.bilibili.com/394620890
5)对正点原子FPGA感兴趣的同学可以加群讨论:905624739 点击加入:
第四十章以太网UDP测试实验
UDP是一种面向无连接的传输层协议,属于TCP/IP协议簇的一种。UDP具有消耗资源少、通信效率高等优点,通常用来传输音频、视频等对实时性要求高的场合。本章我们来学习如何通过达芬奇开发板实现UDP通信的功能。
本章分为以下几个章节:
4040.1简介
40.2实验任务
40.3硬件设计
40.4程序设计
40.5下载验证
40.1简介
UDP概述
UDP(User Datagram Protocol),即用户数据报协议,是一种面向无连接的传输层协议。无连接是指在传输数据时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输(如视频会议等)都会采用UDP协议进行传输,这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
UDP和TCP是传输层中非常重要的两个协议,位于OSI(Open System Interconnection,开放式系统互联)参考模型中的第四层(传输层),是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,位于IP协议层(网络层)之上。OSI将计算机网络体系结构分为七层:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,OSI参考模型如下图所示。
图 40.1.1 OSI参考模型
以太网UDP传输单包数据的格式由图 40.1.2所示。从图中可以看出,以太网的数据包就是对各层协议的逐层封装来实现数据的传输。用户数据打包在UDP协议中,UDP协议又是基于IP协议之上的,IP协议又是走MAC层发送的,即从包含关系来说:MAC帧中的数据段为IP数据报,IP报文中的数据段为UDP报文,UDP报文中的数据段为用户希望传输的数据内容。接下来我们逐个来向大家介绍不同层的数据格式。
图 40.1.2 以太网UDP传输数据包格式
其中以太网的帧格式在“以太网ARP测试实验”中已经向大家作了详细的介绍,如果对以太网帧格式不熟悉的话,可以参考“以太网ARP测试实验”。IP协议(互联网分组交换协议)是TCP/IP协议簇中非常重要的一个协议,下面我们来熟悉下IP协议。
IP协议
IP协议是TCP/IP协议簇中的核心协议,也是TCP/IP协议的载体,IP协议规定了数据传输时的基本单元和格式。从前面介绍的图 40.1.2中可以看出,IP协议位于以太网MAC帧格式的数据段,IP协议内容由IP首部和数据字段组成。所有的TCP、UDP及ICMP数据都以IP数据报格式传输,IP数据包格式如图 40.1.3所示。
图 40.1.3 IP数据报格式
前20个字节和紧跟其后的可选字段是IP数据报的首部,前20个字节是固定的,后面可选字段是可有可无的,首部的每一行以32位(4个字节)为单位。
版本:4位IP版本号(Version),这个值设置为二进制的0100时表示IPv4,设置为0110时表示IPv6,目前使用比较多的IP协议版本号是4。
首部长度:4位首部长度(IHL,Internet Header Length),表示IP首部一共有多少个32位(4个字节)。在没有可选字段时,IP首部长度为20个字节,因此首部长度的值为5。
服务类型:8位服务类型(TOS,Type of service),该字段被划分成两个子字段:3位优先级字段(现在已经基本忽略掉了)和4位TOS字段,最后一位固定为0。服务类型为0时表示一般服务。
总长度:16位IP数据报总长度(Total Length),包括IP首部和IP数据部分,以字节为单位。我们利用IP首部长度和IP数据报总长度,就可以知道IP数据报中数据内容的起始位置和长度。由于该字段长16bit,所以IP数据报最长可达65535字节。尽管理论上可以传输长达65535字节的IP数据报,但实际上还要考虑网络的最大承载能力等因素。
标识字段:16位标识(Identification)字段,用来标识主机发送的每一份数据报。通常每发送一份报文它的值就会加1。
标志字段:3位标志(Flags)字段,第1位为保留位;第2位表示禁止分片(1表示不分片 0:允许分片);第3位标识更多分片(除了数据报的最后一个分片外,其它分片都为1)。
片偏移:13位片偏移(Fragment Offset),在接收方进行数据报重组时用来标识分片的顺序。
生存时间:8位生存时间字段,TTL(Time To Live)域防止丢失的数据包在无休止的传播,一般被设置为64或者128。
协议:8位协议(Protocol)类型,表示此数据报所携带上层数据使用的协议类型,ICMP为1,TCP为6,UDP为17。
首部校验和:16位首部校验和(Header Checksum),该字段只校验数据报的首部,不包含数据部分;校验IP数据报头部是否被破坏、篡改和丢失等。
源IP地址:32位源IP地址(Source Address),即发送端的IP地址,如192.168.1.123。
目的IP地址:32位目的IP地址(Destination Address),即接收端的IP地址,如192.168.1.102。
可选字段:是数据报中的一个可变长度的可选信息,选项字段以32bit为界,不足时插入值为0的填充字节,保证IP首部始终是32bit的整数倍。
以上内容是对IP首部格式的详细阐述,还需要补充的内容是IP首部校验和的计算方法,其计算步骤如下:
将16位检验和字段置为0,然后将IP首部按照16位分成多个单元;
对各个单元采用反码加法运算(即高位溢出位会加到低位,通常的补码运算是直接丢掉溢出的高位);
此时仍然可能出现进位的情况,将得到的和再次分成高16位和低16位进行累加;
最后将得到的和的反码填入校验和字段。
例如,我们使用IP协议发送一个IP数据报总长度为50个字节(有效数据为30个字节)的数据包,发送端IP地址为192.168.1.123,接收端IP地址为192.168.102,则IP首部数据如下:
图 40.1.4 IP首部数据
按照上述提到的 IP 首部校验和的方法计算 IP 首部校验和,即:
0x4500 + 0x0032 + 0x0000 + 0x4000 + 0x4011 + 0x0000(计算时强制置0) + 0xc0a8 + 0x017b + 0xc0a8 + 0x0166 = 0x24974
0x0002 + 0x4974 = 0x4976
0x0000 + 0x4976 = 0x4976(此种情况并未出现进位)
check_sum = ~0x4976(按位取反) = 0xb689
到此为止IP协议内容已经介绍完了,我们从前面介绍的图 40.1.2可以知道,UDP的首部和数据位于IP协议的数据段。既然已经有IP协议了,为什么还需要UDP协议呢?为什么我们选择的是UDP还不是传输更可靠的TCP呢?带着这些疑问我们继续往下看。
UDP协议
首先回答为什么还需要UDP协议?事实上数据是可以直接封装在IP协议里而不使用TCP、UDP或者其它上层协议的。然而在网络传输中同一IP服务器需要提供各种不同的服务,各种不同的服务类型是使用端口号来区分的,例如用于浏览网页服务的80端口,用于FTP(文件传输协议)服务的21端口等。TCP和UDP都使用两个字节的端口号,理论上可以表示的范围为0~65535,足够满足各种不同的服务类型。
然后是为什么不选择传输更可靠的TCP协议,而是UDP协议呢?TCP协议与UDP协议作为传输层最常用的两种传输协议,这两种协议都是使用IP作为网络层协议进行传输。下面是TCP协议与UDP协议的区别:
1、TCP协议面向连接,是流传输协议,通过连接发送数据,而UDP协议传输不需要连接,是数据报协议;
2、TCP为可靠传输协议,而UDP为不可靠传输协议。即TCP协议可以保证数据的完整和有序,而UDP不能保证;
3、UDP由于不需要连接,故传输速度比TCP快,且占用资源比TCP少;
4、应用场合:TCP协议常用在对数据文件完整性较高的一些场景中,如文件传输等。UDP常用于对通讯速度有较高要求或者传输数据较少时,比如对速度要求较高的视频直播和传输数据较少的QQ等。
首先可以肯定的告诉大家,使用FPGA实现TCP协议是完全没有问题的,但是,FPGA发展到现在,却鲜有成功商用的RTL级的TCP协议设计,大部分以太网传输都是基于比较简单的UDP协议。TCP协议设计之初是根据软件灵活性设计的,如果使用硬件逻辑实现,工程量会十分巨大,而且功能和性能无法得到保证,因此,TCP协议设计并不适合使用硬件逻辑实现。UDP协议是一种不可靠传输,发送方只负责数据发送出去,而不管接收方是否正确的接收。在很多场合,是可以接受这种潜在的不可靠性的,例如视频实时传输显示等。
UDP数据格式如图 40.1.5所示:
图 40.1.5 UDP数据格式
UDP首部共8个字节,同IP首部一样,也是一行以32位(4个字节)为单位。
源端口号:16位发送端端口号,用于区分不同服务的端口,端口号的范围从0到65535。
目的端口号:16位接收端端口号。
UDP长度:16位UDP长度,包含UDP首部长度+数据长度,单位是字节(byte)。
UDP校验和:16位UDP校验和。UDP计算校验和的方法和计算IP数据报首部校验和的方法相似,但不同的是IP数据报的校验和只检验IP数据报的首部,而UDP校验和包含三个部分:UDP伪首部,UDP首部和UDP的数据部分。伪首部的数据是从IP数据报头和UDP数据报头获取的,包括源IP地址,目的IP地址,协议类型和UDP长度,其目的是让UDP两次检查数据是否已经正确到达目的地,只是单纯为了做校验用的。在大多数使用场景中接收端并不检测UDP校验和,因此这里不做过多介绍。
以太网的帧格式、IP数据报协议以及UDP协议到这里已经全部介绍完了,关于用户数据、UDP、IP、MAC四个报文的关系如下图所示:
图 40.1.6 以太网包数据格式
用户数据打包在UDP协议中,UDP协议又是基于IP协议之上的,IP协议又是走MAC层发送的,即从包含关系来说:MAC帧中的数据段为IP数据报,IP报文中的数据段为UDP报文,UDP报文中的数据段为用户希望传输的数据内容。现在再回过头看图 40.1.6的内容就非常容易理解了。
40.2实验任务
本节实验任务是上位机通过网口调试助手发送数据给FPGA,FPGA通过以太网接口接收数据并将接收到的数据发送给上位机,完成以太网UDP数据的环回。
40.3硬件设计
千兆以太网接口部分的硬件设计原理及本实验中各端口信号的管脚分配,和“以太网ARP测试实验”完全相同,请参考“以太网ARP读写测试实验”中的硬件设计部分。
40.4程序设计
图 40.4.1是根据本章实验任务画出的系统框图。和“以太网ARP测试实验”相比,将ARP控制模块替换成了以太网控制模块,并增加了一个同步FIFO和UDP顶层模块。本次实验虽然实现的是UDP通信,但保留了ARP顶层模块,这是由于上位机应用程序只知道接收端的目的IP地址和端口号,却不知道接收端的MAC地址,因此这里通过ARP协议来获取接收端的MAC地址,否则需要在发送端手动绑定接收端MAC地址,而手动绑定的方法较为繁琐,因此这里保留了ARP协议。
本次实验同时实现了ARP协议和UDP协议,GMII接收侧的引脚同时连接至ARP顶层模块和UDP顶层模块,这个两个模块会分别根据ARP协议和UDP协议解析数据。而GMII发送侧引脚只能和ARP顶层模块和UDP顶层模块的其中一个连接,因此以太网控制模块会根据当前接收到的协议类型,选择切换GMII发送侧引脚和ARP顶层模块或者UDP顶层模块连接。除此之外,以太网控制模块根据输入的ARP接收的类型,控制ARP顶层模块返回ARP应答信号。以太网单次会接收到大量数据,因此本次实验需要一个FIFO模块用来缓存数据,由于本次实验所使用的GMII接收时钟和GMII发送时钟实际上为同一个时钟,因此这里使用的是同步FIFO。
图 40.4.1 以太网UDP测试系统框图
系统时钟经过PLL时钟模块后,输出200Mhz的时钟,用于IDELAYCTRL原语的参考时钟;GMII TO RGMII模块负责将双沿(DDR)数据和单沿(SDR)数据之间的转换;ARP顶层模块解析ARP请求命令,并返回开发板的MAC地址;以太网控制模块根据输入的ARP接收完成信号类型,控制ARP顶层模块返回ARP应答信号,并根据当前接收到的协议类型,选择切换ARP顶层模块和UDP顶层模块的GMII发送侧引脚;UDP顶层模块实现了以太网UDP数据包的接收、发送以及CRC校验的功能。同步FIFO模块是由Vivado软件自带的FIFO IP核生成的,FIFO的大小为2048个32bit,为了能够满足单包数据量较大的情况(尽管通常情况下,以太网帧有效数据不超过1500个字节),所以FIFO的深度最好设置的大一点,这里把深度设置为2048,宽度为32位。
各模块端口及信号连接如下图所示:
图 40.4.2 顶层模块原理图
由上图可知,FPGA顶层模块例化了以下六个模块,PLL时钟模块(clk_wiz)、GMII TO RGMII模块(gmii_to_rgmii)、ARP顶层模块(arp)、UDP顶层模块(udp)、同步FIFO模块(sync_fifo_2048x32b)和以太网控制模块(eth_ctrl),实现了各模块之间的数据交互。
其中GMII TO RGMII(gmii_to_rgmii)模块和ARP顶层模块(arp)在“以太网ARP测试实验”中已经向大家作了详细的介绍,如果大家对这部分内容不熟悉的话,可以参考“以太网ARP测试实验”。
本章我们重点介绍UDP顶层模块(udp)。
UDP顶层模块实现了整个以太网帧格式与UDP协议的功能,其模块端口及信号连接如下图所示:
图 40.4.3 UDP模块原理图
由上图可知,UDP顶层模块例化了UDP接收模块(udp_rx)、UDP发送模块(udp_tx)和CRC校验模块(crc32_d8)。
UDP接收模块(udp_rx):UDP接收模块较为简单,因为我们不需要对数据做IP首部校验也不需要做CRC循环冗余校验,只需要判断目的MAC地址与开发板MAC地址、目的IP地址与开发板IP地址是否一致即可。接收模块的解析顺序是:前导码+帧起始界定符→以太网帧头→IP首部→UDP首部→UDP数据(有效数据)→接收结束。IP数据报一般以32bit为单位,为了和IP数据报格式保持一致,所以要把8位数据转成32位数据,因此接收模块实际上是完成了8位数据转32位数据的功能。
UDP发送模块(udp_tx):UDP发送模块和接收模块比较类似,但是多了IP首部校验和和CRC循环冗余校验的计算。CRC的校验并不是在发送模块完成,而是在CRC校验模块(crc32_d4)里完成的。发送模块的发送顺序是前导码+帧起始界定符→以太网帧头→IP首部→UDP首部→UDP数据(有效数据)→CRC校验。输入的有效数据为32位数据,GMII接口为8位数据接口,因此发送模块实际上完成的是32位数据转8位数据的功能。
CRC校验模块(crc32_d4):CRC校验模块是对UDP发送模块的数据(不包括前导码和帧起始界定符)做校验,把校验结果值拼在以太网帧格式的FCS字段,如果CRC校验值计算错误或者没有的话,那么电脑网卡会直接丢弃该帧导致收不到数据(有些网卡是可以设置不做校验的)。CRC32校验在FPGA实现的原理是LFSR(Linear Feedback Shift Register,线性反馈移位寄存器),其思想是各个寄存器储存着上一次CRC32运算的结果,寄存器的输出即为CRC32的值。
其中CRC校验模块和ARP模块例化的校验模块完全相同,这里我们重点介绍UDP接收模块和UDP发送模块。
UDP接收模块按照UDP的数据格式解析数据,并实现将8位用户数据转成32位数据的功能。由UDP的数据格式可知,解析UDP数据很适合使用状态机来实现,下图为UDP接收模块的状态跳转图。
图 40.4.4 UDP接收模块的状态跳转图
接收模块使用三段式状态机来解析以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。这里需要注意的一点是,在中间状态如前导码错误、MAC地址错误以及IP地址错误时跳转到st_rx_end状态而不是跳转到st_idle状态。因为中间状态在解析到数据错误时,单包数据的接收还没有结束,如果此时跳转到st_idle状态会误把有效数据当成前导码来解析,所以状态跳转到st_rx_end。而eth_rxdv信号为0时,单包数据才算接收结束,所以st_rx_end跳转到st_idle的条件是eth_rxdv=0,准备接收下一包数据。因为代码较长,只粘贴了第三段状态机的接收数据状态和接收结束状态源代码,代码如下:
- 241 st_rx_data : begin
- 242 //接收数据,转换成32bit
- 243 if(gmii_rx_dv) begin
- 244 data_cnt <= data_cnt + 16'd1;
- 245 rec_en_cnt <= rec_en_cnt + 2'd1;
- 246 if(data_cnt == data_byte_num - 16'd1) begin
- 247 skip_en <= 1'b1; //有效数据接收完成
- 248 data_cnt <= 16'd0;
- 249 rec_en_cnt <= 2'd0;
- 250 rec_pkt_done <= 1'b1;
- 251 rec_en <= 1'b1;
- 252 rec_byte_num <= data_byte_num;
- 253 end
- 254 //先收到的数据放在了rec_data的高位,所以当数据不是4的倍数时,
- 255 //低位数据为无效数据,可根据有效字节数来判断(rec_byte_num)
- 256 if(rec_en_cnt == 2'd0)
- 257 rec_data[31:24] <= gmii_rxd;
- 258 else if(rec_en_cnt == 2'd1)
- 259 rec_data[23:16] <= gmii_rxd;
- 260 else if(rec_en_cnt == 2'd2)
- 261 rec_data[15:8] <= gmii_rxd;
- 262 else if(rec_en_cnt==2'd3) begin
- 263 rec_en <= 1'b1;
- 264 rec_data[7:0] <= gmii_rxd;
- 265 end
- 266 end
- 267 end
- 268 st_rx_end : begin //单包数据接收完成
- 269 if(gmii_rx_dv == 1'b0 && skip_en == 1'b0)
- 270 skip_en <= 1'b1;
- 271 end
复制代码
程序中的st_rx_data状态表示接收UDP的有效数据,在接收完有效数据后,拉高rec_pkt_done(单包有效数据接收完成)信号,如程序中第250行代码所示。
图 40.4.5为接收过程中ILA采集的波形图,上位机通过网口调试助手发送http://www.openedv.com(十六进制为:68 74 74 70 3A 2F 2F 77 77 77 2E 6F 70 65 6E 65 64 76 2E 63 6F 6D),图中gmii_rx_dv和gmii_rxd为GMII接口的接收有效信号和数据,skip_en为状态机的跳转信号。每次单包数据接收完成都会产生rec_pkt_done信号,rec_en和rec_data为收到的数据有效信号和32位数据。
图 40.4.5 UDP接收采集的ILA波形图
UDP发送模块按照UDP的数据格式发送数据,并将32位用户数据转成8位数据的功能,也就是接收模块的逆过程。同样也非常适合使用状态机来完成发送数据的功能,状态跳转图如下图所示:
图 40.4.6 UDP发送模块的状态跳转图
发送模块和接收模块有很多相似之处,同样使用三段式状态机来发送以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。
发送模块的代码中定义了数组来存储以太网的帧头、IP首部以及UDP的首部,在复位时初始化数组的值,部分源代码如下。
- 69 reg [7:0] preamble[7:0] ; //前导码
- 70 reg [7:0] eth_head[13:0] ; //以太网首部
- 71 reg [31:0] ip_head[6:0] ; //IP首部 + UDP首部
- 省略部分代码……
- 209 //初始化数组
- 210 //前导码 7个8'h55 + 1个8'hd5
- 211 preamble[0] <= 8'h55;
- 212 preamble[1] <= 8'h55;
- 213 preamble[2] <= 8'h55;
- 214 preamble[3] <= 8'h55;
- 215 preamble[4] <= 8'h55;
- 216 preamble[5] <= 8'h55;
- 217 preamble[6] <= 8'h55;
- 218 preamble[7] <= 8'hd5;
- 219 //目的MAC地址
- 220 eth_head[0] <= DES_MAC[47:40];
- 221 eth_head[1] <= DES_MAC[39:32];
- 222 eth_head[2] <= DES_MAC[31:24];
- 223 eth_head[3] <= DES_MAC[23:16];
- 224 eth_head[4] <= DES_MAC[15:8];
- 225 eth_head[5] <= DES_MAC[7:0];
- 226 //源MAC地址
- 227 eth_head[6] <= BOARD_MAC[47:40];
- 228 eth_head[7] <= BOARD_MAC[39:32];
- 229 eth_head[8] <= BOARD_MAC[31:24];
- 230 eth_head[9] <= BOARD_MAC[23:16];
- 231 eth_head[10] <= BOARD_MAC[15:8];
- 232 eth_head[11] <= BOARD_MAC[7:0];
- 233 //以太网类型
- 234 eth_head[12] <= ETH_TYPE[15:8];
- 235 eth_head[13] <= ETH_TYPE[7:0];
- 236 end
- 以上代码在复位时对数组进行初始化。
- 244 st_idle : begin
- 245 if(trig_tx_en) begin
- 246 skip_en <= 1'b1;
- 247 //版本号:4 首部长度:5(单位:32bit,20byte/4=5)
- 248 ip_head[0] <= {8'h45,8'h00,total_num};
- 249 //16位标识,每次发送累加1
- 250 ip_head[1][31:16] <= ip_head[1][31:16] + 1'b1;
- 251 //bit[15:13]: 010表示不分片
- 252 ip_head[1][15:0] <= 16'h4000;
- 253 //协议:17(udp)
- 254 ip_head[2] <= {8'h40,8'd17,16'h0};
- 255 //源IP地址
- 256 ip_head[3] <= BOARD_IP;
- 257 //目的IP地址
- 258 if(des_ip != 32'd0)
- 259 ip_head[4] <= des_ip;
- 260 else
- 261 ip_head[4] <= DES_IP;
- 262 //16位源端口号:1234 16位目的端口号:1234
- 263 ip_head[5] <= {16'd1234,16'd1234};
- 264 //16位udp长度,16位udp校验和
- 265 ip_head[6] <= {udp_num,16'h0000};
- 266 //更新MAC地址
- 267 if(des_mac != 48'b0) begin
- 268 //目的MAC地址
- 269 eth_head[0] <= des_mac[47:40];
- 270 eth_head[1] <= des_mac[39:32];
- 271 eth_head[2] <= des_mac[31:24];
- 272 eth_head[3] <= des_mac[23:16];
- 273 eth_head[4] <= des_mac[15:8];
- 274 eth_head[5] <= des_mac[7:0];
- 275 end
- 276 end
- 277 end
- 在程序的第248行至265行代码,为IP首部数组进行赋值。
- 344 st_tx_data : begin //发送数据
- 345 crc_en <= 1'b1;
- 346 gmii_tx_en <= 1'b1;
- 347 tx_bit_sel <= tx_bit_sel + 3'd1;
- 348 if(data_cnt < tx_data_num - 16'd1)
- 349 data_cnt <= data_cnt + 16'd1;
- 350 else if(data_cnt == tx_data_num - 16'd1)begin
- 351 //如果发送的有效数据少于18个字节,在后面填补充位
- 352 //补充的值为最后一次发送的有效数据
- 353 gmii_txd <= 8'd0;
- 354 if(data_cnt + real_add_cnt < real_tx_data_num - 16'd1)
- 355 real_add_cnt <= real_add_cnt + 5'd1;
- 356 else begin
- 357 skip_en <= 1'b1;
- 358 data_cnt <= 16'd0;
- 359 real_add_cnt <= 5'd0;
- 360 tx_bit_sel <= 3'd0;
- 361 end
- 362 end
- 363 if(tx_bit_sel == 1'b0)
- 364 gmii_txd <= tx_data[31:24];
- 365 else if(tx_bit_sel == 3'd1)
- 366 gmii_txd <= tx_data[23:16];
- 367 else if(tx_bit_sel == 3'd2) begin
- 368 gmii_txd <= tx_data[15:8];
- 369 if(data_cnt != tx_data_num - 16'd1)
- 370 tx_req <= 1'b1;
- 371 end
- 372 else if(tx_bit_sel == 3'd3)
- 373 gmii_txd <= tx_data[7:0];
- 374 end
复制代码
程序第344行至374行代码为发送UDP数据段的状态。我们前面讲过以太网帧格式的数据部分最少是46个字节,去掉IP首部字节和UDP首部字节后,有效数据至少为18个字节,程序设计中已经考虑到这种情况,当发送的有效数据少于18个字节时,会在有效数据后面发送补充位,填充的数据为0。
- 375 st_crc : begin //发送CRC校验值
- 376 gmii_tx_en <= 1'b1;
- 377 tx_bit_sel <= tx_bit_sel + 3'd1;
- 378 if(tx_bit_sel == 3'd0)
- 379 gmii_txd <= {~crc_next[0], ~crc_next[1], ~crc_next[2],~crc_next[3],
- 380 ~crc_next[4], ~crc_next[5], ~crc_next[6],~crc_next[7]};
- 381 else if(tx_bit_sel == 3'd1)
- 382 gmii_txd <= {~crc_data[16],~crc_data[17], ~crc_data[18],~crc_data[19],
- 383 ~crc_data[20],~crc_data[21],~crc_data[22],~crc_data[23]};
- 384 else if(tx_bit_sel == 3'd2) begin
- 385 gmii_txd <= {~crc_data[8], ~crc_data[9], ~crc_data[10],~crc_data[11],
- 386 ~crc_data[12], ~crc_data[13], ~crc_data[14],~crc_data[15]};
- 387 end
- 388 else if(tx_bit_sel == 3'd3) begin
- 389 gmii_txd <= {~crc_data[0], ~crc_data[1], ~crc_data[2],~crc_data[3],
- 390 ~crc_data[4], ~crc_data[5], ~crc_data[6],~crc_data[7]};
- 391 tx_done_t <= 1'b1;
- 392 skip_en <= 1'b1;
- 393 end
- 394 end
复制代码
程序的第375行至394行代码为发送CRC校验值状态,发送模块的CRC校验是由crc32_d4模块完成的,发送模块将输入的crc的计算结果每4位高低位互换,按位取反发送出去。
图 40.4.7为发送过程中Vivado抓取的波形图,图中tx_start_en作为开始发送的启动信号,eth_tx_en和eth_tx_data即为GMII接口的发送接口。在开始发送以太网帧头时crc_en拉高,开始CRC校验的计算,在将要发送有效数据时拉高tx_req(发送数据请求)信号,tx_data即为待发送的有效数据,在所有数据发送完成后输出tx_done(发送完成)信号和crc_clr(CRC校验值复位)信号。
图 40.4.7 UDP发送采集的ILA波形图
以太网控制模块的代码如下:
- 1 module eth_ctrl(
- 2 input clk , //系统时钟
- 3 input rst_n , //系统复位信号,低电平有效
- 4 //ARP相关端口信号
- 5 input arp_rx_done, //ARP接收完成信号
- 6 input arp_rx_type, //ARP接收类型 0:请求 1:应答
- 7 output arp_tx_en, //ARP发送使能信号
- 8 output arp_tx_type, //ARP发送类型 0:请求 1:应答
- 9 input arp_tx_done, //ARP发送完成信号
- 10 input arp_gmii_tx_en,//ARP GMII输出数据有效信号
- 11 input [7:0] arp_gmii_txd, //ARP GMII输出数据
- 12 //UDP相关端口信号
- 13 input udp_gmii_tx_en,//UDP GMII输出数据有效信号
- 14 input [7:0] udp_gmii_txd, //UDP GMII输出数据
- 15 //GMII发送引脚
- 16 output gmii_tx_en, //GMII输出数据有效信号
- 17 output [7:0] gmii_txd //UDP GMII输出数据
- 18 );
- 19
- 20 //reg define
- 21 reg protocol_sw; //协议切换信号
- 22
- 23 //*****************************************************
- 24 //** main code
- 25 //*****************************************************
- 26
- 27 assign arp_tx_en = arp_rx_done && (arp_rx_type == 1'b0);
- 28 assign arp_tx_type = 1'b1; //ARP发送类型固定为ARP应答
- 29 assign gmii_tx_en = protocol_sw ? udp_gmii_tx_en : arp_gmii_tx_en;
- 30 assign gmii_txd = protocol_sw ? udp_gmii_txd : arp_gmii_txd;
- 31
- 32 //根据ARP发送使能/完成信号,切换GMII引脚
- 33 always @(posedge clk or negedge rst_n) begin
- 34 if(!rst_n)
- 35 protocol_sw <= 1'b1;
- 36 else if(arp_tx_en)
- 37 protocol_sw <= 1'b0;
- 38 else if(arp_tx_done)
- 39 protocol_sw <= 1'b1;
- 40 end
- 41
- 42 endmodule
复制代码
以太网控制模块的代码较简单,如果输入的arp_rx_done(ARP接收完成信号)为高电平,且arp_rx_type为低电平(ARP接收类型为请求)时,表示接收到ARP请求数据包,此时将arp_rx_done赋值给arp_tx_en信号,并拉高arp_tx_type信号,表示控制ARP模块返回应答数据包,如程序中第27行和28行代码所示。
另外,程序中的第32行至40行代码根据ARP发送使能(完成)信号,控制protocol_sw信号的高低电平,用于选择切换GMII发送引脚的连接方式。当protocol_sw等于1时,GMII发送引脚和UDP GMII发送引脚相连,否则和ARP GMII发送引脚相连,如程序中第29行第30行代码所示。
40.5下载验证
将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,将网线一端连接开发板的网口,另一端连接电脑的网口,接下来连接电源线,并打开开发板的电源开关。网口的位置如下图所示。
图 40.5.1网口位置
点击Vivado左侧“Flow Navigator”窗口最下面的“Open Hardware Manager”,此时Vivado软件识别到下载器,点击“Hardware”窗口中“Program Device”下载程序,在弹出的界面中选择“Program”下载程序。
程序下载完成后,PHY芯片会和电脑网卡进行通信(自协商),如果程序下载正确并且硬件连接无误的话,我们点击电脑右下角的网络图标,会看到本地连接刚开始显示的是正在识别,一段时间之后显示未识别的网络,打开方式如下图所示(WIN7和WIN10操作可能存在差异,但基本相同)。
图 40.5.2 点击网络图标
接下来就可以使用网口调试助手进行通信了,该工具位于开发板所随附的资料“6_软件资料/1_软件/网口调试助手”目录下(打开网口调试助手前,开发板必须硬件连接正确并且程序下载完成)。网口调试助手打开界面如图 40.5.3所示:
图 40.5.3 网口调试助手界面
打开网口调试助手后,协议类型选择:UDP;本地主机地址选择:本地连接的IP地址(在这里是192.168.1.102);本地主机端口号:1234;设置完成后点击【打开】按钮。如下图所示:
图 40.5.4 网口调试助手打开界面
远程主机选择:192.168.1.10 : 1234 (开发板的IP地址和端口号),在这里本机主端口号和远程主机端口号都为1234,见ip_send模块,源代码如下所示:
262 //16位源端口号:1234 16位目的端口号:1234
263 ip_head[5] <= {16'd1234,16'd1234};
网口调试助手打开后,在发送文本框中输入数据“http://www.openedv.com”并点击发送,如下图所示:
图 40.5.5 网口调试助手收发数据界面
可以看到网口调试助手中接收到数据“http://www.openedv.com”,接收到的数据与发送的数据一致。
接下来通过Wireshark软件抓取网口的数据包,界面如下图所示:
图 40.5.6 wireshark打开界面
双击上图所示的以太网或者先选中以太网,再点击上方红框选中的蓝色按钮,即可开始抓取本地连接的数据包,抓取界面如下图所示:
图 40.5.7 wireshark以太网打开界面
从上图可以看到,已经抓取到其它应用程序使用以太网发送的数据包,但是这些数据包并不是开发板发送的数据包,我们这个时候重新在网口调试助手中点击“发送”按钮,可以看到Wireshark软件中抓取的数据,如下图所示。
图 40.5.8 wireshark以太网抓取到的数据包
上图中第39行是上位机发送的ARP请求数据包,第40行是开发板返回的ARP应答数据包,第41行是上位机发送的UDP数据包,第42行是开发板返回的UDP数据包。双击开发板返回的数据包,可以看到开发板发送的详细数据,如下图所示:
图 40.5.9 Wireshark抓取到的详细数据
由上图可知,源IP地址(开发板IP地址)为192.168.1.10,目的IP地址(电脑IP地址)为192.168.1.102,源端口号和目的端口号都是1234。上图中下方红框为开发板发送的16进制数据(去掉前导码、SFD和CRC值),可以看到,UDP的用户数据段对应的ASIC码为“http://www.openedv.com”。 |