当前位置: 首页 > news >正文

以太网基础②RGMII 与 GMII 转换电路设计

1. 今日摸鱼任务

02_【逻辑教程】基于HDL的FPGA逻辑设计与验证教程V3.5.2.pdf

53 RGMII GMII 转换电路设计

2. RGMII 发送接口

2.1  RGMII 接口信号与时序

以太网基础①以太网相关通信接口-CSDN博客

2.3简单的提到了一下RGMII,可以去看与GMII的差别

以太网 MAC 层角度来看:

RGMII 数据在时钟的上升沿和下降沿均进行采样,因此在时钟上升沿、下降沿时,数据需要保持稳定。
来自 RGMII PHY 的时钟和数据同时生成,即边缘对齐,因此必须在 PCB 上对时钟信号加入布线延迟来使得数据在时钟的上升沿时处于稳定状态。
 现 PHY 芯片都针对 RGMII 接口提供了可选的内部延迟电路:
       通过引脚管脚(例如 RTL8211 )或配置寄存器(例如 KSZ9031 88E1512)的方式
              对时钟加入延迟,这样就可以降低 PCB 的布线要求。

2.2 RGMII 发送的 FPGA 实现方案

RGMII 发送设计逻辑框图

OLOGIC块是Xilinx 7系列FPGA中与I/O块(IOB)相邻的专用同步逻辑资源,主要用于通过IOB将数据从FPGA内部发送到外部器件。

实现RGMII 发送设计逻辑时,需要使用 xilinx ODDR Output Double Data Rate,输出双倍数据速率)原语,将该接口使用 OLOGIC 块实现。
ODDR 原语的例化示例:
// ODDR: Output Double Data Rate Output Register with Set, Reset
// and Clock Enable.
// 7 Series
// Xilinx HDL Libraries Guide, version 14.7
ODDR #(
        .DDR_CLK_EDGE( "OPPOSITE_EDGE" ), // "OPPOSITE_EDGE" or "SAME_EDGE"
        .INIT( 1'b0 ), // Initial value of Q: 1'b0 or 1'b1
        .SRTYPE( "SYNC" ) // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_inst (
        .Q(Q), // 1-bit DDR output
        .C(C), // 1-bit clock input
        .CE(CE), // 1-bit clock enable input
        .D1(D1), // 1-bit data input (positive edge)
        .D2(D2), // 1-bit data input (negative edge)
        .R(R), // 1-bit reset
        .S(S) // 1-bit set
);
        // End of ODDR_inst instantiation
ODDR 原语只有一个时钟输入,下降沿数据由输入时钟的本地反转来计时, 反馈到 I/O 块的所有的时钟被完全复用, ODDR 原语的框图如图 53-3 所示

ODDR 原语框图
ODDR 端口信号:

      set reset 在同一时刻 只能有一个被置高
ODDR 属性:

两种操作模式:

        1. OPPOSITE_EDGE 模式

            时钟的两个边沿被用来以两倍的吞吐量从 FPGA 逻辑中捕获数据。

            注意D1、D2有延迟

        2. SAME_EDGE 模式

            数据可以在相同的时钟边沿送给 IOB。相同的时钟沿将数据 送给 IOB 可以避免建立时间违规,并允许用户使用最小的寄存器来执行更高的DDR频率来进行寄存器的延迟,而不是使用 CLB 寄存器。

             注意D1、D2同步

两种模式下:输出端 OQ 先输出 D1 端采集到的值,再输出 D2 端采集到的值。
一个 8 位的 GMII 数据在转换为 RGMII 后,   PHY 芯片在时钟上升沿发送低 4 位,下降沿发送高 4 位。因此,在例化 ODDR 原语时, 高 4 位连接到 D2 端口,低 4 位连接到 D1 端口
对于数据和时钟传输,大多数支持 RGMII PHY芯片提供了对发送时钟 TX_CLK添加延迟的选项。
1. 启用延迟 PHY 设备内 TX_CLK 的选项时, FPGA 必须生成与数据和波形边缘对齐的时钟。

       此时,ODDR DDR_CLK_EDGE 的属性值设置为 SAME_EDGE

        PHY 芯片在对 TX_CLK 时钟进行相移后才能与数据中心对齐。

2. 关闭延迟。FPGA 必须对时钟信号进行相位调整后再作为TX_CLK 输出。

        以使 TXD 和 TX_CLK 成中心对齐关系。如果PCB 上的 TX_CLK 信号线路本身就引入了一定的延迟,则需要将这一部分延迟考虑在内,再计算 FPGA 需要对 TX_CLK 调整的相位值。

可以使用 PLL 产生 2 路相位相差 90 度的时钟信号,一路作为 ODDR 寄存器的工作时钟,一路作为
TX_CLK 输出。
使用 PLL 能够实现任意相位差的两路时钟信号,可以随意调整时钟和数据传输的对齐关系,但是这种方式会占用更多 FPGA 资源。因此,在使用时,用户可以根据实际情况选择不同的方案。

2.3 RGMII 发送接口实现

例化 6 ODDR
         4 个发送 4 位的 TXD 信号;
         一个发送 TXEN TXER 信号;
         一个输出 TX_CLK 信号。

ODDR 原语的 RGMII 发送实现
        系统框图中rgmii_tx_clk使用 ODDR输出,而非使用 PLL产生并输出。
        可以最大程度上保证输出的 rgmii_tx_clk 与 rgmii_txd[3:0]保持边沿对齐关系,实现更加可靠的时钟和数据边沿对齐特性。
         这种情况下,在 PHY 侧,需在 PHY 部对 rgmii_tx_clk 加入时间延迟,或控制 PCB 走线来为 rgmii_tx_clk 增加 一定时间的延迟。
        开发板上,一般都是通过设置 PHY 内部对 rgmii_tx_clk 加入时间延迟来实现的。

module rgmii_tx(
                input reset_n , 
                input gmii_tx_clk ,
                input [7:0] gmii_tx_data ,
                input gmii_tx_en ,
                input gmii_tx_error ,
                output rgmii_tx_clk ,
                output [3:0] rgmii_tx_data ,
                output rgmii_tx_en
                );
    
    genvar i ;
    generate
        for(i = 0 ; i < 4 ; i = i + 1 )
        begin: rgmii_tx_data_o //named block
            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_tdata (
                            .Q(rgmii_tx_data[i]), // 1-bit DDR output
                            .C(gmii_tx_clk), // 1-bit clock input
                            .CE(1'b1), // 1-bit clock enable input
                            .D1(gmii_tx_data[i]), // 1-bit data input (positive edge)
                            .D2(gmii_tx_data[i+4]), // 1-bit data input (negative edge)
                            .R(!reset_n), // 1-bit reset
                            .S(1'b0) // 1-bit set
                    );
        //   rgmii_tx_data_o[0]->ODDR_rgmii_tdata[0]
        //                   ......
        // rgmii_tx_data_o[3]->ODDR_rgmii_tdata[3]

        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_tx_ctl (
                    .Q(rgmii_tx_en), // 1-bit DDR output
                    .C(gmii_tx_clk), // 1-bit clock input
                    .CE(1'b1), // 1-bit clock enable input
                    .D1(gmii_tx_en), // 1-bit data input (positive edge)
                    .D2(gmii_tx_en^gmii_tx_error), // XOR
                    .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

ODDR_rgmii_tx_ctl D2 XOR编码

这种编码方式有以下几个优点:

        1. 节省引脚: 将两个控制信号(TX_EN和TX_ER)合并到一个DDR信号中

                            保持了与GMII相同的功能,但引脚数减半

        2. 可靠恢复: PHY可以通过以下方式恢复原始信号:

                            TX_EN = 上升沿值 TX_ER = 上升沿值 XOR 下降沿值

        3. 时序简单: 保持与数据信号相同的时序关系 简化了PHY和MAC之间的接口设计

gmii_tx_engmii_tx_errorD1D2
0000^0=0
0100^1=1
1011^0=1
1111^1=0

`timescale 1ns / 1ns
`define CLK_PERIOD 8
module rgmii_tx_tb();
    reg reset_n;
    reg gmii_tx_clk;
    reg [3:0] tx_byte_cnt;
    reg [7:0] gmii_tx_data;
    reg gmii_tx_en;
    reg gmii_tx_error;
    wire rgmii_tx_clk;
    wire [3:0] rgmii_tx_data;
    wire rgmii_tx_en;
    
   
    rgmii_tx gmii_to_rgmii(
                .reset_n(reset_n),
                .gmii_tx_clk(gmii_tx_clk),
                .gmii_tx_data(gmii_tx_data),
                .gmii_tx_en(gmii_tx_en),
                .gmii_tx_error(gmii_tx_error),
                .rgmii_tx_clk(rgmii_tx_clk),
                .rgmii_tx_data(rgmii_tx_data),
                .rgmii_tx_en(rgmii_tx_en)
                );
    //clock generate
    initial gmii_tx_clk = 1'b1;
    always #(`CLK_PERIOD/2)gmii_tx_clk = ~gmii_tx_clk;
    
    always@(posedge gmii_tx_clk or negedge reset_n)
    if(!reset_n)
        tx_byte_cnt <= 4'd0;
    else if(gmii_tx_en)
        tx_byte_cnt <= tx_byte_cnt + 1'b1;
    else
        tx_byte_cnt <= 4'd0;
        
    always@(*)
    begin
        case(tx_byte_cnt)
            16'd0  : gmii_tx_data = 8'h01;
            16'd1  : gmii_tx_data = 8'h32;
            16'd2  : gmii_tx_data = 8'h58;
            16'd3  : gmii_tx_data = 8'h96;
            16'd4  : gmii_tx_data = 8'hA8;
            16'd5  : gmii_tx_data = 8'hCD;
            16'd6  : gmii_tx_data = 8'hEF;
            16'd7  : gmii_tx_data = 8'h53;
            16'd8  : gmii_tx_data = 8'hE2;
            16'd9  : gmii_tx_data = 8'hC6;
            16'd10 : gmii_tx_data = 8'h3F;
            16'd11 : gmii_tx_data = 8'hD5;
            16'd12 : gmii_tx_data = 8'h93;
            16'd13 : gmii_tx_data = 8'h2A;
            16'd14 : gmii_tx_data = 8'hB7;
            16'd15 : gmii_tx_data = 8'h91;
        endcase
    end
    
    initial
    begin
        reset_n = 0;
        gmii_tx_en = 0;
        gmii_tx_error = 0;
        #201;
        reset_n = 1;
        gmii_tx_en = 1;
        #2000; 
        $stop;
    end


endmodule
 

上升沿输出数据低 4 位,在时钟下降沿输出数据高 4

3. RGMII接收接口

3.1 RGMII 接收的 FPGA 实现方案

同理,这里与发送的换一下就OK,用到的是IDDR原语和ILOGIC块。

IDDR 原语的例化示例:
// IDDR: Input Double Data Rate Input Register with Set, Reset
// and Clock Enable.
// 7 Series
// Xilinx HDL Libraries Guide, version 14.7
IDDR #(
        .DDR_CLK_EDGE( "OPPOSITE_EDGE" ), // "OPPOSITE_EDGE", "SAME_EDGE"
        // or "SAME_EDGE_PIPELINED"
        .INIT_Q1( 1'b0 ), // Initial value of Q1: 1'b0 or 1'b1
        .INIT_Q2( 1'b0 ), // Initial value of Q2: 1'b0 or 1'b1
        .SRTYPE( "SYNC" ) // Set/Reset type: "SYNC" or "ASYNC"
) IDDR_inst (
        .Q1(Q1), // 1-bit output for positive edge of clock
        .Q2(Q2), // 1-bit output for negative edge of clock
        .C(C), // 1-bit clock input
        .CE(CE), // 1-bit clock enable input
        .D(D), // 1-bit DDR data input
        .R(R), // 1-bit reset
        .S(S) // 1-bit set
);
// End of IDDR_inst instantiation

IDDR 原语框图

IDDR 端口信号:

set reset 在同一时刻 只能有一个被置高
IDDR 属性:

三种操作模式:

        1. OPPOSITE_EDGE模式

               通过 ILOGIC 块中的单个输入实现,Q1 端口在每个时钟周期的上升沿输出数据给 FPGA 逻辑, Q2 端口在每个时钟周期的下降沿输出数据给 FPGA逻辑。

        2. SAME_EDGE模式

               数据在同一时钟边沿被输出给 FPGA 逻辑。

        3. SAME_EDGE_PIPELINED

               数据在同一时钟边沿传输给FPGA逻辑。

           与 SAME_EDGE 模 式不同 ,数据对需要额外的时钟延迟来消除SAME_EDGE 模式的分离效应。

选型建议 :

        优先选择SAME_EDGE模式以简化后续逻辑设计。

        仅在超高频(如≥800MHz)时使用SAME_EDGE_PIPELINED模式。

        OPPOSITE_EDGE适用于兼容旧设计或时序约束宽松的场景。

为了让接收的数据更容易的被 MAC 捕获,大多数 PHY 芯片提供对接收时钟 RX_CLK 添加延迟的选项。
1.  启用延迟 PHY 设备内 RX_CLK 的选项时, PHY 输出 RX_CLK RXD 会呈中心对齐的关系
     在这种情况下,FPGA 可直接使用 RX_CLK 来捕获输入的数据

2. 关闭延迟。FPGA 必须对 RX_CLK 时钟信号进行一定的相位调整后再用来捕获数据。

     可以使用 FPGA 内的 PLL 将该信号进行一定的相位移动后再用来捕获数据。

    让 RX_CLK 进入 PLL,要求该信号是从 FPGA 器件的专用时钟输入管脚或 DQS 脚输入。

3.2 RGMII 接收接口实现

例化 5 IDDR
         4 个接收 4 位的 RXD 信号
         1 个接收 RXEN RXER

IDDR 原语的 RGMII 接收实现

先配置一个PLL

module rgmii_rx(
                input reset , 
                input rgmii_rx_clk ,
                input [3:0] rgmii_rx_data ,
                input rgmii_rx_dv ,
                output gmii_rx_clk ,
                output [7:0] gmii_rx_data , 
                output gmii_rx_dv ,
                output gmii_rx_error 
                );
                
    assign gmii_rx_clk = rgmii_rx_clk;
    wire rgmii_rx_dv_neg;
    genvar i ;
    generate
        for(i = 0 ; i < 4 ; i = i + 1 )
        begin: rgmii_rx_data_i //named block
            IDDR #(
                    .DDR_CLK_EDGE("SAME_EDGE_PIPELINED"), // "OPPOSITE_EDGE", "SAME_EDGE"
                    // or "SAME_EDGE_PIPELINED"
                    .INIT_Q1(1'b0), // Initial value of Q1: 1'b0 or 1'b1
                    .INIT_Q2(1'b0), // Initial value of Q2: 1'b0 or 1'b1
                    .SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
                        ) IDDR_rgmii_rx_data (
                                .Q1(gmii_rx_data[i]), // 1-bit output for positive edge of clock
                                .Q2(gmii_rx_data[i+4]), // 1-bit output for negative edge of clock
                                .C(rgmii_rx_clk), // 1-bit clock input
                                .CE(1'b1), // 1-bit clock enable input
                                .D(rgmii_rx_data[i]), // 1-bit DDR data input
                                .R(reset), // 1-bit reset
                                .S(1'b0) // 1-bit set
                                 );
        end
    endgenerate
    
    IDDR #(
            .DDR_CLK_EDGE("SAME_EDGE_PIPELINED"), // "OPPOSITE_EDGE", "SAME_EDGE"
            // or "SAME_EDGE_PIPELINED"
            .INIT_Q1(1'b0), // Initial value of Q1: 1'b0 or 1'b1
            .INIT_Q2(1'b0), // Initial value of Q2: 1'b0 or 1'b1
            .SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
            ) IDDR_rgmii_rx_dv (
                    .Q1(gmii_rx_dv), // 1-bit output for positive edge of clock
                    .Q2(rgmii_rx_dv_neg), // 1-bit output for negative edge of clock
                    .C(rgmii_rx_clk), // 1-bit clock input
                    .CE(1'b1), // 1-bit clock enable input
                    .D(rgmii_rx_dv), // 1-bit DDR data input
                    .R(reset), // 1-bit reset
                    .S(1'b0) // 1-bit set
            );
    assign gmii_rx_error = gmii_rx_dv ^ rgmii_rx_dv_neg ;
    
endmodule
 
`timescale 1ns / 1ns
`define CLK_PERIOD 8
module rgmii_rx_tb();
    reg reset ; 
    
    reg [3:0] rgmii_rx_data ;
    reg rgmii_rx_dv ;
    wire gmii_rx_clk ;
    wire [7:0] gmii_rx_data ; 
    wire gmii_rx_dv ;
    wire gmii_rx_error ;
    
    wire rgmii_rx_clk ;
    reg clk ;
    wire locked ;
    reg [3:0]rx_byte_cnt;
    
    rgmii_rx_pll rgmii_125M
                     (
                      .clk_out1(rgmii_rx_clk) ,
                      .reset(reset) ,
                      .locked(locked) ,
                      .clk_in1(clk) 
                     );
    
    rgmii_rx rx_test(
                . reset(reset) , 
                . rgmii_rx_clk(rgmii_rx_clk) ,
                . rgmii_rx_data(rgmii_rx_data) ,
                . rgmii_rx_dv(rgmii_rx_dv) ,
                . gmii_rx_clk(gmii_rx_clk) ,
                . gmii_rx_data(gmii_rx_data) , 
                . gmii_rx_dv(gmii_rx_dv) ,
                . gmii_rx_error(gmii_rx_error)
                );
                
    //clock generate
    initial clk = 1'b1;
    always #(`CLK_PERIOD/2)clk = ~clk;
    
    always@(clk or posedge reset)
    if(reset)
     rx_byte_cnt <= 4'd0;
    else if(rgmii_rx_dv && locked)
        rx_byte_cnt <= rx_byte_cnt + 1'b1;
    else
        rx_byte_cnt <= 4'd0;
    
    always@(*)
    begin
        case(rx_byte_cnt)
            16'd0  : rgmii_rx_data = 4'h1;
            16'd1  : rgmii_rx_data = 4'h2;
            16'd2  : rgmii_rx_data = 4'h3;
            16'd3  : rgmii_rx_data = 4'h4;
            16'd4  : rgmii_rx_data = 4'h5;
            16'd5  : rgmii_rx_data = 4'h6;
            16'd6  : rgmii_rx_data = 4'h7;
            16'd7  : rgmii_rx_data = 4'h8;
            16'd8  : rgmii_rx_data = 4'h9;
            16'd9  : rgmii_rx_data = 4'hA;
            16'd10 : rgmii_rx_data = 4'hB;
            16'd11 : rgmii_rx_data = 4'hC;
            16'd12 : rgmii_rx_data = 4'hD;
            16'd13 : rgmii_rx_data = 4'hE;
            16'd14 : rgmii_rx_data = 4'hF;
            16'd15 : rgmii_rx_data = 4'h0;
        endcase
    end
    
    initial
    begin
        reset = 1;
        rgmii_rx_dv = 0;
        #201;
        reset = 0;
        rgmii_rx_dv = 1;
        #2000; 
        $stop;
    end
    
endmodule
 

//摸摸鱼
http://www.xdnf.cn/news/1068085.html

相关文章:

  • Spring Boot 系统开发:打造高效、稳定、可扩展的企业级应用
  • 学习日记-spring-day37-6.25
  • SpringCloud系列(35)--使用HystrixDashboard进行服务监控
  • OSS跨区域复制灾备方案:华东1到华南1的数据同步与故障切换演练
  • 数智时代如何构建人才培养生态?生成式人工智能(GAI)认证,引领数智时代人才培养新方向
  • Kafka如何保证消息可靠?
  • 计算机网络期末复习
  • Linux操作系统Nginx Web服务
  • JVM(12)——详解G1垃圾回收器
  • 多模态大模型(从0到1)
  • JavaEE初阶第四期:解锁多线程,从 “单车道” 到 “高速公路” 的编程升级(二)
  • 《AI大模型应用技术开发工程师》学习总结
  • 2025.6.16-实习
  • 【Linux网络与网络编程】15.DNS与ICMP协议
  • 《仿盒马》app开发技术分享-- 兑换列表展示(68)
  • AI时代工具:AIGC导航——AI工具集合
  • MySQL:深入总结锁机制
  • C++信奥赛闯关题目1
  • 有AI后,还用学编程吗?
  • ABP VNext + BFF(Backend for Frontend)模式:Angular/React 专用聚合层
  • 爬取小红书相关数据导入到excel
  • SQL关键字三分钟入门:UPDATE —— 修改数据
  • Redis 分布式锁原理与实战-学习篇
  • 【计算机网络】期末复习
  • 轻量化实物建模革命:WebGL如何实现复杂模型的高效加载与交互
  • 14.OCR字符识别
  • 同济大学多模态感知具身导航全面综述
  • 10-Python模块详解
  • Netty内存池核心PoolArena源码解析
  • 机器学习×第十四卷:集成学习中篇——她从每次错误中修正自己