先对齐一下"缓存解码块"指什么
香山硬件里没有一个叫"缓存解码块"的独立模块,这个说法实际对应的是 IFU(取指单元)里的一条两级链路:
预译码(PreDecode)+ 指令缓冲(IBuffer)
单位是一个"预测块 / 取指块 "(fetch block / prediction block):昆明湖下最大 32 字节(跨块时最多 34 字节),最多 16 条指令 。
顺带澄清一个常见误解:这个不是 x86 的 decoded ICache / uop cache(DSB、LSD 那种把微码缓存起来跳过译码的东西)。RISC-V 是定长 4B + 压缩 2B 的混合,译码本身很便宜,没必要缓存微码。香山缓存的是"指令码 + 预译码出来的少量元信息",真正的完整译码仍在后端的 Decode 级做。
一、完整数据通路(一个块从 BPU 到 IBuffer 要过几关)
BPU 预测 → FTQ 暂存预测块
↓ (FTQ 同时发给 ICache 和 IFU)
IFU F0:收 FTQ 请求、拉 ready、接收重定向
IFU F1:算块内每个 2B 的 PC、half_pc、cut_ptr(后面切指令码的依据)
IFU F2:等 ICache 返回数据 → 按 cut_ptr 切分 → 4 个 PreDecode 并行预译码
→ 同时把指令码按 4B 组合
IFU F3:16 个 RVCExpander 把 RVC 扩成 32 位
→ PredChecker 做前端预测错误早检查
→ 写 IBuffer(缓存!)+ 写回 FTQ(训练预测器)
F2 级:预译码到底译出什么
PreDecode 接收切分后的 17 个 2 字节初始指令码,查译码表产出 :
| 输出信息 | 用途 |
|---|---|
| 是否是一条有效指令的起始(validStart/validEnd) | RVC/RVI 混编必须先把指令边界切出来,否则后面全是错的 |
| 是否 RVC(16 位压缩指令) | 决定要不要送 RVCExpander 扩展 |
| 是否 CFI(控制流指令) | 分支预测校验的输入 |
| brType(CFI 类型) | 00 非 CFI / 01 branch / 10 jal / 11 jalr |
| CFI 的目标地址计算偏移 | PredChecker 拿它算真实跳转目标,跟预测值比对 |
因为要在一个周期内处理整个块,香山实际例化了 4 个 PreDecode 模块并行 工作(ICache 的两个端口各返回 hit/miss 两路数据,凑出 4 种组合同时预译码)。另外 validStart 的处理被拆成前后两半(0→PredictWidth/2、PredictWidth/2→PredictWidth)分别计算------纯为时序收敛 。
F3 级:RVC 扩展 + 前端早纠错
- RVCExpander ×16:RVC 按手册规则扩成 32 位 RVI,RVI 保持原码。这一步之后,后端看到的每条指令都是规整的 32 位。
- PredChecker :拿预译码信息做前端就能发现的 预测错误检查------jal 类错误、ret 错误、"预测成 CFI 但实际不是"、无效指令预测错误,同时算 16 个目标地址跟预测目标比对 。
- 这类错误在前端就地拦掉并冲刷 IFU 流水线,代价远小于放进去让后端执行完再回滚。
- 检查后会重新生成指令有效范围向量 fixedRange(基于 jump_range / ftr_range 收窄到第一条未检出的 jal/ret)。
二、IBuffer:真正的"缓存"那一侧
IFU 把最终指令码 + PC + 预译码信息 + 异常信息 + FTQ 指针打包(FetchToIBuffer bundle)写进 IBuffer ,后端再按 DecodeWidth(V2 六宽 / V3 八宽)从里面取指令进译码级 。
它解决的三个问题:
- 解耦前端供指与后端译码:后端因为长延迟 load、ROB 满、IQ 满而卡住时,前端可以接着取指往里灌;反过来 ICache miss 时后端还能消化存粮。这就是香山"解耦前端(FDIP)"架构的核心价值之一。
- 吸收取指抖动:ICache miss、跨行、跨块这些会让取指断断续续,IBuffer 把这些气泡抹平,让后端看到的是连续指令流。
- 反压与重定向锚点:IBuffer 满了会反压 IFU/ICache;发生重定向时整个 IBuffer 被 flush。
实现上还有一条 bypass 路径------IFU 的输出可以不落队列直接送译码,避免 IBuffer 空转时无谓多等一拍(这块逻辑在 2024 年后做过专门的时序优化,依赖"IFU→IBuffer 预对齐"约定来选 bypass 项)。
对应的 topdown / 性能事件(看前端瓶颈很有用):
| 事件 | 含义 |
|---|---|
IBUFFER.ibuffer_empty / ibuffer_full |
后端饿死 / 前端堵死,Frontend/Backend Bound 的直接判据 |
IBUFFER.ibuffer_12/24/36_valid |
IBuffer 水位刻度(容量在数十项量级) |
IBUFFER.ibuffer_flush |
重定向导致的冲刷频率 |
IFU.to_ibuffer_package_num |
实际送进 IBuffer 的包数 |
IFU.to_ibuffer_cache_miss_num |
因 ICache miss 而断流的次数 |
IFU.frontendFlush / predecodeRedirect |
预译码查出的预测错误引发的冲刷 |
三、几个容易踩的边界情况
| 情况 | 香山的处理 |
|---|---|
| 跨 cacheline | 历史上 ICache 用双端口取两行;2024 年 6 月后改低功耗设计,ICache 内部完成选数与拼接,由 port0 一次性返回最多 48B(16B bank 下够覆盖 34B 块),IFU 不再管拼接 |
| 跨预测块的 RVI 指令 | 半条 4B 指令落在不同块里,由 IFU_CROSS_BLOCK 专门处理 |
| MMIO / uncache 取指 | 走 InstrUncache,退化成一条一条顺序执行,IFU 停顿等 ROB 的 commit 信号才能取下一条 |
**ICache meta 里的 maybeRvc** |
只是"这一行可能含压缩指令"的粗粒度提示 ,不是解码结果,别当成解码缓存 |
四、回到 XS-GEM5:为什么脚本里找不到它
承接你上一轮的问题------gem5 侧没有独立的 IBuffer / PreDecode 硬件模型 。这条链(切分、预译码、RVC 扩展、PredChecker 前端早纠错)在 XS-GEM5 里被 fetch 级整体抽象掉了,既不建模逐块预译码的时序,也不建模 IBuffer 的队列行为。所以 kmhv3.py / kmhv2.py 里你只能看到 fetchWidth、fetchQueueSize、decodeWidth 这类粗粒度参数,找不到任何 IBuffer 相关项。
这带来一个实际影响:IBuffer 相关的前端气泡在 gem5 里是不可见的 。如果仿真里 ibuffer_empty 那类瓶颈很重(真实 RTL 上很常见),gem5 的 Frontend Bound 归因会偏乐观。你在 gem5 上做前端优化评估时,心里要有这个数。
一句话总结:"缓存解码块"= IFU 里"预译码出指令边界与分支信息 → RVC 扩展成规整 32 位 → 前端就地纠错 → 整块塞进 IBuffer 等后端来取"这条流水线,它的存在意义是用一小部分译码工作量换取"指令边界可见 + 分支错误早发现 + 前后端速度解耦",是香山解耦前端能跑宽的关键前提。