计算机体系结构 13:ROB 与精确异常
ROB 与精确异常
重命名允许年轻指令提前计算,也允许多个尚未提交的寄存器版本同时存在。程序仍需要一种可解释的异常状态:若某条指令失败,异常处理程序看到的状态应对应顺序执行到该指令之前,而不能是几条年轻指令已经生效、几条较老指令尚未完成的混合结果。
本篇的核心问题是:执行顺序被打乱以后,怎样让异常边界仍然对应一段完整的程序前缀?案例在长延迟操作之后注入异常,并让更年轻的 store 最先完成。验收同时检查寄存器和内存,尤其检查提前完成的 store 没有污染架构状态。
证据等级为教学时序模型。四个条目的完成时刻是题设;模型运行后产生完成、提交和异常事件。它没有真实访存延迟、页表、RISC-V 陷阱 CSR 或异常处理程序。原始记录见 rob.json.txt,可独立执行的附件见 rob_model.py.txt。
架构状态需要对应一个程序前缀
设程序顺序为 A、B、C、D。A 需要很久,B 最终报告异常,C 是写内存的操作,D 写寄存器。异常处理时允许 A 已生效,不允许 B 自己的正常结果、C 的 store 或 D 的寄存器写入生效。
“精确”描述的是这个边界。它不表示异常一被发现就立即进入处理程序,也不表示异常期间硬件内部没有任何推测活动。异常可以早于较老指令完成而被检测到,但体系结构可见状态仍要等到合适的提交位置。
BOOM 的 ROB 文档区分了完成与提交,并规定队首异常触发流水线清除,年轻指令的架构变化不成为可见结果。本篇用更小的条目模型展示这个条件;完成时刻和提交带宽并非 BOOM 的实测参数。
ROB 保存程序顺序
ROB 是 Reorder Buffer,即重排序缓冲区。名称中的“重排序”容易让人只想到把结果重新排列,实际上它至少需要保存指令的程序先后、是否完成、是否异常,以及提交或恢复所需的信息。
调度器可以先发射 C,再发射 B;ROB 的队列顺序仍然是 A、B、C、D。完成反馈只更新对应条目的状态。提交逻辑从队首检查:未完成就等待;完成且正常则提交;遇到队首异常就恢复到该位置对应的架构边界。不能因为 D 已经 ready 就跨过 B 提交 D。
本模型的条目保存寄存器结果或 store 的地址和值,类似把结果保留到提交时再应用的简化设计。真实处理器也可以把值保存在物理寄存器文件里,让 ROB 主要保存状态和恢复信息。BOOM 的 Rename Stage 文档明确对比了显式物理寄存器设计和 data-in-ROB 设计;“有 ROB”不等于“所有执行结果都放在 ROB 内”。
1 | |
第 12 篇的物理映射说明了旧值怎样继续存在,本篇的提交边界说明了哪个版本能够成为正式架构状态。两者结合以后,年轻指令提前完成不会迫使架构状态提前变化。
长延迟之后注入异常
初始状态设为 r1=0、r2=0、mem[128]=7。四个条目按下表进入 ROB:
| 条目 | 语义或事件 | 完成/异常已知时刻 |
|---|---|---|
| A_long | r1 = 42 | t=6 |
| B_fault | 产生同步异常 | t=2 |
| C_store | mem[128] = 99 | t=1 |
| D_add | r2 = 88 | t=3 |
条目已经在 t=0 前入队,模型每个整数时刻先接收完成反馈,再尝试一次队首提交。提交宽度固定为一;完成和提交允许在同一时刻发生。t=1 的 store 完成只表示地址和值可供提交,不能据此更新内存。
实际输出中,t=1、2、3 分别记录 C_store、B_fault、D_add 完成。t=2 已经知道 B 异常,但 A 还没有完成,因而 t=2 不能把 A 跳过。到 t=6,A 完成并提交 r1=42;同一时刻提交额度已用完。t=7 队首变成 B,触发异常并清除 C、D。
| 时刻 | 完成反馈 | 提交或恢复 | 架构状态变化 |
|---|---|---|---|
| 1 | C_store ready | 队首 A 未完成 | 无 |
| 2 | B_fault 异常已知 | 队首 A 未完成 | 无 |
| 3 | D_add ready | 队首 A 未完成 | 无 |
| 6 | A_long ready | 提交 A_long | r1=42 |
| 7 | 无 | 处理 B_fault,取消 C、D | 无 |
最终记录为:
1 | |
C 和 D 虽然都完成了,r2 仍为 0,地址 128 仍为 7。这里没有通过“先写入再把值改回去”恢复内存;它们的架构副作用从未被提交。
用顺序前缀检查恢复结果
仅检查最终 r1=42 不够。一个坏实现也可能保留 r1,却漏掉年轻 store 的写入。顺序参考应同时覆盖所有被模型表示的架构状态。
附件中的 reference_prefix 按程序顺序应用条目,遇到第一个 fault 立即停止。A 被应用,B 和后面的条目不被应用,因此参考状态恰好是上面的 r1、r2 和内存。ROB 运行结束后对整个状态比较,而不是只挑一个结果寄存器。
这个参考函数与乱序时刻无关。把 C 的完成时刻从 t=1 改成 t=5,正确的异常状态仍然相同;把 A 的完成时刻推迟,则异常处理时间可能推迟。时间变化与语义变化应分别观察。
验收还包含一个无异常的对照:A 在 t=3 完成,年轻 store S 在 t=1 完成。A 于 t=3 提交,S 于 t=4 提交,此时内存才变成 99。这样可以排除“模型从来不提交 store,所以异常测试通过”的错误实现。
提前写 store 的故障反例
故障模式 early_store=True 只修改一条规则:store 一完成就立即更新内存。其他 ROB 顺序和异常处理逻辑保持不变。于是 t=1 出现:
1 | |
t=7 仍然出现 trap,C 和 D 也仍然被列为 squashed。单看“取消列表正确”会误判为恢复成功,但最终 mem[128]=99,与顺序参考的 7 不同,matches_reference 为 false。
这说明检查恢复不能只检查控制信号,还必须检查可能已经传播出去的副作用。普通寄存器结果可以保留在未提交版本里;内存写若已经被其他观察者看见,再把值写回原值也未必能撤销先前影响。本篇只模拟单线程内存状态,尚未加入其他核或设备,因此没有测量这种外部观察。
store 提交与写入内存仍有距离
本教学模型把“store 获准提交”和“更新内存数组”放在同一个动作里。这样可以用十几个事件解释异常隔离,但它省略了真实存储系统的队列和可见性。
BOOM 文档中的 store 在提交后获得写内存许可,由 LSU 在后续时机排空。提交、store buffer 排空、缓存一致性完成,以及其他核何时观察到结果,不应被压成同一个时间点。第 14 篇只讨论单线程的 load/store 依赖;跨核顺序还需要后面的内存模型章节。
同样,本篇的异常条目是人为注入的状态,不把它称为真机 page fault。若要实现真正的 RISC-V 异常,还需要依据特权规范保存异常 PC、原因、相关地址,并建立跳转和返回语义。当前模型的 trap 只是轨迹事件。
恢复时要清除哪些状态
有限模型把寄存器结果保留在条目里,所以丢弃年轻条目就能阻止年轻寄存器结果生效。使用物理寄存器文件的实现还要恢复映射并处理年轻分配的位置;分支预测历史、未完成请求和执行单元中的条目也可能需要标记或取消。
这些结构能否恢复应由各自的不变量验收。例如年轻 load 即使不能提交,发出的缓存访问也可能留下微架构痕迹;本模型没有缓存状态,不能由“架构状态正确”推出“所有内部痕迹都被清空”。侧信道选修将单独讨论这种边界。
对于本文的条目模型,验收条件可以直接写成:异常位置之前的所有正常条目按序生效;异常条目及其后条目不产生架构写入;最终寄存器和内存与顺序前缀一致。这些条件比“ROB 里没有残留”更贴近程序可见语义。
| 检查问题 | 对应记录 | 本例结果 |
|---|---|---|
| 较老 A 是否先完成并生效 | complete、commit | t=6 两事件 |
| 异常是否在队首处理 | trap 的时刻与名字 | t=7 的 B_fault |
| 年轻寄存器是否泄漏 | 最终 r2 | 0 |
| 年轻 store 是否泄漏 | 最终 mem[128] | 7 |
| 检查器能否发现已知错误 | early_store 对照 | 状态比较失败 |
复跑与验收
在仓库根目录执行累计回归:
1 | |
下载附件后可以独立执行:
1 | |
阅读 outputs/rob.json 中的 correct、broken_early_store 和 without_fault 三部分。正确模型必须与顺序前缀相等;故障模型必须不相等;无异常对照必须真正写入 99。三项合起来才覆盖这次实验的结论。
本模型没有实现分支检查点、ROB 容量回压、不同提交宽度、物理寄存器回收、设备访问或跨核可见性。该测试也不验证第 07 篇执行器的特权异常语义,因为那一执行器并未实现它们。
两道练习
练习一: B 的异常在 t=2 已知,为什么不立即跳到处理程序,并让 A 以后完成?
解答:若处理程序在 A 生效之前读取架构状态,会看见缺少一条较老指令的结果,不能对应异常前的顺序前缀。可以采用不同恢复机制,但必须补足这个状态条件。本模型通过等待 A 提交使条件成立;没有实现先回滚再重执行较老指令的其他方案。
练习二: 把提交宽度改为二,允许同一时刻依次检查两个队首条目,异常最早能否在 t=6 处理?年轻 store 能否一并提交?
解答:按照这个新题设,t=6 可以先提交 A,再看到作为新队首的 B 异常,因此异常处理可提前一周期。C 仍在 B 之后,必须被取消,不能因为还有带宽就越过异常提交。这个推导是手算,当前附件只执行宽度为一的配置。
参考资料
- BOOM:The Reorder Buffer and the Dispatch Stage。2026-09-19 核查在线版;ROB State、Commit Stage、Exceptions and Flushes 支持按序提交、队首异常和 store 提交许可的区分。
- BOOM:The Rename Stage。Explicit Renaming Design 与 Resets on Exceptions and Flushes 支持结果存储位置和映射恢复的不同设计。
- 第 12 篇:重命名与乱序执行窗口。前一篇保存操作数版本,本篇检查这些版本怎样成为架构状态。






