017、PCIe数据包结构:TLP、DLLP与Ordered Sets
017、PCIe数据包结构TLP、DLLP与Ordered Sets上周调一块FPGA的PCIe端点设备Link已经训练成功但主机死活收不到TLP。抓链路层日志时发现一串“00 00 00 00”在反复发送——这明显是Electrical Idle链路根本没在传有效数据。折腾半天才发现原来我们的逻辑在发送TLP前漏发了一个TS1 Ordered Set。就这一个疏忽链路直接躺平不干活了。今天咱们就掰开揉碎讲讲PCIe里那些真正在链路上跑的东西TLP、DLLP和Ordered Sets。搞嵌入式或FPGA的兄弟迟早要和它们打交道这几个概念要是含糊了调试时能让你掉层皮。一、TLP真正干活的“货物运输车”TLPTransaction Layer Packet是PCIe的灵魂所有数据读写、配置访问、消息传递全靠它。你可以把它想象成高速上的货车车厢里装着你要传的数据或指令。一个TLP包长这样以Memory Read为例// 典型的TLP头部3DW配置不带数据 // DW0 [7:0] FmtType 4b0000_0000 // 3DW头无数据Mem Read [29:24] Length 6h01 // 读1个DW4字节 [31:30] TC 2b00 // 流量类别Traffic Class // DW1 [31:0] Requester ID 16h0100 // Bus 01, Device 00, Function 0 // DW2 [31:2] Address 32hA000_0000 // 要读的物理地址 [1:0] EP/AT 2b00 // 非错误包普通属性调试时我习惯先看FmtType字段——它决定了TLP的“车型”。比如0x40带数据的Memory Write0x20不带数据的Completion。这个字段要是配错了对端可能直接扔包连个错误响应都不给。踩坑记录有一次把Completion的FmtType设成了带数据结果接收方一直等数据payload超时计数器爆了。这种低级错误最耗时间建议把常用FmtType做成宏定义或枚举别手写魔数。TLP的校验ECRC是可选的但生产环境强烈建议打开。我们吃过亏某批板卡在高温下偶发比特翻转没ECRC时数据错了都不知道业务层直接崩。二、DLLP默默无闻的“交通协管员”DLLPData Link Layer Packet只管链路层那点事ACK/NAK应答、流量控制、电源管理。它只有8字节比TLP短得多而且只在相邻两个设备之间传递不会跨Switch传播。最常见的DLLP是ACK/NAK和流量控制更新包// ACK DLLP格式示例 DLLP Type 4b0000 // ACK Seq Num 12h123 // 确认收到的TLP序列号 CRC 16hxxxx // 注意这是DLLP自己的CRC和TLP的ECRC两码事流量控制DLLPUpdateFC更得小心它告诉对端“我还能收多少包”。初期调试时最容易忽略这个结果发送方狂发TLP接收方缓冲区溢出包就丢了。特别是VC0的Credit初始化必须正确否则链路虽然能Up但数据就是不动。个人经验抓包时如果看到TLP突然停了紧接着一串DLLP那八成是流量控制窗口关了。这时候得查接收端的Credit返回是否及时。有些FPGA IP核的Credit计算有延迟需要手动调优。三、Ordered Sets链路的“基础设施”Ordered Sets是物理层发的特殊序列用于链路训练、时钟补偿、电源状态切换。它们不以包形式存在而是连续的编码流。最重要的几个TS1/TS2 Training Sequences链路训练时互相交换的“握手信号”。里面包含链路速率、通道映射、极性反转等信息。开头说的那个坑就是TS1没发链路以为训练没完成根本不让TLP通过。Electrical Idle那一串00 00 00 00表示链路进入低功耗状态。但要是活跃状态下突然出现大概率是物理层出问题了——比如参考时钟不稳。SKP Ordered Set用于时钟容差补偿。PCIe允许两端时钟有±300ppm误差SKP就是用来插入或删除冗余符号的。这个机制很巧妙但如果你在逻辑里硬编码SKP发送间隔可能违反协议规定。最好用IP核自带的SKP生成逻辑别自己造轮子。抓物理层日志时Ordered Sets最容易识别它们都以COM符号0xBC开头。要是看到COM后面跟着的符号不对比如该TS1时却是SKP那训练流程肯定有问题。四、三层结构如何协同工作看个实际场景主机要读设备BAR空间的一个寄存器。事务层组装一个Mem Read TLP填好地址、长度、Requester ID。数据链路层给TLP加个序列号Seq Num和LCRC链路层CRC然后扔给物理层。同时启动定时器等对端的ACK DLLP。物理层在TLP前面加个STPStart of TLP符号后面加个END符号然后串行化发出去。如果此时需要发SKP还得在合适间隙插入。设备收到后反向操作物理层识别STP/END链路层校验LCRC并回ACK事务层解析地址并返回Completion TLP。关键点这三层是并行工作的。一个设备可能同时在收TLP、发DLLP、插SKP。所以调试时要分层看如果TLP不通先看物理层链路是否稳定TS1/TS2正常交换再看链路层ACK/NAK有没有异常最后查事务层参数。给工程师的几点建议抓包分层看用协议分析仪时别只看TLP。把物理层、链路层、事务层日志分开显示更容易定位问题在哪一层。初始化顺序不能乱PCIe链路从断电到能传TLP必须经历检测、训练、流量控制初始化等阶段。很多驱动问题都是跳过某一步导致的。注意跨时钟域TLP可能从用户逻辑时钟域转到PCIe核心时钟域这里的异步FIFO深度要留够。特别是突发大包时FIFO溢出会静默丢包极难排查。利用好Completion很多工程师只关注Request TLP但Completion包里的状态字段Completion Status才是宝藏。3表示CAUnsupported Request4表示CRSConfiguration Retry Status这些信息能直接告诉你失败原因。模拟错误注入好的PCIe设计必须测试错误路径。比如故意发个LCRC错的TLP看对端是否回NAK或者让流量控制Credit耗尽观察恢复流程。这些案例攒多了真出问题时心里就有谱。调PCIe就像和老江湖打交道表面风平浪静链路Up底下可能暗流汹涌包丢了、延迟大了、CRC错了。把TLP、DLLP、Ordered Sets这几个基本单元吃透至少能看懂它在“说什么”出了问题也知道该去哪层找线索。下次遇到链路不通别急着翻代码先抓个物理层波形看看——说不定又是一串安静的00在流淌。