重命名与乱序执行窗口

一条长延迟指令尚未完成时,后面的独立指令能否先执行?第 09–11 篇的顺序流水线通过前递、停顿和分支恢复保持正确,但遇到队首等待时,后面的就绪操作仍可能受阻。乱序执行把“按程序顺序进入机器”和“按操作数就绪情况开始计算”分开。寄存器重命名进一步区分不同指令产生的同名寄存器值,使多个版本可以同时存在。

本篇的核心问题是:哪些依赖必须等待,哪些只是寄存器名字重复造成的限制;消除后者以后,加宽发射是否就能缩短执行时间?核心案例只有六条算术操作,支持逐项手算和 Python 事件模型复跑。证据等级为教学时序模型,关键路径另有手算核对。它没有取指、缓存、分支、提交或操作系统,因此六周期的结果不能称为一台完整 CPU 的运行时间。

实验目录为 examples/computer-architecture/ooo/。完整轨迹见 rename.json.txt,独立可运行附件见 rename_model.py.txt。模型的算术语法是题设,不是新增的 RISC-V 指令。

同名寄存器可以表示不同版本的值

设初始架构寄存器满足 ri=i。程序按表中 A 到 F 的顺序执行;latency 是开始执行到结果可用之间的教学周期数。

操作 顺序语义 latency 顺序执行得到的值
A r1 = r2 + 8 4 10
B r3 = r1 + r4 1 14
C r1 = r5 + 15 1 20
D r4 = r6 + 24 1 30
E r7 = r1 + r4 1 50
F r2 = r3 + r7 1 64

B 中的 r1 应当是 A 写出的 10,E 中的 r1 应当是 C 写出的 20。r4 也有相同情况:B 读取初值 4,E 读取 D 写出的 30。寄存器名字没有附带版本信息,执行器必须根据程序顺序确定每个源应读取哪一次写入。

如果只保留一份 r1,并且允许 C 先写入 20,B 随后从这份存储位置取数,就可能算成 24。错误来自 B 读到了错误版本,不能通过“最终 r1 恰好还是 20”发现。逐条结果和操作数的来源都需要核对。

RAW、WAR 和 WAW 分别约束什么

RAW(Read After Write,写后读)是生产者到消费者的真数据依赖。B 需要 A 的结果,A→B 必须保留;B→F、C→E、D→E、E→F 也一样。改变寄存器名字不会提前产生一个尚未算出的值。

WAR(Write After Read,读后写)要求较老的读取得正确旧值。例如 B 读 r1,C 写 r1;若 B 尚未取数,C 不能覆盖 B 所需版本。B 读 r4 与 D 写 r4 也形成这种关系。A 读取 r2 与 F 写入 r2 则是另一个名称复用。WAW(Write After Write,写后写)涉及两次写同一名字,例如 A 和 C 都写 r1,最终架构 r1 必须对应较年轻的 C。

BOOM 的重命名文档把 WAR/WAW 列为重命名能够解除的名称依赖,把 RAW 保留为真正的数据依赖。这一分类说明需要保存什么语义,不规定所有处理器必须采用相同的寄存器文件组织。

这几种关系可以同时出现在一个程序里。A→C 有 WAW,而 A→B→C 的名字使用又引入 RAW 与 WAR。检查依赖时应分别写出源、目标和程序先后,不宜只给每条指令贴一个“有冒险”的标签。

先捕获源映射,再分配新目标

物理寄存器为同一个架构名字提供多个存储位置。初始映射是 ri→pi,A 起依次分配 p8、p9,直到 p13。每条指令重命名时先读取源映射,再替换目标映射:

1
2
3
4
sources = current_map[logical_sources]
stale = current_map[logical_destination]
new = allocate()
current_map[logical_destination] = new

按程序顺序应用以后,得到:

操作 物理源 新物理目标 被替换的旧目标映射
A p2 p8 p1
B p8、p4 p9 p3
C p5 p10 p8
D p6 p11 p4
E p10、p11 p12 p7
F p9、p12 p13 p2

B 已经捕获 p8 和 p4;C、D 改变后续指令看到的映射,不会回头修改 B 的源。于是 C 可以在 B 尚未执行时产生 p10=20,D 可以产生 p11=30,而 B 仍从 p8、p4 得到 10 与 4。名称冲突解除后,数值依赖图只剩下:

1
2
3
A --4--> B --1--> F --1--> 最终结果
C --1--> E --1---^
D --1----^

箭头旁的数字在这里表示左侧操作的延迟;更直接的关键路径写法是 A(4) + B(1) + F(1)。图中的 C、D 与 A 可以重叠,但 E 仍须等 C、D 的结果。

同一条指令读写同一个逻辑寄存器时,先读源映射尤其必要。例如 r1=r1+1 必须捕获旧 r1;先更新目标映射会让该指令等待自己的新目标,形成错误的自依赖。这个规则同样适用于一周期重命名多条指令时的内部传递:后面的指令要看见前面已经产生的新映射。

就绪表与空闲表不是同一个问题

映射表回答“这个源引用哪个物理版本”。就绪表回答“那个版本的数值是否已经可用”。空闲表回答“还有哪个物理位置可以分配”。三个问题独立:找到物理源不表示该源已经就绪,找到空闲目标也不表示这条指令能够发射。

本模型只实现前两个问题所需的有限状态。六条操作在 t=0 前全部完成重命名,每个写目标使用一个从未重复分配的位置;它不模拟空闲表耗尽和寄存器回收。BOOM 文档给出的实现会在相应指令提交后回收其 stale destination。实际实现必须结合提交、分支恢复和仍在使用该版本的指令判断回收安全性,不能在映射表被年轻写替换时立即释放旧位置。

以 C 为例,C 分配 p10 时替换了映射中的 p8,但 B 仍引用 p8。若立刻把 p8 交给其他指令覆盖,名称依赖又会变成数据破坏。这个反例说明:逻辑名字不再指向某个位置,与所有消费者都不再需要该位置,是两个条件。

有限窗口的发射规则

发射(issue)是把已就绪操作交给执行单元。本模型每个时刻先使到期结果可用,再从程序顺序最老的就绪操作中选择,最多选择 width 条。未就绪的 B 不妨碍选择更年轻但独立的 C。结果在 issue_time + latency 可用。

题设还固定了六条窗口容量、足够的功能单元、不限制写回端口;t=0 前装入全部操作。这里改变的唯一资源参数是发射宽度。下表每个单元格均为“发射→完成”时刻,来自实际运行输出:

操作 width=1 width=2 width=4
A 0→4 0→4 0→4
B 4→5 4→5 4→5
C 1→2 0→1 0→1
D 2→3 1→2 0→1
E 3→4 2→3 1→2
F 5→6 5→6 5→6

width=4 时 C、D 更早结束,E 从 t=3 提前到 t=1 发射,但 F 的另一个源来自 B,仍要等到 t=5。三种配置最后都在 t=6 完成。这与关键路径的六周期下界一致。

为了区分“这个输入不受宽度限制”和“宽度没有作用”,回归另含四条相互独立、延迟都为一的操作。它们在 width=1/2/4 时分别耗时 4/2/1。改变工作负载以后限制因素变了,不能把一个依赖链案例推广为所有程序的性能结论。

窗口、宽度和关键路径怎样分别检查

对于给定依赖图,关键路径约束最早完成时间;宽度限制每个时刻能开始多少操作;窗口大小限制调度器能看见多少候选。增加窗口只有在新纳入的操作提供了有用独立工作时才有收益。本篇窗口恰好装下全部六条,因此没有测量窗口增大的效果。

若真正实现有限分配带宽、少量功能单元或有限写回端口,表中的最早时间可能达不到。那些额外等待要在事件记录中有对应原因,不能靠修改最后的周期数来“模拟”。同样,当前六周期不含顺序提交。F 完成以后架构状态何时正式可见,留给下一篇 ROB 模型处理。

观察到的限制 需要检查的状态 对应问题
源结果未产生 生产者及 ready time RAW 关键路径
源齐备但没有发射 宽度、单元、端口 资源限制
独立操作尚未进入窗口 队列容量与分配进度 可见候选不足
同名寄存器互相覆盖 源物理版本、旧目标寿命 重命名或回收错误

复跑与验收

在仓库根目录执行:

1
PYTHONDONTWRITEBYTECODE=1 python3 examples/computer-architecture/ooo/src/run_all.py

只复跑本篇附件也可以:

1
python3 rename_model.py.txt

累计脚本同时检查后续章节的相关模型;本篇关注 outputs/rename.json。验收应核对 B 的物理源为 p8、p4,E 的物理源为 p10、p11,三种宽度最终 r2 都为 64,依赖案例完成时刻都为 6,独立案例分别为 4、2、1。仅看脚本正常退出不足以解释周期表,应从 A、B、F 三项重新计算关键路径。

这些记录证明指定有限模型的执行符合题设。没有 gem5、RTL 或真机计数器参与,不提供商品 CPU 的吞吐预测,也不证明物理寄存器回收、分支恢复或整套 ISA 正确。

两道练习

练习一: 如果 C 在 t=1 已经完成,但 B 到 t=4 才发射,B 应读到 r1 的哪个值?如果实现是在发射时读取当前逻辑映射,会有什么错误?

解答:B 应使用重命名时捕获的 p8,即 A 产生的 10,加上旧 p4=4 得到 14。发射时重新查当前 r1 映射会读到 C 的 p10=20,得到 24;F 可能因此变成 74。需要修正的是操作数版本捕获,而不是给 B 加一个随意的延迟。

练习二: 把 A 的延迟从 4 改成 2,width=4 的完成时间下界是多少?是否可以直接声称真实处理器提速 1.5 倍?

解答:A→B→F 的长度变成 2+1+1=4;C/D→E→F 为 3,在本模型足够资源的前提下可以四周期完成。六除以四得到 1.5,只描述同一教学模型这个输入的周期比。实际实现还受前端、频率、提交和其他资源限制,当前证据无法给出真机加速比。

参考资料

  • BOOM:The Rename Stage。2026-09-19 核查在线版;Purpose of Renaming、Rename Map Table、Busy Table、Free List、Stale Destination Specifiers 分别支持依赖分类、映射、就绪和回收机制。本文有限调度器不声称复刻 BOOM。
  • BOOM:The Reorder Buffer。Commit Stage 说明旧物理目标的回收与提交联系,作为下一篇的接口。
  • 第 07 篇:有限 RV32I 参考执行器。本篇的有限算术轨迹与 RV32I 累计执行器分开;它没有扩充该执行器的支持清单。