跳转至

访存系统:从 LSU 到 MMU、LSQ 与 DCache

对软件工程师而言,香山访存系统最重要的性质不是“Load 有几级流水”,而是: 一条访存指令会同时穿过地址翻译、权限检查、内存顺序、数据前递和缓存访问 五条彼此耦合的路径。任何一条路径暂时不能给出确定答案,Load 都可能 replay; Store 则可以先计算地址和数据,但只有退休后才进入可见的缓存写路径。

读完本页,你应该能够:

  • 从一次 Load replay 追到 TLB、SQ/Sbuffer、DCache MSHR 或内存顺序检查;
  • 区分 Virtual Load Queue、Replay Queue、RAR/RAW Queue 和 Store Queue 的职责;
  • 解释 MMIO、NC、cacheable 访问为何具有不同的发射与完成条件;
  • fencefence.isfence.vma、LR/SC 和 AMO 建立 RTL 路径;
  • 把软件性能现象转化成可用计数器、波形或最小实验验证的假设。

版本与证据边界

本页固定在主仓库 kunminghu-v3 @ c7b373bdbe9a9417b4c22ede4ddd0d970eb48b73。 参数快照指 DefaultConfig, 不代表其他 config 或最终物理实现。

  • 源码事实:可由上述提交的 Scala/配置直接确认。
  • 软件推导:由硬件行为推导出的 bring-up 或优化假设,仍需实验确认。
  • 待验证:源码连线不足以证明,或取决于 elaboration、平台和工作负载。

先建立一条端到端路径

flowchart LR
  IQ["Issue Queue<br/>访存微操作"] --> LSU["Load / Store Unit<br/>AGU 与流水控制"]
  LSU --> TLB["L1 DTLB<br/>VA → PA · PBMT"]
  TLB --> PMP["PMP / PMA<br/>权限与内存属性"]
  LSU <--> LSQ["LSQ<br/>LQ · SQ · replay · RAR/RAW"]
  LSQ <--> FWD["SQ / Sbuffer / Uncache<br/>Store-to-load forwarding"]
  LSU --> L1D["L1 DCache<br/>VIPT · MSHR · refill"]
  LSQ --> SB["Sbuffer<br/>已提交 Store 合并与排空"]
  SB --> L1D
  L1D --> L2["私有 L2"]
  L2 --> LLC["OpenLLC / Memory"]
  PMP -->|"NC / IO"| UC["Uncache Buffer"]
  UC --> MMIO["MMIO / uncached fabric"]

源码事实。 MemBlock 实例化 3 条标量 Load pipeline、2 条 Store-address pipeline、Store-data 单元、AtomicsUnit、LSQ、Sbuffer、DCache、Uncache 和 L2TLB, 并把 LoadUnit 同 DCache、SQ/Sbuffer、Uncache、MSHR、TileLink-D、RAR/RAW 检查连接起来。入口可从 MemBlock.scala#L314-L350#L500-L579#L872-L1048 开始阅读。

当前默认配置快照

结构 DefaultConfig 当前值 作用
标量 Load / Store pipeline 3 / 2 每周期可并行进入 LSU 的标量通道数
向量 Load / Store pipeline 2 / 2 向量访存拆分后的执行通道
Virtual Load Queue 120 保存尚未完成/提交的 Load 状态
Load Replay Queue 120 保存需要择机重放的 Load
RAR / RAW Queue 96 / 56 检查 Load-Load 与 Store-Load 顺序违例
Physical Store Queue 64 保存推测态 Store;虚拟指针倍数为 2
Load Uncache Buffer 16 LQ 一侧的 MMIO/NC Load 状态
通用 Uncache Buffer 16 向总线发出 Get/Put 并等待响应
Sbuffer 16,阈值 9,入队宽度 2 缓冲并合并已经提交的 cacheable Store
硬件非对齐 Load / Store 启用 / 启用 cacheable 标量非对齐访问可拆分执行

参数来自 Parameters.scala#L104-L115#L166-L205

队列项数不是软件可用窗口的简单加法

这些结构保存的状态和分配/释放条件不同。例如 Replay Queue 与 Virtual LQ 可能同时描述同一条 Load,SQ 的虚拟指针空间也不等于物理存储项数。不要把表中 数字相加后称为“访存窗口大小”。

Load:一次读取为何可能反复执行

S0:请求仲裁、地址生成和并行查询

源码事实。 LoadUnitS0 的请求优先级由高到低为:

  1. 非对齐访问的 tail;
  2. Load Replay Queue 的高优先级请求,包括 NC/MMIO replay;
  3. S3 返回的 fast replay;
  4. 普通 replay;
  5. 高置信度硬件预取;
  6. 向量 Load 元素;
  7. Issue Queue 发出的标量 Load;
  8. 低置信度硬件预取。

这一顺序直接写在 NewLoadUnit.scala#L41-L121。 S0 生成虚拟地址,并行发起 DTLB 请求、L1D VIPT 索引和多路前递查询;前递源包括 SQ/Sbuffer、Uncache、MSHR 和 TileLink-D。

软件推导。 当 replay、向量访存和硬件预取同时活跃时,普通标量 Load 的 S0 仲裁压力可能升高。若观察到 issue 数不低但有效 Load 完成率下降,应把“入口 竞争”与“L1 miss”分开验证,不能只凭 IPC 判断。

S1:TLB 结果、早期异常与 Store nuke

源码事实。 S1 接收 TLB 的 PA、PBMT 和异常结果;TLB miss 或翻译异常会 终止当前 DCache 路径。它还把虚拟地址跨 word 的非对齐访问拆出 tail,并检查 更老 Store 刚解析出的地址是否与这条 Load 冲突。相关逻辑见 NewLoadUnit.scala#L610-L745

这里的 nuke 不表示 cache line 被驱逐,而表示:Load 已经越过了一个地址此前 未知、现在证明与之冲突的更老 Store,需要撤销或快速重放。

S2:权限、内存类型、前递和 replay 原因

源码事实。 S2 将 TLB/PBMT 与 PMP/PMA 结果合并,区分 cacheable、NC 和 MMIO;随后组合 SQ、Sbuffer、Uncache、MSHR 和 TileLink-D 的 byte mask/data。 只有所需字节全部被覆盖时,Load 才能不依赖 DCache 数据完成。核心逻辑见 NewLoadUnit.scala#L923-L1007

Load 可能同时带有多个 replay cause;Replay Queue 按固定优先级选择重新发射条件:

Cause 直接含义 软件排查方向
C_UNCACHE 进入 NC/MMIO 流程 PBMT/PMA、设备地址、ROB-head 串行化
C_SMF Store match/forward 不能安全完成 VA/PA alias、部分字节覆盖
C_MA 更老 Store 地址仍未知 Store-address 延迟、MDP 预测不足
C_TM TLB miss/translation 等待 页表层级、TLB working set、sfence
C_FF Store 数据尚未准备好 Store-data 依赖链、前递覆盖
C_DR DCache 请求被拒绝/需重试 MSHR 或主流水竞争
C_DM 等待 DCache miss/refill L1 miss、下层延迟、MSHR 占用
C_WF 等待 refill/forward 条件 refill 与前递时序
C_BC DCache bank conflict 同 bank 地址分布、并发 Load
C_RAR Load-Load ordering 检查未完成 coherence release、同地址 Load
C_RAW Store-Load ordering 检查未完成 更老 Store 解析、真实 RAW 违例
C_NK 被更老 Store nuke StoreSet 命中质量、地址生成延迟
C_MF miss/forward 相关等待 结合波形确认具体触发源

Cause 编号和优先级以 LoadQueueReplay.scala#L31-L72 为准;不同 cause 的解除条件在 #L422-L463

L1 miss 不等于立刻重新发射整条 Load

L1D 可以把首次 miss 分配给 MSHR,后续同 line 请求还可 merge。只有请求被 nack、bank conflict、取消,或仍缺少必要数据时,才需要重新竞争 LoadUnit。 L1 LoadPipe 的 MSHR allocate/merge 与 replay 判定见 LoadPipe.scala#L310-L469

S3/S4:完成、写 LQ 或发起 rollback

源码事实。 后级组合 cache/refill/forward 数据,决定向后端写回、写入 LQ, 或生成 memory-order rollback。关键位置为 NewLoadUnit.scala#L1423-L1463#L1513-L1552。 Load 完成并不代表已经成为架构状态;ROB 仍按程序顺序退休,之后发现的内存顺序 违例可以 redirect 并重做更年轻工作。

Store:先执行,退休后才进入缓存写路径

地址流水和 Store Queue

源码事实。 Store-address pipeline 的主要阶段为:

阶段 工作 源码
S0 AGU、对齐检查、发起 TLB NewStoreUnit.scala#L163-L240
S1 接收翻译、生成 page/access fault NewStoreUnit.scala#L343-L499
S2 PMP/PMA/PBMT,区分 cacheable/NC/MMIO NewStoreUnit.scala#L618-L662

SQ 有 64 个物理项,StoreQueueMultiple=2 使虚拟 Store 指针空间扩大到 128, 用于在重定向和指针回绕时维持年龄关系;这不表示存在 128 个实体数据项。

Store 可以在推测态提前得到地址和数据,但 cacheable Store 只有在 retired/ precommitted、有效且无异常后,才按序送入 Sbuffer;提交和出队逻辑见 NewStoreQueue.scala#L1167-L1303#L1921-L1963

Store-to-load forwarding

源码事实。 SQ 对所有更老 Store 做 VA 与 byte-mask 搜索,在多个完整匹配中 选择最年轻者;之后还要用 PA 复核,避免虚拟 alias 造成错误前递。若只有部分字节 命中、数据未就绪、存在多个不安全匹配,Load 会等待或 replay。严格 StoreSet 依赖会等待所有更老且地址未知的 Store;非严格依赖只等待 LFST 预测出的目标。 实现见 NewStoreQueue.scala#L376-L625

软件推导。C_MA/C_FF 高而 DCache miss 低,优化重点通常不是数据 局部性,而是:缩短 Store 地址或数据依赖链、减少难预测 alias、让生产 Store 更早 发射。这个判断必须用 replay-cause 事件或波形验证。

Sbuffer:退休 Store 与 L1D 之间的弹性层

源码事实。 默认 16-entry Sbuffer 按 line/word 和 byte mask 合并 Store;达到 阈值、收到 fence/atomic/CMO flush,或遇到必须排空的转发/alias 条件时向 DCache drain。实现入口:

软件推导。 Streaming Store、频繁 fence、写 miss 和 line ownership 争用都会 延长 drain,最终表现为 Sbuffer 满、SQ 不能提交、ROB head 停顿。连续写同一 line 则可能从 merge 获益;应把“Store 指令数”与“实际发往 DCache 的 line/word 请求数” 分开测量。

LSQ 不是一个队列

flowchart TB
  DISP["Dispatch"] --> VLQ["Virtual Load Queue<br/>完成与退休状态"]
  DISP --> VSQ["Virtual / Physical Store Queue"]
  VLQ --> LRQ["Load Replay Queue<br/>等待条件解除"]
  VLQ --> RAR["RAR Queue<br/>Load-Load ordering"]
  VLQ --> RAW["RAW Queue<br/>Store-Load violation"]
  VLQ --> LUC["Load Uncache Buffer<br/>MMIO / NC Load"]
  VSQ --> SB["Sbuffer<br/>committed stores"]
  RAW -->|"redirect + train"| MDP["SSIT + LFST"]
  MDP -->|"下次预测等待"| DISP

源码事实。 LSQWrapper 只有在 LQ 与 SQ 都能接受时才接收 dispatch;它还按 ROB 年龄在 Load/Store uncache 请求间仲裁。顶层关系见 LSQWrapper.scala#L85-L200#L203-L331

MDP:当前有效的是 StoreSet

源码事实。 当前 MemCtrl 实例化 SSIT 和 LFST;WaitTable 端口被置为 DontCare,因此仓库中仍存在的 WaitTable.scala 不是这条配置下的生效预测器。 证据见 MemCtrl.scala#L11-L31

StoreSet 默认参数为:

  • SSIT:1024 entries,使用 10-bit PC hash;
  • LFST:64 sets × 2 ways;
  • SSID:6 bits;
  • strict reset period:8192 cycles。

参数定义在 Parameters.scala#L819-L833

flowchart LR
  PC["Decode PC"] --> SSIT["SSIT<br/>SSID · strict"]
  SSIT --> REN["Rename<br/>storeSetHit"]
  REN --> LFST["Dispatch 查询 LFST"]
  LFST --> WAIT["loadWaitBit<br/>waitForRobIdx"]
  WAIT --> SQMAP["Virtual SQ ptr<br/>→ Physical SQ ptr"]
  SQMAP --> ISSUE["满足依赖后发射 Load"]
  VIO["真实 RAW violation"] -->|"分配 / 合并 SSID"| SSIT

SSIT 的读、周期清空和违例训练位于 StoreSet.scala#L39-L229, SSID 分配/合并位于 #L271-L329, LFST dispatch、issue 清除和 redirect 恢复位于 #L423-L550

RAR 与 RAW:预测之后仍要做真实检查

源码事实。 Store 地址解析后,RAW Queue 检测已经执行的年轻同地址 Load; 命中会生成 flush redirect,并把 Load/Store PC 送回 MDP 训练,见 LoadQueueRAW.scala#L330-L396

RAR Queue 只在必要时保存尚未完成的老 Load;DCache release 会标记相关项。若 同地址年轻 Load 已越过该 ordering 点,则产生 nuke,见 LoadQueueRAR.scala#L130-L190#L239-L277

软件推导。 RAW rollback 浪费的是违例 Load 之后已经完成但尚未退休的工作; 代价可能远大于重新执行一条 Load。RAR replay 则可能在共享 line 被其他 hart 写入或驱逐时上升。具体内存模型语义仍应通过 litmus test 和波形验证。

MMU:地址翻译和数据访问并行推进

flowchart LR
  VA["Load / Store VA"] --> L1TLB["各端口 L1 DTLB<br/>默认 48-entry FA"]
  L1TLB -->|"hit"| PA["PA + permission + PBMT"]
  L1TLB -->|"miss"| L2TLB["共享 L2TLB / PageTableCache"]
  L2TLB -->|"cache hit"| REFILL["refill L1 DTLB"]
  L2TLB -->|"walk"| PTW["LLPTW / PTW / HPTW"]
  PTW --> TL["TileLink Get<br/>读取 64B PTE block"]
  TL --> PTC["PageTableCache refill"]
  PTC --> REFILL
  PA --> CHECK["PMP / PMA / PBMT"]
  CHECK --> ACCESS["DCache 或 Uncache"]

源码事实。 Load、Store 和 prefetch 使用分离的 nonblocking L1 DTLB,PTW 响应在 MemBlock 中仲裁和分发;每个 DTLB port 后都有 PMP/PMA 检查。连线见 MemBlock.scala#L587-L729。 默认 Load/Store TLB 是 48-entry fully associative,参数位于 Parameters.scala#L220-L277

L2TLB/PageTableCache 的默认几何为:L3 non-leaf 16 FA、L2 non-leaf 16 FA、 L1 4×2、L0 64×4、bitmap 32×4、superpage 16 FA、6 个 LLPTW 项和 64B page-table block,定义在 MMUConst.scala#L44-L93。 实际 page-table memory request 使用 TileLink Get 读取对齐 block,见 L2TLB.scala#L454-L553

PBMT、PMA 与 PMP 的分工

PBMT 本实现中的解释 访问路径
00 使用 PMA 判定 可能 cacheable,也可能由 PMA 判为 MMIO
01 NC:幂等、弱序主存 Uncache path,可做受限合并/并发
10 IO:非幂等、强序设备 Uncache path,严格等待提交位置
11 Reserved 不应作为正常映射使用

PBMT 编码见 MMUBundle.scala#L453-L466。 两阶段地址翻译时,如果 stage-1 PBMT 非零,它优先于 stage-2 PBMT,见 TLB.scala#L421-L431

Bring-up:页表正确不等于访问一定成功

TLB 给出有效 PA 后,PMP/PMA 仍可把访问转为 access fault 或 MMIO。调试异常时 应同时记录 VA、PA、页表权限、PBMT、当前 privilege、PMP 配置和 PMA region, 不要只检查 PTE。

MMIO 与 NC:不能沿用普通缓存 Load 的假设

源码事实。 Uncache 模块默认有 16 entries,用独立 TileLink source ID 发出 单拍 Get/Put。只有同一 8B block、属性一致、对齐且 byte mask 可安全组合的 NC Store 才允许 merge;同 block 未完成请求还会被串行化。响应中的 deniedcorrupt 分别进入访问错误/硬件错误路径。实现见 Uncache.scala#L174-L240#L263-L298#L392-L473

Load 与 Store 的门槛不同:

  • NC Load 可以在分配后较早请求;MMIO Load 只有成为 ROB pending/head 才发送;
  • MMIO/NC Store 必须是已经提交的 SQ head;NC 等请求 ack,MMIO 等 response 后 才向 ROB writeback;
  • Uncache Store-to-load forwarding 先用 VA 搜索,再用 PA 复核;不匹配时会排空, 不能把设备 alias 当作普通 cache forwarding。

Load 侧状态机见 LoadQueueUncache.scala#L70-L239, Store 侧状态机见 NewStoreQueue.scala#L973-L1079, Uncache 前递见 Uncache.scala#L497-L574

默认参数 EnableUncacheWriteOutstanding=false,见 Parameters.scala#L190-L199; 运行时还有 uncache_write_outstanding_enable 控制进入 MemBlock,见 MemBlock.scala#L1062-L1074

软件推导。 设备轮询、逐字节 MMIO 和过多 ordering barrier 往往受到 ROB-head 串行化和总线往返延迟支配;优化时应优先减少访问次数、批量处理状态,并确认设备 协议允许,而不是尝试依赖 cache locality。

Fence、原子操作与 CMO

Fence 家族

源码事实。 Fence 状态机首先请求 flush Sbuffer,并等待 sbIsEmpty;MemBlock 把“空”定义为 Sbuffer 与 Uncache 都为空。之后:

  • fence.i 进入 ICache flush;
  • sfence.vma / hfence.* 进入 TLB flush;
  • 普通 fence 在存储缓冲排空后完成。

状态机见 Fence.scala#L25-L87, Sbuffer/Uncache 汇合见 MemBlock.scala#L1372-L1440

软件推导。 Fence 的代价取决于执行时尚未 drain 的 Store/Uncache 数量和 I$/TLB flush 后的重新填充,而不只是“执行一条特殊指令”的固定延迟。测量 barrier 时应改变 barrier 前的写集合大小,以区分固定控制开销和排空开销。

LR/SC 与 AMO

源码事实。 Decode 将 LR/SC、AMO 和 AMOCAS 标为 noSpecblockBack; AtomicsUnit 同时发 DTLB 请求和 Sbuffer flush,拒绝 MMIO/NC 原子访问,并通过 DCache replay loop 直到完成。入口见 DecodeUnit.scala#L260-L308AtomicsUnit.scala#L247-L413

LR/SC reservation 窗口参数为 64 cycles,backoff 为 8;LR 记录 cache block,SC 或 reservation invalidation 清除,SC 返回 0/1 表示成功/失败。实现见 Parameters.scala#L810-L814MainPipe.scala#L686-L731

待验证。 从结构上看,AMO 路径采用了较保守的非推测执行和 Store drain;但 仅凭这些连线不能证明每种 aq/rl 组合的全部 ISA ordering。应以 ISA litmus、 difftest 和波形验证精确语义。

Cache-block operation

源码事实。 CBO clean/flush/inval 在已提交 SQ head 发起,并先 flush Sbuffer; CBO.ZERO 当前先通过 Sbuffer 写零,再执行相关 flush 流程,见 NewStoreQueue.scala#L913-L970。 源码仍保留将 ZERO 改为 next-level operation 的 TODO,因此不要把当前实现机制 写成长期接口保证。

从软件现象反推硬件瓶颈

软件现象 优先观察 可能路径 下一步实验
Load latency 长但 L1 miss 低 replay cause、bank conflict、forward C_BCC_MAC_FFC_NK 改地址低位、拆 Store 依赖链
TLB miss 上升 L1/L2 TLB、PTW memory request 页表 working set、频繁 sfence huge page、固定页表、冷热页分组
Store-heavy IPC 降低 Sbuffer/SQ full、drain/replay 写 miss、fence、ownership 竞争 改 barrier 间距、顺序写同 line
多核共享数据抖动 RAR/RAW、release、L1/L2 miss true/false sharing 单核对照、64B padding、读写角色互换
MMIO 极慢 uncache request/response、ROB head 强序串行化、设备总线延迟 减访问次数、批量轮询、检查 PBMT/PMA
SC failure 高 reservation、Probe/ownership 同 line 竞争、LR/SC 窗口过长 缩短 LR-SC 区间、指数退避、换 AMO

哪些事件能由软件 HPM 读取

源码事实。 mhpmevent 的每个 CSR 可选择 4 个局部事件:四个 10-bit index 位于 [9:0][19:10][29:20][39:30],再由 OR/AND/XOR/ADD 操作组合。实现见 HardwarePerfMonitor.scala#L23-L78

29 个 mhpmcounter3..31 当前分为 frontend 8、backend 8、MemBlock 8、L2 5, 因此访存事件进入 mhpmcounter19..26,私有 L2 事件进入 27..31。分组见 NewCSR.scala#L1335-L1350。 MemBlock 暴露 LoadUnit、Sbuffer、LSQ、DCache、DTLB、PTW 和 issue-count 等事件, 见 MemBlock.scala#L1750-L1770

不要把 XSPerfAccumulate 名称直接当成可编程 HPM event ID

很多 XSPerfAccumulate 只用于仿真/调试。真正软件可读的候选必须进入模块 perfEvents 并被 HPM 汇聚。最终 event ID 又取决于 config 和 elaboration 顺序,应从本次构建的 printEventCoding 输出提取,而不是硬编码源码数组下标。

建议的 bring-up 验证顺序

  1. 固定配置和 commit。 保存 CONFIG、主仓库 SHA、所有 submodule SHA 与 elaboration 参数;本页只覆盖 c7b373bDefaultConfig
  2. 先验证地址属性。 对一个 cacheable DRAM 地址、一个 NC 映射和一个 MMIO 地址分别记录 VA→PA、PBMT、PMP/PMA 和最终请求端口。
  3. 再验证基本数据路径。 使用单 hart、无中断、单 cache line 的 aligned Load/Store,确认 SQ→Sbuffer→DCache 和 store-to-load forwarding。
  4. 逐个制造 replay。 分别构造 TLB thrash、L1 bank conflict、地址未知的老 Store、部分字节前递和 L1 miss;确认 cause 与解除条件。
  5. 验证顺序语义。 运行 RAW、RAR、fence、LR/SC、AMO litmus;同时记录 redirect、MDP train、Sbuffer empty、Probe/Release。
  6. 最后再做性能归因。 先用 HPM/Top-down 找结构,再用波形或最小 microbench 证明因果,避免把相关性当成根因。

待验证与维护触发条件

  • 最终生成配置可能覆盖队列、TLB、DCache 和预取参数;换 CONFIG 后需重做快照。
  • uncache_write_outstanding_enable 的运行时默认值还要结合 CSR reset/elaboration 输出确认,不能只看功能参数。
  • StoreSet 的实际命中率、strict 项比例和周期清空影响必须用 workload 测量。
  • RAR/RAW 与 RVWMO、aq/rl 的精确对应需用 litmus 证明。
  • CBO.ZERO 源码存在 TODO;上游修改相关 SQ/CMO 逻辑时应刷新本节。
  • HPM event 编码随 perfEvents 组合变化;每次升级 commit 都应保存新的 printEventCoding 映射。

继续阅读 Cache 与一致性,可以把这里的 L1 miss、Release、 RAR replay 和 LR/SC failure 连接到私有 L2、CHI 与 OpenLLC。