后端总览:从译码到精确提交¶
本文面向从事 RISC-V64 bring-up、性能分析和编译优化的软件工程师,目标不是逐行解释 RTL,而是建立一套可用于读性能现象的后端心智模型:一条指令在哪里获得物理寄存器、为什么会停在 Dispatch、何时真正完成,以及误预测、访存违例和异常如何恢复机器状态。
版本与阅读约定¶
- 仓库:
Daydream0929/XiangShan - 固定提交:
c7b373b - 主配置:
DefaultConfig。除“BackendV2Config 的区别”一节外,本文的宽度、容量和端口数都指该配置。 - 源码事实表示能从固定提交直接确认的结构或条件。
- 软件侧推导表示基于该结构得到的性能含义,仍需结合目标芯片计数器或仿真验证。
- 待验证表示源码无法独立证明,或可能被生成参数、芯片版本、PMU 接线改变的结论。
不要把 Chisel 默认参数自动等同于手中芯片。首先确认芯片对应的生成配置和 RTL 提交,再使用本文中的具体数字。
一张图理解后端¶
flowchart LR
FE["Frontend / IBuffer"] --> DEC["Decode\n标量译码与向量 uop 展开"]
DEC --> REN["Rename\nRAT + FreeList + Snapshot"]
REN --> DIS["Dispatch\nBusyTable + 资源门控"]
DIS --> ROB["ROB / RAB\n顺序与恢复日志"]
DIS --> IQ["Issue Queues\n按域等待操作数"]
IQ --> RF["RF / RegCache / Bypass"]
RF --> EXU["Integer / FP / Vector EXU"]
RF --> MEM["Load / Store / VLSU"]
EXU --> WB["Writeback Arbitration"]
MEM --> WB
WB --> ROB
ROB --> COMMIT["In-order Commit\n精确异常"]
EXU -. "branch/jump redirect" .-> REC["Redirect Generator"]
MEM -. "load violation replay" .-> REC
ROB -. "exception / interrupt / flush" .-> REC
REC -. "恢复 RAT / FreeList / ROB / RAB / VType" .-> REN
REC -. "杀死年轻 uop" .-> IQ
后端顶层实例化 CtrlBlock、整数/浮点/向量三个 Region,汇总执行结果和写回,并将最老 redirect 送回控制路径。Backend.scala:173-216 每个 Region 内包含 Issue Queue、DataPath、BypassNetwork、执行块和写回数据通路。Region.scala:38-120
DefaultConfig 的后端基线¶
Makefile 默认选择 DefaultConfig。Makefile:50 DefaultConfig 组合默认 Tile、缓存和 LLC 配置。Configs.scala:534-539
| 项目 | 默认值 | 性能分析时代表什么 |
|---|---|---|
| XLEN / VLEN / ELEN | 64 / 128 / 64 | 标量宽度、向量寄存器位宽、向量元素上限 |
| Decode / Rename / Commit | 8 / 8 / 8 | 理论前端到退休带宽上限,不是持续 IPC 保证 |
| ROB / RAB | 352 / 352 | 在途顺序窗口 / 目的映射恢复日志;两者单位不同 |
| VTypeBuffer | 64 | 在途向量类型状态的重排序容量 |
| Int preg | 224,4 bank | 整数物理寄存器容量与 bank 结构 |
| FP preg | 256 | 浮点物理寄存器容量 |
| VF preg | 128 | 向量/浮点向量数据物理寄存器容量 |
| V0 / VL preg | 22 / 32 | mask 与向量长度状态分别重命名 |
| 标量 Load / Store | 3 / 2 | 访存执行通路数量;还会受 LSQ、cache、TLB 限制 |
| 向量访存通路 | 2 | 其中一条承担 segment 类操作 |
参数来源:Parameters.scala:48-55、Parameters.scala:85-153、Parameters.scala:168-187。
软件侧推导¶
- 8-wide 只描述某些级的最大宽度。分支恢复、复杂向量指令展开、寄存器短缺、IQ/LSQ/ROB 满、数据依赖和写回冲突都可能降低实际 IPC。
- ROB 和 RAB 都是 352 并不表示“一条指令恰好占两边各一项”。ROB 跟踪顺序完成;RAB 只为实际发生的目的寄存器映射记日志。
- 向量状态不是隐藏的单一全局寄存器:V0、VL、向量目的寄存器和 vtype 都参与乱序跟踪,因此向量代码会制造标量代码没有的资源压力。
Decode:架构指令变成什么 uop¶
源码事实¶
DecodeStage 使用 8 个简单译码器,并用 DecodeUnitComp 处理复杂向量指令;它还定位本组中的第一条复杂向量指令。DecodeStage.scala:113-164
复杂向量指令产生的 uop 被放在简单标量结果之前,并受可用输出槽约束。DecodeStage.scala:219-246 Decode 会重排向量源,单独转换 V0,并断言一个 uop 最多写一个寄存器文件。DecodeStage.scala:247-265
向量译码生成 mask、old-vd、partial-vd、VL、vtype 和 uop 信息;非法指令检查还覆盖特权级、FS/VS、舍入模式和部分 CBO 约束。DecodeUnit.scala:905-948 DecodeUnit.scala:1101-1168
vtype 恢复期间,Decode 可能不接受新的向量工作;本级提供 stall_cycle、recovery_bubble、输入 valid/fire 和融合指令计数。DecodeStage.scala:166-206 DecodeStage.scala:423-450
软件侧推导¶
- 一条 RVV 指令可能占用多个内部 uop 槽,因此同样的架构指令数不能直接与标量代码比较 decode 压力。
- 频繁改变
vtype或在错误路径上执行向量状态更新,会放大恢复气泡。 - old-vd 是否形成真实依赖取决于 VL、tail/mask policy;不要只从汇编中的目的寄存器名字推断依赖图。
Rename:消除假依赖并建立恢复点¶
RAT、FreeList 与 move elimination¶
Rename 为整数、FP、VF、V0、VL 分别维护映射和空闲物理寄存器。整数使用支持 move elimination 的 MEFreeList,其他域使用 StdFreeList。Rename.scala:114-124
RenameTable 同时维护 speculative table 和 architectural table。redirect 时,speculative table 可从选中的 snapshot 或 architectural table 恢复;提交更新 architectural table,并计算可释放的旧物理寄存器。RenameTable.scala:105-177
整数 move 可令新目的直接复用源物理寄存器,不再分配新寄存器;同一 rename 组内还会逐 lane 旁路刚建立的映射。Rename.scala:684-759
Rename 输出按组门控:只要本组需要的任一 FreeList、LSQ 或下游 Dispatch 资源不足,整组就不能前进。Rename.scala:264-305 Rename.scala:450-580
Snapshot 的准确边界¶
默认只配置 4 个 rename snapshot;生成 snapshot 的间距门限为 RobCommitWidth * 4,默认即 32 个 ROB 项。当前触发条件明确写成 FuType.isJump 且要求 first uop,并不是每条条件分支都创建快照。Rename.scala:777-805
软件侧推导¶
rename_stall_cycle_*Fl表示物理寄存器生命周期压力,不等同于编译器 spill。大展开、大 LMUL、长依赖链和较晚提交都可能让旧映射迟迟不能回收。- V0/VL/Vec FreeList 短缺可以连带阻塞同一 rename 组里的标量 uop。
- move elimination 能减轻整数物理寄存器压力;是否真正生效应以 RTL 计数器或仿真为准,不能仅看到
mv伪指令就下结论。
Dispatch:有空位不代表能进入执行队列¶
源码事实¶
Dispatch 为 Int、FP、VF、V0、VL 维护 BusyTable。分配物理目的时置 busy,写回或有效 wakeup 清除 busy,load cancel 等失败路径可重新置 busy。Dispatch.scala:242-360 BusyTable.scala:144-176
向量 old-vd 在 VL、VTA、VMA 满足条件时可以被判定为无需等待。Dispatch.scala:363-384
Dispatch 会在兼容 IQ 间比较占用并选择较空者;若发生 stall,则保持原选择,防止 uop 在队列间来回摆动。Dispatch.scala:412-559
Load、Store 和向量访存还必须通过每周期结构限制以及 LQ/SQ 容量检查。Dispatch 保持程序顺序,只有实际 fire 的 uop 才会进入 ROB。Dispatch.scala:667-708 Dispatch.scala:835-879
可观察的 stall 分类包括 ROB、IQ、LSQ、FreeList、各执行类型和各 IQ 类型。Dispatch.scala:889-899 Dispatch.scala:1029-1082
ROB 与 RAB:完成、提交和恢复不是一回事¶
flowchart TD
UOP["uop dispatched"] --> ROBE["分配 ROB entry"]
UOP -->|"有真实目的映射"| RABE["写 RAB mapping log"]
ROBE --> EXEC["执行 / replay / 写回"]
EXEC --> DONE["ROB completion tracking"]
DONE --> HEAD{"到达 ROB head?"}
HEAD -->|否| WAIT["保持乱序完成状态"]
HEAD -->|是,正常| RETIRE["顺序提交并释放旧映射"]
HEAD -->|是,异常/中断/replay| FLUSH["生成精确 flush"]
FLUSH --> WALK["ROB + RAB + VType walk"]
WALK --> RESUME["恢复取指与重命名"]
ROB¶
ROB 为 352 项、8 bank。它能否接收新工作不仅取决于 ROB 自身,还取决于 RAB、VTypeBuffer 和向量异常合并资源。Rob.scala:164-209
ROB 记录 uop 的完成数,并按程序顺序提交。异常、中断和 replay 只有在相关项到达 ROB 头且满足完成条件时才成为架构可见事件。Rob.scala:607-699 Rob.scala:771-820
RAB¶
RAB 的一项对应一次实际目的映射,而不是必然对应一条指令。提交时它把旧、新映射送回 Rename;恢复时按相反方向 walk。Rab.scala:34-64 Rab.scala:215-274
有可用 snapshot 时,RAB 可以从快照位置直接进入 walk;没有快照时先执行 special_walk。ROB 只有等待 ROB、RAB、VType 三者的恢复都完成后才回到 idle。Rob.scala:870-955
精确异常与向量 vstart¶
ExceptionGen 先按 ROB 顺序选择最老异常;若多个异常属于同一 ROB 项,再根据 vuopIdx/vstart 保留最早的向量异常。ExceptionGen.scala:49-175 向量 load 异常还会等待必要的 RAB 映射提交。Rob.scala:621-639
软件侧推导: 即使年轻指令已经执行完,也不能越过 ROB 头异常提交。性能采样看到某条年轻指令“已完成”并不代表它已退休。RVV 异常调试应同时检查 vstart、faulting element、目的映射恢复和 ROB 头状态。
Redirect、恢复与 replay 的三种语义¶
Redirect 携带 ROB 位置、flush 级别、目标、误预测以及调试分类;多个候选中选择程序顺序最老者。Bundle.scala:211-287
RedirectGenerator 在执行单元 redirect 与 load replay 间选择最老者,过滤已经被更老 redirect 冲刷的请求;ROB flush 具有更高优先级。RedirectGenerator.scala:27-61 CtrlBlock 随后杀死错误路径写回,并选择不晚于 redirect 的最近有效 snapshot 恢复 RAT、FreeList、ROB 和 RAB。CtrlBlock.scala:109-151 CtrlBlock.scala:557-604
调试时必须区分三类“重试”:
| 类别 | 是否重定向前端 | 典型触发 | 观察位置 |
|---|---|---|---|
| Issue/operand retry | 否 | RF 仲裁失败、load cancel、OG 阶段未成功 | og0Failed、og1Failed、IQ finalSuccess |
| Load pipeline replay | 通常否 | cache/TLB、bank conflict、数据未就绪、RAW/RAR 等 | load replay cause、fast/high/low replay |
| Load ordering violation | 是 | 已执行 load 被发现违反内存顺序 | loadReplay redirect、恢复周期 |
IQ 的早期失败反馈见 DataPath.scala:608-648。Load pipeline 的 replay 仲裁与计数见 NewLoadUnit.scala:95-155 和 NewLoadUnit.scala:482-500。Replay 原因枚举见 LoadQueueReplay.scala:31-72。内存顺序违例形成 CtrlBlock loadReplay 的路径见 CtrlBlock.scala:208-213。
当前提交保留 ROB replayInst 基础设施,但源码搜索未发现默认 FU 把 replayInst 置真;因此它应列为待验证/扩展路径,而不是默认活跃的第四类 replay。
面向软件性能工程师的诊断顺序¶
先确定“窗口为什么不能退休”,再追执行端。单看 IPC 或单个 cache miss 计数通常不足以定位后端瓶颈。
| 症状 | 第一组证据 | 第二组证据 | 可能的软件动作 |
|---|---|---|---|
| ROB 长期接近满,提交低 | ROB occupancy、head wait 类型 | 对应 IQ、load replay、cache/TLB | 缩短关键链;改善局部性;检查异常/序列化指令 |
| Rename 停顿 | Int/FP/Vec/V0/VL FreeList stall | ROB 提交与物理寄存器利用率 | 减少展开和 live range;调整 LMUL;帮助编译器缩短变量生命周期 |
| 分支恢复气泡高 | BR_MIS_PRED、TOTAL_FLUSH |
recovery bubble、walk cycle、snapshot 使用 | 改善热路径布局;降低难预测控制流;评估 if-conversion |
| Load replay 高 | 各 replay cause | LQ/SQ、cache/TLB、RAW/RAR | 减少地址别名;改善对齐/局部性;检查 store-to-load 模式 |
| 向量 IPC 低 | Vec/V0/VL FreeList、VecIQ、VLSU stall | old-vd、RF 端口、replay | 调整 LMUL/展开;减少无谓 mask/merge 依赖;改善向量访存对齐 |
ROB 提供 commit IPC/CPI、ROB head wait、各 FuType 的 dispatch/select/issue/execute/commit 延迟分解、walk 和 snapshot 使用统计。Rob.scala:1324-1426 分支误预测、flush、异常、replay 和 ROB 占用统计见 Rob.scala:1693-1740。
待验证:
XSPerf名称是否映射到量产芯片的mhpmevent或 Linuxperf事件,取决于生成配置、PMU 接线、特权软件和芯片版本。源码中存在计数器,不等于用户态一定能直接读取。
DefaultConfig 与 BackendV2Config 的区别¶
BackendV2Config 继承 DefaultConfig 后覆写参数,并带 DeprecatedConfigWarning;它不是本提交 Makefile 的默认选择。Configs.scala:447-490
| 参数 | DefaultConfig | BackendV2Config |
|---|---|---|
EnableBackendV2Config |
false | true |
| Dispatch IQ balance | true | false |
IBuffer NumReadBank |
基础配置值 | 6 |
| DecodeWidth | 8 | 6 |
| RenameWidth | 8 | 6 |
| RabCommitWidth | 8 | 6 |
| CommitWidth / RobCommitWidth | 8 | 未覆写,仍为 8 |
| ROB | 352 | 208 |
| RAB | 352 | 256 |
| Int preg | 224,4 bank | 224,1 bank |
| FP preg | 256 | 192,1 bank |
| VF preg | 128 | 128,1 bank |
| V0 / VL preg | 22 / 32 | 22 / 32,均 1 bank |
当 EnableBackendV2Config=true 时,后端改用 BackendV2SchdParams 的整数、FP、向量 scheduler 和 wakeup 拓扑。Parameters.scala:503-517 因此不能把 V2 的执行单元数量、RF 端口编号或 IQ 深度混入 DefaultConfig 分析。
已知待验证点¶
- 实际 CPU 使用的生成配置、提交和后续 patch 是否与
c7b373b + DefaultConfig一致。 Rename.scala:1057-1059对 Vec/V0/VL 使用了重复的内部累计名称stall_cycle_vec,而命名事件在后文分开,需在生成 RTL/仿真中确认最终计数器行为。源码Dispatch.scala:899的 LSQ stall 条件使用!lsqCanAccept,而dispatch_stall_cycle_lsq事件处使用lsqCanAccept,命名与条件疑似不一致,不能未经验证直接用于归因。源码一 源码二- Load replay 枚举中的
C_WF、C_MF在当前 NewLoadUnit 路径未见有效置位;应先视为保留原因,再用波形或生成 RTL确认。 - rename snapshot 只由
FuType.isJump触发是本提交的源码事实;其微架构设计意图及量产 RTL 是否保持该策略仍需验证。