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。
TileLink 五个通道在 L1D 的职责¶
| 通道 | 方向 | 本页关注的作用 |
|---|---|---|
| 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 发生连接的位置。
私有 L2:上行 TileLink、下行 CHI¶
为什么可以称为 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-L48
和
coupledL2/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 TXREQ、TXRSP、TXDAT、RXSNP 等模块,见
coupledL2/Slice.scala#L31-L72。
L2Param 默认每 slice 16 MSHRs,见
L2Param.scala#L69-L82;
MSHRCtl 真正按该参数实例化 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。
默认 PmemRanges 从 0x8000_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-L1681
与
LoadQueueRAR.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-L731
和
Probe.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 观测¶
源码事实。 核内默认启用 StreamStride 与 SMS prefetcher,见
Parameters.scala#L158-L169。
L2 默认启用 BOP,并因 core prefetcher 非空而加入 PrefetchReceiver;TP 在
DefaultConfig 中关闭,见
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 的最小验证集¶
- 单 line、单 hart。 验证 Load hit、Store→Sbuffer→L1、L1 eviction/Release, 并确认 64B line 地址。
- 逐层增大 working set。 依次跨过 L1、私有 L2、OpenLLC 容量,保存 miss、MSHR occupancy、refill latency 和外部流量。
- 固定容量、改变 bank bits。 检查 L1 channel/L2/LLC bank hot spot。
- 双 hart true sharing。 同一 word 交替写,确认 CHI Snoop→L2 Probe→L1 Ack/data 路径。
- 双 hart false sharing。 写同 line 的不同 word,再用 64B padding 对照。
- LR/SC。 逐步延长 LR→SC 区间,引入同 line writer,测 SC failure 与 Probe。
- 自修改代码。 Store 新指令后分别省略/执行
fence.i,确认 ICache flush 路径和 difftest 预期。 - 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 角度继续下钻,请回到 访存系统;若要建立计数器实验方法,请继续读 性能分析与优化。