Polling 状态与链路宽度协商
这一讲,我们来聊聊 LTSSM 里一个非常关键的环节——Polling。我个人觉得,它就是链路建立过程中的“握手阶段”。说白了,在 Detect 状态下发现了对端设备,但双方还没正式对上话。Polling 就是要确定“咱俩之间有几条通道能用?速度能不能对齐?”。
我早些年调试一块 Gen3 的板卡时,就卡在这个状态。链路死活只认 x1,明明硬件连了 x8 的线。查了整整两天,最后发现是某个 Lane 的电容虚焊,导致均衡训练老失败。从那以后,我养成了个习惯——只要链路宽度不对,第一件事就是看 Polling 阶段的日志。
Polling 阶段的核心任务
先搞清楚 Polling 到底是干嘛的。它有两个目标:
- Bit Lock 和 Symbol Lock:接收端要对齐发送端的数据流,找到正确的 bit 边界和字符边界。
- 链路宽度训练:双方通过交换训练序列(TS1/TS2),协商出实际工作的 Lane 数量。
你想想看,物理层如果连 bit 都锁不住,上层协议根本无从谈起。所以 Polling 是 LTSSM 从“物理存在”走向“数据可通信”的必经之路。
关键点
Polling 阶段不会进行速度协商(速度协商在后面的 Configuration 状态完成)。它只负责确定宽度和基本对齐。
链路宽度协商的过程
这个协商过程比我以前想的要复杂得多。它不是简单地说“我有 16 条 Lane,你也有 16 条,那就全用上”。实际流程是这样的:
- 训练序列的发送:连接的两端设备,在各自的所有 Lane 上,以最低速率(目前一般是 2.5 GT/s)发送 TS1 序列。TS1 里包含了一些关键信息,比如“我支持的宽度”、“我的 Lane 编号”等等。
- 接收与反馈:每个 Lane 的接收端尝试锁定 TS1 序列。锁定了,就认为这条 Lane 是“好的”。锁不上的,可能就是物理链路有问题,或者对端没拉这个 Lane。
- 宽度确定:一方会发现:“我只有 Lane 0, 1, 2, 3 收到了 TS1,Lane 4~7 毫无反应。”于是它就会在 TS2 序列里告诉对方:“我们就在这 4 条好的 Lane 上工作吧。”最终,双方取最小值作为链路宽度。
其实这个过程有点像两个人拿对讲机喊话:你先喊一句“我在,你听到了吗?”,对面回复“收到了,我也在”。反复几次,确认信号质量没问题,才开始聊正事。
| TS 字段 | 作用 |
|---|---|
| Sequence Number | 区分 TS1 和 TS2 |
| Lane Number | 标识当前 Lane 的编号 |
| Link Number | 用于多链路场景 |
| Bit Lock / Symbol Lock | 指示接收端是否已锁定 |
一个小技巧
我在项目中习惯直接看 PCIe 控制器提供的 debug 寄存器,里面通常有“Polling Status”字段。如果它显示“Polling.Active”,说明正在宽度协商;如果卡在“Polling.Configuration”,那就看看 Lane 状态寄存器,哪几条 Lane 没置上 Bit Lock 一目了然。
Polling 状态的子状态与时序
LTSSM 把 Polling 又分成了几个子状态:
- Polling.Active:发送 TS1/TS2,进行宽度发现。这个阶段时间比较短,如果 2ms 内没完成(具体指没收到任何对端的 TS 序列),就认为超时,跳回 Detect。
- Polling.Configuration:在宽度确定后,短暂停留,确保所有 Lane 都完成了 Symbol Lock。这里有一个
N_FTS参数的计数。 - Polling.Speed:嗯,实际上 Polling 阶段不包含 Speed 子状态,有些资料会混淆。速度协商是进入 Configuration 状态后的事。
这个时序设计是有讲究的。为什么要有超时?因为如果对端设备还在复位,或者链路有严重故障,死等只会浪费带宽。快速回退到 Detect,然后重新尝试,反而更高效。
注意:宽度协商失败的常见原因
我曾经遇到过一块 FPGA 板卡,由于参考时钟源质量不佳(抖动太大),导致在 Polling 阶段个别 Lane 的 PLL 锁不住。表现就是链路宽度不稳定,一会儿 x8,一会儿 x4。解决方法不是改软件,而是换了低抖动的时钟芯片。
N_FTS:一个容易被忽略的参数
在 TS2 序列里,有一个字段叫 N_FTS。这个参数的意思是:“进入 L0s 状态后,如果我要进入 L0,需要发送多少个 FTS(Fast Training Sequence,快速训练序列)?”。
这个值是怎么确定的呢?就是在 Polling 阶段协商出来的。接收端根据自身 PLL 的锁定速度,在 TS2 里告诉发送端:“我恢复时间长,请准备好发送足够多的 FTS。”如果设置得太小,会导致从 L0s 唤醒时链路不稳定;设置得太大,又浪费带宽。
我一般建议:N_FTS 的值不要低于硬件手册推荐值。曾经有团队为了微调功耗,把 N_FTS 从 100 改成了 50,结果发现链路唤醒概率下降了 20%。
实战经验:看波形判断 Polling 状态
如果只能用示波器看 PCIe 总线,怎么做?你可以抓 PERST# 和 差分对上的信号:
- Detect 阶段:信号线是静默的(电气空闲)。
- Polling 阶段:信号线开始出现连续的训练序列(TS1),频率固定为参考时钟的速率(比如 100 MHz 经过倍频后的 2.5 GT/s)。
- 宽度协商失败:你会看到某些 Lane 上 TS1 一直发,但对端没任何反应,永远无法进入 TS2。
嗯,这里要注意:不要只看一个 Lane。要同时看多个 Lane 的波形。宽度协商说白了就是个“投票机制”——每个 Lane 自己投票“我 OK”,然后汇总。如果有一票反对(Lane 故障),整个协商就缩小宽度。
常见坑与检查清单
我总结了一张表格,你调试时可以直接对照:
| 现象 | 可能原因 | 检查点 |
|---|---|---|
| 链路宽度只有 x1 | 某条 Lane 的电容故障,或差分对走线不等长 | 查看各个 Lane 的 Bit Lock 状态寄存器 |
| Polling 反复超时 | 对端设备没上电,或 PERST# 时序错误 | 检查电源、复位信号,以及参考时钟是否稳定 |
| 链路宽度经常变化 | Lane 间 skew 过大,或处于温漂临界状态 | 用 BER(误码率)测试仪扫描各 Lane 的眼图 |
| 进入 Configuration 后立即回退 | Symbol Lock 失败,或 N_FTS 协商失败 | 检查 PLL 锁定指示,确认 N_FTS 值在合理范围内 |
避坑指南:宽度协商中的“最小公倍数”
我曾经遇到一个案例,A 设备支持 x1, x4, x8 宽度,B 设备支持 x2, x4, x16。你猜它们协商成什么?结果是 x4。因为两边都支持 x4,且宽度取双方都支持的最大值。但注意,宽度必须是 2 的幂次(1, 2, 4, 8, 16)。如果是 x3?不存在的。
关于 Polling 阶段的功耗
很多人觉得 Polling 阶段不省电,其实不然。我调整一下看法:Polling 本身就是一种“省电”机制——它只在需要时才激活。如果链路长时间空闲,LTSSM 会退回到 Detect,那里的功耗几乎为零。但在 Polling 阶段,所有 Lane 都在发送训练序列,功耗确实不低。所以,尽量缩短 Polling 持续时间,对整体功耗有好处。
有些芯片设计允许在 Polling.Active 期间关闭那些“坏掉”的 Lane 的发送器,以节省功耗。这是可选的,但值得关注。
一个小结思路
Polling 状态,说白了就是 PCIe 链路的“体检”和“分组”。通过 TS1/TS2 的训练序列,完成 bit 对齐、符号对齐,并协商出最佳宽度。宽度协商是硬件自动完成的,但调试时你得知道它背后的逻辑。记住三个核心点:超时时间、N_FTS、以及 Lane 状态寄存器。这三样东西能帮你解决 90% 的宽度相关问题。