作者:翁贞华
代码版本:Kunminghu V3
适用对象:芯片验证工程师、CPU 架构学习者

1. 概述

1.1 IFU 在香山流水线中的位置

IFU(Instruction Fetch Unit,取指单元)是香山处理器前端流水线的最后一环,位于 BPU 分支预测和后端 Decode 之间:

1
BPU → FTQ → ICache → IFU → IBuffer → Backend (Decode)

香山的前端取指采用双发射(FetchPorts=2),每个周期最多可以从两个 fetch block 中取出 32 条指令(每块 16 条,每条 2 字节)。

1.2 IFU 的核心功能

IFU 负责以下工作:

  1. 接收 FTQ 的取指请求,解析 fetch block 信息
  2. 向 ICache 发起取指请求,获取 64B 的指令数据
  3. 指令定界与边界检测,识别 RVC(压缩指令)和 RVI(普通指令)的边界
  4. 跨块指令处理,处理跨越两个 fetch block 的半条指令
  5. 分支预测验证,通过 PredChecker 检验 BPU 预测是否正确
  6. RVC 展开,将 16-bit RVC 指令扩展为 32-bit
  7. 指令入 IBuffer,将处理好的指令发送给后端

2. IFU 接口详解

IFU 位于 src/main/scala/xiangshan/frontend/ifu/Ifu.scala,继承自 IfuModule,主要接口定义在 IfuBundle 中。

2.1 完整 IO 接口

1
2
3
4
5
6
7
8
9
10
11
12
class IfuIO(implicit p: Parameters) extends IfuBundle {
// ========== FTQ 交互 ==========
val fromFtq: FtqToIfuIO = Flipped(new FtqToIfuIO) // 输入:取指请求、重定向、flush
val toFtq: IfuToFtqIO = new IfuToFtqIO // 输出:写回、重定向请求

// ========== ICache 交互 ==========
val fromICache: ICacheToIfuIO = Flipped(new ICacheToIfuIO) // 输入:指令数据响应
val toICache: IfuToICacheIO = new IfuToICacheIO // 输出:取指请求、stall

// ========== IBuffer 交互 ==========
val toIBuffer: DecoupledIO[FetchToIBuffer] // 输出:指令入队
}

2.2 与 FTQ 的接口详解

输入(fromFtq):

信号 类型 说明
req.valid Bool 取指请求有效
req.bits.fetch[i] FtqFetchRequest 第 i 个 fetch block 的请求(i=0,1)
redirect.valid Bool 后端重定向信号
flushFromBpu BpuFlushInfo BPU 冲刷信号

输出(toFtq):

信号 类型 说明
req.ready Bool IFU 可以接收新的取指请求
wbRedirect.valid Bool IFU 产生的重定向(预测错误)
resp FtqIfuWbBundle 写回信息(taken、cfiPosition、target 等)

2.3 与 ICache 的接口详解

输出(toICache):

信号 类型 说明
req.valid Bool 取指请求有效
req.bits.addr UInt(PAddrBits.W) 取指的物理地址
stall Bool 反压信号,告诉 ICache 不要发送新的 resp

输入(fromICache):

信号 类型 说明
fetchResp.valid Bool ICache 返回的响应有效
fetchResp.bits.data Vec(FetchBlockSize, UInt(16.W)) 128 个 half-word(256 字节,实际用 64B)
fetchResp.bits.maybeRvcMap Vec(FetchBlockInstNum, Bool) 哪些位置可能是 RVC

3. IFU 子模块详解

3.1 PreDecode(预译码)

文件: PreDecode.scala

功能: 在 IFU 流水线的 S2 阶段,PreDecode 模块对每条指令进行基础的预译码:

  1. 检测分支/jump 指令:识别 jal、jalr、条件分支等
  2. 计算 jump offset:根据指令类型(jal/r/jalr)计算目标偏移
  3. 指令类型分类:区分 RVC 和 RVI 指令
1
2
3
4
5
6
7
8
9
10
11
12
13
class PreDecode extends Module {
val io = IO(new Bundle {
val req = Input(new PreDecodeReq)
val resp = Output(new PreDecodeResp)
})

for (i <- 0 until FetchBlockInstNum) {
val halfInstr = io.req.rawInstr(i)
val isRvc = halfInstr(1, 0) =/= "b11".U // RVC 识别
io.resp.isJal(i) := isJal
io.resp.isBranch(i) := isBranch
}
}

3.2 InstrBoundary(指令边界检测)

文件: InstrBoundary.scala

功能: 这是 IFU 中最关键的组合逻辑之一。香山的 fetch block 大小是 64 字节,其中可能包含 RVC(16-bit)和 RVI(32-bit)混合的指令。InstrBoundary 的任务是:

  1. 识别每条指令的边界(instrEndVec)
  2. 判断哪些位置是有效指令(instrValid)
  3. 识别 RVC 指令(isRvc)
  4. 处理半条 RVI 指令:当 fetch block 边界落在一条 RVI 指令中间时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class InstrBoundary extends Module {
val io = IO(new Bundle {
val req = Input(new InstrBoundaryReq)
val resp = Output(new InstrBoundaryResp)
})

for (i <- 0 until FetchBlockInstNum) {
currentInstrIsRvc := maybeRvc(i) && (instrRange(i) || (i == 0 && prevLastIsHalfRvi))
currentInstrEnd := Mux(currentInstrIsRvc, i * 2 + 2, i * 2 + 4)
instrValid(i) := instrRange(i) && !跨块半RVI
instrEndVec(i) := currentInstrEnd
isRvc(i) := currentInstrIsRvc
}
}

3.3 PredChecker(预测检查器)

文件: PredChecker.scala

功能: 验证 BPU 的预测是否正确。这是香山前端实现精确异常的关键模块。

PredChecker 采用两级流水线:

Stage 1(S2 阶段):

检测以下错误类型:

  • jalFault:jal 指令预测错误
  • retFault:ret 指令预测错误
  • notCFIFault:预测跳转到某个位置但实际不是 CFI
  • invalidTakenFault:不该跳转但 BPU 预测了跳转

Stage 2(S3 阶段,写回阶段):

检测目标地址错误:

  • targetFault:跳转的目标地址与 BPU 预测不一致
1
2
3
4
5
val checkerRedirect = Wire(new Redirect)
val targetFault = targetAddr =/= bpuPredictedTarget

toFtq.resp.takeRedirect := checkerRedirect.valid
toFtq.resp.target := ...

3.4 RvcExpander × 8(8个 RVC 展开器)

文件: RvcExpander.scala

功能: 将 16-bit RVC 指令展开为 32-bit。香山每周期最多处理 8 条 RVC 展开。

1
2
3
4
5
6
7
private val rvcExpanders = Seq.fill(IBufferEnqueueWidth)(Module(new RvcExpander))

for (i <- 0 until IBufferEnqueueWidth) {
rvcExpanders(i).io.in := s3_preDecode_out.instr(i)
rvcExpanders(i).io.expand := s3_preDecode_out.instrValid(i) && s3_preDecode_out.isRvc(i)
s3_expandedInstr(i) := rvcExpanders(i).io.out
}

RVC 展开规则(部分):

  • c.addiaddi
  • c.jjal x0, offset
  • c.jaljal x1, offset
  • c.ldld
  • c.sdsd
  • …共 44 条 RVC 指令

3.5 IfuUncacheUnit(MMIO 取指单元)

文件: IfuUncacheUnit.scala

功能: 处理 MMIO(Memory-Mapped I/O)区域的取指。MMIO 地址不能缓存,需要通过特殊的 uncache 路径访问。

状态机:

1
IDLE ──▶ REQ_SENT ──▶ WAIT_COMMIT ──▶ DONE
  1. IDLE:等待 uncache 请求
  2. REQ_SENT:发送 MMIO 请求到 InstrUncache,等待响应
  3. WAIT_COMMIT:等待该 MMIO 指令 commit(保证精确异常)
  4. DONE:指令已提交,清除状态

关键设计点:

  • uncache 指令必须等待 commit 后才能发送下一个 uncache 请求(保证异常顺序)
  • uncache 区域没有缓存,所以每次都要重新请求

4. IFU 4 级流水线详解

IFU 采用 4 级流水线设计(S0/S1/S2/S3),每级有 firereadyflush 信号控制流动。

流水线概览图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
          ┌─────────────────────────────────────────────────────┐
│ S0 │
│ 接收 FTQ 请求 → 解析 fetch block → 发送 ICache req │
└──────────────────────────┬──────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ S1 │
│ 接收 ICache resp → ValidHold → 调用 InstrBoundary │
└──────────────────────────┬──────────────────────────┘

┌────────────────────────────────────┴────────────────────────────┐
│ S2 │
│ PreDecode + InstrCompact + PredChecker Stage1 + Align │
│ 处理跨块半 RVI + prevLastIsHalfRvi 寄存器更新 │
└──────────────────────────┬───────────────────────────────────┘

┌───────────────────────────┴──────────────────────────────┐
│ S3 │
│ MMIO 处理 (IfuUncacheUnit) + RVC Expand × 8 │
│ PredChecker Stage2 (targetFault 检测) │
│ 写回到 FTQ (wbRedirect) + IBuffer 入队 │
└───────────────────────────────────────────────────────────┘

S0: 发起取指请求

主要工作:

  1. fromFtq.req 接收取指请求
  2. 生成两个 fetch block 的 FetchBlockInfo
  3. 检测 crossCacheline
  4. 向 ICache 发送请求
1
2
io.toICache.req.valid := s0_fire && !s0_flush
fromFtq.req.ready := s1_ready && io.fromICache.fetchReady

S1: 接收 ICache 响应

主要工作:

  1. 接收 ICache 的 fetchResp
  2. ValidHold 保持 S0 的有效信号
  3. 调用 InstrBoundary 计算指令边界
  4. 更新 prevLastIsHalfRvi 寄存器

S2: 预译码和 RVC 展开准备

主要工作:

  1. PreDecode 进行指令预译码
  2. InstrCompact 合并两个 block 的指令
  3. PredChecker Stage1(错误检测)
  4. 计算 instrCountBeforeCurrent

S3: IBuffer 入队和预测验证

主要工作:

  1. MMIO 处理(通过 IfuUncacheUnit)
  2. RVC 展开(8 个 RvcExpander)
  3. PredChecker Stage2(targetFault 检测)
  4. 写回 FTQ(toFtq.resp
  5. IBuffer 入队(io.toIBuffer

5. 关键设计细节

5.1 prevLastIsHalfRvi 处理

这是香山处理 RVC/RVI 混合指令边界的最巧妙设计之一。

1
2
3
4
5
6
private val s1_prevLastIsHalfRvi = RegInit(false.B)

val s2_prevLastHalfData = RegEnable(s1_rawData(FetchBlockInstNum - 1), s1_fire)
val s2_prevLastHalfPc = RegEnable(s1_fetchBlock(0).startVAddr + s1_firstEndPos, s1_fire)

val alignedInstrData = Cat(s3_prevLastHalfData(15, 0), /* 当前 block 低 16-bit */)

5.2 flush 和 redirect 优先级

1
2
3
4
5
backendRedirect := fromFtq.redirect.valid  // 最高:后端重定向
s3_flush := backendRedirect || (wbRedirect.valid && !s3_wbNotFlush)
s2_flush := backendRedirect || uncacheRedirect.valid || wbRedirect.valid
s1_flush := s2_flush || s1_flushFromBpu(0)
s0_flush := s1_flush || s0_flushFromBpu(0)

优先级(从高到低):

  1. backendRedirect(后端分支 mispred / 例外)
  2. wbRedirect(IFU 自己产生的重定向)
  3. uncacheRedirect(Uncache 重定向)
  4. flushFromBpu(BPU 冲刷)

6. 验证要点

从验证工程师的角度,以下是需要重点关注的场景:

6.1 crossCacheline 场景

  • fetch block 跨越两个 cache line 时,IFU 正确请求两个 line
  • 两个 line 的 hit/miss 组合(4 种)都正确处理
  • isDoubleLine 信号正确设置

6.2 RVC 与非 RVC 混合

  • 纯 RVC block:所有指令都是 2 字节
  • 纯 RVI block:所有指令都是 4 字节
  • 混合 block:RVC 和 RVI 交替出现
  • 边界情况:RVC 指令在 block 最后一个位置

6.3 跨块半 RVI 指令

  • 前一个 block 最后一条是半条 RVI
  • 后一个 block 第一条指令正确拼接
  • prevLastIsHalfRvi 寄存器正确更新

6.4 分支预测验证(PredChecker)

  • jalFault:jal 指令的目标与预测不一致
  • retFault:ret 指令的目标与预测不一致
  • notCFIFault:预测跳转但实际不是 CFI
  • targetFault:跳转目标地址错误

6.5 MMIO 路径

  • MMIO 指令进入 Uncache FSM
  • 等待最老指令 commit 后才发送请求
  • 收到响应后正确生成 flush 请求

7. IFU 目录结构

1
2
3
4
5
6
7
8
9
10
11
12
src/main/scala/xiangshan/frontend/ifu/
├── Ifu.scala # 主模块,定义 IfuIO,4 级流水线
├── IfuBundle.scala # Bundle 定义
├── IfuParameters.scala # 参数定义
├── PreDecode.scala # 预译码模块
├── InstrBoundary.scala # 指令边界检测
├── PredChecker.scala # 分支预测验证
├── RvcExpander.scala # RVC 指令展开器
├── InstrCompact.scala # 指令合并压缩
├── IfuUncacheUnit.scala # MMIO 取指单元
├── IfuPerfAnalysis.scala # 性能分析
└── IfuToFtqIO.scala # 与 FTQ 的接口定义

8. 总结

IFU 是香山处理器前端流水线的核心,承担着从 ICache 取指令、将指令边界定界、验证分支预测、处理 RVC 展开、检测预测错误等多重职责。

IFU 设计的关键亮点:

  1. 4 级流水线:S0 取指请求 → S1 ICache 响应 → S2 预译码/合并 → S3 入 IBuffer/验证,每级职责清晰
  2. 双发射支持:两个 fetch block 并行处理,每个 block 最多 16 条指令
  3. 跨块半 RVI 处理prevLastIsHalfRvi 寄存器和拼接逻辑解决了指令跨越 block 边界的问题
  4. 两级 PredChecker:Stage1 在 S3 同一周期检测大部分错误,Stage2 延迟到写回阶段检测 targetFault
  5. MMIO 特殊路径:IfuUncacheUnit FSM 保证 MMIO 指令按序 commit,实现精确异常
  6. 性能监控完整:IfuPerfAnalysis 追踪所有关键事件,支持 top-down 性能分析

理解 IFU 的工作原理,对于验证香山前端功能、调试取指相关 bug、以及优化前端性能都至关重要。


文档版本:香山处理器 IFU 深度解读 v1.0
代码版本:Kunminghu V3 (kunminghu-v3 分支)