FPGA以太网学习-RGMII与GMII

以太网口都叫RJ45接口,从功能角度说,网口只是信号连接,本身没有通信能力。

PHY(物理层),这边需要一个芯片,将并行的以太网数据到符合以太网物理层链路数据传输格式的电平信号转换。

上图PHY右边是经过编码后的串行数据信号,左侧是提供多种并行信号。网络变压器连接串行信号和网口。

MII接口(百兆/十兆接口)

MAC向PHY侧传输数据的时序图如下

 


MII 介质无关接口

MAC 介质访问接口

RTL8211F-CG是一款支持 RGMII 接口的以太网物理层收发器,能够工作在 10M、100M 或 1000M Base模式,对 MAC 层可提供 RGMII 接口模式,并提供了标准的 MDIO 管理接口与处理器相连。

以太网物理层芯片都有一个器件地址,该器件地址就是在介绍 PHY 管理接口 MDIO 时所说的 PHY 器件地址,该器件地址用来由 MDIO 主机(如 MAC 或处理器)寻址 MDIO 总线上连接的指定的 PHY 芯片。

对于 RTL8211F-CG 芯片,该地址分为固定部分和硬件可设定两部分。器件地址共有 5 位,其中高 2 位为固定的 00,低三位通过三个地址设置脚在芯片上电或复位时候设置, 这三位分别对应 RTL8211F 芯片的 22、27、26 脚。下表为这三个脚功能说明。

注意:手册里下面这个话好像写错了,PHY_ADDR1通过电阻下拉到低电平,其他两个通过电阻上拉到高电平。

所以器件地址为00101.


以太网MAC帧协议介绍

以太网技术的正式标准是 IEEE802.3 标准,它规定了在以太网中传输的数据帧结构,如下图所示。

从目的地址开始到数据和填充字段的最后一个为止,这些跟CRC进行计算。不包括前同步码和分隔符。

前导码的作用是为了标记一帧以太网帧即将开始传输,分隔符存在的意义是指前导码传输完毕。

MC地址对应,一对一,多播,广播。MAC地址,每个设备都唯一,需要向美国IEEE申请。48位地址,前24位IEEE指定,后24位设备厂商自行定义。

以太网中的数据部分,一般都在另一个上层协议,如TCP/IP协议。用户数据都是包含在该上层协议中。


RGMII 接口信号与时序

这个RGMII和GMII的互转,是因为需要使用RGMII并行传输协议,RGMII是上下沿采样,所以是双倍于GMII的速度

在PHY和MAC层之间传输并行数据的方法。所以从PHY到MAC这边是RGMII转GMII,从MAC到PHY这边是GMII转RGMII

用下面这个回环测试的示意图能看出来。

1、网口连接电脑以太,从电脑这边发一个数据,经过PHY串转并,通过RGMII转GMII进入,然后输出来之后,给GMII转RGMII就行了

RGMII数据在时钟的上升沿和下降沿均进行采样。

数据边沿对齐的形式使得 PCB 设计变得更加的复杂,所以,后来的 PHY 芯片都针对 RGMII接口提供了可选的内部延迟电路。(暂时不理解)

RGMII调用FPGA的ODDR原语,实现输出DDR寄存器,实例化原语就会自动访问该功能。

端口信号

ODDR属性

 

直接看代码(写注释里面)

这个跟TB文件中的代码对应,

rgmii_to_gmii.v


module rgmii_to_gmii(
  reset,            //复位信号

  rgmii_rx_clk,     //接收数据参考时钟,由PHY输出过来的
  rgmii_rxd,        //PHY传向MAC的数据[3:0]
                    
  rgmii_rxdv, //在RX_CTL里面的,传输GMII中的RX_DV和RX_ER信号

  gmii_rx_clk,  //gmii的参考时钟,跟RGMII的对应就行了
  gmii_rxdv,    //Reveive Data Valid,接收数据有效信号,作用类似于发送通道的TX_EN
  gmii_rxd,    //接收数据
  gmii_rxer     //接收错误error,高电平有效
);



  input         reset;

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

  output        gmii_rx_clk;
  output [7:0]  gmii_rxd;
  output        gmii_rxdv;
  output        gmii_rxer;
  
  wire          gmii_rxer_r;
  

  assign gmii_rx_clk = rgmii_rx_clk;   //时钟狠狠同步

  genvar i;
  generate
    for(i=0;i<4;i=i+1)
    begin: rgmii_rxd_i
      IDDR #(
        .DDR_CLK_EDGE("SAME_EDGE_PIPELINED"), // "OPPOSITE_EDGE", "SAME_EDGE" 
                                              // or "SAME_EDGE_PIPELINED" Q1Q2变化发生在同一个上升沿,都是下一个
        .INIT_Q1(1'b0   ),  // Initial value of Q1: 1'b0 or 1'b1,默认0
        .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,低4位
        .Q2   (gmii_rxd[i+4] ), // 1-bit output for negative edge of clock,高4位
        .C    (rgmii_rx_clk  ), // 1-bit clock input,rgmii参考时钟
        .CE   (1'b1          ), // 1-breset_nit clock enable input
        .D    (rgmii_rxd[i]  ), // 上升沿传GMII的RXD的低4位,下降沿传高4位
        .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" 
                                          //Q1Q2变化发生在同一个上升沿,但是都是下一个上升沿
    .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_r), // 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,这个就对应RX_CTL的,因为上升沿传DV,下降沿传ER
                            //
    .R    (reset        ), // 1-bit reset
    .S    (1'b0         )  // 1-bit set
  );

    assign gmii_rxer = gmii_rxer_r^gmii_rxdv; //异或,相异为1.
endmodule
rgmii_to_gmii_tb.v
 `timescale 1ns / 1ps
`define CLK_PERIOD 8

module rgmii_to_gmii_tb();
    reg  reset;
    wire gmii_rx_clk;
    
    reg [3:0] rx_byte_cnt;//接收比特计数
    
    wire [7:0] gmii_rxd;
    wire gmii_rxdv;
    wire gmii_rxer;
    wire rgmii_rx_clk;
    reg [3:0] rgmii_rxd;
    reg rgmii_rxdv;
    reg rx_clk;
    wire locked;
    
    rgmii_to_gmii rgmii_to_gmii(
        .reset(reset),
        .gmii_rx_clk(gmii_rx_clk),  
        .gmii_rxdv(gmii_rxdv),
        .gmii_rxd(gmii_rxd),
        .gmii_rxer(gmii_rxer),
    
        .rgmii_rx_clk(rgmii_rx_clk),
        .rgmii_rxd(rgmii_rxd),
        .rgmii_rxdv(rgmii_rxdv)  //RX_CTL
    );
    
    //对rgmii_rx_clk时钟进行90度相位调制,以保证该时钟到达IDDR时候
    //与rgmii_rxd和rgmii_rxdv信号成中心对齐关系。
    //其实就是为了能够在对rgmii_rx_clk上升沿和下降沿的时候,正好处在rgmii_rxd的值的中心位置采样。
      rx_pll rx_pll
   (
    // Clock out ports
    .clk_out1(rgmii_rx_clk),     // output clk_out1,90度相位调制
    // Status and control signals
    .reset(reset), // input reset
    .locked(locked),       // output locked,是时钟稳定的信号
   // Clock in ports
    .clk_in1(rx_clk)      // input clk_in1
    );

    //clock generate
    initial rx_clk = 1'b1;
    always #(`CLK_PERIOD/2)rx_clk = ~rx_clk;  //每4ns转,周期8ns
    
    always@(rx_clk or posedge reset)
    if(reset)
        rx_byte_cnt <= 4'd0;
    else if(rgmii_rxdv && locked)   //RX_CTL的
        rx_byte_cnt <= rx_byte_cnt + 1'b1;   //16个数据
    else
        rx_byte_cnt <= 4'd0;

    always@(*)
    begin
        case(rx_byte_cnt)
        16'd0  : rgmii_rxd = 12;
        16'd1  : rgmii_rxd = 7;
        16'd2  : rgmii_rxd = 9;
        16'd3  : rgmii_rxd = 6;
        16'd4  : rgmii_rxd = 11;
        16'd5  : rgmii_rxd = 15;
        16'd6  : rgmii_rxd = 0;
        16'd7  : rgmii_rxd = 8;
        16'd8  : rgmii_rxd = 4;
        16'd9  : rgmii_rxd = 2;
        16'd10 : rgmii_rxd = 5;
        16'd11 : rgmii_rxd = 1;
        16'd12 : rgmii_rxd = 3;
        16'd13 : rgmii_rxd = 10;
        16'd14 : rgmii_rxd = 14;
        16'd15 : rgmii_rxd = 13;
        endcase
    end

  initial
    begin
    reset = 1;
    rgmii_rxdv = 0;
    #201;
    reset = 0;   //因为PLL时钟的RESET信号,高电平有效
    rgmii_rxdv = 1;
    #2000; 
    $stop;
  end

endmodule

1、这边看到计数0的时候,RGMII_RXD的值是12(C),在RGMII_RX_CLK的上升沿传输低4位,下降沿传输高四位

2、以第一个GMII的数据为例,上升沿C,下降沿7,因为是SAME_EDGE_PIPELINED,所以Q1Q2在下一个上升沿发生变化,在GMII_RXD这边就是7C的呈现。

3、时钟偏90度相位,我的理解就是方便采样取值

 

下面是GMII转RGMII接口。

gmii_to_rgmii.v
 /*例化6个ODDR就可以实现,其中4个用来发送4个TXD信号
一个用来发送TXEN和TXER信号
一个用来输出TX_CLK信号

*/
module gmii_to_rgmii(
  reset_n,

  gmii_tx_clk,   //gmii发送参考时钟,mac提供
  gmii_txd,      //gmii_txd[7:0]
  gmii_txen,    //发送使能
  gmii_txer,    //发送错误信息

  rgmii_tx_clk,
  rgmii_txd,     
  rgmii_txen     //tx_ctl,上升沿传tx_en,下降沿传tx_er
);
  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必须是用这个genvar定义参数
  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]     ), // 输出RGMII的TXD
        .C   (gmii_tx_clk      ), // gmii的发送时钟
        .CE  (1'b1             ), // 使能
        .D1  (gmii_txd[i]      ), // GMII_TXD的低4位
        .D2  (gmii_txd[i+4]    ), // GMII_TXD的高4位
        .R   (~reset_n         ), // 复位,因为复位reset跟set,高电平有效,所以reset=0的时候,这边才复位
        .S   (1'b0             )  // 高电平有效,不设置
      );
    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           ), // 上升沿采集txen信号
    .D2  (gmii_txen^gmii_txer ), // RGMII的TX_CTL传输GMII的TX_EN 和 TX_ER 两种信息,TX_CLK 的上升沿发送 TX_EN,下降沿发送 TX_ER
    //.D2  (gmii_txer ),
    //所以这边D2应该GMII_TXER也可以,等会仿真看看
    .R   (~reset_n            ), // 1-bit reset
    .S   (1'b0                )  // 1-bit set
  );

//这边对于RGMII_TX_CLK的输出,也是用ODDR输出,而不是PLL产生。
//优势在于最大程度保证输出的RGMII_TX_CLK与RGMII_TXD[3:0]保持边缘对齐
//前面的TXD和CLK都能ODDR输出,可以保证传输路径的时序模型完全一致
  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
gmii_to_rgmii_tb.v
 `timescale 1ns / 1ps
`define CLK_PERIOD 8

module gmii_to_rgmii_tb();
    reg  reset_n;
    reg gmii_tx_clk;
    reg [3:0] tx_byte_cnt;  //发送数据计数
    reg [7:0] gmii_txd;    //发送数据
    reg gmii_txen;
    reg gmii_txer;
    wire rgmii_tx_clk;
    wire [3:0] rgmii_txd;
    wire rgmii_txen;
    
    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(gmii_txer),
    
      .rgmii_tx_clk(rgmii_tx_clk),
      .rgmii_txd(rgmii_txd),
      .rgmii_txen(rgmii_txen)
    );

        //clock generate
        //每4ns翻转一次,周期8ns
    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_txen) //如果gmii的发送使能,计数开始
        tx_byte_cnt <= tx_byte_cnt + 1'b1;
    else
        tx_byte_cnt <= 4'd0;

    always@(*)
    begin
        case(tx_byte_cnt)  //计数16次,分别赋值的发送数据
        16'd0  : gmii_txd = 25;
        16'd1  : gmii_txd = 34;
        16'd2  : gmii_txd = 18;
        16'd3  : gmii_txd = 96;
        16'd4  : gmii_txd = 78;
        16'd5  : gmii_txd = 29;
        16'd6  : gmii_txd = 63;
        16'd7  : gmii_txd = 42;
        16'd8  : gmii_txd = 51;
        16'd9  : gmii_txd = 74;
        16'd10 : gmii_txd = 39;
        16'd11 : gmii_txd = 60;
        16'd12 : gmii_txd = 27;
        16'd13 : gmii_txd = 36;
        16'd14 : gmii_txd = 145;
        16'd15 : gmii_txd = 231;
        endcase
    end

  initial
    begin
    reset_n = 0;
    gmii_txen = 0;
    gmii_txer = 0;
    #201;
    reset_n = 1;
    gmii_txen = 1;
    #2000; 
    $stop;
  end

endmodule

1、gmii_txd的第一个数据是19(其实是0001 1001)换算十进制就是25,用的SAME_EDGE,所以是时钟上升沿取D1D2(低四位高四位),然后依次在一个时钟周期内赋值给Q

2、可以看到RGMII_TXD是先9再1,而9是上面的低4位,1是上面的高4位,且是在同一个时钟周期。

posted @ 2024-07-07 16:55  祈愿树下  阅读(71)  评论(0编辑  收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css