TS1 TS2序列解析与链路协商判定 - 第1节
一、PCIe 是什么?从并行到串行
先聊聊 PCIe 的出身。PCIe 全称是 Peripheral Component Interconnect Express,也就是“快速外设互连”。它取代了早期的 PCI 总线,解决了并行总线在高速下的瓶颈问题。
我个人习惯把 PCIe 理解为一条点对点的高速串行链路。你想想看,PCI 是并行总线,所有设备共享同一组数据线,频率一旦提高,信号同步就成问题。PCIe 改成串行,用差分对传数据,每条链路独立工作——这想法很聪明。
我在项目中遇到过一块老主板,PCI 插槽上插了张 1GbE 网卡,跑满时 CPU 占用飙升。换成 PCIe 网卡后,CPU 占用直接掉到 5% 以下。为什么?因为 PCIe 是点对点,不占用共享总线,背后有专门的交换架构来处理路由。
1.1 PCIe 架构速览
PCIe 系统由三部分组成:根复合体(Root Complex,RC)、交换器(Switch)和端点(Endpoint,EP)。
- 根复合体(RC):连接 CPU 和内存,是链路的最顶层。CPU 发起的配置、内存读写都得经过它。
- 交换器(Switch):相当于 PCIe 的中枢,内部有一个虚拟 PCI 桥,把上游端口的数据转发到下游端口。多个端点可以挂在同一 Switch 下。
- 端点(EP):实际的功能设备,比如显卡、NVMe SSD、网卡。它不能发起配置周期,只能响应来自 RC 的配置请求。
另外,PCIe 的拓扑图里还有一个隐藏角色叫 PCIe 桥(Bridge),一般集成在芯片组的内部,用来向上挂接 RC,向下连接多个 Switch 或 EP。你不太会直接看到它,但它负责做事务层包的转发。
1.2 链路、通道与速率
一条 PCIe 链路(Link)由多条通道(Lane)组成。每条 Lane 包含两对差分信号:一对发送、一对接收。x1、x4、x8、x16 这些称呼就是指 Lane 的宽度。
核心点:链路建立前,两端必须通过训练序列(Training Sequences)协商出共同的工作参数,包括 Lane 宽度、速率、极性等。
| 版本 | 编码方式 | 单 Lane 数据速率 | x16 总带宽 |
|---|---|---|---|
| PCIe 1.0 | 8b/10b | 2.5 GT/s | ~4 GB/s |
| PCIe 2.0 | 8b/10b | 5 GT/s | ~8 GB/s |
| PCIe 3.0 | 128b/130b | 8 GT/s | ~15.75 GB/s |
| PCIe 4.0 | 128b/130b | 16 GT/s | ~31.5 GB/s |
注意!GT/s 是每秒传输的 Giga-Transfers,不等于真实带宽。8b/10b 编码会有 20% 开销,128b/130b 只有约 1.5% 开销。PCIe 3.0 之后效率提升很大。
二、链路初始化:从电气闲逛到正常数据流
链路初始化听着挺玄乎,说白了就是设备上下电、时钟稳定、然后两端相互确认的过程。
我记得我第一次看 PCIe Spec 里那个训练状态机(LTSSM)的状态图——密密麻麻,差点劝退。但后来发现,核心逻辑就那么几步:
- 检测(Detect):物理层发送电气空闲退出信号(Electrical Idle Exit),判断对端是否存在。
- 轮询(Polling):配置基础的速度和 Lane 宽度,发送训练序列 TS1/TS2。
- 配置(Configuration):确认 Lane 到 Lane 的映射,协商极性、去抖动等细节。
- 恢复(Recovery):如果速度和 Lane 宽度需要调整,这里再协商。
- L0(正常操作):数据传输阶段。还可以进入 L0s/L1/L2 省电状态。
嗯,这里要注意:Polling 阶段是 TS1/TS2 序列第一次登场的地方。在 Polling.Active 状态下,两端连续发送 TS1 序列,携带链路编号、Lane 编号、速率等信息。我以前调试一块 FPGA 板卡时,发现 TS1 始终不对,最终定位到是时钟抖动太大导致物理层无法锁定——修了电源再试就过了。
一个小技巧:如果链路起不来,先拿示波器看差分信号的摆幅和共模电压。PCIe 3.0 的规范要求摆幅在 800–1200 mV 之间,太弱或太强都可能让 TS1 无法被正确识别。
2.1 训练状态机(LTSSM)的骨架
LTSSM,全称 Link Training and Status State Machine。PCIe 设备内部有一个硬件状态机,独立运行在物理层。它负责控制链路的训练、重训练和电源管理。
| 状态 | 子状态举例 | 作用 |
|---|---|---|
| Detect | Detect.Quiet, Detect.Active | 检测对端是否在线 |
| Polling | Polling.Active, Polling.Compliance, Polling.Configuration | 发送 TS1,交换基本参数 |
| Configuration | Configuration.LaneWidth, Configuration.Complete | 确定 Lane 到 Lane 的映射 |
| Recovery | Recovery.RcvrLock, Recovery.Equalization, Recovery.Speed | 速度/宽度变更时的重新训练 |
| L0 | — | 正常数据传输 |
| L0s/L1/L2 | — | 省电模式 |
我们这门课重点关注 Polling 和 Configuration 这两个阶段里的 TS1/TS2 序列内容。因为链路能不能协商成功,全看这两个阶段有没有正确识别出对端发来的序列。
2.2 链路初始化的关键指标
我从实际项目中总结出三个最常见的链路问题:
- Lane 宽度不匹配:两端支持的宽度不同,比如一端是 x8,另一端是 x4,最终会协商为较小的宽度。
- 极性翻转:差分信号的 P/N 接反了。PCIe 允许通过训练序列中的极性翻转位来修正。
- 去抖动未通过:去抖动(De-Skew)阶段需要每个 Lane 调整自身相位,如果某个 Lane 的延迟超出范围,会导致链路断开。
我曾经遇到一个坑:在 Recovery 阶段,由于一端的参考时钟频率偏差超过 ±300 ppm(百万分之一),链路无法进入 L0。查了半天才想起来 PCIe 3.0 要求时钟精度必须控制在 ±300 ppm 以内,如果用的是分离参考时钟,这个值还可能更严格。