ISA 如何落到流水线¶
ISA 定义软件可以依赖的结果,微架构决定用多少并行、预测和投机去得到这个结果。 理解乱序 CPU 的关键,是始终分清两种状态:
- 架构状态:已经退休的 PC、寄存器、CSR 和内存效果,软件可以观察;
- 投机状态:已经取指、译码甚至执行,但尚未退休,错误时必须能撤销。
一条指令的生命周期¶
sequenceDiagram
participant BPU as BPU / FTQ
participant IFU as ICache / IFU
participant REN as Decode / Rename
participant IQ as Dispatch / Issue
participant EXU as EXU / LSU
participant ROB as ROB / RAB
participant ARCH as 架构状态
BPU->>IFU: 预测取指块和下一 PC
IFU->>REN: 对齐、预译码后的指令
REN->>IQ: 微操作 + 物理寄存器映射
IQ->>EXU: 操作数就绪后乱序发射
EXU-->>IQ: 写回与唤醒
EXU-->>ROB: 完成 / 异常 / redirect
ROB->>ARCH: 按程序顺序退休
ROB-->>BPU: 分支结果与恢复信息
“执行完成”不等于“软件可见”。只有 ROB 头部之前的所有指令都满足提交条件, 结果才按程序顺序成为架构状态;这正是精确异常能够成立的基础。
指令类型与硬件路径¶
| ISA 语义 | 主要微架构路径 | 软件最容易看到的现象 |
|---|---|---|
| 整数 ALU | Rename → Int IQ → ALU → bypass/writeback | 真依赖链限制 IPC;独立指令可并行 |
| 条件分支/跳转 | BPU 预测 → BJU 验证 → redirect | 错误预测造成前端与年轻指令清空 |
| Load | 地址生成 → TLB/PMP/PMA → LSQ/前递/DCache → writeback | cache/TLB miss、依赖预测错误与 replay |
| Store | 地址和数据可分开发射 → SQ → 提交后进入 SBuffer/Cache | store buffer 压力、fence 和一致性延迟 |
| CSR | Decode/CSR FU → 权限检查 → CSR 状态/副作用 | 串行化、状态切换、页表或前端控制刷新 |
| Fence/原子 | 内存顺序检查 → 队列排空/一致性事务 | 内存级并行度下降,但建立跨 hart 可见顺序 |
| 浮点/向量 | 独立寄存器域 → FP/Vec 调度与执行 → ROB | 长延迟、吞吐端口和向量拆分/合并限制 |
为什么需要寄存器重命名¶
ISA 只给出有限数量的逻辑寄存器。如果两条指令都写 x10,硬件若直接以
x10 为存储位置,会产生并非数据语义要求的 WAW/WAR 相关。Rename 为每次
新定义分配物理寄存器,让后端只保留真正的 RAW 依赖。
提交时还要安全回收旧物理寄存器;redirect 时则要恢复到正确的历史映射。香山的 Rename snapshot、ROB/RAB 和 freelist 共同服务这件事。
分支:预测不是 ISA 行为¶
ISA 只规定最终 PC,BPU 可以在分支执行前猜测方向和目标。猜对时隐藏控制依赖; 猜错时后端产生 redirect,前端恢复 PC/历史,并清除错误路径的年轻微操作。
因此软件层面的“分支代价”至少取决于:
- 预测器是否有足够历史和目标信息;
- 分支何时到达执行单元并被验证;
- 错误路径已占用了多少 FTQ、IBuffer、ROB、IQ 和 cache 带宽;
- 正确路径的 ICache/ITLB 是否已经准备好。
Load:值可能早到,也可能被推翻¶
为了隐藏内存延迟,年轻 Load 可以在更老 Store 的地址尚未完全确定时投机执行。 如果之后发现真实地址冲突且顺序不允许,处理器必须触发内存顺序违例恢复。
一个标量 Load 通常要同时满足:
- 有效地址计算正确且无地址异常;
- TLB 翻译命中,权限、PMP/PMA/PBMT 检查通过;
- 从更老 Store 前递的数据完整,或 DCache/uncache 返回正确数据;
- 没有更老指令引发会覆盖它的 redirect/exception;
- 结果写回并最终随 ROB 顺序退休。
这解释了为什么“L1 命中”仍不保证固定延迟:bank conflict、前递、TLB、 replay 和写回端口都可能造成额外等待。
Store:退休与全局可见不是同一个时刻¶
Store 的地址和数据可以独立准备,在 SQ 中重新配对。到达 ROB 头并满足异常与 顺序条件后,它可从指令层面退休;数据通常还会经过提交后的 store buffer 和 cache/coherence 路径,稍后才对其他 hart 或设备可见。
所以:
fence约束的是特定内存操作之间的可见顺序,不是普通算术流水级;- MMIO、uncache 与 cacheable memory 的完成条件不同;
- 自旋锁慢可能来自一致性所有权迁移,而不只是执行
amo的功能单元延迟。
精确异常¶
异常可以在取指、译码、执行或访存阶段发现,但软件必须看到“异常指令之前均已 完成、异常指令及之后均未提交”的状态。实现上,异常信息跟随微操作进入 ROB, 当它成为最老待处理事件时再进入 CSR/trap 路径。
这也是调试 Difftest mismatch 时应首先记录 commit 序列的原因:错误可能更早 发生,但架构分歧通常在错误微操作退休或 trap 入口处首次可见。
用 ISA 问题驱动源码阅读¶
| 想回答的问题 | 先看 |
|---|---|
| 一个 opcode 被译成什么控制信号/微操作? | backend/decode/isa、DecodeUnit.scala |
| 逻辑寄存器怎样变成物理寄存器? | backend/rename |
| 哪些 FU 能执行这条指令? | backend/exu、backend/fu、调度参数 |
| 分支错预测怎样恢复? | backend/ctrlblock/RedirectGenerator.scala、frontend/Frontend.scala |
| Load 为什么 replay? | mem/pipeline/NewLoadUnit.scala、mem/lsqueue/LoadQueueReplay.scala |
异常怎样到达 mtvec/stvec/vstvec? |
backend/rob、backend/fu/NewCSR |