一、前言
以太网的通信离不开物理层PHY芯片的支持,以太网MAC和PHY之间有一个接口,这个接口最常见的是GMII和RGMII。
GMII接口全称为Gigabit Medium Independent Interface,它向下兼容MII接口,支持10Mbps、100Mbps和1000Mbps的操作,数据位宽为8位,在1000Mbps传输速率下,时钟频率为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的理解发表出来也是想做个技术交流,也许我和你遇到了同样的问题,但是我们解决问题的思路不一样,可以借此为引做一个技术交流。