把开源乱序核推到 105 MHz:一次跨阶段组合桥的流水化重构

这是一篇关于“时序收敛”的文章,不是关于“从零设计 CPU”的文章。基座是清华大学 NOP 队开源的 NOP-Core(MIT License),本队的工作集中在 S3v2 前端时序重构、CPUCFG 功能适配、AXI 壳适配,以及全部时序收敛与等价性验证。把边界说清楚,后面的数字才有意义。

基座与边界

2026 年龙芯杯团体赛的初赛要求实现一套支持 LA32R 的裸机微系统,功能测试 58 项、性能测试 20 项,评分同时看功能分和系统计数器比值。我们选择在 NOP-Core 上二次开发:它已经是一个三发射、乱序执行、带分支预测的处理器核,基座提交为 1a5986d,用 SpinalHDL 1.8.1 / Scala 2.12 构建。

基座提供的结构包括:

结构 参数
发射/提交宽度 3 发射:整数 ALU ×3 + 乘除 ×1 + 访存 ×1
分支预测 BTB 1024 项、PHT 8192 项(gshare)、GHR 5 位、RAS 8 项
乱序窗口 ROB 32 项,按程序序提交
发射队列 IntIQ 压缩队列(CompressedQueue)
取指缓冲 FetchBuffer,4 路入队 / 3 路出队
流水深度 宏观三级流水段,最深路径约 13 级

这些不是本队重新设计的部分。本文要讲的是:在这样一个基座上,怎样让它在 105 MHz 下收敛,并且证明时序重构没有改变功能。

目标不是“跑得快”,而是“计数器比值”

比赛的性能分不是直接测秒数,而是对 20 项 benchmark 分别统计 CPU 域计数和 SoC 域计数,与官方参考核求比,再取几何平均。这个比值同时受频率和 IPC 影响:

system counter ratio ∝ 实测频率 × IPC

最终提交版的数据是:

指标 结果
System counter ratio(20 项几何平均) 3.817781304
IPC ratio(20 项几何平均) 1.180267034
实测频率 105.887336 MHz
功能测试 58/58 × 3 随机种子全 PASS
性能测试 20/20 全 PASS
时序 setup WNS +0.035 ns,hold +0.057 ns

从比赛基线 0.287 到最终 3.8178,是团队整个迭代过程的结果,包含基座能力、前端预测、后端调度和大量板上调试。本文只拆解其中与 S3v2 相关的一段:为什么 105 MHz 下唯一不收敛的是一条跨阶段组合桥,以及怎样用协议保持的方式把它斩断。

问题:提交段到取指段的组合桥

100 MHz 版本在板上全绿之后,第一次冲 105 MHz,setup WNS 是 −0.098 ns。时序报告里最差的路径很集中:

ROB 重定向判定(提交段)
        ↓ 同拍组合穿透
nextPC 三候选选择
        ↓
ICache 读地址

也就是说,后端在提交段发现分支误预测,重定向信号同拍穿过前端的候选选择逻辑,直接抵达 ICache 的读地址。功能上这是对的,但在 105 MHz 下,逻辑级数加上布线延迟,这条路径无法收敛。

这不是一个“再加一级流水”就能随便解决的问题。乱序核的重定向路径本来就是关键路径:它决定了误预测后多久能重新取指。随便加寄存器可能改变握手语义,导致漏冲或重复冲刷。我们需要的是只改时序、不改值和次序的重构。

S3v2:把组合穿透改成寄存器切片

基座的重定向接口叫 backendJumpInterface,原来用 s2mPipe() 做流水化。S3v2 把它改成 m2sPipe():

// 改动示意(ProgramCounterPlugin.scala)
backendJumpInterface
  .m2sPipe()   // 原为 s2mPipe()

在 SpinalHDL 的 Stream 语义里,这两个算子都是协议保持的流水化原语,区别在于寄存器切片放在接口的哪一侧。换成 m2sPipe() 之后,重定向锥到 nextPC/ICache 读地址的组合链路被寄存器化斩断,跨阶段组合桥变成干净的寄存器到寄存器路径。

但只改这一行还不够。m2sPipe() 让重定向晚一拍到达,取指重启和冲刷配对出现了同拍依赖。如果不同步冲刷流水寄存器里的旧路径指令,就会漏冲一条。最终提交版在功能适配基线之上的网表差异只有 42 行,其中另一行关键改动是:

arbitration.flushIt setWhen jumpPipe.valid

它保证重定向流水寄存器里的旧路径指令在同拍被冲刷。收益和代价都很明确:

高扇出治理三法

S3v2 是主刀,但 105 MHz 不是靠一刀就收敛的。过程中形成了三条可复用的连线级重构方法,共同纪律是:不改值、不改协议、零新增状态元。

1. 级间寄存器切片 + 独立使能副本

对扇出数百的宽级间寄存器广播网,按位切片(例如 128 bit),并为每片生成独立的使能副本和 dont_touch 驱动副本。这样每片负载群就近驱动,布线局部化;使能信号同步复制,保证各片更新语义一致。

等价性是构造性的:同一个布尔表达式生成 k 个副本,负载按位切片恰好覆盖原负载全集,每个输出 bit 的次态函数字面不变。这个方法把 ROB push 占用度广播锥(fanout 621/687)消除,100 MHz 下 WNS 从 −0.057 ns 收敛到 +0.018 ns。

2. 优先级 mux 重结合

对“选择信号 → 地址函数(index 抽取/hash/递增)→ 存储读地址”的串行链,把选择操作从地址函数之前搬到每个 bank 加法器之后:各候选地址并行完成全部计算,晚到的选择信号到达后只剩一级独热多选。

等价性依据是重结合恒等式:

sel · f(x) ≡ f(sel · x)

先选后算与先算后选,在同一个选择语义下结果一致,真值表只有 3 行,可以穷尽验证。这个改动让 BTB/PHT 读地址锥从时序报告头部彻底消失,而且寄存器计数 21553 = 21553,逐位相等。

3. 并行前缀求和

压缩队列的 popCounts 原本是串行 Mux(b, pc+1, pc) 前缀链,级数随队列深度线性增长,是发射通路的固有长链。把它重构成并行前缀平衡求和树,级数从 6 级降到约 3 级;再辅以发射槽独热选择的 64 bit 切片副本和唤醒比较器副本。

等价性对全部 2^7 × 2 = 256 个输入组合(含不可达 grant 数)做数值穷尽验证,0 失配。

等价性不是“跑一遍功能测试”

时序重构最危险的地方是“功能测试过了,但边界条件下漏冲一条指令”。所以这套工作采用真值表级/逐拍级的验证标准,而不是仿真容差:

这套标准把“等价”从纸面主张变成了可重复的实验事实。也正因为如此,42 行的网表改动才敢直接上板。

结果与瓶颈

20 项性能测试里,几何平均是 3.817781304,但分布并不均匀:

benchmark IPC ratio system counter ratio
bitcount 1.847140 5.964467
coremark 1.035607 3.350227
stream_copy 1.188413 3.823799
quick_sort 0.869319 2.812430
fireye_C0 0.725695 2.347451

最低的几项都是分支密集负载:fireye_C0 0.7257、quick_sort 0.8693、bubble_sort 1.0226;计算密集的 bitcount 则达到 1.8471。分支误预测冲刷是 IPC 与三发射理论上限之间的最大缺口。 访存密集项的 CPU/SoC 计数比偏差最大,对应 DDR 延迟占比随频率上升的衰减机制。

这也解释了为什么频率不是越高越好:基座 IPC 从 25 MHz 的 1.527 衰减到 105 MHz 的 1.180。DDR 访存的绝对延迟不随频率下降,它的相对占比随频率上升,最终决定了频率收益的上界窗口。

下一步:四发射与更好的预测器

设计报告里记录了两个已经在实验线完成等价性实证、但未并入最终提交网表的方向:

  1. 压缩队列的循环队列重构:当前 IntIQ 的压缩位移链(queue_0 → queue_1 的寄存器到寄存器搬移)在 105 MHz 下是四发射扩展的架构级瓶颈;扇出/组合级修复在原理上不可收敛,唯一正解是循环队列。实测收益锚点是 3.875。
  2. S3v2 + mini-TAGE:把更强的预测器与已经收敛的前端时序结合,待频率余量重建后合入。

这两个方向都没有进入最终提交版。比赛提交的是稳定、可验证的那一版,实验线保留在仓库历史里,供赛后继续。

可迁移的教训

这次工作留下三条比分数更重要的经验:

  1. 先定位唯一不收敛的锥,再做时序重构。 105 MHz 下最差路径只有一条,改一条比改十条有效。
  2. 协议保持的重构,等价性由协议担保,但边界条件要自己验证。 m2sPipe() 是标准算子,但它带来的 +1 拍延迟会制造新的同拍依赖;flushIt 那一行才是功能等价的关键。
  3. “功能全过”不是等价性证明。 3 个随机种子、逐拍 bit 一致、零新增状态元,才足以支撑一次上板。

小结

这不是一个“自研 CPU”的故事,而是一个更常见、也更有借鉴意义的工程故事:在一个成熟的开源乱序核上,怎样把最后一条不收敛的路径解决掉,并且证明你没有改坏它。

最终结果是 105.887 MHz、setup WNS +0.035 ns、功能 58/58、性能 20/20、System counter ratio 3.817781304。数字属于整个团队和基座;本文拆解的是其中 S3v2 那 42 行。

基座与许可证声明、设计报告、20 项明细和实验线历史都在 nscscc2026-loongarch-cpu。如果你也在做 FPGA 上的乱序核,希望这篇能帮你少走一点“功能过了但时序不收敛”的弯路。