【正点原子FPGA连载】第二十八章 以太网ARP测试实验 摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

news2024/12/23 23:12:00

1)实验平台:正点原子MPSoC开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html

第二十八章 以太网ARP测试实验

在以太网中,一个主机和另一个主机进行通信,必须要知道目的主机的MAC地址(物理地址),而目的MAC地址的获取由ARP协议完成。本章我们来学习如何通过DFZU2EG/4EV MPSoC开发板实现ARP协议的功能。
本章分为以下几个章节:
222828.1简介
28.2实验任务
28.3硬件设计
28.4程序设计
28.5下载验证

28.1简介

ARP概述

ARP(Address Resolution Protocol),即地址解析协议,是根据IP地址(逻辑地址)获取MAC地址的一种TCP/IP协议。在以太网通信中,数据是以“帧”的格式进行传输的,帧格式里面包含目的主机的MAC地址。源主机的应用程序知道目的主机的IP地址,却不知道目的主机的MAC地址。而目的主机的MAC地址直接被网卡接收和解析,当解析到目的MAC地址非本地MAC地址时,则直接丢弃该包数据,因此在通信前需要先获得目的的MAC地址,而ARP协议正是实现了此功能。
ARP协议的基本功能是通过目的设备的IP地址,查询目的设备的MAC地址,以保证通信的顺利进行。MAC地址在网络中表示网卡的ID,每个网卡都需要并有且仅有一个MAC地址。在获取到目的MAC地址之后,将目的MAC地址更新至ARP缓存表中,称为ARP映射,下次通信时,可以直接从ARP缓存表中获取,而不用重新通过ARP获取MAC地址。但一般ARP缓存表会有过期时间,过期后需要重新通过ARP协议进行获取。
ARP映射是指将IP地址和MAC地址映射起来,分为静态映射和动态映射。
静态映射指手动创建一张ARP表,把IP地址和MAC地址关联起来。手动绑定之后,源主机在通信之前,就可以直接从ARP表中直接找到IP地址对应的MAC地址,但这样做有一定的局限性,因为MAC地址可能会变化,比如:
1)机器可能更换NIC(网络适配器),结果变成一个新的物理地址;
2)在某些局域网中,每当计算机加电时,他的物理地址都要改变一次。
3)移动电脑可以从一个物理网络转移到另一个物理网络,这样会改变物理地址。
要避免这些问题出现,必须定期维护更新ARP表,此类比较麻烦而且会影响网络性能。
动态映射指使用协议来获取相对应的物理地址,之所以用动态这个词是因为这个过程是自动完成的,一般应用程序的用户或系统管理员不必关心。已经设计出用于实现动态映射协议的有ARP和RARP(逆地址解析协议)两种,如下图所示。
在这里插入图片描述

图 28.1.1 地址解析协议:RAP和RRAP
ARP把IP地址映射为物理地址,RARP把物理地址映射为IP地址。RRAP是被那些没有磁盘驱动器的系统使用(一般是无盘工作站或 X终端),此类应用较少,本章不做讨论。
ARP协议分为ARP请求和ARP应答,源主机发起查询目的MAC地址的报文称为ARP请求,目的主机响应源主机并发送包含本地MAC地址的报文称为ARP应答。
当主机需要找出这个网络中的另一个主机的物理地址时,它就可以发送一个ARP请求报文,这个报文包含了发送方的MAC地址和IP地址以及接收方的IP地址。因为发送方不知道接收方的物理地址,所以这个查询分组会在网络层中进行广播,即ARP请求时发送的接收方物理地址为广播地址,用48’hff_ff_ff_ff_ff_ff表示。ARP请求的示意图如下图所示:
在这里插入图片描述

图 28.1.2 ARP请求示意图
上图中的主机A发起ARP请求,由于发送的目的MAC地址为广播地址,所以此时局域网中的所有主机都会进行接收并处理这个ARP请求报文,然后进行验证,查看接收方的IP地址是不是自己的地址。是则返回ARP应答报文,不是则不响应。
只有验证成功的主机才会返回一个ARP应答报文,这个应答报文包含接收方的IP地址和物理地址。ARP应答的示意图如下图所示:
在这里插入图片描述

图 28.1.3 ARP应答示意图
主机B利用收到的ARP请求报文中的请求方物理地址,以单播的方式直接发送给主机A,主机A将收到的ARP应答报文中的目的MAC地址解析出来,将目的MAC地址和目的IP地址更新至ARP缓存表中。当再次和主机A通信时,可以直接从ARP缓存表中获取,而不用重新发起ARP请求报文。需要说明的是,ARP缓存表中的表项有过期时间(一般为20分钟),过期之后,需要重新发起ARP请求以获取目的MAC地址。
ARP协议通过以太网进行传输,那么必须也要按照以太网所规定的格式进行传输,我们先来介绍下以太网的帧格式,随后再来向大家详细介绍ARP协议的具体格式。
以太网是目前应用最广泛的局域网通讯方式,同时也是一种协议。以太网协议定义了一系列软件和硬件标准,从而将不同的计算机设备连接在一起。我们知道串口通信单次只传输一个字节,而以太网通信是以数据包的形式传输,其单包数据量达到几十,甚至成百上千个字节。下图为以太网通过ARP传输单包数据的格式,从图中可以看出,以太网的数据包就是对协议的封装来实现数据的传输,即ARP数据位于以太网帧格式的数据段。这里只是让大家了解下以太网数据包的格式,后面会逐个展开来讲。
在这里插入图片描述

图 28.1.4 以太网ARP数据包格式
以太网MAC帧格式
以太网技术的正式标准是IEEE 802.3,它规定了以太网传输数据的帧结构,我们可以把以太网MAC层理解成高速公路,我们必须遵循它的规则才能在上面通行,以太网MAC层帧格式如图 28.1.5所示。
在这里插入图片描述

图 28.1.5 以太网帧格式
以太网传输数据时按照上面的顺序从头到尾依次被发送和接收,我们下面进一步解释各个区域。
前导码(Preamble):为了实现底层数据的正确阐述,物理层使用7个字节同步码(0和1交替(55-55-55-55-55-55-55))实现数据的同步。
帧起始界定符(SFD,Start Frame Delimiter):使用1个字节的SFD(固定值为0xd5)来表示一帧的开始,即后面紧跟着传输的就是以太网的帧头。
目的MAC地址:即接收端物理MAC地址,占用6个字节。MAC地址从应用上可分为单播地址、组播地址和广播地址。单播地址:第一个字节的最低位为0,比如00-00-00-11-11-11,一般用于标志唯一的设备;组播地址:第一个字节的最低位为1,比如01-00-00-11-11-11,一般用于标志同属一组的多个设备;广播地址:所有48bit全为1,即FF-FF-FF-FF-FF-FF,它用于标志同一网段中的所有设备。
源MAC地址:即发送端物理MAC地址,占用6个字节。
长度/类型:上图中的长度/类型具有两个意义,当这两个字节的值小于1536(十六进制为 0x0600)时,代表该以太网中数据段的长度;如果这两个字节的值大于1536,则表示该以太网中的数据属于哪个上层协议,例如0x0800代表IP协议(网际协议)、0x0806代表ARP协议(地址解析协议)等。
数据:以太网中的数据段长度最小46个字节,最大1500个字节。最大值1500称为以太网的最大传输单元(MTU,Maximum Transmission Unit),之所以限制最大传输单元是因为在多个计算机的数据帧排队等待传输时,如果某个数据帧太大的话,那么其它数据帧等待的时间就会加长,导致体验变差,这就像一个十字路口的红绿灯,你可以让绿灯持续亮一小时,但是等红灯的人一定不愿意的。另外还要考虑网络I/O控制器缓存区资源以及网络最大的承载能力等因素,因此最大传输单元是由各种综合因素决定的。为了避免增加额外的配置,通常以太网的有效数据字段小于1500个字节。
帧检验序列(FCS,Frame Check Sequence):为了确保数据的正确传输,在数据的尾部加入了4个字节的循环冗余校验码(CRC校验)来检测数据是否传输错误。CRC数据校验从以太网帧头开始即不包含前导码和帧起始界定符。通用的CRC标准有CRC-8、CRC-16、CRC-32、CRC-CCIT,其中在网络通信系统中应用最广泛的是CRC-32标准。
在这里还有一个要注意的地方就是以太网相邻两帧之间的时间间隔,即帧间隙(IFG,Interpacket Gap)。帧间隙的时间就是网络设备和组件在接收一帧之后,需要短暂的时间来恢复并为接收下一帧做准备的时间,IFG的最小值是96 bit time,即在媒介中发送96位原始数据所需要的时间,在不同媒介中IFG的最小值是不一样的。不管10M/100M/1000M的以太网,两帧之间最少要有96bit time,IFG的最少间隔时间计算方法如下:
10Mbit/s最小时间为:96100ns = 9600ns;
100Mbit/s最小时间为:96
10ns = 960ns;
1000Mbit/s最小时间为:96*1ns = 96ns。
接下来我们介绍ARP协议以及它和以太网MAC层的关系。在介绍ARP协议之前,我们先了解下TCP(传输控制协议)/IP(网际协议)协议簇。TCP/IP是网络使用中最基本的通信协议,虽然从名字看上去TCP/IP包括两个协议,TCP和IP,但TCP/IP实际上是一组协议,它包括上百个各种功能的协议,如:TCP、IP、ARP、UDP等。而TCP协议和IP协议是保证数据完整传输的两个重要的协议,因此TCP/IP协议用来表示Internet协议簇。
TCP/IP协议不仅可以运行在以太网上,也可以运行在FDDI(光纤分布式数据接口)和WLAN(无线局域网)上。反过来,以太网的高层协议不仅可以是TCP/IP协议,也可以是IPX协议(互联网分组交换协议)等,只不过以太网+TCP/IP成为IT行业中应用最普遍的技术。下面我们来熟悉下ARP协议。
ARP协议
ARP协议属于TCP/IP协议簇的一种,从前面介绍的图 28.1.4可以看出,ARP协议位于以太网MAC帧格式的数据段,ARP数据包格式如下图所示。
在这里插入图片描述

图 28.1.6 ARP数据包格式
硬件类型(Hardware type):硬件地址的类型,1表示以太网地址。
协议类型(Protocol type):要映射的协议地址类型,ARP协议的上层协议为IP协议,因此该协议类型为IP协议,其值为0x0800。
硬件地址长度(Hardware size):硬件地址(MAC地址)的长度,以字节为单位。对于以太网上IP地址的ARP请求或者应答来说,该值为6。
协议地址长度(Protocol size):IP地址的长度,以字节为单位。对于以太网上IP地址的ARP请求或者应答来说,该值为4。
OP(Opcode):操作码,用于表示该数据包为ARP请求或者ARP应答。1表示ARP请求,2表示ARP应答。
源MAC地址:发送端的硬件地址。
源IP地址:发送端的协议(IP)地址,如192.168.1.102。
目的MAC地址:接收端的硬件地址,在ARP请求时由于不知道接收端MAC地址,因此该字段为广播地址,即48’hff_ff_ff_ff_ff_ff。
目的IP地址:接收端的协议(IP)地址,如192.168.1.10。
以太网的帧格式、ARP数据格式到这里已经全部介绍完了,关于通过以太网传输ARP报文的格式如下图所示:
在这里插入图片描述

图 28.1.7 以太网ARP数据包格式
由上图可知,28字节的ARP数据位于以太网帧格式的数据段。由于以太网数据段最少为46个字节,而ARP数据包总长度为28个字节,因此在ARP数据段后面需要填充18个字节的数据,以满足以太网传输格式的要求。这个填充的过程称为Padding(填充),填充的数据可以为任意值,但一般为0。
RGMII接口介绍
以太网的通信离不开物理层PHY芯片的支持,以太网MAC和PHY之间有一个接口,常用的接口有MII、RMII、GMII、RGMII等。
MII(Medium Independent Interface,媒体独立接口):MII支持10Mbps和100Mbps的操作,数据位宽为4位,在100Mbps传输速率下,时钟频率为25Mhz。
RMII(Reduced MII):RMII是MII的简化版,数据位宽为2位,在100Mbps传输速率下,时钟频率为50Mhz。
GMII(Gigabit MII):GMII接口向下兼容MII接口,支持10Mbps、100Mbps和1000Mbps的操作,数据位宽为8位,在1000Mbps传输速率下,时钟频率为125Mhz。
RGMII(Reduced GMII):RGMII是GMII的简化版,数据位宽为4位,在1000Mbps传输速率下,时钟频率为125Mhz,在时钟的上下沿同时采样数据。在100Mbps和10Mbps通信速率下,为单个时钟沿采样。
在千兆以太网中,常用的接口为RGMII和GMII接口。RGMII接口的优势是同时适用于10M/100M/1000Mbps通信速率,同时占用的引脚数较少。但RGMII接口也有其缺点,就是在PCB布线时需要尽可能对时钟、控制和数据线进行等长处理,且时序约束相对也更为严格。
为了节省引脚,DFZU2EG/4EV MPSoC开发板板载的PHY芯片采用的接口为RGMII接口,下图是MAC侧与PHY侧接口的连接。
在这里插入图片描述

图 28.1.8 MAC侧与PHY侧接口连接
ETH_RXC:接收数据参考时钟,1000Mbps速率下,时钟频率为125MHz,时钟为上下沿同时采样;100Mbps速率下,时钟频率为25MHz;10Mbps速率下,时钟频率为2.5MHz,ETH_RXC由PHY侧提供。
ETH_RXCTL(ETH_RX_DV):接收数据控制信号。
ETH_RXD:四位并行的接收数据线。
ETH_TXC:发送参考时钟,1000Mbps速率下,时钟频率为125MHz,时钟为上下沿同时采样;100Mbps速率下,时钟频率为25MHz;10Mbps速率下,时钟频率为2.5MHz,ETH_TXC由MAC侧提供。
ETH_TXCTL(ETH_TXEN):发送数据控制信号。
ETH_TXD:四位并行的发送数据线。
ETH_RESET_N:芯片复位信号,低电平有效。
ETH_MDC:数据管理时钟(Management Data Clock),该引脚对ETH_MDIO信号提供了一个同步的时钟。
ETH_MDIO:数据输入/输出管理(Management Data Input/Output),该引脚提供了一个双向信号用于传递管理信息。
其中ETH_RXC、ETH_RXCTL和ETH_RXD为MAC接收侧引脚;ETH_TXC、ETH_TXCTL和ETH_TXD为MAC发送侧引脚;ETH_MDC和ETH_MDIO为MDIO接口引脚,用于配置PHY芯片内部寄存器;ETH_RST_N为PHY芯片硬件复位信号。由于PHY芯片的内部寄存器在默认配置下也可以正常工作,因此本次实验没有对MDIO接口进行读写操作,只用到了以太网的RGMII接口信号和复位信号。
RGMII使用4bit数据接口,在1000Mbps通信速率下,ETH_TXC和ETH_RXC的时钟频率为125Mhz,采用上下沿DDR(Double Data Rate)的方式在一个时钟周期内传输8位数据信号,即上升沿发送/接收低4位数据,下降沿发送/接收高4位数据。ETH_TXCTL和ETH_RXCTL控制信号同样采用DDR的方式在一个时钟周期内传输两位控制信号,即上升沿发送/接收数据使能(TX_EN/RX_DV)信号,下降沿发送/接收使能信号与错误信号的异或值(TX_ERR xor TX_EN、RX_ERR xor RX_DV)。当RX_DV为高电平(表示数据有效),RX_ERR为低电平(表示数据无错误),则异或的结果值为高电平,因此只有当ETH_RXCTL和ETH_TXCTL信号的上下沿同时为高电平时,发送和接收的数据有效且正确。
当RGMII工作在100Mbps时,ETH_TXC和ETH_RXC的时钟频率为25Mhz,采用上升沿SDR的方式在一个周期内传输4位数据。不过此时ETH_TXCTL和ETH_RXCTL控制信号仍采用上下沿DDR的传输方式。
当RGMII工作在10Mbps时,ETH_TXC和ETH_RXC的时钟频率为2.5Mhz,采用上升沿SDR的方式在一个周期内传输4位数据。ETH_TXCTL和ETH_RXCTL控制信号也采用SDR的传输方式。
RGMII接口时序
PHY芯片的RGMII接口时序,其时钟、控制信号和数据的对齐方式,一般由MDIO接口或者硬件上的特殊引脚进行配置。
RGMII接收端口时钟、控制信号和数据对齐的时序图如下:
在这里插入图片描述

图 28.1.9 RGMII接收端口信号对齐
由上图可知,RXC的上下边沿与RXD和RX_CTL信号对齐,相位相同。
RGMII接收端口时钟和控制/数据信号增加延时的时序图如下:
在这里插入图片描述

图 28.1.10 RGMII接收信号增加时钟延时
由上图可知,RXC的上下边沿与RXD和RX_CTL信号的中间位置对齐,RXC的时钟周期为8ns,单个高电平或者低电平为4ns,RXC相对于RXD和RX_CTL延时约2ns。
YT8521 RGMII接收端口的信号对齐模式由硬件上的特殊引脚外接上下拉电阻进行配置,如图 28.1.11所示。从下图中可以看出,当管脚RXDLY(RXD0)接上拉电阻时,表示RXC时钟相对于RXD信号,会增加约2ns的延时。而DFZU2EG/4EV MPSoC开发板硬件原理图中YT8521的管脚RXDLY(RXD0)连接的是上拉电阻,因此RXC和RXD之间会有约2ns的延时,RGMII接收端口的时序图如图 28.1.10所示。
在这里插入图片描述

图 28.1.11 RGMII接收端口模式配置
在这里插入图片描述

图 28.1.12 YT8521 引脚分配
RGMII发送端口正常模式时序图如下:
在这里插入图片描述

图 28.1.13 RGMII发送端口正常模式
由上图可知,RGMII发送端口正常模式下,需要满足TXC的上下边沿与TXD和TX_CTL信号对齐,相位相同。YT8521在硬件上面没有做TX端的delay模式,可根据实际情况,选择是否在代码中进行延时(因为一般对端设备的接收端会有延时处理的功能,因此发送端也可以不延时),延时后的时序图如下所示:
在这里插入图片描述

图 28.1.14 RGMII发送端口延时模式
由RGMII的接口时序可知,RGMII发送端口在TXC时钟的上升沿传输TXD的低4位和TX_CTL的使能信号;下降沿传输TXD的高4位和TX_CTL的错误信号(实际上是使能信号和错误信号的异或值);RGMII接收端口在RXC时钟的上升沿传输RXD的低4位和RX_CTL的使能信号;下降沿传输RXD的高4位和RX_CTL的错误信号(实际上是使能信号和错误信号的异或值)。
Xilinx原语
原语是Xilinx器件底层硬件中的功能模块,它使用专用的资源来实现一系列的功能。相比于IP核,原语的调用方法更简单,但是一般只用于实现一些简单的功能。本章主要用到了BUFG、BUFIO、IDDRE1、ODDRE1、IDELAYE3和IDELAYCTRL。
BUFG:全局缓冲,BUFG的输出到达FPGA内部的IOB、CLB、块RAM的时钟延迟和抖动最小。BUFG原语模板如下:
BUFG BUFG_inst (
.O(O), // 1-bit output: Clock output
.I(I) // 1-bit input: Clock input
);
除了BUFG外,常用的还有BUFR,BUFR是regional时钟网络,它的驱动范围只能局限在一个clock region的逻辑。BUFR相比BUFG的最大优势是偏斜和功耗都比较小。
BUFIO:BUFIO是IO时钟网络,其独立于全局时钟资源,适合采集源同步数据。它只能驱动IO Block里面的逻辑,不能驱动CLB里面的LUT,REG等逻辑。BUFIO原语模板如下:
BUFIO BUFIO_inst (
.O(O), // 1-bit output: Clock output (connect to I/O clock loads).
.I(I) // 1-bit input: Clock input (connect to an IBUF or BUFMR).
);
BUFIO在采集源同步IO数据时,提供非常小的延时,因此非常适合采集比如RGMII接收侧的数据,但是由于其不能驱动FPGA的内部逻辑,因此需要BUFIO和BUFG配合使用,以达到最佳性能。如ETH_RXC的时钟经过BUFIO,用来采集端口数据;ETH_RXC经过BUFG,用来作为除端口采集外的其他模块的操作时钟。
IDDRE1:在UltraScale系列设备的ILOGIC block中有专属的registers来实现input double-data-rate(IDDRE1) registers,将输入的上下边沿DDR信号,转换成两位单边沿SDR信号。IDDR的原语结构图如下图所示:
在这里插入图片描述

图 28.1.15 IDDRE1原语结构图
C:输入的高速时钟;
D:输入的1位DDR数据;
Q1和Q2:分别是“C”时钟上升沿和下降沿同步输出的SDR数据;
CB:高速时钟C的反转;
R:置位/复位信号,高有效;
IDDRE1原语模板如下:

IDDRE1 #(
      .DDR_CLK_EDGE("OPPOSITE_EDGE"),
      // IDDRE1 mode (OPPOSITE_EDGE, SAME_EDGE, SAME_EDGE_PIPELINED)
      .IS_CB_INVERTED(1'b0),          // Optional inversion for CB
      .IS_C_INVERTED(1'b0)            // Optional inversion for C
   )
   IDDRE1_inst (
      .Q1(Q1), // 1-bit output: Registered parallel output 1
      .Q2(Q2), // 1-bit output: Registered parallel output 2
      .C(C),   // 1-bit input: High-speed clock
      .CB(CB), // 1-bit input: Inversion of High-speed clock C
      .D(D),   // 1-bit input: Serial Data Input
      .R(R)    // 1-bit input: Active High Async Reset
   );

DDR_CLK_EDGE参数为IDDRE1的三种采集模式,分别为“OPPOSITE_EDGE”、“SAME_EDGE”和“SAME_EDGE_PIPELINED”模式。
OPPOSITE_EDGE模式的时序图如下图所示:
在这里插入图片描述

图 28.1.16 IDDRE1“OPPOSITE_EDGE”模式时序图
OPPOSITE_EDGE模式下,在时钟的上升沿输出的Q1,时钟的下降沿输出Q2。
SAME_EDGE模式的时序图如下图所示:
在这里插入图片描述

图 28.1.17 IDDRE1“SAME_EDGE”模式时序图
SAME_EDGE模式下,在时钟的上升沿输出Q1和Q2,但Q1和Q2不在同一个cycle输出。
SAME_EDGE_PIPELINED模式的时序图如下图所示:
在这里插入图片描述

图 28.1.18 IDDRE1“SAME_EDGE_PIPELINED”模式时序图
SAME_EDGE_PIPELINED模式下,在时钟的上升沿输出Q1和Q2,Q1和Q2虽然在同一个cycle输出,但整体延时了一个时钟周期。在使用IDDRE1时,一般采用此种模式。
ODDRE1:通过ODDRE1把两路单端的数据合并到一路上输出,上下沿同时输出数据,上升沿输出a路,下降沿输出b路;如果两路输入信号一路固定为1,另外一路固定为0,那么输出的信号实际上是时钟信号。
ODDRE1的原语结构图如下图所示:
在这里插入图片描述

图 28.1.19 ODDRE1原语结构图
C:输入的高速时钟;
Q:输出的1位DDR数据;
D1和D2:分别是“C”时钟上升沿和下降沿同步输入的SDR数据。
SR:置位/复位信号,高有效
ODDRE1原语模板如下:

   ODDRE1 #(
      .IS_C_INVERTED(1'b0),      // Optional inversion for C
      .IS_D1_INVERTED(1'b0),     // Unsupported, do not use
      .IS_D2_INVERTED(1'b0),     // Unsupported, do not use
      .SIM_DEVICE("ULTRASCALE"), 
// Set the device version (ULTRASCALE, ULTRASCALE_PLUS, ULTRASCALE_PLUS_ES1,ULTRASCALE_PLUS_ES2, VERSAL, VERSAL_ES1, VERSAL_ES2)
      .SRVAL(1'b0)
//Initializes the ODDRE1 Flip-Flops to the specified value (1'b0, 1'b1)
   )
   ODDRE1_inst (
      .Q(Q),   // 1-bit output: Data output to IOB
      .C(C),   // 1-bit input: High-speed clock input
      .D1(D1), // 1-bit input: Parallel data input 1
      .D2(D2), // 1-bit input: Parallel data input 2
      .SR(SR)  // 1-bit input: Active High Async Reset
   );

SIM_DEVICE参数为ODDRE1的七种设备版本,分别为UltraScale、UltraScale Plus、UltraScale Plus ES1、UltraScale Plus ES2、Versal、Versal ES1、Versal ES2。
28.2实验任务
本节实验任务是使用DFZU2EG/4EV MPSoC开发板上的PL端以太网接口,和上位机实现ARP请求和应答的功能。当上位机发送ARP请求时,开发板返回ARP应答数据。当按下开发板的PL Key1按键时,开发板发送ARP请求,此时上位机返回应答数据。
28.3硬件设计
PL端千兆以太网接口部分的硬件设计原理和“MDIO接口读写测试实验” 完全相同,请参考“MDIO接口读写测试实验”中的硬件设计部分。
本实验中,各端口信号的管脚分配如下表所示:
表 28.3.1 以太网ARP测试实验管脚分配
在这里插入图片描述

对应的XDC约束语句如下所示:
#时钟周期约束
create_clock -name sys_clk_p -period 10.000 [get_ports sys_clk_p]
create_clock -period 8.000 -name eth_rxc [get_ports eth_rxc]

#IO管脚约束
set_property IOSTANDARD DIFF_HSTL_I_12 [get_ports sys_clk_p]
set_property IOSTANDARD DIFF_HSTL_I_12 [get_ports sys_clk_n]
set_property PACKAGE_PIN AE5 [get_ports sys_clk_p]
set_property PACKAGE_PIN AF5 [get_ports sys_clk_n]
set_property -dict {PACKAGE_PIN AH11 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]

set_property -dict {PACKAGE_PIN AD11 IOSTANDARD LVCMOS33} [get_ports key]

set_property -dict {PACKAGE_PIN E5 IOSTANDARD LVCMOS18} [get_ports eth_rxc]
set_property -dict {PACKAGE_PIN D5 IOSTANDARD LVCMOS18} [get_ports eth_rx_ctl]
set_property -dict {PACKAGE_PIN G6 IOSTANDARD LVCMOS18} [get_ports {eth_rxd[0]}]
set_property -dict {PACKAGE_PIN F6 IOSTANDARD LVCMOS18} [get_ports {eth_rxd[1]}]
set_property -dict {PACKAGE_PIN G8 IOSTANDARD LVCMOS18} [get_ports {eth_rxd[2]}]
set_property -dict {PACKAGE_PIN F7 IOSTANDARD LVCMOS18} [get_ports {eth_rxd[3]}]

set_property -dict {PACKAGE_PIN D6 IOSTANDARD LVCMOS18} [get_ports eth_txc]
set_property -dict {PACKAGE_PIN F8 IOSTANDARD LVCMOS18} [get_ports eth_tx_ctl]
set_property -dict {PACKAGE_PIN D7 IOSTANDARD LVCMOS18} [get_ports {eth_txd[0]}]
set_property -dict {PACKAGE_PIN E8 IOSTANDARD LVCMOS18} [get_ports {eth_txd[1]}]
set_property -dict {PACKAGE_PIN E9 IOSTANDARD LVCMOS18} [get_ports {eth_txd[2]}]
set_property -dict {PACKAGE_PIN D9 IOSTANDARD LVCMOS18} [get_ports {eth_txd[3]}]
28.4程序设计
根据实验任务,我们可以大致规划出系统的控制流程:首先我们需要完成RGMII接口数据和GMII接口数据的转换,以方便数据的采集和解析,在数据采集过程中所用到的延时原语参考时钟由锁相环输出的时钟提供;其次整个以太网帧格式与ARP协议的实现由ARP顶层模块完成;ARP控制模块负责检测输入的按键是否被按下,控制ARP顶层模块发起请求与产生应答等操作。由此画出系统的功能框图如下图所示:
在这里插入图片描述

图 28.4.1 以太网ARP测试系统框图
GMII TO RGMII模块负责将双沿(DDR)数据和单沿(SDR)数据之间的转换;ARP顶层模块实现了以太网ARP数据包的接收、发送以及CRC校验的功能;ARP控制模块根据输入的PL Key1按键信号和接收到的ARP请求信号,控制ARP顶层模块发送ARP请求或者ARP应答。
各模块端口及信号连接如下图所示:
在这里插入图片描述

图 28.4.2 顶层模块原理图
由上图可知,FPGA顶层模块例化了以下四个模块,GMII TO RGMII模块(gmii_to_rgmii)、时钟模块、ARP顶层模块(arp)和ARP控制模块(arp_ctrl),实现了各模块之间的数据交互。
其中ARP顶层模块和GMII TO RGMII模块内部也例化了多个其它模块,这样设计的目的是为了方便模块的重用。
顶层模块的代码如下:

1   module eth_arp_test(
2       input              sys_clk_p ,
3       input              sys_clk_n ,
4       input              sys_rst_n , //系统复位信号,低电平有效 
5       input              key       , //PL功能按键1,用于触发开发板发出ARP请求
6       //PL以太网RGMII接口   
7       input              eth_rxc   , //RGMII接收数据时钟
8       input              eth_rx_ctl, //RGMII输入数据有效信号
9       input       [3:0]  eth_rxd   , //RGMII输入数据
10      output             eth_txc   , //RGMII发送数据时钟    
11      output             eth_tx_ctl, //RGMII输出数据有效信号
12      output      [3:0]  eth_txd     //RGMII输出数据          
13      );
14  
15  //parameter define
16  //开发板MAC地址 00-11-22-33-44-55
17  parameter  BOARD_MAC = 48'h00_11_22_33_44_55;     
18  //开发板IP地址 192.168.1.10     
19  parameter  BOARD_IP  = {8'd192,8'd168,8'd1,8'd10};
20  //目的MAC地址 ff_ff_ff_ff_ff_ff
21  parameter  DES_MAC   = 48'hff_ff_ff_ff_ff_ff;
22  //目的IP地址 192.168.1.102
23  parameter  DES_IP    = {8'd192,8'd168,8'd1,8'd102};
24  
25  //wire define              
26  wire          gmii_rx_clk; //GMII接收时钟
27  wire          gmii_rx_dv ; //GMII接收数据有效信号
28  wire  [7:0]   gmii_rxd   ; //GMII接收数据
29  wire          gmii_tx_clk; //GMII发送时钟
30  wire          gmii_tx_en ; //GMII发送数据使能信号
31  wire  [7:0]   gmii_txd   ; //GMII发送数据
32                
33  wire          arp_rx_done; //ARP接收完成信号
34  wire          arp_rx_type; //ARP接收类型 0:请求  1:应答
35  wire  [47:0]  src_mac    ; //接收到目的MAC地址
36  wire  [31:0]  src_ip     ; //接收到目的IP地址    
37  wire          arp_tx_en  ; //ARP发送使能信号
38  wire          arp_tx_type; //ARP发送类型 0:请求  1:应答
39  wire          tx_done    ; //发送的目标MAC地址
40  wire  [47:0]  des_mac    ; //发送的目标IP地址
41  wire  [31:0]  des_ip     ; //以太网发送完成信号    
42  
43  //*****************************************************
44  //**                    main code
45  //*****************************************************
46  
47  assign des_mac = src_mac;
48  assign des_ip  = src_ip ; 
49  assign eth_txc = clk_125m_deg; 
50  
51  clk_wiz_0 u_clk_wiz_0
52   (
53    // Clock out ports
54    .clk_out1   (clk_125m_deg ),      // output clk_out1
55    // Status and control signals
56    .reset      (~sys_rst_n   ),      // input reset
57    .locked     (locked       ),      // output locked
58   // Clock in ports
59    .clk_in1    (rgmii_txc    )       // input clk_in1
60    );      
61  
62  //GMII接口转RGMII接口
63  gmii_to_rgmii u_gmii_to_rgmii(
64      .gmii_rx_clk   (gmii_rx_clk ),
65      .gmii_rx_dv    (gmii_rx_dv  ),
66      .gmii_rxd      (gmii_rxd    ),
67      .gmii_tx_clk   (gmii_tx_clk ),
68      .gmii_tx_en    (gmii_tx_en  ),
69      .gmii_txd      (gmii_txd    ),
70      
71      .rgmii_rxc     (eth_rxc     ),
72      .rgmii_rx_ctl  (eth_rx_ctl  ),
73      .rgmii_rxd     (eth_rxd     ),
74      .rgmii_txc     (rgmii_txc    ),
75      .rgmii_tx_ctl  (eth_tx_ctl  ),
76      .rgmii_txd     (eth_txd     )
77      );
78  
79  //ARP通信
80  arp                                             
81     #(
82      .BOARD_MAC     (BOARD_MAC),      //参数例化
83      .BOARD_IP      (BOARD_IP ),
84      .DES_MAC       (DES_MAC  ),
85      .DES_IP        (DES_IP   )
86      )
87     u_arp(
88      .rst_n         (sys_rst_n  ),
89                      
90      .gmii_rx_clk   (gmii_rx_clk),
91      .gmii_rx_dv    (gmii_rx_dv ),
92      .gmii_rxd      (gmii_rxd   ),
93      .gmii_tx_clk   (gmii_tx_clk),
94      .gmii_tx_en    (gmii_tx_en ),
95      .gmii_txd      (gmii_txd   ),
96                      
97      .arp_rx_done   (arp_rx_done),
98      .arp_rx_type   (arp_rx_type),
99      .src_mac       (src_mac    ),
100     .src_ip        (src_ip     ),
101     .arp_tx_en     (arp_tx_en  ),
102     .arp_tx_type   (arp_tx_type),
103     .des_mac       (des_mac    ),
104     .des_ip        (des_ip     ),
105     .tx_done       (tx_done    )
106     );
107 
108 //ARP控制
109 arp_ctrl u_arp_ctrl(
110     .clk           (gmii_rx_clk),
111     .rst_n         (sys_rst_n  ),
112                    
113     .key           (key        ),
114     .arp_rx_done   (arp_rx_done),
115     .arp_rx_type   (arp_rx_type),
116     .arp_tx_en     (arp_tx_en  ),
117     .arp_tx_type   (arp_tx_type)
118     );
119 
120 endmodule

顶层模块主要完成对其余模块的例化。在程序的第15行至第23行代码定义了开发板的MAC地址、IP地址、默认的目的MAC地址和目的IP地址。开发板的MAC地址为00:11:22:33:44:55;开发板的IP地址为192.168.1.10;默认目的MAC地址为ff:ff:ff:ff:ff:ff,这是一个广播MAC地址,在收到上位机的请求或者应答之后,ARP模块会替换成实际的目的MAC地址。目的IP地址这里设置为192.168.1.102,因此大家在做本次实验时,需要把电脑的以太网的IP地址改成192.168.1.102,或者将代码中定义的DES_IP改成电脑的IP地址。
程序的第47行和48行代码将收到的对端设备MAC地址和目的IP地址,作为开发板发送时的目的MAC地址和IP地址。
gmii_to_rgmii模块代码如下:

1  module gmii_to_rgmii(
2      //以太网GMII接口    
3      output             gmii_rx_clk , //GMII接收时钟
4      output             gmii_rx_dv  , //GMII接收数据有效信号
5      output      [7:0]  gmii_rxd    , //GMII接收数据
6      output             gmii_tx_clk , //GMII发送时钟
7      input              gmii_tx_en  , //GMII发送数据使能信号
8      input       [7:0]  gmii_txd    , //GMII发送数据            
9      //以太网RGMII接口   
10     input              rgmii_rxc   , //RGMII接收时钟
11     input              rgmii_rx_ctl, //RGMII接收数据控制信号
12     input       [3:0]  rgmii_rxd   , //RGMII接收数据
13     output             rgmii_txc   , //RGMII发送时钟    
14     output             rgmii_tx_ctl, //RGMII发送数据控制信号
15     output      [3:0]  rgmii_txd     //RGMII发送数据          
16     );
17  
18 //*****************************************************
19 //**                    main code
20 //*****************************************************
21 
22 assign gmii_tx_clk = gmii_rx_clk;
23 
24 //RGMII接收
25 rgmii_rx u_rgmii_rx(
26     .gmii_rx_clk   (gmii_rx_clk ),
27     .rgmii_rxc     (rgmii_rxc   ),
28     .rgmii_rx_ctl  (rgmii_rx_ctl),
29     .rgmii_rxd     (rgmii_rxd   ),
30     
31     .gmii_rx_dv    (gmii_rx_dv ),
32     .gmii_rxd      (gmii_rxd   )
33     );
34 
35 //RGMII发送
36 rgmii_tx u_rgmii_tx(
37     .gmii_tx_clk   (gmii_tx_clk ),
38     .gmii_tx_en    (gmii_tx_en  ),
39     .gmii_txd      (gmii_txd    ),
40               
41     .rgmii_txc     (rgmii_txc   ),
42     .rgmii_tx_ctl  (rgmii_tx_ctl),
43     .rgmii_txd     (rgmii_txd   )
44     );
45 
46 endmodule

由该模块的端口可知,该模块实现了双沿(DDR)数据和单沿(SDR)数据之间的转换。程序中第22行将GMII接收时钟赋值给GMII发送时钟,因此GMII的发送时钟和接收时钟实际上为同一个时钟。GMII TO RGMII模块例化了rgmii_rx模块和rgmii_tx模块。
rgmii_rx模块代码如下所示:

1  module rgmii_rx(
2      //以太网RGMII接口
3      input              rgmii_rxc   ,    //RGMII接收时钟
4      input              rgmii_rx_ctl,    //RGMII接收数据控制信号
5      input       [3:0]  rgmii_rxd   ,    //RGMII接收数据    
6  
7      //以太网GMII接口
8      output             gmii_rx_clk ,    //GMII接收时钟
9      output             gmii_rx_dv  ,    //GMII接收数据有效信号
10     output      [7:0]  gmii_rxd         //GMII接收数据   
11     );
12 
13 //wire define
14 wire         rgmii_rxc_bufg;             //全局时钟缓存
15 wire         rgmii_rxc_bufio;            //全局时钟IO缓存
16 wire  [1:0]  gmii_rxdv_t;                //两位GMII接收有效信号 
17 
18 //*****************************************************
19 //**                    main code
20 //*****************************************************
21 
22 assign gmii_rx_clk = rgmii_rxc_bufg;
23 assign gmii_rx_dv  = gmii_rxdv_t[0] & gmii_rxdv_t[1];
24 
25 //全局时钟缓存
26 BUFG BUFG_inst (
27   .I            (rgmii_rxc),      // 1-bit input: Clock input
28   .O            (rgmii_rxc_bufg)  // 1-bit output: Clock output
29 );
30 
31 //全局时钟IO缓存
32 BUFIO BUFIO_inst (
33   .I            (rgmii_rxc),      // 1-bit input: Clock input
34   .O            (rgmii_rxc_bufio) // 1-bit output: Clock output
35 );
36 
37 //将输入的上下边沿DDR信号,转换成两位单边沿SDR信号
38  IDDRE1 #(
39     .DDR_CLK_EDGE     ("SAME_EDGE_PIPELINED"),
40     // IDDRE1 mode (OPPOSITE_EDGE, SAME_EDGE, SAME_EDGE_PIPELINED)
41     .IS_CB_INVERTED   (1'b0),    // Optional inversion for CB
42     .IS_C_INVERTED    (1'b0)     // Optional inversion for C
43  )
44  IDDRE1_inst (
45     .Q1  (gmii_rxdv_t[0]),  // 1-bit output: Registered parallel output 1
46     .Q2  (gmii_rxdv_t[1]),  // 1-bit output: Registered parallel output 2
47     .C   (rgmii_rxc_bufio), // 1-bit input: High-speed clock
48     .CB  (~rgmii_rxc_bufio),// 1-bit input: Inversion of High-speed clock C
49     .D   (rgmii_rx_ctl),    // 1-bit input: Serial Data Input
50     .R   (1'b0)             // 1-bit input: Active High Async Reset
51  );
52 
53 genvar i;
54 generate for (i=0; i<4; i=i+1)
55     begin : rxdata_bus
56         IDDRE1 #(
57            .DDR_CLK_EDGE      ("SAME_EDGE_PIPELINED"),  
58            // IDDRE1 mode (OPPOSITE_EDGE, SAME_EDGE, SAME_EDGE_PIPELINED)
59            .IS_CB_INVERTED    (1'b0),              // Optional inversion for CB
60            .IS_C_INVERTED     (1'b0)               // Optional inversion for C
61         )
62         IDDRE1_inst (
63            .Q1   (gmii_rxd[i]),       // 1-bit output: Registered parallel output 1
64            .Q2   (gmii_rxd[4+i]),     // 1-bit output: Registered parallel output 2
65            .C    (rgmii_rxc_bufio),   // 1-bit input: High-speed clock
66            .CB   (~rgmii_rxc_bufio),  // 1-bit input: Inversion of High-speed clock C
67            .D    (rgmii_rxd[i]),      // 1-bit input: Serial Data Input
68            .R    (1'b0)               // 1-bit input: Active High Async Reset
69         ); 
70     end
71 endgenerate
72 
73 endmodule

该模块通过调用BUFG、BUFIO、IDDRE1原语,实现了RGMII接口输入的DDR数据到SDR数据的转换,输入的rgmii_rx_ctl控制信号的转换方法同样类似。rgmii_rx模块信号转换示意图如下图所示:
在这里插入图片描述

图 28.4.3 rgmii_rx模块信号转换示意图
时钟专用引脚输入的rgmii_rxc时钟经过BUFG后,得到gmii_rx_clk,该时钟为全局缓冲时钟,其到达FPGA内部的IOB、CLB、块RAM的时钟延迟和抖动非常小,为其他模块提供操作时钟;另外rgmii_rxc时钟也经过BUFIO,专门用于采集IO端口的数据。
rgmii_rx_ctl控制信号和4位rgmii_rxd数据经过IDDRE1将双沿1位数据转换成单沿两位数据。
另外,在程序的第53行至71行代码通过generate for语句实现对IDDRE1的例化,由于输入的数据引脚为4位数据,因此这里共例化了4次。其等效于分别对输入的IDDRE1例化4次,这里采用generate for的写法可以减少很多的代码量,当需要对某个模块例化较多次数时,这种写法能够大大提高效率。
rgmii_tx模块代码如下所示:

1  module rgmii_tx(
2      //GMII发送端口
3      input              gmii_tx_clk , //GMII发送时钟    
4      input              gmii_tx_en  , //GMII输出数据有效信号
5      input       [7:0]  gmii_txd    , //GMII输出数据        
6      
7      //RGMII发送端口
8      output             rgmii_txc   , //RGMII发送数据时钟    
9      output             rgmii_tx_ctl, //RGMII输出数据有效信号
10     output      [3:0]  rgmii_txd     //RGMII输出数据     
11     );
12 
13 //*****************************************************
14 //**                    main code
15 //*****************************************************
16 
17 assign rgmii_txc = gmii_tx_clk;
18 
19 //输出双沿采样寄存器 (rgmii_tx_ctl)
20 ODDRE1 #(
21       .IS_C_INVERTED     (1'b0),            // Optional inversion for C
22       .IS_D1_INVERTED    (1'b0),            // Unsupported, do not use
23       .IS_D2_INVERTED    (1'b0),            // Unsupported, do not use
24       .SIM_DEVICE        ("ULTRASCALE"),   
25        // Set the device version (ULTRASCALE, ULTRASCALE_PLUS,
26        // ULTRASCALE_PLUS_ES1,ULTRASCALE_PLUS_ES2)
27       .SRVAL(1'b0)                          
28 // Initializes the ODDRE1 Flip-Flops to the specified value (1'b0, 1'b1)
29    )
30    ODDRE1_tx_ctl (
31       .Q     (rgmii_tx_ctl),    // 1-bit output: Data output to IOB
32       .C     (gmii_tx_clk),     // 1-bit input: High-speed clock input
33       .D1    (gmii_tx_en),      // 1-bit input: Parallel data input 1
34       .D2    (gmii_tx_en),      // 1-bit input: Parallel data input 2
35       .SR    (1'b0)             // 1-bit input: Active High Async Reset
36    );
37 
38 genvar i;
39 generate for (i=0; i<4; i=i+1)
40     begin : txdata_bus
41       ODDRE1 #(
42       .IS_C_INVERTED(1'b0),      // Optional inversion for C
43       .IS_D1_INVERTED(1'b0),     // Unsupported, do not use
44       .IS_D2_INVERTED(1'b0),     // Unsupported, do not use
45       .SIM_DEVICE("ULTRASCALE"), 
46       // Set the device version (ULTRASCALE, ULTRASCALE_PLUS,
47       // ULTRASCALE_PLUS_ES1,ULTRASCALE_PLUS_ES2)
48       .SRVAL(1'b0)               
49 // Initializes the ODDRE1 Flip-Flops to the specified value (1'b0, 1'b1)
50    )
51    ODDRE1_inst (
52       .Q     (rgmii_txd[i]),      // 1-bit output: Data output to IOB
53       .C     (gmii_tx_clk),       // 1-bit input: High-speed clock input
54       .D1    (gmii_txd[i]),       // 1-bit input: Parallel data input 1
55       .D2    (gmii_txd[4+i]),     // 1-bit input: Parallel data input 2
56       .SR    (1'b0)               // 1-bit input: Active High Async Reset
57    );             
58     end
59 endgenerate
60 
61 endmodule

该模块通过调用ODDRE1原语将输入的单沿8位数据(gmii_txd)转换成双沿采样的4位数据(rgmii_txd),gmii_tx_en和rgmii_tx_ctl信号的处理方法同样类似。rgmii_tx模块信号转换示意图如下图所示:
在这里插入图片描述

图 28.4.4 rgmii_tx模块信号转换示意图
gmii_tx_en数据使能信号和8位gmii_txd数据经过ODDRE1将单沿2位数据转换成双沿1位数据。需要说明的是,在程序的第38行至第59行同样通过generate for语句实现原语的多次例化。
ARP顶层模块实现了整个以太网帧格式与ARP协议的功能,其模块端口及信号连接如下图所示:
在这里插入图片描述

图 28.4.5 ARP模块原理图
由上图可知,ARP顶层模块例化了ARP接收模块(arp_rx)、ARP发送模块(arp_tx)和CRC校验模块(crc32_d8)。
ARP接收模块(arp_rx):ARP接收模块负责解析以太网的数据,判断目的MAC地址和目的IP地址是否为开发板的地址,然后按照ARP协议将数据解析出来。当解析到正确的ARP数据包后,拉高arp_rx_done信号,持续一个时钟周期。arp_rx_type用于表示ARP数据包的类型,0表示收到ARP请求包,1表示收到ARP应答包。src_mac和src_ip分别是解析出的对端设备MAC地址和IP地址。
ARP发送模块(arp_tx):ARP发送模块根据以太网帧格式和ARP协议发送ARP请求或者ARP应答数据。arp_tx_en和arp_tx_type分别表示ARP发送模块的使能信号和发送ARP类型。dec_mac和dec_ip分别设置对端设备MAC地址和IP地址。
CRC校验模块(crc32_d8):CRC校验模块是对ARP发送模块的数据(不包括前导码和帧起始界定符)做校验,把校验结果值拼在以太网帧格式的FCS字段,如果CRC校验值计算错误或者没有的话,那么电脑网卡会直接丢弃该帧导致收不到数据(有些网卡是可以设置不做校验的)。CRC32校验在FPGA实现的原理是LFSR(Linear Feedback Shift Register,线性反馈移位寄存器),其思想是各个寄存器储存着上一次CRC32运算的结果,寄存器的输出即为CRC32的值。需要说明的是,本次实验只对发送模块做校验,没有对接收模块做校验。这是由于我们可以直接通过解析出的数据来大致判断接收是否正确,而发送模块必须发送正确的校验数据,否则发送的数据直接被电脑的网卡丢弃,导致ARP请求或者应答失败。
在简介部分我们向大家介绍过,ARP的数据包格式包括前导码+SFD、以太网帧头、ARP数据(包括填充部分数据)和CRC校验。在接收以太网数据的过程中,这些不同部分的数据可以刚好对应状态机的不同状态位,因此我们可以通过状态机来解析以太网的数据。
ARP接收模块通过状态机来解析数据,其状态跳转图如下图所示:
在这里插入图片描述

图 28.4.6 ARP接收模块状态跳转图
接收模块使用三段式状态机来解析以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。这里需要注意的一点是,在中间状态如前导码错误、MAC地址错误以及IP地址等错误时跳转到st_rx_end状态,而不是跳转到st_idle状态。因为中间状态在解析到数据错误时,单包数据的接收还没有结束,如果此时跳转到st_idle状态会误把有效数据当成前导码来解析,所以状态跳转到st_rx_end。而gmii_rx_dv信号为0时,单包数据才算接收结束,所以st_rx_end跳转到st_idle的条件是eth_rxdv=0,准备接收下一包数据。因为代码较长,只粘贴了第三段状态机的接收ARP数据状态和接收结束状态源代码,代码如下:

186             st_arp_data : begin
187                 if(gmii_rx_dv) begin
188                     cnt <= cnt + 5'd1;
189                     if(cnt == 5'd6) 
190                         op_data[15:8] <= gmii_rxd;           //操作码       
191                     else if(cnt == 5'd7)
192                         op_data[7:0] <= gmii_rxd;
193                     else if(cnt >= 5'd8 && cnt < 5'd14)      //源MAC地址
194                         src_mac_t <= {src_mac_t[39:0],gmii_rxd};
195                     else if(cnt >= 5'd14 && cnt < 5'd18)     //源IP地址
196                         src_ip_t<= {src_ip_t[23:0],gmii_rxd};
197                     else if(cnt >= 5'd24 && cnt < 5'd28)     //目标IP地址
198                         des_ip_t <= {des_ip_t[23:0],gmii_rxd};
199                     else if(cnt == 5'd28) begin
200                         cnt <= 5'd0;
201                         if(des_ip_t == BOARD_IP) begin       //判断目的IP地址和操作码
202                             if((op_data == 16'd1) || (op_data == 16'd2)) begin
203                                 skip_en <= 1'b1;
204                                 rx_done_t <= 1'b1;
205                                 src_mac <= src_mac_t;
206                                 src_ip <= src_ip_t;
207                                 src_mac_t <= 48'd0;
208                                 src_ip_t <= 32'd0;
209                                 des_mac_t <= 48'd0;
210                                 des_ip_t <= 32'd0;
211                                 if(op_data == 16'd1)         
212                                     arp_rx_type <= 1'b0;     //ARP请求
213                                 else
214                                     arp_rx_type <= 1'b1;     //ARP应答
215                             end
216                             else
217                                 error_en <= 1'b1;
218                         end 
219                         else
220                             error_en <= 1'b1;
221                     end
222                 end                                
223             end
224             st_rx_end : begin     
225                 cnt <= 5'd0;
226                 //单包数据接收完成   
227                 if(gmii_rx_dv == 1'b0 && skip_en == 1'b0)
228                     skip_en <= 1'b1; 
229             end 

st_arp_data状态根据ARP协议解析数据,在程序的第201行至第202行代码判断目的IP地址和OP操作码是否正确,如果错误,则丢弃该包数据。在程序的第211行至第214行代码根据操作码为arp_rx_type(接收到的ARP数据包类型)赋值,当接收到ARP请求包时,arp_rx_type等于0;当接收到ARP应答包时,arp_rx_type等于1。
ARP接收过程中采集的ILA波形图如图 28.4.7所示,gmii_rx_dv信号拉高表示此时输入的数据有效,根据gmii_rxd的值来解析数据。从图中可以看出,发送端的MAC地址和地址,以及当前接收到的以太网数据包类型为ARP(0x0806)。在接收完ARP数据包之后,拉高arp_rx_done信号表示接收完成,图中arp_rx_type信号为低电平,表示当前接收到的是ARP请求数据包。
在这里插入图片描述

图 28.4.7 ARP接收采集ILA波形图
ARP发送模块则是根据以太网帧格式是ARP协议发送数据,也就是接收模块的逆过程,同样也非常适合使用状态机来完成发送数据的功能,状态跳转图如下图所示:
在这里插入图片描述

图 28.4.8 ARP发送模块状态跳转图
发送模块和接收模块有很多相似之处,同样使用三段式状态机来发送以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。
发送模块的代码中定义了数组来存储前导码+帧头、以太网的帧头、ARP数据,在复位时初始化数组的值,部分源代码如下。

66  reg  [7:0]  preamble[7:0] ; //前导码+SFD
67  reg  [7:0]  eth_head[13:0]; //以太网首部
68  reg  [7:0]  arp_data[27:0]; //ARP数据
省略部分代码……
155         //初始化数组    
156         //前导码 7个8'h55 + 1个8'hd5 
157         preamble[0] <= 8'h55;                
158         preamble[1] <= 8'h55;
159         preamble[2] <= 8'h55;
160         preamble[3] <= 8'h55;
161         preamble[4] <= 8'h55;
162         preamble[5] <= 8'h55;
163         preamble[6] <= 8'h55;
164         preamble[7] <= 8'hd5;
165         //以太网帧头 
166         eth_head[0] <= DES_MAC[47:40];      //目的MAC地址
167         eth_head[1] <= DES_MAC[39:32];
168         eth_head[2] <= DES_MAC[31:24];
169         eth_head[3] <= DES_MAC[23:16];
170         eth_head[4] <= DES_MAC[15:8];
171         eth_head[5] <= DES_MAC[7:0];        
172         eth_head[6] <= BOARD_MAC[47:40];    //源MAC地址
173         eth_head[7] <= BOARD_MAC[39:32];    
174         eth_head[8] <= BOARD_MAC[31:24];    
175         eth_head[9] <= BOARD_MAC[23:16];    
176         eth_head[10] <= BOARD_MAC[15:8];    
177         eth_head[11] <= BOARD_MAC[7:0];     
178         eth_head[12] <= ETH_TYPE[15:8];     //以太网帧类型
179         eth_head[13] <= ETH_TYPE[7:0];      
180         //ARP数据                           
181         arp_data[0] <= HD_TYPE[15:8];       //硬件类型
182         arp_data[1] <= HD_TYPE[7:0];
183         arp_data[2] <= PROTOCOL_TYPE[15:8]; //上层协议类型
184         arp_data[3] <= PROTOCOL_TYPE[7:0];
185         arp_data[4] <= 8'h06;               //硬件地址长度,6
186         arp_data[5] <= 8'h04;               //协议地址长度,4
187         arp_data[6] <= 8'h00;               //OP,操作码 8'h01:ARP请求 8'h02:ARP应答
188         arp_data[7] <= 8'h01;
189         arp_data[8] <= BOARD_MAC[47:40];    //发送端(源)MAC地址
190         arp_data[9] <= BOARD_MAC[39:32];
191         arp_data[10] <= BOARD_MAC[31:24];
192         arp_data[11] <= BOARD_MAC[23:16];
193         arp_data[12] <= BOARD_MAC[15:8];
194         arp_data[13] <= BOARD_MAC[7:0];
195         arp_data[14] <= BOARD_IP[31:24];    //发送端(源)IP地址
196         arp_data[15] <= BOARD_IP[23:16];
197         arp_data[16] <= BOARD_IP[15:8];
198         arp_data[17] <= BOARD_IP[7:0];
199         arp_data[18] <= DES_MAC[47:40];     //接收端(目的)MAC地址
200         arp_data[19] <= DES_MAC[39:32];
201         arp_data[20] <= DES_MAC[31:24];
202         arp_data[21] <= DES_MAC[23:16];
203         arp_data[22] <= DES_MAC[15:8];
204         arp_data[23] <= DES_MAC[7:0];  
205         arp_data[24] <= DES_IP[31:24];      //接收端(目的)IP地址
206         arp_data[25] <= DES_IP[23:16];
207         arp_data[26] <= DES_IP[15:8];
208         arp_data[27] <= DES_IP[7:0];
以上代码在复位时对数组进行初始化。
216             st_idle : begin
217                 if(pos_tx_en) begin
218                     skip_en <= 1'b1;  
219                     //如果目标MAC地址和IP地址已经更新,则发送正确的地址
220                     if((des_mac != 48'b0) || (des_ip != 32'd0)) begin
221                         eth_head[0] <= des_mac[47:40];
222                         eth_head[1] <= des_mac[39:32];
223                         eth_head[2] <= des_mac[31:24];
224                         eth_head[3] <= des_mac[23:16];
225                         eth_head[4] <= des_mac[15:8];
226                         eth_head[5] <= des_mac[7:0];  
227                         arp_data[18] <= des_mac[47:40];
228                         arp_data[19] <= des_mac[39:32];
229                         arp_data[20] <= des_mac[31:24];
230                         arp_data[21] <= des_mac[23:16];
231                         arp_data[22] <= des_mac[15:8];
232                         arp_data[23] <= des_mac[7:0];  
233                         arp_data[24] <= des_ip[31:24];
234                         arp_data[25] <= des_ip[23:16];
235                         arp_data[26] <= des_ip[15:8];
236                         arp_data[27] <= des_ip[7:0];
237                     end
238                     if(arp_tx_type == 1'b0)
239                         arp_data[7] <= 8'h01;            //ARP请求 
240                     else 
241                         arp_data[7] <= 8'h02;            //ARP应答
242                 end    
243             end                                                                   
在程序的第220行至241行代码,根据输入的发送类型、目的MAC地址和IP地址,重新更新数组里的值。            
265             st_arp_data : begin                          //发送ARP数据                           
266                 crc_en <= 1'b1;                                                              
267                 gmii_tx_en <= 1'b1;                                                          
268                 //至少发送46个字节                                                                  
269                 if (cnt == MIN_DATA_NUM - 1'b1) begin                                        
270                     skip_en <= 1'b1;                                                         
271                     cnt <= 1'b0;                                                             
272                     data_cnt <= 1'b0;                                                        
273                 end                                                                          
274                 else                                                                         
275                     cnt <= cnt + 1'b1;                                                       
276                 if(data_cnt <= 6'd27) begin                                                   
277                     data_cnt <= data_cnt + 1'b1;                                             
278                     gmii_txd <= arp_data[data_cnt];                                          
279                 end                                                                          
280                 else                                                                         
281                     gmii_txd <= 8'd0;                    //Padding,填充0                       
282             end                                                                              
程序第265行至第282行代码为发送ARP数据的状态。我们前面讲过以太网帧格式的数据部分最少是46个字节,ARP数据只有28个字节,因此在发送完ARP数据之后补充发送18个字节,填充的数据为0283             st_crc      : begin                          //发送CRC校验值                          
284                 gmii_tx_en <= 1'b1;                                                          
285                 cnt <= cnt + 1'b1;                                                           
286                 if(cnt == 6'd0)                                                              
287                     gmii_txd <= {~crc_next[0], ~crc_next[1], ~crc_next[2],~crc_next[3],      
288                                  ~crc_next[4], ~crc_next[5], ~crc_next[6],~crc_next[7]};     
289                 else if(cnt == 6'd1)                                                         
290                     gmii_txd <= {~crc_data[16], ~crc_data[17], ~crc_data[18],                
291                                  ~crc_data[19], ~crc_data[20], ~crc_data[21],                
292                                  ~crc_data[22],~crc_data[23]};                               
293                 else if(cnt == 6'd2) begin                                                   
294                     gmii_txd <= {~crc_data[8], ~crc_data[9], ~crc_data[10],                  
295                                  ~crc_data[11],~crc_data[12], ~crc_data[13],                 
296                                  ~crc_data[14],~crc_data[15]};                               
297                 end                                                                          
298                 else if(cnt == 6'd3) begin                                                   
299                     gmii_txd <= {~crc_data[0], ~crc_data[1], ~crc_data[2],~crc_data[3],      
300                                  ~crc_data[4], ~crc_data[5], ~crc_data[6],~crc_data[7]};     
301                     tx_done_t <= 1'b1;                                                       
302                     skip_en <= 1'b1;                                                         
303                     cnt <= 1'b0;                                                             
304                 end                                                                                                                                             
305             end         

程序的第283行至305行代码为发送CRC校验值状态,发送模块的CRC校验是由crc32_d8模块完成的,发送模块将输入的crc的计算结果每4位高低位互换,按位取反发送出去,crc计算部分在后面阐述。
ARP发送过程中采集的ILA波形图如图 28.4.9所示,arp_tx_en信号作为开始发送ARP数据包的触发信号,arp_tx_type为高电平,表示发送ARP应答数据包。ARP应答数据包中的目的MAC地址和目的IP地址从ARP接收数据包中获取,gmii_tx_en拉高,表示gmii_txd数据有效,在发送完ARP数据包后,输出一个脉冲信号(tx_done),表示发送完成。
在这里插入图片描述

图 28.4.9 ARP发送采集的ILA波形图
CRC校验模块主要完成对ARP发送模块数据的校验,由于代码较长,这里不再贴出代码。CRC32校验在FPGA实现的原理是线性反馈移位寄存器,其思想是各个寄存器储存着上一次CRC32运算的结果,寄存器的输出即为CRC32的值。CRC32的原理与公式推导较复杂,只需稍作修改就可以直接使用,在此可不比深究。
ARP控制模块的代码如下:

1  module arp_ctrl(
2      input                clk        , //输入时钟   
3      input                rst_n      , //复位信号,低电平有效
4      
5      input                key        , //PL功能按键1,用于触发开发板发出ARP请求
6      input                arp_rx_done, //ARP接收完成信号
7      input                arp_rx_type, //ARP接收类型 0:请求  1:应答 
8      output  reg          arp_tx_en  , //ARP发送使能信号
9      output  reg          arp_tx_type  //ARP发送类型 0:请求  1:应答
10     );
11 
12 //reg define
13 reg         key_d0;
14 reg         key_d1;
15 
16 //wire define
17 wire        pos_key;  //key信号上升沿
18 
19 //*****************************************************
20 //**                    main code
21 //*****************************************************
22 
23 assign pos_key = ~key_d1 & key_d0;
24 
25 //对arp_tx_en信号延时打拍两次,用于采key的上升沿
26 always @(posedge clk or negedge rst_n) begin
27     if(!rst_n) begin
28         key_d0 <= 1'b0;
29         key_d1 <= 1'b0;
30     end
31     else begin
32         key_d0 <= key;
33         key_d1 <= key_d0;
34     end
35 end
36 
37 //为arp_tx_en和arp_tx_type赋值
38 always @(posedge clk or negedge rst_n) begin
39     if(!rst_n) begin
40         arp_tx_en <= 1'b0;
41         arp_tx_type <= 1'b0;
42     end
43     else begin
44         if(pos_key == 1'b1) begin    //检测到输入按键上升沿
45             arp_tx_en <= 1'b1;           
46             arp_tx_type <= 1'b0;
47         end
48         //接收到ARP请求,开始控制ARP发送模块应答
49         else if((arp_rx_done == 1'b1) && (arp_rx_type == 1'b0)) begin
50             arp_tx_en <= 1'b1;
51             arp_tx_type <= 1'b1;
52         end
53         else
54             arp_tx_en <= 1'b0;
55     end
56 end
57 
58 endmodule

ARP控制模块的代码较简单,首先检测输入PL Key1按键的上升沿,当检测到上升沿之后,触发ARP顶层模块发起ARP请求信号;同时检测输入的arp_rx_done和arp_rx_type信号,当接收上位机的ARP请求信号后,触发ARP顶层模块发送ARP应答信号,将开发板的MAC地址发送给上位机。
28.5下载验证
将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,将网线一端连接开发板的PL网口(PL_ETH),另一端连接电脑的网口,最后连接电源线后拨动开关按键给开发板上电。PL_ETH网口的位置如下图所示。
在这里插入图片描述

图 28.5.1 PL_ETH网口位置
点击Vivado左侧“Flow Navigator”窗口最下面的“Open Hardware Manager”,此时Vivado软件识别到下载器,点击“Hardware”窗口中“Program Device”下载程序,在弹出的界面中选择“Program”下载程序。
程序下载完成后,PHY芯片会和电脑网卡进行通信(自协商),如果程序下载正确并且硬件连接无误的话,我们点击电脑右下角的网络图标,会看到本地连接刚开始显示的是正在识别,一段时间之后显示未识别的网络,打开方式如下图所示(WIN7和WIN10操作可能存在差异,但基本相同)。
在这里插入图片描述

图 28.5.2 点击网络图标
点击图 27.5.3中的“未识别的网络(无Internet)”,弹出如下图所示界面。
在这里插入图片描述

图 28.5.3 网络设置界面
点击“更改适配器”选项,弹出如下图所示界面。
在这里插入图片描述

图 28.5.4 “网络适配器界面”
如果看到上图“以太网”显示未识别的网络之后,说明硬件连接和程序都是没有问题的,接下来设置以太网的IP地址,改成代码中设置的目的IP地址,顶层模块参数定义如下:
//目的IP地址 192.168.1.102
parameter DES_IP = {8’d192,8’d168,8’d1,8’d102};
因此接下来将电脑以太网的IP地址设置成192.168.1.102。鼠标右击图 27.5.5中的以太网,如下图所示:
在这里插入图片描述

图 28.5.5 鼠标右击“以太网”
点击“属性”,弹出如下图所示界面。
在这里插入图片描述

图 28.5.6 以太网“属性”界面
鼠标双击“Internet协议版本4(TCP/IPv4)”,弹出如下图所示界面。
在这里插入图片描述

图 28.5.7 设置以太网IP地址
在“Internet协议版本4(TCP/IPv4)”属性界面中,选择使用下面的IP地址,IP地址设置成192.168.1.102,并点击确定完成设置。
接下来以管理员身份打开电脑的命令的DOS命令窗口(注意必须以管理员身份打开),打开方式如下:
在这里插入图片描述

图 28.5.8 打开电脑DOS命令窗口
打开DOS命令窗口后,在命令行中输入“arp -a”,如下图所示:
在这里插入图片描述

图 28.5.9 输入命令“arp -a”
输入完成后,按下键盘的回车键,此时会弹出电脑中所有网络接口的ARP缓存表,我们只需要关注以太网接口的ARP缓存表(IP地址为192.168.1.102),如下图所示:
在这里插入图片描述

图 28.5.10 以太网接口ARP缓存表
可以发现,此时ARP缓存表中还没有开发板的MAC地址和IP地址,此时我们按下开发板的PL Key1按键。按下后,开发板会向电脑发起ARP请求,并且电脑会返回自己的MAC地址到开发板。
需要说明的是,在开发板发起ARP请求时,会将开发板的MAC地址和IP地址都发给电脑,此时电脑就已经获取到了开发板的MAC地址和IP地址,并更新至ARP的缓存表中,我们重新在DOS命令中输入“arp -a”,如图 28.5.11和图 28.5.12所示。
在这里插入图片描述

图 28.5.11 输入“arp -a”
在这里插入图片描述

图 28.5.12 开发板的MAC地址更新至缓存表中
由上图可知,此时以太网接口的ARP缓存表中已经添加了开发板的IP地址(192.168.1.10)和MAC地址(00-11-22-33-44-55),说明开发板发送ARP请求成功。如果大家操作失败,请检查开发板的PL网口(GE_PL)是否通过网线连接电脑的网口,并且此时开发板已经下载程序以及通过按下PL Key1按键来触发ARP请求。另外,如果电脑网口不支持千兆网通信,那么也会导致ARP操作失败。
接下来我们再来通过电脑发起ARP请求,验证开发板有没有正确返回ARP应答。我们先从以太网ARP缓存表中删除开发板的MAC地址,删除方法是在DOS命令中输入“arp -d”,并按下按键的回车键,如下图所示:
在这里插入图片描述

图 28.5.13 输入“arp -d”
接下来重新在DOS命令中输入“arp -a”,来验证是否删除成功(如果没有以管理员运行会导致删除失败),输入完成后,如下图所示;
在这里插入图片描述

图 28.5.14 删除开发板MAC地址
此时我们之前获取的开发板MAC地址已经删除成功,接下来在DOS命令中输入“ping 192.168.1.10”,来让电脑发起ARP请求,如下图所示。
在这里插入图片描述

图 28.5.15 电脑发起“ARP”请求
需要说明的是,ping是一个十分强大的TCP/IP工具,它可以用来检测网络的连通情况和分析网络速度。ping命令是一个固定格式的ICMP(Internet控制报文协议)请求数据包,之后会发起ARP请求命令,所以我们这里是通过ping命令来间接发起ARP请求。由于开发板并没有实现ICMP协议,因此在ping时会请求超时,但是在ping的过程中发起的ARP请求,开发板会响应并返回ARP应答数据。
接下来再次在DOS命令中输入“arp -a”,查询是否成功获取到开发板MAC地址,如下图所示:
在这里插入图片描述

图 28.5.16 开发板MAC地址获取成功
由上图可知,电脑正确获取到开发板的MAC地址,并更新至ARP缓存表中。到这里,开发板实现的ARP协议就已经全部验证成功了。
接下来介绍一个以太网通信时经常使用的抓包软件,该软件位于开发板所随附的资料“6_软件资料/1_软件/Wireshark”目录下,也可以直接在网上搜索下载,我们现在打开Wireshark,界面如下图所示:
在这里插入图片描述

图 28.5.17 wireshark打开界面
双击上图所示的以太网或者先选中以太网,再点击上方红框选中的蓝色按钮,即可开始抓取本地连接的数据包,抓取界面如下图所示:
在这里插入图片描述

图 28.5.18 wireshark以太网打开界面
从上图可以看到,已经抓取到其它应用程序使用以太网发送的数据包,但是这些数据包并不是开发板发送的数据包,我们这个时候按下开发板的PL Key1按键,就可以在wireshark中抓取到数据包了,抓取到的数据包如下图所示:
在这里插入图片描述

图 28.5.19 wireshark抓取到的数据包
上图中第38行数据包是开发板发送给电脑的ARP请求包,第39行数据包是电脑发送给开发板的ARP应答包,此时双击第38行即可看到开发板发送的详细数据,如下图所示:
在这里插入图片描述

图 28.5.20 wireshark抓取到的详细数据
上图中下方红框为开发板发送的16进制数据(去掉前导码、SFD和CRC值),可以看到,后面的18个0就是我们在发送时填充的18个字节数据。需要说明的是,当打开第39行电脑返回的ARP请求包时,看不到填充的0,这是由于后面填充的数据是网卡自动填充的,因此wireshark中会看不到。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/66242.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(十七) 共享模型之工具【JUC】【读写锁】

一、ReentrantReadWriteLock&#xff08;P247&#xff09; 当读操作远远高于写操作时&#xff0c;这时候使用 【读写锁】让 【读-读】可以并发&#xff0c;提高性能。 类似于数据库中的 select ... from ... lock in share mode 提供一个 数据容器类内部分别使用读锁保护数据的…

【论文阅读 CIKM‘2021】Learning Multiple Intent Representations for Search Queries

文章目录Original PaperMotivationMethodTask Description and Problem FormulationNMIR Framework: A High-Level OverviewModel Implementation and TrainingDataOriginal Paper Learning Multiple Intent Representations for Search Queries More related papers can be …

基于Electron的桌面端应用开发和实践

引言 如果开发跨桌面端的应用开发的话&#xff0c;我相信&#xff0c;electron目前绝对是不可避免的技术方案。web应用大家都知道&#xff0c;通过浏览器访问的应用就是web应用&#xff0c;那什么是桌面端&#xff1f;桌面端有两个重要特点&#xff1a; 具备独立运行于操作系…

学习压力容器中卡箍快开结构的强度计算

导读:压力容器的设计一定要考虑安全性、经济性、环保及健康问题。首先安全是核心问题&#xff0c;在保证安全的前提下尽可能的再做到经济合理。 本文从强度计算软件SW6-2011 V3.1补丁二&#xff08;单机版&#xff09;和&#xff08;网络版&#xff09;所解决的问题&#xff0…

Redis 性能问题优化方案

Redis性能问题&优化方案前言Redis真的变慢了吗&#xff1f;使用复杂度过高的命令操作bigkey集中过期实例内存达到上限fork耗时严重开启内存大页开启AOF绑定CPU使用Swap碎片整理网络带宽过载其他原因频繁短连接运维监控其它程序争抢资源总结前言 Redis 作为优秀的内存数据库…

Java高效率复习-MySQL上篇[MySQL]

前言 本文章是用于总结尚硅谷MySQL教学视频的记录文章&#xff0c;主要用于复习&#xff0c;非商用 原视频连接&#xff1a;https://www.bilibili.com/video/BV1iq4y1u7vj/?p21&spm_id_frompageDriver&vd_sourcec4ecde834521bad789baa9ee29af1f6c https://www.bilib…

Spring Boot 项目优化和 JVM 调优,亲测!真实有效。。

三、Jvm调优实战 1、未设置JVM参数的情况 我现在有一个项目&#xff0c;默认情况下&#xff0c;没有设置任何Jvm参数。 下面我来启动看一下。 看一下堆栈分配&#xff1a; 很明显默认的最大堆内存分配了8个G。很明显的不合理嘛。 2、下面我们来设置下Jvm参数 例如要配置JVM…

vue2 ElementUI 表单标签、表格表头添加问号图标提示

文章目录1. 问题背景2. element-ui悬浮提示定义3. 基础4. 延申5. 参考1. 问题背景 使用element-ui有时候需要对表格的表头、表单的标签进行自定义&#xff0c;添加问号的悬浮提示。 要达到的效果&#xff0c;如图所示&#xff1a; 2. element-ui悬浮提示定义 https://elemen…

【菜菜的sklearn课堂笔记】聚类算法Kmeans-基于轮廓系数来选择n_clusters

视频作者&#xff1a;菜菜TsaiTsai 链接&#xff1a;【技术干货】菜菜的机器学习sklearn【全85集】Python进阶_哔哩哔哩_bilibili 我们通常会绘制轮廓系数分布图和聚类后的数据分布图来选择我们的最佳n_clusters from sklearn.metrics import silhouette_samples,silhouette_s…

c++还原简单的vector

文章目录vectorvecotor的介绍vector的模拟实现类的框架成员变量迭代器构造函数析构函数size()capacity()operator[]重载扩容resize()尾插验证是否为空尾删clear 清除swap交换insert插入erase删除迭代器区间初始化构造函数拷贝构造赋值运算符重载n个val构造函数再谈构造函数vect…

数仓日记 - 数仓理论

寒刃尽断处&#xff0c;吾心作剑霜作锋&#x1f3c2; 目录 一、数仓简介 二、关系建模与维度建模 1. 关系建模   2. 维度建模    • 三种模型    • 事实表    • 维度表   3. 事实表的分类    • 事务型事实表    • 周期型快照事实表    • 累积型快照事实表…

Python操作Excel表格

本文介绍如何通过轻量级、零依赖&#xff08;仅使用标准库&#xff09;的 pylightxl 库操作Excel表格。 官网&#xff1a;Welcome to pylightxl documentation — pylightxl 2019 documentation 目录 一、入门 1. 读写CSV文件 2. 读Excel文件 3. 获取工作表和单元格数据 3…

前端css实现特殊日期网页变灰功能

前端变灰效果在网页实际使用过程中使用的比较少&#xff0c;但有时候又缺一不可&#xff0c;一般在大型哀悼日或纪念日的时候使用&#xff0c;使用后的网站页面会变成灰色(黑白色)。 我们先看下各大网站是怎么实现的&#xff1a; 1.csdn实现方式 2.淘宝 3.人民网 4.京东 5.掘…

Unity【Multiplayer 多人在线】服务端、客户端通用架构的使用指南

文章目录&#x1f6a9; Import&#x1f680; protogen使用方法&#x1fa90; 客户端接口&#x1f308; 服务端接口&#x1f9ed; 数据处理&#x1f3a8; Example&#x1f6a9; Import 下载SKFramework框架&#xff0c;导入到Unity中&#xff1b; 在框架Package Manager中搜索并…

osgEarth示例分析——osgearth_colorfilter

前言 osgearth_colorfilter颜色过滤器示例。本示例中&#xff0c;主要展示了6种颜色过滤器的使用&#xff0c;分别是:HSLColorFilter、RGBColorFilter、CMYKColorFilter、BrightnessContrastColorFilter、GammaColorFilter、ChromaKeyColorFilter。 执行命令 // 一条命令是一…

Docker日常运维小技巧

一、故障定位 1、查看容器内部 https 请求响应时间 docker exec -t $(docker ps -f nameblog_web -q) curl -H X-Forwarded-Proto:https \-w %{time_total} -o /dev/null -s localhost 2、查看容器日志 docker logs --tail 50 --follow --timestamps mediawiki_web_1 3、删…

深圳SMT贴片行业MES系统解决方案~MES系统服务商~先达智控

随着我国工业的迅速发展&#xff0c;所有电子行业都离不开SMT贴片生产&#xff0c;SMT贴片生产是电子行业的至关重要的一道工业环节&#xff0c;我国作为一个工业制造大国&#xff0c;有着完备的SMT现代产业体系。SMT贴片领域是我国支柱性产业其一&#xff0c;SMT贴片产品涵盖工…

【JavaWeb开发-Servlet】day01-使用TomCat实现本地web部署

目录 1、准备java web开发环境 &#xff08;1&#xff09;下载javaJDK&#xff08;推荐使用JDK1.8&#xff0c;企业常用且稳定&#xff09; &#xff08;2&#xff09;下载TomCat服务器 2、创建web服务器TomCat (1)创建一个项目文件夹 (2)在文件夹中新建一个记事本并编以下…

算法大神左程云耗尽5年心血分享程序员代码面试指南第2版文档

前言 学习是一种基础性的能力。然而&#xff0c;“吾生也有涯&#xff0c;而知也无涯。”&#xff0c;如果学习不注意方法&#xff0c;则会“以有涯随无涯&#xff0c;殆矣”。 学习就像吃饭睡觉一样&#xff0c;是人的一种本能&#xff0c;人人都有学习的能力。我们在刚出生…

移动WEB开发之rem布局--苏宁首页案例制作(技术方案1)

案例&#xff1a;苏宁网移动端首页 访问地址&#xff1a;苏宁易购(Suning.com)-家电家装成套购&#xff0c;专注服务省心购&#xff01; 1. 技术选型 方案&#xff1a;我们采取单独制作移动页面方案 技术&#xff1a;布局采取rem适配布局&#xff08;less rem 媒体查询&am…