Vortex GPGPU Warp 调度与指令执行流程解读
目标:读完后你能看清 Vortex 的完整执行过程和依赖关系,并知道性能分析和配置调优应该从哪里下手。
1. 三十分钟版本:先记住这几条结论
Vortex 是一个按序(in-order)的 SIMT GPGPU 核心。 它最反直觉、也最重要的一点是:
调度单位是 warp,但流水线上流动的是一条条指令。
由此推出五条主线:
-
调度粒度 = warp × 1 条指令。 调度器每次只选中一个 warp,只发出该 warp 当前 PC 对应的一条取指请求(该 warp 内所有激活线程并行执行这同一条指令,即 SIMT 的"一个 warp = 一条指令、多个线程")。
-
warp 被调度后立即锁定("单飞行"窗口)。
schedule_fire触发时stalled_warps_n[schedule_wid] = 1,该 warp 在这条指令解锁之前不可能 被再次调度------哪怕它后面的指令与当前指令毫无数据依赖。前端的串行性是结构性的,不是依赖性的。 -
解锁有两条路径:
- 普通指令(ALU/FPU/LSU 等,
is_wstall=0)在 decode 阶段组合逻辑内即解锁 ------解锁等的是"下一条 PC 的确定性",不是"运算结果",所以fadd.s不等 FPU 算完; - 控制流 / warp-control 指令(JAL/JALR/BRANCH、TMC、WSPAWN、SPLIT、BAR、wsync 等
is_wstall=1)保持 stall,直到 execute 阶段算出branch_ctl_if/warp_ctl_if才解锁。
- 普通指令(ALU/FPU/LSU 等,
-
每一条指令都要完整走一遍
schedule → fetch → decode → issue(ibuffer/scoreboard/operands/dispatcher) → execute → commit。这是对每条指令而言的,不是对整个 CTA/kernel 只走一次。CTA dispatcher 只在 warp 激活时参与一次,之后该 warp 的所有指令都走通用指令流水线。 -
真正的并行度来自多 warp 交织(warp-level parallelism)+ 执行单元内部流水 ,而不是单个 warp 的指令级并行。前端
schedule_fire → decode unlock的往返延迟(≈ IFETCH_LT ≈ 9.87 cycle)决定了单个 warp 每隔约 10 cycle 才能产出一条新指令;要让 FMA 达到 1 条/cycle,需要的 warp 数 N ≥ 往返延迟(Little's Law:吞吐 = 并发数 / 延迟)。
一句话记住整个架构:
调度器选 warp → 前端串行取指解码 → scoreboard 放行发射 → 执行单元并行计算 → commit 仲裁写回并释放依赖。
2. 名词表(先统一语言)
| 术语 | 含义 | 直觉类比 |
|---|---|---|
| warp | 一组同步执行的线程(如 32 个),共享一条 PC,按 tmask 掩码激活 | 一支"齐步走的队伍" |
| CTA / block | Cooperative Thread Array,由若干 warp 组成 | 一个"排" |
| grid | 全部 CTA 的集合,即一次 kernel launch | 整个"连" |
| SIMT | Single Instruction, Multiple Threads | 一个口令,多人并行执行 |
| tmask | 线程掩码,标记 warp 内哪些 lane 活跃 | 点名册 |
| RAW | Read-After-Write:后一条指令读前一条还没写回的寄存器 | "我要用你还没写完的东西" |
| WAW | Write-After-Write:两条指令写同一个寄存器 | "两人抢着写同一个格子" |
| scoreboard | 为每个 warp 的每个架构寄存器维护 in-use 标志,阻塞依赖指令发射 | "占用告示牌",不是寄存器堆 |
| ibuffer | 解码后的指令缓冲,深度 VX_CFG_IBUF_SIZE |
待办队列 |
| FU | Functional Unit 执行单元(ALU/LSU/SFU/FPU/TCU) | 工位 |
| uop | 复杂宏指令(TCU WMMA、LSU pack-load 等)拆出的内部微操作序列 | 工序拆解 |
| backpressure | 下游 ready=0,阻塞一路向上游传导 | 下游堵车,上游也动不了 |
| elastic/skid buffer | 寄存器+握手实现的 1~2 项缓冲,解耦时序、吸收瞬时背压 | 缓冲带 |
握手约定 (理解所有 STALL 的钥匙):valid 由上游置位,ready 由下游置位,仅当 fire = valid && ready 时传输真正发生。
3. 全局流程图
3.1 核心指令流水线(VX_core.sv 中的模块串联)
#mermaid-svg-fsCaaoNEx8TZjXlW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fsCaaoNEx8TZjXlW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fsCaaoNEx8TZjXlW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fsCaaoNEx8TZjXlW .error-icon{fill:#552222;}#mermaid-svg-fsCaaoNEx8TZjXlW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fsCaaoNEx8TZjXlW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fsCaaoNEx8TZjXlW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fsCaaoNEx8TZjXlW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fsCaaoNEx8TZjXlW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fsCaaoNEx8TZjXlW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fsCaaoNEx8TZjXlW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fsCaaoNEx8TZjXlW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fsCaaoNEx8TZjXlW .marker.cross{stroke:#333333;}#mermaid-svg-fsCaaoNEx8TZjXlW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fsCaaoNEx8TZjXlW p{margin:0;}#mermaid-svg-fsCaaoNEx8TZjXlW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fsCaaoNEx8TZjXlW .cluster-label text{fill:#333;}#mermaid-svg-fsCaaoNEx8TZjXlW .cluster-label span{color:#333;}#mermaid-svg-fsCaaoNEx8TZjXlW .cluster-label span p{background-color:transparent;}#mermaid-svg-fsCaaoNEx8TZjXlW .label text,#mermaid-svg-fsCaaoNEx8TZjXlW span{fill:#333;color:#333;}#mermaid-svg-fsCaaoNEx8TZjXlW .node rect,#mermaid-svg-fsCaaoNEx8TZjXlW .node circle,#mermaid-svg-fsCaaoNEx8TZjXlW .node ellipse,#mermaid-svg-fsCaaoNEx8TZjXlW .node polygon,#mermaid-svg-fsCaaoNEx8TZjXlW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fsCaaoNEx8TZjXlW .rough-node .label text,#mermaid-svg-fsCaaoNEx8TZjXlW .node .label text,#mermaid-svg-fsCaaoNEx8TZjXlW .image-shape .label,#mermaid-svg-fsCaaoNEx8TZjXlW .icon-shape .label{text-anchor:middle;}#mermaid-svg-fsCaaoNEx8TZjXlW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fsCaaoNEx8TZjXlW .rough-node .label,#mermaid-svg-fsCaaoNEx8TZjXlW .node .label,#mermaid-svg-fsCaaoNEx8TZjXlW .image-shape .label,#mermaid-svg-fsCaaoNEx8TZjXlW .icon-shape .label{text-align:center;}#mermaid-svg-fsCaaoNEx8TZjXlW .node.clickable{cursor:pointer;}#mermaid-svg-fsCaaoNEx8TZjXlW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fsCaaoNEx8TZjXlW .arrowheadPath{fill:#333333;}#mermaid-svg-fsCaaoNEx8TZjXlW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fsCaaoNEx8TZjXlW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fsCaaoNEx8TZjXlW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fsCaaoNEx8TZjXlW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fsCaaoNEx8TZjXlW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fsCaaoNEx8TZjXlW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fsCaaoNEx8TZjXlW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fsCaaoNEx8TZjXlW .cluster text{fill:#333;}#mermaid-svg-fsCaaoNEx8TZjXlW .cluster span{color:#333;}#mermaid-svg-fsCaaoNEx8TZjXlW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fsCaaoNEx8TZjXlW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fsCaaoNEx8TZjXlW rect.text{fill:none;stroke-width:0;}#mermaid-svg-fsCaaoNEx8TZjXlW .icon-shape,#mermaid-svg-fsCaaoNEx8TZjXlW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fsCaaoNEx8TZjXlW .icon-shape p,#mermaid-svg-fsCaaoNEx8TZjXlW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fsCaaoNEx8TZjXlW .icon-shape .label rect,#mermaid-svg-fsCaaoNEx8TZjXlW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fsCaaoNEx8TZjXlW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fsCaaoNEx8TZjXlW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fsCaaoNEx8TZjXlW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} schedule_if
{wid, PC, tmask}
fetch_if · STALL_FETCH
decode_if · STALL_IBUF
staging_if
scoreboard_if
operands_if
dispatch_if · STALL_FPU/ALU/...
commit_if
writeback_if 释放依赖
writeback_if 写回寄存器堆
VX_scheduler
选 warp
VX_fetch
发 icache 请求
VX_decode
译码 + 提前解锁判定
IBuffer
VX_scoreboard
RAW/WAW 检测 · STALL_SCRB
VX_operands / VX_opc_unit
取操作数 · STALL_OPDS
VX_dispatcher
按 ex_type 路由
执行单元
ALU/LSU/SFU/FPU/TCU
VX_commit
固定优先级仲裁 + 写回
3.2 从 Kernel Launch 到执行完成的闭环
#mermaid-svg-CLhFhmNZSZAonZ6F{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CLhFhmNZSZAonZ6F .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CLhFhmNZSZAonZ6F .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CLhFhmNZSZAonZ6F .error-icon{fill:#552222;}#mermaid-svg-CLhFhmNZSZAonZ6F .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CLhFhmNZSZAonZ6F .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CLhFhmNZSZAonZ6F .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CLhFhmNZSZAonZ6F .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CLhFhmNZSZAonZ6F .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CLhFhmNZSZAonZ6F .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CLhFhmNZSZAonZ6F .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CLhFhmNZSZAonZ6F .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CLhFhmNZSZAonZ6F .marker.cross{stroke:#333333;}#mermaid-svg-CLhFhmNZSZAonZ6F svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CLhFhmNZSZAonZ6F p{margin:0;}#mermaid-svg-CLhFhmNZSZAonZ6F .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CLhFhmNZSZAonZ6F .cluster-label text{fill:#333;}#mermaid-svg-CLhFhmNZSZAonZ6F .cluster-label span{color:#333;}#mermaid-svg-CLhFhmNZSZAonZ6F .cluster-label span p{background-color:transparent;}#mermaid-svg-CLhFhmNZSZAonZ6F .label text,#mermaid-svg-CLhFhmNZSZAonZ6F span{fill:#333;color:#333;}#mermaid-svg-CLhFhmNZSZAonZ6F .node rect,#mermaid-svg-CLhFhmNZSZAonZ6F .node circle,#mermaid-svg-CLhFhmNZSZAonZ6F .node ellipse,#mermaid-svg-CLhFhmNZSZAonZ6F .node polygon,#mermaid-svg-CLhFhmNZSZAonZ6F .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CLhFhmNZSZAonZ6F .rough-node .label text,#mermaid-svg-CLhFhmNZSZAonZ6F .node .label text,#mermaid-svg-CLhFhmNZSZAonZ6F .image-shape .label,#mermaid-svg-CLhFhmNZSZAonZ6F .icon-shape .label{text-anchor:middle;}#mermaid-svg-CLhFhmNZSZAonZ6F .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CLhFhmNZSZAonZ6F .rough-node .label,#mermaid-svg-CLhFhmNZSZAonZ6F .node .label,#mermaid-svg-CLhFhmNZSZAonZ6F .image-shape .label,#mermaid-svg-CLhFhmNZSZAonZ6F .icon-shape .label{text-align:center;}#mermaid-svg-CLhFhmNZSZAonZ6F .node.clickable{cursor:pointer;}#mermaid-svg-CLhFhmNZSZAonZ6F .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CLhFhmNZSZAonZ6F .arrowheadPath{fill:#333333;}#mermaid-svg-CLhFhmNZSZAonZ6F .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CLhFhmNZSZAonZ6F .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CLhFhmNZSZAonZ6F .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CLhFhmNZSZAonZ6F .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CLhFhmNZSZAonZ6F .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CLhFhmNZSZAonZ6F .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CLhFhmNZSZAonZ6F .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CLhFhmNZSZAonZ6F .cluster text{fill:#333;}#mermaid-svg-CLhFhmNZSZAonZ6F .cluster span{color:#333;}#mermaid-svg-CLhFhmNZSZAonZ6F div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CLhFhmNZSZAonZ6F .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CLhFhmNZSZAonZ6F rect.text{fill:none;stroke-width:0;}#mermaid-svg-CLhFhmNZSZAonZ6F .icon-shape,#mermaid-svg-CLhFhmNZSZAonZ6F .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CLhFhmNZSZAonZ6F .icon-shape p,#mermaid-svg-CLhFhmNZSZAonZ6F .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CLhFhmNZSZAonZ6F .icon-shape .label rect,#mermaid-svg-CLhFhmNZSZAonZ6F .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CLhFhmNZSZAonZ6F .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CLhFhmNZSZAonZ6F .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CLhFhmNZSZAonZ6F :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} kmu_bus_if
cta_fire/cta_wid/PC/tmask
普通指令 is_wstall=0:
decode_sched_if.unlock=1
控制流/SFU 指令 is_wstall=1
commit_if
writeback_if
writeback_if
branch_ctl_if 分支解析完成
warp_ctl_if: tmc/split/join/
bar_unlock/wsync
tmc 且 tmask==0
warp_done / cta_warp_done
Host: DCR 写入 + start 脉冲
VX_kmu
Grid Walk 生成 CTA
VX_cta_dispatch
CTA 展开为 Warp
VX_scheduler
激活 warp
ready_warps = active_warps & ~stalled_warps
preferred_warps 过滤 ibuf_full
优先级编码器选出 schedule_wid
schedule_fire:
stalled_warps_nschedule_wid=1 锁定
VX_fetch 发 icache 请求
VX_decode
立即解锁
PC+4(或 RVC +2/+4)
IBuffer
Scoreboard RAW/WAW 检测
Operands
Dispatcher
执行单元 ALU/LSU/SFU/FPU/TCU
VX_commit
ALU > LSU > SFU > FPU > TCU
解锁: stalled_warps_n 清 0
3.3 关键接口信号一览
| 接口 | 生产者 → 消费者 | 承载内容 | 阻塞 → 计数器 |
|---|---|---|---|
schedule_if |
scheduler → fetch | wid, PC, tmask | icache 未就绪 → STALL_FETCH |
fetch_if / decode_if |
fetch → decode | 指令字 + 译码结果 | ibuffer 满 → STALL_IBUF |
staging_if |
ibuffer → scoreboard | 已译码指令、寄存器掩码 | 依赖未解除 → STALL_SCRB |
scoreboard_if |
scoreboard → operands | 可发射指令 | --- |
operands_if |
operands → dispatcher | 操作数已就绪 | regfile bank 冲突 → STALL_OPDS |
dispatch_if[u] |
dispatcher → FU | 按 ex_type 分发 | FU 队列将满 → STALL_FPU/STALL_ALU/... |
commit_if[u] |
FU → commit | 执行结果 | 提交端口竞争(内部) |
writeback_if |
commit → scoreboard / operands | 写回结果、释放依赖 | 无条件 ready(不阻塞) |
branch_ctl_if |
execute → scheduler | 分支目标 PC | 延迟解锁 |
warp_ctl_if |
execute → scheduler | tmc/split/join/bar/wsync | 延迟解锁 |
4. 执行过程逐一介绍
4.1 Kernel Launch:谁把 warp 生出来
VX_kmu:host 写下 DCR 并给出 start 脉冲后,做 grid walk,逐个生成 CTA。VX_cta_dispatch:把一个 CTA 展开为若干 warp,写入active_warps[cta_wid]、warp_pcs[cta_wid]、thread_masks[cta_wid]。- 此后 CTA dispatcher 就"下班"了 。它只在 warp 激活时参与一次;该 warp 后续几十条指令不再经过 CTA dispatcher。接管它的是
VX_scheduler自身的选择逻辑(ready_warps + 优先级编码器)。
warp 变为 active 后,没有固定的"下一拍被调度"时延------只要 ready_warps 非空且优先级编码器轮到它,即可被选中。warp 的 PC 由 warp_pcs 寄存器数组统一管理,每个 warp 一个槽位,由多个事件源写入(激活、顺序推进、分支/陷入、split/join、wspawn),时钟上升沿锁存。
4.2 调度选择:怎么挑下一个 warp
text
ready_warps = active_warps & ~stalled_warps
preferred_warps = ready_warps 再剔除 ibuffer 已满的 warp
schedule_wid = VX_priority_encoder(preferred_warps)
schedule_fire = schedule_if.valid && schedule_if.ready,其中:
- valid :调度器选出 warp 且
out_buf(容量 2 的弹性缓冲)未满; - ready :
icache_req_ready(req_buf 未满)或 RVC decompressor 缓冲命中(sched_buffered_match)。
选中后 stalled_warps_n[schedule_wid] = 1 立即上锁------"单飞行"窗口开始。
4.3 Fetch:取指令(全核唯一的 icache 通道)
VX_fetch 接收 schedule_if(wid, PC, tmask),向 icache 发请求。要点:
- 每个 VX_core 只有一个
icache_bus_if接口 ,所有 warp 的请求都要排队经过req_buf(容量 2、OUT_REG=1的弹性缓冲:加一级寄存器改善时序 + 吸收 1~2 拍瞬时背压,避免 icache 的 ready 组合进schedule_if关键路径)。 - 每 warp 最多一个在途请求 (
inflight位)------"单飞行"在 fetch 层面的体现。 - 两类"慢"要区分:
- 请求被拒绝 (
icache_req_ready=0):req_buf 满,被同核其它 warp 或共享 icache 的其它核心抢占 → 计入STALL_FETCH; - 响应慢 (icache miss / L2/L3/DRAM 延迟):体现为
IFETCH_LT变大 ,但不计入STALL_FETCH------fetch 只要 tag 槽位没满仍可接受其它 warp 的请求。
- 请求被拒绝 (
计数器:
平均取指延迟 = VX_CSR_MPM_IFETCH_LT / VX_CSR_MPM_IFETCHES(cycle/request)。
4.4 Decode:译码与"提前解锁"判定
VX_decode 把指令字段(opcode/funct3/rd/rs1/rs2/rs3)解析为 ex_type、op_type、寄存器使用掩码等,同时做两件事:
- 检查目标 ibuffer 是否已满 :满则记
STALL_IBUF,不推进。 - 产生
decode_sched_if.unlock:- 普通指令(
is_wstall=0):unlock 在 decode 阶段组合逻辑内即成立(valid 与 unlock 同拍)------解锁等的是"下一条 PC 的确定性",不是"运算结果"; - 控制流 / warp-control 指令(
is_wstall=1):保持 stall,直到 execute。
- 普通指令(
4.5 PC 推进:warp 怎么"往前走"
由 decode_sched_if 触发:
- 顺序路径:PC += 指令长度(+4,或 RVC 下 +2/+4);
- 控制流路径:遇到分支、trap、split/join、wspawn 时,
warp_pcs被branch_ctl_if/warp_ctl_if里的新 PC 覆盖式写入(PC := target),不再是"加 4"。
PC 推进同时清除 stall,该 warp 重新进入 ready_warps,可再次被调度。这段"schedule_fire → 解锁 → 重新 ready"的往返就是单飞行窗口的精确边界 ,其长度 = icache 请求/响应 + decode 寄存器级数(≈ IFETCH_LT)。
4.6 Issue 四件套:ibuffer → scoreboard → operands → dispatcher
这是整条流水线最容易堵的一段。
- 真正做"发射(issue)"判定与仲裁的是
VX_scoreboard,不是 dispatcher。VX_issue_slice.sv中scoreboard_fire = scoreboard_if.valid && scoreboard_if.ready即标记为warp_issued并上报调度器。 - scoreboard 不做寄存器分配/池化 。它只给每个 warp 的每个架构寄存器维护 in-use 标志:目的寄存器写回落库前,阻止后续读/写同一寄存器的指令发射------经典的 RAW/WAW 冒险检测,与物理寄存器资源耗尽无关。
operands_ready = 0的五种根因(都是 STALL_SCRB 的子原因):
| 根因 | 判定条件 | 含义 |
|---|---|---|
| 数据冒险 RAW/WAW | `inuse_regs & (ibf_regs_mask | stg_regs_mask) != 0` |
| 特殊寄存器冒险 | `inuse_xregs & (ibf_xregs_mask | stg_xregs_mask) != 0` |
| 连续预留冲突 | 同一 cycle 对 rd/xreg 的连续预留 | ibf_rsv / stg_rsv |
| FU 队列将满 | fu_pending 达到 alm_full |
ibf_goingfull / stg_goingfull |
| FU 锁 | warp 非 FU 锁持有者(uop 序列独占) | arb_valid_in 被抑制 |
最终:arb_valid_in = staging_if.valid && operands_ready && !fu_locked → out_arb 仲裁 → scoreboard_if.valid。
STALL_OPDS:VX_opc_unit从寄存器堆读操作数时因 bank 冲突受阻。STALL_FPU/STALL_ALU/... :dispatcher 发现目标 FU 调度队列已满(fu_goingfull),指令发不出去。
发射槽位与 SIMD:从一条指令到真正的硬件并行
| 概念 | 含义 | 公式 / 代码 |
|---|---|---|
Issue 槽位 (VX_CFG_ISSUE_WIDTH) |
每个 core 内并行、独立的发射流水线数量 | VX_issue 实例化 ISSUE_WIDTH 个 VX_issue_slice |
| 每槽位的 warp 数 | warp 按 wid % ISSUE_WIDTH 静态映射到某槽位,组内竞争 scoreboard 仲裁器 |
PER_ISSUE_WARPS = NUM_WARPS / ISSUE_WIDTH |
| 资源占用释放时机 | 目的寄存器在发射时标记"占用中",直到 commit/writeback 才释放;多 uop 序列额外持有 FU 锁 | scoreboard_->reserve/release;RTL 侧 FU lock |
| 执行单元 block = Issue 槽位 | 每个 issue 槽位 1:1 对应一个 ALU/LSU/FPU "block" | NUM_{ALU,LSU,FPU}_BLOCKS = ISSUE_WIDTH |
| SIMD lane 拆分(不是重新发射) | 若 NUM_THREADS > NUM_LANES,一条指令被拆成 NUM_THREADS/NUM_LANES 个顺序 SIMD packet,由 FU 内部的 lane-dispatch 逻辑处理,不会再回到 issue/scoreboard 仲裁 |
PID_BITS = CLOG2(NUM_THREADS/NUM_LANES) |
| FMA/PE 串行化 | 在一个 SIMD packet 的 lane 内部,若物理 FMA PE 数小于 lane 数,VX_pe_serializer 把这些 lane 按 NUM_LANES/NUM_PES 批次串行送入 PE |
--- |
三个容易混淆的层次,务必分开:
- uop(微操作) :Vortex 把一条复杂的"宏指令"拆成若干条更简单的内部指令。普通指令通常 1 条指令 = 1 个 uop(直通不拆分);部分复杂指令(TCU 的 WMMA/WGMMA、LSU 的 pack-load、RTU 的 TRACE/GETW、OM 的 fragment export)会被
VX_uop_sequencer展开成一串 uop,依次送入后续流水线。uop 展开发生在 issue 之后,但多 uop 序列会让该 warp 持有 FU 锁,期间同 FU 的其它发射被抑制。 - SIMD packet(pid) :一条指令按
NUM_PACKETS = SIMD_WIDTH / NUM_LANES拆出的顺序批次,每批正好填满NUM_LANES个 lane,由VX_lane_dispatch/VX_lane_gather负责拆分与汇总。SIMD lane 是执行单元内部物理并行通道数(VX_CFG_NUM_ALU_LANES/NUM_FPU_LANES),每个 lane 对应一个线程的数据通路。 - PE 串行化 :
VX_pe_serializer在 lane 之下还有一层------若物理 FMA PE 数 < lane 数,packet 内的 lane 还要按批次串行进 PE。
配置上
VX_CFG_SIMD_WIDTH直接等于VX_CFG_NUM_THREADS,不是独立参数。VX_lane_dispatch位于每个执行单元(VX_alu_unit/VX_fpu_unit/VX_lsu_unit)内部、dispatch 之后、真正执行逻辑之前,和负责展开宏指令的VX_uop_sequencer是完全不同的两层机制。
4.7 Execute → Commit:提交仲裁与依赖释放
VX_commit用固定优先级仲裁 :ALU > LSU > SFU > FPU > TCU。同一周期多个 FU 都想提交,只有一个能过,其余等待------这是 commit 阶段 STALL 的根因(多个执行单元竞争同一个 per-issue-slot 提交端口)。- 同一个
writeback_if干两件事 (语义不同,不要混淆):- 给 scoreboard :释放
inuse_regs/inuse_xregs(依赖解除,下游指令可发射); - 给 operands / 寄存器堆:真正把结果写回。
- 给 scoreboard :释放
writeback_if无条件 ready:写回路径永远不会反压执行单元。
4.8 CTA 退休闭环
VX_scheduler 根据 warp_ctl_if.tmc_valid && tmask == 0 算出 cta_warp_done,反馈给 VX_cta_dispatch 释放槽位,形成闭环。
4.9 Warp 生命周期状态机
#mermaid-svg-4UnieeDZ0a7fhWcH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4UnieeDZ0a7fhWcH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4UnieeDZ0a7fhWcH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4UnieeDZ0a7fhWcH .error-icon{fill:#552222;}#mermaid-svg-4UnieeDZ0a7fhWcH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4UnieeDZ0a7fhWcH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4UnieeDZ0a7fhWcH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4UnieeDZ0a7fhWcH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4UnieeDZ0a7fhWcH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4UnieeDZ0a7fhWcH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4UnieeDZ0a7fhWcH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4UnieeDZ0a7fhWcH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4UnieeDZ0a7fhWcH .marker.cross{stroke:#333333;}#mermaid-svg-4UnieeDZ0a7fhWcH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4UnieeDZ0a7fhWcH p{margin:0;}#mermaid-svg-4UnieeDZ0a7fhWcH defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-4UnieeDZ0a7fhWcH g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-4UnieeDZ0a7fhWcH g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-4UnieeDZ0a7fhWcH g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-4UnieeDZ0a7fhWcH g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-4UnieeDZ0a7fhWcH g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-4UnieeDZ0a7fhWcH .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-4UnieeDZ0a7fhWcH .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-4UnieeDZ0a7fhWcH .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-4UnieeDZ0a7fhWcH .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-4UnieeDZ0a7fhWcH .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-4UnieeDZ0a7fhWcH .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-4UnieeDZ0a7fhWcH .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-4UnieeDZ0a7fhWcH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4UnieeDZ0a7fhWcH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4UnieeDZ0a7fhWcH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4UnieeDZ0a7fhWcH .edgeLabel .label text{fill:#333;}#mermaid-svg-4UnieeDZ0a7fhWcH .label div .edgeLabel{color:#333;}#mermaid-svg-4UnieeDZ0a7fhWcH .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-4UnieeDZ0a7fhWcH .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-4UnieeDZ0a7fhWcH .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-4UnieeDZ0a7fhWcH .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-4UnieeDZ0a7fhWcH .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-4UnieeDZ0a7fhWcH .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4UnieeDZ0a7fhWcH .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4UnieeDZ0a7fhWcH #statediagram-barbEnd{fill:#333333;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4UnieeDZ0a7fhWcH .cluster-label,#mermaid-svg-4UnieeDZ0a7fhWcH .nodeLabel{color:#131300;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-4UnieeDZ0a7fhWcH .note-edge{stroke-dasharray:5;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagram-note text{fill:black;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagram-note .nodeLabel{color:black;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagram .edgeLabel{color:red;}#mermaid-svg-4UnieeDZ0a7fhWcH #dependencyStart,#mermaid-svg-4UnieeDZ0a7fhWcH #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-4UnieeDZ0a7fhWcH .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4UnieeDZ0a7fhWcH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} reset
cta_fire 激活
warp_pcswid <= start_PC
schedule_fire
stalled_warps_nwid <= 1
普通指令 decode 解锁
控制流指令
等 branch_ctl_if / warp_ctl_if
执行阶段解锁
branch/join/split/bar/wsync
再次被调度
warp_done
(tmc_valid && tmask==0)
Inactive
Active
Stalled
Ready
数据冒险反馈闭环(以 RAW 为例):
指令 i (写 r3) ──→ scoreboard: inuse_regs[r3]=1
│
指令 i+1 (读 r3) ──→ scoreboard: operands_ready=0 ──→ STALL_SCRB
↑
└── VX_commit / writeback_if 清 inuse_regs[r3]
WAW 同理:i+1 写 r3 而 i 还没写回 → 阻塞 i+1(否则写顺序错乱)。
4.10 背压方向:从下游传导到上游
| 阶段 | 被谁阻塞 | 计数器 |
|---|---|---|
| Fetch | icache 请求被拒绝/响应排队 | STALL_FETCH |
| Decode → IBuffer | ibuffer 满 | STALL_IBUF |
| IBuffer → Scoreboard | RAW/WAW 依赖未解除 | STALL_SCRB |
| Operands → Dispatch | 寄存器堆 bank 冲突 | STALL_OPDS |
| Dispatch → 执行单元 | 目标 FU 队列满 | STALL_FPU/STALL_ALU/... |
因果链:
text
FU 执行/写回越慢
→ scoreboard 的 in-use 位保持越久
→ 更多依赖指令卡在 scoreboard(STALL_SCRB 最大)
→ ISSUED_WARPS 下降
→ ibuffer 消耗变慢(可能触发 STALL_IBUF)
→ fetch 请求被间接抑制
越往上游,背压被在途 warp 吸收得越多,梯度递减。
5. 两个思想实验(检验你是否真的理解了)
Q1:只有一个 warp、32 条指令,launch 后硬件最开始在干什么?
CTA dispatcher 把 warp 标为 active 并写入起始 PC;此后调度器每个 cycle 选中它 → 取指 → decode 解锁 → PC+4,循环 32 次。因为没有其它 warp 可轮转,这个单 warp 独占"schedule → decode unlock"串行链路,每 ~IFETCH_LT 个 cycle 才产出一条指令。
Q2:同一个 warp 里 16 条互相无依赖的 fadd.s,能并行吗?
前端不能,执行单元能。
- 前端(fetch/decode/issue)仍一周期一条串行------单飞行锁定与指令间依赖无关,是结构性的;
- 但
fadd.s是is_wstall=0,decode 即解锁,PC 能较快推进; - 真正的并行来自 FPU 内部流水线重叠:16 条 fadd 背靠背进入 FMA 流水线,流水线深度内同时有多条处于不同阶段,吞吐接近 1 条/周期------前提是有足够多 warp 交替来掩盖前端往返延迟。
定量(Little's Law):吞吐 = 并发数 / 延迟。本例往返延迟 ≈ 9.87 cycle(IFETCH_LT),所以:
- 1 个 warp:FMA 利用率 ≈ 1/9.87 ≈ 10%;
- 需要 N ≥ 10 个 warp 交织,FMA 流水线才能每周期有活干。
这就是 GPU "warp-level parallelism 填满流水线"的数学本质。
6. 实例分析:FMA 测试的 STALL 数据
6.1 数据(per-cycle,2 核合计)
| 计数器 | 值 | 含义 |
|---|---|---|
STALLED_WARPS |
24.41 | 平均处于 stall 的 warp 数 |
SCHED_IDLE |
0.73 | 调度器空转的核数 |
ISSUED_WARPS |
1.23 | 每周期发射的 warp 数 |
STALL_FETCH |
0.28 | fetch 阻塞 |
STALL_IBUF |
0 | ibuffer 满 |
STALL_SCRB |
1.18 | scoreboard 依赖阻塞 |
STALL_FPU |
0.66 | FPU 队列满 |
INSTR_FPU |
1.21 | FPU 指令数 |
IFETCHES |
1.23 | 取指请求数 |
IFETCH_LT |
9.87/fetch | 平均取指往返延迟 |
6.2 解读
递减梯度 = 背压衰减:
text
STALL_SCRB (1.18) > STALL_FPU (0.66) > STALL_FETCH (0.28) > STALL_IBUF (0)
↑ 瓶颈点 ↑ 越往上游,背压被在途 warp 吸收越多
- 发射数太少 :
ISSUED_WARPS = 1.23/cyc(2 核)→ 每核每周期约 0.6 条。INSTR_FPU ≈ ISSUED_WARPS,即几乎每条发射的指令都是 FPU 指令 → FPU 被喂入速率卡死,没有机会跑满。 - 瓶颈在 scoreboard,不在 FPU :
STALL_SCRB = 1.18显著 >STALL_FPU = 0.66------大多数周期 warp 死在数据依赖检查上,不是 FPU 队列满。 - 调度器没空转 :
SCHED_IDLE = 0.73→ 36.5% 的核-周期找不到可发射 warp。它选出来的 warp 大多因 RAW/WAW 卡在 scoreboard。 - STALL_IBUF = 0:ibuffer 深度足够,不是问题。
7. 性能分析与配置:从哪里下手
7.1 排查决策树
text
先看 ISSUED_WARPS:距离 ISSUE_WIDTH 的上限差多少?
├─ 差距大 → 找主导 STALL
│ ├─ STALL_SCRB 最大 → 数据依赖问题(RAW/WAW 链长、warp 数不足)
│ ├─ STALL_FETCH / IFETCH_LT 大 → 取指瓶颈(icache 容量/miss、核心间抢占)
│ ├─ STALL_FPU/ALU/... 大 → FU 队列满(FU 副本不足或依赖集中到同一 FU)
│ ├─ STALL_IBUF 大 → ibuffer 深度不够(发射消耗太慢往往是上游 STALL_SCRB 的果)
│ ├─ STALL_OPDS 大 → 寄存器堆 bank 冲突
│ └─ SCHED_IDLE 大 → 无 ready warp(warp 全部 stall:检查 warp 总数、同步/barrier 使用)
└─ 差距小 → 执行单元吞吐见顶 → 加 FU 副本/lane 数
7.2 优化手段对照表
| 瓶颈信号 | 硬件/配置手段 | 软件手段 |
|---|---|---|
STALL_SCRB 大(本例头号问题) |
增加 warp 数(NUM_WARPS)、加大 ibuffer、非阻塞 scoreboard / register renaming |
打散依赖链:指令调度、循环展开、用更多独立累加寄存器、减少短依赖回环 |
| 单 warp 前端往返延迟(≈IFETCH_LT) | 最直接:保证每核活跃 warp 数 ≥ IFETCH_LT(本例 ≥10) | 提高占用率:CTA 维度足够大、避免过早 warp 退出 |
STALL_FETCH / IFETCH_LT 大 |
加大 icache、prefetch | 代码布局优化、减少分支发散 |
STALL_FPU/STALL_ALU 大 |
加深 FU 队列、增加 FU 副本 / ISSUE_WIDTH |
均衡指令类型、提高算术强度 |
STALL_OPDS 大 |
增加 regfile bank | 调整寄存器访问模式 |
STALL_IBUF 大 |
加大 VX_CFG_IBUF_SIZE |
通常先解决上游 STALL_SCRB |
SCHED_IDLE 大 |
增加 warp 数 | 检查 barrier/同步是否让所有 warp 集体停住 |
7.3 三句话总结
- Vortex 是"按序发射、乱序完成":issue 按序(scoreboard 保证),execute/commit 可重叠(FU 流水线 + commit 仲裁)。
- warp 是停顿的原子单位,指令是流动的数据 :
stalled_warps按 warp 置位,流水线上跑的是一条条指令。 - 优化方向 = 消灭 STALL 面积 :本例头号敌人是
STALL_SCRB→ 增加活跃 warp 数 / 打散 RAW/WAW 依赖链;STALL_FETCH次之 → icache 与访存局部性;STALL_FPU再次 → FU 资源。
附:RTL 阅读顺序建议
VX_core.sv--- 模块拓扑与接口命名VX_scheduler.sv---stalled_warps_n置位/清零、warp_pcs多源写入、ready_warps/preferred_warps/优先级编码器VX_fetch.sv---req_buf(容量 2,OUT_REG=1)、inflight位、RVCsched_buffered_matchVX_decode.sv---is_wstall真值表、decode_sched_if.unlock、is_rvc对 PC 推进的影响VX_ibuffer.sv/VX_scoreboard.sv---operands_ready五种根因、inuse 置位/清零VX_opc_unit.sv--- bank 冲突 →STALL_OPDSVX_dispatcher.sv---fu_goingfull/fu_locked→STALL_FPU等VX_commit.sv--- 固定优先级仲裁、per-issue-slot、writeback 双去向VX_cta_dispatch.sv---cta_warp_done回收槽位,CTA 退休闭环