访存系统:从 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 访问为何具有不同的发射与完成条件;
- 为
fence、fence.i、sfence.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 的请求优先级由高到低为:
- 非对齐访问的 tail;
- Load Replay Queue 的高优先级请求,包括 NC/MMIO replay;
- S3 返回的 fast replay;
- 普通 replay;
- 高置信度硬件预取;
- 向量 Load 元素;
- Issue Queue 发出的标量 Load;
- 低置信度硬件预取。
这一顺序直接写在
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。实现入口:
- 数据与 mask 合并:
Sbuffer.scala#L97-L189; - 入队与 line merge:
#L300-L378; - drain 条件与 DCache 请求:
#L534-L705; - replay 和 VA/PA mismatch:
#L716-L858。
软件推导。 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 未完成请求还会被串行化。响应中的 denied 与
corrupt 分别进入访问错误/硬件错误路径。实现见
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 标为 noSpec、blockBack;
AtomicsUnit 同时发 DTLB 请求和 Sbuffer flush,拒绝 MMIO/NC 原子访问,并通过
DCache replay loop 直到完成。入口见
DecodeUnit.scala#L260-L308
和
AtomicsUnit.scala#L247-L413。
LR/SC reservation 窗口参数为 64 cycles,backoff 为 8;LR 记录 cache block,SC
或 reservation invalidation 清除,SC 返回 0/1 表示成功/失败。实现见
Parameters.scala#L810-L814
和
MainPipe.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_BC、C_MA、C_FF、C_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 验证顺序¶
- 固定配置和 commit。 保存
CONFIG、主仓库 SHA、所有 submodule SHA 与 elaboration 参数;本页只覆盖c7b373b的DefaultConfig。 - 先验证地址属性。 对一个 cacheable DRAM 地址、一个 NC 映射和一个 MMIO 地址分别记录 VA→PA、PBMT、PMP/PMA 和最终请求端口。
- 再验证基本数据路径。 使用单 hart、无中断、单 cache line 的 aligned Load/Store,确认 SQ→Sbuffer→DCache 和 store-to-load forwarding。
- 逐个制造 replay。 分别构造 TLB thrash、L1 bank conflict、地址未知的老 Store、部分字节前递和 L1 miss;确认 cause 与解除条件。
- 验证顺序语义。 运行 RAW、RAR、fence、LR/SC、AMO litmus;同时记录 redirect、MDP train、Sbuffer empty、Probe/Release。
- 最后再做性能归因。 先用 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。