跳转至

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 依赖。

add x10, x1, x2      # x10 -> p41
mul x10, x3, x4      # x10 -> p57;不必等上一条写回
sub x11, x10, x5     # 读取当前映射 p57

提交时还要安全回收旧物理寄存器;redirect 时则要恢复到正确的历史映射。香山的 Rename snapshot、ROB/RAB 和 freelist 共同服务这件事。

分支:预测不是 ISA 行为

ISA 只规定最终 PC,BPU 可以在分支执行前猜测方向和目标。猜对时隐藏控制依赖; 猜错时后端产生 redirect,前端恢复 PC/历史,并清除错误路径的年轻微操作。

因此软件层面的“分支代价”至少取决于:

  • 预测器是否有足够历史和目标信息;
  • 分支何时到达执行单元并被验证;
  • 错误路径已占用了多少 FTQ、IBuffer、ROB、IQ 和 cache 带宽;
  • 正确路径的 ICache/ITLB 是否已经准备好。

Load:值可能早到,也可能被推翻

为了隐藏内存延迟,年轻 Load 可以在更老 Store 的地址尚未完全确定时投机执行。 如果之后发现真实地址冲突且顺序不允许,处理器必须触发内存顺序违例恢复。

一个标量 Load 通常要同时满足:

  1. 有效地址计算正确且无地址异常;
  2. TLB 翻译命中,权限、PMP/PMA/PBMT 检查通过;
  3. 从更老 Store 前递的数据完整,或 DCache/uncache 返回正确数据;
  4. 没有更老指令引发会覆盖它的 redirect/exception;
  5. 结果写回并最终随 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/isaDecodeUnit.scala
逻辑寄存器怎样变成物理寄存器? backend/rename
哪些 FU 能执行这条指令? backend/exubackend/fu、调度参数
分支错预测怎样恢复? backend/ctrlblock/RedirectGenerator.scalafrontend/Frontend.scala
Load 为什么 replay? mem/pipeline/NewLoadUnit.scalamem/lsqueue/LoadQueueReplay.scala
异常怎样到达 mtvec/stvec/vstvec backend/robbackend/fu/NewCSR

下一步可以按指令类别进入 执行单元,或按性能现象进入 性能分析