作者:Xiao Hong 🌸
日期:2026-05-05


一、概述

LSU(Load-Store Unit,存取单元)是处理器前端与内存子系统之间的桥梁,承担着所有内存访问操作的发起、协调与完成。香山(XiangShan)处理器的 LSU 是一个高度复杂的模块,包含了完整的 5 级流水线、LSQ(Load-Store Queue)队列管理、存储转发(Store-to-Load Forwarding)、乱序执行支持、内存依赖预测、以及向量存取扩展等一系列机制。

本文将自顶向下、从宏观到微观,对香山 LSU 的各个子模块进行系统性的剖析。

声明:本文基于香山开源代码(kunminghu-v3 分支)编写,部分细节可能随版本迭代而变化。建议结合源代码 src/main/scala/xiangshan/mem/ 目录共同阅读。


二、整体架构:MemBlock

MemBlockMemBlock.scala)是 LSU 乃至整个内存子系统的中央调度器。它实例化了所有存取执行单元、队列、缓存和 TLB 子系统,并将它们通过大量信号线连接在一起。

2.1 角色定位

MemBlock 位于乱序后端(OoO Backend)和 L1 DCache / Uncache 之间。它的上游是从 Issue Queue 发来的指令,下游是 TileLink 总线通往 DCache 和 Uncache。整体上看:

1
2
3
4
5
6
7
Issue Queue ──▶ MemBlock ──▶ DCache / Uncache

├── TLB (DTLB + L2 TLB)
├── PMP
├── LSQ Wrapper
├── Store Buffer
└── Prefetcher

2.2 核心子模块

MemBlock 内部实例化的子模块非常丰富:

模块 数量 职责
NewLoadUnit LduCnt 5 级标量加载流水线
NewStoreUnit StaCnt 5 级标量存储地址流水线
StdExeUnit StdCnt 存储数据写回单元
AtomicsUnit 1 LR/SC/AMO 原子操作 FSM
VLSplitImp / VSSplitImp 各多个 向量存取元素拆分单元
VLMergeBufferImp / VSMergeBufferImp 各多个 向量存取合并缓冲
VSegmentUnit 多个 向量分段指令单元
VfofBuffer 多个 向量 First-Over-Fault 缓冲
LsqWrapper 1 统一的 LSQ 封装(LoadQueue + StoreQueue)
Sbuffer 1 存储合并缓冲
PrefetcherWrapper 1 L1 预取器 + L2 预取接口
TLBNonBlock 3 非阻塞 DTLB(dtlb_ld / dtlb_st / dtlb_prefetch)
L2TLBWrapper 1 页表巡游器(L2 TLB)
DCacheWrapper 1 L1 数据缓存
Uncache 1 不可缓存(MMIO/NC)访问单元
PMP / PMPChecker 各多个 物理内存保护

2.3 Uncache 仲裁 FSM

MemBlock 内部维护了一个 3 状态 FSM 来仲裁 Load 和 Store 的 Uncache 请求:

  • s_idle:空闲态,同时监听 load 和 store 请求,按 ROB 顺序公平选择
  • s_load:正在处理 Uncache 加载
  • s_store:正在处理 Uncache 存储

当两个请求同时到达时,用 robIdx 排序决定优先级,避免饥饿。


三、Load 流水线:NewLoadUnit

NewLoadUnit 是香山 LSU 中最复杂的模块之一,实现了 5 级(Stage 0~4)的有序加载执行流水线。

3.1 流水线各阶段

Stage 0(S0):请求仲裁

S0 负责从多个请求源中选择最高优先级的一个,并发起 TLB 和 DCache 请求。请求源的优先级顺序(由高到低)为:

1
2
unalignTail > replayHiPrio > fastReplay > replayLoPrio
> prefetchHiConf > vectorIssue > scalarIssue > prefetchLoConf

S0 会同时向以下模块发送请求:

  • DTLB:发起虚拟地址翻译请求
  • DCache:发起缓存访问请求
  • SQ(Store Queue):请求存储转发数据
  • Sbuffer:请求存储合并缓冲转发
  • Uncache / MSHR / TLD:其他转发源

Stage 1(S1):地址翻译与一致性检查

S1 从 TLB 获取物理地址,并执行以下操作:

  • Load Trigger 检查:检查触发器条件
  • Nuke 查询:向所有 Load 单元发送 RAW 冲突查询(store-to-load 冲突)
  • 未对齐尾部注入:如果加载跨越 16B 边界,将尾部注回 S0 重新处理
  • 写入 LSQ:将地址信息写入 Load Queue

Stage 2(S2):转发与 Replay 原因生成

S2 是最关键的阶段:

  • PMP 检查:验证物理地址的访问权限
  • 数据转发汇聚:从 SQ、Sbuffer、Uncache、MSHR、TLD 和 DCache 多路收集转发数据
  • Replay 原因生成:根据 13 种优先级有序的 Replay 原因生成错误标志

Stage 3(S3):写回与快速重放

S3 完成实际的数据写回:

  • 向后端写回 LsPipelineBundle
  • 向 LSQ 写入完整的加载信息
  • 为 Store-Load 冲突生成 Rollback 重定向
  • 撤销 RAR 条目
  • 触发 Fast Replay(不需要完整 LSQ 重新插入的快速重放)

Stage 4(S4):未对齐数据拼接

简单的透传阶段,负责将跨越 16B 边界的加载的头部和尾部数据拼接在一起。

3.2 数据通路的 3 级流水

Load 单元还有一个独立于控制流水的数据通路(LoadUnitDataPath),它本身也是 3 级流水:

  • 第 1 级:收集各转发源的数据
  • 第 2 级:对齐数据
  • 第 3 级:向写回阶段交付数据

3.3 未对齐加载处理

当加载操作跨 16B 边界时,会拆分为:

  • Head 部分:在 S0 发起
  • Tail 部分:在 S1 通过 unalignTail 端口注回 S0 重新处理
  • S4 将两部分数据拼接后输出

3.4 Fast Replay 机制

S3 可以直接将某些 Replay 场景(Bank Conflict、Replay Carry 等)通过 fastReplay 路径发回 S0,而不需要经过 LoadQueueReplay 的完整调度流程。这种机制大幅降低了轻量级冲突的重放延迟。


四、Store 流水线:NewStoreUnit

NewStoreUnit 实现 5 级有序存储地址流水线,主要负责地址生成、翻译和 Store Queue 的写入。

4.1 流水线各阶段

Stage 0(S0):地址生成与检查

  • 计算 vaddr = src(0) + imm
  • 检查对齐:isUnalign、跨 16B 检查、跨 4K 页检查
  • 发送 TLB 请求

Stage 1(S1):TLB 响应与 Nuke 查询

  • 接收 TLB 响应
  • 处理 Store Trigger
  • 向所有 Load 单元发送 Nuke 查询请求(RAW 冲突检测)
  • 将地址写入 StoreQueue(toSqAddr
  • 更新 LFST(清除预测的 Store)
  • 将未对齐尾部注回 S0

Stage 2(S2):PMP 检查与异常处理

  • 接收 PMP 响应
  • 检测异常(访问fault、地址错位)
  • 生成内存类型标识(Cacheable / NC / MMIO)
  • 通过 toSqAddrRe 重新写入 SQ(此时已知 paddr)
  • 发送预取训练信号

Stage 3(S3):写回

  • 向 ROB 写回(标量通过 io.stout
  • DelayPipeline 延迟向量存储写回(RAW 同步)
  • 拼接未对齐的头部和尾部

Stage 4(S4):未对齐拼接

透传阶段,同 Load 流水线的 S4。

4.2 未对齐存储

与未对齐加载类似,跨 16B 的存储被拆分为头部和尾部:

  • 尾部:在 S1 通过 unalignTail 注回 S0
  • 第二地址:存入 UnalignQueueSQUnalignQueueSize 个条目)

4.3 CBO 操作

CBO(Cache Block Operation,如 cbo.clean/flush/inval/zero)作为特殊 Store 类型处理,在地址已知的情况下可以绕过 TLB。


五、存储数据单元:StdExeUnit

StdExeUnitStdExeUnit.scala)是纯组合逻辑的存储数据调度单元,负责将标量和向量存储数据路由到正确的目标。

5.1 核心机制

  • 仲裁逻辑io.in.ready := !io.vstdIn.valid,即向量存储数据优先于标量
  • ROB 写回:仅在 scalar && !vector && !isAMO 时有效
  • AMO 数据转发:当 FuType.storeIsAMO 为真时,数据通过 io.atomicData 转发到 AtomicsUnit
  • SQ 数据写入:向量和标量存储数据通过 io.sqData 写入 StoreQueue,向量优先

六、原子操作单元:AtomicsUnit

AtomicsUnit 是一个 11 状态的 FSM,负责处理 RISC-V 的 LR(Load-Reserved)、SC(Store-Conditional)和 AMO(原子内存操作)指令。

6.1 状态机

状态 主要操作
s_invalid 空闲
s_tlb_and_flush_sbuffer_req 并行发送 TLB 请求和 Sbuffer 刷新请求
s_pm PMP 检查
s_wait_flush_sbuffer_resp 等待 Sbuffer 刷新完成
s_cache_req 发送 DCache 原子访问请求
s_cache_resp 接收 DCache 响应
s_cache_resp_latch 锁存数据
s_finish / s_finish2 写回(部分 AMO 需要多次写回)
s_extra_wb / s_extra_wb2 AMOCAS.Q 的额外写回(4 个 std uops,2 个 sta uops)

6.2 关键设计

  • SLR/SC 保证:LR 会预留一个缓存行(设置有效位),SC 只有在该行仍然reserved 时才能成功
  • Sbuffer 刷新:在发起 DCache 原子访问前,必须确保 Store Buffer 已排空(否则可能看到旧数据)
  • AMOCAS.Q:64 位双字 CAS 需要 4 个 std uops 和 2 个 sta uops,产生 2 次寄存器写回

七、LSQ 封装:LsqWrapper

LsqWrapperLSQWrapper.scala)将 LoadQueue 和 NewStoreQueue 封装为统一接口,负责指令入队、Uncache 仲裁和所有流水线信号的路由。

7.1 核心接口

1
2
3
4
5
6
7
8
9
io.enq       ← Dispatch(入队)
io.ldu ← Load 流水线(rawNukeQuery, rarNukeQuery, ldin)
io.sta ← Store 流水线(storeAddrIn, storeAddrInRe, unalignQueueReq)
io.std ← Store 数据(StdExeUnit)
io.forward ← SQForward 到 Load 单元
io.replay ← LoadQueueReplay 到 Load 流水线
io.sbuffer ← SbufferWriteIO(提交存储到存储缓冲)
io.rob ← ROB 提交追踪
io.uncache ← UncacheWordIO(NC/MMIO 请求共享)

7.2 Uncache 仲裁

同 MemBlock,Load 和 Store 的 Uncache 请求在 LsqWrapper 内部也经过一个 3 状态 FSM 仲裁,使用 robIdx 保证顺序。


八、LoadQueue 体系

LoadQueue 并非单一模块,而是由多个协同工作的子模块组成:VirtualLoadQueue、LoadQueueRAR、LoadQueueRAW、LoadQueueReplay 和 LoadQueueUncache。

8.1 VirtualLoadQueue

跟踪所有已分配、在飞行中、已提交的加载指令的循环队列。管理 LQ 指针算术,并向 Dispatch 单元提供分配/释放状态。

关键机制:

  • 入队:在 enqPtrExt 处分配,enqPtrExt 按接受条目数推进
  • 写回:S3 的 ldin.fire 触发 committed 标志设置
  • 出队DeqPtrMoveStride = CommitWidth,ROB 提交时推进
  • Redirect 恢复:通过 lqCancelCnt 计算并清除已取消条目

8.2 LoadQueueRAR(Load-After-Load 冲突检测)

使用 16 位 XOR 哈希的部分物理地址 CAM 来检测 RAR 冲突:

  • 低 5 位paddr[6+i] XOR paddr[15-i]
  • 高 11 位paddr[i+6] XOR paddr[i+17] XOR paddr[i+28] XOR paddr[i+39]

当 S2 的加载查询命中一个更旧的、未释放的加载条目时,触发 RAR 冲突并撤销该条目。

8.3 LoadQueueRAW(Store-Load RAW 冲突检测)

使用 24 位部分物理地址 CAM 进行更精确的 RAW 冲突检测:

  • 当 StoreUnit S1 写入新存储地址时,触发对 LoadQueueRAW 的 CAM 查询
  • 命中的条目经过 多周期最旧条目选择SelectOldest 递归优先级选择器)
  • 冲突时产生 rollback 重定向,并发送 mdpTrain 到前端更新 StoreSet 预测器

8.4 LoadQueueReplay(重放调度)

负责将需要重新执行的加载调度回 Load 流水线 S0,核心机制包括:

3 级重放流水线

1
2
3
S0: 选择
S1: 读取 vaddr / 准备
S2: 发起重放请求

13 个 Replay 原因(优先级从高到低)

优先级 原因 描述
0 C_UNCACHE 不可缓存访问
1 C_SMF 存储多路转发无效
2 C_MA St-Ld 冲突重新执行检查
3 C_TM TLB 未命中
4 C_FF 存储到加载转发检查
5 C_DR DCache 重放
6 C_DM DCache 未命中
7 C_WF 写回单元预测失败
8 C_BC 银行冲突 / 未对齐尾部拆分失败
9 C_RAR RAR 队列接受检查
10 C_RAW RAW 队列接受检查
11 C_NK St-Ld 冲突 nuke
12 C_MF 未对齐缓冲满

⚠️ 优先级顺序对死锁预防至关重要,代码中明确警告不可随意调整。

冷启动机制(Cold-Down)

DCache 未命中的条目进入冷启动计数器(默认 16 周期),冷却期满后才重新考虑重放。L2 Hint 可以提前唤醒冷启动中的条目。

8.5 LoadQueueUncache

管理 MMIO/NC 加载事务的每条目 5 状态 FSM:

1
s_idle → s_req → s_resp → s_wakeup → s_wait
  • MMIO:写回必须等待 ROB 提交顺序保证
  • NC:直接写回,无 ROB 顺序约束

九、StoreQueue 体系:NewStoreQueue

NewStoreQueue 管理从入队到完成的全部存储指令流,包含三个核心子模块。

9.1 ForwardModule(存储到加载转发)

3 级流水线负责对每个加载查询返回正确的转发数据:

Stage 0

  • 生成加载字节起止位置、ageMask、转发掩码
  • Store-Set 命中检测(LFST 启用时比较 robIdx,否则比较 SSID)

Stage 1

  • 虚拟地址匹配(忽略低 6 位 VWordOffset)
  • 跨 16B 处理(s1Same16BMatchVec / s1Next16BMatchVec
  • 字节重叠检查:storeRangeStart <= loadRangeEnd && storeRangeEnd >= loadRangeStart
  • 两段最年轻选择(findYoungest 递归优先级选择器)

Stage 2

  • 物理地址匹配检查
  • 数据旋转:rotateByteRight 将存储数据移位对齐到加载位置
  • 转发无效条件检测:s2PaddrNoMatchs2Cross4KPages2MultiMatch(无完全覆盖)

9.2 DeqModule(出队管理)

处理四类存储的出队逻辑:

  • 可缓存存储:格式化为 WriteToSbufferReqEntry 发往 EnterSbufferQueue,跨 16B/跨页时拆分
  • NC/MMIO 存储:经 Uncache 仲裁器发往 Uncache 缓冲
  • CBO 操作:经 CMO 接口发往 DCache
  • 强制写回forceWrite 时(sbuffer 接近满),加速写入 sbuffer

9.3 EnterSbufferQueue

StoreQueue 和 Sbuffer 之间的顺序写缓冲,确保在 Sbuffer 单周期无法接受全部 EnsbufferWidth 条目时仍能维持顺序性(环形队列,FIFO 顺序)。

9.4 UnalignQueue

存储跨页或未对齐存储的第二物理地址,条目格式为 (paddrHigh, robIdx, sqIdx)。在出队时由 DeqModule 读取,用于补全跨页存储的第二个物理地址。


十、存储缓冲:Sbuffer

Sbuffer 是提交存储与 L1 DCache 之间的一级写合并缓冲,采用多状态 FSM 管理。

10.1 顶层状态机

1
2
3
4
x_idle        ← 正常操作,接受 SQ 写入,执行 DCache 写回
x_replace ← 替换(eviction)
x_drain_all ← 排空所有条目(用于 fence 等)
x_drain_sbuffer ← 排空特定条目

10.2 核心机制

  • 写合并(Write Merging):收到写请求时,先检查是否存在相同 ptag 的条目。命中则合并(掩码 OR,新数据覆盖重叠字节);未命中则按 PLRU 分配新条目
  • DCache 写流水线(3 级)
    • Out S0:从 SbufferData 读取条目数据
    • Out S1:向 DCache 发送写请求
    • Out extra:处理 DCache 写命中响应,清理条目
  • 转发(Forwarding):负载可从 Sbuffer 条目直接获取数据(VTag 匹配)。但如果 vtag 与实际 paddr 不匹配,则触发微架构排空(evacuate)——将冲突条目 evacuate 到 DCache
  • 一致性超时cohCount 计数器追踪条目的有效时长,超时强制写回 DCache 以防止饥饿
  • PLRU 替换:伪 LRU 树用于在 Sbuffer 满时选择淘汰候选

十一、内存依赖预测:StoreSet / SSIT / LFST / WaitTable

11.1 SSIT(Store Set 标识表)

SRAM 实现的 Store Set 标识表:

  • 读端口:DecodeWidth(decode 读取和 update 逻辑共用)
  • 写端口:2 个(Port 0 = load 更新 + flush,Port 1 = store 更新)
  • 4 种更新情况覆盖 load 和 store 的 SSID 分配/继承/竞争

11.2 LFST(最后取指存储表)

每个 SSID 一个 FIFO 表:

  • Dispatch/Rename:记录该 SSID 下最近取指的 Store
  • Store 发射:清除对应 LFST 条目中的预测
  • Redirect 恢复:LFST 在分支 mispredict 等重定向时恢复

11.3 WaitTable

Alpha 21264 风格的 Load 等待预测表,使用 2 位饱和计数器:

  • 读:loadWaitBit = (counter[1] || csrCtrl.no_spec_load) && !csrCtrl.lvpred_disable
  • 更新:violation 时向饱和方向(11)计数
  • 超时重置:防止计数器永久饱和

十二、向量化扩展

香山 LSU 包含了丰富的向量存取支持:

  • VSplit / VSSplit:将向量存取指令拆分为独立的元素级访问
  • VLSplitImp / VSSplitImp:元素拆分与分发
  • VLMergeBufferImp / VSMergeBufferImp:元素合并缓冲
  • VSegmentUnit:向量分段指令(segment load/store)
  • VfofBuffer:向量 First-Over-Fault 缓冲(用于处理向量访问中的首个错误元素)

向量存储数据流:vsSplit → stdExeUnits.io.vstdIn → lsq.io.std.storeDataIn


十三、关键设计哲学

13.1 优先级有序的冲突处理

LoadQueueReplay 中的 13 个 Replay 原因严格按优先级编码,这个顺序是死锁安全的核心。改变优先级可能导致死锁,因此在代码中有明确的警告注释。

13.2 多级转发

香山 LSU 实现了从多个来源的数据转发路径:StoreQueue、Sbuffer、Uncache、MSHR(Miss Status Holding Register)、TLD(Top-Level Directory),以及最终的 DCache。优先级和选择逻辑分布在多个流水线阶段。

13.3 解耦设计

存储端的 Store Queue、EnterSbufferQueue 和 Sbuffer 形成三级缓冲,每级都可以独立进行握手和反压,实现了乱序执行后端和解耦的 DCache 写入管道之间的有效隔离。

13.4 预测与投机

WaitTable 和 StoreSet 预测器使 Load 可以投机性地提前执行,绕过未完成的 Store 依赖。当预测失败时,通过 Rollback 重定向和 MDP 训练来纠正。


十四、总结

香山处理器的 LSU 是一个工程复杂度极高的内存子系统,其设计融合了现代处理器内存层次管理的最佳实践:

  • 乱序执行:通过 LSQ 体系(VirtualLoadQueue、StoreQueue)与 Replay 机制支持乱序发射和内存顺序保证
  • 低延迟访问:多级存储转发(Sbuffer → SQ → DCache)尽量减少加载延迟
  • 内存一致性:通过 RAR/RAW 队列在流水线内部完成冲突检测,避免深度的重放链
  • 原子操作:专用 11 状态 FSM 处理 LR/SC/AMO,确保 RISC-V 内存模型的正确性
  • 向量扩展:完整的向量存取流水线支持,满足 RISC-V Vector 扩展需求
  • 可预期性:全面的性能计数器、TLP(Top-Level Performance)接口和调试信号支持

理解 LSU 的每个子模块及其交互,是掌握香山处理器微架构的必经之路。


参考资料

  • 香山源代码:src/main/scala/xiangshan/mem/
  • 架构文档:docs/mem-block-spec.md
  • RISC-V 架构规范

本文由 Xiao Hong 🌸 基于开源代码撰写,如有疏漏欢迎指正。