3、PCIe协议层基础归纳总结

协议层这东西,说简单点就是规定了数据该怎么包装、怎么传输、怎么保证不出错。我看过太多工程师盯着眼图分析仪一头雾水,但对协议层的理解反而不够深。其实PCIe最核心的逻辑都在这里了。

我个人习惯把PCIe协议层拆成三块来看:事务层、数据链路层、物理层。每一层都有自己清晰的职责,有点像公司的三层管理。

关键理解:PCIe的分层架构不是为了炫技,而是为了解耦。物理层变快了,数据链路层不用改;链路层加了新功能,事务层照样跑。我在项目中遇到过一次,从Gen3升级到Gen4,只需要改物理层的模拟部分,上层逻辑几乎没动。这就是分层的好处。

PCIe 协议层架构图 事务层(Transaction Layer) 核心职责:TLP 的组装与解析 | 事务排序 | 流量控制 数据链路层(Data Link Layer) 核心职责:DLLP 管理 | ACK/NAK 重传 | 序列号校验 | 数据完整性 物理层(Physical Layer) 核心职责:编码解码 | 8b/10b 或 128b/130b | 电气特性 | 链路训练 说明:每层只与相邻层通信,不跨层调用。 TLP 穿越链路层加序列号与 CRC,DLLP 直接由链路层产生。 TLP 与 DLLP 是两大核心报文

3.1 TLP 的格式与抓包分析

事务层产生的报文叫 TLP(Transaction Layer Packet)。这是 PCIe 通信最基本的载体。所有的内存读写、配置访问、IO 操作,最后都变成一个个 TLP 在链路上飞。

一个完整的 TLP 长什么样?

TLP 格式:
+---------+--------+-----------+-------------+
| 前缀    | TLP头  | 数据负载  | CRC 摘要    |
| (可选)  | (必须) | (可选)    | (必须)      |
+---------+--------+-----------+-------------+

TLP 头(32位为例):
+----+----+---+----+----+--------+--------+
| Fmt| Type| TC| Attr| TH  | 其他   | 地址   |
+----+----+---+----+----+--------+--------+

我在调试时遇到过一种情况:验证了一个下午的 CRC,发现没一个对的。最后检查才发现原来还有可选的 TLP 前缀(Prefix),某个开关打开后,CRC 计算范围变了。嗯,这里要注意。

抓包技巧: 用逻辑分析仪或协议分析仪抓 TLP 时,先看 Fmt 和 Type 字段。这两个字节决定了序列要如何被解析。我一般第一步就是确认 Fmt 是 2'b00 还是 2'b10——这决定了头是 3DW 还是 4DW(数据字长)。很多常见的解析错误都出在这里。

3.2 DLLP 与流控机制

数据链路层产生的报文叫 DLLP(Data Link Layer Packet)。它不暴露给软件,只负责链路的传输健康。

DLLP 只有 6 字节,非常轻量。主要包含什么呢?

  • ACK/NAK: 收到 TLP 后,接收方告诉发送方“对的,收到了”或“有错,重传”。
  • InitFC1/InitFC2/UpdateFC: 初始化和更新流控信用,保证接收方的缓冲区不会被撑爆。
  • PM_*: 电源管理相关的 DLLP,比如进入 L0s 或 L1 状态。

流控这块,说白了就是接收方告诉发送方:“我还有 X 个空间,你最多只能发 Y 个过来”。

信用类型 含义 常见问题
PH (Posted Header) 未等待完成的写请求头的信用 Posted 请求不回复,必须确保接收方有空间
PD (Posted Data) 写请求数据负载的信用 数据量大的时候容易耗尽
NPH (Non-Posted Header) 读请求或配置请求的信用 耗尽后会堵住所有读请求,我踩过几次坑
CPL (Completion) 完成报文(读返回)的信用 读请求发出后,等待 CPL,如果信用不够会死锁

曾经我遇到过一个死锁问题: 某个 FPGA 原型上,RC(根复合体)发了读请求后,一直等不到 Completion。排查了一天才发现——EP(端点)的 NPH 信用本来就只有 4,被前面的 Posted 请求填满了。读请求根本发不出去。后来加了信用初始化的检查,在 InitFC2 阶段确认了对方的信用值才进入 L0。这个问题其实很好避免,就是不能在链路训练完成前发任何非 posted 请求。

3.3 链路训练与状态机

物理层最开始要做的事,就是链路训练。链路训练不光是为了让电气信号同步,还为了协商宽度、速率、极性反转。

链路训练的状态机,我记得刚接触时觉得特别复杂,后来发现记住几个关键状态就行:

  1. Detect: 检测对端是否存在。发脉冲,看有没有回波。
  2. Polling: 收发基本的训练序列(TS1/TS2)。在这里确认位锁和符号锁。
  3. Configuration: 协商宽度和速率。
  4. L0: 正常工作态,TLP 和 DLLP 可以在这个状态传输。
  5. Recovery: 比特错误太多,或者速率切换,回到 Recovery 重新练一遍。

做个比喻:Detect 像握手,Polling 像认识对方,Configuration 像交换名片,L0 就是开始正常办公了。Recovery 是因为刚才卡纸了,重新重启。

3.4 虚拟通道与流量类

PCIe 支持虚拟通道(VC),虽然很多实际系统只用 VC0。你想想看,如果所有的内存读写、中断消息、配置访问都挤在一根虚拟通道里,那实时性要求高的消息很容易被长的写请求堵住。

VC 本质上是在同一物理链路上创建多条逻辑通道,每条通道有自己的流控和仲裁。TC(Traffic Class)与 VC 有一对一的映射关系,但也可以多个 TC 映射到同一个 VC。

# TC 到 VC 的映射示例(通过软件配置)
TC0 -> VC0
TC1 -> VC1
TC2 -> VC2
TC3 -> VC0   # 多个 TC 可以映射到同一个 VC
TC4 -> VC1

工程建议: 我建议不要轻易打开多 VC 配置。因为一旦启用 VC1,VC 的排序规则和流控信用划分复杂很多。很多 SoC 的实现中,物理层和数据链路层为了支持多 VC,面积会增加 10%-20%。除非你有明确的需求(比如实时音视频流与普通数据流必须隔离),否则老老实实用 VC0 就行。

3.5 小结一下

协议层这块其实不难,就是三层各自分工:事务层管报文内容、数据链路层管报文正确性、物理层管报文传输。

我建议你从两个报文入手:TLP 和 DLLP。先搞懂 TLP 的格式和路由方式,再搞清楚 DLLP 的流控机制。这两块通了,PCIe 协议层的大半个架构也就通了。