FPGA 以太网通信UDP通信环回

1 实验任务

上位机通过网口调试助手发送数据给 FPGA FPGA 通过 PL 端以太网接口接收数据并将接收到的数据发送给上位机,完成以太网 UDP 数据的环回。

2 系统设计

PLL时钟模块将PHY芯片的RGMII输入数据时钟125Mhz,转为偏移90度的125Mhz的时钟用于RGMII发送数据时钟。
GMII接口转RGMII接口模块发送时将8位数据转为4位数据,接收时将4位数据转为8位数据。
ARP模块解析电脑上位机发送给开发板ARP请求命令,并返回开发板的 MAC 地址。
UDP顶层模块实现了以太网UDP数据包的接收、发送以及CRC校验的功能。
以太网控制模块根据输入的ARP接收完成信号,控制ARP顶层模块返回ARP应答信号,然后切换为UDP协议对数据进行发送和接收。
FIFO模块用来缓存以太网单次接收的数据,由于所使用的GMII接收时钟和GMII发送时钟实际上为同一个时钟,因此使用的是同步FIFO。

3 Verilog代码

3.1 udp回环通信顶层模块

`timescale 1ns / 1ps
//以太网通信UDP通信环回顶层模块


module udp_loop_top (
    input        sys_rst_n,   //系统复位信号,低电平有效 
    //以太网RGMII接口   
    input        eth_rxc,     //RGMII输入数据时钟 125mhz
    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输出数据          

);

  //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        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_gmii_tx_en;  //ARP GMII输出数据有效信号 
  wire [ 7:0] arp_gmii_txd;  //ARP 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 [47:0] des_mac;  //发送的目标MAC地址
  wire [31:0] des_ip;  //发送的目标IP地址   
  wire        arp_tx_done;  //ARP发送完成信号

  wire        udp_gmii_tx_en;  //UDP GMII输出数据有效信号 
  wire [ 7:0] udp_gmii_txd;  //UDP GMII输出数据
  wire        rec_pkt_done;  //UDP单包数据接收完成信号
  wire        rec_en;  //UDP接收的数据使能信号
  wire [31:0] rec_data;  //UDP接收的数据
  wire [15:0] rec_byte_num;  //UDP接收的有效字节数 单位:byte 
  wire [15:0] tx_byte_num;  //UDP发送的有效字节数 单位:byte 
  wire        udp_tx_done;  //UDP发送完成信号
  wire        tx_req;  //UDP读数据请求信号
  wire [31:0] tx_data;  //UDP待发送数据

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

  assign tx_start_en = rec_pkt_done;  //单包数据接收完成后开始发送新数据
  assign tx_byte_num = rec_byte_num;  //接收数据数等于发送数据数
  assign des_mac = src_mac;
  assign des_ip = src_ip;
  assign eth_txc = clk_125m_deg;  //pll时钟输出的偏移90度的125Mhz的时钟成为RGMII发送数据时钟


  //MMCM/PLL
  clk_wiz u_clk_wiz (
      // Clock out ports
      .clk_out1(clk_125m_deg),  // output clk_out1
      // Status and control signals
      .reset   (~sys_rst_n),    // input reset
      .locked  (locked),        // output locked
      // Clock in ports
      .clk_in1 (rgmii_txc)      // input clk_in1:RGMII发送时钟
  );


  //GMII接口转RGMII接口
  gmii_to_rgmii u_gmii_to_rgmii (
      .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   (rgmii_txc),
      .rgmii_tx_ctl(eth_tx_ctl),
      .rgmii_txd   (eth_txd)
  );

  //ARP通信
  arp_top #(
      .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 (arp_gmii_tx_en),
      .gmii_txd   (arp_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    (arp_tx_done)
  );

  //UDP通信
  udp_top #(
      .BOARD_MAC(BOARD_MAC),  //参数例化
      .BOARD_IP (BOARD_IP),
      .DES_MAC  (DES_MAC),
      .DES_IP   (DES_IP)
  ) u_udp (
      .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 (udp_gmii_tx_en),
      .gmii_txd   (udp_gmii_txd),

      .rec_pkt_done(rec_pkt_done),
      .rec_en      (rec_en),
      .rec_data    (rec_data),
      .rec_byte_num(rec_byte_num),
      .tx_start_en (tx_start_en),
      .tx_data     (tx_data),
      .tx_byte_num (tx_byte_num),
      .des_mac     (des_mac),
      .des_ip      (des_ip),
      .tx_done     (udp_tx_done),
      .tx_req      (tx_req)
  );

  //同步FIFO
  sync_fifo_2048x32b u_sync_fifo_2048x32b (
      .clk  (gmii_rx_clk),  // input wire clk
      .srst (~sys_rst_n),   // input wire rst
      .din  (rec_data),     // input wire [31 : 0] din
      .wr_en(rec_en),       // input wire wr_en
      .rd_en(tx_req),       // input wire rd_en
      .dout (tx_data),      // output wire [31 : 0] dout
      .full (),             // output wire full
      .empty()              // output wire empty
  );

  //以太网控制模块
  eth_ctrl u_eth_ctrl (
      .clk  (gmii_rx_clk),
      .rst_n(sys_rst_n),

      .arp_rx_done   (arp_rx_done),
      .arp_rx_type   (arp_rx_type),
      .arp_tx_en     (arp_tx_en),
      .arp_tx_type   (arp_tx_type),
      .arp_tx_done   (arp_tx_done),
      .arp_gmii_tx_en(arp_gmii_tx_en),
      .arp_gmii_txd  (arp_gmii_txd),

      .udp_tx_start_en(tx_start_en),
      .udp_tx_done    (udp_tx_done),
      .udp_gmii_tx_en (udp_gmii_tx_en),
      .udp_gmii_txd   (udp_gmii_txd),

      .gmii_tx_en(gmii_tx_en),
      .gmii_txd  (gmii_txd)
  );

endmodule

3.2 GMII接口转RGMII接口模块

`timescale 1ns / 1ps
//GMII接口转RGMII接口模块
//发送时将8位数据转为4位数据,接收时将4位数据转为8位数据

module gmii_to_rgmii (
    //以太网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发送数据          
);

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

  assign gmii_tx_clk = gmii_rx_clk;

  //RGMII接收
  rgmii_rx u_rgmii_rx (
      .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

3.3 RGMII接收模块

`timescale 1ns / 1ps
//RGMII接收模块


module rgmii_rx (
    //以太网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接收数据   
);

  //wire define
  wire       rgmii_rxc_bufg;  //全局时钟缓存
  wire       rgmii_rxc_bufio;  //全局时钟IO缓存
  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
  );

  //将输入的上下边沿DDR信号,转换成两位单边沿SDR信号
  IDDRE1 #(
      .DDR_CLK_EDGE     ("SAME_EDGE_PIPELINED"),// 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(gmii_rxdv_t[0]),    // 1-bit output: Registered parallel output 1
      .Q2(gmii_rxdv_t[1]),    // 1-bit output: Registered parallel output 2
      .C (rgmii_rxc_bufio),   // 1-bit input: High-speed clock
      .CB(~rgmii_rxc_bufio),  // 1-bit input: Inversion of High-speed clock C
      .D (rgmii_rx_ctl),      // 1-bit input: Serial Data Input
      .R (1'b0)               // 1-bit input: Active High Async Reset
  );

  genvar i;
  generate
    for (i = 0; i < 4; i = i + 1) begin : rxdata_bus
      IDDRE1 #(
          .DDR_CLK_EDGE      ("SAME_EDGE_PIPELINED"),  // 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(gmii_rxd[i]),       // 1-bit output: Registered parallel output 1
          .Q2(gmii_rxd[4+i]),     // 1-bit output: Registered parallel output 2
          .C (rgmii_rxc_bufio),   // 1-bit input: High-speed clock
          .CB(~rgmii_rxc_bufio),  // 1-bit input: Inversion of High-speed clock C
          .D (rgmii_rxd[i]),      // 1-bit input: Serial Data Input
          .R (1'b0)               // 1-bit input: Active High Async Reset
      );
    end
  endgenerate

endmodule

3.4 RGMII发送模块

`timescale 1ns / 1ps
//RGMII发送模块


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)
  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)
      .SRVAL(1'b0)  // Initializes the ODDRE1 Flip-Flops to the specified value (1'b0, 1'b1)
  ) ODDRE1_tx_ctl (
      .Q (rgmii_tx_ctl),  // 1-bit output: Data output to IOB
      .C (gmii_tx_clk),   // 1-bit input: High-speed clock input
      .D1(gmii_tx_en),    // 1-bit input: Parallel data input 1
      .D2(gmii_tx_en),    // 1-bit input: Parallel data input 2
      .SR(1'b0)           // 1-bit input: Active High Async Reset
  );

  genvar i;
  generate
    for (i = 0; i < 4; i = i + 1) begin : txdata_bus
      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)
          .SRVAL(1'b0)  // Initializes the ODDRE1 Flip-Flops to the specified value (1'b0, 1'b1)
      ) ODDRE1_inst (
          .Q (rgmii_txd[i]),   // 1-bit output: Data output to IOB
          .C (gmii_tx_clk),    // 1-bit input: High-speed clock input
          .D1(gmii_txd[i]),    // 1-bit input: Parallel data input 1
          .D2(gmii_txd[4+i]),  // 1-bit input: Parallel data input 2
          .SR(1'b0)            // 1-bit input: Active High Async Reset
      );
    end
  endgenerate

endmodule

3.5 ARP顶层模块

`timescale 1ns / 1ps
//arp顶层模块


module arp_top (
    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.6 ARP接收模块

`timescale 1ns / 1ps
//arp接收模块


module arp_rx (
    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

  //开发板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};

  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;  //操作码
  reg        rx_done_t;  //ARP接收完成信号

  //*****************************************************
  //**                    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;
      rx_done_t <= 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;
      rx_done_t <= 1'b0;
      case (next_state)
        st_idle: begin  //检测到第一个8'h55
          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;
                  rx_done_t <= 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

  //输出arp_rx_done信号
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) arp_rx_done <= 1'b0;
    else arp_rx_done <= rx_done_t;
  end

endmodule

3.7 ARP发送模块

`timescale 1ns / 1ps
//arp发送模块


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;

  //前导码+SFD,共8个字节
  reg  [7:0] preamble                                  [ 7:0];
  //以太网首部,共14个字节
  reg  [7:0] eth_head                                  [13:0];
  //ARP数据,共28个字节
  reg  [7:0] arp_data                                  [27:0];


  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开始发送信号延时打拍两次,用于采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

3.8 CRC32校验模块

`timescale 1ns / 1ps
//CRC32校验模块


module crc32_d8 (
    input             clk,       //时钟信号
    input             rst_n,     //复位信号,低电平有效
    input      [ 7:0] data,      //输入待校验8位数据
    input             crc_en,    //crc使能,开始校验标志
    input             crc_clr,   //crc数据复位信号            
    output reg [31:0] crc_data,  //CRC校验数据
    output     [31:0] crc_next   //CRC下次校验完成数据
);

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

  //输入待校验8位数据,需要先将高低位互换
  wire [7:0] data_t;

  assign data_t = {data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]};

  //CRC32的生成多项式为:G(x)= x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 
  //+ x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + 1

  assign crc_next[0] = crc_data[24] ^ crc_data[30] ^ data_t[0] ^ data_t[6];
  assign crc_next[1] = crc_data[24] ^ crc_data[25] ^ crc_data[30] ^ crc_data[31] 
                     ^ data_t[0] ^ data_t[1] ^ data_t[6] ^ data_t[7];
  assign crc_next[2] = crc_data[24] ^ crc_data[25] ^ crc_data[26] ^ crc_data[30] 
                     ^ crc_data[31] ^ data_t[0] ^ data_t[1] ^ data_t[2] ^ data_t[6] 
                     ^ data_t[7];
  assign crc_next[3] = crc_data[25] ^ crc_data[26] ^ crc_data[27] ^ crc_data[31] 
                     ^ data_t[1] ^ data_t[2] ^ data_t[3] ^ data_t[7];
  assign crc_next[4] = crc_data[24] ^ crc_data[26] ^ crc_data[27] ^ crc_data[28] 
                     ^ crc_data[30] ^ data_t[0] ^ data_t[2] ^ data_t[3] ^ data_t[4] 
                     ^ data_t[6];
  assign crc_next[5] = crc_data[24] ^ crc_data[25] ^ crc_data[27] ^ crc_data[28] 
                     ^ crc_data[29] ^ crc_data[30] ^ crc_data[31] ^ data_t[0] 
                     ^ data_t[1] ^ data_t[3] ^ data_t[4] ^ data_t[5] ^ data_t[6] 
                     ^ data_t[7];
  assign crc_next[6] = crc_data[25] ^ crc_data[26] ^ crc_data[28] ^ crc_data[29] 
                     ^ crc_data[30] ^ crc_data[31] ^ data_t[1] ^ data_t[2] ^ data_t[4] 
                     ^ data_t[5] ^ data_t[6] ^ data_t[7];
  assign crc_next[7] = crc_data[24] ^ crc_data[26] ^ crc_data[27] ^ crc_data[29] 
                     ^ crc_data[31] ^ data_t[0] ^ data_t[2] ^ data_t[3] ^ data_t[5] 
                     ^ data_t[7];
  assign crc_next[8] = crc_data[0] ^ crc_data[24] ^ crc_data[25] ^ crc_data[27] 
                     ^ crc_data[28] ^ data_t[0] ^ data_t[1] ^ data_t[3] ^ data_t[4];
  assign crc_next[9] = crc_data[1] ^ crc_data[25] ^ crc_data[26] ^ crc_data[28] 
                     ^ crc_data[29] ^ data_t[1] ^ data_t[2] ^ data_t[4] ^ data_t[5];
  assign crc_next[10] = crc_data[2] ^ crc_data[24] ^ crc_data[26] ^ crc_data[27] 
                     ^ crc_data[29] ^ data_t[0] ^ data_t[2] ^ data_t[3] ^ data_t[5];
  assign crc_next[11] = crc_data[3] ^ crc_data[24] ^ crc_data[25] ^ crc_data[27] 
                     ^ crc_data[28] ^ data_t[0] ^ data_t[1] ^ data_t[3] ^ data_t[4];
  assign crc_next[12] = crc_data[4] ^ crc_data[24] ^ crc_data[25] ^ crc_data[26] 
                     ^ crc_data[28] ^ crc_data[29] ^ crc_data[30] ^ data_t[0] 
                     ^ data_t[1] ^ data_t[2] ^ data_t[4] ^ data_t[5] ^ data_t[6];
  assign crc_next[13] = crc_data[5] ^ crc_data[25] ^ crc_data[26] ^ crc_data[27] 
                     ^ crc_data[29] ^ crc_data[30] ^ crc_data[31] ^ data_t[1] 
                     ^ data_t[2] ^ data_t[3] ^ data_t[5] ^ data_t[6] ^ data_t[7];
  assign crc_next[14] = crc_data[6] ^ crc_data[26] ^ crc_data[27] ^ crc_data[28] 
                     ^ crc_data[30] ^ crc_data[31] ^ data_t[2] ^ data_t[3] ^ data_t[4]
                     ^ data_t[6] ^ data_t[7];
  assign crc_next[15] =  crc_data[7] ^ crc_data[27] ^ crc_data[28] ^ crc_data[29]
                     ^ crc_data[31] ^ data_t[3] ^ data_t[4] ^ data_t[5] ^ data_t[7];
  assign crc_next[16] = crc_data[8] ^ crc_data[24] ^ crc_data[28] ^ crc_data[29] 
                     ^ data_t[0] ^ data_t[4] ^ data_t[5];
  assign crc_next[17] = crc_data[9] ^ crc_data[25] ^ crc_data[29] ^ crc_data[30] 
                     ^ data_t[1] ^ data_t[5] ^ data_t[6];
  assign crc_next[18] = crc_data[10] ^ crc_data[26] ^ crc_data[30] ^ crc_data[31] 
                     ^ data_t[2] ^ data_t[6] ^ data_t[7];
  assign crc_next[19] = crc_data[11] ^ crc_data[27] ^ crc_data[31] ^ data_t[3] ^ data_t[7];
  assign crc_next[20] = crc_data[12] ^ crc_data[28] ^ data_t[4];
  assign crc_next[21] = crc_data[13] ^ crc_data[29] ^ data_t[5];
  assign crc_next[22] = crc_data[14] ^ crc_data[24] ^ data_t[0];
  assign crc_next[23] = crc_data[15] ^ crc_data[24] ^ crc_data[25] ^ crc_data[30] 
                      ^ data_t[0] ^ data_t[1] ^ data_t[6];
  assign crc_next[24] = crc_data[16] ^ crc_data[25] ^ crc_data[26] ^ crc_data[31] 
                      ^ data_t[1] ^ data_t[2] ^ data_t[7];
  assign crc_next[25] = crc_data[17] ^ crc_data[26] ^ crc_data[27] ^ data_t[2] ^ data_t[3];
  assign crc_next[26] = crc_data[18] ^ crc_data[24] ^ crc_data[27] ^ crc_data[28] 
                      ^ crc_data[30] ^ data_t[0] ^ data_t[3] ^ data_t[4] ^ data_t[6];
  assign crc_next[27] = crc_data[19] ^ crc_data[25] ^ crc_data[28] ^ crc_data[29] 
                      ^ crc_data[31] ^ data_t[1] ^ data_t[4] ^ data_t[5] ^ data_t[7];
  assign crc_next[28] = crc_data[20] ^ crc_data[26] ^ crc_data[29] ^ crc_data[30] 
                      ^ data_t[2] ^ data_t[5] ^ data_t[6];
  assign crc_next[29] = crc_data[21] ^ crc_data[27] ^ crc_data[30] ^ crc_data[31] 
                      ^ data_t[3] ^ data_t[6] ^ data_t[7];
  assign crc_next[30] = crc_data[22] ^ crc_data[28] ^ crc_data[31] ^ data_t[4] ^ data_t[7];
  assign crc_next[31] = crc_data[23] ^ crc_data[29] ^ data_t[5];

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) crc_data <= 32'hff_ff_ff_ff;
    else if (crc_clr)  //CRC校验值复位
      crc_data <= 32'hff_ff_ff_ff;
    else if (crc_en) crc_data <= crc_next;
  end

endmodule

3.9 UDP顶层模块

`timescale 1ns / 1ps
//udp顶层模块


module udp_top (
    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        rec_pkt_done,  //以太网单包数据接收完成信号
    output        rec_en,        //以太网接收的数据使能信号
    output [31:0] rec_data,      //以太网接收的数据
    output [15:0] rec_byte_num,  //以太网接收的有效字节数 单位:byte     
    input         tx_start_en,   //以太网开始发送信号
    input  [31:0] tx_data,       //以太网待发送数据  
    input  [15:0] tx_byte_num,   //以太网发送的有效字节数 单位:byte  
    input  [47:0] des_mac,       //发送的目标MAC地址
    input  [31:0] des_ip,        //发送的目标IP地址    
    output        tx_done,       //以太网发送完成信号
    output        tx_req         //读数据请求信号    
);

  //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;

  //以太网接收模块    
  udp_rx #(
      .BOARD_MAC(BOARD_MAC),  //参数例化
      .BOARD_IP (BOARD_IP)
  ) u_udp_rx (
      .clk         (gmii_rx_clk),
      .rst_n       (rst_n),
      .gmii_rx_dv  (gmii_rx_dv),
      .gmii_rxd    (gmii_rxd),
      .rec_pkt_done(rec_pkt_done),
      .rec_en      (rec_en),
      .rec_data    (rec_data),
      .rec_byte_num(rec_byte_num)
  );

  //以太网发送模块
  udp_tx #(
      .BOARD_MAC(BOARD_MAC),  //参数例化
      .BOARD_IP (BOARD_IP),
      .DES_MAC  (DES_MAC),
      .DES_IP   (DES_IP)
  ) u_udp_tx (
      .clk        (gmii_tx_clk),
      .rst_n      (rst_n),
      .tx_start_en(tx_start_en),
      .tx_data    (tx_data),
      .tx_byte_num(tx_byte_num),
      .des_mac    (des_mac),
      .des_ip     (des_ip),
      .crc_data   (crc_data),
      .crc_next   (crc_next[31:24]),
      .tx_done    (tx_done),
      .tx_req     (tx_req),
      .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.10 UDP接收模块

`timescale 1ns / 1ps
//以太网数据接收模块
//UDP接收模块简化了处理流程,不需要进行IP首部和CRC循环冗余校验。
//它仅核对目的MAC地址和IP地址是否与开发板地址匹配。
//解析顺序为:前导码+帧起始界定符→以太网帧头→IP首部→UDP首部→UDP数据(有效数据)→接收结束
//为了与IP数据报32位对齐,接收模块执行了8位到32位数据的转换。


module udp_rx (
    input clk,   //时钟信号
    input rst_n, //复位信号,低电平有效

    input             gmii_rx_dv,    //GMII输入数据有效信号
    input      [ 7:0] gmii_rxd,      //GMII输入数据
    output reg        rec_pkt_done,  //以太网单包数据接收完成信号
    output reg        rec_en,        //以太网接收的数据使能信号
    output reg [31:0] rec_data,      //以太网接收的数据
    output reg [15:0] rec_byte_num   //以太网接收的有效字节数 单位:byte     
);

  //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};

  localparam st_idle = 7'b000_0001;  //初始状态,等待接收前导码
  localparam st_preamble = 7'b000_0010;  //接收前导码状态 
  localparam st_eth_head = 7'b000_0100;  //接收以太网帧头
  localparam st_ip_head = 7'b000_1000;  //接收IP首部
  localparam st_udp_head = 7'b001_0000;  //接收UDP首部
  localparam st_rx_data = 7'b010_0000;  //接收有效数据
  localparam st_rx_end = 7'b100_0000;  //接收结束

  localparam ETH_TYPE = 16'h0800;  //以太网协议类型 IP协议
  localparam UDP_TYPE = 8'd17;  //UDP协议类型

  //reg define
  reg [ 6:0] cur_state;
  reg [ 6:0] next_state;

  reg        skip_en;  //控制状态跳转使能信号
  reg        error_en;  //解析错误使能信号
  reg [ 4:0] cnt;  //解析数据计数器
  reg [47:0] des_mac;  //目的MAC地址
  reg [15:0] eth_type;  //以太网类型
  reg [31:0] des_ip;  //目的IP地址
  reg [ 5:0] ip_head_byte_num;  //IP首部长度
  reg [15:0] udp_byte_num;  //UDP长度
  reg [15:0] data_byte_num;  //数据长度
  reg [15:0] data_cnt;  //有效数据计数    
  reg [ 1:0] rec_en_cnt;  //8bit转32bit计数器

  //*****************************************************
  //**                    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_ip_head;
        else if (error_en) next_state = st_rx_end;
        else next_state = st_eth_head;
      end
      st_ip_head: begin  //接收IP首部
        if (skip_en) next_state = st_udp_head;
        else if (error_en) next_state = st_rx_end;
        else next_state = st_ip_head;
      end
      st_udp_head: begin  //接收UDP首部
        if (skip_en) next_state = st_rx_data;
        else next_state = st_udp_head;
      end
      st_rx_data: begin  //接收有效数据
        if (skip_en) next_state = st_rx_end;
        else next_state = st_rx_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 <= 48'd0;
      eth_type <= 16'd0;
      des_ip <= 32'd0;
      ip_head_byte_num <= 6'd0;
      udp_byte_num <= 16'd0;
      data_byte_num <= 16'd0;
      data_cnt <= 16'd0;
      rec_en_cnt <= 2'd0;
      rec_en <= 1'b0;
      rec_data <= 32'd0;
      rec_pkt_done <= 1'b0;
      rec_byte_num <= 16'd0;
    end else begin
      skip_en <= 1'b0;
      error_en <= 1'b0;
      rec_en <= 1'b0;
      rec_pkt_done <= 1'b0;
      case (next_state)
        st_idle: begin  //初始状态
          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 <= {des_mac[39:0], gmii_rxd};  //目的MAC地址
            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;
              //判断接收到的目的MAC地址是否为开发板MAC地址或者公共地址
              if(((des_mac == BOARD_MAC) ||(des_mac == 48'hff_ff_ff_ff_ff_ff))
                       && eth_type[15:8] == ETH_TYPE[15:8] && gmii_rxd == ETH_TYPE[7:0])
                skip_en <= 1'b1;
              else error_en <= 1'b1;
            end
          end
        end
        st_ip_head: begin  //解析IP首部
          if (gmii_rx_dv) begin
            cnt <= cnt + 5'd1;
            if (cnt == 5'd0) ip_head_byte_num <= {gmii_rxd[3:0], 2'd0};  //寄存IP首部长度
            else if (cnt == 5'd9) begin
              if (gmii_rxd != UDP_TYPE) begin
                //如果当前接收的数据不是UDP协议,停止解析数据                        
                error_en <= 1'b1;
                cnt <= 5'd0;
              end
            end else if ((cnt >= 5'd16) && (cnt <= 5'd18))
              des_ip <= {des_ip[23:0], gmii_rxd};  //寄存接收到的目的IP地址
            else if (cnt == 5'd19) begin
              des_ip <= {des_ip[23:0], gmii_rxd};
              //判断IP地址是否为开发板IP地址
              if ((des_ip[23:0] == BOARD_IP[31:8]) && (gmii_rxd == BOARD_IP[7:0])) begin
                if (cnt == ip_head_byte_num - 1'b1) begin
                  skip_en <= 1'b1;
                  cnt <= 5'd0;
                end
              end else begin
                //IP错误,停止解析数据                        
                error_en <= 1'b1;
                cnt <= 5'd0;
              end
            end else if (cnt == ip_head_byte_num - 1'b1) begin
              skip_en <= 1'b1;  //IP首部解析完成
              cnt     <= 5'd0;
            end
          end
        end
        st_udp_head: begin  //解析UDP首部
          if (gmii_rx_dv) begin
            cnt <= cnt + 5'd1;
            if (cnt == 5'd4) udp_byte_num[15:8] <= gmii_rxd;  //解析UDP字节长度 
            else if (cnt == 5'd5) udp_byte_num[7:0] <= gmii_rxd;
            else if (cnt == 5'd7) begin
              //有效数据字节长度,(UDP首部8个字节,所以减去8)
              data_byte_num <= udp_byte_num - 16'd8;
              skip_en <= 1'b1;
              cnt <= 5'd0;
            end
          end
        end
        st_rx_data: begin  //接收数据,转换成32bit            
          if (gmii_rx_dv) begin
            data_cnt   <= data_cnt + 16'd1;
            rec_en_cnt <= rec_en_cnt + 2'd1;
            if (data_cnt == data_byte_num - 16'd1) begin
              skip_en      <= 1'b1;  //有效数据接收完成
              data_cnt     <= 16'd0;
              rec_en_cnt   <= 2'd0;
              rec_pkt_done <= 1'b1;
              rec_en       <= 1'b1;
              rec_byte_num <= data_byte_num;
            end
            //先收到的数据放在了rec_data的高位,所以当数据不是4的倍数时,
            //低位数据为无效数据,可根据有效字节数来判断(rec_byte_num)
            if (rec_en_cnt == 2'd0) rec_data[31:24] <= gmii_rxd;
            else if (rec_en_cnt == 2'd1) rec_data[23:16] <= gmii_rxd;
            else if (rec_en_cnt == 2'd2) rec_data[15:8] <= gmii_rxd;
            else if (rec_en_cnt == 2'd3) begin
              rec_en <= 1'b1;
              rec_data[7:0] <= gmii_rxd;
            end
          end
        end
        st_rx_end: begin  //单包数据接收完成   
          //UDP接收模块在前导码、MAC地址或IP地址错误时,应跳至st_rx_end状态而非st_idle,以避免将有效数据误判为前导码。
          //只有在eth_rxdv信号为0,即单包数据接收完毕时,st_rx_end才跳转至st_idle,准备接收下一批数据。
          if (gmii_rx_dv == 1'b0 && skip_en == 1'b0) skip_en <= 1'b1;
        end
        default: ;
      endcase
    end
  end

endmodule

3.11 UDP发送模块

`timescale 1ns / 1ps
//以太网数据发送模块
//UDP发送模块按照UDP的数据格式发送数据,并将32位用户数据转成8位数据,也就是接收模块的逆过程。

module udp_tx (
    input clk,   //时钟信号
    input rst_n, //复位信号,低电平有效

    input             tx_start_en,  //以太网开始发送信号
    input      [31:0] tx_data,      //以太网待发送数据  
    input      [15:0] tx_byte_num,  //以太网发送的有效字节数
    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        tx_req,       //读数据请求信号
    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 = 7'b000_0001;  //初始状态,等待开始发送信号
  localparam st_check_sum = 7'b000_0010;  //IP首部校验和
  localparam st_preamble = 7'b000_0100;  //发送前导码+帧起始界定符
  localparam st_eth_head = 7'b000_1000;  //发送以太网帧头
  localparam st_ip_head = 7'b001_0000;  //发送IP首部+UDP首部
  localparam st_tx_data = 7'b010_0000;  //发送数据
  localparam st_crc = 7'b100_0000;  //发送CRC校验值

  localparam ETH_TYPE = 16'h0800;  //以太网协议类型 IP协议
  //以太网数据最小46个字节,IP首部20个字节+UDP首部8个字节
  //所以数据至少46-20-8=18个字节
  localparam MIN_DATA_NUM = 16'd18;
  localparam UDP_TYPE = 8'd17;  //UDP协议类型  

  //reg define
  reg [6:0] cur_state;
  reg [6:0] next_state;

  reg [7:0] preamble[7:0];  //前导码
  reg [7:0] eth_head[13:0];  //以太网首部
  reg [31:0] ip_head[6:0];  //IP首部 + UDP首部

  reg start_en_d0;
  reg start_en_d1;
  reg [15:0] tx_data_num;  //发送的有效数据字节个数
  reg [15:0] total_num;  //总字节数
  reg trig_tx_en;
  reg [15:0] udp_num;  //UDP字节数
  reg skip_en;  //控制状态跳转使能信号
  reg [4:0] cnt;
  reg [31:0] check_buffer;  //首部校验和
  reg [1:0] tx_byte_sel;  //32位数据转8位数据计数器
  reg [15:0] data_cnt;  //发送数据个数计数器
  reg tx_done_t;
  reg [4:0] real_add_cnt;  //以太网数据实际多发的字节数

  //wire define                       
  wire pos_start_en;  //开始发送数据上升沿
  wire [15:0] real_tx_data_num;  //实际发送的字节数(以太网最少字节要求)
  //*****************************************************
  //**                    main code
  //*****************************************************

  assign pos_start_en = (~start_en_d1) & start_en_d0;
  assign real_tx_data_num = (tx_data_num >= MIN_DATA_NUM) ? tx_data_num : MIN_DATA_NUM;

  //采tx_start_en的上升沿
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      start_en_d0 <= 1'b0;
      start_en_d1 <= 1'b0;
    end else begin
      start_en_d0 <= tx_start_en;
      start_en_d1 <= start_en_d0;
    end
  end

  //寄存数据有效字节
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      tx_data_num <= 16'd0;
      total_num <= 16'd0;
      udp_num <= 16'd0;
    end else begin
      if (pos_start_en && cur_state == st_idle) begin
        //数据长度
        tx_data_num <= tx_byte_num;
        //UDP长度:UDP首部长度 + 有效数据            
        udp_num <= tx_byte_num + 16'd8;
        //IP长度:IP首部长度 + UDP首部 + 有效数据             
        total_num <= tx_byte_num + 16'd20 + 16'd8;
      end
    end
  end

  //触发发送信号
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) trig_tx_en <= 1'b0;
    else trig_tx_en <= pos_start_en;
  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_check_sum;
        else next_state = st_idle;
      end
      st_check_sum: begin  //IP首部校验
        if (skip_en) next_state = st_preamble;
        else next_state = st_check_sum;
      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_ip_head;
        else next_state = st_eth_head;
      end
      st_ip_head: begin  //发送IP首部+UDP首部               
        if (skip_en) next_state = st_tx_data;
        else next_state = st_ip_head;
      end
      st_tx_data: begin  //发送数据                  
        if (skip_en) next_state = st_crc;
        else next_state = st_tx_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 <= 5'd0;
      check_buffer <= 32'd0;
      ip_head[1][31:16] <= 16'd0;
      tx_byte_sel <= 2'b0;
      crc_en <= 1'b0;
      gmii_tx_en <= 1'b0;
      gmii_txd <= 8'd0;
      tx_req <= 1'b0;
      tx_done_t <= 1'b0;
      data_cnt <= 16'd0;
      real_add_cnt <= 5'd0;
      //初始化数组    
      //前导码 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;
      //目的MAC地址
      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];
      //源MAC地址
      eth_head[6] <= BOARD_MAC[47:40];
      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];
    end else begin
      skip_en <= 1'b0;
      tx_req <= 1'b0;
      crc_en <= 1'b0;
      gmii_tx_en <= 1'b0;
      tx_done_t <= 1'b0;
      case (next_state)
        st_idle: begin  //初始状态
          if (trig_tx_en) begin
            skip_en <= 1'b1;
            //版本号:4 首部长度:5(单位:32bit,20byte/4=5)
            ip_head[0] <= {8'h45, 8'h00, total_num};
            //16位标识,每次发送累加1      
            ip_head[1][31:16] <= ip_head[1][31:16] + 1'b1;
            //bit[15:13]: 010表示不分片
            ip_head[1][15:0] <= 16'h4000;
            //协议:17(udp)                  
            ip_head[2] <= {8'h40, UDP_TYPE, 16'h0};
            //源IP地址               
            ip_head[3] <= BOARD_IP;
            //目的IP地址    
            if (des_ip != 32'd0) ip_head[4] <= des_ip;
            else ip_head[4] <= DES_IP;
            //16位源端口号:1234  16位目的端口号:1234                      
            ip_head[5] <= {16'd1234, 16'd1234};
            //16位udp长度,16位udp校验和              
            ip_head[6] <= {udp_num, 16'h0000};
            //更新MAC地址
            if (des_mac != 48'b0) begin
              //目的MAC地址
              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];
            end
          end
        end
        st_check_sum: begin  //IP首部校验和计算
          cnt <= cnt + 5'd1;
          if (cnt == 5'd0) begin
            check_buffer <= ip_head[0][31:16] + ip_head[0][15:0]
                                    + ip_head[1][31:16] + ip_head[1][15:0]
                                    + ip_head[2][31:16] + ip_head[2][15:0]
                                    + ip_head[3][31:16] + ip_head[3][15:0]
                                    + ip_head[4][31:16] + ip_head[4][15:0];
          end else if (cnt == 5'd1)  //可能出现进位,累加一次
            check_buffer <= check_buffer[31:16] + check_buffer[15:0];
          else if (cnt == 5'd2) begin  //可能再次出现进位,累加一次
            check_buffer <= check_buffer[31:16] + check_buffer[15:0];
          end else if (cnt == 5'd3) begin  //按位取反 
            skip_en <= 1'b1;
            cnt <= 5'd0;
            ip_head[2][15:0] <= ~check_buffer[15:0];
          end
        end
        st_preamble: begin  //发送前导码+帧起始界定符
          gmii_tx_en <= 1'b1;
          gmii_txd   <= preamble[cnt];
          if (cnt == 5'd7) begin
            skip_en <= 1'b1;
            cnt <= 5'd0;
          end else cnt <= cnt + 5'd1;
        end
        st_eth_head: begin  //发送以太网首部
          gmii_tx_en <= 1'b1;
          crc_en <= 1'b1;
          gmii_txd <= eth_head[cnt];
          if (cnt == 5'd13) begin
            skip_en <= 1'b1;
            cnt <= 5'd0;
          end else cnt <= cnt + 5'd1;
        end
        st_ip_head: begin  //发送IP首部 + UDP首部
          crc_en <= 1'b1;
          gmii_tx_en <= 1'b1;
          tx_byte_sel <= tx_byte_sel + 2'd1;
          if (tx_byte_sel == 2'd0) gmii_txd <= ip_head[cnt][31:24];
          else if (tx_byte_sel == 2'd1) gmii_txd <= ip_head[cnt][23:16];
          else if (tx_byte_sel == 2'd2) begin
            gmii_txd <= ip_head[cnt][15:8];
            if (cnt == 5'd6) begin
              //提前读请求数据,等待数据有效时发送
              tx_req <= 1'b1;
            end
          end else if (tx_byte_sel == 2'd3) begin
            gmii_txd <= ip_head[cnt][7:0];
            if (cnt == 5'd6) begin
              skip_en <= 1'b1;
              cnt <= 5'd0;
            end else cnt <= cnt + 5'd1;
          end
        end
        st_tx_data: begin  //发送数据
          crc_en <= 1'b1;
          gmii_tx_en <= 1'b1;
          tx_byte_sel <= tx_byte_sel + 2'd1;
          if (tx_byte_sel == 1'b0) gmii_txd <= tx_data[31:24];
          else if (tx_byte_sel == 2'd1) gmii_txd <= tx_data[23:16];
          else if (tx_byte_sel == 2'd2) begin
            gmii_txd <= tx_data[15:8];
            if (data_cnt != tx_data_num - 16'd2) tx_req <= 1'b1;
          end else if (tx_byte_sel == 2'd3) gmii_txd <= tx_data[7:0];
          if (data_cnt < tx_data_num - 16'd1) data_cnt <= data_cnt + 16'd1;
          else if (data_cnt == tx_data_num - 16'd1) begin
            //如果发送的有效数据少于18个字节,在后面填补充位
            //补充的值为最后一次发送的有效数据
            tx_req <= 1'b0;
            if (data_cnt + real_add_cnt < real_tx_data_num - 16'd1)
              real_add_cnt <= real_add_cnt + 5'd1;
            else begin
              skip_en <= 1'b1;
              data_cnt <= 16'd0;
              real_add_cnt <= 5'd0;
              tx_byte_sel <= 2'd0;
            end
            if (real_add_cnt > 0) begin
              gmii_txd <= 8'd0;
            end
          end
        end
        st_crc: begin  //发送CRC校验值
          gmii_tx_en  <= 1'b1;
          tx_byte_sel <= tx_byte_sel + 2'd1;
          if (tx_byte_sel == 2'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 (tx_byte_sel == 2'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 (tx_byte_sel == 2'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 (tx_byte_sel == 2'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;
          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

3.12 控制模块

`timescale 1ns / 1ps
//以太网控制模块
//GMII接口的输出信号根据当前的协议切换信号来确定是发送UDP数据还是ARP数据。

module eth_ctrl (
    input            clk,              //系统时钟
    input            rst_n,            //系统复位信号,低电平有效 
    //ARP相关端口信号                                   
    input            arp_rx_done,      //ARP接收完成信号
    input            arp_rx_type,      //ARP接收类型 0:请求  1:应答
    output reg       arp_tx_en,        //ARP发送使能信号
    output           arp_tx_type,      //ARP发送类型 0:请求  1:应答
    input            arp_tx_done,      //ARP发送完成信号
    input            arp_gmii_tx_en,   //ARP GMII输出数据有效信号 
    input      [7:0] arp_gmii_txd,     //ARP GMII输出数据
    //UDP相关端口信号
    input            udp_tx_start_en,  //UDP开始发送信号
    input            udp_tx_done,      //UDP发送完成信号
    input            udp_gmii_tx_en,   //UDP GMII输出数据有效信号  
    input      [7:0] udp_gmii_txd,     //UDP GMII输出数据   
    //GMII发送引脚                     
    output           gmii_tx_en,       //GMII输出数据有效信号 
    output     [7:0] gmii_txd          //UDP GMII输出数据 
);

  //reg define
  reg protocol_sw;  //协议切换信号,为1时正在处理UDP,为0时正在处理ARP
  reg udp_tx_busy;  //UDP正在发送数据标志信号
  reg arp_rx_flag;  //接收到ARP请求信号的标志

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

  assign arp_tx_type = 1'b1;  //ARP发送类型固定为ARP应答,上位机发送ARP请求,获得开发板MAC地址                                   
  assign gmii_tx_en = protocol_sw ? udp_gmii_tx_en : arp_gmii_tx_en;//为1时gmii接口发送使能为udp发送使能
  assign gmii_txd = protocol_sw ? udp_gmii_txd : arp_gmii_txd;  //为1时gmii接口发送udp数据

  //控制UDP发送忙信号
  //当udp发送开始时,udp发送忙信号置为1;当udp发送完成之后或者没有发送,udp发送忙信号置为0
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) udp_tx_busy <= 1'b0;
    else if (udp_tx_start_en) udp_tx_busy <= 1'b1;
    else if (udp_tx_done) udp_tx_busy <= 1'b0;
  end

  //控制接收到ARP请求信号的标志
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) arp_rx_flag <= 1'b0;
    else if (arp_rx_done && (arp_rx_type == 1'b0)) arp_rx_flag <= 1'b1;
    else if (protocol_sw == 1'b0) arp_rx_flag <= 1'b0;
  end

  //控制protocol_sw和arp_tx_en信号
  //根据UDP发送开始信号和ARP请求标志来切换协议处理状态,并控制ARP发送使能
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      protocol_sw <= 1'b0;
      arp_tx_en   <= 1'b0;
    end else begin
      arp_tx_en <= 1'b0;
      if (udp_tx_start_en) protocol_sw <= 1'b1;
      else if (arp_rx_flag && (udp_tx_busy == 1'b0)) begin
        protocol_sw <= 1'b0;
        arp_tx_en   <= 1'b1;
      end
    end
  end

endmodule

3.13 引脚约束

create_clock -period 8.000 -name eth_rxc [get_ports eth_rxc]

set_property IOSTANDARD LVCMOS33 [get_ports sys_rst_n]
set_property PACKAGE_PIN AH11 [get_ports sys_rst_n]

set_property IOSTANDARD LVCMOS18 [get_ports {eth_rxd[3]}]
set_property IOSTANDARD LVCMOS18 [get_ports {eth_rxd[2]}]
set_property IOSTANDARD LVCMOS18 [get_ports {eth_rxd[1]}]
set_property IOSTANDARD LVCMOS18 [get_ports {eth_rxd[0]}]
set_property IOSTANDARD LVCMOS18 [get_ports {eth_txd[3]}]
set_property IOSTANDARD LVCMOS18 [get_ports {eth_txd[2]}]
set_property IOSTANDARD LVCMOS18 [get_ports {eth_txd[1]}]
set_property IOSTANDARD LVCMOS18 [get_ports {eth_txd[0]}]
set_property IOSTANDARD LVCMOS18 [get_ports eth_rx_ctl]
set_property IOSTANDARD LVCMOS18 [get_ports eth_rxc]
set_property IOSTANDARD LVCMOS18 [get_ports eth_tx_ctl]
set_property IOSTANDARD LVCMOS18 [get_ports eth_txc]
set_property PACKAGE_PIN F7 [get_ports {eth_rxd[3]}]
set_property PACKAGE_PIN G8 [get_ports {eth_rxd[2]}]
set_property PACKAGE_PIN F6 [get_ports {eth_rxd[1]}]
set_property PACKAGE_PIN G6 [get_ports {eth_rxd[0]}]
set_property PACKAGE_PIN D9 [get_ports {eth_txd[3]}]
set_property PACKAGE_PIN E9 [get_ports {eth_txd[2]}]
set_property PACKAGE_PIN E8 [get_ports {eth_txd[1]}]
set_property PACKAGE_PIN D7 [get_ports {eth_txd[0]}]
set_property PACKAGE_PIN E5 [get_ports eth_rxc]
set_property PACKAGE_PIN F8 [get_ports eth_tx_ctl]
set_property PACKAGE_PIN D6 [get_ports eth_txc]
set_property PACKAGE_PIN D5 [get_ports eth_rx_ctl]






4 验证

使用网络调试助手进行验证

本地IP设置为 192.168.1.102 端口1234

目标IP设置为 192.168.1.10 端口1234

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UDP FPGA接收模块是一个用于接收UDP协议数据的模块。该模块通过解析数据包中的协议类型,识别出UDP协议,并提取出UDP数据。 在FPGA中,接收模块名为eth_receive,它能够识别并处理UDP协议的数据。当接收到UDP数据时,eth_receive模块会产生cmd_flag和cmd_data信号,其中cmd_flag与UDP包中的数据对齐,cmd_data则是UDP数据总线。 为了测试UDP FPGA接收模块的功能,可以使用网络调试助手向FPGAUDP数据包。比如送1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9这样的18个数据FPGA接收到这些UDP数据后,eth_receive模块会提取出这些数据,并将其传递给后续的处理模块进行进一步处理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [FPGA实现千兆网口UDP协议收实例](https://download.csdn.net/download/weixin_40615338/86511024)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [FPGA千兆网系列2-----UDP送与接收](https://blog.csdn.net/chengfengwenalan/article/details/84501584)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值