首页 > 其他分享 >FPGA千兆网口学习

FPGA千兆网口学习

时间:2024-11-29 17:32:44浏览次数:13  
标签:FPGA IP rx 千兆 gmii crc 网口 input reg

   学习FPGA过程中~,有挺多不会的地方,有指正的我一定耐心接受。我跟着小梅哥课程视频学的。

  不论是再MCU还是FPGA中都涉及有以太网项目,以前学单片机以太网的时候是用的W5500的以太网模块,网上有挺多开源项目,加上商家给的例程结合结合,改一改就可以完成单片机的以太网项目,那时候就会涉及到“OSI七层模型和TCP/IP五层模型”。可以在网上搜一搜,了解了解。

  一般说的FPGA UDP通信,FPGA只做到了传输层,传输层以上的会话层、表示层等等,FPGA是没有的。FPGA 开发板通过一片 以太网PHY芯片 提供对以太网连接的支持,PHY芯片内提供物理层,进行4b/10b编码,PHY芯片提供MII/GMII/RGMII 接口的MAC连接。

  在传输层中 TCP 和 UDP都是传输层协议,它们都属于TCP/IP协议族。

FPGA UDP通信硬件构成

  FPGA 和 PHY 芯片实现以太网传输的数据链路两端,有三种接口形式:MII、GMII、RGMII,其中MII适用于百兆网口,GMII和RGMII都用于百兆网口,GMII的传输是四位四位传输,RGMII是八位数据传输,所以RGMII有更高的数据传输速率。

MII接口

  MII的接口图

信号位宽说明
TX_ER1 发送数据错误提示信号,同步于 TX_CLK ,高电平 有效,表示 TX_ER 有效期内传输的数据无效。对于 10Mbps 速率 下,TX_ER 不起作用。
TX_EN1 发送使能信号,只有在 TX_EN 有效期内传的数 据才有效。
TX_CLK1 发送参考时钟, 100Mbps 速率下,时钟频率 25MHz , 10Mbps 速率 下,时钟频率为 2.5MHz 。需要注意的是, TX_CLK 时钟的方向是从 PHY 侧指向 MAC 侧的,此时钟是由 PHY 提供的
TXD4数据发送
RX_ER1 接收数据错误提示信号,同步于 RX_CLK ,高电平 有效,表示 RX_ER 有效期内传输的数据无效。对于 10Mbps 速率 下,RX_ER 不起作用。
RX_DV1 接收数据有效信号,作用类似于发送通道的 TX_EN。
RXD4数据接收
RX_CLK 1 接收数据参考时钟, 100Mbps 速率下,时钟频率为 25MHz , 10Mbps 速率下,时钟频率为 2.5MHz 。 RX_CLK 也是由 PHY 侧提供的。
CRS 1 即 Carrier Sense ,载波侦测信号,不需要同步于参考时钟,只要有数 据传输,CRS 就有效。需要注意的是 CRS 只有 PHY 在半双工模式下 有效。(感觉没啥用,可能是我没用上的缘故)
COL 1 冲突检测信号,不需要同步于参考时钟。需要 注意的是 COL 只有 PHY 在半双工模式下有效。(感觉没啥用,可能是我没用上的缘故)

GMII接口

信号位宽说明
TX_ER1 发送数据错误提示信号,同步于G TX_CLK ,高电平 有效,表示 TX_ER 有效期内传输的数据无效。对于 10Mbps 速率 下,TX_ER 不起作用。
TX_EN1 发送使能信号,只有在 TX_EN 有效期内传的数 据才有效。
GTX_CLK1 发送参考时钟,时钟频率为 125MHz 。需要注意的是, GTX_CLK 时钟的方向是从 MAC 侧指向 PHY 侧的,此时钟是由 MAC 提供 的,这里与 MII 接口有所差别的地方。
TXD4数据发送
RX_ER1 接收数据错误提示信号,同步于 RX_CLK ,高电平 有效,表示 RX_ER 有效期内传输的数据无效。对于 10Mbps 速率 下,RX_ER 不起作用。
RX_DV1 接收数据有效信号,作用类似于发送通道的 TX_EN。
RXD4数据接收
RX_CLK 1 接收数据参考时钟,时钟频率为 125MHz 。 RX_CLK 是由 PHY 侧提 供的。
CRS 1 即 Carrier Sense ,载波侦测信号,不需要同步于参考时钟,只要有数 据传输,CRS 就有效。需要注意的是 CRS 只有 PHY 在半双工模式下 有效。(感觉没啥用,可能是我没用上的缘故)
COL 1 冲突检测信号,不需要同步于参考时钟。需要 注意的是 COL 只有 PHY 在半双工模式下有效。(感觉没啥用,可能是我没用上的缘故)

RGMII接口

  RGMII属于GMII的简化版,数据传输效率也没那么高。

信号位宽说明
TX_CLK1 发送参考时钟, 1000Mbps 速率下,时钟频率为 125MHz , TX_CLK 时钟是由 MAC 提供的
TXD4 在时钟 TX_CLK 的上升沿发送 GMII 接口中的 TXD[3:0] ,在时钟 TX_CLK 的下降沿发送 GMII 接口中的 TXD[7:4] 。
TX_CTL1 该信号线上传送 GMII 接口中的 TX_EN 和 TX_ER 两种信息,在 TX_CLK 的上升沿发送 TX_EN ,下降沿发送 TX_ER 。
RX_CTL1 该信号线上传送 GMII 接口中的 RX_DV 和 RX_ER 两种信息,在 RX_CLK 的上升沿传输 RX_DV ,下降沿传输 RX_ER 。
RXD4 时钟 RX_CLK 的上升沿传输 GMII 接口中的 RXD[3:0] ,在时钟 RX_CLK 的下降沿发送 GMII 接口中的 RXD[7:4] 。
RX_CLK1 接收数据参考时钟, 1000Mbps 速率下,时钟频率为 125MHz 。 RX_CLK 由 PHY 侧提供的。

  根据以上介绍,我们使用RGMII接口的以太网 PHY 与 MAC的连接实现方法,解决了接口问题,后面学习的是以太网的数据组成。

以太网帧格式

前同步码(前导码):7 个字节的 0x55,用于帧的同步;

SFD(帧开始符):标明下一个字节为目的 MAC 字段;

目的地址:数据接收者;

源地址:数据发送者;

长度/类型:当这两个字节的值小于 1518 时,那么它就代表其后数据字 段的长度;如果这两个字节的值大于 1518,则表示该以太 网帧中的数据属于哪个上层协议(例如 0x800,代表 IP 数 据包;0x806,代表 ARP 数据包等)。

数据和填充:要发送的数据;

FCS(校验):判断是否帧发生错误,错误就丢弃。

想了解具体过程的可以去b站找一下课程看看。

RGMII和GMII转换电路设计

  RGMII 是 IEEE802.3z 标准中定义的千兆媒体独立接口( Gigabit Medium Independent Interface ) GMII 的一个替代品。RGMII 数据在时钟的上升沿和下降沿均进行采样。对于 FPGA 来说,实现 RGMII 接口的发送是一个非常直接的过程, 整个发送逻辑框图如图:

  gmii_to_rgmii

  设计实现时,我们需要使用 xilinx 的 ODDR ( Output Double Data Rate ,输出双倍数据速率)原语,将该接口使用 OLOGIC 块实现。 OLOGIC 块在 7 系列 FPGA 内的位置紧挨着 IOB ,其作用是 FPGA 通过 IOB 发送数据到器件外部的专用同步块。在 OLOGIC 块中,有着专用的 寄存器,用于实现输出 DDR 寄存器,当我们实例化 ODDR 原语时便会自动访问该功能。ODDR 原语只有一个时钟输入,下降沿数据由输入时钟的本地反转来计时,反馈到 I/O 块的所有的时钟被完全复用,ODDR 原语的框图如图。  各端口功能:
Port NameFunctionDescription
Q数据输出ODDR寄存器输出
C时钟输入端口时钟输入引脚
CE时钟使能端口 CE 表示时钟使能引脚。当断言为低时,此端口禁用端口Q 上的输出时钟。
D1和D2数据输入 ODDR 寄存器输入
S/R设置/复位 同步 / 异步的设置 / 复位引脚。该引脚高电平有效

  一开始用的mdy的板子,然后就开始在b站上看着课程学,但他课程里面没有适配于这个我用的板子的代码,然后看小梅哥还有网上现有的以太网代码的时候,就看到这个,在哪抓耳挠腮的像这个是啥啊,也没交过啊,然后后来看到小梅哥的课程时候才知道这是什么,然后小梅哥也交了该怎么用,真不戳啊(感觉小梅哥售后群回答问题挺快,相比之前前者就是不咋回,问售后的问题两天也没回,很气)

  言归正传,说实话我还没用好这个,可以专门找原语的内容看一看。下面是gmii_to_rgmii的代码,这个代码不是我自己写的,我也是学习,大家一起学哈。对于 RGMII 发送,只需要例化 6 个 ODDR 就可以实现,其中 4 个用来发送 4 位的 TXD 信号; 一个用来发送 TXEN 和 TXER 信号。另一个则用来输出 TX_CLK 信号。

module gmii_to_rgmii(
  reset_n,

  gmii_tx_clk,
  gmii_txd,
  gmii_txen,
  gmii_txer,

  rgmii_tx_clk,
  rgmii_txd,
  rgmii_txen
);



  input        reset_n;

  input        gmii_tx_clk;
  input  [7:0] gmii_txd;
  input        gmii_txen;
  input        gmii_txer;
  
  output       rgmii_tx_clk;
  output [3:0] rgmii_txd;
  output       rgmii_txen;

  genvar i;
  generate
    for(i=0;i<4;i=i+1)
    begin: rgmii_txd_o
      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_rgmii_txd (
        .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[i+4]    ), // 1-bit data input (negative edge)
        .R   (~reset_n         ), // 1-bit reset
        .S   (1'b0             )  // 1-bit set
      );
    end
  endgenerate

  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_rgmii_txd (
    .Q   (rgmii_txen          ), // 1-bit DDR output
    .C   (gmii_tx_clk         ), // 1-bit clock input
    .CE  (1'b1                ), // 1-bit clock enable input
    .D1  (gmii_txen           ), // 1-bit data input (positive edge)
    .D2  (gmii_txen^gmii_txer ), // 1-bit data input (negative edge)
    .R   (~reset_n            ), // 1-bit reset
    .S   (1'b0                )  // 1-bit set
  );

  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_rgmii_clk (
    .Q   (rgmii_tx_clk  ), // 1-bit DDR output
    .C   (gmii_tx_clk   ), // 1-bit clock input
    .CE  (1'b1          ), // 1-bit clock enable input
    .D1  (1'b1          ), // 1-bit data input (positive edge)
    .D2  (1'b0          ), // 1-bit data input (negative edge)
    .R   (~reset_n      ), // 1-bit reset
    .S   (1'b0          )  // 1-bit set
  );

endmodule
  对于 rgmii_tx_clk 的输出也是使用 ODDR 输出的,而非使用 PLL 产生并输出。   以下是课程教程里给出的解释:“使用 ODDR 输出 rgmii_tx_clk 信号的最大优势在于,可以最大程度上保证输出的rgmii_tx_clk 与 rgmii_txd[3:0]保持边沿对齐关系。因为从实现原理上来讲,rgmii_tx_clk 和rgmii_txd[3:0]都是 ODDR 在 gmii_tx_clk 的驱动下输出其高位和低位的数据,这样能够保证两者的传输路径的时序模型完全一致,从而使得 rgmii_tx_clk 和 rgmii_txd[3:0]都能在同一时刻发生变化, 实现更加可靠的时钟和数据边沿对齐特性。只是这种情况下,在 PHY 侧,要么通过设置,在 PHY 内部对 rgmii_tx_clk 加入时间延迟,要么通过控制 PCB 走线来为rgmii_tx_clk 增加一定时间的延迟。如果对于某些设计时时序没有严格控制好的平台板卡,在 FPGA 侧使用边沿对齐和中心都无法保证数据和时钟到达 PHY 时恰好呈中心对齐或边沿对齐模式,则需要使用 PLL 直接输出 rgmii_tx_clk,并通过实验调整其与 FPGA 内部使用的gmii_tx_clk 的相位来最终达到稳定输出数据的目的。在我们设计的开发板上,一般都是通过设置 PHY 内部对 rgmii_tx_clk 加入时间延迟来实现的。 无需使用 PLL 来调整发送时钟rgmii_tx_clk 的相位。 ”

rgmii_to_gmii

实现 RGMII 接口的接收是一个非常直接的过程, 整个发送逻辑框图如图:

  也涉及IDDR的原语,可取b站看一看这部分,这部分代码跟前一部分类似吧。对于 RGMII 发送,只需要例化 5 个 IDDR 就可以实现,其中 4 个用来接收 4 位的 RXD 信号;一个用来接收 rxdv 信号。这也有部分解释“对于 RGMII 接口来说,rgmii_rx_clk 时钟是由 PHY 产生,提供给 FPGA 的。 而该时钟 在到达 FPGA 内部接收 rgmii_rxd 和 rgmii_rxdv 的 IDDR 时需要与 rgmii_rxd 和 rgmii_rxdv 信号 呈中心对齐的关系。而 rgmii_rxdv 时钟不仅需要作为 IDDR 的接口时钟,在 FPGA 内部还会作 为以太网数据的解包逻辑时钟,是一个典型的时钟信号。所以如果直接使用普通的 FPGA IO 接入该时钟,势必会导致时钟质量下降,从而引起逻辑运行的不稳定。而且,一旦该时钟信 号和 rgmii_rxd 不满足中心对齐的关系,要调整会非常的麻烦。所以,综合考虑,将该时钟 信号接入了 FPGA 的专用时钟管脚,使其能够直接进入 FPGA 内部的 PLL,也能够直接上 FPGA 的全局时钟资源。在保证时钟质量的同时,还能确保万一该时钟相位无法满足 rgmii_rxd 的 捕获要求,还能通过 FPGA 内部的 PLL 进行相位调制,以确保最终 rgmii_rxd 能够被正确接 收。 所以在上述框图中,rgmii_rx_clk 接入 FPGA 后并没有直接接到 IDDR 的时钟脚,而是先 进入了 PLL,由 PLL 调整到合适的相位之后,再用来捕获 rgmii_rxd"。

module rgmii_to_gmii(
    reset_n,
    gmii_rx_clk,    
    gmii_rxdv,
    gmii_rxd,
    gmii_rxerr,

    rgmii_rx_clk,
    rgmii_rxd,
    rgmii_rxdv
);
    input         reset_n;

    output        gmii_rx_clk;
    output [7:0]  gmii_rxd;
    output        gmii_rxdv;
    output        gmii_rxerr;

    input         rgmii_rx_clk;
    input  [3:0]  rgmii_rxd;
    input         rgmii_rxdv; 
    wire gmii_rxer;

    assign gmii_rx_clk = rgmii_rx_clk;
    assign gmii_rxerr = gmii_rxer^gmii_rxdv ;

    genvar i;
    generate
        for(i=0;i<4;i=i+1)
        begin: rgmii_rxd_i
        IDDR #(
        // "OPPOSITE_EDGE", "SAME_EDGE" or "SAME_EDGE_PIPELINED" 
            .DDR_CLK_EDGE("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" 
        ) IDDR_rxd (
            .Q1(gmii_rxd[i]),//1-bit output for positive edge of clock
            .Q2(gmii_rxd[i+4]),//1-bit output for negative edge of clock
            .C    (rgmii_rx_clk  ), // 1-bit clock input
            .CE   (1'b1          ), // 1-breset_nit clock enable input
            .D    (rgmii_rxd[i]  ), // 1-bit DDR data input
            .R    (!reset_n      ), // 1-bit reset
            .S    (1'b0          )  // 1-bit set
        );
        end
    endgenerate  
    
    IDDR #(
        // "OPPOSITE_EDGE", "SAME_EDGE" or "SAME_EDGE_PIPELINED"
        .DDR_CLK_EDGE("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" 
    ) IDDR_rxdv (
        .Q1(gmii_rxdv), // 1-bit output for positive edge of clock
        .Q2(gmii_rxer), // 1-bit output for negative edge of clock
        .C    (rgmii_rx_clk ), // 1-bit clock input
        .CE   (1'b1         ), // 1-breset_nit clock enable input
        .D    (rgmii_rxdv   ), // 1-bit DDR data input
        .R    (!reset_n     ), // 1-bit reset
        .S    (1'b0         )  // 1-bit set
    );

endmodule

  接口部分的设计代码就是这些,看完这些代码,觉得这两部分是利用IDDR和ODDR模块的性质去例化然后使用完成功能的,不知道这么理解对不对,有懂得知道一下么。

网络层 ARP 协议

  在网络通讯时,源主机的应用程序知道目的主机的 IP 地址和端口号,却不知道目的主 机的硬件(MAC) 地址,而数据包首先是被网卡接收到再去处理上层协议的,如果接收到的数 据包的硬件地址与本机不符,则直接丢弃。因此在通讯前必须获得目的主机的硬件地址。ARP 协议就起到这个作用。   每台主机都维护一个 ARP 缓存表,可以用 arp -a 命令查看。由于网络上的 IP 地址本身是动态分配的(Ipv4 资源严重不足,所以采用动态分配机制),上网时每个设备的 IP 地址在不同的时间点其地址都有可能不同,所以某个网络设备在不同的时间,其 IP 地址可能不一样,所以需要用这种缓存表项定期失效的机制来避免 MAC 地址和不同时刻 IP 地址绑定的冲突。完整的 ARP 帧每个字段的数据意义可去百度一下哦。

引入crc校验机制

  数据信号在传输过程中不可避免地会受到噪声干扰 ,或者信道不理想 ,从而造成的码间干扰而产 生差错 ,即出现误码。 我们通常是通过一个校验码去判断接收的消息是否出现差错。信息 在发送时,发送端会通过某个算法得到一个值,这个值被称为校验码。   当数据报中任何一个字节的内容更改之后,CRC 的值都需要重新计算,这在实际应用中是不现实的。所以引入自动crc校验,直接用在线生成 CRC32 的 Verilog 代码的网站: https://www.easics.com/webtools/crctool但我登不上,等我登上再将这部分补全是怎么用的。 教程代码如下
module crc32_d8
(
  input           clk         ,
  input           reset_p     ,

  input    [7:0]  data        ,
  input           crc_init    ,
  input           crc_en      ,
  output   [31:0] crc_result
);

wire   [7:0]   data_i;
reg    [31:0]  crc_result_o;

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

assign crc_result = ~{crc_result_o[00],crc_result_o[01],crc_result_o[02],crc_result_o[03],crc_result_o[04],crc_result_o[05],crc_result_o[06],crc_result_o[07],
                      crc_result_o[08],crc_result_o[09],crc_result_o[10],crc_result_o[11],crc_result_o[12],crc_result_o[13],crc_result_o[14],crc_result_o[15],
                      crc_result_o[16],crc_result_o[17],crc_result_o[18],crc_result_o[19],crc_result_o[20],crc_result_o[21],crc_result_o[22],crc_result_o[23],
                      crc_result_o[24],crc_result_o[25],crc_result_o[26],crc_result_o[27],crc_result_o[28],crc_result_o[29],crc_result_o[30],crc_result_o[31]};

always @(posedge clk or posedge reset_p)
begin
  if(reset_p)
    crc_result_o <= 32'hffff_ffff;
  else if(crc_init)
    crc_result_o <= 32'hffff_ffff;
  else if(crc_en)
    crc_result_o <= nextCRC32_D8( data_i, crc_result_o);
  else
    crc_result_o <= crc_result_o;
end

  // polynomial: 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
  // data width: 8
  // convention: the first serial bit is D[7]
  function [31:0] nextCRC32_D8;

    input [7:0] Data;
    input [31:0] crc;
    reg [7:0] d;
    reg [31:0] c;
    reg [31:0] newcrc;
  begin
    d = Data;
    c = crc;

    newcrc[0] = d[6] ^ d[0] ^ c[24] ^ c[30];
    newcrc[1] = d[7] ^ d[6] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[30] ^ c[31];
    newcrc[2] = d[7] ^ d[6] ^ d[2] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[26] ^ c[30] ^ c[31];
    newcrc[3] = d[7] ^ d[3] ^ d[2] ^ d[1] ^ c[25] ^ c[26] ^ c[27] ^ c[31];
    newcrc[4] = d[6] ^ d[4] ^ d[3] ^ d[2] ^ d[0] ^ c[24] ^ c[26] ^ c[27] ^ c[28] ^ c[30];
    newcrc[5] = d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[27] ^ c[28] ^ c[29] ^ c[30] ^ c[31];
    newcrc[6] = d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[2] ^ d[1] ^ c[25] ^ c[26] ^ c[28] ^ c[29] ^ c[30] ^ c[31];
    newcrc[7] = d[7] ^ d[5] ^ d[3] ^ d[2] ^ d[0] ^ c[24] ^ c[26] ^ c[27] ^ c[29] ^ c[31];
    newcrc[8] = d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[0] ^ c[24] ^ c[25] ^ c[27] ^ c[28];
    newcrc[9] = d[5] ^ d[4] ^ d[2] ^ d[1] ^ c[1] ^ c[25] ^ c[26] ^ c[28] ^ c[29];
    newcrc[10] = d[5] ^ d[3] ^ d[2] ^ d[0] ^ c[2] ^ c[24] ^ c[26] ^ c[27] ^ c[29];
    newcrc[11] = d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[3] ^ c[24] ^ c[25] ^ c[27] ^ c[28];
    newcrc[12] = d[6] ^ d[5] ^ d[4] ^ d[2] ^ d[1] ^ d[0] ^ c[4] ^ c[24] ^ c[25] ^ c[26] ^ c[28] ^ c[29] ^ c[30];
    newcrc[13] = d[7] ^ d[6] ^ d[5] ^ d[3] ^ d[2] ^ d[1] ^ c[5] ^ c[25] ^ c[26] ^ c[27] ^ c[29] ^ c[30] ^ c[31];
    newcrc[14] = d[7] ^ d[6] ^ d[4] ^ d[3] ^ d[2] ^ c[6] ^ c[26] ^ c[27] ^ c[28] ^ c[30] ^ c[31];
    newcrc[15] = d[7] ^ d[5] ^ d[4] ^ d[3] ^ c[7] ^ c[27] ^ c[28] ^ c[29] ^ c[31];
    newcrc[16] = d[5] ^ d[4] ^ d[0] ^ c[8] ^ c[24] ^ c[28] ^ c[29];
    newcrc[17] = d[6] ^ d[5] ^ d[1] ^ c[9] ^ c[25] ^ c[29] ^ c[30];
    newcrc[18] = d[7] ^ d[6] ^ d[2] ^ c[10] ^ c[26] ^ c[30] ^ c[31];
    newcrc[19] = d[7] ^ d[3] ^ c[11] ^ c[27] ^ c[31];
    newcrc[20] = d[4] ^ c[12] ^ c[28];
    newcrc[21] = d[5] ^ c[13] ^ c[29];
    newcrc[22] = d[0] ^ c[14] ^ c[24];
    newcrc[23] = d[6] ^ d[1] ^ d[0] ^ c[15] ^ c[24] ^ c[25] ^ c[30];
    newcrc[24] = d[7] ^ d[2] ^ d[1] ^ c[16] ^ c[25] ^ c[26] ^ c[31];
    newcrc[25] = d[3] ^ d[2] ^ c[17] ^ c[26] ^ c[27];
    newcrc[26] = d[6] ^ d[4] ^ d[3] ^ d[0] ^ c[18] ^ c[24] ^ c[27] ^ c[28] ^ c[30];
    newcrc[27] = d[7] ^ d[5] ^ d[4] ^ d[1] ^ c[19] ^ c[25] ^ c[28] ^ c[29] ^ c[31];
    newcrc[28] = d[6] ^ d[5] ^ d[2] ^ c[20] ^ c[26] ^ c[29] ^ c[30];
    newcrc[29] = d[7] ^ d[6] ^ d[3] ^ c[21] ^ c[27] ^ c[30] ^ c[31];
    newcrc[30] = d[7] ^ d[4] ^ c[22] ^ c[28] ^ c[31];
    newcrc[31] = d[5] ^ c[23] ^ c[29];
    nextCRC32_D8 = newcrc;
  end
  endfunction

endmodule
  生成的代码里面仅为一个计算 CRC32 的一个函数 function 。 CRC 的计算与除了与生成多 项式相关外,还与 CRC 的初始值,输入输出数据是否位反向以及计算结果是否取反等操作 相关。

IP校验

  IP 是 TCP/IP 协议族中最核心的协议,所有的 TCP 、 UDP 、 ICMP 、 IGMP 数据都以 IP 数据 报的格式传输。IP 数据报的格式如下,IP 数据报的长度/类型段的数值为 0x0800,数据和填充段包括IP 头部数据和 IP 数据两个部分。

 

  其中,和以太网帧具有帧头一样, IP 数据报也包含了一个 IP 报头部分,与 IP 协议相 关的一些信息如 IP 地址,数据包长度等会被打包进 IP 报头中,然后再与需要传输的 IP 报 文数据一起,作为 MAC 帧的数据和填充字段送往 MAC 层发送 。感兴趣的可以去了解了解具体过程,代码如下:
module ip_checksum(
  input           clk            ,
  input           reset_p        ,

  input           cal_en         ,

  input   [3:0]   IP_ver         ,
  input   [3:0]   IP_hdr_len     ,
  input   [7:0]   IP_tos         ,
  input   [15:0]  IP_total_len   ,
  input   [15:0]  IP_id          ,
  input           IP_rsv         ,
  input           IP_df          ,
  input           IP_mf          ,
  input   [12:0]  IP_frag_offset ,
  input   [7:0]   IP_ttl         ,
  input   [7:0]   IP_protocol    ,
  input   [31:0]  src_ip         ,
  input   [31:0]  dst_ip         ,

  output  [15:0]  checksum       
);

  reg  [31:0]suma;
  wire [16:0]sumb;
  wire [15:0]sumc;

  always@(posedge clk or posedge reset_p)
  if(reset_p)
    suma <= 32'd0;
  else if(cal_en)
    suma <= {IP_ver,IP_hdr_len,IP_tos}+IP_total_len+IP_id+
           {IP_rsv,IP_df,IP_mf,IP_frag_offset}+{IP_ttl,IP_protocol}+
           src_ip[31:16]+src_ip[15:0]+dst_ip[31:16]+dst_ip[15:0];
  else
    suma <= suma;

  assign sumb = suma[31:16]+suma[15:0];
  assign sumc = sumb[16]+sumb[15:0];

  assign checksum = ~sumc;

endmodule

UDP协议及FPGA实现

  UDP 协议全称是用户数据报协议,在网络中它与 TCP 协议一样用于处理数据包,是一种 无连接的协议,与所熟知的 TCP(传输控制协议)协议一样,UDP 协议直接位于 IP(网际协议)协议的 顶层。每一个数据包的前 8 个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。从包含关系来说:MAC 帧中的数据段为 IP 数据报,IP 报文中的数据段为 UDP报文,UDP 报文中的数据段为用户希望传输的数据内容, 发送部分:
//这个代码是从整个代码上拆下来的,我尽量注释一下,有挺多不会,请大佬帮个忙
module eth_udp_tx_gmii(
  clk125m,
  reset_p,

  tx_en_pulse,
  tx_done,

  dst_mac,
  src_mac,
  dst_ip,
  src_ip,
  dst_port,
  src_port,

  data_length,
  
  payload_req_o,
  payload_dat_i,

  gmii_tx_clk,
  gmii_txen,
  gmii_txd
);

  input          clk125m;
  input          reset_p;

  input          tx_en_pulse;
  output reg     tx_done;

  input [47:0]   dst_mac;
  input [47:0]   src_mac;
  input [31:0]   dst_ip;
  input [31:0]   src_ip;
  input [15:0]   dst_port;
  input [15:0]   src_port;

  input [15:0]   data_length;
  
  output         payload_req_o;
  input     [7:0]payload_dat_i;

  output         gmii_tx_clk;
//  (*IOB = "TRUE"*) output  reg[7:0] gmii_txd;
//  (*IOB = "TRUE"*) output  reg      gmii_txen;
  output  reg[7:0] gmii_txd;
  output  reg      gmii_txen;

  parameter ETH_type       = 16'h0800,
            IP_ver         = 4'h4,
            IP_hdr_len     = 4'h5,
            IP_tos         = 8'h00,
            IP_id          = 16'h0000,
            IP_rsv         = 1'b0,
            IP_df          = 1'b0,
            IP_mf          = 1'b0,
            IP_frag_offset = 13'h0000,
            IP_ttl         = 8'h40,
            IP_protocol    = 8'h11;

  localparam
    IDLE          = 8'b00000001,
    TX_PREAMBLE   = 8'b00000010,
    TX_ETH_HEADER = 8'b00000100,
    TX_IP_HEADER  = 8'b00001000,
    TX_UDP_HEADER = 8'b00010000,
    TX_DATA       = 8'b00100000,
    TX_FILL_DATA  = 8'b01000000,
    TX_CRC        = 8'b10000000;

  wire [15:0] IP_total_len;
  wire [15:0] IP_check_sum;
  wire [15:0] udp_length;
  wire [15:0] udp_check_sum = 16'h0000;

  reg  [47:0] dst_mac_reg;
  reg  [47:0] src_mac_reg;
  reg  [15:0] dst_port_reg;
  reg  [15:0] src_port_reg;
  reg  [31:0] dst_ip_reg;
  reg  [31:0] src_ip_reg;
  reg  [15:0] data_length_reg; 
  reg  [15:0] IP_total_len_reg;
  reg  [15:0] udp_length_reg;

  reg  [3:0]  cnt_preamble;
  reg  [3:0]  cnt_eth_header;
  reg  [4:0]  cnt_ip_header;
  reg  [3:0]  cnt_udp_header;
  reg  [15:0] cnt_data;
  reg  [4:0]  cnt_fill_data;
  reg  [1:0]  cnt_crc;
  reg  [7:0]  tx_data;
  reg         tx_en;
  reg  [7:0]  curr_state;
  reg  [7:0]  next_state;

  wire        crc_state;
  reg         crc_state_dly1;
  reg         crc_state_dly2;
  reg  [1:0]  cnt_crc_dly1;
  reg  [1:0]  cnt_crc_dly2;
  reg         tx_en_dly1;
  reg  [7:0]  tx_data_dly1;
  reg         crc_init;
  reg         crc_en_temp;
  reg         crc_en;
  reg  [7:0]  crc_in;
  wire [31:0] crc_result;

  reg  [7:0]  gmii_txd_renewcrc;
  reg         gmii_txen_renewcrc; 

  assign gmii_tx_clk = clk125m;//这个是顶层有一个pll分频出来的125m的时钟,因为这个接口是需要125

  assign udp_length = data_length + 8'd8;   //udp_header: 8byte
  assign IP_total_len = udp_length + 8'd20; //ip_header: 20byte

//校验IP的,说实话,我没弄太明白校验的代码
  ip_checksum ip_checksum(
    .clk            (clk125m        ),
    .reset_p        (reset_p        ),

    .cal_en         (tx_en_pulse    ),

    .IP_ver         (IP_ver         ),
    .IP_hdr_len     (IP_hdr_len     ),
    .IP_tos         (IP_tos         ),
    .IP_total_len   (IP_total_len   ),
    .IP_id          (IP_id          ),
    .IP_rsv         (IP_rsv         ),
    .IP_df          (IP_df          ),
    .IP_mf          (IP_mf          ),
    .IP_frag_offset (IP_frag_offset ),
    .IP_ttl         (IP_ttl         ),
    .IP_protocol    (IP_protocol    ),
    .src_ip         (src_ip         ),
    .dst_ip         (dst_ip         ),

    .checksum       (IP_check_sum   )
  );
  
  //将以太网报文源/目的参数寄存,防止跳变
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
  begin
    dst_mac_reg  <= 48'd0;
    src_mac_reg  <= 48'd0;
    dst_ip_reg   <= 32'd0;
    src_ip_reg   <= 32'd0;
    dst_port_reg <= 16'd0;
    src_port_reg <= 16'd0;
  end
  else if(tx_en_pulse)
  begin
    dst_mac_reg  <= dst_mac;
    src_mac_reg  <= src_mac;
    dst_port_reg <= dst_port;
    src_port_reg <= src_port;
    dst_ip_reg   <= dst_ip;
    src_ip_reg   <= src_ip;
  end

  //将以太网报文数据部分长度参数寄存
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
  begin
    data_length_reg  <= 16'h0;
    IP_total_len_reg <= 16'h0;
    udp_length_reg   <= 16'h0;
  end
  else if(tx_en_pulse)
  begin
    data_length_reg  <= data_length;
    IP_total_len_reg <= IP_total_len;
    udp_length_reg   <= udp_length; 
  end
//发送的状态机准备阶段
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)//这块的条件是这个原因是在整个的顶层文件中定义了reset_p = ~reset_n所以才这么写。
    curr_state <= IDLE;//这个curr_state是为了后面的组合逻辑状态机。
  else
    curr_state <= next_state;

  always@(*)
  begin
    case(curr_state)
      IDLE:
        if(tx_en_pulse)
          next_state = TX_PREAMBLE;
        else
          next_state = IDLE;

      TX_PREAMBLE:
        if(cnt_preamble == 4'd7)
          next_state = TX_ETH_HEADER;
        else
          next_state = TX_PREAMBLE;

      TX_ETH_HEADER:
        if(cnt_eth_header == 4'd13)
          next_state = TX_IP_HEADER;
        else
          next_state = TX_ETH_HEADER;

      TX_IP_HEADER:
        if(cnt_ip_header == 5'd19)
          next_state = TX_UDP_HEADER;
        else
          next_state = TX_IP_HEADER;

      TX_UDP_HEADER:
        if(cnt_udp_header == 4'd7)
          next_state = TX_DATA;
        else
          next_state = TX_UDP_HEADER;

      TX_DATA:
        if(data_length_reg <5'd18 && cnt_data == data_length_reg - 1'b1)
          next_state = TX_FILL_DATA;
        else if(cnt_data == data_length_reg - 1'b1)
          next_state = TX_CRC;
        else
          next_state = TX_DATA;

      TX_FILL_DATA:
        if(cnt_fill_data == 5'd17 - data_length_reg)
          next_state = TX_CRC;
        else
          next_state = TX_FILL_DATA;
        
      TX_CRC:
        if(cnt_crc == 2'd3)
          next_state = IDLE;
        else
          next_state = TX_CRC;

      default:next_state = IDLE;

    endcase
  end
//计数器需要达到对应部位才能跳转
  //cnt_preamble
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    cnt_preamble <= 4'd0;
  else if(curr_state == TX_PREAMBLE)
    cnt_preamble <= cnt_preamble + 1'b1;
  else
    cnt_preamble <= 4'd0;

  //cnt_eth_header
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    cnt_eth_header <= 4'd0;
  else if(curr_state == TX_ETH_HEADER)
    cnt_eth_header <= cnt_eth_header + 1'b1;
  else
    cnt_eth_header <= 4'd0;

  //cnt_ip_header
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    cnt_ip_header <= 5'd0;
  else if(curr_state == TX_IP_HEADER)
    cnt_ip_header <= cnt_ip_header + 1'b1;
  else
    cnt_ip_header <= 5'd0;

  //cnt_udp_header
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    cnt_udp_header <= 4'd0;
  else if(curr_state == TX_UDP_HEADER)
    cnt_udp_header <= cnt_udp_header + 1'b1;
  else
    cnt_udp_header <= 4'd0;

  //cnt_data
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    cnt_data <= 16'd0;
  else if(curr_state == TX_DATA)
    cnt_data <= cnt_data + 1'b1;
  else
    cnt_data <= 16'd0;

  //cnt_fill_data
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    cnt_fill_data <= 5'd0;
  else if(curr_state == TX_FILL_DATA)
    cnt_fill_data <= cnt_fill_data + 1'b1;
  else
    cnt_fill_data <= 5'd0;

  //cnt_crc
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    cnt_crc <= 5'd0;
  else if(curr_state == TX_CRC)
    cnt_crc <= cnt_crc + 1'b1;
  else
    cnt_crc <= 5'd0;

  //tx_data,tx_en
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
  begin
    tx_en   <= 1'b0;
    tx_data <= 8'h00;
  end
  else
  begin
    case(curr_state)
      IDLE:
      begin
        tx_en   <= 1'b0;
        tx_data <= 8'h00;
      end

      TX_PREAMBLE:
      begin
        tx_en <= 1'b1;
        if(cnt_preamble <= 4'd6)
          tx_data <= 8'h55;
        else
          tx_data <= 8'hd5;
      end

      TX_ETH_HEADER:
      begin
        tx_en <= 1'b1;
        case(cnt_eth_header)
          4'd0:   tx_data <= dst_mac_reg[47:40];
          4'd1:   tx_data <= dst_mac_reg[39:32];
          4'd2:   tx_data <= dst_mac_reg[31:24];
          4'd3:   tx_data <= dst_mac_reg[23:16];
          4'd4:   tx_data <= dst_mac_reg[15:8];
          4'd5:   tx_data <= dst_mac_reg[7:0];
          4'd6:   tx_data <= src_mac_reg[47:40];
          4'd7:   tx_data <= src_mac_reg[39:32];
          4'd8:   tx_data <= src_mac_reg[31:24];
          4'd9:   tx_data <= src_mac_reg[23:16];
          4'd10:  tx_data <= src_mac_reg[15:8];
          4'd11:  tx_data <= src_mac_reg[7:0];
          4'd12:  tx_data <= ETH_type[15:8];
          4'd13:  tx_data <= ETH_type[7:0];
          default:tx_data <= 8'h00;
        endcase
      end

      TX_IP_HEADER:
      begin
        tx_en <= 1'b1;
        case(cnt_ip_header)
          5'd0:   tx_data <= {IP_ver,IP_hdr_len};
          5'd1:   tx_data <= IP_tos;
          5'd2:   tx_data <= IP_total_len_reg[15:8];
          5'd3:   tx_data <= IP_total_len_reg[7:0];
          5'd4:   tx_data <= IP_id[15:8];
          5'd5:   tx_data <= IP_id[7:0];
          5'd6:   tx_data <= {IP_rsv,IP_df,IP_mf,IP_frag_offset[12:8]};
          5'd7:   tx_data <= IP_frag_offset[7:0];
          5'd8:   tx_data <= IP_ttl;
          5'd9:   tx_data <= IP_protocol;
          5'd10:  tx_data <= IP_check_sum[15:8];
          5'd11:  tx_data <= IP_check_sum[7:0];
          5'd12:  tx_data <= src_ip_reg[31:24];
          5'd13:  tx_data <= src_ip_reg[23:16];
          5'd14:  tx_data <= src_ip_reg[15:8];
          5'd15:  tx_data <= src_ip_reg[7:0];
          5'd16:  tx_data <= dst_ip_reg[31:24];
          5'd17:  tx_data <= dst_ip_reg[23:16];
          5'd18:  tx_data <= dst_ip_reg[15:8];
          5'd19:  tx_data <= dst_ip_reg[7:0];
          default:tx_data <= 8'h00; 
        endcase
      end

      TX_UDP_HEADER:
      begin
        tx_en <= 1'b1;
        case(cnt_udp_header)
          4'd0:   tx_data <= src_port_reg[15:8];
          4'd1:   tx_data <= src_port_reg[7:0];
          4'd2:   tx_data <= dst_port_reg[15:8];
          4'd3:   tx_data <= dst_port_reg[7:0];
          4'd4:   tx_data <= udp_length_reg[15:8];
          4'd5:   tx_data <= udp_length_reg[7:0];
          4'd6:   tx_data <= udp_check_sum[15:8];
          4'd7:   tx_data <= udp_check_sum[7:0];
          default:tx_data <= 8'h00; 
        endcase
      end

      TX_DATA:
      begin
        tx_en <= 1'b1;
        tx_data <= payload_dat_i;
      end

      TX_FILL_DATA:
      begin
        tx_en <= 1'b1;
        tx_data <= 8'h00;
      end

      TX_CRC:
      begin
        tx_en <= 1'b1;
        tx_data <= 8'h00;
      end

      default:
      begin
        tx_en   <= 1'b0;
        tx_data <= 8'h00;
      end
    endcase
  end

  //payload_req_o
  assign payload_req_o = (curr_state == TX_DATA) ? 1'b1 : 1'b0;

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    crc_init <= 1'b0;
  else if (tx_en_pulse && (curr_state == IDLE))
    crc_init <= 1'b1;
  else 
    crc_init <= 1'b0;

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    crc_en_temp <= 1'b0;
  else if (curr_state == TX_ETH_HEADER || curr_state == TX_IP_HEADER || 
           curr_state == TX_UDP_HEADER || curr_state == TX_FILL_DATA || curr_state == TX_DATA)
    crc_en_temp <= 1'b1;
  else 
    crc_en_temp <= 1'b0;

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    crc_en <= 1'b0;
  else if (crc_en_temp)
    crc_en <= 1'b1;
  else 
    crc_en <= 1'b0;

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    crc_in <= 8'h00;
  else if(crc_en_temp)
    crc_in <= tx_data;
  else
    crc_in <= crc_in;

  crc32_d8 crc32_d8
  (
    .clk         (clk125m    ),
    .reset_p     (reset_p    ),

    .data        (crc_in     ),
    .crc_init    (crc_init   ),
    .crc_en      (crc_en     ),
    .crc_result  (crc_result )//latency=1
  );

  assign crc_state = curr_state == TX_CRC;

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
  begin
    tx_en_dly1   <= 1'b0;
    tx_data_dly1 <= 8'h00;
  end
  else
  begin
    tx_en_dly1   <= tx_en;
    tx_data_dly1 <= tx_data;
  end

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
  begin
    crc_state_dly1 <= 1'b0;
    crc_state_dly2 <= 1'b0;
    cnt_crc_dly1   <= 2'd0;
    cnt_crc_dly2   <= 2'd0;
  end
  else
  begin
    crc_state_dly1 <= crc_state;
    crc_state_dly2 <= crc_state_dly1;
    cnt_crc_dly1   <= cnt_crc;
    cnt_crc_dly2   <= cnt_crc_dly1;
  end

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    gmii_txd_renewcrc <= 8'h00;
  else if(crc_state_dly2)
  begin
    case(cnt_crc_dly2)
      2'd0:gmii_txd_renewcrc <= crc_result[7:0];
      2'd1:gmii_txd_renewcrc <= crc_result[15:8];
      2'd2:gmii_txd_renewcrc <= crc_result[23:16];
      2'd3:gmii_txd_renewcrc <= crc_result[31:24];
    endcase
  end
  else
    gmii_txd_renewcrc <= tx_data_dly1;

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    gmii_txen_renewcrc <= 1'b0;
  else if(tx_en_dly1)
    gmii_txen_renewcrc <= 1'b1;
  else
    gmii_txen_renewcrc <= 1'b0;

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
  begin
    gmii_txen <= 1'b0;
    gmii_txd  <= 8'h00;
  end
  else
  begin
    gmii_txen <= gmii_txen_renewcrc;
    gmii_txd  <= gmii_txd_renewcrc;
  end

  //tx_done
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    tx_done <= 1'b0;
  else if(curr_state == TX_CRC && cnt_crc == 2'd3)
    tx_done <= 1'b1;
  else
    tx_done <= 1'b0;

endmodule

接收部分:

module eth_udp_rx_gmii(
  reset_p,

  local_mac,
  local_ip,
  local_port,

  clk125m_o,
  exter_mac,
  exter_ip,
  exter_port,
  rx_data_length,
  data_overflow_i,
  payload_valid_o,
  payload_dat_o,

  one_pkt_done,
  pkt_error,
  debug_crc_check,

  gmii_rx_clk,  
  gmii_rxdv,
  gmii_rxd
);
  input              reset_p;

  input     [47:0]   local_mac;
  input     [31:0]   local_ip;
  input     [15:0]   local_port;

  output             clk125m_o;
  output reg[47:0]   exter_mac;
  output reg[31:0]   exter_ip;
  output reg[15:0]   exter_port;
  output reg[15:0]   rx_data_length;
  input              data_overflow_i;
  output reg         payload_valid_o;
  output reg[7:0]    payload_dat_o;

  output reg         one_pkt_done;
  output reg         pkt_error;
  output    [31:0]   debug_crc_check;

  input        gmii_rx_clk;
  (*IOB = "TRUE"*) input  [7:0] gmii_rxd;
  (*IOB = "TRUE"*) input        gmii_rxdv;

  parameter ETH_type       = 16'h0800,
            IP_ver         = 4'h4,
            IP_hdr_len     = 4'h5,
            IP_protocol    = 8'h11;

  localparam
    IDLE          = 9'b000000001,
    RX_PREAMBLE   = 9'b000000010,
    RX_ETH_HEADER = 9'b000000100,
    RX_IP_HEADER  = 9'b000001000,
    RX_UDP_HEADER = 9'b000010000,
    RX_DATA       = 9'b000100000,
    RX_DRP_DATA   = 9'b001000000,
    RX_CRC        = 9'b010000000,
    PKT_CHECK     = 9'b100000000;

  wire        clk125m;
  reg  [7:0]  reg_gmii_rxd;
  reg         reg_gmii_rxdv;
  reg  [7:0]  rx_data_dly1;
  reg  [7:0]  rx_data_dly2;
  reg         rx_datav_dly1;
  reg         rx_datav_dly2;
  reg  [47:0] local_mac_reg;
  reg  [31:0] local_ip_reg;
  reg  [15:0] local_port_reg;  
  reg  [8:0]  curr_state;
  reg  [8:0]  next_state;

  reg         reg_data_overflow;

  reg  [47:0] rx_dst_mac;
  reg  [47:0] rx_src_mac;
  reg  [15:0] rx_eth_type;
  reg         eth_header_check_ok;

  reg  [3:0]  rx_ip_ver;
  reg  [3:0]  rx_ip_hdr_len;
  reg  [7:0]  rx_ip_tos;
  reg  [15:0] rx_total_len;
  reg  [15:0] rx_ip_id;
  reg         rx_ip_rsv;
  reg         rx_ip_df;
  reg         rx_ip_mf;
  reg  [12:0] rx_ip_frag_offset;
  reg  [7:0]  rx_ip_ttl;
  reg  [7:0]  rx_ip_protocol;
  reg  [15:0] rx_ip_check_sum;
  reg  [31:0] rx_src_ip;
  reg  [31:0] rx_dst_ip;
  reg         ip_checksum_cal_en;  
  wire [15:0] cal_check_sum;
  reg         ip_header_check_ok;

  reg  [15:0] rx_src_port;
  reg  [15:0] rx_dst_port;
  reg  [15:0] rx_udp_length;
  reg         udp_header_check_ok;

  reg         crc_init;
  reg         crc_en;
  reg  [7:0]  crc_data;
  wire [31:0] crc_check;

  reg  [3:0]  cnt_preamble;
  reg  [3:0]  cnt_eth_header;
  reg  [4:0]  cnt_ip_header;
  reg  [3:0]  cnt_udp_header;
  reg  [15:0] cnt_data;
  reg  [4:0]  cnt_drp_data;

  assign clk125m_o = clk125m;
  assign debug_crc_check = crc_check;

  BUFG BUFG_pclk (
    .O(clk125m        ), // 1-bit output: Clock output
    .I(gmii_rx_clk    )  // 1-bit input: Clock input
  ); 

  //将本地MAC、IP、PORT寄存
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
  begin
    local_mac_reg  <= 48'h00_00_00_00_00_00;
    local_ip_reg   <= 32'h00_00_00_00;
    local_port_reg <= 16'h00_00;
  end
  else if(curr_state == IDLE)
  begin
    local_mac_reg  <= local_mac;
    local_ip_reg   <= local_ip;
    local_port_reg <= local_port;
  end

  //将以太网输入的接收信号寄存
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
  begin
    reg_gmii_rxd  <= 8'h00;
    reg_gmii_rxdv <= 1'b0;
  end
  else
  begin
    reg_gmii_rxd  <= gmii_rxd;
    reg_gmii_rxdv <= gmii_rxdv;
  end

  //将以太网输入的接收信号寄存后打拍
  always@(posedge clk125m)
  begin
    rx_data_dly1  <= reg_gmii_rxd;
    rx_data_dly2  <= rx_data_dly1;
    rx_datav_dly1 <= reg_gmii_rxdv;
    rx_datav_dly2 <= rx_datav_dly1;
  end

  //cnt_preamble
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    cnt_preamble <= 4'd0;
  else if(curr_state == RX_PREAMBLE && rx_data_dly2 == 8'h55)
    cnt_preamble <= cnt_preamble + 1'b1;
  else
    cnt_preamble <= 4'd0;

  //cnt_eth_header
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    cnt_eth_header <= 4'd0;
  else if(curr_state == RX_ETH_HEADER)
    cnt_eth_header <= cnt_eth_header + 1'b1;
  else
    cnt_eth_header <= 4'd0;

  //eth_header
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
  begin
    rx_dst_mac  <= 48'h00_00_00_00_00_00;
    rx_src_mac  <= 48'h00_00_00_00_00_00;
    rx_eth_type <= 16'h0000;
  end    
  else if(curr_state == RX_ETH_HEADER)
  begin
    case(cnt_eth_header)
      4'd0 :rx_dst_mac[47:40] <= rx_data_dly2;
      4'd1 :rx_dst_mac[39:32] <= rx_data_dly2;
      4'd2 :rx_dst_mac[31:24] <= rx_data_dly2;
      4'd3 :rx_dst_mac[23:16] <= rx_data_dly2;
      4'd4 :rx_dst_mac[15:8]  <= rx_data_dly2;
      4'd5 :rx_dst_mac[7:0]   <= rx_data_dly2;

      4'd6 :rx_src_mac[47:40] <= rx_data_dly2;
      4'd7 :rx_src_mac[39:32] <= rx_data_dly2;
      4'd8 :rx_src_mac[31:24] <= rx_data_dly2;
      4'd9 :rx_src_mac[23:16] <= rx_data_dly2;
      4'd10:rx_src_mac[15:8]  <= rx_data_dly2;
      4'd11:rx_src_mac[7:0]   <= rx_data_dly2;

      4'd12:rx_eth_type[15:8] <= rx_data_dly2;
      4'd13:rx_eth_type[7:0]  <= rx_data_dly2;
      default: ;
    endcase
  end
  else
  begin
    rx_dst_mac  <= rx_dst_mac;
    rx_src_mac  <= rx_src_mac;
    rx_eth_type <= rx_eth_type;
  end  

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    eth_header_check_ok <= 1'b0;
  else if(rx_eth_type == ETH_type && (rx_dst_mac == local_mac_reg || rx_dst_mac == 48'hFF_FF_FF_FF_FF_FF))
    eth_header_check_ok <= 1'b1;
  else
    eth_header_check_ok <= 1'b0;

  //cnt_ip_header
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    cnt_ip_header <= 5'd0;
  else if(curr_state == RX_IP_HEADER)
    cnt_ip_header <= cnt_ip_header + 1'b1;
  else
    cnt_ip_header <= 5'd0;  

  //ip_header
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
  begin
    {rx_ip_ver,rx_ip_hdr_len}     <= 8'h0;
    rx_ip_tos                     <= 8'h0;
    rx_total_len                  <= 16'h0;
    rx_ip_id                      <= 16'h0;
    {rx_ip_rsv,rx_ip_df,rx_ip_mf} <= 3'h0;
    rx_ip_frag_offset             <= 13'h0;
    rx_ip_ttl                     <= 8'h0;
    rx_ip_protocol                <= 8'h0;
    rx_ip_check_sum               <= 16'h0;
    rx_src_ip                     <= 32'h0;
    rx_dst_ip                     <= 32'h0;
  end    
  else if(curr_state == RX_IP_HEADER)
  begin
    case(cnt_ip_header)
      5'd0:   {rx_ip_ver,rx_ip_hdr_len}                             <= rx_data_dly2;
      5'd1:   rx_ip_tos                                             <= rx_data_dly2;
      5'd2:   rx_total_len[15:8]                                    <= rx_data_dly2;
      5'd3:   rx_total_len[7:0]                                     <= rx_data_dly2;
      5'd4:   rx_ip_id[15:8]                                        <= rx_data_dly2;
      5'd5:   rx_ip_id[7:0]                                         <= rx_data_dly2;
      5'd6:   {rx_ip_rsv,rx_ip_df,rx_ip_mf,rx_ip_frag_offset[12:8]} <= rx_data_dly2;
      5'd7:   rx_ip_frag_offset[7:0]                                <= rx_data_dly2;
      5'd8:   rx_ip_ttl                                             <= rx_data_dly2;
      5'd9:   rx_ip_protocol                                        <= rx_data_dly2;
      5'd10:  rx_ip_check_sum[15:8]                                 <= rx_data_dly2;
      5'd11:  rx_ip_check_sum[7:0]                                  <= rx_data_dly2;
      5'd12:  rx_src_ip[31:24]                                      <= rx_data_dly2;
      5'd13:  rx_src_ip[23:16]                                      <= rx_data_dly2;
      5'd14:  rx_src_ip[15:8]                                       <= rx_data_dly2;
      5'd15:  rx_src_ip[7:0]                                        <= rx_data_dly2;
      5'd16:  rx_dst_ip[31:24]                                      <= rx_data_dly2;
      5'd17:  rx_dst_ip[23:16]                                      <= rx_data_dly2;
      5'd18:  rx_dst_ip[15:8]                                       <= rx_data_dly2;
      5'd19:  rx_dst_ip[7:0]                                        <= rx_data_dly2;      
      default: ;
    endcase
  end

  //udp_header: 8byte
  //ip_header: 20byte
  //rx_data_length = rx_total_len - udp_header - ip_header;
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    rx_data_length <= 16'd0;
  else if(curr_state == RX_IP_HEADER && cnt_ip_header == 5'd19)
    rx_data_length <= rx_total_len - 8'd20 - 8'd8;
  else
    rx_data_length <= rx_data_length;

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    ip_checksum_cal_en <= 1'b0;
  else if(curr_state == RX_IP_HEADER && cnt_ip_header == 5'd19)
    ip_checksum_cal_en <= 1'b1;
  else
    ip_checksum_cal_en <= 1'b0;

  ip_checksum ip_checksum(
    .clk            (clk125m           ),
    .reset_p        (reset_p           ),
  
    .cal_en         (ip_checksum_cal_en),

    .IP_ver         (rx_ip_ver         ),
    .IP_hdr_len     (rx_ip_hdr_len     ),
    .IP_tos         (rx_ip_tos         ),
    .IP_total_len   (rx_total_len      ),
    .IP_id          (rx_ip_id          ),
    .IP_rsv         (rx_ip_rsv         ),
    .IP_df          (rx_ip_df          ),
    .IP_mf          (rx_ip_mf          ),
    .IP_frag_offset (rx_ip_frag_offset ),
    .IP_ttl         (rx_ip_ttl         ),
    .IP_protocol    (rx_ip_protocol    ),
    .src_ip         (rx_src_ip         ),
    .dst_ip         (rx_dst_ip         ),

    .checksum       (cal_check_sum     )
  ); 

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    ip_header_check_ok <= 1'b0;
  else if({IP_ver,IP_hdr_len,IP_protocol,cal_check_sum,local_ip_reg} == 
          {rx_ip_ver,rx_ip_hdr_len,rx_ip_protocol,rx_ip_check_sum,rx_dst_ip})
    ip_header_check_ok <= 1'b1;
  else
    ip_header_check_ok <= 1'b0;  

  //cnt_udp_header信号在状态处于RX_ETH_HEADER 时,进入计数,否则清零,
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    cnt_udp_header <= 4'd0;
  else if(curr_state == RX_UDP_HEADER)
    cnt_udp_header <= cnt_udp_header + 1'b1;
  else
    cnt_udp_header <= 4'd0;  

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
  begin
    rx_src_port  <= 16'h0;
    rx_dst_port  <= 16'h0;
    rx_udp_length<= 16'h0;
  end    
  else if(curr_state == RX_UDP_HEADER)
  begin
    case(cnt_udp_header)
      4'd0: rx_src_port[15:8]   <= rx_data_dly2;
      4'd1: rx_src_port[7:0]    <= rx_data_dly2;
      4'd2: rx_dst_port[15:8]   <= rx_data_dly2;
      4'd3: rx_dst_port[7:0]    <= rx_data_dly2;
      4'd4: rx_udp_length[15:8] <= rx_data_dly2;
      4'd5: rx_udp_length[7:0]  <= rx_data_dly2;
      default: ;
    endcase
  end

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    udp_header_check_ok <= 1'b0;
  else if(rx_dst_port == local_port_reg)
    udp_header_check_ok <= 1'b1;
  else
    udp_header_check_ok <= 1'b0;

  //cnt_data
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    cnt_data <= 16'd0;
  else if(curr_state == RX_DATA)
    cnt_data <= cnt_data + 1'b1;
  else
    cnt_data <= 16'd0;

  //cnt_drp_data
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    cnt_drp_data <= 5'd0;
  else if(curr_state == RX_DRP_DATA)
    cnt_drp_data <= cnt_drp_data + 1'b1;
  else
    cnt_drp_data <= 5'd0;

  //FSM
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    curr_state <= IDLE;
  else
    curr_state <= next_state;

  always@(*)
  begin
    case(curr_state)
      IDLE:
        if(!rx_datav_dly2 && rx_datav_dly1)//产生接受数据有效信号时进入下一个状态
          next_state = RX_PREAMBLE;
        else
          next_state = IDLE;
//处于 RX_PREAMBLE 状态的时候,当以太网接收到帧界定符(D5)和 5 个的前导码(55)时,进入到RX_ETH_HEADER 状态,如果接收超过 7 个前导码,则表明此时数据接收错误,进入 IDLE 状态,
      RX_PREAMBLE:
        if(rx_data_dly2 == 8'hd5 && cnt_preamble > 4'd5)
          next_state = RX_ETH_HEADER;
        else if(cnt_preamble > 4'd7)
          next_state = IDLE;
        else
          next_state = RX_PREAMBLE;
//处于 RX_ETH_HEADER 状态时,接收以太网头部数据,当接收完 14 个以太网头部数据之后,进入到 RX_IP_HEADER 状态,
      RX_ETH_HEADER:
        if(cnt_eth_header == 4'd13)
          next_state = RX_IP_HEADER;
        else
          next_state = RX_ETH_HEADER;

      RX_IP_HEADER:
        if(cnt_ip_header == 5'd2 && eth_header_check_ok == 1'b0)
          next_state = IDLE;
        else if(cnt_ip_header == 5'd19)
          next_state = RX_UDP_HEADER;
        else
          next_state = RX_IP_HEADER;

      RX_UDP_HEADER:
        if(cnt_udp_header == 4'd2 && ip_header_check_ok == 1'b0)
          next_state = IDLE;
        else if(cnt_udp_header == 4'd7 && udp_header_check_ok == 1'b0)
          next_state = IDLE;
        else if(cnt_udp_header == 4'd7)
          next_state = RX_DATA;
        else
          next_state = RX_UDP_HEADER;

      RX_DATA:
        if((rx_data_length < 5'd18) && (cnt_data == rx_data_length - 1'b1))
          next_state = RX_DRP_DATA;
        else if(cnt_data == rx_data_length - 1'b1)
          next_state = RX_CRC;
        else
          next_state = RX_DATA;

      RX_DRP_DATA:
        if(cnt_drp_data == 5'd17 - rx_data_length)
          next_state = RX_CRC;
        else
          next_state = RX_DRP_DATA;
      
      RX_CRC:
        if(rx_datav_dly2 == 1'b0)
          next_state = PKT_CHECK;
        else
          next_state = RX_CRC;

      PKT_CHECK:
        next_state = IDLE;

      default:next_state = IDLE;

    endcase
  end  

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    crc_init <= 1'b0;
  else if (rx_datav_dly1 && (~rx_datav_dly2))
    crc_init <= 1'b1;
  else 
    crc_init <= 1'b0;

  //crc_en
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    crc_en <= 1'b0;
  else if(curr_state == IDLE)
    crc_en <= 1'b0;
  else if (curr_state != RX_PREAMBLE && rx_datav_dly2)
    crc_en <= 1'b1;
  else 
    crc_en <= 1'b0;

  //crc_data
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    crc_data <= 8'd0;
  else 
    crc_data <= rx_data_dly2;

  crc32_d8 crc32_d8
  (
    .clk         (clk125m       ),
    .reset_p     (reset_p       ),

    .data        (crc_data      ),
    .crc_init    (crc_init      ),
    .crc_en      (crc_en        ),
    .crc_result  (crc_check     )//latency=1
  );

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
    reg_data_overflow <= 1'b0;
  else if(curr_state == RX_DATA && data_overflow_i == 1'b1)
    reg_data_overflow <= 1'b1;
  else
    reg_data_overflow <= reg_data_overflow;

  //payload output
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
  begin
    payload_valid_o <= 1'b0;
    payload_dat_o   <= 8'h0;
  end
  else if(curr_state == RX_DATA)
  begin
    payload_valid_o <= 1'b1;
    payload_dat_o   <= rx_data_dly2;
  end
  else
  begin
    payload_valid_o <= 1'b0;
    payload_dat_o   <= 8'h0;
  end

  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
  begin
    exter_mac  <= 48'h0;
    exter_ip   <= 32'h0;
    exter_port <= 16'h0;
  end
  else if(curr_state == PKT_CHECK)
  begin
    exter_mac  <= rx_src_mac;
    exter_ip   <= rx_src_ip;
    exter_port <= rx_src_port;
  end

  //done
  always@(posedge clk125m or posedge reset_p)
  if(reset_p)
  begin
    one_pkt_done <= 1'b0;
    pkt_error    <= 1'b0;
  end
  else if(curr_state == PKT_CHECK)
  begin
    one_pkt_done <= 1'b1;
    if(crc_check == 32'h2144DF1C && reg_data_overflow == 1'b0)
      pkt_error  <= 1'b0;
    else
      pkt_error  <= 1'b1;
  end
  else
  begin
    one_pkt_done <= 1'b0;
    pkt_error    <= 1'b0;
  end

endmodule

最后再加一个顶层文件就完成以太网回环了,其他功能可自行更改,例化,然后使用

module eth_udp_loopback_rgmii(
  input         reset_n,
  //eth_rx
  input         rgmii_rx_clk_i,
  input  [3:0]  rgmii_rxd,
  input         rgmii_rxdv,
  output 		eth_rst_n, 
  //eth_tx
  output        rgmii_tx_clk,
  output  [3:0] rgmii_txd,
  output        rgmii_txen,
  //led
  output [7:0]  led
);
parameter LOCAL_MAC  = 48'h00_0a_35_01_fe_c0;
parameter LOCAL_IP   = 32'hc0_a8_00_02;
parameter LOCAL_PORT = 16'd5000;
//eth_rx
wire        reset_p;
wire        clk125m;
wire [47:0] exter_mac;
wire [31:0] exter_ip;
wire [15:0] exter_port;
wire [15:0] rx_data_length;
wire        data_overflow;
wire [7:0]  rx_payload_dat;
wire        rx_payload_valid;
wire        rx_pkt_done;
wire        rx_pkt_err;
reg  [3:0]  pkt_right_cnt;
reg  [3:0]  pkt_err_cnt;
//eth_tx
wire        tx_done;
wire        tx_en_pulse;
wire [47:0] tx_dst_mac;
wire [31:0] tx_dst_ip;
wire [15:0] tx_dst_port;
wire [15:0] tx_data_length;
wire [7:0]  tx_payload_dat;
wire        tx_payload_req;

//eth_msg_buf interface
 wire        eth_msg_wr_en;
 wire [111:0]eth_msg_din;
 reg         eth_msg_rd_en;
 wire [111:0]eth_msg_dout;
 wire [4 : 0]eth_msg_dat_cnt;
 reg         eth_msg_rd_en_dly1;
 reg         eth_tx_state;  //1:tx  0:idle
// gmii interface
 wire gmii_rx_clk;
 wire [7:0] gmii_rxd;
 wire gmii_rxdv;
 
 wire gmii_tx_clk;
 wire [7:0] gmii_txd;
 wire gmii_txen;
 
 wire rgmii_rx_clk;
 wire pll_locked;
 assign eth_rst_n = 1;
 
   clk_wiz_0 pll   //加锁相环调整相位,同时加强时钟扇出能力
   (
    // Clock out ports
    .clk_out1(rgmii_rx_clk),     // output clk_out1
    .clk_in1(rgmii_rx_clk_i),      // input clk_in1
    .locked (pll_locked)
    );    

  assign led = {pkt_err_cnt,pkt_right_cnt};
  assign reset_p = ~reset_n;
  
  always@(posedge clk125m or posedge reset_p)
  begin
    if(reset_p)
      pkt_right_cnt <= 4'd0;
    else if(~rx_pkt_err && rx_pkt_done)
      pkt_right_cnt <= pkt_right_cnt + 1'b1;
    else
      pkt_right_cnt <= pkt_right_cnt;
  end

  always@(posedge clk125m or posedge reset_p)
  begin
    if(reset_p)
      pkt_err_cnt <= 4'd0;
    else if(rx_pkt_err && rx_pkt_done)
      pkt_err_cnt <= pkt_err_cnt + 1'b1;
    else
      pkt_err_cnt <= pkt_err_cnt;  
  end
 
  rgmii_to_gmii rgmii_to_gmii(
    .reset_n(reset_n),
    .gmii_rx_clk(gmii_rx_clk),  
    .gmii_rxdv(gmii_rxdv),
    .gmii_rxd(gmii_rxd),
    .gmii_rxerr(),

    .rgmii_rx_clk(rgmii_rx_clk),
    .rgmii_rxd(rgmii_rxd),
    .rgmii_rxdv(rgmii_rxdv)
  );

  
  //以太网接收
  eth_udp_rx_gmii eth_udp_rx_gmii(
    .reset_p         (reset_p               ),

    .local_mac       (LOCAL_MAC             ),
    .local_ip        (LOCAL_IP              ),
    .local_port      (LOCAL_PORT            ),

    .clk125m_o       (clk125m               ),
    .exter_mac       (exter_mac             ),
    .exter_ip        (exter_ip              ),
    .exter_port      (exter_port            ),
    .rx_data_length  (rx_data_length        ),
    .data_overflow_i (data_overflow         ),
    .payload_valid_o (rx_payload_valid      ),
    .payload_dat_o   (rx_payload_dat        ),

    .one_pkt_done    (rx_pkt_done           ),
    .pkt_error       (rx_pkt_err            ),
    .debug_crc_check (                      ),

    .gmii_rx_clk     (gmii_rx_clk           ),
    .gmii_rxdv       (gmii_rxdv             ),
    .gmii_rxd        (gmii_rxd              )
  );
  
    //对以太网接收数据缓存
  eth_data_buf eth_data_buf (
    .clk   (clk125m          ), // input wire clk
    .din   (rx_payload_dat   ), // input wire [7 : 0] din
    .wr_en (rx_payload_valid ), // input wire wr_en
    .rd_en (tx_payload_req   ), // input wire rd_en
    .dout  (tx_payload_dat   ), // output wire [7 : 0] dout
    .full  (data_overflow    ), // output wire full
    .empty (                 )  // output wire empty
  );
  //同时对报文中MAC、IP等消息数据缓存
  assign eth_msg_wr_en = rx_pkt_done;
  assign eth_msg_din   = {exter_mac,exter_ip,exter_port,rx_data_length};

  eth_msg_buf eth_msg_buf (
    .clk        (clk125m         ), // input wire clk
    .din        (eth_msg_din     ), // input wire [111 : 0] din
    .wr_en      (eth_msg_wr_en   ), // input wire wr_en
    .rd_en      (eth_msg_rd_en   ), // input wire rd_en
    .dout       (eth_msg_dout    ), // output wire [111 : 0] dout
    .full       (                ), // output wire full
    .empty      (                ), // output wire empty
    .data_count (eth_msg_dat_cnt )  // output wire [4 : 0] data_count
  );

  always@(posedge clk125m or posedge reset_p)
  begin
    if(reset_p)
      eth_tx_state <= 1'b0;
    else if(tx_done)
      eth_tx_state <= 1'b0;
    else if(eth_msg_dat_cnt >0)
      eth_tx_state <= 1'b1;
    else
      eth_tx_state <= eth_tx_state;
  end

  always@(posedge clk125m or posedge reset_p)
  begin
    if(reset_p)
      eth_msg_rd_en <= 1'b0;
    else if((eth_tx_state == 1'b0)&&(eth_msg_dat_cnt >0))
      eth_msg_rd_en <= 1'b1;
    else
      eth_msg_rd_en <= 1'b0;
  end

  always@(posedge clk125m)
    eth_msg_rd_en_dly1 <= eth_msg_rd_en;

  assign tx_en_pulse    = eth_msg_rd_en_dly1;
  assign tx_dst_mac     = eth_msg_dout[111:64];
  assign tx_dst_ip      = eth_msg_dout[63:32];
  assign tx_dst_port    = eth_msg_dout[31:16];
  assign tx_data_length = eth_msg_dout[15:0];
  
  eth_udp_tx_gmii eth_udp_tx_gmii
  (
    .clk125m       (clk125m               ),
    .reset_p       (reset_p               ),

    .tx_en_pulse   (tx_en_pulse           ),
    .tx_done       (tx_done               ),

    .dst_mac       (tx_dst_mac            ),
    .src_mac       (LOCAL_MAC             ), 
    .dst_ip        (tx_dst_ip             ),
    .src_ip        (LOCAL_IP              ),
    .dst_port      (tx_dst_port           ),
    .src_port      (LOCAL_PORT            ),
    

    .data_length   (tx_data_length        ),
    
    .payload_req_o (tx_payload_req        ),
    .payload_dat_i (tx_payload_dat        ),

    .gmii_tx_clk   (gmii_tx_clk                ),
    .gmii_txen     (gmii_txen             ),
    .gmii_txd      (gmii_txd              )
  );
  
 gmii_to_rgmii gmii_to_rgmii(
  .reset_n(reset_n),

  .gmii_tx_clk(gmii_tx_clk),
  .gmii_txd(gmii_txd),
  .gmii_txen(gmii_txen),
  .gmii_txer(1'b0),

  .rgmii_tx_clk(rgmii_tx_clk),
  .rgmii_txd(rgmii_txd),
  .rgmii_txen(rgmii_txen)
);
endmodule

代码基本都是小梅哥的代码,我属于纯学习,另外一块别家的板子我没弄好。。。。

标签:FPGA,IP,rx,千兆,gmii,crc,网口,input,reg
From: https://blog.csdn.net/weixin_68654423/article/details/144109769

相关文章

  • FPGA基础知识学习
     记录一下看《设计与验证VerilogHDL》的知识学习,我属于突然开始学,且得边学边用,所以基础知识并不会太多,大部分项目还是得靠找别人的代码去修改,所以开始看这本书进行基础知识学习,看到这本书是看野火的教程中推荐的书籍,所以买来看看verilg的基础语法一类。连续赋值语句assign......
  • 1分钟学会如何提升PCIe通信速率,基于RK3568J + FPGA国产平台!
    测试数据汇总表1 PCIe总线介绍PCIe,即PCI-Express(peripheralcomponentinterconnectexpress)是一种高速串行计算机扩展总线标准。主要用于扩充计算机系统总线数据吞吐量以及提高设备通信速度。图1 DMA技术介绍DMA(DirectMemoryAccess,直接内存访问)是一种让硬件外......
  • 基于 FPGA 的 AD9910 + ADF4351 扫频信号发射平台设计方案
    基于FPGA的AD9910+ADF4351扫频信号发射平台设计方案概要本文介绍一种基于FPGA的扫频信号发射系统设计方案,采用AD9910作为主扫频信号产生器,并利用ADF4351提供可控的混频信号,通过两者的协同工作实现宽频带、高分辨率的扫频信号输出。系统结合FPGA的强大逻辑控......
  • 【FPGA数字信号处理】- 数字信号处理如何入门?从零基础到精通,收藏这篇就够了!
    数字信号处理(DigitalSignalProcessing,简称DSP)是一种利用计算机或专用数字硬件对信号进行处理的技术,在通信、音频、视频、雷达等领域发挥着越来越重要的作用。现场可编程门阵列(Field-ProgrammableGateArray,简称FPGA)作为一种高性能的数字信号处理硬件,已成为许多工程师和......
  • 数字图像处理(4):FPGA中的定点数、浮点数
            (1)定点数:小数点固定在数据的某一位置的数,可以分为定点整数和定点小数和普通定点数。定点数广泛应用于数字图像处理(图像滤波、图像缩放)和数字信号处理(如FFT、定点卷积)中。定点整数:小数点在整个数据的最右侧。    +100(D)=01100100(B)定点小数:小数点在......
  • FPGA、VHDL 基于RISC-V格式的16位位缩模型机设计
    项目地址:FPGA、VHDL基于RISC-V格式的16位位缩模型机设计设计目的实现基于RISC-V格式的16位MCU设计,参考RISC-V的基本格式,进行位数缩减。实现RISC-V中寄存器加法add,立即数加法addi,半字加载lh,半字存储sh,不等条件跳转bne,相等条件跳转beq,无条件跳转链接jal。实现立即寻址、寄存......
  • 【深入浅出玩转FPGA】之FPGA配置模式
    FPGA配置模式XilinxUltraScaleFPGA有7种配置模式,由模式输入引脚M[2:0]决定。主串配置模式从串配置模式主并配置模式(8位或16位)从并配置模式(8位、16位或32位)主SPI配置模式主BPI配置模式JTAG/边界扫描配置模式1、主,即配置时钟CCLK由FPGA提供;从,即配置时钟CCLK由外部控制器提......
  • FPGA时序约束基础
    一、时序约束的目的由于实际信号在FPGA内部期间传输时,由于触发器等逻辑期间并非理想期间,因此不可避免地存在传输延时,这种延迟在高速工作频率、高逻辑级数时会造成后级触发器地建立时间和保持时间不满足,造成时序违例。(这也是为什么需要把FPGA设计不能以高级编程语言思想看的原因,设......
  • 基于FPGA控制的AD采集,ads8688芯片8通道扫描
     1. ads8688芯片简介        芯片详细介绍可仔细查看数据手册,链接:    由于数据手册内容太多,在次不做过多介绍,此处将只对实现8通道的扫描采集所涉及到的知识点做解释说明,大概需掌握如下3点。1.1 程序寄存器配置    程序寄存器映射图如下所示。......
  • 【教程4>第3章>第22节】基于双向链路的自适应调制解调通信链路FPGA实现1——理论分析研
      欢迎订阅FPGA/MATLAB/Simulink系列教程《★教程1:matlab入门100例》《★教程2:fpga入门100例》《★教程3:simulink入门60例》《★教程4:FPGA/MATLAB/Simulink联合开发入门与进阶X例》目录1.软件版本2.基于双向链路的自适应调制解调通信链路理论分析3.双向链路自......