在以太网中,一个主机和另一个主机进行通信前,首先就需要知道其目的主机的MAC地址才可以进行正常通信,而目的MAC地址的获取正是由ARP协议所实现的。
其实关于以太网的知识,在工作之前,笔者对以太网的认识仍停留在本科时期谢希仁版的“计算机网络”和408计算机统考中,时间久了脑子里只剩下网络的分层概念即应用层、表示层、会话层、传输层、网络层、数据链路层、物理层,可以说没有深入动手思考的学习,永远只会让人们停留在最表面的东西。
工作以后首先在单片机STM32开发中接触到了Lwip协议栈,并在Freertos下成功移植稳定,经过可靠性测试百兆网口的UDP、TCP协议准确不丢包并作为转产代码,接着在兴趣的驱动下,用FPGA开发板再次成功实现了ICMP、UDP、ARP协议,实际编码后很多模糊地方也慢慢清晰起来,最后因为公司产品更新换代,在ARM Linux上又通过Socket编程实现UDP、TCP协议,更一步地加深了对以太网协议的理解,所以也许真的动手实践才是最好的老师。
在这个例程和下个例程中,笔者会和大家一起去动手用FPGA时序逻辑还原ICMP、UDP、ARP协议,因为例程中涉及到的知识点相对多一些,所以笔者还是按照顺序把相关的知识也做较为详细地介绍,从而丰富大家的知识面。
如下图1所示是以太网的帧格式,以太网传输数据时按照下面的顺序从头到尾依次被发送和接收,以太网各类型的协议也均满足下图所示的帧格式,下面笔者为大家逐一解释报文帧的每段区域的具体含义。
图1 以太网的帧格式
前导码:为实现底层数据的准确传输,物理层使用7个字节同步码即0和1相互交替出现,用7个字节的0x55来实现数据的同步;
帧起始界定符:使用1个字节固定值为0xd5来表示一帧的开始,即后面紧跟着传输的即为以太网的帧头;
目的MAC地址:接收端的物理MAC地址占6个字节,MAC地址从应用上可分为单播地址、组播地址和广播地址;
源MAC地址:发送端的物理MAC地址占6个字节;
长度/类型:用来指定协议类型,常用有0x0800表示IP协议,0x0806表示ARP协议, 0x8035表示RARP协议;
数据:以太网中的数据段长度最小46字节,不足需要补全46字节,最大1500 字节;
帧检验序列:为了确保数据的正确传输,在数据的报文尾部都加入了4个字节的循环冗余校验码即CRC校验来检测数据是否传输错误,CRC数据校验从以太网帧头处开始计算即不包含前导码和帧起始界定符。
ARP即地址解析协议是根据IP地址获取MAC地址的一种TCP/IP协议,在以太网通信中,数据是以“帧”的格式进行传输的,帧的报文格式中包含了目的主机的MAC地址,所以很显然在通信前,发送端需要先获得目的主机的MAC地址,ARP协议正是为了解决这个问题,即可以实现通过目的主机的IP地址来查询目的主机的MAC地址的功能,从而确保以太网通信的顺利进行。
MAC地址在网络中表示网卡的ID号码,每个网卡都需要有且只有一个的MAC地址。在获取到目的MAC地址后,将目的MAC地址更新至ARP缓存表中,该技术被称为ARP映射,下次通信时可以直接从ARP缓存表中获取目的主机的MAC地址,而不用重新再通过ARP获取,但一般来说ARP缓存表会有过期时间,过期后就需要重新通过ARP协议进行获取。
ARP映射是指将IP地址和MAC地址映射起来,分为静态映射和动态映射。静态映射指手动创建一张ARP表,把IP地址和MAC地址关联起来。手动绑定之后,源主机在通信前,就可以直接从ARP表中直接找到IP地址对应的MAC地址,但这样做有显然有很繁琐且一定的局限性,而动态映射指使用协议来获取对应的MAC地址,同时这个过程是自动完成的,一般应用程序的用户也不必关心,相比静态映射,动态映射具有更高地稳定性和可靠性。
ARP协议分为ARP请求和ARP应答,源主机发起查询目的MAC地址的报文称为ARP 请求,目的主机响应源主机并发送包含本地MAC地址的报文称为ARP应答。
当主机需要找出这个网络中的另一个主机的物理地址时,它就可以发送一个ARP请求报文,这个报文中包含了发送方的MAC地址和IP地址、接收方的IP地址。又因为发送方事先不知道接收方的MAC地址,所以这个查询分组会在网络层中进行广播,即ARP请求时发送的接收方物理地址为广播地址,用48'hff_ff_ff_ff_ff_ff表示,如图5-32所示是ARP协议请求示意图。
如下图所示主机A发起ARP请求,因为发送的目的MAC地址是广播地址,所以此时局域网中的所有主机都会进行接收并处理这个ARP请求报文,然后进行确认查看请求报文中目的IP地址是不是自己的地址,如果是则返回ARP应答报文,如果不是则不做任何的响应。
图2 ARP协议请求示意图
如图3所示是ARP协议应答示意图,主机B利用收到的ARP请求报文中的请求方MAC地址,以单播的方式直接发送给主机A,然后主机A将收到的ARP应答报文中的目的MAC地址解析出来,将目的MAC地址和目的 IP地址更新至ARP缓存表中。当再次和主机A通信时,可以直接从ARP缓存表中获取,而不用重新发起ARP请求报文。
需要注意的是ARP缓存表中的表项有过期时间,当然过期后,需要重新发起ARP请求以获取目的MAC地址,从而实现ARP动态映射的平衡。
图3 ARP协议应答示意图
如图4所示是以太网ARP报文格式,其实大家对比图5-31中的以太网的帧格式示意图,从图中可以看出,以太网的数据包就是对协议的封装来实现数据的传输,即ARP数据位于以太网帧格式的数据段,例如下面2个ARP报文:
1. ff ff ff ff ff ff_00 0a 35 01 fe c0_08 06_00 01_08 00_06_04_00 01_00 0a 35 01 fe c0_c0 a8 00 02_ff ff ff ff ff ff_c0 a8 00 03 表示向192.168.0.3地址发送ARP请求。
2. 00 0a 35 01 fe c0_60 ab c1 a2 d5 15_08 06_00 01_08 00_06_04_00 02_60 ab c1 a2 d5 15_c0 a8 00 03_00 0a 35 01 fe c0_c0 a8 00 02 表示向192.168.0.2地址发送ARP应答。
图4 以太网ARP报文格式
如下图5所示是以太网ARP数据报格式,简单地介绍下ARP数据报格式各个字段的含义。
图5 ARP数据报格式
硬件类型:硬件地址的类型,其中1表示以太网地址;
协议类型:要映射的协议地址类型,ARP协议的上层协议为IP协议,所以该协议类型为IP协议值为0x0800;
硬件地址长度:对于以太网上IP地址的ARP请求或者应答来说值为6;
协议地址长度:对于以太网上IP地址的ARP请求或者应答来说值为4;
OP:操作码用于表示该数据报为ARP请求或者ARP应答,其中1表示ARP请求,2表示ARP应答;
源MAC地址:发送端的MAC地址;
源IP地址:发送端的IP地址;
目的MAC地址:接收端的MAC地址,在发送ARP请求报文时这个字段变成广播地址,即48'h ff_ff_ff_ff_ff_ff;
目的IP地址:接收端的IP地址。
图6 RGMII接口示意图
如图6所示是RGMII接口示意图,显然以太网通信离不开物理层PHY芯片的支持,以太网MAC和PHY间有一个接口,常用的接口有MII、RMII、GMII、RGMII等。
MII支持10Mbps和100Mbps 的操作,数据位宽是4位,在100Mbps传输速率下,时钟频率是25Mhz;
RMII是MII的简化版,数据位宽是2位,100Mbps传输速率下,时钟频率是50Mhz;
GMII接口向下兼容MII接口,支持10Mbps、100Mbps和1000Mbps的操作,数据位宽是8位,在1000Mbps传输速率下,时钟频率是125Mhz;
同样的RGMII是GMII的简化版,数据位宽是4位,在1000Mbps传输速率下,时钟频率是125Mhz,且在时钟的上下沿都采样数据,而在100Mbps和10Mbps通信速率下,只是单个时钟沿采样。
MII和RMII接口多用在STM32的百兆以太网开发上,而在千兆以太网中,常用的接口则是RGMII和GMII。豌豆开发板这里选择KSZ9031RN芯片下的RGMII接口,该接口同时适用于10M/100M/1000Mbps通信速率,且占用的引脚数更少,如图7所示是MAC侧与PHY侧接口连接示意图。
图7 MAC侧与PHY侧接口连接示意图
ETH_RX_CLK:接收数据参考时钟,1000Mbp速率下,时钟频率125MHz,时钟为上下沿同时采样;100Mbps速率下,时钟频率25MHz;10Mbps速率下,时钟频率2.5MHz,ETH_RX_CLK由PHY芯片提供;
ETH_RX_DV:接收数据的控制信号;
ETH_RXD:四位并行的接收数据线;
ETH_TX_CLK:发送数据参考时钟,1000Mbp速率下,时钟频率125MHz,时钟为上下沿同时采样;100Mbps速率下,时钟频率25MHz;10Mbps速率下,时钟频率2.5MHz,ETH_TX_CLK由MAC控制器提供;
ETH_TX_EN:发送数据的控制信号;
ETH_TXD:四位并行的发送数据线;
ETH_RESET_N:PHY芯片复位信号,低电平有效;
ETH_MDC:数据管理时钟,该引脚对ETH_MDIO信号提供了一个同步的时钟;ETH_MDIO:数据输入和输出管理,该引脚提供了一个双向信号用于传递管理信息。
在这里ETH_RX_CLK、ETH_RX_DV和ETH_RXD为MAC接收端引脚;ETH_TX_CLK、ETH_TX_EN和ETH_TXD为MAC发送端引脚;ETH_MDC和ETH_MDIO为MDIO接口引脚,用于配置 PHY芯片内部寄存器;ETH_RST_N为PHY芯片硬件复位信号。
在这个例程中,我们设计了如下功能:上电以后PHY芯片复位1秒钟后,就开始正常工作,每隔2秒钟通过MDIO总线和PHY芯片进行一次自协商操作读取相关寄存器,判断当前是否在千兆网速下自协商正确,如果正确则用板载指示灯标识,当按下豌豆开发板的按键则向PC端发送一次ARP请求即读取PC端的MAC地址,同时如果发现PC端的ARP请求则回复ARP应答。
RGMII使用了4位数据接口,在1000Mbps通信速率下,ETH_RX_CLK和ETH_TX_CLK的时钟频率均为125Mhz,采用上下沿DDR的方式在一个时钟周期内传输8位数据信号,即上升沿发送或者接收低4位数据,上升沿发送或者接收高4位数据。
同样的ETH_TX_EN和ETH_RX_DV控制信号也采用DDR的方式在一个时钟周期内传输两位控制信号,即上升沿发送或者接收数据使能信号,下降沿发送或者接收使能信号与错误信号的异或值(TX_ERR xor TX_EN、RX_ERR xor RX_DV)。当RX_DV为高电平则表示数据有效,RX_ERR为低电平则表示数据无错误,所以只有当ETH_RX_DV和ETH_TX_EN信号的上下沿同时为高电平时,发送和接收的数据有效且正确。
当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的传输方式。
如图8所示,是PHY芯片KSZ9031RNX在RGMII接口下的通信时序示意图,可以看到在RGMII 2.0版本的协议规范下,时序上满足ETH_TX_CLK的上下边沿与ETH_TXD和ETH_TX_EN信号对齐相位相同;ETH_RX_CLK的上下边沿与ETH_RXD和ETH_RX_DV信号对齐相位相同,如图9所示是KSZ9031RNX在RGMII接口下的保持建立时间要求。
图8 KSZ9031RNX在RGMII接口下的通信时序示意图
图9 KSZ9031RNX在RGMII接口下的保持建立时间要求
为了实现正确接收数据以及解析数据,需要将这种双沿采集4位数据的端口转换成常规的单沿采集8位数据的端口,即实现从RGMII接口到GMII接口的实时转换。一般性的有两种方法可以实现,即调用XILINX原语或者采用SelectIO IP核,原语是XILINX器件底层硬件中的功能模块,使用专用的资源来实现一系列的功能。相比IP核,原语的调用方法更加直接,但是一般只能实现基础的功能,因为原语调用需要再学习一些底层的知识和特殊的语法,所以为了上手应用更加方便且适用范围更加广泛,笔者推荐大家用SelectIO IP核去实现RGMII接口到GMII接口的转换,该IP核简单实用且应用广阔,如图10所示,大家可以打开Vivado,并搜索SelectIO IP核。
图10 在Vivado的搜索栏中搜索SelectIO IP核
如图11所示是SelectIO IP核系统时钟配置界面,一些关键性参数说明如下:
Interface Template: 接口模板中包括了常用DDR和SDR相互转换的接口类型,很多情况下可以选择Custom自定义,用来配置模板外的接口类型。
Data Bus Direction: 数据总线方向,这里可以根据实际需求配置成输入、输出、输入输出双向。
Data Rate: 数据传输速率,这里有DDR(上下沿数据采样传输)和SDR(单边沿数据采样传输)两个选择。
Serialization Factor: 序列化因子,选择此选项可实例化输出serdes元素,可以从结构逻辑转换数据位宽,一般情况下默认不选择。
External Data Width: 外部数据位宽,当选择Data Bus Direction是输出Output时,则代表data_out_to_pins输出的数据位宽; 当选择Data Bus Direction是输出Input时,则代表data_in_from_pins输入的数据位宽。
I/O Signaling:IO信号的类型和电平标准设置,其中类型有Single-ended和Differential即单线和差分两种,电平包括LVCMOS、SSTL等常见类型。
Input DDR Data Ailgnment:DDR输入数据队列类型,一共包括三种采集模式,分别为OPPOSITE_EDGE、SAME_EDGE和SAME_EDGE_PIPELINED模式。
Output DDR Data Ailgnment:DDR输出数据队列类型,一共包括两种输出模式,分别为OPPOSITE_EDGE和SAME_EDGE模式。
图11 SelectIO IP核的数据总线设置
关于Input DDR Data Ailgnment和Output DDR Data Ailgnment下的采集和输出模式,在这里详细地为大家说明一下,加深对SelectIO IP核配置的理解。
对于DDR输入数据队列模式,如图12所示在OPPOSITE_EDGE模式下,在时钟的上升沿输出的Q1,时钟的下降沿输出Q2;如图13所示在SAME_EDGE模式下,在时钟的上升沿输出Q1和Q2,但Q1 和Q2不在同一个时钟周期输出;如图14所示,在SAME_EDGE_PIPELINED模式下,在时钟的上升沿输出Q1和Q2,Q1和Q2虽然在同一个时钟周期输出 但整体延时了一个时钟周期,所以一般的采用这种模式配置DDR输入数据队列。
图12 DDR输入数据队列OPPOSITE_EDGE模式时序图
图13 DDR输入数据队列SAME_EDGE模式时序图
图14 DDR输入数据队列SAME_EDGE_PIPELINED模式时序图
对于DDR输出数据队列模式,如图15所示在OPPOSITE_EDGE模式下,为了实现这种模式,需要在FPGA内部用两个反相时钟来同步D1和D2,因此这种模式使用较少;如图16所示在SAME_EDGE模式下,数据可以在相同的时钟边沿输出到Q,所以一般的采用这种模式。
图15 DDR输出数据队列OPPOSITE_EDGE模式时序图
图16 DDR输出数据队列SAME_EDGE模式时序图
如图17所示是SelectIO IP核的时钟类型设置界面,核心的参数说明如下:
Clock Signaling: 类似于上一界面普通IO信号的类型和电平标准设置,时钟信号的类型也有Single-ended和Differential即单线和差分两种,电平包括LVCMOS、SSTL等常见类型。
Clocking Options:这里有内部时钟和外部时钟两个选项。
Clock Forwarding:时钟转发,当选择Data Bus Direction为Output时有效,默认不做选择,选择Clock Forwarding时会多出clk_reset和clk_to_pins信号。
IDDR Reset Type:IDDR复位类型,当选择Data Bus Direction为Input时有效,有同步和异步两个选择,默认选择异步,选择同步时会多出一个sync_reset信号。
图17 SelectIO IP核的时钟类型设置
如图18所示是SelectIO IP核的数据和时钟偏移设置界面,主要为了满足不同需求下的时钟延迟设计,感兴趣的同学可以参考官方手册pg070-selectio-wiz查看更多细节上的说明。
图18 SelectIO IP核的数据和时钟偏移设置
如表1所示是rgmii_to_gmii模块信号列表,要在这个模块中去实现RGMII接口和GMII接口之间的相互转换,因为模块中有4组信号要进行DDR到SDR之间的转换,即gmii_txd和rgmii_txd,rgmii_rxd和gmii_rxd,gmii_tx_en和rgmii_tx_en,rgmii_rx_dv和gmii_rx_dv,所以调用了4个SelectIO IP核,如图19所示是RGMII接口和GMII接口转换的代码设计。
信号列表 | ||
信号名 | I/O | 位宽 |
rgmii_rx_clk | I | 1 |
rst_n | I | 1 |
gmii_txd | I | 8 |
gmii_tx_en | I | 1 |
rgmii_rxd | I | 4 |
rgmii_rx_dv | I | 1 |
gmii_tx_clk | O | 1 |
gmii_rx_clk | O | 1 |
gmii_rxd | O | 8 |
gmii_rx_dv | O | 1 |
rgmii_tx_clk | O | 1 |
rgmii_txd | O | 4 |
rgmii_tx_en | O | 1 |
表1 rgmii_to_gmii模块信号列表
图19 RGMII接口和GMII接口转换的代码设计