第四章:Configuration状态——链路宽度协商与 Lane 极性交换
进入 Configuration 状态之后,链路就开始干正事了。这个阶段要做的事情说白了就两件:确定到底用几根 lane 跑,以及把每根 lane 上的信号极性对齐。很多芯片第一次调通 PCIe 的时候,卡就卡在这两个环节上。
4.1 宽度协商:从 1x 到目标宽度的多轮握手
你可能以为宽度协商是一上来就谈好 ×4 还是 ×16,其实不是。PCIe 的物理层有一个很聪明的机制:从一根 lane 开始建链,然后在 Configuration 状态里逐步“扩容”。为什么这么设计?
我早年做一块 FPGA 的 PCIe 硬核时,遇到过链路始终只有 ×1 的情况。检查了半天,发现是参考时钟抖动太大导致某几根 lane 的 CDR 锁不住。硬件没坏,但宽度协商就是上不去。那个时候我才深刻理解:宽度协商本质上是一种“能跑几根就用几根”的优雅降级策略。
具体的协商流程,以 LTSSM 视角来看是这样的:
- Config.Linkwidth.Start:UP 端按照接收到的 TS1/TS2 中的 Lane Number 字段,决定自己当前被分配了哪些 lane。所有 lane 在此时都被认为已对齐。
- Config.Linkwidth.Accept:双方互相确认宽度。UP 端会持续发送 TS2 有序集,并在 TS2 的
Link Number字段中填入协商好的链路编号。DN 端收到后回复相同内容。 - Config.Lanenum.Wait / Accept:再次确认每条 lane 的编号分配,防止出现 lane 错位。
- Config.Complete:双方都确认无误后,发送两个 TS2 并转出到 L0。
这里有一个细节:宽度协商必须满足“所有 lane 必须同时进入同一个子状态”。如果某根 lane 的电气条件不好,迟迟没有收到有效 TS,这条 lane 就会被标记为“Lane Not Present”,宽度协商就会自动降低到可用 lane 数。
我个人的经验:在调试硬件时,用逻辑分析仪抓 TS1/TS2 的 Lane Number 字段非常有用。如果看到某根 lane 一直发 0x00 而不是物理 lane 号,十有八九是这跟 lane 的极性反了或者电气阻抗匹配出了问题。
4.2 Lane 极性交换:为什么要手动翻转?
你可能会想,信号极性反转不是硬件 PCB 设计上的错误吗?为什么不直接要求 PCB 保证正确?
理想很丰满,现实很骨感。PCIe 的差分管脚定义是固定的:P 极和 N 极(正负),但在多层 PCB 上为了走线方便,经常会把一对差分线在接收端交叉。如果不做处理,接收端就会收到反相数据,无法解码。
PCIe 规范里留了一个后门:Lane Polarity Inversion。接收端可以检测到极性反了,然后在物理层内部把数据线的 D+/D- 逻辑上交换回来。这样一来,PCB 工程师就不用小心翼翼地保证每一根 lane 都不交叉了。
我见过一个项目,板子回来后链路死活无法进入 L0,复位后始终反复在 Config.Linkwidth.Start 和 Detect.Quiet 之间跳动。最后发现是因为 PCB 上有一组 lane 的差分对交叉了,而接收端芯片的极性交换逻辑默认没有开启。手动在配置寄存器里将 Polarity Inversion Enable 置位后,问题立刻解决。
4.3 极性交换的检测与协商流程
极性交换不是自动进行的,它依赖于两阶段检测:
- 在 Recovery 和 Configuration 状态中:接收端会检查收到的 TS1/TS2 的 EIEOS(Electrical Idle Exit Ordered Set)之后的数据模式。如果发现 COM 码型(K28.5) 的极性反了(比如本该是 0xBC 却收到 0x43),就说明该 lane 极性反了。
- Invert 动作:一旦判定极性反,接收端内部的并串转换器会自动反转输入数据的极性,然后重新做 8B/10B 解码。这个过程完全是物理层内部完成,对链路层透明。
关键点:极性交换 不是 通过握手报文来协商的。接收端检测到极性反了就自己悄悄地翻过来。发送端并不知道也不需要知道。这跟宽度协商完全是两回事儿。
下面这张图总结了 Configuration 状态下宽度协商与极性交换的并行流程:
4.4 宽度协商失败时的退避策略
如果某些 lane 在 Config.Linkwidth.Start 阶段始终无法对齐,LTSSM 会怎么做?
规范规定:每个 lane 独立计时。如果一个 lane 在 Tx_TS2_Count 达到设定上限后仍然没有收到有效的 TS2 回复,该 lane 会被标记为 Lane Not Present。然后宽度协商会回退到剩余可用的 lane 数。举个例子,一块 ×16 的根端口可能因为某根 lane 的电容不良只能跑 ×8,甚至 ×4。
我曾经踩过的坑:有一款桥片在低温下出现 ×16 只能协商到 ×1 的怪现象。排查了所有电气特性都没问题,最后发现是桥片的固件里写死了一个宽度限制寄存器。硬件其实能跑 ×16,但软件在冷启动时把最大宽度设成了 ×1。所以各位排查宽度协商问题时,别忘了看看 Device Control Register 里的 Max_Read_Request_Size 以及隐式的 Max_Link_Width 字段。
4.5 极性交换的配置寄存器
虽然物理层自动检测并翻转,但软件可以通过配置寄存器强制关闭极性交换。相关寄存器在PCIe 扩展开端 Capability 的 Link Control 2 寄存器中,有一个 Polarity Inversion Enable 位。默认是 1(使能自动翻转)。我建议除非你有特殊测试需求,否则保持默认。
| 配置项 | 位置 | 默认值 | 说明 |
|---|---|---|---|
| Polarity Inversion Enable | Link Control 2 寄存器 bit 7 | 1b | 1=允许物理层自动反转;0=禁止反转,必须硬件正确 |
| Max Link Width (软件限幅) | Link Capabilities / Device Control | 实际硬件宽度 | 软件可以在初始化阶段将最大宽度设得更低,用于调试 |
4.6 一个小总结:宽度与极性是两个独立维度
最后,我这里想强调一个容易混淆的点:宽度协商和极性交换是并行的,互不依赖。一根 lane 可能极性反了但宽度协商正常(因为接收端自动反转了),也可能宽度协商失败但极性正常(电气环境太差)。
调试时,我习惯先抓 TS1/TS2 看 Lane Number 是否连续递增,再用示波器看每对差分信号的摆幅和极性。两者结合起来,基本五分钟就能定位到根因。
我的调试清单:
- 检查 Link Control 2 寄存器的极性翻转开启。
- 用协议分析仪抓 Config.Linkwidth.Start 阶段每个 lane 发送的 TS2 中的 Lane Number。
- 测量所有 lane 的差分信号到接收焊盘,确认没有反接。
- 查看链路状态寄存器中的
Negotiated Link Width和Link Speed字段,确认协商结果。
嗯,宽度协商和极性交换说到这里,你应该已经对 Configuration 状态的核心动作有了一个比较立体的认识。下一环节,我们会深入 Equalization 协商——那是速率从 Gen1 往 Gen3/Gen4 爬升时必须经历的一场“信号质量谈判”。