Polling 状态详解:位锁定、符号锁定、TS1/TS2 序列交换、Polling.Active 与 Polling.Compliance 子状态

链路从 Detect 出来后,第一个真正干活的训练状态就是 Polling。说白了,这个时候物理层还没对齐,大家得先互相找到信号、对上节拍、再握个手。我早期做 PCIe 验证时,有一半的调式时间都花在 Polling 阶段——因为一旦这里出问题,后面全是黑屏。

Polling 阶段要做三件事:位锁定(从串行数据里恢复时钟)、符号锁定(找到 10-bit 码字边界)、TS 序列交换(协商链路宽度和速率)。这两个子状态——Polling.Active 和 Polling.Compliance——分别对应正常训练和 Compliance 测试模式。咱们逐个拆开看。

位锁定:从噪声中找回时钟

接收端没有专门的时钟线,全靠 CDR(Clock Data Recovery)从数据流里挖出时钟。CDR 会检测跳变沿,调整本地 PLL 的相位去跟踪输入信号。

关键点:位锁定必须在 1 ms 内完成,否则 LTSSM 会超时退回到 Detect。

联调时最容易出坑的地方是参考时钟的精度。比如说,两个端的 Refclk 频偏超过 ±300 ppm,CDR 锁不住,位锁定失败。我遇到过一块板子,PCIe 参考时钟是从 FPGA 内部 PLL 分出来的,晶振误差累加成 700 ppm,结果死活进不了 L0。后来换了独立晶振,一次就过。

另外,信号幅度太小或抖动太大也会导致 CDR 滑码。我记得一个案例:PCB 走线过长,差分对跨过一段过孔反焊盘挖得太深,插损超标,眼图闭合到只有 30% 的余量。CDR 虽然能勉强锁定,但后续符号锁定频频出错。所以调试时先看眼图——眼膜测试通过,位锁定基本没问题。

符号锁定:找到 comma 就搞定

位锁定之后,CDR 已经能采到连续的比特流了,但比特流没有帧结构,得先把 10-bit 边界找出来。PCIe 用的是 8b/10b 编码(Gen1/Gen2),每个符号对应一个 10-bit 码字。那怎么知道从哪一位开始算一个符号?靠 comma 字符——特殊码型 K28.5(001111 1010 或 110000 0101),它在 10-bit 序列里是唯一的。

符号锁定过程:接收端滑动 10-bit 窗口,搜索 K28.5。一旦命中,就认定这个窗口对齐了符号边界,然后一直保持。所以只要 CDR 没丢锁,符号锁一般很快——通常几十个码字内就能完成。

注意:8b/10b 编码的码组里可能出现与 K28.5 部分匹配的序列,导致误锁定。标准要求连续检测到 至少两个 有效 comma 才算真正锁定,避免假锁。

我曾经碰到过一个诡异问题:链路插在老化箱里跑高温,符号锁定偶尔失败。抓 trace 发现逗号码偶尔被错误翻转(极性反转 bit 导致逗号变形成非逗号)。后来查到是发送端极性控制逻辑在高温下有毛刺,修正后故障消失。所以说,符号锁定不只是接收端的事,发送端的信号完整性也一样重要。

TS1/TS2 序列交换:链路参数握手

位锁和符锁搞定后,两边就可以传有序集了。Polling 阶段交换的是 TS1 序列 和 TS2 序列。这俩序列格式一样,就是 Type 字段不同——TS1 固定 0x78,TS2 固定 0x84。

每个序列由 16 个符号组成,包含以下字段:

符号位置字段说明
0Comma (K28.5)序列起始标志
1TypeTS1(0x78) 或 TS2(0x84)
2Link Number本端期望的链路号
3Lane Number本端期望的通道号
4–7Reserved保留(全 0)
8–11Bit Rate ID支持的速率集(例如 Gen1 / Gen2 / Gen3)
12Data Rate Identifier协商后的数据速率
13–14Symbol 4/5用于 Negotiated Link/Lane
15CRC序列校验

交换流程是这样的:

  1. 接收端在符号锁定后,开始发送 TS1 序列,同时监听对端发来的 TS1。
  2. 如果收到连续的 TS1(通常 2 个无错序列就算有效),则解析 Link Number 和 Lane Number,确定这个端口对应哪条链路和通道。
  3. 两边都确认后,发送端会改发 TS2 序列,表示“我准备好协商了”。
  4. 收到 TS2 后,比较双方支持的速率和宽度,取交集,然后进入 Configuration 状态做进一步协商。

调试技巧:用协议分析仪抓 TS1/TS2 序列时,注意看 Bit Rate ID 字段。如果两边填的不一致(比如一端只支持 Gen1,另一端只写了 Gen2),就会卡在 Polling 态不出来。我常用一个小脚本把 SMBus 或 I²C 读出的 LTSSM 状态和 TS 字段打印出来,一眼就能看出哪一方在降级。

Polling.Active 子状态

这是 Polling 的主要工作区。内部其实又分好几个微状态:

  • Polling.Active.L0s:等待接收器稳定,CDR 开始锁定。
  • Polling.Active.L0:位锁定完成,开始符号锁定。
  • Polling.Active.Logical:符号锁定成功,开始接收 TS1/TS2。
  • 如果所有步骤通过,跳转到 Configuration.LinkWidth.Start。

实际项目中,最常看到的卡死点是 Polling.Active.L0s 永远超时——意味着 CDR 没锁住。我见过一个 case:A 端芯片的 SerDes 电源纹波太大(30 mVpp),导致 PLL 抖动高,位锁定一直在抖动中反复丢失。最后在电源端加了个 LC π 滤波器,纹波降到 5 mVpp,问题解决。

Polling.Compliance 子状态

这个子状态不是正常训练流程里的,而是用于 接收端 Compliance 测试。当上位机或者测试设备通过边带信号强制进入 Compliance 模式后,链路会在 Polling.Compliance 下持续发送 Compliance Pattern(通常是 D10.2 码型),让接收端测量眼图或进行抖动测试。

我在板级调式时常手动触发 Compliance:通常是通过 GPIO 置位一个寄存器,让 Endpoint 进入 Compliance 模式,然后用示波器看眼图。如果眼图张不开,你得回头查 PCB 走线的阻抗或 AC 耦合电容。

不要混淆:Polling.Compliance 不是错误状态。它更像一个工厂测试模式。如果产品在客户现场莫名其妙进了 Compliance,多半是某个边带信号被误触发了。我曾经在某个 FPGA 设计中把 GPIO 上拉电阻焊错了,导致上电后引脚电平为高,直接触发了 Compliance 模式,链路一直出不来。排查了两天才发现。

核心流程一览

下面这张图帮你串起整个 Polling 阶段:

Polling 状态核心流程 从 Detect 进入 Polling 位锁定 (CDR 锁定) 符号锁定 (找到 K28.5) 发送 TS1 / 接收 TS2 参数协商成功 进入 Configuration 态 超时或错误 Polling.Compliance 说明: • 绿色节点为位锁定/符号锁定,失败则直接超时退回 Detect。 • 紫色节点为 TS 序列交换,若 2ms 内未收到有效 TS2 则进入超时分支。 • 红色分支表示 Compliance 模式或超时错误,需要外部干预。 • 橙色节点标志着 Polling 成功完成。

常见调试场景与避坑

列几个我亲手踩过的坑:

  • 位锁定抖动: 参考时钟源纹波 > 50 mVpp,CDR 一直重锁。解决方案:串一个 0.1 μF 电容 + 10 Ω 电阻构成 RC 滤波器。
  • 符号锁定假锁: 串扰导致 K28.5 的边界被误判。增加逗号检测窗口数(从 2 个提至 4 个)可降低误检率。
  • TS 序列 CRC 错误: 信号质量差,序列连续出错。检查差分对等长和过孔数量,必要时加均衡(Gen2 以上强制要求 CTLE)。
  • Polling.Compliance 意外进入: 检查 PERST# 和 WAKE# 边带信号是否按要求释放。有些 Bboard 的 pin 浮空时会被干扰拉高。

嗯,这里要注意一点:很多软件工程师觉得 Polling 是硬件的事,不太关注。但如果你在写 RC 驱动或调试 EP 初始化,卡在 Polling 太久会触发 PCIe 枚举超时(典型 100ms)。我见过因为 CDR 锁定慢了 2ms 导致整个 Root Complex 认为 EP 不存在,直接标记为 “Unconnected”。所以 Polling 的每个子阶段的时序余量最好在硬件设计阶段就留够。

Polling 搞定了,链路就在 Configuration 态里拍板定案。至于 Configuration 里怎么配 Lane 和 Width,那是下一阶段的故事了——咱们回头再聊。