跳到主要内容

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
}

它需要满足四个核心不变量:

  1. 顺序一致:MainPipe 消费的 entry 必须对应同一个 FTQ 请求;
  2. flush 一致:错误路径的 entry 不能在 redirect 后被消费;
  3. refill 可见:entry 排队期间若目标 line refill,waymask 必须得到更新;
  4. 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。

这里有三个容易犯的实现错误:

  1. 只看 64 B line 内偏移,忘记 start≥4 时必须查询下一 line 的 tag;
  2. bank 拼接顺序正确,但异常/waymask 的 line 编号对反;
  3. 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 MSHR4MainPipe总体优先于 prefetch;低 index 优先
Prefetch MSHR10IPrefetchPipeFIFO 次序

需求取指与预取使用不同池,既保护 demand miss,也允许预取保持一定并发。

7.2 重复请求​

新请求到来时,MissUnit 比较所有有效 MSHR 的 block address:

  • 已有同 line 请求:不再发第二个 Acquire;接口仍可表现为该请求被接受;
  • 无同 line 且有空项:分配 MSHR;
  • 无空项:反压来源流水。

“接口 fire”不等于“一定新分配了 MSHR”,调试计数器时要区分 request accepted、duplicate merged 和 new allocate。

64 B line 经 32 B TileLink data path 分两个 beat 返回。当前实现假设一个 line 的两个 beat 不与另一 line 交错,因此全部 MSHR 共享一组 refill data register:

  1. Acquire 的 source ID 编码 MSHR;
  2. 第一个 Grant beat 写入半行;
  3. 第二个 beat 补齐;
  4. 完整 line 写 DataArray,tag/valid 写 MetaArray;
  5. WayLookup 更新可能仍在排队的请求;
  6. 最后一个 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 前
ITLBpage fault、guest page fault、access faultIPrefetchPipe 翻译后
PMP物理取指权限失败预取/MainPipe 权限检查
PBMT/MMIONC/IO、非缓存地址翻译属性合并后
L2/总线Grant corruptrefill 返回时
SRAM ECCmeta/data parityMeta 或 Data 响应后

实现必须遵守两个原则:

  1. 更早且更确定的架构异常不能被后续 miss/corrupt 覆盖;
  2. 有异常或 MMIO 属性的 line 不应错误分配普通 ICache MSHR。

V2R2 只向 ICache 传递部分 PC 高位以节省硬件;JALR 形成的非法高地址由后端识别后,经 FTQ 把异常重新带到 ICache/IFU 路径。这是跨模块异常契约,单看 ICache 输入宽度会误以为高位没有被检查。

11. ECC、失效与自动 refetch​

默认 parity 只能检测错误,不能原位纠正。检测到错误后采用“失效 + 从 L2 重取”的策略:

错误为什么这样失效
Meta corrupt连命中 way 都不可信,因此清该 set 的所有 way valid
Data corrupttag/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 没拿到指令”的固定流程​

按下列顺序可以避免在错误层次浪费时间:

  1. 请求是否存在:FTQ fetch request 是否 valid,是否被 redirect 杀掉?
  2. 预取凭据是否存在:对应 WayLookup entry 是否已经 enqueue?
  3. 翻译是否合法:ITLB/PMP/PBMT 是否允许缓存取指?
  4. S0 为何没 fire:WayLookup、DataArray port、S1 ready 哪个条件缺失?
  5. 两条 line 各自状态:分别是 hit、miss、exception 还是不需要?
  6. miss 是否真的分配:是 duplicate merge、无空 MSHR,还是已发 Acquire?
  7. refill 是否完整:两个 Grant beat、source ID、SRAM write 是否正确?
  8. S2 为何不响应:ECC/corrupt、IFU backpressure、flush 或 identity mismatch?

只看最后的 resp.valid=0 无法区分“没有预测请求”“翻译未完成”“Cache miss”“响应被 flush”这四类完全不同的问题。

17. 源码导航​

文件建议关注内容
ICache.scalaICacheParameters、Array 实现、顶层模块连接、软件/硬件预取仲裁
IPrefetch.scalaMeta/ITLB 并行、tag compare、异常与 WayLookup 写入
WayLookup.scalaFIFO、bypass、flush、MSHR refill update
ICacheMainPipe.scalaS0/S1/S2、bank 拼接、ECC、PMP、miss 等待
ICacheMissUnit.scalaMSHR 分配、重复检测、Acquire/Grant、refill 写回
ICacheCtrlUnit.scalaECC enable、错误注入和 Array 端口接管
ICacheBundle.scalaMeta/Data read-write bundle 与跨模块契约

进一步对照官方 V2R2 文档:ICache、MainPipe、MissUnit 和 WayLookup。

18. 设计题​

  1. 若把 WayLookup 深度从 32 降到 8,哪些 workload 最先退化?应看哪些计数器证明是容量不足而不是 ICache miss?
  2. 若 L2 改为允许不同 source 的 Grant beat 交错,MissUnit 的共享 refill buffer 要如何改?
  3. 若把 line 从 64 B 改为 128 B,但仍保持 34 B 取指窗口,Meta/Data bank、MSHR 和 L2 beat 计数分别怎样变化?
  4. 为什么 prefetch hit 不应和真正 MainPipe hit 一样无条件更新 PLRU?
  5. 如何写一个断言,证明 redirect 之前的 FTQ entry 永远不会在 redirect 之后产生 IFU response?