以太网基础②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( ODDR #( | ||||||||||||||||||||
ODDR_rgmii_tx_ctl D2 XOR编码 这种编码方式有以下几个优点: 1. 节省引脚: 将两个控制信号(TX_EN和TX_ER)合并到一个DDR信号中 保持了与GMII相同的功能,但引脚数减半 2. 可靠恢复: PHY可以通过以下方式恢复原始信号: TX_EN = 上升沿值 TX_ER = 上升沿值 XOR 下降沿值 3. 时序简单: 保持与数据信号相同的时序关系 简化了PHY和MAC之间的接口设计
| ||||||||||||||||||||
`timescale 1ns / 1ns
| ||||||||||||||||||||
| ||||||||||||||||||||
上升沿输出数据低 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 |
|
//摸摸鱼 |