3. PCIe数据链路层:DLLP包格式、ACK/NAK流控机制、数据完整性校验(LCRC)、重传机制

好,我们进入数据链路层。这一层在PCIe协议栈里,说实话,是承上启下的关键角色。它负责把事务层包(TLP)安全可靠地送到对端。我经常跟团队里的小朋友说,数据链路层就像快递公司的中转站——你包裹写得再好,中转站给你弄丢了,一切白搭。

3.1 DLLP包格式:链路层的“管理报文”

数据链路层包,简称DLLP。它不是用来传用户数据的,而是用来管理链路本身的。你可以把它理解成链路层的“握手信号”或“控制指令”。

DLLP的格式非常精简,总共只有6个字节:

字段 长度 说明
起始(Start) 1字节 固定值,标识DLLP开始
DLLP类型 1字节 指示DLLP的种类(ACK、NAK、PM等)
数据载荷 2字节 根据类型不同,内容不同
CRC 2字节 16位CRC校验,保护整个DLLP

常见的DLLP类型有这么几种:

  • ACK DLLP:告诉发送方“我收到了,没问题”。
  • NAK DLLP:告诉发送方“出错了,重发吧”。
  • PM DLLP:电源管理相关的握手。
  • InitFC1/InitFC2 DLLP:初始化流控参数。

我个人习惯把DLLP比作“快递回执单”。你寄了个包裹(TLP),对方收到后给你撕一张回执(ACK)。如果包裹破损,对方就给你一张“拒收单”(NAK)。

小提示: DLLP的CRC只有16位,而TLP的LCRC是32位。为什么?因为DLLP很短,16位CRC的检错能力已经足够。我在项目中见过有人把DLLP的CRC也改成32位,结果链路延迟暴增——没必要,真的没必要。

3.2 ACK/NAK流控机制:可靠传输的基石

PCIe的数据链路层使用一种“带重传的肯定确认”机制。说白了,就是发送方每发一个TLP,都等着对方回ACK。如果收到NAK,或者超时没收到ACK,就重传。

具体流程是这样的:

  1. 发送方给每个TLP分配一个序列号(Sequence Number)。
  2. 发送方把TLP存到重传缓冲区(Replay Buffer)里。
  3. 接收方收到TLP后,检查LCRC。如果正确,发ACK DLLP,并带上期望的下一个序列号。
  4. 如果LCRC错误,发NAK DLLP,要求重传。
  5. 发送方收到ACK后,从重传缓冲区里删除对应的TLP。
  6. 发送方收到NAK,或者超时未收到ACK,就从重传缓冲区里取出TLP重新发送。

嗯,这里要注意:ACK/NAK机制只保证链路层点到点的可靠传输。它不保证端到端的可靠——那是软件协议栈的事。

核心要点: ACK/NAK机制是PCIe数据链路层的“保险丝”。没有它,TLP在链路上传输时一旦出错,整个系统就可能挂掉。

我曾经在一个项目中遇到过这样的问题:某个FPGA实现的PCIe端点,在压力测试下频繁丢包。查了三天,最后发现是重传缓冲区太小,导致TLP还没等到ACK就被覆盖了。嗯,从那以后,我设计重传缓冲区时都会留至少两倍的余量。

3.3 数据完整性校验(LCRC):32位CRC的威力

LCRC,全称是Link CRC,32位。它保护的是TLP从序列号到结束符的全部内容。接收方收到TLP后,会重新计算LCRC,跟收到的LCRC比对。不一致?那就说明数据在传输过程中被破坏了。

LCRC的生成多项式是固定的,PCIe规范里写得很清楚。不过,你不需要自己实现它——大多数PCIe硬核IP都帮你做好了。但作为架构师,你得知道它的计算范围:

LCRC覆盖范围:
[序列号(2字节)] + [TLP头(12或16字节)] + [数据载荷(可选)] + [ECRC(可选,4字节)]

注意,LCRC不覆盖DLLP本身。DLLP有自己的16位CRC。为什么这么设计?因为DLLP和TLP是分开处理的,它们的校验逻辑也独立。这样设计的好处是,DLLP出错不会影响TLP的重传机制,反之亦然。

避坑指南: 我曾经见过一个设计,把LCRC和ECRC搞混了。ECRC是端到端的CRC,由事务层生成,在TLP头里携带。LCRC是链路层的,每跳都会重新计算。如果你在芯片里把这两个混为一谈,调试的时候会非常痛苦。

3.4 重传机制:丢了就重来

重传机制是ACK/NAK的配套动作。发送方维护一个重传缓冲区,里面存着所有已发送但未确认的TLP。一旦触发重传条件,就把缓冲区里的TLP全部重新发送一遍。

重传条件有三个:

  • 收到NAK DLLP:接收方明确告诉你“数据坏了”。
  • ACK超时:发送方等ACK等得太久,默认对方没收到。
  • 重复ACK:收到两次相同的ACK序列号,说明中间有TLP丢了。

重传的时候,发送方会把重传缓冲区里的所有TLP都发一遍。注意,不是只发一个,而是从出错的序列号开始,全部重发。为什么?因为接收方可能只收到了部分TLP,但顺序乱了。全部重发,接收方可以重新排序。

我建议你在设计重传缓冲区时,考虑以下几点:

  1. 缓冲区大小:至少能容纳链路延迟时间内发送的所有TLP。链路延迟包括:发送时间、传播时间、接收处理时间、ACK返回时间。
  2. 序列号管理:序列号是12位的,范围0-4095。注意处理回绕(wrap-around)的情况。
  3. 重传触发逻辑:不要只依赖NAK,超时机制也很重要。有些错误场景下,NAK可能根本发不出来。
经验之谈: 我在调试一个Gen3 x8链路时,发现重传频率异常高。最后定位到是接收端的LCRC检查逻辑有bug,导致正确的TLP也被判为错误。所以,当你看到大量重传时,别急着怀疑链路质量,先检查一下接收端的校验逻辑。

好了,数据链路层的内容就这些。总结一下:DLLP是管理报文,ACK/NAK是可靠传输的基石,LCRC是数据完整性的守护神,重传机制是最后的兜底方案。这四个东西配合起来,才保证了PCIe链路层的高可靠性。

下一章,我们进入事务层,看看TLP到底长什么样,以及怎么用它来读写数据。