FPGA之RGMII转GMII接口

一、前言         

        以太网的通信离不开物理层PHY芯片的支持,以太网MAC和PHY之间有一个接口,这个接口最常见的是GMII和RGMII。

        GMII接口全称为Gigabit Medium Independent Interface,它向下兼容MII接口,支持10Mbps、100Mbps和1000Mbps的操作,数据位宽为81000Mbps传输速率下,时钟频率为125Mhz。GMII接口时序如图1.1所示:

                                                        图1.1 GMII接口时序

      RGMII中R为Reduced,也就是说它是GMII的简化版,数据位宽为4位,在1000Mbps传输速率下,时钟频率为125Mhz在时钟的上下沿同时采样数据100Mbps和10Mbps通信速率下,为单个时钟沿采样

        一般PYH芯片会芯片内部将TXC延时2ns,所以般PHY芯片都是要求FPGA发出的TXC和TXD 信号要沿对齐,FPGA的RGMII接口发送时序如图1.2所示:

图1.2 RGMII接口发送时序

       RGMII的接收时序如图1.3所示:

图1.3  RGMII的接收时序

        但是为了方便为了方便FPGA更准确地采集到数据,时钟与数据会做延时对齐 ,所以FPGA的接收时序为图1.4所示:

                                                              图1.4 RGMII的FPGA接收时序

        而PHY芯片控制时钟慢与RXD信号的引脚为LED_10_100,如下图1.5所示:

        

图1.5 PHY芯片引脚

        当该引脚接上拉电阻时,RXC时钟会比RXD信号慢2ns左右。YT8511数据手册上也明确指出LED_10_100拉高为打开延时使能。

        

那么为什么需要RGMII接口转GMII接口呢?

        答案很简单,因为GMII接口比RGMII接口信号,接口信号多就意味着增加了PCB布线难度,而FPGA的开发板上的布线是相当密集的,你多几根信号线就相当于给layout工程师增加了布线线难度,所以很多PHY芯片的接口就设计成了RGMII接口,但是呢,RGMII是双沿传输的,但是FPGA内部信号都是单沿处理的,FPGA内部使用GMII接口形式传输数据,对PHY芯片做通信时转为RGMII接口。

二、RGMII接口转GMII接口

        我在学以太网的时候看某点原子和某火,以及B站奇哥的视频时,在做RGMII转GMII时很懵,为什么要这样做?所以我将代码做了一个整理,流程图如图2.1所示:

图2.1 RGMII接口GMII接口互转流程图

         看到这里可能有个疑问,为什么时钟需要过一个BUFIO来驱动IDDR?

        在回答这个问题前我先简单介绍一下这些BUF。

        IBUF:输入缓存器,用于将外部输入信号从引脚输入到模块中;

        OBUF:输出缓冲器,用于将模块中的输出信号从模块输出到引脚;

        BUFIO:输入\输出时钟缓存器,用于高速I\O接口时钟,如串行器/反串行器的时钟;

        BUFG:全局时钟网络,它可以驱动所有的IO和逻辑,并且可以被Transceiver所驱动;

        BUFR:regional时钟网络,顾名思义,它的驱动范围只能局限在一个clock region的逻辑,但是它可以同时驱动IO和内部逻辑。

        注:BUFR相比BUFG的最大优势是skew和功耗都比较小,在源同步的设计中,这一点也是很关键的。

        时钟需要过一个BUFIO来驱动IDDR需要从Xilinx的FPGA上时钟资源布局上找到答案如图2.2所示,因为从以太网接口进来的数据延迟不能太大,否则采集的数据就会产生误码。BUFG在能驱动所以逻辑资源的同时为了让时钟到每个资源的延迟一致,它放置的位置就会靠近FPGA芯片中心,而IDDR和ODDR为I/Obank上的资源,你将时钟先引入BUFG再去驱动IDDR势必为造成很大的延迟。

        而BUFIO 是 IO 时钟网络,并且提供非常小的延时,因此非常适合采集比如 RGMII 接收侧的数据但是它只能驱动 IO Block 里面的逻辑,不能驱动 CLB 里面的 LUT,REG 等逻辑,所以需要配合BUFG使用。

图2.2 BUFR/BUFMR/BUFIO Clock Region Detail

为何进入ODDR的时钟需要过一个非门?

        这是因为我ODDR选择的模式为OPPOSITE_EDGE,OPPOSITE_EDGE模式会在上升沿读取D1数据,在半个时钟周期后输出,在下降沿读取D2数据,也是过半个周期后输出。

当TXC反转后正好和ODDR输出的数据对齐。时序如下图所示:

        

                                                                图2.3 OPPOSITE_EDGE模式输出时序

        此种模式下,在FPGA内部需要两个反相时钟来同步D1和D2,此种模式使用较少。但是如果使用SAME_EDGE模式,数据可以在相同的时钟边沿输出到Q,一般采用此种模式。

三、代码

                      

module RGMII_Tri(
    /*------- RGMII port ---------*/
    input                               i_rxc                           ,
    input   [3 :0]                      i_rxd                           ,
    input                               i_rx_ctl                        ,
    
    output                              o_txc                           ,
    output  [3 :0]                      o_txd                           ,
    output                              o_tx_ctl                        ,
    
    /*------- data  port ---------*/
    output                               o_rxc                          ,
    input    [7 :0]                      i_send_data                    ,
    input                                i_send_valid                   ,
    
    output   [7 :0]                      o_rec_data                     ,
    output                               o_rec_valid                    ,
    output                               o_rec_end                      ,
    
    output   [1 :0]                      o_speed                        ,
    output                               o_link
   );
/************************    reg      *********************************/
reg     [7 :0]                          ro_rec_data      =  0           ;
reg                                     ro_rec_valid     =  0           ;
reg                                     ro_rec_end       =  0           ;
reg                                     r_cnt_10_100     =  0           ;
reg                                     r_rec_valid      =  0           ;
reg     [7 :0]                          ri_send_data     =  0           ;
reg                                     ri_send_valid    =  0           ;
reg                                     r_tx_cnt_10_100  =  0           ;
reg     [1 :0]                          ro_speed         =  0           ;
reg                                     ro_link          =  0           ;

/************************    wire     *********************************/
wire                                    i_speed1000                     ;//千兆使能信号
wire                                    w_rxc_bufio                     ;
wire                                    w_rxc_idelaye                   ;
wire    [3 :0]                          w_rxd_ibuf                      ;
wire                                    w_rx_ctl_ibuf                   ;
wire    [7 :0]                          w_rec_data                      ;
wire    [1 :0]                          w_rec_valid                     ;
wire                                    w_rxc_bufr                      ;
wire    [3 :0]                          w_send_d1                       ;
wire    [3 :0]                          w_send_d2                       ;
wire                                    w_send_valid                    ;
wire                                    w_txc                           ;
/*********************    component    ********************************/

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
assign i_speed1000 = 1            ;
assign o_rec_data  = ro_rec_data  ;
assign o_rec_valid = ro_rec_valid ;
assign o_rec_end   = ro_rec_end   ;
assign o_speed     = ro_speed     ;
assign o_link      = ro_link      ;
assign w_txc       = ~w_rxc_bufr  ;//取反是让oddr的输出第一个数据中心在txc上升沿
assign o_rxc       = w_rxc_bufr   ;

/*------- RGMII RX ---------*/
BUFIO BUFIO_inst (
   .O                       (w_rxc_bufio        ), 
   .I                       (i_rxc              )  
   );
   
genvar rxd_i;
generate
    for(rxd_i = 0;rxd_i < 4;rxd_i = rxd_i + 1)begin
        IBUF #( 
        .IBUF_LOW_PWR       ("TRUE"             ),  
        .IOSTANDARD         ("DEFAULT"          ) 
        ) 
        IBUF_U (
        .O                  (w_rxd_ibuf[rxd_i]  ),     
        .I                  (i_rxd[rxd_i]       )      
        );
        
        //将数据从单沿传送变为双沿传送,即一个时钟传4bit改为一个时钟8bit
        IDDR #(
            .DDR_CLK_EDGE   ("SAME_EDGE_PIPELINED"),  
            .INIT_Q1        (1'b0               ), 
            .INIT_Q2        (1'b0               ), 
            .SRTYPE         ("SYNC"             )
        ) 
        IDDR_U0 (
            .Q1             (w_rec_data[rxd_i]  ), 
            .Q2             (w_rec_data[rxd_i + 4]), 
            .C              (w_rxc_bufio        ), 
            .CE             (1                  ), 
            .D              (w_rxd_ibuf[rxd_i]  ),  
            .R              (0                  ),  
            .S              (0                  )   
   );
    end 
endgenerate

IBUF #(
   .IBUF_LOW_PWR            ("TRUE"             ),  
   .IOSTANDARD              ("DEFAULT"          )  
) 
IBUF_U (
   .O                       (w_rx_ctl_ibuf      ),    
   .I                       (i_rx_ctl           )     
);

IDDR #(
    .DDR_CLK_EDGE   ("SAME_EDGE_PIPELINED"),  
    .INIT_Q1        (1'b0               ), 
    .INIT_Q2        (1'b0               ), 
    .SRTYPE         ("SYNC"             )
 ) 
 IDDR_U1 (
    .Q1             (w_rec_valid[0]     ), 
    .Q2             (w_rec_valid[1]     ), 
    .C              (w_rxc_bufio        ), 
    .CE             (1                  ), 
    .D              (w_rx_ctl_ibuf      ),  
    .R              (0                  ),  
    .S              (0                  )   
   );

BUFR #(
    .BUFR_DIVIDE    ("BYPASS"           ),   
    .SIM_DEVICE     ("7SERIES"          )  
   )
BUFR_inst (
    .O              (w_rxc_bufr         ),     
    .CE             (1                  ),  
    .CLR            (0                  ), 
    .I              (i_rxc              )     
   );
   
always@(posedge w_rxc_bufr)begin
    if(!i_speed1000 && (&w_rec_valid))
        r_cnt_10_100 <= r_cnt_10_100 + 1;
    else 
        r_cnt_10_100 <= 'd0;
end 

always@(posedge w_rxc_bufr)begin
    if(&w_rec_valid && i_speed1000)//2bit都为1时
        ro_rec_valid <= 'd1;
    else 
        ro_rec_valid <= r_cnt_10_100;
end 

always@(posedge w_rxc_bufr)begin
    if(i_speed1000)
        ro_rec_data <= w_rec_data;
    else 
        ro_rec_data <= {w_rec_data[3:0],ro_rec_data[7:4]};
end 

always@(posedge w_rxc_bufr)begin
    r_rec_valid <= w_rec_valid;
end 

always@(posedge w_rxc_bufr)begin
    if(!w_rec_valid && r_rec_valid)
        ro_rec_end <= 'd1;
    else 
        ro_rec_end <= 'd0;
end 

always@(posedge w_rxc_bufr)
begin
    if(w_rec_valid == 'd0) begin
        ro_speed <= w_rec_data[2:1];
        ro_link  <= w_rec_data[0];
    end else begin
        ro_speed <= ro_speed;
        ro_link  <= ro_link ;
    end
end
/*------- RGMII TX ---------*/
always@(posedge w_rxc_bufr)begin
    ri_send_data  <= i_send_data ;
    ri_send_valid <= i_send_valid;
end 

always@(posedge w_rxc_bufr)begin
    if(i_send_valid)
        r_tx_cnt_10_100 <= r_tx_cnt_10_100 + 1;
    else 
        r_tx_cnt_10_100 <= 'd0;
end 

OBUF #(
   .DRIVE       (12                 ),
   .IOSTANDARD  ("DEFAULT"          ),
   .SLEW        ("SLOW"             ) 
) 
OBUF_inst (
   .O           (o_txc              ),
   .I           (w_txc              ) 
);

genvar txd_i;
generate 
    for(txd_i = 0;txd_i < 4;txd_i = txd_i +1)begin
        assign w_send_d1[txd_i] = i_speed1000 ? i_send_data[txd_i]  
                                              : r_tx_cnt_10_100 == 0 ? i_send_data[txd_i] : ri_send_data[txd_i + 4];
        assign w_send_d2[txd_i] = i_speed1000 ? i_send_data[txd_i + 4]
                                              : r_tx_cnt_10_100 == 0 ? i_send_data[txd_i] : ri_send_data[txd_i + 4];
        ODDR #(
            .DDR_CLK_EDGE       ("OPPOSITE_EDGE"        ), 
            .INIT               (1'b0                   ),  
            .SRTYPE             ("SYNC"                 ) 
        ) 
        ODDR_U (
            .Q                  (o_txd[txd_i]           ), 
            .C                  (w_txc                  ), 
            .CE                 (1                      ), 
            .D1                 (w_send_d1[txd_i]       ), 
            .D2                 (w_send_d2[txd_i]   ), 
            .R                  (0                      ),   
            .S                  (0                      ) 
   );
    end 
endgenerate 

assign w_send_valid = i_speed1000 ? i_send_valid : i_send_valid | ri_send_valid;

ODDR #(
    .DDR_CLK_EDGE       ("OPPOSITE_EDGE"        ), 
    .INIT               (1'b0                   ),  
    .SRTYPE             ("SYNC"                 ) 
 ) 
ODDR_u4 (
    .Q                  (o_tx_ctl               ), 
    .C                  (w_txc                  ), 
    .CE                 (1                      ), 
    .D1                 (w_send_valid           ), 
    .D2                 (w_send_valid           ), 
    .R                  (0                      ),   
    .S                  (0                      ) 
   );
   

endmodule

代码风格是学习的B站FPGA奇哥,如果你喜欢这样的风格可以去B站找他学习。

四、感想

        我为什么想到写博客呢,因为最近在翻开以前写的笔记和总结的问题是发现自己记的笔记很多都是错的,当时理解的不对,就突发奇想记录一下我现在对FPGA的认识,也许等我一年后再翻开我写的博客,我可能会感叹这问题怎么能这样回答呢,这不是误人子弟吗。同时将自己现在对FPGA的理解发表出来也是想做个技术交流,也许我和你遇到了同样的问题,但是我们解决问题的思路不一样,可以借此为引做一个技术交流。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值