简介:SGMII Specification V1.8 是一份面向以太网物理层与 MAC 设计、FPGA 高速接口开发及硬件验证人员的接口规范,重点解决 10/100/1000M 端口在减少信号引脚数量的同时传输网络数据与端口速率的问题。压缩包共 1 个 PDF 文件,大小约 133KB,内容集中、便于查阅;已有 3665 人学习下载,在接口设计、联调与排错场景中具备一定参考热度。文档围绕两对数据信号、两对时钟信号、1.25Gbaud 传输速率和 625MHz DDR 时钟展开,说明基于 1000BASE-SX PCS 的编码、串行化与时钟恢复机制,并梳理 MII 与 GMII 的定义、自协商流程、数据采样以及前导码首字节可能丢失等细节。V1.8 版本重点在 PHY 发送路径加入 shim,于 TX_EN 未断言时抑制 TX_ER,同时保留 Cisco 知识产权相关规范,适合用于接口时序核对、兼容性检查与调试参考,对理解接口差异、定位 TX_EN/TX_ER 异常与核对自协商结果均有帮助。
1. SGMII 规范到底解决什么问题:四对差分线顶掉 GMII 的二十四根走线
第一次在一块四层板上把 MAC 和光模块对接时,我盯着原理图上 GMII 那一大把线发愁:16 根数据、4 根控制、2 根时钟,再加 CRS 和 COL,正好 24 根,走线密度顶到天花板,连接器也快塞不下了。后来把这段换成 SGMII,收发各一对差分、时钟各一对差分,八根单端线跑完 10/100/1000 三档速率,布线一下就活了。但省下来的引脚,复杂度全转嫁进了协议:8B/10B 编码、双沿采样、速率适配、自协商寄存器,每一样都能让人调一整天。这份 SGMII Specification V1.8(文档编号 ENG-46158)就是把这套东西讲清楚的原始依据——它描述 PHY 与 MAC 之间如何用两根数据信号加两根时钟信号传递帧数据和链路速率,并给出 LVDS 电气参数与自协商位定义。适合正在做 MAC/PHY 对接、光模块选型、FPGA 侧 RTL 实现,以及握着示波器排查千兆/百兆互通问题的人。
2. 读规范的正确顺序:从 8B/10B 到 1.25 Gbaud 的链路拆解
拿到这类只有十几页、图比字多的接口规范,很多人第一反应是从第一页翻到最后一页,结果被一堆符号图劝退。我的习惯是反过来:先算引脚账和速率账,把链路画成一条从 MAC 到 PHY 的单向流水线,再回头去看每一段用了哪个标准里的哪张图。SGMII 每条路径的流水线是这样的:GMII 侧 125 MHz 的 8 位数据先进速率适配块,变成 PCS 发送状态机的输入,编成 10 位编码段,再串化成 1.25 Gbit/s 的差分对发出去,同时输出 625 MHz 的 DDR 时钟。反向路径把这几步倒过来做一遍,中间还夹着一个同步状态机盯着编码段有没有跑偏。
2.1 引脚账与速率账:四对差分线是怎么算出来的
先把数字对一遍,这一遍对完,整篇规范的骨架就出来了。GMII 侧是 8 位数据加 4 个控制信号(TX_EN、TX_ER、RX_DV、RX_ER),再加 GTX_CLK、RX_CLK 两个时钟和 CRS、COL 两个状态信号,合计 24 根单端线。SGMII 侧把这些全部串行化:TX、RX、TXCLK、RXCLK,四对差分,八根线,省下三分之二。
速率的算法是:8B/10B 编码后每字节变成 10 bit,数据通路按 125 MHz 时钟出 10 位,所以线速率是 125 M × 10 = 1.25 Gbaud;而 DDR 时钟只有 625 MHz,靠双边沿采样,一个时钟周期送两位。这个 1.25 Gbaud 相对千兆以太网的 1 Gbit/s 有效载荷,开销是 25%(8B/10B 的 20% 加上控制有序集),这是用引脚数换带宽的必然代价,实现时别指望能省。
需要提前算清楚的是另一个账:为什么 625 MHz 的 DDR 时钟会让板级设计变紧。625 MHz 对应周期 1.6 ns,双沿采样后有效的数据窗口只有 800 ps 量级,再被走线偏斜、连接器、过孔吃掉一部分,剩下的余量非常有限。所以在这类接口上,差分对等长、阻抗控制不是锦上添花,是能不能跑通的前提。
提示:规范里给的时序参数是"代表性预算",具体怎么分配到 PCB、连接器、芯片内部,规范明确说是实现相关的。别指望从文档里抄一个固定数字当验收线。
2.2 复用 1000BASE-X 的 PCS:哪一段能直接抄,哪一段必须改
SGMII 最聪明的地方是没有重新发明编码,而是直接借用 1000BASE-X 的 PCS 层,收发数据通路都跑在这套状态机上。这意味着 8B/10B 编解码表、有序集定义、同步与失步重对齐逻辑,都可以从对应的标准条款里搬。
能直接用的:8B/10B 编解码、逗号对齐(comma)、同步状态机的失步判定与重对齐、发送侧的包起始与包结束分隔符机制。规范里描述的流程是:PHY 根据 RX_DV 的拉高和拉低,编码出 Start_of_Packet 分隔符(/S/)和 End_of_Packet 分隔符来标记每帧的起止;MAC 靠检测这两个分隔符恢复 RX_DV;PHY 编码 Error_Propagation 有序集(/V/)表示传输错误,MAC 检测到它就拉高 RX_ER。
必须自己改的有四处,这也是这份文档存在的理由:自协商里的 link_timer 从 10 ms 改成 1.6 ms;tx_config_Reg 的 16 位位定义完全重写,语义和原标准不同;速率适配块放在 PCS 之上,负责低速时的帧延长;PHY 发送通路上加一个 shim,在全双工下抑制 TX_ER。
新手最容易翻车的地方就在第二点。很多人看到"自协商用 Config_Reg",就直接拿标准里的位定义去解码,结果读出来的"速率"字段全是错的。SGMII 的 tx_config_Reg 是另一套编码,必须对着这份规范里的位定义表逐位解析。
2.3 625 MHz DDR 与 90 度相移:源同步时钟的边界在哪
SGMII 是源同步接口:发送端数据与时钟一起发,接收端按收到的时钟采样。规范里给的参考电路是数据与时钟保持 90 度相位差,接收端在时钟的两个边沿采样数据,这样数据跳变正好落在采样点的中间,最舒服。
但规范同时留了一个口子:实现可以选择从数据里恢复时钟,而不是用随路时钟。这在光模块、长背板这类场景很常见,因为随路时钟和数据的偏斜在长距离上会跑得很难看。选择恢复时钟要接受一个约束——所有发出数据的源端,无论接收端用什么方式采样,都必须把自己的随路时钟产生出来。也就是说,你可以不用,但对方必须给,这是做兼容性验证时容易被忽略的一条。
做 RTL 的人在这一节要盯住两个点:一是 tclock2q 的最小值和最大值构成了一个采样窗口,你的接收逻辑如果只用单沿采样,等于主动放弃了半个窗口的余量;二是同步状态机的重对齐动作会带来周期级的停顿,状态切换期间的 RX_DV 行为要按失步处理,不能在重对齐瞬间把半个字节当有效数据吐给上层。
3. 10/100 Mbps 的帧延长:重复 10 次、100 次背后的采样规则
SGMII 的线速率固定 1.25 Gbaud,但端口可以跑 10M、100M、1000M 三档。千兆时数据刚好填满,百兆和十兆时链路会空得离谱。规范的处理办法很朴素:把每个字节重复 10 次(100 Mbps)或 100 次(10 Mbps),带宽就"占满"了。这种延长发生在 PCS 层之上,所以起始分隔符每帧只出现一次,不会跟着重复。反过来,MAC 侧接收时要从 10 份或 100 份里挑出 1 份,挑错了就是整帧错位。
3.1 速率适配块的位置决定了 SFD 只能出现一次
先看 PHY 侧接收方向:外部 10/100/1000 PHY 送来的 GMII 信号按 2.5/25/125 MHz 的时钟进来,经过接收速率适配块,统一输出到 125 MHz 时钟域下的 8 位 RXD,然后才进 PCS 发送状态机编成 10 位段。发送方向反过来:从 MAC 收到的编码段先过 PCS 接收状态机还原成 GMII 信号,再过发送速率适配块按端口速率输出。
这个顺序很关键。因为速率适配在 PCS 之上,PCS 看到的已经是被拉长的字节流,它会在这一串相同字节的最前面加一个 /S/,在后面加一个结束分隔符,中间的重复字节它不关心。所以 MAC 侧如果傻乎乎地按"每 10 个字节取 1 个"去抽,一旦起点错了,抽出来的就是同一字节的错位版本,表现是整帧数据平移,CRC 全错但长度看着正常——这种故障最难查,因为计数器上什么都不会报。
正确的锚点是 /S/。接收侧必须以第一个数据段(也就是 /S/ 后面的第一个有效字节)作为采样起点,之后每 10 个(或 100 个)数据段采一个,这就是规范里 SAMPLE_EN 的作用。
3.2 前置码必须给足两个 0x55:首字节可能只重复 9 次
规范对帧起始做了硬性要求:PHY 发出的帧,开头必须至少有两个前置码符号,后面跟一个 SFD 符号,具体到 RXD[7:0] 上的形态是 {8'h55, 8'h55, …, 8'hD5},之后才是有效帧数据。为什么强调"至少两个",因为 PCS 层可能会把延长帧的第一个字节吃掉。
这就带来一个很反直觉的结论:在 100 Mbps 模式下,第一字节的前置码可能只被重复 9 次,而不是 10 次;10 Mbps 模式下可能是 99 次而不是 100 次。原因是 802.3z 发送状态机的算法本身不保证第一个字节被完整重复满。所以接收逻辑绝对不能假设"第一个字节一定重复满",也不能用自由跑的计数器去定位采样点,必须以 /S/ 为锚。
做 PHY 侧发送的人则要反过来检查:你的前置码给够了吗?如果只给一个 0x55 就上 SFD,在低速档位下很可能被吃掉,对端同步不上或者直接丢帧。
3.3 用一段 Python 把帧延长和抽取模型跑一遍
纸上推演容易漏边界,我习惯写个几十行的模型把延长和抽取都模拟一遍,把"丢首字节"这个条件也加进去,看错误是怎么产生的。
# 模拟 SGMII 速率适配:帧延长 + MAC 侧抽取 PREAMBLE = [0x55] * 7 + [0xD5] # 常规前置码,规范要求至少 2 个 0x55 + SFD PAYLOAD = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06] FRAME = PREAMBLE + PAYLOAD FACTOR = {1000: 1, 100: 10, 10: 100} # 每个字节重复的次数 def elongate(frame, speed): """PHY 接收速率适配:按端口速率把每个字节重复 N 次""" f = FACTOR[speed] out = [] for b in frame: out.extend([b] * f) return out def sample_anchored(stream, speed): """MAC 侧正确做法:以 /S/ 后的第一个数据段为锚,每 f 个取一个""" f = FACTOR[speed] return bytes(stream[0::f]) def sample_by_counter(stream, speed): """MAC 侧错误做法:计数器自由跑,不管锚点,且首字节被 PCS 吃掉一个""" f = FACTOR[speed] eaten = stream[1:] # PCS 可能移除延长帧的首字节 return bytes(eaten[0::f]) for sp in (100, 10): total = elongate(FRAME, sp) ok = sample_anchored(total, sp) bad = sample_by_counter(total, sp) print(sp, "Mbps 延长后长度", len(total)) print(" 锚点抽取:", ok[:6].hex(), "正确" if ok == bytes(FRAME) else "错") print(" 丢首字节+自由计数:", bad[:6].hex(), "正确" if bad == bytes(FRAME) else "错")这段代码跑出来的结果会很清楚:只要丢掉第一个字节又不用锚点,抽取结果第一个字节就变成 0x55 的重复项,后面整体平移,长度甚至可能差一个。参数上有三处可以改着玩:把FACTOR改成 1000 验证千兆直通路径;把PREAMBLE缩成一个 0x55 看前置码不足时抽取起点怎么崩;把eaten改成不丢字节,对比两种抽法的差异。
真实硬件里sample_anchored对应的是 SAMPLE_EN 使能后从第一个数据段开始每 N 段锁存一次,逻辑等价但要用状态机实现,不能用取模运算——因为帧与帧之间的空闲期会打断计数。
4. tx_config_Reg 自协商:16 位寄存器怎么把速率、双工、链路状态送过去
引脚省下来之后,MAC 怎么知道对端 PHY 现在跑的是百兆还是千兆、全双工还是半双工?SGMII 的答案是复用自协商机制:PHY 把控制信息塞进 16 位的 tx_config_Reg,通过自协商过程发给 MAC;MAC 收到后回一个确认,双方再切回正常数据通路。整个过程有几个毫秒的延迟,这个延迟值是定死的,超了就是实现有问题。
4.1 位域逐位拆解:两个方向的定义不对称
这是整份规范里最该背下来的一张表。PHY 发给 MAC 的 16 位和 MAC 回给 PHY 的 16 位,含义完全不同,而且大部分位是保留位。
| 位 | PHY → MAC 含义 | MAC → PHY 含义 |
|---|---|---|
| 15 | Link:1 链路 up,0 链路 down | 保留 |
| 14 | 保留(自协商确认位) | 自协商确认:置 1 表示已收到更新 |
| 13 | 保留 | 保留 |
| 12 | 双工:1 全双工,0 半双工 | 保留 |
| 11:10 | 速率:10=1000M,01=100M,00=10M,11 保留 | 保留 |
| 9:0 | 保留 | 保留 |
用法上要注意三点。第一,只有 PHY 侧会主动发起更新,触发条件是它检测到控制信息变了(比如插拔网线导致链路状态翻转、或者协商结果变了),此时 PHY 把发送块从"数据"态切到"配置"态,把新的 tx_config_Reg 发出去。第二,MAC 侧收到后必须回一个 bit14 置 1 的确认,PHY 收到确认才算这次协商完成,然后回到数据态。第三,如果 MAC 一直不确认,PHY 会停在配置态反复发,链路数据通路就一直是断的——这是"寄存器读着都对但就是不通"的经典成因。
还要提醒一句位定义的坑。规范 1.4 版的修订记录里明确写着,把 tx_config_Reg[0] 从 0 改成 1 才能让自协商正常工作,可位定义表里 bit0 标的是"保留"。表和修订记录打架的情况在老规范里不罕见,落地时以能跑通协商的实测行为为准,别自作主张把这一位"清理"成 0。
4.2 1.6 ms 的 link_timer 与 3.4 ms 的链路更新延迟
标准自协商的 link_timer 是 10 ms,SGMII 把它改成了 1.6 ms。改小的理由很实际:链路速率变化需要尽快通知到 MAC,10 ms 的粒度在频繁切换速率的场景下太钝。
由此推出的端到端延迟是 3.4 ms,算法是两个 link_timer 加一次确认过程:PHY 发配置信息等一个 timer,MAC 回确认等一个 timer,再加确认往返的处理时间。这个数字可以直接拿去写验收用例——从 PHY 侧检测到链路状态变化,到 MAC 侧寄存器读到新值,超过 3.4 ms 量级就该怀疑是不是 link_timer 没按 1.6 ms 配,或者确认位没回。
外部 IP 核最容易在这里埋雷。有些早期 IP 直接沿用了 10 ms 的默认值,功能上看着正常,就是切换慢半拍,测试用例里如果只验"最终状态对不对"根本发现不了,得专门测切换时间。
4.3 上板读寄存器:把 16 位配置字解出来
规范里的 tx_config_Reg 在实际芯片上通常映射到自协商用的配置寄存器对(本端配置寄存器和对端配置寄存器),通过 MDIO 就能读。用常见的小工具读一下:
# 读 PHY 地址 0x1 的配置寄存器(本端 / 对端各一个 16 位字) phytool read eth0/0x1/0x04 # 本端 Config_Reg phytool read eth0/0x1/0x05 # 对端 Config_Reg # 触发一次重新自协商:控制寄存器 bit12 使能 AN,bit9 重启 phytool write eth0/0x1/0x00 0x1200读回来的十六进制数直接看是没意义的,写个解析函数对着位定义表拆:
SPEED = {0b10: 1000, 0b01: 100, 0b00: 10} # 11 为保留值 def decode_phy_to_mac(reg: int) -> dict: """解析 PHY 发给 MAC 的 tx_config_Reg[15:0]""" return { "link": (reg >> 15) & 0x1, # 1 = 链路 up "duplex": "full" if (reg >> 12) & 0x1 else "half", "speed": SPEED.get((reg >> 10) & 0b11, "reserved"), } def decode_mac_to_phy(reg: int) -> dict: """解析 MAC 回给 PHY 的确认字,只有 bit14 有意义""" return {"ack": (reg >> 14) & 0x1} # 示例:0x8C00 -> link=1, duplex=full, speed=1000 print(decode_phy_to_mac(0x8C00))三个函数各有各的用处。decode_phy_to_mac用来确认 PHY 报的速率和双工跟预期一致;decode_mac_to_phy用来确认 MAC 到底回没回确认——如果一直是 0,链路协商永远完不成;写控制寄存器那一步用来在调试时强制重协商,比拔插网线快得多。参数上要注意 PHY 地址,多 PHY 的系统里地址搞错会读到隔壁芯片的寄存器,值看着"像那么回事"但完全对不上,这种错最难发现。
5. 避坑与排查:TX_ER shim、Config_Reg 误读与 AC 耦合的五次翻车
规范只有薄薄一份,但每一条看似废话的描述背后基本都对应一个真实会踩的坑。下面这几条是我和同事在不同板子上反复遇到的,按"现象—原因—解决"整理,遇上了可以直接对号入座。
5.1 五个高频翻车现场
现象一:千兆跑得好好的,一切到百兆就大量 CRC 错,包长还对。原因是不用 /S/ 做锚点,靠自由计数器每 10 个字节抽一个。前置码第一字节可能只重复 9 次,起点一偏,整帧平移。 解决是接收侧用 SAMPLE_EN 从第一个数据段开始计数,把 /S/ 检测作为唯一复位条件。改完再跑一遍长包短包混合流量,确认没有偶发。
现象二:链路状态寄存器读出来是千兆全双工,实际插的是百兆电口。原因十有八九是拿标准里的 Config_Reg 位定义去解 SGMII 的字。两者位定义完全不同,速率字段的位置都不一样。 解决是严格按 SGMII 的位定义表解析,并且确认读的是"PHY 发给 MAC"那个方向。另外别忘了 bit0 的历史包袱,按修订记录的实测行为配。
现象三:全双工下偶尔丢最后一小段,对端报 RX_ER,换个老网卡更明显。这是 1.8 版专门修的问题。某些老端点只要在帧结束后的第一个时钟看到 RX_ER 拉高就直接丢帧,而接收 PCS 状态机在特定帧结尾恰好会产生这个信号。 解决是在 PHY 发送通路上加 shim:全双工模式下,当门控信号未断言时把 TX_ER 强制拉 0。要注意规范正文里两处措辞分别写的是 TX_EN 和 RX_DV,实现时以自己数据通路上实际的那个信号为准,验证时两种条件都覆盖一遍。
现象四:为了隔离直流加了 AC 耦合电容,链路时通时断,温度一变就变。SGMII 的编码本身是 DC 平衡的,规范也允许在满足 AC 参数但不满足 DC 参数时做 AC 耦合。问题往往出在耦合之后没重新核共模:输出偏移电压 Vos 的允许范围是 1075~1325 mV,耦合电容和接收端偏置没配好,共模就飘出去了。 解决是耦合归耦合,测试点保留,实测 Vos 和差分电压 |Vod|(150~400 mV)是否还在窗口内,同时确认输出过冲 Vring 没超过 10%。
现象五:切换速率后主机侧要等十几毫秒才反应过来,自动化测试偶发超时。原因是用外部 IP 时沿用了 10 ms 的默认 link_timer,而 SGMII 要求 1.6 ms,端到端应该在 3.4 ms 量级完成更新。 解决是改 link_timer 参数,并把"链路状态变化到寄存器更新"的时间做成一条固定测试项,用示波器或者时间戳日志卡一遍,别只验最终状态。
5.2 上电自检的固定顺序
板子第一次上电时,我一般按固定顺序走一遍,避免多个问题叠在一起互相掩盖:
- 先量静态电平。四对差分线的共模、差分摆幅、终端电阻,在没建立链路前就应该在规范窗口内。
- 再看时钟。625 MHz DDR 时钟有没有、占空比和交叉点是否正常、数据与时钟的 90 度相移对不对。
- 然后强制自协商。用控制寄存器触发一次协商,读本端和对端的配置字,确认 bit15 链路位翻转、bit11:10 速率正确。
- 接着验证确认通路。读 MAC 侧的确认位,确认它确实回了 bit14,而不是一直挂在那里。
- 最后跑流量。从千兆开始,逐步降到百兆、十兆,每一档都跑长包、短包、突发三种模式,重点看 CRC 错和丢包位置。
这个顺序的价值在于:前四步任何一步不过,第五步的流量测试结果都没有参考意义,只会把你带偏。
6. 拿规范当验收单:LVDS 参数与时序预算的逐项核对
规范最后给了一张驱动器的 DC 参数表,很多人扫一眼就跳过去了,实际上这张表是硬件验收最硬的依据。参考负载是 100 Ω±1%,所有参数都在这个负载下测:
| 参数 | 含义 | 最小 | 最大 | 单位 |
|---|---|---|---|---|
| Voh | 输出高电平 | 1525 | — | mV |
| Vol | 输出低电平 | — | 875 | mV |
| Vring | 输出振铃 | — | 10% | — |
| |Vod| | 输出差分电压 | 150 | 400 | mV |
| Vos | 输出偏移电压 | 1075 | 1325 | mV |
| Ro | 单端输出阻抗 | 40 | 140 | Ω |
| ΔRo | 对内失配 | — | 10% | — |
| Δ|Vod| | 0/1 之间差分电压变化 | — | 25 | mV |
| ΔVos | 0/1 之间偏移电压变化 | — | 25 | mV |
| Isa/Isb | 单端对地短路电流 | — | 40 | mA |
| Isab | 两端短接电流 | — | 12 | mA |
| Ixa/Ixb | 掉电漏电流 | — | 10 | mA |
这张表里有两个数最容易被忽略。一个是 Δ|Vod| 和 ΔVos 都是 25 mV,管的是"0"和"1"两种状态之间的对称性,单看某一点的绝对值合格没用,码型变化时电平漂移超了照样出问题,测的时候要用交替码型而不是长连 0 或长连 1。另一个是 Ro 的 40~140 Ω 范围宽得离谱,这说明规范对阻抗匹配的态度是"给足自由度但你必须自洽",做板时该按 100 Ω 差分阻抗走还是按芯片手册走,得看具体器件,不能拿这个宽范围当挡箭牌。
时序这边,规范给的是收发路径的代表性预算框架,明确说具体分配是实现相关的。所以别去找一个"标准余量值",正确做法是自己建一张表:把发送端 tclock2q 的最大最小值、PCB 走线偏斜、连接器与过孔损耗、接收端建立保持窗口四项列出来,用最坏情况做加减,看剩下的裕量。我一般会留出至少 20% 的余量给温度和工艺角漂移,尤其是背板加连接器的场景。
有个习惯是从那以后一直保持的:不管链路看着多正常,验收时一定把这张 DC 参数表和时序余量表各走一遍,实测值填进去,缺一项就不签字。因为这类接口的故障特别玄学——它会自己在机房里跑一个星期不出事,然后在客户现场第一次上电就挂。规范不会告诉你怎么修板子,但它把该量的点都列出来了,剩下的就是老老实实一格一格填。希望帮到你。
本文还有配套的精品资源,点击获取