4. 降级触发条件之链路层:ACK/NAK重传超限、TLP重放次数过多、CRC校验失败
链路层,说白了就是PCIe通信的“保底机制”。
事务层往下发TLP,链路层负责保证它不出错地送到对端。一旦发现丢包、数据错、序列号对不上,链路层会启动重传机制。但重传不是无限的——超过阈值,就会触发链路降级。
这一章我重点讲三个最直接的降级触发条件:
- ACK/NAK重传超限
- TLP重放次数过多
- CRC校验失败
这三种情况,在现场调试中几乎天天遇到。你搞明白了它们,链路层的问题就能解决一大半。
4.1 ACK/NAK重传超限:最常见的“软故障”
链路层有个核心机制:发送端每发一个TLP,都会保存一份副本到重放缓冲区。直到收到接收端返回的ACK,才把副本释放掉。
如果收到的是NAK,或者超时没收到任何回应,发送端会重传TLP。这个过程叫重放(Replay)。
触发降级的临界条件是什么?
每个PCIe设备都有一组寄存器记录了重放计数。一旦重放次数超过 Replay_Count_Limit(通常由链路协商决定,默认31次),链路层会直接触发降级:
- 数据链路层状态从
DL_Active切到DL_Recovery。 - 重传窗口清空,重新协商链路参数。
- 严重情况下直接切回
DL_Init,链宽或速率降一档。
关键点:重传次数是在一个“滑动窗口”内统计的。连续重传导致窗口内错误率超过阈值,触发降级。
我在项目中遇到过一台NVMe SSD,固件Bug导致发送TLP时序列号重复,接收端疯狂回复NAK。重放缓冲区直接撑爆,链路降到x1。后来靠抓Logic Analyzer才发现是固件序列号模块写错了。
4.2 TLP重放次数过多:不只是硬件问题
很多人以为重放次数过多一定是电气问题。其实不然。
我见过这样的情况:链路电气测试没问题,BER也正常,但重放计数一直往上跳。查到最后,是接收端Buffer满导致的。
接收端内部有个接收边界(Receiver Boundary),一旦Buffer满了,它不会返回ACK,而是丢包或发NAK。发端尝试重放,但收端一直满,最终重放超限。
典型场景:
| 场景 | 根因 | 我的排查经验 |
|---|---|---|
| 接收Buffer溢出 | 流量控制没配好,VC Buffer不够 | 先查 FC Credits 寄存器,看是不是一直为0 |
| 序列号不连续 | 发送端SEQUENCE_ID跳变 | 抓TLP Header,看接收端期望的Seq Num |
| 物理层符号错误 | Signal Integrity差,丢Symbol | 看 Rx Errors Counter,配合示波器 |
| 参考时钟抖动大 | PLL锁定不稳,偶尔丢数据 | 量SSC(扩频时钟)偏移量 |
避坑指南:我曾经排查过一个案子,重放计数器涨得飞快,但误码率极低。后来发现是发送端重放缓冲区的释放机制有延迟,导致同一个TLP被重复重放了。嗯,这种“软Bug”最难抓。
4.3 CRC校验失败:最后一层防线
TLP和DLLP都带有CRC。链路层收到一个TLP时,会先算CRC。如果不对,这个TLP就被丢弃了,同时发送NAK回去。
CRC失败意味着什么?
- 物理层的8B/10B或128B/130B编码没能纠正这个错误。
- 或者,这个错误发生在编码之后、解码之前,编码器没检测到。
- 说白了,就是物理层“兜不住了”。
触发降级的逻辑:
链路层会记录连续CRC失败的次数。除了单次丢弃重传外,如果短时间内CRC失败率超过 CRC_Error_Threshold(不同厂商定义不同,一般是32次/秒),链路层会认为对端不可靠,触发降级。
注意:CRC校验失败不一定全是对方的问题。我见过很多次是发送端自己把CRC算错了。比如硬件实现中LCRC(链路CRC)计算逻辑有个边界条件没处理好,在特定数据模式下算错。我自己就踩过这个坑,查了三天才发现是RTL里一个加法器溢出了。
4.4 三种条件的“联动效应”
你想想看,这三种情况经常串联出现:
- 先出CRC错误 → TLPs被丢弃
- 接收端发NAK → 发送端启动重放
- 重放次数超限 → 链路降级
所以,当你看到链路降级时,不要只盯着重放计数。往回查:CRC失败是从哪儿开始的?
我个人的调试习惯:
- 第一步:读
Data Link Error Counters寄存器。 - 第二步:对比
CRC Error Counter和Replay Counter的增长趋势。 - 第三步:如果CRC计数涨得快,优先查物理层信号质量;如果重放计数涨得比CRC快,优先查接收端Buffer或序列号逻辑。
一句话总结:链路层降级不是一次性判决,而是“CRC错误 → NAK → 重放 → 重放超限”的递进链。抓住最上游的CRC错误,才是治本。