以太网标准802.3解析:分层编码、FEC与链路排查

发布时间:2026/10/24 13:31:23
以太网标准802.3解析:分层编码、FEC与链路排查

简介:IEEE 802.3-2022《以太网标准》正式版面向网络工程师、协议与芯片开发者、PHY及测试认证人员,提供从1 Mb/s到400 Gb/s以太网MAC、MII、PHY与管理信息库的权威规范依据,可用于接口设计、合规验证和协议排错。相较2018版,文档扩充至约7025页,重点增补25G至100G相关条款并修订10G内容,同时整合此前多项修订。资源包为1个PDF文件,大小93.79MB,单文件体量较大,但章节体系完整,便于按速率、子层或关键词检索。当前已有287人学习,适合需要查阅正式标准原文、核对寄存器与电气规范或撰写测试方案的读者。需注意2022版之后的802.3df-2024、802.3ck-2022等修订未纳入,涉及400/800Gb/s及100/200/400Gb/s新增内容时应另行对照。

1. 7025 页不是冗余:以太网标准为什么必须这么厚

打开 802.3-2022 这份以太网标准,第一反应多半是文件坏了:7025 页。比 2018 版多出来的一千四百多页,绝大部分在讲 25G 到 100G 这一段链路怎么把比特稳稳送出去。反直觉的地方在于,以太网标准的膨胀不是因为技术越堆越多,而是因为它几乎不删东西——上世纪的 CSMA/CD 还躺在文档里,当着半双工的最后一道兼容底线;8b/10b 编码也还在,只是退到了 1G 和更老速率的角落里。搞懂这份文档的价值就在这儿:它把“为什么这么设计”的账全记在条款编号、状态机和参数表里,而不是散落在各种二手的“经验帖”里。读懂它,你排查链路不通的时候就不再靠玄学。

2. 分层不是画着好看:MAC 与 MII 之间为什么要插一个 RS

以太网的分层图谁都见过:MAC、RS、MII、PCS、PMA、PMD、MDI。但多数人对 RS(Reconciliation Sublayer,协调子层)的印象就是一个“过渡方块”,画在那占位置。实际上这层是整个设计里最省事的一笔投资——它把“帧”这种逻辑概念和“引脚上的电平”这种物理事实彻底隔开了。

2.1 RS 到底做了什么:从服务原语到引脚电平的翻译

MAC 子层是纯逻辑,它只认字节流和状态位:这帧有效、那帧错、现在可以发。它不知道对面是同轴、双绞线还是光纤,也不知道速率是多少。RS 的存在就是把这套内部语言翻译成 MII 上的信号组:发送时给出 TX_EN 和 TXD,接收时把 RXD、RX_DV、RX_ER 收进来,同时把 PHY 报上来的载波监听、冲突检测映射回 MAC 需要的状态。

这意味着同一套 MAC 逻辑,接 10M 的 PHY 和接 400G 的 PHY,上层代码几乎不用改,换掉的只是 RS 与 MII 之间的适配。常见的实现思路是把前导码和 SFD 的生成放在 RS 一侧、由 MAC 提供帧内容;也有些实现把它们算作 MAC 的一部分。两种划分在标准里都能找到对应描述,真正重要的是那条边界:前导码参与物理同步,但不参与 FCS 校验。

PREAMBLE = b"\x55" * 7 # 7 字节,作用是让接收端 CDR 锁定、AGC 收敛 SFD = b"\xd5" # 帧起始定界符,最后一个字节用 0xD5 而不是 0x55 def to_wire(payload: bytes, ifg_bits: int = 96): frame = payload # 已含 DA / SA / Type # 1. 最小帧长 64 字节(不含前导码):载荷不足就补零,补的是载荷,不是前导码 if len(frame) < 60: frame += b"\x00" * (60 - len(frame)) # 2. FCS 覆盖 DA 到载荷末尾,不含前导码和 SFD;字节内比特是 LSB 先发 fcs = crc32(frame) frame += fcs.to_bytes(4, "little") # 低字节先上线,这点最容易写错 # 3. RS 把前导码和 SFD 贴在帧头交给 MII,PHY 全程不改内容 return PREAMBLE + SFD + frame, ifg_bits

这段代码里有三个参数值得记住,因为它们在排查现场会反复出现。60是不含 FCS 的最小载荷加头部长度,加上 4 字节 FCS 正好 64 字节——短于此的帧会被 PHY 或交换芯片直接丢弃,抓包工具里表现为“runt”。crc32的输出必须按小端序写入,因为以太网规定每个字节内部低位先发,这个比特序问题在自制抓包工具或 FPGA 实现里翻车率极高。ifg_bits = 96是帧间隔,也就是 12 个字节时间,留给接收端做时钟恢复和缓冲,半双工下还兼作别的站点的退避窗口。

2.2 MII 的每一次重造:为什么速率一提接口就得换

MII 是 MAC 和 PHY 之间的契约。契约的内容很简单:并行数据总线、时钟、几个控制信号。问题是速率一上去,这套并行总线就没法用了——要么把时钟拉到几百兆,要么把位宽摊到几十位,两条路在 PCB 上都走不通。于是标准选择了第三条路:逻辑上保持一个 MII,物理上换成串行加编码,或者拆成多条 lane。

接口位宽时钟承载速率典型场景
MII4 bit25 MHz100 Mb/s快速以太网
GMII8 bit125 MHz1 Gb/s千兆铜缆/光纤
RGMII4 bit DDR125 MHz1 Gb/s交换芯片省引脚
SGMII1 对差分1.25 Gb/s1 Gb/s光模块、板间
XGMII32 bit DDR156.25 MHz10 Gb/s10G 早期实现
CGMII64 bit多 lane100 Gb/s100G 背板

SGMII 是这张表里最能说明设计动机的一行:它把 8 位并行总线换成一对 1.25 Gb/s 差分线,用 8b/10b 编码把 1 Gb/s 的有效载荷塞进去。代价是 20% 的编码开销,换来的是管脚数量和布线难度大幅下降。这种“拿带宽换工程可行性”的取舍,在后面每一代速率上都会重演一次。

管理接口同理。早期的 MDIO 用 5 位寄存器地址,一共 32 个寄存器,够用了。到 10G 之后,PCS、PMA、PMD 各自都有几十个寄存器和一堆误码统计计数器,32 个地址根本不够分,于是有了间接寻址的管理接口:先写设备地址,再写寄存器地址,最后读写数据。你在一台设备上读误码计数,背后走的往往是这一串间接访问。

2.3 半双工为什么还留着:CSMA/CD 与 PLCA 的接力

CSMA/CD 的核心不是“监听”,而是那个冲突窗口。发送站在发出第一个比特后的一个往返时间内必须能检测到冲突,否则它已经把帧发完了,重传就没有意义。这个窗口叫时隙时间,10 Mb/s 下是 512 比特时间,也就是 51.2 微秒;换算成同轴电缆的传播速度,就倒推出约 2500 米的网络直径上限。整条链路的物理尺寸,是被 MAC 层的一个计时器定死的。

到了千兆,同样的 512 比特时间只剩 0.512 微秒,物理上根本铺不开,所以千兆半双工基本只存在于纸面。真正有意思的是后来的分支:面向车载和工业的多点总线上,标准没有沿用 CSMA/CD,而是引入了 PLCA——用一个协调节点周期性发 beacon,各站按节点 ID 轮流获得发送机会。冲突从“随机发生再退避”变成“结构上不会发生”,代价是需要一个协调者、需要维护轮转周期,链路里多了一个必须活着的角色。

3. 64b/66b 与 PCS:编码开销从 25% 降到 3% 的代价

物理编码子层是整份标准里条款最密集的地方,也是最容易被跳过的地方。跳过它的后果是:你能算出线速,但算不对有效吞吐;你能看到链路 up,但不知道为什么误码计数在涨。

3.1 8b/10b 的 25% 税,和 64b/66b 怎么把它压到 3.125%

8b/10b 的逻辑很优雅:每 8 位数据映射成 10 位码组,保证任意码流里 0 和 1 的数量大致平衡(直流平衡),并且连续相同比特不超过 5 个(游程受限)。这两个性质正好喂饱接收端的时钟恢复电路和 AC 耦合电容。代价是每传 8 位要发 10 位,25% 的带宽就这么交出去了。

10G 开始这笔税交不起了。64b/66b 的思路完全反过来:不再靠编码表保证游程,而是给每 64 位数据加一个 2 位同步头,01表示这 64 位全是数据,10表示里面混了控制字符,00和11是非法组合,用来判错。剩下的比特平衡问题交给扰码器,用的生成多项式是 1 + x^39 + x^58,把数据打散成近似随机的序列,长连 0 和长连 1 的概率被压到可接受范围。

SYNC_DATA, SYNC_CTRL, SYNC_BAD = 0b01, 0b10, 0b00 def encode_block(payload64: int, is_control: bool, scrambler_state: int) -> int: # 1. 先扰码:只有 64 位载荷参与,同步头不扰码,否则接收端无法识别边界 scrambled = scrambler(payload64, scrambler_state) # 2. 同步头是唯一的定界信息,接收端靠它做块同步,靠它区分数据块与控制块 sync = SYNC_CTRL if is_control else SYNC_DATA return (sync << 64) | scrambled # 控制块里的类型字段决定语义:0x1E=IDLE,0x78=START,0xFE=ERROR CTRL_IDLE, CTRL_START, CTRL_ERROR = 0x1E, 0x78, 0xFE

代码里最关键的一行是注释“同步头不扰码”。这是块同步能工作的前提:接收端先扫描 66 位窗口,看这 2 位是不是合法的01或10,合法才认为对齐。如果同步头也被扰码,边界信息就没了,接收端只能靠猜测。

开销从 25% 降到 3.125%,账面上很划算,代价是接收端多了一整套状态机:块同步、扰码器同步、控制字符解析。而且因为不再有编码表兜底,一旦同步丢失,恢复过程比 8b/10b 慢得多。

3.2 块同步与对齐标记:接收端怎么知道自己对齐了

块同步的逻辑在标准里写成状态机,核心是两个计数器。接收端连续收到 64 个合法的同步头,才认为锁定;锁定之后如果连续若干个块同步头非法,就判定失锁,向下滑动一个比特重新找边界。这个“64 个”不是随便定的,太少会被随机数据里的伪同步头骗过去,太多则恢复时间过长。

多 lane 场景更麻烦。100G 走 4 条 25G lane,每条 lane 有自己的延迟,接收端必须把它们重新对齐成一个字节流。做法是在每条 lane 上周期性插入对齐标记,标记里编了 lane 编号,接收端一旦在每条 lane 上都找到对齐标记,就知道各自的偏移量,做去偏斜。

def block_lock(rx_bits): good = 0 for i in range(0, len(rx_bits) - 65, 66): sync = (rx_bits[i] << 1) | rx_bits[i + 1] if sync in (0b01, 0b10): good += 1 if good >= 64: return ("LOCKED", i) # 连续 64 个合法同步头才认锁 else: good = 0 # 一个非法就清零,滑动 1 bit 重找 return ("HUNT", None)

实际实现里不会真的逐比特滑,而是几个候选相位并行检测。但状态机的语义就是这个意思:锁定的判据是连续命中,而不是单次命中。这条设计原则能解释很多现场现象,比如链路起来后误码计数先涨一段再稳定,那就是同步还在收敛。

3.3 PAM4 与通道绑定:25G/lane 之后信噪比怎么换带宽

NRZ 每个符号周期只传 1 位,判决门限一个,眼睛张开度大,抗噪好。PAM4 每个符号传 2 位,四个电平三个眼,相同波特率下速率翻倍,代价是每个眼的张开度只有 NRZ 的三分之一,信噪比要求提高大约 9 到 10 dB。

这笔账直接决定了为什么 25G/lane 之后 FEC 从“可选”变成“必须有”。也决定了为什么高速链路的标准里,编码、FEC、训练序列总是绑在一起出现——它们是同一个信噪比预算下的三个变量,动一个另外两个就得跟着动。你在现场看到“同型号模块,A 厂商的能起,B 厂商的起不来”,多半就是这三个变量的实现细节有差异,而不是模块坏了。

4. FEC 与自协商:链路先“谈”还是先“纠”

链路起不来的时候,最折磨人的两种情况是:两端都不报错,就是不 up;或者 up 了但误码率在 1e-8 附近晃。前者十有八九是自协商,后者十有八九是 FEC。

4.1 自协商:16 位基础页能谈出什么

自协商的本质是交换能力位图。两端各自发一串差分曼彻斯特编码的脉冲,里面装的是 16 位基础页:速率能力、双工能力、是否支持下一页、远端故障位。双方收到对方的位图后,按标准里定义的优先级顺序取交集,选出共同支持的最高能力,然后各自配置到那个模式。

这里有个必须记住的设计前提:自协商是双向的、对等的。一端开自协商、另一端强制固定速率,协商就不成立。强制端不发脉冲,自协商端收不到对端能力,只能退化成并行检测——只看线路上有没有信号能量,据此猜一个速率。猜出来的结果通常是最保守的组合,比如半双工。

下一页机制让这个能力位图可以扩展。标准的做法是第一条下一页放一个消息页,说明后面跟的是什么类型的数据,后面的非格式化页承载实际内容。这让自协商能传厂商自定义信息、能传 FEC 能力、能传 EEE 支持情况。

4.2 FEC:为什么 25G 之后纠错变成必选项

FEC 在标准里的存在感,随速率单调上升。1G 时代基本靠编码本身的性质就够了;10G 时代 FEC 是背板场景的可选项;25G 之后,PAM4 带来的信噪比损失让 FEC 成为链路预算里不可省的一环。

主流的两套里德-所罗门参数是这样的:

def rs_profile(n, k, symbol_bits=10): return { "码长 n": n, "信息位 k": k, "可纠符号数 t": (n - k) // 2, # 每个符号 10 bit,错一个符号可能只错 1 bit "开销 %": round((n - k) / k * 100, 2), } print(rs_profile(528, 514)) # t=7, 开销约 2.72%,25G/100G 背板与铜缆常用 print(rs_profile(544, 514)) # t=15, 开销约 5.84%,50G/lane 以上常用

参数表背后是两个取舍。第一,纠错能力 t 越大,能容忍的误码越多,开销也越大,5.84% 的开销意味着 100G 线路上有近 6G 是校验位,不载业务。第二,光有纠错不够,还要交织。PAM4 接收端的错误往往是突发的,一个判决错误会连累相邻几个符号;把码字按符号轮转交织到不同位置,突发错误就被摊成随机错误,落到每个码字里只剩零星几个,正好在 t 的能力范围内。

排查现场最有用的一个数是“已纠正码字数”。这个计数在缓慢增长,说明链路处于临界状态:现在能通,温度变一点、插损大一点就会掉。它涨得很快但链路没断,说明 FEC 正在硬扛,属于黑匣子里的预警信号,比等到链路 down 再看要早得多。

5. 避坑与排查:读标准与做链路时最容易翻车的五件事

5.1 现象:按条款号去查,翻到的内容和记忆里不一样

原因很常见:802.3 是滚动合并的版本,每次修订把当年所有修订件并进一本,但条款号一旦分配就不再回收,废弃的条款号会留空或标注保留。而你手上的其他资料,可能是基于某个中间修订件写的,编号自然对不上。

解决方法是先确认手上的文档集合。802.3-2022 是基准版本,2022 年之后发布的那些修订件并不在里面——包括 400G/800G 相关的内容、以及 100/200/400G 的若干补充。判断一个特性在不在你手上的文档里,最稳的办法是先查那份修订件清单,再回到基准文档里比对条款是否存在,而不是直接搜关键词然后抱怨搜不到。

5.2 现象:一端自协商、一端强制,链路起不来或降到半双工

原因是对端不发协商脉冲,自协商端只能并行检测猜速率,猜出来的组合往往不匹配。标准里写得很清楚,自协商是可选功能,但一旦启用就必须两端都启用。

解决办法是统一策略:要么两端都自协商,要么两端都强制且参数完全一致。工程上更推荐全自协商,因为强制模式会屏蔽掉 FEC、EEE 这些后续才加的能力协商,等于主动放弃了链路自愈的余地。

5.3 现象:链路能 up,但误码计数持续增长,业务偶发丢包

原因大概率是 FEC 配置不一致,或者一端开了交织另一端没开。FEC 不是“开了就更好”,收发两端必须在码型、交织深度、符号映射上完全对齐,否则接收端解出来的就是噪声。

解决办法是先读两端的 FEC 状态寄存器,确认码型一致;再看已纠正码字和不可纠正码字的比例。只涨前者是链路余量不足,两者都涨就是配置错位。

5.4 现象:按 8b/10b 的 25% 开销算带宽,算出来的吞吐对不上

原因是 10G 之后大量链路用的是 64b/66b,开销是 3.125% 而不是 25%,两者差了一倍多。反过来的坑也存在:某些老链路你按 3.125% 算,实际却是 8b/10b。

解决办法是看这条链路工作在哪个 PCS 类型上,别按速率猜编码。同是 10G,光纤和背板可能用不同的编码;同是 1G,铜缆和光纤的编码也不一样。

5.5 现象:低负载时延迟抖动明显,抓包看到周期性停顿

原因是 EEE。链路空闲一段时间后 PHY 进入低功耗状态,有数据要发时得先唤醒,唤醒时间在几十微秒量级,对时延敏感的业务就是灾难。

解决办法是把 EEE 关掉,或者只在确认业务对微秒级抖动不敏感时开启。这个功能的取舍很典型:省电和确定性在物理层是互斥的,标准把选择权交给了部署方。

6. 兼容、解耦、可验证:三笔账压出来的设计取舍

把这份 7000 多页的文档当成一个整体看,会发现它反复在做三笔交易。

第一笔是兼容换简洁。CSMA/CD、8b/10b、半双工这些机制在新场景里几乎没有意义,但标准不删。删掉它们等于宣布旧设备不合法,而以太网的全部价值就建立在“新旧能插在同一个网络里”这件事上。代价是文档越来越厚,实现者的状态机越来越多。

第二笔是解耦换效率。RS 隔开 MAC 与 PHY,MII 隔开逻辑与引脚,PCS/PMA/PMD 再逐层切分。每一刀都带来一点开销——多一层映射、多一组寄存器、多一次状态转换——换来的是任何一层单独演进都不影响其他层。

第三笔是可验证换灵活。标准里几乎所有关键逻辑都写成显式状态机,而不是描述性文字。这让一致性测试有据可依,也让不同厂商的实现能互通。代价是状态机一旦写进标准就很难改,后来的修订只能加分支、不能重写主干。

我的习惯是:每次拿到一条新链路,先把这三笔账在纸上过一遍——这条链路在兼容什么、在哪一层解耦、哪个状态机可能还没收敛。从那以后我每次定位链路问题,都强制先读一遍对应条款的状态图和参数表,再动手改配置,而不是先怀疑硬件。希望帮到你。

本文还有配套的精品资源,点击获取