ICache、ITLB 与持续供给
分支预测器解决“下一拍应该从哪里取”,ICache 解决“那个地址的指令字节何时可靠地交给 IFU”。在昆明湖 V2R2 中,ICache 不是一个收到 PC 后简单读 SRAM 的模块,而是一套把预测预取、地址翻译、权限检查、跨行取数、缺失处理、错误检测和流水撤销对齐起来的前端供给系统。
本页先建立完整数据流;实现级细节见 ICache 深入:VIPT、流水与 Miss。
1. ICache 在前端中的位置
预测地址/FTQ 项
┌────────────────────────┐
│ BPU + FTQ │
└───────┬────────┬───────┘
│ │
预取地址 │ │ 真正取指请求
v v
┌───────────┐ ┌───────────┐
│IPrefetch │ │ MainPipe │
│Pipe │ │ │
└─────┬─────┘ └─────┬─────┘
│ │
ITLB/PMP + Meta │ Data + PMP/ECC
│ │
v │
┌───────────┐ │
│ WayLookup │────────┘
└─────┬─────┘
│ miss/prefetch miss
v
┌───────────┐ TileLink ┌──────┐
│ MissUnit │<──────────────────>│ L2 │
└─────┬─────┘ └──────┘
│ refill Meta/Data Array
v
┌───────────┐
│ Meta/Data │──────命中数据──────> IFU ──> IBuffer
│ Array │
└───────────┘
这里最容易混淆的是两条请求流:
- IPrefetchPipe 接收 FTQ 提前给出的硬件预取地址,也接收后端
prefetch.i软件预取。它查询 ITLB 和 MetaArray,提前算出 way hit、异常和 ECC 信息,并写入 WayLookup。 - MainPipe 接收 FTQ 已经决定要消费的真正取指请求。它从 WayLookup 取得前面准备好的元数据,只读命中的 DataArray way,最后把指令块、异常和请求身份一起交给 IFU。
也就是说,正常稳态下并不是 MainPipe 临时做一次完整 tag lookup;预取流水跑在真正取指流水前面,用 WayLookup 把“地址翻译和选路结果”传给 MainPipe。
2. 默认组织参数
V2R2 源码 ICacheParameters 的默认值如下:
| 项目 | 默认值 | 含义 |
|---|---|---|
| Sets | 256 | 每路有 256 个集合 |
| Ways | 4 | 四路组相联 |
| Cache line | 64 B | refill 和 tag 管理的最小单位 |
| 总容量 | 64 KiB | 256 × 4 × 64 B |
| Data banks | 8 | 每行分成 8 个 8 B bank |
| Meta ports/banks | 2 | 偶、奇 line 可并行查 tag,支持跨行取指 |
| Fetch MSHR | 4 | 服务真正取指 miss |
| Prefetch MSHR | 10 | 服务硬件/软件预取 miss |
| WayLookup entries | 32 | 暂存预取流水产生的翻译、命中与异常信息 |
| 默认替换策略 | set-PLRU | MainPipe 命中时 touch,MissUnit 申请 victim |
| 默认校验码 | parity | 检错而不纠错;配置上也可选择更强编码 |
这些是当前 V2R2 默认配置,不应被理解为 ICache 微架构的不可变常数。
3. 一次命中请求如何完成
3.1 预取流水先行
当 FTQ 的 prefetch pointer 领先于 fetch pointer 时,IPrefetchPipe 先处理将来可能真正取用的地址:
- 用虚拟地址索引 MetaArray;
- 同时访问 ITLB,得到物理 tag、页异常与 PBMT 属性;
- 比较各 way 的 tag,形成最多两条 Cache line 的
waymask; - 进行预取侧 PMP 检查和 Meta ECC 检查;
- 把虚拟地址、物理 tag、waymask、异常等写入 WayLookup;
- 若 tag miss 且请求可缓存,则向 MissUnit 发出 prefetch miss。
预取流水的关键产物不是指令数据,而是 MainPipe 稍后要消费的访问凭据。
3.2 MainPipe 消费真正请求
真正取指到达时,MainPipe 按以下顺序工作:
- S0 发射:取 FTQ 请求,并读取与之对应的 WayLookup 项;根据 waymask 只选择命中的 DataArray way;
- S1 对齐:收到 data bank,完成 PMP 检查、替换状态 touch,并监听同地址 MSHR 的返回;
- S2 决策:检查 data/meta parity、TileLink corrupt、异常和 miss 状态;命中则组装取指块返回 IFU,miss 则向 MissUnit 请求并等待 refill。
源码中能看到 S0/S1/S2 三组名称;官方文档称其为“两级流水”,是把 S0 看作请求发射,把 S1、S2 看作两级寄存处理。分析波形时应直接按信号上的 s0_* / s1_* / s2_* 区分,不要只凭“二级/三级”这个叫法推断延迟。
4. 为什么一次取指可能读两条 Cache line
IFU 每次需要的不只是 PC 所在的一个 8 B bank,而是从任意起始位置取得一段连续字节。V2R2 的一个 64 B line 被切成 8 个 8 B bank;一次取指窗口访问连续 5 个 bank(共覆盖 40 B,供后续截取有效的 34 B 范围)。
line N: bank0 bank1 bank2 bank3 bank4 bank5 bank6 bank7
line N+1: bank0 bank1 bank2 bank3 bank4 bank5 bank6 bank7
起点 bank2: [ b2 | b3 | b4 | b5 | b6 ] 单行
起点 bank6: [ b6 | b7 | b0 | b1 | b2 ]
跨到下一行
因此:
- 起始 bank 为 0~3 时,窗口不跨 64 B 边界;
- 起始 bank 为 4~7 时,需要同时处理当前 line 和下一 line;
- MetaArray 分成奇偶两组,使相邻两 line 的 tag 可以同时读取;
- MainPipe 分别判断两条 line 是否命中、异常或需要 miss,再按起始偏移拼成 IFU 所需字节。
“跨行”是 ICache 自身的访问问题;“一条 32 位指令跨取指块边界”则由 IFU 的预译码/拼接逻辑继续处理,两者不要混为一谈。
5. WayLookup 为什么存在
如果没有 WayLookup,真正取指到来后必须重新等待 ITLB、tag 比较,再启动 DataArray;这会把地址翻译和 tag lookup 延迟直接放到供给关键路径上。
WayLookup 是预取流水和 MainPipe 之间的 FIFO 契约,主要保存:
- 对应 FTQ 请求的虚拟地址和物理 tag;
- 两条候选 line 的命中 waymask;
- ITLB/PMP/PBMT/后端携带的异常属性;
- Meta ECC 信息以及跨行相关状态。
当 MissUnit 完成 refill 时,WayLookup 会监听写回并更新尚未被 MainPipe 消费的项。这样一个“预取时 miss、真正消费前已 refill”的请求可以直接成为 hit。
如果 redirect 后预取指针来不及领先,IPrefetchPipe 和 MainPipe 可能在同一地址重新汇合;此时 MainPipe 必须等待新的 WayLookup 项,所以 redirect 后首个正确取指通常会多付出一拍元数据准备代价。
6. Miss、MSHR 与 refill
MainPipe 或 IPrefetchPipe 判定 miss 后,不会自己直接访问 L2,而是交给 MissUnit:
miss request
│
├─ 地址已在某个 MSHR 中 ──> 合并/丢弃重复下行请求
│
└─ 新 line ──> 分配 MSHR ──> TileLink Acquire
│
两个 32 B beat 返回
│
写 DataArray + MetaArray
│
更新 WayLookup / 唤醒 MainPipe
V2R2 将 MSHR 分成两组:
- 4 个 fetch MSHR,只服务真正取指,优先级更高;
- 10 个 prefetch MSHR,只服务预取,避免预取完全挤占需求请求资源。
L2 侧数据通路为 32 B,而 Cache line 为 64 B,所以一个 refill 由两个 beat 构成。当前设计不交错不同 line 的返回 beat,所有 MSHR 可以共享一组 refill data register;source ID 用来找到最终应释放的 MSHR。
7. ITLB、PMP、PBMT 与 MMIO
一次访问是否能作为普通 ICache hit 返回,不只由 tag 决定:
- ITLB 给出物理地址,并可能产生 instruction page fault、guest page fault 等异常;
- PMP 检查物理地址的取指权限;
- PBMT 可把页面标成不可缓存或 I/O 属性;
- MMIO/不可缓存取指 不走普通 ICache refill,由 IFU 的非缓存取指路径处理;
- 后端对非法高地址等情况形成的异常,也会随 FTQ 请求带到 ICache。
异常优先级按“已经确定的上游异常优先”组织,整体可理解为:后端携带异常优先于 ITLB,ITLB 优先于 PMP,再之后才考虑 L2 返回的 corrupt。mmio、PBMT 的 NC/IO 属性是路由与访问类型,不应直接当成一种架构异常。
8. 预取请求从哪里来
V2R2 有两种 ICache 预取来源:
| 来源 | 产生者 | 目的 |
|---|---|---|
| 硬件预取 | FTQ/FDIP | 让地址翻译、tag lookup 和 refill 跑在真正 fetch 之前 |
| 软件预取 | LoadUnit 执行 prefetch.i | 由软件提示未来会执行的代码地址 |
IPrefetchPipe 每拍只接收一个请求,软件预取优先。软件预取只负责检查目标是否 miss/异常并尝试填充,不改变控制流;多个同拍软件请求不会全部排队,已有待处理请求在特定阻塞条件下也可能被后来的请求覆盖,因此它是性能提示而不是功能正确性依赖。
9. Flush 与 fence.i
不同 flush 的语义并不相同:
| 事件 | 必须清理的状态 | 原因 |
|---|---|---|
| 后端/IFU redirect | MainPipe、IPrefetchPipe、WayLookup、相关 MSHR 状态 | 旧路径上的请求身份已经失效 |
| BPU redirect | 与 redirect 阶段和 FTQ index 对应的预取流水项 | 较早、较精细地取消错误预测路径 |
fence.i | 流水、WayLookup、MSHR、Meta valid | 保证后续取指不再看到旧指令内容 |
sfence.vma | ITLB 相关状态 | 地址翻译失效;通常不等于清空 ICache 数据 |
fence.i 不要求把 DataArray 的每个 bit 真正写零。只要 Meta valid 被清除,旧 data 就不可命中;随后按新内容 refill 即可。这是“逻辑失效”和“物理擦除”的区别。
10. ECC/Parity 错误如何处理
默认配置对 Meta 和每个 data bank使用 parity。一个 line 包含 1 份 Meta 校验和 8 份 data-bank 校验:
- Meta 错误可能使命中 way 本身不可信,因此会失效该 set 的所有 way;
- Data 错误只失效已经选中的命中 way;
- 错误会上报 Bus Error Unit;
- ICache 是只读副本,失效后可以复用普通 miss/refill 路径从下层重新取回正确内容。
因此这里的 parity 并非“报错后继续把坏指令交给 IFU”,也不必像脏 DCache 那样担心丢失唯一数据副本。
11. 性能分析:前端为什么会断粮
看到 IFU 没有 valid 时,应先按资源层次分类:
| 类别 | 典型迹象 | 首要检查点 |
|---|---|---|
| 预测供给不足 | FTQ 没有可发请求 | BPU redirect、FTQ enqueue/dequeue |
| 预取未领先 | MainPipe 等 WayLookup | prefetchPtr 与 fetchPtr 距离、WayLookup valid |
| ITLB miss | Meta 可能已读,但翻译未返回 | ITLB req/resp、miss queue |
| Data/Meta 端口冲突 | S0 ready 拉低 | refill 写 SRAM、错误注入占端口 |
| ICache miss | MSHR 分配并发出 Acquire | fetch MSHR 占用、重复 line、L2 延迟 |
| 跨行半命中 | 两条 line 中仅一条 miss | line0/line1 waymask 与 MSHR 请求顺序 |
| IFU 反压 | ICache 已有响应但不能提交 | resp.ready、IBuffer 空间 |
| flush | 流水 valid 被取消 | redirect 类型、FTQ index、fence.i |
稳态吞吐高不代表 redirect 延迟低:双流水可以把大部分 tag/翻译延迟藏在预取距离里,但 redirect 会缩短甚至清空这段距离。
12. 推荐的波形观察顺序
不要一上来盯 DataArray。按请求身份从上游一路追:
- FTQ 给 IPrefetchPipe 和 MainPipe 的地址、FTQ index、valid/ready;
- ITLB 请求/响应、物理 tag、page fault/PBMT;
- Meta 响应与两条 line 的 waymask;
- WayLookup enqueue、dequeue、flush、refill update;
- MainPipe 的
s0/s1/s2valid、stall 和 line0/line1 状态; - MissUnit 的 fetch/prefetch MSHR 分配、Acquire、Grant beat、SRAM write;
- Data/Meta ECC、corrupt、异常优先级;
- 最终送给 IFU 的数据、起始偏移、异常和请求身份。
若地址对了但 FTQ identity 错了,数据内容即使碰巧正确也不能算一次正确取指。
13. 阅读源码的最短路径
建议按数据流,而不是文件名排序阅读:
frontend/icache/ICache.scala:参数、Meta/Data Array 和顶层连线;frontend/icache/IPrefetch.scala:ITLB/Meta 查询与 WayLookup 写入;frontend/icache/WayLookup.scala:预取结果如何排队和被 refill 更新;frontend/icache/ICacheMainPipe.scala:真正取指的数据访问和最终判定;frontend/icache/ICacheMissUnit.scala:MSHR、TileLink 与 refill;frontend/icache/ICacheCtrlUnit.scala:ECC 控制和错误注入。
官方设计文档可对照阅读:ICache 总览、MainPipe、MissUnit 和 WayLookup。
14. 自测题
- 为什么 WayLookup 存的是 waymask 和异常等元数据,而不是完整指令数据?
- 起始地址落在 bank6 时,需要哪五个 bank?为什么 MetaArray 需要同时处理两个 line?
- 一条请求“ITLB hit、tag hit、PMP fault”时能否把 DataArray 内容交给 IFU?
- 为什么
fence.i清 Meta valid 就足以让旧 DataArray 内容不可见? - 当一个 prefetch miss 与后来的真实 fetch 指向同一 line 时,哪些机制避免重复访问 L2?