ICache 深入:VIPT、流水与 Miss
本页从实现者视角解释昆明湖 V2R2 ICache:地址如何拆分,为什么需要 IPrefetchPipe + WayLookup + MainPipe,跨行取指如何映射到 bank,miss/refill 与 flush 如何保持一致,以及哪些不变量适合用断言和形式验证检查。
如果还没有建立整体数据流,先读 ICache、ITLB 与持续供给。
1. 从参数推导地址拆分
默认参数为 64 B line、256 sets、4 ways:
虚拟地址 VA
┌────────────── higher bits ──────────────┬─ index ─┬ block offset ┐
│ │ 8 bits │ 6 bits │
└─────────────────────────────────────────┴─────────┴──────────────┘
└─ bank/byte offset
物理地址 PA
┌──────────────── physical tag ───────────┬─ index ─┬ block offset ┐
│ │ 8 bits │ 6 bits │
└─────────────────────────────────────────┴─────────┴──────────────┘
block offset = log2(64) = 6位;set index = log2(256) = 8位;- 因而 set index 使用地址
[13:6]; - 4 KiB 页内偏移只有
[11:0],index 的高 2 位超出页内偏移。
这说明 V2R2 并不是“所有 index 位都天然不受翻译影响”的最简单 VIPT。源码用 aliasBitsOpt 携带这 2 个 alias bit,在 refill/下层请求中保持虚拟索引相关信息;软件映射和缓存维护也必须遵守同义地址约束。阅读代码时遇到 alias 字段,应把它理解为同一物理页可能由不同虚拟 index 访问时所需的颜色信息,而不是额外的物理 tag。
2. 为什么拆成预取流水和主流水
传统描述常写成“VA 同时查 TLB 和 Cache,再比较 PA tag”,但 V2R2 为缩短真正取指的关键路径,把过程拆成两次相互解耦的访问:
时间领先方向 ───────────────────────────────────────────────>
IPrefetchPipe: VA ── Meta read + ITLB ── tag compare/PMP ──> WayLookup
│
MainPipe: 真 fetch ── consume ──────┘
│
└─ Data read ─ ECC/exception ─> IFU
2.1 IPrefetchPipe 的责任
它负责所有“在读数据之前可以提前完成”的工作:
- 接收 FTQ 硬件预取或
prefetch.i软件预取; - 同时读两个相邻 line 的 MetaArray;
- 访问 ITLB,拼出物理 tag;
- 比较 tag,得到每条 line 的 waymask;
- 检查 Meta ECC、PMP、页异常、PBMT/MMIO 属性;
- 向 WayLookup 写入结果;
- 对可缓存 miss 提前申请 prefetch MSHR。
2.2 MainPipe 的责任
它只处理已经成为架构取指需求的请求:
- 通过 FTQ identity 找到对应 WayLookup 项;
- 用 waymask 只读命中的 DataArray way;
- 做最终 PMP、data parity/ECC 和 TileLink corrupt 检查;
- 对尚未被预取填回的 line 申请 fetch MSHR;
- 在 refill 返回后旁路/重读,向 IFU 交付正确字节和异常。
拆分的收益是命中吞吐,代价是必须维护“预取结果属于哪个真正请求”的严格队列契约。
3. WayLookup 的队列契约
WayLookup 不是普通 tag cache,而是按取指顺序保存 IPrefetchPipe 结果的 FIFO。可以把一项抽象成:
WayLookupEntry = {
request identity / vaddr,
ptag[0..1],
waymask[0..1],
ITLB/PMP/PBMT/backend exception,
meta ECC state,
cross-line state
}
它需要满足四个核心不变量:
- 顺序一致:MainPipe 消费的 entry 必须对应同一个 FTQ 请求;
- flush 一致:错误路径的 entry 不能在 redirect 后被消费;
- refill 可见:entry 排队期间若目标 line refill,waymask 必须得到更新;
- SRAM 冲突一致:MissUnit 写 DataArray 的周期不能让 MainPipe 使用尚未稳定的 lookup 结果启动读。
WayLookup 为空时支持 bypass,减少正常延迟。发生 MSHR 新写入时,dequeue 会被阻塞;同周期 DataArray 本来就因写端口冲突而不能接受 MainPipe 读,因此这项保护通常不会额外损失吞吐。
4. MainPipe 的 S0/S1/S2
4.1 S0:请求发射与 DataArray 选路
S0 同时要求:
- FTQ 的真正 fetch 请求有效;
- 对应 WayLookup 项有效;
- DataArray 读端口可用;
- S1 能接收新请求。
S0 根据两个 line 的 waymask、起始 bank 和跨行状态生成各 bank 的读取选择。这里必须避免 multi-hit:正常情况下每条 line 的 one-hot waymask 最多只有一位。
如果 redirect 后 IPrefetchPipe 尚未形成新的 WayLookup 项,S0 就会停在这里;这是 redirect 后元数据没有被预取距离隐藏时的额外代价。
4.2 S1:权限、替换状态与 refill 旁路
S1 保存 vaddr、ptag、FTQ identity、waymask、Meta code 等,并执行:
- 接收 DataArray 返回;
- 做 PMP 检查;
- 检查 Meta parity/ECC 与 multi-hit;
- 命中时更新 PLRU touch;
- 监听 MissUnit response。
若 SRAM data 与 MSHR response 同时可用,MSHR 返回优先。原因是 refill response 是对旧 miss 的最新、确定数据;不能让 SRAM 中尚未更新或同拍冲突的数据覆盖它。
4.3 S2:最终决策与响应
S2 执行最终 data ECC、corrupt 和异常合并,并分别处理最多两条 line:
line_need_miss = line_needed
&& (!tag_hit || corrupt_requires_refetch)
&& !architectural_exception
&& !mmio_or_uncacheable
- 两条 line 都 hit:按 bank offset 拼接,返回 IFU;
- 一条 hit、一条 miss:保存已知部分,按顺序请求缺失 line;
- 两条都 miss:依次完成两个 fetch MSHR 请求;
- refill response 到来:可直接作为当前等待请求的数据来源;
- IFU 反压:S2 保持请求身份和数据,不得错配下一项请求。
因此这里更准确的描述是“stall/hold 直到所需 line 完成”,而不是笼统地说“所有冲突都 replay”。
5. DataArray:8 bank 为什么一次访问 5 个
每个 64 B line 分成 8 个 8 B bank。默认 data SRAM 每项以 64 data bit 加校验位组织。取指窗口从任意 8 B bank 起点开始,需要覆盖连续 5 个 bank:
bank index = block_offset[5:3]
start=0 -> 0,1,2,3,4
start=1 -> 1,2,3,4,5
start=2 -> 2,3,4,5,6
start=3 -> 3,4,5,6,7
start=4 -> 4,5,6,7 + next.0
start=5 -> 5,6,7 + next.0,1
start=6 -> 6,7 + next.0,1,2
start=7 -> 7 + next.0,1,2,3
源码的 getBankSel 会断言恰好访问 ICacheBankVisitNum = 5 个 bank。getLineSel 决定跨行后每个输出 bank 应来自当前 line 还是下一 line。
这里有三个容易犯的实现错误:
- 只看 64 B line 内偏移,忘记 start≥4 时必须查询下一 line 的 tag;
- bank 拼接顺序正确,但异常/waymask 的 line 编号对反;
- refill 写 bank 与 MainPipe 读 bank 同拍,未正确拉低 ready。
6. MetaArray:双端口语义而非“两份完整 Cache”
MetaArray 按相邻 line 的奇偶关系拆成两个物理 bank/port,以支持一次取指同时查询当前 line 和下一 line。每个端口仍需比较所有 4 个 way:
line N set/tag ──> even/odd meta bank ──> 4-way compare ──> waymask[0]
line N+1 set/tag ──> other meta bank ──> 4-way compare ──> waymask[1]
valid bit 与 tag/data SRAM 分开管理。fence.i 或不可纠错错误可以清 valid,而无需擦除 SRAM 内容。命中判定必须是 valid && tag_equal && !fatal_meta_state,不能仅比较 tag。
7. MissUnit 的资源和仲裁
7.1 两类 MSHR
| 类型 | 数量 | 请求来源 | 仲裁特征 |
|---|---|---|---|
| Fetch MSHR | 4 | MainPipe | 总体优先于 prefetch;低 index 优先 |
| Prefetch MSHR | 10 | IPrefetchPipe | FIFO 次序 |
需求取指与预取使用不同池,既保护 demand miss,也允许预取保持一定并发。
7.2 重复请求
新请求到来时,MissUnit 比较所有有效 MSHR 的 block address:
- 已有同 line 请求:不再发第二个 Acquire;接口仍可表现为该请求被接受;
- 无同 line 且有空项:分配 MSHR;
- 无空项:反压来源流水。
“接口 fire”不等于“一定新分配了 MSHR”,调试计数器时要区分 request accepted、duplicate merged 和 new allocate。
7.3 TileLink refill
64 B line 经 32 B TileLink data path 分两个 beat 返回。当前实现假设一个 line 的两个 beat 不与另一 line 交错,因此全部 MSHR 共享一组 refill data register:
Acquire的 source ID 编码 MSHR;- 第一个 Grant beat 写入半行;
- 第二个 beat 补齐;
- 完整 line 写 DataArray,tag/valid 写 MetaArray;
- WayLookup 更新可能仍在排队的请求;
- 最后一个 beat 完成后释放对应 MSHR。
若未来互连允许不同 source 的多 beat 交错,这个“共享一组 data register”的前提就必须重新设计。
8. Replacer 的时序语义
默认 set-PLRU 有两类操作:
- MainPipe 对真正命中的 line 发
touch(set, way); - MissUnit 为新 line 请求
victim(set)。
预取流水仅做 tag lookup 不等于架构访问,不能随意 touch,否则大量未被消费的预取会污染替换状态。另一方面,refill 前 victim 选择必须和最终写入的 set/way 保持绑定,不能在等待 L2 期间被另一个请求覆盖。
9. 软件预取与硬件预取的真实限制
顶层把 LoadUnit 的软件 prefetch.i 和 FTQ 硬件预取汇入同一个 IPrefetchPipe:
- 同拍软件预取优先;
- 最多接收一个预取请求;
- 多个 LoadUnit 端口同拍发请求时按固定优先顺序选择一个,其余可能丢弃;
- 暂存的软件预取在流水阻塞时可能被新的软件预取覆盖。
这些限制不会影响程序正确性,因为 prefetch 是提示;它们会影响性能实验,所以测 prefetch.i 效果时必须同时观察“软件发出了请求”和“ICache 实际接收了请求”。
10. 异常与属性如何对齐
每个取指窗口最多涉及两条 line,但异常最终必须与同一个 FTQ 请求一起返回。主要来源包括:
| 来源 | 例子 | 何时已知 |
|---|---|---|
| 后端/FTQ | 非法高地址等已识别异常 | 请求进入 ICache 前 |
| ITLB | page fault、guest page fault、access fault | IPrefetchPipe 翻译后 |
| PMP | 物理取指权限失败 | 预取/MainPipe 权限检查 |
| PBMT/MMIO | NC/IO、非缓存地址 | 翻译属性合并后 |
| L2/总线 | Grant corrupt | refill 返回时 |
| SRAM ECC | meta/data parity | Meta 或 Data 响应后 |
实现必须遵守两个原则:
- 更早且更确定的架构异常不能被后续 miss/corrupt 覆盖;
- 有异常或 MMIO 属性的 line 不应错误分配普通 ICache MSHR。
V2R2 只向 ICache 传递部分 PC 高位以节省硬件;JALR 形成的非法高地址由后端识别后,经 FTQ 把异常重新带到 ICache/IFU 路径。这是跨模块异常契约,单看 ICache 输入宽度会误以为高位没有被检查。
11. ECC、失效与自动 refetch
默认 parity 只能检测错误,不能原位纠正。检测到错误后采用“失效 + 从 L2 重取”的策略:
| 错误 | 为什么这样失效 |
|---|---|
| Meta corrupt | 连命中 way 都不可信,因此清该 set 的所有 way valid |
| Data corrupt | tag/way 仍可信,只清命中的 way |
| TileLink corrupt | 本次 refill 数据不可交付,按错误路径上报/重试 |
MainPipe 在 S1 检查 Meta,在 S2 检查 data。自动 refetch 复用普通 miss 通路,因此要防止“错误 line 仍为 valid,新的 miss 又被旧 hit 挡住”的循环;正确顺序是先使相关 valid 失效,再让请求成为 miss。
CtrlUnit 的错误注入会接管 Meta/Data Array 端口。注入期间普通读写必须停住,否则测试写入与 refill/取指互相覆盖,得到不可解释的错误定位。
12. Flush 的精确边界
12.1 后端或 IFU redirect
错误路径上的数据即使来自正确物理地址,也不能再交给错误 FTQ identity。因此要取消:
- MainPipe 内的在途请求;
- IPrefetchPipe 对错误路径的请求;
- WayLookup 中未消费项;
- 与已取消请求关联、尚不应继续产生可见响应的 miss 状态。
12.2 BPU redirect
BPU redirect 发生更早,可以根据 redirect 来自 S2 还是 S3、以及 FTQ index,选择性冲刷 IPrefetchPipe 阶段。MetaArray 本身只是无副作用读取,不需要因为一次预测改向而清空。
12.3 fence.i
fence.i 的目标是让后续取指看见数据侧写入后的新指令:
- 停止并清理在途取指/预取;
- 清 WayLookup 和有关 MSHR;
- 清所有 Meta valid;
- 不必逐项擦 DataArray。
12.4 sfence.vma
它主要清地址翻译状态,而不是普通 ICache 内容。实现中 guest-page-fault 相关缓存与预取流水有额外耦合:若预取 S1 被冲刷,相应 gpf 状态也必须同步清除,避免后续请求永久等待陈旧翻译状态。
13. Redirect 后为什么会多一拍
稳态下 prefetch pointer 领先 fetch pointer,MainPipe 取数据时 WayLookup 已准备好。redirect 改变下一 PC 后:
cycle R: redirect 产生,新目标 T
cycle R+1: IPrefetchPipe 对 T 做 Meta/ITLB;MainPipe 也想消费 T,但 WayLookup 尚空
cycle R+2: T 的 WayLookup 信息可用,MainPipe 才启动 DataArray
实际周期会受 ready/valid 和 redirect 来源影响,但性能本质不变:redirect 清空了原有预取距离,必须重新建立“元数据领先数据消费”的间隔。所以优化 redirect 延迟不能只看 BPU 自己,还要看 FTQ→IPrefetch→WayLookup→MainPipe 的重新启动路径。
14. 关键不变量
下面这些不变量比“跑了若干随机测试”更能直接约束错误类型:
14.1 命中与选路
- 每条 line 的有效
waymask必须 one-hot 或全零; - 返回 IFU 的每个 bank 都来自选中 way 和正确 line;
- 跨行时 line0/line1 的 tag、异常、ECC 不得交换;
- 任何 Meta invalid 的 way 都不能形成 hit。
14.2 请求身份
- S0、S1、S2 的地址、FTQ index、异常、数据属于同一请求;
- flush 后旧 identity 永不产生 response;
- WayLookup dequeue 与 MainPipe fetch 顺序一致;
- refill update 只能修改相同 block address 的 WayLookup 项。
14.3 Miss/refill
- 同一 block 最多有一个有效下行 miss;
- fetch MSHR 不会被 prefetch 资源永久饿死;
- 两个 Grant beat 都到齐前不能写完整 line valid;
- source ID、victim way、set、alias bit 在整个 miss 生命周期不变;
- fence/flush 后被取消的 response 不得唤醒新路径请求。
14.4 错误与权限
- page/PMP fault 时不把 data 标成正常有效指令;
- corrupt/parity error 时不提交对应字节;
- Meta corrupt 清全 way,Data corrupt 只清命中 way;
- MMIO/NC 请求不会误走可缓存 refill。
15. 验证矩阵
15.1 命中路径
- 8 种起始 bank,覆盖单行与跨行;
- 4 个 way 分别命中;
- 当前 line hit/下一 line miss,以及反向组合;
- WayLookup bypass 与排队两种路径;
- IFU 反压下连续请求身份保持。
15.2 Miss 路径
- 单个 fetch miss;
- 两条 line 同时 miss;
- 同 line fetch/prefetch 合并;
- 4 个 fetch MSHR 占满后的反压;
- 10 个 prefetch MSHR 占满但 demand 仍可前进;
- 两个 Grant beat 间插入 flush;
- refill 写 SRAM 与新请求读端口冲突。
15.3 地址与权限
- TLB hit/miss/page fault/gpf;
- PMP execute deny;
- PBMT NC/IO 与普通 cacheable;
- 4 KiB 页边界、64 B line 边界和 alias bit 变化;
- 非法高地址异常从后端经 FTQ 回传。
15.4 维护与可靠性
- 后端 redirect、BPU S2/S3 redirect;
fence.i与 miss/refill 同拍;sfence.vma与 gpf 状态;- Meta/Data parity 注入;
- TileLink corrupt;
- reset 后第一条请求和 valid array 初态。
16. 调试一个“IFU 没拿到指令”的固定流程
按下列顺序可以避免在错误层次浪费时间:
- 请求是否存在:FTQ fetch request 是否 valid,是否被 redirect 杀掉?
- 预取凭据是否存在:对应 WayLookup entry 是否已经 enqueue?
- 翻译是否合法:ITLB/PMP/PBMT 是否允许缓存取指?
- S0 为何没 fire:WayLookup、DataArray port、S1 ready 哪个条件缺失?
- 两条 line 各自状态:分别是 hit、miss、exception 还是不需要?
- miss 是否真的分配:是 duplicate merge、无空 MSHR,还是已发 Acquire?
- refill 是否完整:两个 Grant beat、source ID、SRAM write 是否正确?
- S2 为何不响应:ECC/corrupt、IFU backpressure、flush 或 identity mismatch?
只看最后的 resp.valid=0 无法区分“没有预测请求”“翻译未完成”“Cache miss”“响应被 flush”这四类完全不同的问题。
17. 源码导航
| 文件 | 建议关注内容 |
|---|---|
ICache.scala | ICacheParameters、Array 实现、顶层模块连接、软件/硬件预取仲裁 |
IPrefetch.scala | Meta/ITLB 并行、tag compare、异常与 WayLookup 写入 |
WayLookup.scala | FIFO、bypass、flush、MSHR refill update |
ICacheMainPipe.scala | S0/S1/S2、bank 拼接、ECC、PMP、miss 等待 |
ICacheMissUnit.scala | MSHR 分配、重复检测、Acquire/Grant、refill 写回 |
ICacheCtrlUnit.scala | ECC enable、错误注入和 Array 端口接管 |
ICacheBundle.scala | Meta/Data read-write bundle 与跨模块契约 |
进一步对照官方 V2R2 文档:ICache、MainPipe、MissUnit 和 WayLookup。
18. 设计题
- 若把 WayLookup 深度从 32 降到 8,哪些 workload 最先退化?应看哪些计数器证明是容量不足而不是 ICache miss?
- 若 L2 改为允许不同 source 的 Grant beat 交错,MissUnit 的共享 refill buffer 要如何改?
- 若把 line 从 64 B 改为 128 B,但仍保持 34 B 取指窗口,Meta/Data bank、MSHR 和 L2 beat 计数分别怎样变化?
- 为什么 prefetch hit 不应和真正 MainPipe hit 一样无条件更新 PLRU?
- 如何写一个断言,证明 redirect 之前的 FTQ entry 永远不会在 redirect 之后产生 IFU response?