一、GMII接口信号定义与时序

GMII,全称Gigabit Media Independent Interface。

说白了,就是MAC层和PHY层之间的“翻译官”。

我刚开始接触千兆以太网时,总觉得这接口名字挺唬人。后来发现,它其实就是一组并行的数据线加上时钟和控制信号。

1.1 信号定义

GMII接口有8位数据总线。发送和接收各占8位。

我个人习惯把信号分成三组来看:

  • 发送组:GTX_CLK(125MHz)、TXD[7:0]、TX_EN、TX_ER
  • 接收组:RX_CLK(125MHz)、RXD[7:0]、RX_DV、RX_ER
  • 管理组:MDC、MDIO

这里有个坑。GTX_CLK和RX_CLK虽然都是125MHz,但来源不同。

GTX_CLK由MAC提供,RX_CLK由PHY恢复出来。

我曾经在项目中遇到过时钟不同步导致数据采错的问题,后来加了异步FIFO才解决。

1.2 时序要求

GMII的时序其实挺宽松的。数据在时钟上升沿采样。

但要注意,TXD和TX_EN必须在GTX_CLK上升沿之前建立好。

关键参数

  • 建立时间(Tsu):至少2ns
  • 保持时间(Th):至少2ns
  • 时钟周期:8ns(125MHz)

你想想看,8ns的周期里,留给你的裕量其实不多。

特别是当PCB走线长了,或者温度变化了,时序裕量会进一步缩小。

二、RGMII双沿采样机制

RGMII的出现,说白了就是为了省引脚。

GMII需要25根线,而RGMII只需要13根。

怎么做到的?答案就是双沿采样。

2.1 双沿采样原理

RGMII在时钟的上升沿和下降沿都传输数据。

上升沿传低4位,下降沿传高4位。

这样一来,数据线就从8根减到了4根。

嗯,这里要注意。RGMII的时钟频率还是125MHz,但数据速率翻倍到了250Mbps每根线。

所以RGMII对时序的要求比GMII严格得多。

我的经验

做RGMII设计时,一定要考虑PCB走线等长。我曾经因为数据线和时钟线差了5mm,导致高速模式下频繁丢包。后来用示波器一看,数据采样点已经跑到边沿上了。

2.2 时钟延迟调整

RGMII有个特殊要求:时钟需要相对于数据延迟至少1.5ns。

为什么?因为数据是在时钟边沿变化的,如果时钟和数据对齐,采样点就在数据跳变处,根本采不准。

延迟可以通过两种方式实现:

  • PCB走线延迟:时钟线比数据线长一些
  • 内部延迟:使用IDELAY或IODELAY原语

我个人更推荐用内部延迟。因为PCB走线延迟受工艺影响大,而且不好调整。

三、IDELAY与IODELAY原语使用

IDELAY和IODELAY是Xilinx FPGA里的专用硬件资源。

它们可以精细调整信号的延迟,精度能达到几十皮秒。

3.1 IDELAY原语

IDELAY用于输入路径的延迟调整。

它的工作原理很简单:把输入信号通过一串抽头延迟线,每个抽头约78ps(具体取决于器件)。

// IDELAYE2原语例化
IDELAYE2 #(
    .IDELAY_TYPE("FIXED"),      // 固定延迟模式
    .DELAY_SRC("IDATAIN"),      // 从IOB输入
    .IDELAY_VALUE(16),          // 延迟值,约1.25ns
    .HIGH_PERFORMANCE_MODE("TRUE")
) IDELAYE2_inst (
    .DATAOUT(data_delayed),     // 延迟后的数据
    .DATAIN(1'b0),              // 内部数据输入
    .C(CLK_200M),               // 参考时钟
    .CE(1'b0),                  // 使能
    .INC(1'b0),                 // 递增/递减
    .IDATAIN(rgmii_rx_data),    // 来自IOB的输入
    .LD(1'b0),                  // 加载
    .LDPIPEEN(1'b0),            // 流水线使能
    .REGRST(1'b0)               // 复位
);

我记得第一次用IDELAY时,调了半天发现延迟值不对。

后来才意识到,参考时钟频率会影响延迟精度。200MHz时钟下,每个抽头约78ps;如果换成300MHz,精度会更高。

3.2 IODELAY原语

IODELAY是IDELAY的升级版,同时支持输入和输出延迟。

在7系列及以后的FPGA中,推荐使用IODELAY。

注意事项

IODELAY的延迟值在配置后就不能动态改了(除非使用VAR_LOAD模式)。

所以,如果你需要在运行时调整延迟,记得选对模式。

四、接口时序约束实战

时序约束,说白了就是告诉工具你的信号什么时候该到。

不做约束,工具就会乱优化,结果就是你的设计在板上跑不起来。

4.1 输入延迟约束

对于RGMII输入,我们需要约束数据相对于时钟的到达时间。

# RGMII输入约束示例
# 时钟周期8ns,数据在时钟边沿变化
# 假设PCB走线延迟0.5ns

create_clock -name rgmii_rx_clk -period 8.0 [get_ports rgmii_rxc]

# 上升沿数据
set_input_delay -clock rgmii_rx_clk -max 0.5 [get_ports rgmii_rxd[*]]
set_input_delay -clock rgmii_rx_clk -min -0.5 [get_ports rgmii_rxd[*]]

# 下降沿数据(需要生成负沿时钟)
create_generated_clock -name rgmii_rx_clk_90 -source [get_ports rgmii_rxc] \
    -divide_by 1 -invert [get_pins clk_90_bufg/O]

set_input_delay -clock rgmii_rx_clk_90 -max 0.5 [get_ports rgmii_rxd[*]]
set_input_delay -clock rgmii_rx_clk_90 -min -0.5 [get_ports rgmii_rxd[*]]

这里有个技巧。下降沿的数据约束,我习惯用反向时钟来做。

这样工具能正确分析两个沿的时序。

4.2 输出延迟约束

输出约束相对简单。告诉工具数据从时钟沿到输出的时间。

# RGMII输出约束
set_output_delay -clock rgmii_tx_clk -max 1.0 [get_ports rgmii_txd[*]]
set_output_delay -clock rgmii_tx_clk -min -1.0 [get_ports rgmii_txd[*]]

我曾经在输出约束上吃过亏。约束太松,工具把逻辑推到了IOB外面,结果时序不满足。

后来我学乖了,输出延迟一般设到时钟周期的10%-20%。

4.3 跨时钟域处理

GMII/RGMII设计中,最头疼的就是跨时钟域。

RX_CLK和系统时钟是异步的,数据过来必须做同步处理。

推荐做法

  1. 用IDELAY把输入数据延迟到最佳采样点
  2. 用双触发器同步控制信号(RX_DV)
  3. 数据信号用异步FIFO跨时钟域
  4. 最后用系统时钟读出数据

嗯,这里要注意。异步FIFO的深度至少要有4,防止读写指针碰撞。

五、知识体系总览

下面这张图,是我梳理的本章知识结构。

你可以把它当作一个检查清单,看看自己掌握了哪些。

GMII与RGMII接口知识体系 千兆以太网接口 GMII接口 8位并行数据 125MHz时钟 控制信号 RGMII接口 4位双沿采样 时钟延迟1.5ns 引脚数减半 IDELAY/IODELAY 输入延迟调整 输出延迟调整 78ps精度 时序约束实战 输入延迟约束 输出延迟约束 跨时钟域处理 掌握这四块,GMII/RGMII接口设计不再难

避坑指南

我曾经在调试RGMII时,发现数据总是偶尔出错。查了三天,最后发现是IDELAY的参考时钟没接对。

IDELAY的参考时钟必须是一个稳定的高频时钟(至少200MHz),不能用125MHz的以太网时钟。

否则延迟精度不够,采样点会飘。

好了,这一章的内容就到这里。

GMII和RGMII的接口信号、时序要求、延迟调整和约束方法,我都结合自己的项目经验讲了一遍。

你如果在实际开发中遇到问题,可以回头看看这张知识体系图,定位一下是哪个环节出了问题。


公众号:蓝海资料掘金营,微信deep3321