跳转至

Cache 与一致性:从 L1D、私有 L2 到 OpenLLC

香山当前缓存层次跨越两种协议:核内 L1 与私有 L2 之间使用 TileLink,一致性 L2 与 OpenLLC 之间使用 CHI。对软件工程师而言,容量和命中率只是第一层;更 关键的是理解谁保存数据、谁记录共享者、谁发 Probe/Snoop,以及请求在哪个 边界发生排队

读完本页,你应该能够:

  • 沿一条 L1 miss 追踪 TileLink Acquire、私有 L2、CHI 和 OpenLLC;
  • 正确区分“私有 L2 inclusive”与“OpenLLC single-RN exclusive / multi-RN non-inclusive”;
  • 解释 Release、Probe、RAR replay、false sharing 和 LR/SC failure 的关系;
  • 知道哪些现象能用 core HPM 观察,哪些仍需 CHI logger、数据库或波形;
  • 为单核容量问题和多核一致性问题设计不同的最小实验。

版本与子模块边界

主仓库固定为 XiangShan c7b373bdbe9a9417b4c22ede4ddd0d970eb48b73。 该提交把 XSCache 锁定在 468bad4927be1a2b7b6182b180ab647f63cf1b5d。 本页对子模块文件使用后一个 SHA 的 permalink;否则主仓库链接不能稳定定位 子模块内部源码。

  • 源码事实:固定提交中的配置、连线或状态字段直接支持。
  • 软件推导:从协议行为推导出的性能/调试假设,需要计数器或实验验证。
  • 待验证:依赖实际核数、elaboration、SoC 外设或 workload。

一张图看清协议边界

flowchart LR
  LSU["LSU / Sbuffer"] --> L1D["每核 L1D<br/>64 KiB · 4-way"]
  IFU["ICache"] --> XBAR["Tile 内 TL Xbar"]
  PTW["PTW"] --> XBAR
  L1D -->|"TileLink A/C/E<br/>B/D 返回"| XBAR
  XBAR --> L2["每核私有 L2<br/>2 MiB · 4 banks · inclusive"]
  L2 -->|"CHI Request Node"| LLC["OpenLLC<br/>16 MiB · 4 banks"]
  LLC -->|"CHI Subordinate Node"| MEM["Memory bridge / DRAM"]
  DEV["Instruction/Data Uncache"] -->|"绕过缓存 L2"| MMIO["MMIO bridge"]
  L2 -.->|"CHI Snoop"| L2
  L2 -.->|"TileLink Probe"| L1D

源码事实。 XSTile 把 DCache coherent client nodes、ICache 和 PTW 接入 私有 L2 的 TileLink xbar,并把指令/数据 uncache 接到独立 MMIO 路径;L1 prefetch sender 也在此接入 L2 receiver。见 XSTile.scala#L60-L99。 私有 L2 在 L2Top 中实例化,TileLink 位于上游、CHI 位于下游,见 L2Top.scala#L76-L149

当前默认几何

层级 范围 容量与组织 并发/接口要点
L1D 每 hart 64 KiB、4-way、256 sets、64B line 3 Load、2 Store pipeline;2 个 coherent memory channels
私有 L2 每 hart 总 2 MiB、4 banks;每 bank 8-way、1024 sets 每 slice 默认 16 MSHRs;上行 TL、下行 CHI
OpenLLC SoC 共享 总 16 MiB、4 banks;每 bank 16-way、4096 sets 64B line、32B CHI beat;独立 self/client directory

源码事实。 容量组合来自 DefaultConfig。 L1 sets 由 size / ways / 64 计算,并配置 16 个 miss entries、8 个 Probe entries、 18 个 Release entries 和最多 6 个 prefetch entries,见 Configs.scala#L275-L295。 L2 与 LLC 的 sets 都按总容量除以 banks、ways 和 64B 计算,见 #L297-L383

几何是 config 的结果,不是模块永久常量

L2Param/OpenLLCParam 自身还有用于单元测试或其他配置的默认值。分析当前核时 应从 DefaultConfig → CDE 参数组合 → elaboration 输出 追踪,不能只读子模块 case class 的默认参数。

L1 DCache:VIPT、并行流水和 MSHR

命中路径

源码事实。 L1D block size 为 64B,采用 VIPT 路径并处理可能的 alias;DCache 向 LSU 暴露 3 个 Load port、2 个 nonblocking Store-address port、Release hint, 以及来自 MSHR/TileLink-D 的前递。接口见 DCacheWrapper.scala#L39-L77#L835-L846

LoadPipe 大致分为:

阶段 主要工作 关键结果
S0 用虚拟 index 请求 metadata/tag/data 与 DTLB 并行,缩短 hit latency
S1 接收物理 tag、权限和 data-array 响应 判断候选 way、ECC 与访问条件
S2 选择数据;miss 时 allocate/merge MSHR hit 返回,miss 等 refill 或 replay
S3 metadata/ECC 后处理 形成 LSU 后级响应

源码位置为 LoadPipe.scala#L111-L305#L310-L469#L522-L608

Miss、merge 与 refill

源码事实。 L1D 实例化 3 个 LoadPipe、2 个 StorePipe、MainPipe、MissQueue、 ProbeQueue 和 WritebackQueue,见 DCacheWrapper.scala#L1061-L1097。 MissQueue 按 cache line 合并 secondary miss;首次 miss 分配 entry,后续兼容请求 可以 merge,refill 数据还可同等待中的 Store byte mask 合并。关键位置为 MissQueue.scala#L620-L647#L767-L824

软件推导。 对同一批 L1 misses,较高 MLP 和较好的 same-line merge 可以隐藏 部分延迟;反之,16 个 MSHR 被长延迟请求占满后,新 miss 会 replay/反压。性能 分析应同时看 miss 数、MSHR occupancy、merge 数和 refill latency,而不是只看 L1 miss rate。

通道 方向 本页关注的作用
A L1D → L2 Get/Acquire/Put,申请数据或权限
B L2 → L1D Probe,要求报告、降级或失效 line
C L1D → L2 ProbeAck/Release,可携带 dirty data
D L2 → L1D Grant/refill/ReleaseAck
E L1D → L2 GrantAck,完成权限授予握手

源码事实。 DCache 为每个 memory channel 创建支持 Probe 的 coherent client; B 进入 ProbeQueue,C 连接 WritebackQueue,D 被路由到 MissQueue/WritebackQueue, A/E 完成请求与授权握手。连线见 DCacheWrapper.scala#L959-L997#L1595-L1700

WritebackQueue 发出请求时,DCache 还把 line address 作为 release hint 送给 LSU 的 RAR Queue。这是 coherence 活动与 load-load ordering replay 发生连接的位置。

为什么可以称为 inclusive

源码事实。 当前配置构造函数直接要求 inclusive=true,否则 elaboration 失败,见 Configs.scala#L297-L320。 L2 Directory 为每个 line 保存上层 client bits 和 alias 信息;当 eviction、CHI snoop、CMO 或 alias 条件需要时,MainPipe 可向上层 L1 client 发 TileLink Probe。 证据见 coupledL2/Directory.scala#L31-L48coupledL2/MainPipe.scala#L247-L298

“Inclusive”在这里表示私有 L2 必须能够追踪并包容上层 L1 的 line residency; 它不等于“数据永远只存在一份”,也不说明共享 OpenLLC 的 inclusion 属性。

Bank、slice 与 MSHR

源码事实。 每个 L2 bank 对应一个 slice。Slice 包含上行 SinkA/SinkC/ GrantBuffer、Directory、DataStorage、refill/release buffers、MainPipe/MSHRCtl, 以及下行 CHI TXREQTXRSPTXDATRXSNP 等模块,见 coupledL2/Slice.scala#L31-L72L2Param 默认每 slice 16 MSHRs,见 L2Param.scala#L69-L82MSHRCtl 真正按该参数实例化 entries,见 MSHRCtl.scala#L94-L121

当前 L1D 有两个地址选择的 coherent channels;在 L2 bank 数为偶数时, sliceCoherentClientMap 把 bank i 映射到 DCache channel i % 2,见 L2Top.scala#L110-L138

软件推导。 地址映射不均匀可能造成某些 bank/channel 的 MSHR 或队列先满, 即使总容量尚未成为瓶颈。用固定总 working set、只改变物理地址 bank bits 的 microbench,可区分容量 miss 和 bank hot spot。

OpenLLC:不要简单称为 inclusive L3

数据目录与 Snoop Filter 分离

源码事实。 OpenLLC 是 CHI-to-CHI 缓存:每个 Tile/L2 作为一个 RN 接入, 每个 LLC bank 实例化一个 slice,SN 端连接下游 memory bridge。顶层见 OpenLLC.scala#L26-L95

它维护两个不同目的的目录:

  • selfDir:记录 OpenLLC 自己的数据 line,使用配置的 PLRU replacement;
  • clientDir:作为 Snoop Filter 跟踪上游 RN/L2 可能持有的 line,采用独立的 client-cache 几何和随机替换。

两者的实例化和 replacement 选择见 openLLC/Directory.scala#L293-L355。 因此“LLC 本身没命中”并不等于“系统中没有该 line”;Snoop Filter 仍可能指出 某个私有 L2 拥有最新副本。

inclusion 取决于 RN 数量

源码事实。 OpenLLCParam 的 inclusion 字符串由 client 数决定:一个 RN 为 Exclusive,多个 RN 为 Non-inclusive,见 LLCParam.scala#L67-L106

因此:

  • DefaultConfig() 的默认 n=1 对应 single-RN,OpenLLC 是 exclusive;
  • DefaultConfig(n > 1) 对应 multi-RN,OpenLLC 是 non-inclusive;
  • 私有 L2 仍然是 inclusive,这两个结论并不矛盾。

容量不能机械相加

single-RN exclusive 模式下,L2 与 LLC 旨在减少重复驻留,但具体瞬间仍受 transient state、refill/writeback 影响;multi-RN non-inclusive 模式也不能 假设 LLC 覆盖所有私有 L2 line。不要简单用 L1 + L2 + LLC 得到“有效容量”。

三条典型一致性路径

1. 读 miss:从 L1 Acquire 到内存

sequenceDiagram
  participant C as Core / L1D
  participant L2 as Private L2
  participant LLC as OpenLLC
  participant M as Memory

  C->>L2: TileLink Acquire/Get
  alt L2 hit 且权限可满足
    L2-->>C: Grant/Data
    C->>L2: GrantAck
  else L2 miss或需下层权限
    L2->>LLC: CHI Read/Make request
    alt LLC selfDir/data hit
      LLC-->>L2: CHI CompData
    else clientDir 指向其他 RN
      LLC->>L2: CHI Snoop to owner RN
      L2-->>LLC: Snoop response/data
      LLC-->>L2: Completion/data
    else system miss
      LLC->>M: CHI memory request
      M-->>LLC: data
      LLC-->>L2: completion/data
    end
    L2-->>C: TileLink Grant/Data
    C->>L2: GrantAck
  end

源码事实。 L2 slice 同时包含 TileLink sink/grant 与 CHI TX/RX;OpenLLC slice 同时包含 RN/SN CHI、Directory、DataStorage、RefillUnit、MemoryUnit、 ResponseUnit 和 SnoopUnit,见 openLLC/Slice.scala#L27-L68。 图中具体 CHI opcode 会随命中状态和权限变化;图只表达责任边界,不是逐周期波形。

2. Store/AMO 获取写权限

Cacheable Store 先在 SQ 中等待提交,经 Sbuffer 到 L1D。若 L1/L2 没有足够写权限, 请求向下获取 ownership;其他共享者收到 Snoop/Probe 并降级或失效,dirty owner 可能返回数据。权限完成后 Store 才能在缓存层次中安全生效。

源码事实。 SQ→Sbuffer→DCache 路径见 MemBlock.scala#L1166-L1176; L2 MainPipe 的 Probe 判定和 CHI request 接口见 coupledL2/MainPipe.scala#L247-L298

软件推导。 多 hart 写同一 line 时,瓶颈通常是 ownership 往返而非 DRAM 带宽。64B line 内不同变量仍共享 ownership,形成 false sharing。

3. Snoop 向上穿透到 L1

OpenLLC 根据 clientDir 决定需要 Snoop 的 RN;目标私有 L2 的 RXSNP/MSHR/ MainPipe 处理请求。若 Directory 表示 L1 client 仍持有 line,L2 再发 TileLink B Probe;L1 由 ProbeQueue/MainPipe 查 tag/state,通过 C 返回 ProbeAck 或 dirty data。

源码事实。 OpenLLC 默认 SnoopUnit 提供 16 个 snoop entries,并对同地址及 资源冲突进行仲裁,见 SnoopUnit.scala#L31-L139。 L1 的 B→ProbeQueue 与 C→Writeback 路径见 DCacheWrapper.scala#L1606-L1681

地址路由与绕过路径

源码事实。 当前 Top.scala 的 CHI route 将物理地址 0x0000_0000..0x7fff_ffff 送到每核 MMIO bridge,其余地址送 OpenLLC;OpenLLC 的 SN 端再连接 memory bridge。见 Top.scala#L316-L345。 默认 PmemRanges0x8000_0000 开始,与该边界一致,见 SoC.scala#L52-L72

此外,PBMT/PMA 判为 NC/IO 的数据请求走 Uncache,指令 uncache 与数据 uncache 在 Tile 内都绕过 cached L2,见 XSTile.scala#L94-L99

地址边界是当前 SoC 实现事实,不是 RISC-V ISA 规定

FPGA、仿真 SoC、其他 config 或外接 coherent/non-coherent master 可能有不同 map。Bring-up 时应以生成 DTS、elaboration 参数和顶层 route 为准。

一致性如何反馈到 LSU

Release 与 RAR replay

源码事实。 L1 WritebackQueue 发出 release 时,DCache 将 line PA 送给 RAR Queue;RAR 标记可能受影响的老 Load。如果年轻同地址 Load 已越过 ordering 点, 就产生 nuke/rollback。连接见 DCacheWrapper.scala#L1673-L1681LoadQueueRAR.scala#L239-L277

软件推导。 RAR replay 上升可能是共享 line 被其他 hart 写入,也可能是本地 replacement/release 压力;必须同时看地址、Probe/Snoop 和 miss/replacement 事件,不能把所有 RAR 都归因于 false sharing。

LR/SC reservation 与 Probe

源码事实。 LR 在 L1 MainPipe 记录 reservation block;SC 或 invalidation 清除。ProbeQueue 对命中 locked reservation block 的 Probe 做阻塞/协调,见 MainPipe.scala#L686-L731Probe.scala#L87-L96

软件推导。 高 SC failure 应先检查同 64B line 的竞争、LR→SC 区间长度和中间 访存,而不是仅增加重试次数。可用 padding、缩短 critical section、指数退避或 单条 AMO 做对照。

ICache 不是自动软件透明

源码事实。 fence.i 状态机在 Store/Uncache 排空后显式发出 ICache flush, 见 Fence.scala#L46-L87。 因此自修改代码、JIT 或写入后执行的新指令序列必须遵循 RISC-V 的 fence.i 软件协议;不能因为 DCache coherent 就假设 ICache 自动看到新指令。

Prefetch 会改变 cache 观测

源码事实。 核内默认启用 StreamStrideSMS prefetcher,见 Parameters.scala#L158-L169。 L2 默认启用 BOP,并因 core prefetcher 非空而加入 PrefetchReceiver;TPDefaultConfig 中关闭,见 Configs.scala#L338-L341#L534-L538

软件推导。 Prefetch 可能降低 demand miss,也可能占用 MSHR、bank、带宽和 replacement capacity。比较工作负载时至少区分 demand/prefetch source,并保持 预取 CSR 配置一致;“miss 下降但 IPC 不升”常意味着带宽、污染或晚预取问题。

待验证。 PrefetcherWrapper 内部虽构造 L3 prefetch request,但当前外部 连接只明确把 L1 prefetch sender 接到私有 L2 receiver;L3 request 在 wrapper 内被置 ready,未见直连 OpenLLC 的端口。因此本页不声称“core prefetcher 会直接 向 OpenLLC 发 L3 prefetch”。相关代码见 PrefetcherWrapper.scala#L303-L335

性能观测:能看见什么,不能看见什么

Core HPM 的边界

源码事实。 当前 29 个 mhpmcounter3..31 分组为 frontend 8、backend 8、 MemBlock 8 和私有 L2 5;因此 mhpmcounter19..26 观察 LSU/L1/MMU, 27..31 观察私有 L2。映射见 NewCSR.scala#L1335-L1350

MemBlock 还提供 ROB-head DCache miss、TLB replay/miss、load violation、L1/L2/L3 miss 等 Top-down 信号,见 MemBlock.scala#L1576-L1593

OpenLLC 的完整局部事件没有像私有 L2 一样接入 core 的 5 个 HPM slots;当前 顶层明确回送的是 l3Miss/地址匹配等 Top-down 信息,见 Top.scala#L334-L344。 要分析 OpenLLC Snoop、目录冲突和 CHI 延迟,需要结合 CHI logger、XSCache 性能数据库或波形。

event ID 必须来自本次 elaboration

HPM 事件在各模块中按 perfEvents 展平,精确 ID 随 config 和源码变化。 XSPerfAccumulate 也不一定软件可读。请保存 elaboration 的 printEventCoding 输出,再配置 mhpmevent,不要把文档中的源码顺序当 ID。

一张用于归因的矩阵

现象 至少同时观察 更可能的根因 区分实验
L1 miss 高,L2 hit 高 L1 miss、L2 hit、L1 MSHR/merge L1 capacity/conflict、工作集略大 改 working set,保持地址映射
L1/L2 miss 都高,LLC hit L2 MSHR、CHI latency、LLC hit 私有容量不足、跨核共享 单核/多核对照
L3/system miss 高 DRAM traffic、refill latency、MLP 总工作集超 LLC、随机访问 huge page、顺序/随机对照
Probe/Snoop 与 miss 同升 Release/RAR、CHI snoop、SC fail true/false sharing 每 hart 独立 line、64B padding
miss 不高但 replay 高 bank conflict、MSHR nack、forward 端口/bank/队列冲突 只改地址低位和并发度
预取流量高、IPC 无收益 demand/prefetch miss、MSHR、带宽 late/useless prefetch、污染 固定 workload,切换预取 CSR

面向 Bring-up 的最小验证集

  1. 单 line、单 hart。 验证 Load hit、Store→Sbuffer→L1、L1 eviction/Release, 并确认 64B line 地址。
  2. 逐层增大 working set。 依次跨过 L1、私有 L2、OpenLLC 容量,保存 miss、MSHR occupancy、refill latency 和外部流量。
  3. 固定容量、改变 bank bits。 检查 L1 channel/L2/LLC bank hot spot。
  4. 双 hart true sharing。 同一 word 交替写,确认 CHI Snoop→L2 Probe→L1 Ack/data 路径。
  5. 双 hart false sharing。 写同 line 的不同 word,再用 64B padding 对照。
  6. LR/SC。 逐步延长 LR→SC 区间,引入同 line writer,测 SC failure 与 Probe。
  7. 自修改代码。 Store 新指令后分别省略/执行 fence.i,确认 ICache flush 路径和 difftest 预期。
  8. MMIO/NC 对照。 对同一测试形态改变 PBMT/PMA,确认请求是否绕过缓存层次。

待验证与维护触发条件

  • 实际 DefaultConfig(n) 决定 OpenLLC 是 exclusive 还是 non-inclusive;部署前 必须记录 hart/RN 数量。
  • L1/L2/LLC 的 bank-select 位和最终 SRAM 组织应从 elaboration/生成 RTL 验证。
  • 外部 DMA、PCIe 或其他 master 是否 coherent 取决于 SoC 集成;本页不能证明 非本仓库顶层设备的缓存一致性。
  • CHI opcode、transient state 和 retry 路径需要结合 XSCache 波形/协议 checker, 本页 sequence diagram 只描述责任链。
  • OpenLLC 的 Snoop Filter replacement 可能产生额外 snoop,但实际性能影响需测量。
  • 更新主仓库或 XSCache pin 后,必须同时刷新两个 SHA、几何、HPM event map 和 inclusion 结论。

若要从 replay、StoreSet、TLB、MMIO 和 Fence 角度继续下钻,请回到 访存系统;若要建立计数器实验方法,请继续读 性能分析与优化