大家好,继续我们「PCIe 验证 30 分钟每日打卡」系列。


前 19 天快速串联

  • Day1–7:PCIe 三层架构、LTSSM 链路训练、TLP 基础结构、BAR 地址映射
  • Day8–10:MemRd/MemWr、CfgRd/CfgWr、UR/CA/CRS 异常响应
  • Day11–14:Capability 链表、DLLP/ACK/NAK 重传、Credit 流控机制
  • Day15:Completion 完成包、Tag 匹配、字节计数与重组
  • Day16:地址路由、ID 路由、隐式路由三大规则
  • Day17:Msg TLP、INTx 虚拟中断、MSI/MSI-X 消息中断
  • Day18:物理 / 链路 / 事务三层错误处理、ECRC、Poison TLP、Recovery 恢复
  • Day19:PM 电源管理、D0/D3 设备状态、L0s/L1/L2 链路休眠与唤醒
  • 今日核心:PCIe 最抽象、最容易出 Bug 的模块 ——事务排序规则(Ordering)它决定了 TLP 谁先走、谁后走、谁不能超车、谁必须等待,直接关系系统是否死锁、数据是否错乱。

今日学习结构

  1. 0–12 分钟:为什么需要排序?PCIe Ordering 核心思想与模型
  2. 13–24 分钟:各类交易之间的强序 / 弱序规则
  3. 25–34 分钟:Posted 与 Non-Posted 交易本质区别、阻塞与死锁成因
  4. 35–40 分钟:UVM 验证方案、断言监控、典型测试场景
  5. 40 分钟:必测项 + 高频易错坑点总结

一、为什么 PCIe 必须严格规定排序规则?

在 CPU、多核心、多设备、多级 Switch 同时收发数据的场景下,如果所有 TLP 都可以 “随意超车、随意插队”,会出现两个致命问题:

  1. 数据一致性错误比如 CPU 先写一个标志位,再读状态。如果读请求 “超车” 跑到写前面,就会读到旧值,软件逻辑直接崩溃。

  2. 死锁(Deadlock)两个设备互相等待对方的资源,谁都不退让,整个链路卡死不动。

PCIe 规范为了避免这两类灾难,强制定义了一套不可违反的交易执行顺序,这就是 Ordering。

1.1 两个最基础的分类(理解排序的前提)

所有 TLP 只分为两大类,所有排序规则都围绕它们展开:

(1)Posted Transaction(Posted,简写 P)

  • 发出去不需要 Completion 回应
  • 发送后不用等回复,直接发下一个
  • 典型:MemWr、CfgWr、Msg、MSI

特点:

  • 不阻塞、不等待、效率高
  • 发送端不知道对方是否收到
  • 属于 “发完不管” 类型

(2)Non-Posted Transaction(Non-Posted,简写 NP)

  • 发出去必须等 Completion 回来
  • 必须收到回应才能结束事务
  • 典型:MemRd、CfgRd、IORd

特点:

  • 可靠、有确认
  • 会阻塞、会等待
  • 属于 “一问一答” 类型

1.2 排序的核心原则

  1. 同一数据流必须保持顺序同一个流的多个包,不能乱序。
  2. 不同数据流允许乱序,以提高性能互不相关的请求可以互相超车。
  3. 某些组合绝对禁止乱序(强序规则)一旦乱序会导致数据错误或死锁。
  4. Switch、Bridge 必须严格遵守转发顺序不能私自重排,否则系统崩溃。

二、PCIe 强序规则逐条详解

下面是 PCIe 规范中最核心、验证必考、绝对不能错的排序规则。我用最直白的方式逐条解释,不使用缩写歧义,确保一次看懂。

规则 1:Write → Read 禁止乱序

先写后读,读不允许超车跑到写前面。

解释:

  • CPU 先发一个 MemWr 写数据
  • 紧接着发一个 MemRd 读同一个地址
  • Rd绝对不能跑到 Wr 前面
  • 否则读到旧数据,逻辑错误

这是最重要、最基础、最不能违反的一条强序规则。

规则 2:Write → Write 允许乱序(不同地址)

  • 两个写操作地址不同
  • 可以互相超车、乱序
  • 不影响一致性

但如果是同一地址,芯片内部通常会保证顺序,避免数据覆盖错乱。

规则 3:Read → Read 允许乱序

  • 两个读请求之间可以互相超车
  • 只要它们访问不同地址
  • 完成包返回顺序可以不一致

规则 4:Read → Write 允许乱序

  • 先读后写,写可以超车跑到读前面
  • 规范允许,不会造成一致性问题

规则 5:Non-Posted 不能越过另一个 Non-Posted

  • 两个读请求(都需要 Completion)
  • 后来的读不能插到前面的读之前
  • 避免完成包乱序导致 Tag 匹配混乱

规则 6:Posted 可以越过 Non-Posted

  • 写操作可以 “超车” 插到读前面
  • 这是 PCIe 性能优化的关键
  • 但必须保证不会引发死锁

规则 7:Non-Posted 绝对不能越过 Posted

  • 读请求不允许插到写操作前面
  • 这是强序规则,违反直接协议错误

三、Posted / Non-Posted 深度模型与死锁成因

3.1 为什么会出现死锁?

死锁的经典场景:

  1. DeviceA 发一个 Non-Posted(读)给 DeviceB
  2. DeviceB 发一个 Non-Posted(读)给 DeviceA
  3. 双方都在等对方的 Completion
  4. 中间 Switch 缓冲区被占满
  5. 谁都无法发送,也无法接收→ 死锁,链路卡死

PCIe 通过排序规则 + 流控 Credit 机制,从根源上避免这种情况。

3.2 死锁三大必要条件

  1. 循环等待:A 等 B,B 等 A
  2. 资源互斥:缓冲区不能共享
  3. 不剥夺:已经占有的资源不能被抢走

PCIe 排序规则就是用来打破循环等待。

3.3 规范如何避免死锁?

  • 强制 Posted 优先级高于 Non-Posted
  • 限制转发顺序,避免环路占用
  • 流控 Credit 保证缓冲区不会被完全堵死
  • Completion 必须走 ID 路由,快速回流释放资源

四、UVM 排序验证实现

4.1 验证思路

  1. 构造一对 先写后读 的交易序列
  2. Monitor 监测 TLP 发送顺序与返回顺序
  3. 断言确保 Read 没有跑到 Write 前面
  4. 压力测试多并发场景,检查是否乱序、死锁

4.2 先写后读序列示例

class pcie_wr_then_rd_seq extends uvm_sequence #(pcie_tlp_trans);
    `uvm_object_utils(pcie_wr_then_rd_seq)

    virtual task body();
        pcie_tlp_trans wr, rd;

        // 先发写
        wr = pcie_tlp_trans::type_id::create("wr");
        start_item(wr);
            wr.tlp_type = MEM_WR;
            wr.addr     = 32'h10000000;
            wr.data     = 32'h12345678;
        finish_item(wr);

        // 后发读
        rd = pcie_tlp_trans::type_id::create("rd");
        start_item(rd);
            rd.tlp_type = MEM_RD;
            rd.addr     = 32'h10000000;
        finish_item(rd);
    endtask
endclass

4.3 核心排序断言(绝对不能违反)

module pcie_ordering_assert;
    `include "uvm_macros.svh"
    input clk, rst_n;
    input wr_vld, rd_vld;
    input [31:0] addr;

    // 强序规则:同一地址 先写后读,读不得超车
    property p_wr_before_rd;
        @(posedge clk) disable iff(!rst_n)
        wr_vld |-> not ##[1:100] (rd_vld && !wr_complete);
    endproperty

    a_wr_before_rd: assert property(p_wr_before_rd) else
        `uvm_error("ORDER_ERR", "违反强序规则:读请求超车跑到写前面!")

endmodule

五、今日必测项 + 高频易错点总结

必测项

  1. 同一地址先写后读,读不超车,顺序严格
  2. 不同地址读写允许合理乱序,提升性能
  3. Non-Posted 不越过 Posted
  4. 多并发读写不出现死锁、不阻塞
  5. Switch/Bridge 转发严格遵守排序规则
  6. 压力场景下无协议报错、无卡死

高频易错点

  1. 误以为 “所有 TLP 都必须按顺序”→ 过度约束,性能下降
  2. 允许 Read 超车 Write → 数据一致性错误
  3. 死锁测试不充分,现场复现困难
  4. 忽略 Switch 内部转发顺序,只看端口顺序
  5. 错误认为 Completion 可以随便乱序

明日 Day21 预告

【PCIe 验证每日学习・Day21】PCIe 复位与功能级复位(FLR)机制 + 冷 / 热 / 位宽 / 链路复位全验证内容包括:

  • Cold/Hot/Link/Function Level Reset 全类型
  • 复位时序、隔离规则、寄存器复位值
  • 复位期间 TLP 处理、中断清除、状态恢复
  • UVM 复位序列、断言、异常场景验证

更多推荐