2、PCIe体系结构:分层架构

好,咱们今天聊聊PCIe的分层架构。说实话,很多工程师学PCIe,一上来就被各种层搞晕了。我当年也是,看了三天文档,脑子里还是一团浆糊。后来我换了个思路——把它想象成一个快递系统,一下子就通了。

PCIe的分层架构,说白了就是三个层:事务层数据链路层物理层。每一层各司其职,又互相配合。你想想看,就像公司里的三个部门,一个管业务,一个管物流,一个管运输。

2.1 为什么需要分层?

这个问题我经常被问到。为什么不能搞个单层协议,简单粗暴?

嗯,这里有个关键点:解耦。分层的好处是,每一层只关心自己的事。事务层不用管数据怎么传,物理层不用管数据是什么含义。这样,任何一层升级,其他层都不受影响。

我在项目中遇到过一件事。有一次,客户要求把PCIe从Gen3升级到Gen4。如果协议是单层的,那整个协议栈都得重写。但因为分层,我们只需要改物理层,事务层和数据链路层几乎没动。这就是分层的威力。

核心思想:每一层只对相邻层提供服务,层与层之间通过明确定义的接口通信。

2.2 事务层(Transaction Layer)

事务层是最上层,也是离软件最近的一层。它的任务很简单:处理事务。什么是事务?就是一次完整的读写操作。

我个人习惯把事务层想象成公司的销售部。销售部接单,然后把订单转给物流部。事务层也一样,它接收来自软件层的请求,打包成TLP(事务层包),然后往下传。

2.2.1 事务层的主要功能

  • TLP的组装与解析:把软件请求打包成TLP,或者把收到的TLP解析成软件能理解的格式
  • 事务排序:管理多个事务的执行顺序,保证数据一致性
  • 流量分类(TC/VC):给不同的事务打上优先级标签
  • 端到端数据完整性:通过ECRC(端到端CRC)保证数据从发送端到接收端的完整性

我记得有一次调试一个PCIe设备,发现数据偶尔会出错。查了半天,最后发现是事务层的ECRC没使能。嗯,从那以后,我只要做PCIe设计,第一件事就是检查ECRC有没有打开。

2.2.2 TLP(事务层包)

TLP是事务层的核心。说白了,它就是PCIe通信的基本单位。所有的读写操作,最终都体现在TLP上。

一个TLP长什么样?我给大家画个简图:

+----------------+----------------+----------------+----------------+
|  起始字节       |  头部 (Header)  |  数据负载       |  ECRC (可选)   |
+----------------+----------------+----------------+----------------+
|  1-4字节       |  12或16字节     |  0-4096字节    |  4字节         |
+----------------+----------------+----------------+----------------+

TLP的头部是关键。它包含了:

  • Fmt/Type:包格式和类型(比如是读请求还是写请求)
  • 长度:数据负载的长度
  • 请求者ID:谁发的这个包
  • 完成者ID:谁处理这个包
  • 地址或标签:具体操作的目标地址或事务标签

小技巧:调试时,我最喜欢看TLP的Fmt/Type字段。它直接告诉你这个包是干什么的。比如0x00表示存储器读请求,0x40表示存储器写请求。记住这几个关键值,调试效率翻倍。

2.3 数据链路层(Data Link Layer)

数据链路层是中间层,夹在事务层和物理层之间。它的任务是什么?保证可靠传输

你想想看,事务层把TLP丢给数据链路层,说「帮我传过去」。数据链路层就得保证,这个包能完整、无差错地到达对端。如果传丢了,它还得负责重传。

我曾经在一个项目中,发现链路偶尔会丢包。查来查去,发现是数据链路层的重传缓冲区太小了。嗯,这个坑我踩过,大家设计时一定要注意缓冲区大小。

2.3.1 数据链路层的主要功能

  • DLLP的生成与解析:数据链路层包,用于链路管理
  • TLP的序列化与反序列化:给TLP加上序列号,方便跟踪
  • LCRC校验:链路层CRC,保证TLP在链路上传输的正确性
  • ACK/NAK协议:确认/否定确认机制,实现可靠传输
  • 链路流量控制:防止发送方淹没接收方

2.3.2 DLLP(数据链路层包)

DLLP和TLP不同。TLP是用户数据,DLLP是管理数据。DLLP用于链路管理,比如:

  • ACK/NAK:确认TLP是否成功接收
  • 初始化FC(InitFC1/InitFC2):初始化流量控制信用
  • 更新FC(UpdateFC):更新流量控制信用
  • PM(电源管理):链路电源状态转换

DLLP的长度固定为6字节。结构如下:

+----------------+----------------+----------------+
|  DLLP类型       |  数据字段       |  CRC           |
+----------------+----------------+----------------+
|  1字节          |  3字节         |  2字节         |
+----------------+----------------+----------------+

注意:DLLP不经过事务层,直接在数据链路层处理。所以,如果你在事务层抓包,是看不到DLLP的。很多新手在这里犯迷糊,抓了半天包,发现链路层的数据没抓到。

2.4 物理层(Physical Layer)

物理层是最底层,负责真正的数据传输。它把数据变成电信号,通过差分对传出去。

我习惯把物理层比作公司的运输车队。车队不管车上装的是什么,只管把货从A运到B。物理层也一样,它不管TLP里是什么内容,只管把比特流从一端传到另一端。

2.4.1 物理层的主要功能

  • 8b/10b或128b/130b编码:把数据编码成适合传输的格式
  • 串行化/解串行化:把并行数据转成串行比特流,或者反过来
  • 时钟恢复:从数据流中恢复时钟
  • 链路训练与初始化:建立和维护物理链路
  • 电气特性管理:电压摆幅、去加重等

2.4.2 PLP(物理层包)

PLP是物理层的管理包。它和DLLP类似,不包含用户数据,只用于物理层管理。

PLP的主要用途:

  • TS1/TS2序列:链路训练时使用的有序集
  • SKP序列:用于时钟补偿
  • FTS序列:快速训练序列,用于从低功耗状态恢复
  • EIEOS序列:电气空闲退出有序集

我记得有一次,设备在低功耗状态下唤醒失败。抓了物理层的波形,发现FTS序列没发对。嗯,物理层的问题,往往是最难调的。

2.5 三层之间的协作

说了这么多,三层到底怎么协作?我给大家举个例子。

假设CPU要读取内存中的数据:

  1. 事务层:CPU发出读请求,事务层打包成Memory Read TLP,传给数据链路层
  2. 数据链路层:给TLP加上序列号和LCRC,打包成数据链路层帧,传给物理层
  3. 物理层:把数据编码、串行化,通过差分对发出去

接收端反过来:

  1. 物理层:接收串行数据,解串行化、解码,传给数据链路层
  2. 数据链路层:检查LCRC,确认无误后发送ACK,把TLP传给事务层
  3. 事务层:解析TLP,把数据交给软件层

关键点:每一层只和相邻层通信。事务层不直接和物理层打交道,物理层也不关心TLP的内容。这种设计让PCIe非常灵活,也容易扩展。

2.6 实际项目中的经验

最后,分享几个我在项目中踩过的坑:

  • TLP边界对齐:TLP的起始地址必须按4字节对齐。有一次我忘了这个,结果数据错位,查了两天。
  • DLLP的优先级:DLLP的优先级高于TLP。所以,如果链路拥塞,DLLP会插队。设计缓冲区时一定要考虑这个。
  • 物理层的去加重:高速信号需要去加重,否则信号质量会变差。这个参数需要根据链路长度调整,不是固定的。

好了,这一章就到这里。下一章我们深入聊聊链路训练与初始化,那是PCIe最核心的部分,也是很多工程师头疼的地方。到时候我会分享一些调试技巧,保证让你少走弯路。