• FPGA实现千兆/百兆自适应以太网UDP传输


    0、前言

    笔者最近在项目中需要使用到ZYNQ中PL端做以太网UDP传输并且需要支持100M/1000M自适应切换。使用的PHY型号为RTL8211。以下分享的主要为利用已有的1000M协议栈修改为100M并且实现二者自适应切换,IP核主要实现以下功能

    1、实现100M/1000M自适应

    2、回环测试

    PS:完整的IP核文件下载地址:https://download.csdn.net/download/qq_24025329/87019436

    1、软硬件环境和前置条件

    笔者采用的接口是RGMII接口,即100M模式下单边沿采样,时钟频率为25M。在1000M模式下使用双边沿采样,时钟频率为125M。所以在千兆模式下需要使用原语对数据采样,这里就不过多赘述了,这里默认已经拥有了可以实现1000M通讯的协议栈了。

    2、实现步骤

    第一步:千兆-百兆接收数据转换模块

    这里笔者偷了个懒,既然已经有了千兆模式下读取的数据,那么我们只需要根据千兆与百兆之间的采样模式进行转换就可以读出来正确的数据了。具体思想如下

    1、千兆为双边沿采样,百兆为单边沿采样。所以在千兆模式RGMII转换为GMII接口下跑百兆的速度读出来的8bit数据中高四位=低四位的。所以我们将读出来的数据只保留四位即可。下图为千兆模式下的采样

     2、时钟采用PHY提供的时钟,即百兆模式下25M千兆模式下为125M即可。具体实现代码如下

    1. `timescale 1ns / 1ps
    2. module eth_speed(
    3. input Rst_n, //系统复位
    4. //以太网GMII接口
    5. input gmii_rx_clk , //GMII接收时钟
    6. input gmii_rx_dv , //GMII接收数据有效信号
    7. input [7:0] gmii_rxd , //GMII接收数据
    8. //速度转换后以太网GMII接口
    9. output gmii_rx_clk_s , //GMII接收时钟
    10. output gmii_rx_dv_s , //GMII接收数据有效信号
    11. output reg[7:0] gmii_rxd_s //GMII接收数据
    12. );
    13. //==========参数定义=============//
    14. parameter SPEED = 1000;//1000Mbps
    15. //==========寄存器定义===========//
    16. reg out_clk; //输出时钟
    17. reg count_s; //二分数据计数
    18. reg data0_en;
    19. reg data1_en; //数据有效位
    20. //==========组合逻辑运算=============//
    21. assign gmii_rx_clk_s = out_clk;//输出时钟二分频
    22. assign gmii_rx_dv_s = data0_en && data1_en;//接收数据有效
    23. //==========时序逻辑============//
    24. //输入时钟二分频
    25. always @(posedge gmii_rx_clk or negedge Rst_n)
    26. if(!Rst_n)
    27. out_clk <= 1'd0;
    28. else
    29. out_clk <= ~out_clk;
    30. //二分数据计数
    31. //第一个数据来临后的第一个下降沿=1
    32. //
    33. always @(negedge gmii_rx_clk or negedge Rst_n)
    34. if(!Rst_n)
    35. count_s <= 1'd0;
    36. else if(!gmii_rx_dv)//数据无效 清零
    37. count_s <= 1'd0;
    38. else
    39. count_s = ~count_s;//反转
    40. //采集数据1 下降沿采集数据
    41. always @(negedge gmii_rx_clk or negedge Rst_n)
    42. if(!Rst_n)
    43. gmii_rxd_s[3:0] <= 4'd0;
    44. else if(gmii_rx_dv && (!count_s))//接收数据有效并且二分频时钟位低电平
    45. gmii_rxd_s[3:0] <= gmii_rxd[3:0];
    46. //采集数据2 下降沿采集数据
    47. always @(negedge gmii_rx_clk or negedge Rst_n)
    48. if(!Rst_n)
    49. gmii_rxd_s[7:4] <= 4'd0;
    50. else if(gmii_rx_dv && (count_s))//接收数据有效并且二分频时钟位低电平
    51. gmii_rxd_s[7:4] <= gmii_rxd[3:0];
    52. //采集数据1有效位
    53. always @(posedge gmii_rx_clk or negedge Rst_n)
    54. if(!Rst_n)
    55. data0_en <= 1'd0;
    56. else if(!count_s) //第一个数据采样的周期
    57. if(gmii_rx_dv) //有效
    58. data0_en <= 1'd1;
    59. else
    60. data0_en <= 1'd0;
    61. else
    62. data0_en <= data0_en;
    63. //采集数据2有效位
    64. always @(posedge gmii_rx_clk or negedge Rst_n)
    65. if(!Rst_n)
    66. data1_en <= 1'd0;
    67. else if(count_s) //第二个数据采样的周期
    68. if(gmii_rx_dv) //有效
    69. data1_en <= 1'd1;
    70. else
    71. data1_en <= 1'd0;
    72. else
    73. data1_en <= data1_en;
    74. endmodule

    第二步:千兆-百兆接收数据转换模块

    发送方面就不能偷懒了,只能自己写了一个百兆模式下的发送,代码如下

    1. `timescale 1ns / 1ps
    2. module eth_speed_tx(
    3. input Rst_n,
    4. input gmii_rx_clk, //来自PHY的时钟
    5. //获得数据接口
    6. input [7:0] data_in, //GMII数据
    7. input data_en, //GMII使能
    8. output data_clk, //GMII时钟,用于获取发送数据,是RGMII的1/2
    9. //RGMII接口
    10. output rgmii_txc, //RGMII的时钟
    11. output reg[3:0]rgmii_txd, //RGMII发送的数据
    12. output rgmii_tx_ctl //RGMII发送控制
    13. );
    14. //========寄存器定义===========//
    15. reg rgmii_txc_2_s;//发送时钟的二分频 并滞后90°
    16. reg en_delay;//输入使能延迟
    17. //==========逻辑==============//
    18. assign data_clk = rgmii_txc_2_s;
    19. assign rgmii_tx_ctl = data_en|en_delay;
    20. assign rgmii_txc = gmii_rx_clk;
    21. //==========时序逻辑==========//
    22. //时钟二分频 用于获得数据 并滞后90°
    23. always@(negedge rgmii_txc or negedge Rst_n)
    24. if(!Rst_n)
    25. rgmii_txc_2_s <= 1'd0;
    26. else
    27. rgmii_txc_2_s <= ~rgmii_txc_2_s;
    28. //发送数据赋值
    29. always @(negedge gmii_rx_clk or negedge Rst_n)
    30. if(!Rst_n)
    31. rgmii_txd <= 4'd0;
    32. else if(data_en && rgmii_txc_2_s)//要发送第一个4bit数据 低位
    33. rgmii_txd <= data_in[3:0];
    34. else if(data_en && (!rgmii_txc_2_s))//要发送第二个4bit数据 高位
    35. rgmii_txd <= data_in[7:4];
    36. else
    37. rgmii_txd <= rgmii_txd;
    38. //使能延迟
    39. always @(negedge gmii_rx_clk or negedge Rst_n)
    40. if(!Rst_n)
    41. en_delay <= 1'd0;
    42. else if(data_en && (!rgmii_txc_2_s))
    43. en_delay <= 1'd1;
    44. else
    45. en_delay <= 1'd0;
    46. endmodule

    第三步:百兆千兆切换逻辑

    百兆和千兆模式下切换本质上就是切换RGMII的接口信号源,具体的逻辑较为简单如图所示

     其中SPEED为选择开关,该值可以是定义的参数,也可以当作接口有外部提供。

    最后

    该IP核是笔者在项目中解决自己的问题所编写,水平有限如有疏漏敬请指正。

  • 相关阅读:
    java: 警告: 源发行版 17 需要目标发行版 17
    MySql基础篇——窗口函数和公用表达式
    HTML期末学生大作业-节日网页作业html+css+javascript
    179基于matlab的2D-VMD处理图像
    composer 扩展库。助手库文档
    ROS学习|Nodetlet学习与使用
    酷开科技依托酷开系统推动家庭智能化加速发展
    uniapp实现我的订单页面无感 - 删除数据
    (五)Alian 的 Spring Cloud DB Starter(自己写个starter)
    前端架构师之01_JavaScript_Ajax
  • 原文地址:https://blog.csdn.net/qq_24025329/article/details/127865964