Vortex GPGPU Warp 调度与指令执行流程解读

Vortex GPGPU Warp 调度与指令执行流程解读

目标:读完后你能看清 Vortex 的完整执行过程和依赖关系,并知道性能分析和配置调优应该从哪里下手。


1. 三十分钟版本:先记住这几条结论

Vortex 是一个按序(in-order)的 SIMT GPGPU 核心。 它最反直觉、也最重要的一点是:

调度单位是 warp,但流水线上流动的是一条条指令。

由此推出五条主线:

  1. 调度粒度 = warp × 1 条指令。 调度器每次只选中一个 warp,只发出该 warp 当前 PC 对应的一条取指请求(该 warp 内所有激活线程并行执行这同一条指令,即 SIMT 的"一个 warp = 一条指令、多个线程")。

  2. warp 被调度后立即锁定("单飞行"窗口)。 schedule_fire 触发时 stalled_warps_n[schedule_wid] = 1,该 warp 在这条指令解锁之前不可能 被再次调度------哪怕它后面的指令与当前指令毫无数据依赖。前端的串行性是结构性的,不是依赖性的。

  3. 解锁有两条路径:

    • 普通指令(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 才解锁。
  4. 每一条指令都要完整走一遍 schedule → fetch → decode → issue(ibuffer/scoreboard/operands/dispatcher) → execute → commit。这是对每条指令而言的,不是对整个 CTA/kernel 只走一次。CTA dispatcher 只在 warp 激活时参与一次,之后该 warp 的所有指令都走通用指令流水线。

  5. 真正的并行度来自多 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 发请求。要点:

  1. 每个 VX_core 只有一个 icache_bus_if 接口 ,所有 warp 的请求都要排队经过 req_buf(容量 2、OUT_REG=1 的弹性缓冲:加一级寄存器改善时序 + 吸收 1~2 拍瞬时背压,避免 icache 的 ready 组合进 schedule_if 关键路径)。
  2. 每 warp 最多一个在途请求 (inflight 位)------"单飞行"在 fetch 层面的体现。
  3. 两类"慢"要区分:
    • 请求被拒绝 (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、寄存器使用掩码等,同时做两件事:

  1. 检查目标 ibuffer 是否已满 :满则记 STALL_IBUF,不推进。
  2. 产生 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 ---

三个容易混淆的层次,务必分开:

  1. 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 的其它发射被抑制。
  2. 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 对应一个线程的数据通路。
  3. 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 / 寄存器堆:真正把结果写回。
  • 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 吸收越多
  1. 发射数太少 :ISSUED_WARPS = 1.23/cyc(2 核)→ 每核每周期约 0.6 条。INSTR_FPU ≈ ISSUED_WARPS,即几乎每条发射的指令都是 FPU 指令 → FPU 被喂入速率卡死,没有机会跑满。
  2. 瓶颈在 scoreboard,不在 FPU :STALL_SCRB = 1.18 显著 > STALL_FPU = 0.66------大多数周期 warp 死在数据依赖检查上,不是 FPU 队列满。
  3. 调度器没空转 :SCHED_IDLE = 0.73 → 36.5% 的核-周期找不到可发射 warp。它选出来的 warp 大多因 RAW/WAW 卡在 scoreboard。
  4. 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 三句话总结

  1. Vortex 是"按序发射、乱序完成":issue 按序(scoreboard 保证),execute/commit 可重叠(FU 流水线 + commit 仲裁)。
  2. warp 是停顿的原子单位,指令是流动的数据 :stalled_warps 按 warp 置位,流水线上跑的是一条条指令。
  3. 优化方向 = 消灭 STALL 面积 :本例头号敌人是 STALL_SCRB → 增加活跃 warp 数 / 打散 RAW/WAW 依赖链;STALL_FETCH 次之 → icache 与访存局部性;STALL_FPU 再次 → FU 资源。

附:RTL 阅读顺序建议

  1. VX_core.sv --- 模块拓扑与接口命名
  2. VX_scheduler.sv --- stalled_warps_n 置位/清零、warp_pcs 多源写入、ready_warps/preferred_warps/优先级编码器
  3. VX_fetch.sv --- req_buf(容量 2,OUT_REG=1)、inflight 位、RVC sched_buffered_match
  4. VX_decode.sv --- is_wstall 真值表、decode_sched_if.unlock、is_rvc 对 PC 推进的影响
  5. VX_ibuffer.sv / VX_scoreboard.sv --- operands_ready 五种根因、inuse 置位/清零
  6. VX_opc_unit.sv --- bank 冲突 → STALL_OPDS
  7. VX_dispatcher.sv --- fu_goingfull/fu_locked → STALL_FPU 等
  8. VX_commit.sv --- 固定优先级仲裁、per-issue-slot、writeback 双去向
  9. VX_cta_dispatch.sv --- cta_warp_done 回收槽位,CTA 退休闭环
相关推荐
桃西西呀2 小时前
Spring AI Alibaba 之八:我让 Agent 直接执行 shell 命令,它却读到了目录里的密钥,沙箱到底防住了什么
人工智能·spring·llm
9i编程2 小时前
9. 教 AI 上班:带出我的数字同事 —— 部署到本机再大回归:组织架构又错,被我叫停先改树
人工智能·openai·ai编程
遇码2 小时前
system prompt 里拼了时间和知识大纲,每轮缓存都被击穿:静态/动态分离的提示词装配方法
人工智能·ai·大模型
a93202 小时前
基准真值也会错:我的 AI 审计管线漏报 12 题,先别急着调模型
人工智能
俊哥V2 小时前
每日 AI 研究简报 · 2026-10-10
人工智能·ai
程序人生8882 小时前
PDF 转 Word 版式错乱、表格丢失?DocConverter Web 格式互转引擎的保真实践
前端·人工智能·opencv·机器学习·pdf·word
Dawson Zhu2 小时前
《Agentic Design Patterns》第 8 章导读:记忆管理(Memory Management)
人工智能·语言模型·架构·aigc·agi
m4Rk_2 小时前
【论文阅读】Agent 记忆机制(96):M3-Agent——让 Agent 从视听经历中持续形成情景与语义长期记忆
论文阅读·人工智能·学习·开源·github
xiami_world2 小时前
Xmind、Miro、博思白板AI怎么选?思维导图/流程图自动生成
人工智能·ai·信息可视化·流程图·xmind