一、实验任务
本节的实验任务是使用FPGA开发板上的以太网接口,和上位机实现 ARP请求和应答的功能。当上位机发送ARP请求时,开发板返回ARP应答数据。当按下开发板的触摸按键时,开发板发送ARP请求,此时上位机返回应答数据。
二、ARP简介
(一下简介来自正点原子达芬奇之FPGA开发指南)
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 地址映射起来,分为静态映射和动态映射
1、以太网帧格式
以太网技术的正式标准是 IEEE 802.3,它规定了以太网传输数据的帧结构,我们可以把以太网 MAC 层理解成高速公路, 我们必须遵循它的规则才能在上面通行, 以太网 MAC 层帧格式如图
前导码(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 最小时间为: 96*100ns = 9600ns;
100Mbit/s 最小时间为: 96*10ns = 960ns;
1000Mbit/s 最小时间为: 96*1ns = 96ns。
2、ARP 协议
(1)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。
(2)以太网传输 ARP 报文的格式
28 字节的 ARP 数据位于以太网帧格式的数据段。由于以太网数据段最少为 46 个字节,而 ARP 数据包总长度为 28 个字节,因此在 ARP 数据段后面需要填充 18 个字节的数据,以满足以太网传输格式的要求。这个填充的过程称为 Padding(填充),填充的数据可以为任意值,但一般为 0。
三、代码讲解
1、gmii_to_rgmii模块
RGMII to GMII接口时序如下图:
(1)rgmii_rx部分
module rgmii_rx(
input idelay_clk , //200Mhz时钟,IDELAY时钟
//以太网RGMII接口
input rgmii_rxc , //RGMII接收时钟
input rgmii_rx_ctl, //RGMII接收数据控制信号
input [3:0] rgmii_rxd , //RGMII接收数据
//以太网GMII接口
output gmii_rx_clk , //GMII接收时钟
output gmii_rx_dv , //GMII接收数据有效信号
output [7:0] gmii_rxd //GMII接收数据
);
//parameter define
parameter IDELAY_VALUE = 0;
//wire define
wire rgmii_rxc_bufg; //全局时钟缓存
wire rgmii_rxc_bufio; //全局时钟IO缓存
wire [3:0] rgmii_rxd_delay; //rgmii_rxd输入延时
wire rgmii_rx_ctl_delay; //rgmii_rx_ctl输入延时
wire [1:0] gmii_rxdv_t; //两位GMII接收有效信号
//*****************************************************
//** main code
//*****************************************************
assign gmii_rx_clk = rgmii_rxc_bufg;
assign gmii_rx_dv = gmii_rxdv_t[0] & gmii_rxdv_t[1];
//全局时钟缓存
BUFG BUFG_inst (
.I (rgmii_rxc), // 1-bit input: Clock input
.O (rgmii_rxc_bufg) // 1-bit output: Clock output
);
//全局时钟IO缓存
BUFIO BUFIO_inst (
.I (rgmii_rxc), // 1-bit input: Clock input
.O (rgmii_rxc_bufio) // 1-bit output: Clock output
);
//输入延时控制
// Specifies group name for associated IDELAYs/ODELAYs and IDELAYCTRL
(* IODELAY_GROUP = "rgmii_rx_delay" *)
IDELAYCTRL IDELAYCTRL_inst (
.RDY(), // 1-bit output: Ready output
.REFCLK(idelay_clk), // 1-bit input: Reference clock input
.RST(1'b0) // 1-bit input: Active high reset input
);
//rgmii_rx_ctl输入延时与双沿采样
(* IODELAY_GROUP = "rgmii_rx_delay" *)
IDELAYE2 #(
.IDELAY_TYPE ("FIXED"), // FIXED, VARIABLE, VAR_LOAD, VAR_LOAD_PIPE
.IDELAY_VALUE (IDELAY_VALUE), // Input delay tap setting (0-31)
.REFCLK_FREQUENCY(200.0) // IDELAYCTRL clock input frequency in MHz
)
u_delay_rx_ctrl (
.CNTVALUEOUT (), // 5-bit output: Counter value output
.DATAOUT (rgmii_rx_ctl_delay),// 1-bit output: Delayed data output
.C (1'b0), // 1-bit input: Clock input
.CE (1'b0), // 1-bit input: enable increment/decrement
.CINVCTRL (1'b0), // 1-bit input: Dynamic clock inversion input
.CNTVALUEIN (5'b0), // 5-bit input: Counter value input
.DATAIN (1'b0), // 1-bit input: Internal delay data input
.IDATAIN (rgmii_rx_ctl), // 1-bit input: Data input from the I/O
.INC (1'b0), // 1-bit input: Increment / Decrement tap delay
.LD (1'b0), // 1-bit input: Load IDELAY_VALUE input
.LDPIPEEN (1'b0), // 1-bit input: Enable PIPELINE register
.REGRST (1'b0) // 1-bit input: Active-high reset tap-delay input
);
//输入双沿采样寄存器
IDDR #(
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED"),// "OPPOSITE_EDGE", "SAME_EDGE"
// or "SAME_EDGE_PIPELINED"
.INIT_Q1 (1'b0), // Initial value of Q1: 1'b0 or 1'b1
.INIT_Q2 (1'b0), // Initial value of Q2: 1'b0 or 1'b1
.SRTYPE ("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
) u_iddr_rx_ctl (
.Q1 (gmii_rxdv_t[0]), // 1-bit output for positive edge of clock
.Q2 (gmii_rxdv_t[1]), // 1-bit output for negative edge of clock
.C (rgmii_rxc_bufio), // 1-bit clock input
.CE (1'b1), // 1-bit clock enable input
.D (rgmii_rx_ctl_delay), // 1-bit DDR data input
.R (1'b0), // 1-bit reset
.S (1'b0) // 1-bit set
);
//rgmii_rxd输入延时与双沿采样
genvar i;
generate for (i=0; i<4; i=i+1)
(* IODELAY_GROUP = "rgmii_rx_delay" *)
begin : rxdata_bus
//输入延时
(* IODELAY_GROUP = "rgmii_rx_delay" *)
IDELAYE2 #(
.IDELAY_TYPE ("FIXED"), // FIXED,VARIABLE,VAR_LOAD,VAR_LOAD_PIPE
.IDELAY_VALUE (IDELAY_VALUE), // Input delay tap setting (0-31)
.REFCLK_FREQUENCY(200.0) // IDELAYCTRL clock input frequency in MHz
)
u_delay_rxd (
.CNTVALUEOUT (), // 5-bit output: Counter value output
.DATAOUT (rgmii_rxd_delay[i]),// 1-bit output: Delayed data output
.C (1'b0), // 1-bit input: Clock input
.CE (1'b0), // 1-bit input: enable increment/decrement
.CINVCTRL (1'b0), // 1-bit input: Dynamic clock inversion
.CNTVALUEIN (5'b0), // 5-bit input: Counter value input
.DATAIN (1'b0), // 1-bit input: Internal delay data input
.IDATAIN (rgmii_rxd[i]), // 1-bit input: Data input from the I/O
.INC (1'b0), // 1-bit input: Inc/Decrement tap delay
.LD (1'b0), // 1-bit input: Load IDELAY_VALUE input
.LDPIPEEN (1'b0), // 1-bit input: Enable PIPELINE register
.REGRST (1'b0) // 1-bit input: Active-high reset tap-delay
);
//输入双沿采样寄存器
IDDR #(
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED"),// "OPPOSITE_EDGE", "SAME_EDGE"
// or "SAME_EDGE_PIPELINED"
.INIT_Q1 (1'b0), // Initial value of Q1: 1'b0 or 1'b1
.INIT_Q2 (1'b0), // Initial value of Q2: 1'b0 or 1'b1