Computer Arch 102

项目

从计算机课堂的5级流水线进一步进阶,制作更强大的CPU。

github: https://github.com/HaibinLai/simple-CPU.git

使用指令集:RISC-V

多发射(Superscalar)

Superscalar(超标量)是指 CPU 在一个时钟周期里,不再只发射(issue)一条指令,而是同时发射多条彼此独立的指令到不同执行单元。例如一个现代 CPU 可能同时执行整数加法、浮点乘法、load/store 等不同操作。核心目标是提升 IPC(Instructions Per Cycle)。为了做到这一点,CPU 需要能够分析指令之间是否存在依赖关系,并动态寻找可以并行执行的指令。比如 Intel、AMD 的现代 CPU 通常都是 4-wide、6-wide 甚至更宽的 superscalar 架构。

Intel/AMD 现代 CPU 通常是 4-wide、6-wide 甚至更宽的 superscalar 架构。

本项目实现的是 2-wide(双发射) 前端,slot0/slot1 同时取两条相邻指令并经 IFQ 缓冲,再按配对规则发射到后端。

  • 双 PC 取指(pc / pc+4)与双指令读 IMEM:cpu_top.v
  • IFQ(双 push / 双 pop):ifq.v,例化点 cpu_top.v
  • Slot1 配对门控(id2_issue_slot1,要求 ALU-only、无依赖等):cpu_top.v
  • 关键信号:pc_plus4 / pc_plus8 / if_id_valid0/1 / push_valid_0/1 / id1_is_alu_only

乱序执行(Out-of-Order Execution, OoO)

乱序执行是现代 CPU 提高利用率的核心技术之一。它允许 CPU 不按照程序原本的顺序执行指令,而是优先执行“已经准备好”的指令。比如某条 load 指令正在等待内存返回数据(可能要几百 cycles),CPU 不会傻等,而是继续执行后面那些不依赖它的指令。这样可以隐藏长延迟,提高流水线利用率。需要注意的是,虽然内部执行顺序乱了,但 CPU 最终仍然必须保证程序对外的结果与原始顺序一致(precise state)。

CPU 不按程序原本顺序执行指令,而优先执行已经准备好的指令。

本项目目前是 in-order 后端 + OoO 基础设施 的过渡阶段:rename + ROB 已经就位,但还没有独立的乱序 issue 阶段。后端仍按 PC 顺序发射,但通过 ROB 保证最终对外顺序一致(precise state)。

  • ROB 提供顺序提交、乱序写回的容器:rob.v
  • Rename 提供 ptag 化解假依赖:rename.v
  • 路径规划文档:2issue-implementation-plan.md

Speculative Execution(推测执行)

推测执行是 CPU 在“不确定未来是否正确”的情况下提前执行指令。最典型的场景是 branch prediction(分支预测):CPU 猜测 if/else 会走哪条路径,然后提前执行对应路径上的指令。如果猜对了,就节省了大量等待时间;如果猜错了,则回滚错误执行的结果并重新执行正确路径。现代深流水 CPU 非常依赖 speculative execution,否则 branch stall 会让流水线大量空闲。著名的 Spectre 漏洞,本质上就是 speculative execution 带来的侧信道问题。

最典型的场景是 branch prediction:CPU 猜测分支走哪条路径,提前执行;猜错则回滚。

  • BPU 模块(2-way BTB + GShare BHT + GHR):bpu.v
    • BTB / BHT 存储:bpu.v
    • IF 阶段产出 pred_taken / pred_target:bpu.v
    • EX 阶段反馈训练(更新 BHT/BTB/GHR):bpu.v
  • 误预测回滚(ex_redirect → 注入 NOP + 重定向 PC):cpu_top.v
  • 关键信号:pred_takenpred_targetghrbtb0_valid/btb1_validbht[]upd_validupd_taken

Register Renaming(寄存器重命名)

寄存器重命名用于消除“假依赖(false dependency)”。程序里虽然只有有限个架构寄存器(如 x86 的 rax/rbx),但 CPU 内部实际上有更多“物理寄存器(physical registers)”。CPU 会把逻辑寄存器动态映射到新的物理寄存器上。例如:

mov r1, ...
add r1, ...

表面上两条指令都写 r1,但 CPU 可以把它们映射到不同物理寄存器,从而避免无意义的写后写(WAW)或写后读(WAR)冲突。这样可以让更多指令并行执行。register renaming 是 OoO CPU 的基础技术之一。

把逻辑寄存器动态映射到更多物理寄存器,消除 WAW/WAR 这种"假依赖"。

本项目用 48 个物理寄存器(ptag 0–47,6-bit):ptag 0–31 与架构寄存器一一固定映射,ptag 32–47 由 free list 动态分配给推测写。

  • Rename 模块(双槽 s0/s1 同时分配):rename.v
    • 存储:map[32](speculative RAT,ARF→ptag)、arch_map[32](RRAT,已提交映射,用于 flush 恢复)、busy[48]、free list fl_mem[16]:rename.v
    • 双槽组合查 + bundle 内前递:rename.v
    • Free list 头尾指针 / 分配 vs commit 释放:rename.v
  • 关键信号:map[] / arch_map[] / fl_head / fl_tail / fl_cnt / s0_rd_ptag_new / s0_rd_ptag_old / s1_rd_ptag_new / s1_rd_ptag_old / stall

Tomasulo Algorithm(Tomasulo 算法)

Tomasulo 是 IBM 在 1960s 提出的经典动态调度算法,是现代 OoO CPU 的理论基础之一。它的核心思想是:通过 reservation stations(保留站)和寄存器重命名,让指令在操作数准备好后自动执行,而不必严格按照程序顺序等待。它能动态跟踪数据依赖,并通过广播结果(Common Data Bus)唤醒后续指令。相比早期 scoreboard 方法,Tomasulo 能更好地解决 WAR/WAW hazard。现代 CPU 虽然实现已经复杂很多,但本质思想仍然来自 Tomasulo。

通过 reservation stations + 寄存器重命名,让指令在操作数 ready 时自动执行;通过 CDB 广播结果唤醒后续指令。

本项目当前没有实现 Tomasulo(没有 reservation station,没有 CDB wakeup)。它走的是更简单的 显式 forwarding + load-use stall 路线:

  • 操作数前递(4 优先级 MUX,从最近的生产者取值):forwarding.v
  • 冒险检测(load-use 等):hazard.v

未来要做真正的 OoO 后端时,Tomasulo 风格的 RS + wakeup 是要补的核心结构。


ROB(Reorder Buffer,重排序缓冲区)

ROB 是现代 OoO CPU 用来“恢复程序顺序”的关键结构。虽然 CPU 内部会乱序执行,但最终必须按程序顺序提交(commit)结果,否则异常、中断、错误恢复会完全混乱。ROB 会记录每条指令的执行状态、目标寄存器、结果等信息。指令可以乱序执行,但只有当它之前的所有指令都完成后,它才能按顺序 commit。ROB 保证了 precise exception(精确异常)和程序可见状态的一致性。可以理解为:

OoO 是"乱着干活",ROB 是"按顺序交卷"。

本项目实现的是 16 条目环形 ROB(tag 0–15,4-bit),支持双分配、双写回、双提交,并支持全 flush 与 partial flush。

  • ROB 模块:rob.v
    • 条目存储(valid_q[] / done_q[] / pc_q[] / rd_q[] / result_q[] / is_store_q[] / ptag_new_q[] / ptag_old_q[]):rob.v
    • 分配(alloc_tag_0/1,移动 tail_ptr):rob.v
    • 顺序提交头视图(commit_valid_X = valid_q[head_X] && done_q[head_X]):rob.v
    • 写回置 done、commit 弹出、flush / flush_partial:rob.v
  • 关键信号:valid_q[] / done_q[] / head_ptr / tail_ptr / cnt / alloc_tag_0/1 / commit_valid_0/1 / commit_result_0/1 / flush / flush_partial
  • 配合 Rename:每条进 ROB 时同时记 ptag_new(新分配的目标 ptag)和 ptag_old(被该 rd 覆盖的旧 ptag);commit 时把 arch_map[rd] ← ptag_new 并把 ptag_old 还回 free list,从而做到 precise state。

Benchmark

benchmark CPI
memcpy_64w 1.546
bsort_16 1.820
crc32_64b 1.941
bsearch_64 1.918
dotprod_32 1.927
matmul_4x4 1.890
fib_20 1.805
popcount_64 1.738
sum_1_to_100 2.995

4ca87b06a87cac1cbcc5f13d17489cd6.jpg