ARP测定实验——FPGA学习笔记5

一、实验任务

        本节的实验任务是使用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
            .SRTYPE   ("SYNC")                  // Set/Reset type: "SYNC" or "ASYNC" 
        ) u_iddr_rxd (
            .Q1       (gmii_rxd[i]),            // 1-bit output for positive edge of clock
            .Q2       (gmii_rxd[4+i]),          // 1-bit output for negative edge of clock
            .C        (rgmii_rxc_bufio),        // 1-bit clock input rgmii_rxc_bufio
            .CE       (1'b1),                   // 1-bit clock enable input
            .D        (rgmii_rxd_delay[i]),     // 1-bit DDR data input
            .R        (1'b0),                   // 1-bit reset
            .S        (1'b0)                    // 1-bit set
        );
    end
endgenerate

endmodule
                        ① 全局时钟原语调用

BUFG输出信号直接给到gmii_rx_clk(GMII接收时钟)

BUFIO输出信号rgmii_rxc_bufio(全局时钟IO缓存)后续进入IDDR做输入时钟

gmii_rxdv_t[1]实际上是由rx_dv ^ rx_er得到的,如果rx_dv为1,rx_er为0,gmii_rxdv_t[1]即为1,gmii_rxdv_t[0]为rx_dv为1,所以gmii_rx_dv为1表示数据有效。如果rx_dv为1,rx_er为1,gmii_rxdv_t[1]即为0,gmii_rxdv_t[0]为rx_dv为1,所以gmii_rx_dv为0表示有数据无效;

                        ②IDELAYCTRL原语调用(必须联合IDELAYE使用) 

        IDELAYCTRL与IDELAYE2分组相同均为rgmii_rx_delay(表明在同一时钟区域)。IDELAYCTRL参考时钟为200MHz,对应延时为78ps。

                                1) rgmii_rx_ctl输入延时与双沿采

IDELAYE2模式配置为FIXED(固定模式),虽然给的IDELAY_VALUE(延时参数)为0,但是输入相对输出还是有600ps延迟,将rgmii_rx_ctl(RGMII接收数据控制信号)(因为是从IO接入,所以要接入IDATAIN,DATAIN引脚为逻辑接入)接入,输出rgmii_rx_ctl_delay给到IDDR进行双沿采样。

                        ①IDDR原语调用   

模式为SAME_EDGE_PIPELINED(相同沿流水模式),初值均为0,采用同步复位。

                2)rgmii_rxd输入延时与双沿采

IDELAYE2模式配置为FIXED(固定模式),虽然给的IDELAY_VALUE(延时参数)为0,但是输入相对输出还是有600ps延迟,将rgmii_rxd[i](RGMII接收数据)(因为是从IO接入,所以要接入IDATAIN,DATAIN引脚为逻辑接入)接入,输出rgmii_rxd_delay[i]给到IDDR进行双沿采样。

                        ①IDDR原语调用

 模式为SAME_EDGE_PIPELINED(相同沿流水模式),初值均为0,采用同步复位。

                (2)rgmii_tx部分

module rgmii_tx(
    //GMII发送端口
    input              gmii_tx_clk , //GMII发送时钟    
    input              gmii_tx_en  , //GMII输出数据有效信号
    input       [7:0]  gmii_txd    , //GMII输出数据        
    
    //RGMII发送端口
    output             rgmii_txc   , //RGMII发送数据时钟    
    output             rgmii_tx_ctl, //RGMII输出数据有效信号
    output      [3:0]  rgmii_txd     //RGMII输出数据     
    );

//*****************************************************
//**                    main code
//*****************************************************

assign rgmii_txc = gmii_tx_clk;

//输出双沿采样寄存器 (rgmii_tx_ctl)
ODDR #(
    .DDR_CLK_EDGE  ("SAME_EDGE"),  // "OPPOSITE_EDGE" or "SAME_EDGE" 
    .INIT          (1'b0),         // Initial value of Q: 1'b0 or 1'b1
    .SRTYPE        ("SYNC")        // Set/Reset type: "SYNC" or "ASYNC" 
) ODDR_inst (
    .Q             (rgmii_tx_ctl), // 1-bit DDR output
    .C             (gmii_tx_clk),  // 1-bit clock input
    .CE            (1'b1),         // 1-bit clock enable input
    .D1            (gmii_tx_en),   // 1-bit data input (positive edge)
    .D2            (gmii_tx_en),   // 1-bit data input (negative edge)
    .R             (1'b0),         // 1-bit reset
    .S             (1'b0)          // 1-bit set
); 

genvar i;
generate for (i=0; i<4; i=i+1)
    begin : txdata_bus
        //输出双沿采样寄存器 (rgmii_txd)
        ODDR #(
            .DDR_CLK_EDGE  ("SAME_EDGE"),  // "OPPOSITE_EDGE" or "SAME_EDGE" 
            .INIT          (1'b0),         // Initial value of Q: 1'b0 or 1'b1
            .SRTYPE        ("SYNC")        // Set/Reset type: "SYNC" or "ASYNC" 
        ) ODDR_inst (
            .Q             (rgmii_txd[i]), // 1-bit DDR output
            .C             (gmii_tx_clk),  // 1-bit clock input
            .CE            (1'b1),         // 1-bit clock enable input
            .D1            (gmii_txd[i]),  // 1-bit data input (positive edge)
            .D2            (gmii_txd[4+i]),// 1-bit data input (negative edge)
            .R             (1'b0),         // 1-bit reset
            .S             (1'b0)          // 1-bit set
        );        
    end
endgenerate

endmodule

gmii_tx_clk因为PHY芯片已经有2ns延迟,所以直接给到rgmii_txc。如果没有PHY芯片的物理延迟,要取反后给到rgmii_txc。

                 1)rgmii_tx_ctl输出双沿采样寄存器
                        ①ODDR原语调用

        采用相同沿模式,初值Q为0,D1,D2均为gmii_tx_en,所有数据有效。

                2) rgmii_txd输出双沿采样寄存器

        采用相同沿模式,初值Q为0,D1上升沿给[3:0],下降沿给[7:4],进行双沿采样。

                (3)gmii_to_rgmii顶层

module gmii_to_rgmii(
    input              idelay_clk  , //IDELAY时钟
    //以太网GMII接口
    output             gmii_rx_clk , //GMII接收时钟
    output             gmii_rx_dv  , //GMII接收数据有效信号
    output      [7:0]  gmii_rxd    , //GMII接收数据 
    output             gmii_tx_clk , //GMII发送时钟
    input              gmii_tx_en  , //GMII发送数据使能信号
    input       [7:0]  gmii_txd    , //GMII发送数据            
    //以太网RGMII接口   
    input              rgmii_rxc   , //RGMII接收时钟
    input              rgmii_rx_ctl, //RGMII接收数据控制信号
    input       [3:0]  rgmii_rxd   , //RGMII接收数据
    output             rgmii_txc   , //RGMII发送时钟    
    output             rgmii_tx_ctl, //RGMII发送数据控制信号
    output      [3:0]  rgmii_txd     //RGMII发送数据          
    );

//parameter define
parameter IDELAY_VALUE = 0;  //输入数据IO延时(如果为n,表示延时n*78ps) 

//*****************************************************
//**                    main code
//*****************************************************

assign gmii_tx_clk = gmii_rx_clk;

//RGMII接收
rgmii_rx 
    #(
     .IDELAY_VALUE  (IDELAY_VALUE)
     )
    u_rgmii_rx(
    .idelay_clk    (idelay_clk),
    .gmii_rx_clk   (gmii_rx_clk),
    .rgmii_rxc     (rgmii_rxc   ),
    .rgmii_rx_ctl  (rgmii_rx_ctl),
    .rgmii_rxd     (rgmii_rxd   ),
    
    .gmii_rx_dv    (gmii_rx_dv ),
    .gmii_rxd      (gmii_rxd   )
    );

//RGMII发送
rgmii_tx u_rgmii_tx(
    .gmii_tx_clk   (gmii_tx_clk ),
    .gmii_tx_en    (gmii_tx_en  ),
    .gmii_txd      (gmii_txd    ),
              
    .rgmii_txc     (rgmii_txc   ),
    .rgmii_tx_ctl  (rgmii_tx_ctl),
    .rgmii_txd     (rgmii_txd   )
    );

endmodule

 PHY芯片已有2ns偏移延迟,此处无须延时。

        2、ARP模块

         arp时序如下图

                (1)arp_rx模块

arp_rx时序如下 状态跳转图

st_idle:初始状态,等待接收前导码
st_preamble:接收前导码状态 
st_eth_head:接收以太网帧头
st_arp_datd:接收ARP数据
st_rx_end:接收结束

module arp_rx
  #(
    //开发板MAC地址 00-11-22-33-44-55
    parameter BOARD_MAC = 48'h00_11_22_33_44_55,  
    //开发板IP地址 192.168.1.10   
    parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd10}      
    )
   (
    input                clk        , //时钟信号
    input                rst_n      , //复位信号,低电平有效
                                    
    input                gmii_rx_dv , //GMII输入数据有效信号
    input        [7:0]   gmii_rxd   , //GMII输入数据
    output  reg          arp_rx_done, //ARP接收完成信号
    output  reg          arp_rx_type, //ARP接收类型 0:请求  1:应答
    output  reg  [47:0]  src_mac    , //接收到的源MAC地址
    output  reg  [31:0]  src_ip       //接收到的源IP地址
    );

//parameter define
localparam  st_idle     = 5'b0_0001; //初始状态,等待接收前导码
localparam  st_preamble = 5'b0_0010; //接收前导码状态 
localparam  st_eth_head = 5'b0_0100; //接收以太网帧头
localparam  st_arp_data = 5'b0_1000; //接收ARP数据
localparam  st_rx_end   = 5'b1_0000; //接收结束

localparam  ETH_TPYE = 16'h0806;     //以太网帧类型 ARP

//reg define
reg    [4:0]   cur_state ;
reg    [4:0]   next_state;
                         
reg            skip_en   ; //控制状态跳转使能信号
reg            error_en  ; //解析错误使能信号
reg    [4:0]   cnt       ; //解析数据计数器
reg    [47:0]  des_mac_t ; //接收到的目的MAC地址
reg    [31:0]  des_ip_t  ; //接收到的目的IP地址
reg    [47:0]  src_mac_t ; //接收到的源MAC地址
reg    [31:0]  src_ip_t  ; //接收到的源IP地址
reg    [15:0]  eth_type  ; //以太网类型
reg    [15:0]  op_data   ; //操作码

//*****************************************************
//**                    main code
//*****************************************************

//(三段式状态机)同步时序描述状态转移
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cur_state <= st_idle;  
    else
        cur_state <= next_state;
end

//组合逻辑判断状态转移条件
always @(*) begin
    next_state = st_idle;
    case(cur_state)
        st_idle : begin                     //等待接收前导码
            if(skip_en) 
                next_state = st_preamble;
            else
                next_state = st_idle;    
        end
        st_preamble : begin                 //接收前导码
            if(skip_en) 
                next_state = st_eth_head;
            else if(error_en) 
                next_state = st_rx_end;    
            else
                next_state = st_preamble;   
        end
        st_eth_head : begin                 //接收以太网帧头
            if(skip_en) 
                next_state = st_arp_data;
            else if(error_en) 
                next_state = st_rx_end;
            else
                next_state = st_eth_head;   
        end  
        st_arp_data : begin                  //接收ARP数据
            if(skip_en)
                next_state = st_rx_end;
            else if(error_en)
                next_state = st_rx_end;
            else
                next_state = st_arp_data;   
        end                  
        st_rx_end : begin                   //接收结束
            if(skip_en)
                next_state = st_idle;
            else
                next_state = st_rx_end;          
        end
        default : next_state = st_idle;
    endcase                                          
end    

//时序电路描述状态输出,解析以太网数据
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        skip_en <= 1'b0;
        error_en <= 1'b0;
        cnt <= 5'd0;
        des_mac_t <= 48'd0;
        des_ip_t <= 32'd0;
        src_mac_t <= 48'd0;
        src_ip_t <= 32'd0;        
        eth_type <= 16'd0;
        op_data <= 16'd0;
        arp_rx_done <= 1'b0;
        arp_rx_type <= 1'b0;
        src_mac <= 48'd0;
        src_ip <= 32'd0;
    end
    else begin
        skip_en <= 1'b0;
        error_en <= 1'b0;  
        arp_rx_done <= 1'b0;
        case(next_state)
            st_idle : begin                                  //检测到第一个8'h5
                if((gmii_rx_dv == 1'b1) && (gmii_rxd == 8'h55)) 
                    skip_en <= 1'b1;
            end
            st_preamble : begin
                if(gmii_rx_dv) begin                         //解析前导码
                    cnt <= cnt + 5'd1;
                    if((cnt < 5'd6) && (gmii_rxd != 8'h55))  //7个8'h55  
                        error_en <= 1'b1;
                    else if(cnt==5'd6) begin
                        cnt <= 5'd0;
                        if(gmii_rxd==8'hd5)                  //1个8'hd5
                            skip_en <= 1'b1;
                        else
                            error_en <= 1'b1;    
                    end  
                end  
            end
            st_eth_head : begin
                if(gmii_rx_dv) begin
                    cnt <= cnt + 5'b1;
                    if(cnt < 5'd6) 
                        des_mac_t <= {des_mac_t[39:0],gmii_rxd};
                    else if(cnt == 5'd6) begin
                        //判断MAC地址是否为开发板MAC地址或者公共地址
                        if((des_mac_t != BOARD_MAC)
                            && (des_mac_t != 48'hff_ff_ff_ff_ff_ff))           
                            error_en <= 1'b1;
                    end
                    else if(cnt == 5'd12) 
                        eth_type[15:8] <= gmii_rxd;          //以太网协议类型
                    else if(cnt == 5'd13) begin
                        eth_type[7:0] <= gmii_rxd;
                        cnt <= 5'd0;
                        if(eth_type[15:8] == ETH_TPYE[15:8]  //判断是否为ARP协
                            && gmii_rxd == ETH_TPYE[7:0])
                            skip_en <= 1'b1; 
                        else
                            error_en <= 1'b1;                       
                    end        
                end  
            end
            st_arp_data : begin
                if(gmii_rx_dv) begin
                    cnt <= cnt + 5'd1;
                    if(cnt == 5'd6) 
                        op_data[15:8] <= gmii_rxd;           //操作码       
                    else if(cnt == 5'd7)
                        op_data[7:0] <= gmii_rxd;
                    else if(cnt >= 5'd8 && cnt < 5'd14)      //源MAC地址
                        src_mac_t <= {src_mac_t[39:0],gmii_rxd};
                    else if(cnt >= 5'd14 && cnt < 5'd18)     //源IP地址
                        src_ip_t<= {src_ip_t[23:0],gmii_rxd};
                    else if(cnt >= 5'd24 && cnt < 5'd28)     //目标IP地址
                        des_ip_t <= {des_ip_t[23:0],gmii_rxd};
                    else if(cnt == 5'd28) begin
                        cnt <= 5'd0;
                        if(des_ip_t == BOARD_IP) begin       //判断目的IP地址和
                            if((op_data == 16'd1) || (op_data == 16'd2)) begin
                                skip_en <= 1'b1;
                                arp_rx_done <= 1'b1;
                                src_mac <= src_mac_t;
                                src_ip <= src_ip_t;
                                src_mac_t <= 48'd0;
                                src_ip_t <= 32'd0;
                                des_mac_t <= 48'd0;
                                des_ip_t <= 32'd0;
                                if(op_data == 16'd1)         
                                    arp_rx_type <= 1'b0;     //ARP请求
                                else
                                    arp_rx_type <= 1'b1;     //ARP应答
                            end
                            else
                                error_en <= 1'b1;
                        end 
                        else
                            error_en <= 1'b1;
                    end
                end                                
            end
            st_rx_end : begin     
                cnt <= 5'd0;
                //单包数据接收完成   
                if(gmii_rx_dv == 1'b0 && skip_en == 1'b0)
                    skip_en <= 1'b1; 
            end    
            default : ;
        endcase                                                        
    end
end

endmodule

st_idle:初始状态,等待接收前导码。gmii_rx_dv(GMII输入数据有效信号)为高检测到第一个55(前导码),将skip_en(控制状态跳转使能信号)拉高。

 st_preamble:接收前导码状态 。gmii_rx_dv(GMII输入数据有效信号)为高,cnt计数器记录前导码数量。由于状态机次态,因此检测6个55是否正常(前导码),如有错误error_en(解析错误使能信号)拉高。没有错误在cnt为6下清零cnt计数器,判断gmii_rxd(GMII输入数据)是否为8'hd5,然后对skip_en或error_en进行处理。

st_eth_head:接收以太网帧头。gmii_rx_dv(GMII输入数据有效信号)为高,cnt计数器记录前帧头数量。接受前6位为目的MAC,寄存到des_mac_t中(此处采用移位寄存方法,一次缓存48为数据)。判断接收到的目的MAC是否与开发板MAC地址或者公共地址,如均不符合error_en(解析错误使能信号)拉高。6~12组数据为源MAC地址,FPGA端无需处理。cnt计数器为12,将第13组数据(协议类型高位)赋值给eth_type[15:8](以太网类型),cnt计数器为13,将第14组数据(协议类型高位)赋值给eth_type[7:0](以太网类型),计数器cnt清零。cnt计数器为13时,同步判断判断是否为ARP协议,因为是并行结构,所以分高低位分别比较。

st_arp_datd:接收ARP数据。gmii_rx_dv(GMII输入数据有效信号)为高,cnt计数器记录数据段数量。(只需读取OP段数据)首先发送gmii_rxd(操作码)高8位,在发送gmii_rxd(操作码)低8位。在cnt >= 5'd8 && cnt < 5'd14时间段,移位寄存源MAC地址,在cnt >= 5'd14 && cnt < 5'd18时间段,移位寄存器源IP地址,在cnt >= 5'd24 && cnt < 5'd28时间段,移位目标IP地址;28位ARP数据接收完成后,进行判断目的IP地址和操作码,如果不为目标IP返回error_en拉高,正确目标IP下,判断是否为请求或应答包,skip_en(控制状态跳转使能信号)拉高,arp_rx_done(ARP接收完成信号)拉高,src_mac <= src_mac_t(接受源目的MAC寄存器),src_ip <= src_ip_t(接受源目的IP寄存器),之后清除寄存器缓存值。如果op_data为1,则arp_rx_type <= 1'b0(ARP请求),否则arp_rx_type <= 1'b1(ARP应答)。

st_rx_end:接收结束。gmii_rx_dv为0(数据包接收完成),skip_en为0(控制状态跳转使能信号),将skip_en拉高。

               (2)arp_tx模块 

arp_tx模块时序:

状态跳转:

st_idle //初始状态,等待开始发送信号
st_preamble //发送前导码+帧起始界定符
st_eth_head //发送以太网帧头
st_arp_data  //发送ARP数据
st_crc//发送CRC校验值

module arp_tx( 
    input                clk        , //时钟信号
    input                rst_n      , //复位信号,低电平有效
    
    input                arp_tx_en  , //ARP发送使能信号
    input                arp_tx_type, //ARP发送类型 0:请求  1:应答
    input        [47:0]  des_mac    , //发送的目标MAC地址
    input        [31:0]  des_ip     , //发送的目标IP地址
    input        [31:0]  crc_data   , //CRC校验数据
    input         [7:0]  crc_next   , //CRC下次校验完成数据
    output  reg          tx_done    , //以太网发送完成信号
    output  reg          gmii_tx_en , //GMII输出数据有效信号
    output  reg  [7:0]   gmii_txd   , //GMII输出数据
    output  reg          crc_en     , //CRC开始校验使能
    output  reg          crc_clr      //CRC数据复位信号 
    );

//parameter define
//开发板MAC地址 00-11-22-33-44-55
parameter BOARD_MAC = 48'h00_11_22_33_44_55;
//开发板IP地址 192.168.1.10
parameter BOARD_IP  = {8'd192,8'd168,8'd1,8'd10}; 
//目的MAC地址 ff_ff_ff_ff_ff_ff
parameter  DES_MAC   = 48'hff_ff_ff_ff_ff_ff;
//目的IP地址 192.168.1.102     
parameter  DES_IP    = {8'd192,8'd168,8'd1,8'd102};

localparam  st_idle      = 5'b0_0001; //初始状态,等待开始发送信号
localparam  st_preamble  = 5'b0_0010; //发送前导码+帧起始界定符
localparam  st_eth_head  = 5'b0_0100; //发送以太网帧头
localparam  st_arp_data  = 5'b0_1000; //
localparam  st_crc       = 5'b1_0000; //发送CRC校验值

localparam  ETH_TYPE     = 16'h0806 ; //以太网帧类型 ARP协议
localparam  HD_TYPE      = 16'h0001 ; //硬件类型 以太网
localparam  PROTOCOL_TYPE= 16'h0800 ; //上层协议为IP协议
//以太网数据最小为46个字节,不足部分填充数据
localparam  MIN_DATA_NUM = 16'd46   ;    

//reg define
reg  [4:0]  cur_state     ;
reg  [4:0]  next_state    ;
                          
reg  [7:0]  preamble[7:0] ; //前导码+SFD
reg  [7:0]  eth_head[13:0]; //以太网首部
reg  [7:0]  arp_data[27:0]; //ARP数据
                            
reg         tx_en_d0      ; //arp_tx_en信号延时
reg         tx_en_d1      ; 
reg         skip_en       ; //控制状态跳转使能信号
reg  [5:0]  cnt           ; 
reg  [4:0]  data_cnt      ; //发送数据个数计数器
reg         tx_done_t     ; 
                                
//wire define                   
wire        pos_tx_en     ; //arp_tx_en信号上升沿

//*****************************************************
//**                    main code
//*****************************************************

assign  pos_tx_en = (~tx_en_d1) & tx_en_d0;
                           
//对arp_tx_en信号延时打拍两次,用于采arp_tx_en的上升沿
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        tx_en_d0 <= 1'b0;
        tx_en_d1 <= 1'b0;
    end    
    else begin
        tx_en_d0 <= arp_tx_en;
        tx_en_d1 <= tx_en_d0;
    end
end 

//(三段式状态机)同步时序描述状态转移
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cur_state <= st_idle;  
    else
        cur_state <= next_state;
end

//组合逻辑判断状态转移条件
always @(*) begin
    next_state = st_idle;
    case(cur_state)
        st_idle : begin                     //空闲状态
            if(skip_en)                
                next_state = st_preamble;
            else
                next_state = st_idle;
        end                          
        st_preamble : begin                 //发送前导码+帧起始界定符
            if(skip_en)
                next_state = st_eth_head;
            else
                next_state = st_preamble;      
        end
        st_eth_head : begin                 //发送以太网首部
            if(skip_en)
                next_state = st_arp_data;
            else
                next_state = st_eth_head;      
        end              
        st_arp_data : begin                 //发送ARP数据                      
            if(skip_en)
                next_state = st_crc;
            else
                next_state = st_arp_data;      
        end
        st_crc: begin                       //发送CRC校验值
            if(skip_en)
                next_state = st_idle;
            else
                next_state = st_crc;      
        end
        default : next_state = st_idle;   
    endcase
end                      

//时序电路描述状态输出,发送以太网数据
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        skip_en <= 1'b0; 
        cnt <= 6'd0;
        data_cnt <= 5'd0;
        crc_en <= 1'b0;
        gmii_tx_en <= 1'b0;
        gmii_txd <= 8'd0;
        tx_done_t <= 1'b0; 
        
        //初始化数组    
        //前导码 7个8'h55 + 1个8'hd5 
        preamble[0] <= 8'h55;                
        preamble[1] <= 8'h55;
        preamble[2] <= 8'h55;
        preamble[3] <= 8'h55;
        preamble[4] <= 8'h55;
        preamble[5] <= 8'h55;
        preamble[6] <= 8'h55;
        preamble[7] <= 8'hd5;
        //以太网帧头 
        eth_head[0] <= DES_MAC[47:40];      //目的MAC地址
        eth_head[1] <= DES_MAC[39:32];
        eth_head[2] <= DES_MAC[31:24];
        eth_head[3] <= DES_MAC[23:16];
        eth_head[4] <= DES_MAC[15:8];
        eth_head[5] <= DES_MAC[7:0];        
        eth_head[6] <= BOARD_MAC[47:40];    //源MAC地址
        eth_head[7] <= BOARD_MAC[39:32];    
        eth_head[8] <= BOARD_MAC[31:24];    
        eth_head[9] <= BOARD_MAC[23:16];    
        eth_head[10] <= BOARD_MAC[15:8];    
        eth_head[11] <= BOARD_MAC[7:0];     
        eth_head[12] <= ETH_TYPE[15:8];     //以太网帧类型
        eth_head[13] <= ETH_TYPE[7:0];      
        //ARP数据                           
        arp_data[0] <= HD_TYPE[15:8];       //硬件类型
        arp_data[1] <= HD_TYPE[7:0];
        arp_data[2] <= PROTOCOL_TYPE[15:8]; //上层协议类型
        arp_data[3] <= PROTOCOL_TYPE[7:0];
        arp_data[4] <= 8'h06;               //硬件地址长度,6
        arp_data[5] <= 8'h04;               //协议地址长度,4
        arp_data[6] <= 8'h00;               //OP,操作码 8'h01:ARP请求 8'h02:ARP应答
        arp_data[7] <= 8'h01;
        arp_data[8] <= BOARD_MAC[47:40];    //发送端(源)MAC地址
        arp_data[9] <= BOARD_MAC[39:32];
        arp_data[10] <= BOARD_MAC[31:24];
        arp_data[11] <= BOARD_MAC[23:16];
        arp_data[12] <= BOARD_MAC[15:8];
        arp_data[13] <= BOARD_MAC[7:0];
        arp_data[14] <= BOARD_IP[31:24];    //发送端(源)IP地址
        arp_data[15] <= BOARD_IP[23:16];
        arp_data[16] <= BOARD_IP[15:8];
        arp_data[17] <= BOARD_IP[7:0];
        arp_data[18] <= DES_MAC[47:40];     //接收端(目的)MAC地址
        arp_data[19] <= DES_MAC[39:32];
        arp_data[20] <= DES_MAC[31:24];
        arp_data[21] <= DES_MAC[23:16];
        arp_data[22] <= DES_MAC[15:8];
        arp_data[23] <= DES_MAC[7:0];  
        arp_data[24] <= DES_IP[31:24];      //接收端(目的)IP地址
        arp_data[25] <= DES_IP[23:16];
        arp_data[26] <= DES_IP[15:8];
        arp_data[27] <= DES_IP[7:0];
    end
    else begin
        skip_en <= 1'b0;
        crc_en <= 1'b0;
        gmii_tx_en <= 1'b0;
        tx_done_t <= 1'b0;
        case(next_state)
            st_idle : begin
                if(pos_tx_en) begin
                    skip_en <= 1'b1;  
                    //如果目标MAC地址和IP地址已经更新,则发送正确的地址
                    if((des_mac != 48'b0) || (des_ip != 32'd0)) begin
                        eth_head[0] <= des_mac[47:40];
                        eth_head[1] <= des_mac[39:32];
                        eth_head[2] <= des_mac[31:24];
                        eth_head[3] <= des_mac[23:16];
                        eth_head[4] <= des_mac[15:8];
                        eth_head[5] <= des_mac[7:0];  
                        arp_data[18] <= des_mac[47:40];
                        arp_data[19] <= des_mac[39:32];
                        arp_data[20] <= des_mac[31:24];
                        arp_data[21] <= des_mac[23:16];
                        arp_data[22] <= des_mac[15:8];
                        arp_data[23] <= des_mac[7:0];  
                        arp_data[24] <= des_ip[31:24];
                        arp_data[25] <= des_ip[23:16];
                        arp_data[26] <= des_ip[15:8];
                        arp_data[27] <= des_ip[7:0];
                    end
                    if(arp_tx_type == 1'b0)
                        arp_data[7] <= 8'h01;            //ARP请求 
                    else 
                        arp_data[7] <= 8'h02;            //ARP应答
                end    
            end                                                                   
            st_preamble : begin                          //发送前导码+帧起始界定符
                gmii_tx_en <= 1'b1;
                gmii_txd <= preamble[cnt];
                if(cnt == 6'd7) begin                        
                    skip_en <= 1'b1;
                    cnt <= 1'b0;    
                end
                else    
                    cnt <= cnt + 1'b1;                     
            end
            st_eth_head : begin                          //发送以太网首部
                gmii_tx_en <= 1'b1;
                crc_en <= 1'b1;
                gmii_txd <= eth_head[cnt];
                if (cnt == 6'd13) begin
                    skip_en <= 1'b1;
                    cnt <= 1'b0;
                end    
                else    
                    cnt <= cnt + 1'b1;    
            end                    
            st_arp_data : begin                          //发送ARP数据  
                crc_en <= 1'b1;
                gmii_tx_en <= 1'b1;
                //至少发送46个字节
                if (cnt == MIN_DATA_NUM - 1'b1) begin    
                    skip_en <= 1'b1;
                    cnt <= 1'b0;
                    data_cnt <= 1'b0;
                end    
                else    
                    cnt <= cnt + 1'b1;  
                if(data_cnt <= 6'd27) begin
                    data_cnt <= data_cnt + 1'b1;
                    gmii_txd <= arp_data[data_cnt];
                end    
                else
                    gmii_txd <= 8'd0;                    //Padding,填充0
            end
            st_crc      : begin                          //发送CRC校验值
                gmii_tx_en <= 1'b1;
                cnt <= cnt + 1'b1;
                if(cnt == 6'd0)
                    gmii_txd <= {~crc_next[0], ~crc_next[1], ~crc_next[2],~crc_next[3],
                                 ~crc_next[4], ~crc_next[5], ~crc_next[6],~crc_next[7]};
                else if(cnt == 6'd1)
                    gmii_txd <= {~crc_data[16], ~crc_data[17], ~crc_data[18],
                                 ~crc_data[19], ~crc_data[20], ~crc_data[21], 
                                 ~crc_data[22],~crc_data[23]};
                else if(cnt == 6'd2) begin
                    gmii_txd <= {~crc_data[8], ~crc_data[9], ~crc_data[10],
                                 ~crc_data[11],~crc_data[12], ~crc_data[13], 
                                 ~crc_data[14],~crc_data[15]};                              
                end
                else if(cnt == 6'd3) begin
                    gmii_txd <= {~crc_data[0], ~crc_data[1], ~crc_data[2],~crc_data[3],
                                 ~crc_data[4], ~crc_data[5], ~crc_data[6],~crc_data[7]};  
                    tx_done_t <= 1'b1;
                    skip_en <= 1'b1;
                    cnt <= 1'b0;
                end                                                                                  
            end                          
            default :;  
        endcase                                             
    end
end            

//发送完成信号及crc值复位信号
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        tx_done <= 1'b0;
        crc_clr <= 1'b0;
    end
    else begin
        tx_done <= tx_done_t;
        crc_clr <= tx_done_t;
    end
end

endmodule

对arp_tx_en(ARP发送使能信号)信号打两拍用于记录上升沿。

 同步时序描述状态转移。

组合逻辑判断状态转移条件,均以skip_en(控制状态跳转使能信号)为参考,skip_en为高进入下一状态,否则保持现有状态

时序电路描述状态输出,发送以太网数据:

复位状态:skip_en(控制状态跳转使能信号)、cnt、data_cnt(发送数据个数计数器)、crc_en(CRC开始校验使能)、gmii_tx_en(GMII输出数据有效信号)、gmii_txd(GMII输出数据)、tx_done_t(以太网发送完成信号缓存器)全部置0。

         preamble(前导码+SFD)赋值7个8'h55 + 1个8'hd5;

        eth_head(以太网首部)赋值目的MAC地址、源MAC地址和以太网帧类型。

        arp_data(ARP数据)赋值硬件类型、上层协议类型、硬件地址、协议地址、操作码、发送端(源)MAC地址、发送端(源)IP地址、接收端(目的)MAC地址、接收端(目的)IP地址。

st_idle:初始状态,等待开始发送信号。如果检测到pos_tx_en(arp_tx_en信号上升沿)如果目标MAC地址和IP地址已经更新,则发送正确的地址(更新后的地址)。然后对类型判断arp_tx_type(ARP发送类型 0:请求  1:应答),对arp_data[7]进行赋值。

st_preamble:发送前导码+帧起始界定符状态。依次发送前导码+帧起始界定符,通过cnt计数器控制发送8位。

st_eth_head:发送以太网帧头。发送以太网首部,通过cnt计数器控制发送14位。crc_en <= 1'b1表明需要进行CRC校验。

st_arp_data:发送ARP数据状态。rc_en <= 1'b1表明需要进行CRC校验。gmii_tx_en(拉高)GMII输出数据有效信号拉高,输出有效。至少发送46个字节,发送完46个字节,skip_en(控制状态跳转使能信号)拉高,cnt计数器清零,data_cnt数据计数器清零。在data_cnt小于27时间内,依次发送arp_data[data_cnt](ARP数据)给gmii_txd输出。超过27位后补0到46位。

st_crc:发送CRC校验值状态。此时crc_en <= 1'b1未被拉高。按照crc算法要求,对数据高低互换,并且进行取反。一共4位,cnt计数器进行控制。先取crc_next高八位(因为此时crc_data还是上一个字节数据),后面取crc_data[23:0]位。tx_done_t拉高,skip_en控制状态跳转使能信号)拉高,cnt计数器清零。

将tx_done_t依次赋值给tx_done(以太网发送完成信号)、tx_done(CRC数据复位信号)

               (3)crc32_d8模块

                 (4)ARP顶层

module arp(
    input                rst_n      , //复位信号,低电平有效
    //GMII接口
    input                gmii_rx_clk, //GMII接收数据时钟
    input                gmii_rx_dv , //GMII输入数据有效信号
    input        [7:0]   gmii_rxd   , //GMII输入数据
    input                gmii_tx_clk, //GMII发送数据时钟
    output               gmii_tx_en , //GMII输出数据有效信号
    output       [7:0]   gmii_txd   , //GMII输出数据          

    //用户接口
    output               arp_rx_done, //ARP接收完成信号
    output               arp_rx_type, //ARP接收类型 0:请求  1:应答
    output       [47:0]  src_mac    , //接收到目的MAC地址
    output       [31:0]  src_ip     , //接收到目的IP地址    
    input                arp_tx_en  , //ARP发送使能信号
    input                arp_tx_type, //ARP发送类型 0:请求  1:应答
    input        [47:0]  des_mac    , //发送的目标MAC地址
    input        [31:0]  des_ip     , //发送的目标IP地址
    output               tx_done      //以太网发送完成信号    
    );

//parameter define
//开发板MAC地址 00-11-22-33-44-55
parameter BOARD_MAC = 48'h00_11_22_33_44_55;    
//开发板IP地址 192.168.1.10 
parameter BOARD_IP  = {8'd192,8'd168,8'd1,8'd10};   
//目的MAC地址 ff_ff_ff_ff_ff_ff
parameter  DES_MAC   = 48'hff_ff_ff_ff_ff_ff;
//目的IP地址 192.168.1.102     
parameter  DES_IP    = {8'd192,8'd168,8'd1,8'd102};

//wire define
wire           crc_en  ; //CRC开始校验使能
wire           crc_clr ; //CRC数据复位信号 
wire   [7:0]   crc_d8  ; //输入待校验8位数据
wire   [31:0]  crc_data; //CRC校验数据
wire   [31:0]  crc_next; //CRC下次校验完成数据

//*****************************************************
//**                    main code
//*****************************************************

assign  crc_d8 = gmii_txd;

//ARP接收模块    
arp_rx 
   #(
    .BOARD_MAC       (BOARD_MAC),         //参数例化
    .BOARD_IP        (BOARD_IP )
    )
   u_arp_rx(
    .clk             (gmii_rx_clk),
    .rst_n           (rst_n),

    .gmii_rx_dv      (gmii_rx_dv),
    .gmii_rxd        (gmii_rxd  ),
    .arp_rx_done     (arp_rx_done),
    .arp_rx_type     (arp_rx_type),
    .src_mac         (src_mac    ),
    .src_ip          (src_ip     )
    );                                           

//ARP发送模块
arp_tx
   #(
    .BOARD_MAC       (BOARD_MAC),         //参数例化
    .BOARD_IP        (BOARD_IP ),
    .DES_MAC         (DES_MAC  ),
    .DES_IP          (DES_IP   )
    )
   u_arp_tx(
    .clk             (gmii_tx_clk),
    .rst_n           (rst_n),

    .arp_tx_en       (arp_tx_en ),
    .arp_tx_type     (arp_tx_type),
    .des_mac         (des_mac   ),
    .des_ip          (des_ip    ),
    .crc_data        (crc_data  ),
    .crc_next        (crc_next[31:24]),
    .tx_done         (tx_done   ),
    .gmii_tx_en      (gmii_tx_en),
    .gmii_txd        (gmii_txd  ),
    .crc_en          (crc_en    ),
    .crc_clr         (crc_clr   )
    );     

//以太网发送CRC校验模块
crc32_d8   u_crc32_d8(
    .clk             (gmii_tx_clk),                      
    .rst_n           (rst_n      ),                          
    .data            (crc_d8     ),            
    .crc_en          (crc_en     ),                          
    .crc_clr         (crc_clr    ),                         
    .crc_data        (crc_data   ),                        
    .crc_next        (crc_next   )                         
    );

endmodule

开发板以太网信息配置。 

        3、ARP_Ctrl模块

module arp_ctrl(
    input                clk        , //输入时钟   
    input                rst_n      , //复位信号,低电平有效
    
    input                touch_key  , //触摸按键,用于触发开发板发出ARP请求
    input                arp_rx_done, //ARP接收完成信号
    input                arp_rx_type, //ARP接收类型 0:请求  1:应答 
    output  reg          arp_tx_en  , //ARP发送使能信号
    output  reg          arp_tx_type  //ARP发送类型 0:请求  1:应答
    );

//reg define
reg         touch_key_d0;
reg         touch_key_d1;

//wire define
wire        pos_touch_key;  //touch_key信号上升沿

//*****************************************************
//**                    main code
//*****************************************************

assign pos_touch_key = ~touch_key_d1 & touch_key_d0;

//对arp_tx_en信号延时打拍两次,用于采touch_key的上升沿
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        touch_key_d0 <= 1'b0;
        touch_key_d1 <= 1'b0;
    end
    else begin
        touch_key_d0 <= touch_key;
        touch_key_d1 <= touch_key_d0;
    end
end

//为arp_tx_en和arp_tx_type赋值
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        arp_tx_en <= 1'b0;
        arp_tx_type <= 1'b0;
    end
    else begin
        if(pos_touch_key == 1'b1) begin  //检测到输入触摸按键上升沿
            arp_tx_en <= 1'b1;           
            arp_tx_type <= 1'b0;
        end
        //接收到ARP请求,开始控制ARP发送模块应答
        else if((arp_rx_done == 1'b1) && (arp_rx_type == 1'b0)) begin
            arp_tx_en <= 1'b1;
            arp_tx_type <= 1'b1;
        end
        else
            arp_tx_en <= 1'b0;
    end
end

endmodule

        4、eth_arp_test顶层文件

module eth_arp_test(
    input              sys_clk   , //系统时钟
    input              sys_rst_n , //系统复位信号,低电平有效 
    input              touch_key , //触摸按键,用于触发开发板发出ARP请求
    //PL以太网RGMII接口   
    input              eth_rxc   , //RGMII接收数据时钟
    input              eth_rx_ctl, //RGMII输入数据有效信号
    input       [3:0]  eth_rxd   , //RGMII输入数据
    output             eth_txc   , //RGMII发送数据时钟    
    output             eth_tx_ctl, //RGMII输出数据有效信号
    output      [3:0]  eth_txd   , //RGMII输出数据          
    output             eth_rst_n   //以太网芯片复位信号,低电平有效   
    );

//parameter define
//开发板MAC地址 00-11-22-33-44-55
parameter  BOARD_MAC = 48'h00_11_22_33_44_55;     
//开发板IP地址 192.168.1.10     
parameter  BOARD_IP  = {8'd192,8'd168,8'd1,8'd10};
//目的MAC地址 ff_ff_ff_ff_ff_ff
parameter  DES_MAC   = 48'hff_ff_ff_ff_ff_ff;
//目的IP地址 192.168.1.102
parameter  DES_IP    = {8'd192,8'd168,8'd1,8'd102};
//输入数据IO延时(如果为n,表示延时n*78ps) 
parameter IDELAY_VALUE = 0;

//wire define
wire          clk_200m   ; //用于IO延时的时钟 
              
wire          gmii_rx_clk; //GMII接收时钟
wire          gmii_rx_dv ; //GMII接收数据有效信号
wire  [7:0]   gmii_rxd   ; //GMII接收数据
wire          gmii_tx_clk; //GMII发送时钟
wire          gmii_tx_en ; //GMII发送数据使能信号
wire  [7:0]   gmii_txd   ; //GMII发送数据
              
wire          arp_rx_done; //ARP接收完成信号
wire          arp_rx_type; //ARP接收类型 0:请求  1:应答
wire  [47:0]  src_mac    ; //接收到目的MAC地址
wire  [31:0]  src_ip     ; //接收到目的IP地址    
wire          arp_tx_en  ; //ARP发送使能信号
wire          arp_tx_type; //ARP发送类型 0:请求  1:应答
wire          tx_done    ; //发送的目标MAC地址
wire  [47:0]  des_mac    ; //发送的目标IP地址
wire  [31:0]  des_ip     ; //以太网发送完成信号    

//*****************************************************
//**                    main code
//*****************************************************

assign des_mac = src_mac;
assign des_ip = src_ip;
assign eth_rst_n = sys_rst_n;

//MMCM/PLL
clk_wiz u_clk_wiz
(
    .clk_in1   (sys_clk   ),
    .clk_out1  (clk_200m  ),    
    .reset     (~sys_rst_n), 
    .locked    (locked)
);

//GMII接口转RGMII接口
gmii_to_rgmii 
    #(
     .IDELAY_VALUE (IDELAY_VALUE)
     )
    u_gmii_to_rgmii(
    .idelay_clk    (clk_200m    ),

    .gmii_rx_clk   (gmii_rx_clk ),
    .gmii_rx_dv    (gmii_rx_dv  ),
    .gmii_rxd      (gmii_rxd    ),
    .gmii_tx_clk   (gmii_tx_clk ),
    .gmii_tx_en    (gmii_tx_en  ),
    .gmii_txd      (gmii_txd    ),
    
    .rgmii_rxc     (eth_rxc     ),
    .rgmii_rx_ctl  (eth_rx_ctl  ),
    .rgmii_rxd     (eth_rxd     ),
    .rgmii_txc     (eth_txc     ),
    .rgmii_tx_ctl  (eth_tx_ctl  ),
    .rgmii_txd     (eth_txd     )
    );

//ARP通信
arp                                             
   #(
    .BOARD_MAC     (BOARD_MAC),      //参数例化
    .BOARD_IP      (BOARD_IP ),
    .DES_MAC       (DES_MAC  ),
    .DES_IP        (DES_IP   )
    )
   u_arp(
    .rst_n         (sys_rst_n  ),
                    
    .gmii_rx_clk   (gmii_rx_clk),
    .gmii_rx_dv    (gmii_rx_dv ),
    .gmii_rxd      (gmii_rxd   ),
    .gmii_tx_clk   (gmii_tx_clk),
    .gmii_tx_en    (gmii_tx_en ),
    .gmii_txd      (gmii_txd   ),
                    
    .arp_rx_done   (arp_rx_done),
    .arp_rx_type   (arp_rx_type),
    .src_mac       (src_mac    ),
    .src_ip        (src_ip     ),
    .arp_tx_en     (arp_tx_en  ),
    .arp_tx_type   (arp_tx_type),
    .des_mac       (des_mac    ),
    .des_ip        (des_ip     ),
    .tx_done       (tx_done    )
    );

//ARP控制
arp_ctrl u_arp_ctrl(
    .clk           (gmii_rx_clk),
    .rst_n         (sys_rst_n),
                   
    .touch_key     (touch_key),
    .arp_rx_done   (arp_rx_done),
    .arp_rx_type   (arp_rx_type),
    .arp_tx_en     (arp_tx_en),
    .arp_tx_type   (arp_tx_type)
    );

endmodule

四、ARP实验总结

五、下载验证

按下触摸按键,开发板(192.168.1.10)发送ARP请求,电脑做出(192.168.1.102)应答回应MAC地址。

  • 29
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值