Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

SASS Execution Model — Pipelines, Scoreboards, Scheduling

Recovered from the decoded per-architecture SASS instruction tables in nvdisasm V13.1.115 (CUDA 13.1). The compiler-side hazard resolution (RAW/WAR/WAW) is the Dependency & Hazard Model; this page is the hardware's dynamic execution and warp-issue model.

The single most striking result of decoding the tables is that the warp-scheduler ISA contract has not changed since Volta. The control-word bit layout, the six scoreboards, the 6-bit wait mask, the usched_info (0–27) and batch_t enums are byte-identical from Turing (SM75) through Blackwell (SM121) — every table even self-identifies as ARCHITECTURE "Volta". Six GPU generations have added pipes and datapaths but left the scheduling model untouched. It is a single-issue, scoreboard-assisted, statically stall-scheduled machine: the compiler pre-computes every fixed stall and every variable-latency barrier; the hardware just evaluates the wait mask, honours the stall counter, and picks among ready warps.

The SM as a physical machine

The scheduling model runs on a fixed physical substrate, stable across the Volta→Hopper generations:

ResourceValue
Register file65,536 × 32-bit registers per SM (256 KB), split 16,384 per sub-partition
Register banks2 banks (even/odd by register number) on Volta–Ampere; 4 banks on Kepler–Pascal
Sub-partitions (warp schedulers)4 per SM
Warps per SM64 (Volta, sm_80) · 48 (sm_86) · 32 (Turing)
FP32 FMA lanes64 per SM (4 sub-partitions × 16 lanes)
SMs per TPC2
Constant bank size64 KB per bank
Dependency scoreboards6 per warp (SB0–SB5)
Max registers / thread255 (RZ = R255; allocation granularity 8)

A warp is bound to one of the 4 sub-partitions, which owns its register-file slice, its dispatch unit, and the 6 scoreboards; the single-issue rule is per sub-partition. The 2-bank (even/odd) register file is exactly why the operand collector and the .reuse cache matter — two source operands drawn from the same bank in one cycle would conflict, so the allocator biases an accumulator into the opposite bank from its multiplicands and the reuse cache serves a repeated operand without a second read. (Banking and the allocator's bank bias are in Register Allocation.)

Coupled vs decoupled — the INSTRUCTION_TYPE split

Each instruction's INSTRUCTION_TYPE classifies how its latency is tracked. The enum grows from 9 codes (Turing–Hopper) to 12 (Blackwell adds scoreboard-elided emulatable variants):

CodeNameHazard mechanism
0COUPLED_MATHfixed latency → static stall count in opex; no scoreboard
1DECOUPLED_RD_SCBDvariable → read scoreboard only
2DECOUPLED_RD_WR_SCBDvariable → read + write scoreboards (the load pattern)
3COUPLED_EMULATABLEfixed, software-emulated multi-op sequence
4DECOUPLED_BRU_DEPBAR_RD_SCBDbranch-unit / DEPBAR decoupled, read scoreboard
5DECOUPLED_WR_SCBDvariable → write scoreboard only
6DECOUPLED_RD_NOREQ_SCBDread scoreboard, no wait-mask requirement
7DECOUPLED_WR_NOREQ_SCBDwrite scoreboard, no wait-mask
8DECOUPLED_BRU_DEPBAR_RD_NOREQ_SCBDbranch decoupled, no wait-mask
9–11COUPLED_EMULATABLE_{NORD,NOWR,NORD_NOWR}_SCBDSM100+ only — emulated, scoreboard-elided (used by cctl_c_ldc_*)

Distribution by instruction form (PROPERTIES blocks; ~55–60 % COUPLED_MATH, ~40–45 % DECOUPLED on every arch):

typeSM75SM80SM90SM100SM120
COUPLED_MATH575665820673760
DECOUPLED_RD_WR_SCBD380333461344380
DECOUPLED_RD_SCBD11594145220163
DECOUPLED_BRU_DEPBAR_RD_SCBD71801139292
COUPLED_EMULATABLE834444270
DECOUPLED_WR_SCBD1142014
emulatable-scbd-elided (9/10/11)00040
total forms12291217158913801414

Verified correlation: COUPLED_MATH forms carry no pipe (~99 %) — they are fixed-latency ALU ops dispatched into the back-to-back math pipe — while every DECOUPLED form has a VIRTUAL_QUEUE assignment (variable latency must route to a named functional-unit queue).

Pipe topology — the VIRTUAL_QUEUE functional units

VIRTUAL_QUEUE names the FU pipe an instruction dispatches into (39 codes). The pipe set is where generations diverge (distinct pipes: SM75=17, Ampere=20, SM90=26, Blackwell-DC=34, consumer Blackwell=27). Population by form:

pipemeaningSM75SM80SM90SM100SM120
AGUaddress-gen (smem/local, ALD/AST, ISBERD)124109109112108
AGU_UNORDERED_WRglobal load/atomic, unordered writeback7548464953
MUFUtranscendentals, F2F/F2I/I2F, POPC74741197373
FMA6464-bit FMA path, wide convert66661116767
UNORDEREDBAR/CCTL/LDC/BMOV4845889492
CBUconvergence/branch (BRA/BSSY/BSYNC/CALL/EXIT)5364716868
TEXtexture/surface (TEX/SULD/TXQ)6153572436
REDIRECTABLEFP64 emulation queue (DADD/DFMA/DMUL)293636200
REDIRECTABLE_FP16Turing-only FP16 pipe420000
FP64SM120-only native double precision000020
UMMAHopper-only warpgroup MMA (*GMMA)002400
TC_1CTA / TC_2CTABlackwell-DC tcgen05 tensor cores00030/300
TMEMBlackwell-DC tensor memory (LDTM/STTM)00040
HMMA/IMMA/DMMAlegacy half/int/double tensor8/3/04/3/14/3/14/2/10/0/1
TMA_UNORDERED_WRHopper+ TMA bulk async copy00214040
SYNCS_UNORDERED_WRHopper+ async smem barriers (mbarrier)0091010
ATOM_UNORDERED/SUATOMglobal/surface atomics0/160/822/822/422/6
FENCE_S/FENCE_G/FENCE_Tmemory fences1/1/01/1/11/1/0
IPAattribute interpolation (graphics)77766

Separate queues mean a long-latency texture or tensor op does not block an independent FMA — the scheduler tracks readiness per queue. The emulation→native migration is visible directly: Turing's dedicated REDIRECTABLE_FP16 folds away by SM80; FP64 stays on the emulated REDIRECTABLE queue until SM120 gains a native FP64 pipe; tensor goes emulated HMMA/IMMA → Hopper UMMA → Blackwell-DC TC_1CTA/TC_2CTA + TMEM. COUPLED_EMULATABLE reaches zero on SM120/121 — consumer Blackwell implements FP64 and its tensor variants natively.

The scheduling-control word

Bits 102–125 of every instruction carry the schedule ptxas computed (only bits 127–126 are reserved; layout identical on all arches):

FieldBitsWRole
pm_pred102–1032perf-monitor predicate (PMN/PM1/PM2/PM3) — SM90+
opex105–109 + 122–1259stall + yield + dual-issue batch + .reuse (below)
dst_wr_sb110–1123write scoreboard the producer sets on writeback (7 = none)
src_rel_sb113–1153read scoreboard released when sources are consumed (7 = none)
req_bit_set116–1216wait mask — one bit per scoreboard SB0–SB5

opex = batch_t<<5 | usched_info

Verified across 4500 table rows, 0 mismatches: the 8-bit opex byte is the high-3-bit batch_t (122–124) over the low-5-bit usched_info (105–109).

usched_info (0–27) — the unified stall/yield field:

valuemeaning
0 (DRAIN, default)yield — drain the warp from the issue deck; scheduler prefers another ready warp
1–15 (W1EG…W15EG)stall n cycles and end the co-issue group
17–27 (W1…W11)stall n−16 cycles (pipeline transition), no group-end

This is the Volta-family successor to the Maxwell/Pascal "stall count + yield bit" control word, packed into one field.

batch_t — the dual-issue/batch control: NOP(0) standalone · BATCH_START(1) · BATCH_START_TILE(2) · BATCH_END(4) · BARRIER_EXEMPT(5).

Dual-issue is in the instruction, not the scheduler

Every arch reports ISSUE_SLOTS 1 — one math issue per warp per cycle. Superscalar co-issue is expressed entirely through batch_t: BATCH_START … BATCH_END mark instructions that issue together into distinct pipes in one scheduler decision. The TABLES_opex_N(batch_t, usched_info) legality tables encode the co-issue rule: TABLES_opex_0 permits a stall (usched 17–27) in any batch position (~40 % of forms), but all other tables force a multi-cycle stall to batch_t=0 — an instruction inside a co-issue batch cannot also carry a stall. You may batch or stall, not both. The compiler, not the hardware, decides co-issue groups.

Operand-reuse cache

A separate 64-bit "OE" operand-encoding view exposes per-source reuse-cache flags OEReuseA/OEReuseB/OEReuseC (bits 46/45/44). When set, a source operand is taken from a small reuse cache instead of re-reading the register file — a power/bandwidth optimization the compiler controls per source slot. Reuse is mutually exclusive with ?DRAIN/?WAITn_END_GROUP scheduler tokens (a CONDITIONS rule enforces it).

Scoreboard / barrier model

Exactly six dependency scoreboards, SB0–SB5, on every arch SM75→SM121 (values 6/7 are the "no barrier" sentinel in the 3-bit producer fields). Resolution:

  1. A variable-latency producer (DECOUPLED_*_SCBD) is assigned dst_wr_sb (set when its result is written back) and/or src_rel_sb (set when its sources have been read). The format syntax WR:wr = dst_wr_sb, RD:rd = src_rel_sb makes this explicit; a load (ldg) sets both.
  2. The producer issues and the warp continues with independent instructions.
  3. A consumer sets the matching bit in its 6-bit req_bit_set wait mask. The warp may not issue until every scoreboard in the mask has reached its target count — up to 6 outstanding dependencies waited on at once.

Fixed-latency (COUPLED_MATH) producers skip all of this: the RAW latency is a compile-time constant emitted as a usched stall, and MIN_WAIT_NEEDED gives the floor (0 for ~55 % of math, up to 6 for rpcmov/depbar).

DEPBAR.LE sb, cnt, {list} is an explicit scoreboard wait-down: block until the indexed scoreboard's outstanding count drops to ≤ cnt. This waits on partial completion of a batch of outstanding ops (e.g. "until ≤2 of 5 loads remain").

MEM_SCBD — the orthogonal memory-ordering axis. Separate from register scoreboards, MEM_SCBD (NONE/SOURCE_RD/SOURCE_WR/SINK/SOURCE_SINK_WR/…) + MEM_SCBD_TYPE (BARRIER_INST/MEM_INST/BB_ENDING_INST/ALL) sequence the memory subsystem: SINK/BARRIER_INST = membar/TMA drain the memory pipe; SOURCE_WR/ALL = fence produces global ordering; SOURCE_SINK_WR = async smem barriers. This is the fence/membar model layered on top of register scoreboarding.

The per-warp issue rule

Each cycle, for a warp's next decoded instruction I:

  1. Wait-mask gate — if req_bit_set ≠ 0 and not all its scoreboards have reached target → WAIT (a variable-latency RAW/WAR dependency).
  2. Dispatch-stall gate — if cycles_since_last_issue < MIN_WAIT_NEEDED(I)STALL; honour the usched stall before the next issue (0 → yield-candidate; 1–15 → stall+end-batch; 17–27 → stall n−16).
  3. Yieldusched == DRAIN(0) voluntarily yields the issue deck (greedy-then-oldest), avoiding SM monopolization.
  4. BatchBATCH_START … BATCH_END co-issue into distinct pipes in one decision (the only "dual issue"; mutually exclusive with a stall on most pipes).
  5. Issue — into the instruction's VIRTUAL_QUEUE. COUPLED_MATH → fixed pipe, no scoreboard; DECOUPLED_* → FU queue, arm dst_wr_sb/src_rel_sb, move on. BRANCH_DELAY == 0 on all arches — no architected branch delay slot; convergence is the CBU pipe (BSSY/BSYNC) + control scoreboards.

Validation against ptxas-emitted SASS

The model above is reproduced exactly by the schedule ptxas computes. The check assembles PTX with ptxas, reads each instruction's 128-bit encoding with nvdisasm -c -hex, and decodes the control word (bits 105–124) back into usched/dst_wr_sb/src_rel_sb/req_bit_set/batch_t. A saxpy inner block on SM90 decodes as:

Opusched→stalldst_wrsrc_relwait maskrole
IMAD.WIDE (addr of x)6 → stall 6SB0waits the index S2R chain
LDG.E R2 (load x)17 → stall 1SB2arms write scoreboard SB2
IMAD.WIDE (addr of y)5 → stall 5SB1independent address math, fills the load shadow
LDG.E R7 (load y)18 → stall 2SB2second load reuses SB2 (count-based)
FFMA R7, R2, a, R75 → stall 5SB2consumer waits SB2 until both loads complete
STG.E (store)17 → stall 1

The two loads share one scoreboard (SB2); the consuming FFMA carries a single req_bit_set bit for SB2 and blocks until that scoreboard's outstanding count returns to its target — i.e. until both loads have written back. This is the producer→consumer pairing the model predicts, realized verbatim.

Across sm_75 sm_80 sm_86 sm_89 sm_90 sm_90a sm_100 sm_120, over a battery of math-chain, load, transcendental, tensor and cp.async probes (1912 decoded instructions): every consumer wait-bit pairs with an earlier producer that armed exactly that scoreboard — 221 pairs, 0 unmatched — and opex = batch_t<<5 | usched_info holds on every single instruction (0 violations, recomputed independently from the raw 128-bit words). A co-issue batch (batch_t ≠ 0) never carries a group-ending stall (usched 1–15); the only stall that rides a batch member is the 1–2-cycle no-group-end spacing of a .reuse co-issue pair — confirming "batch or stall, not both".

Observed dispatch stall by producer class

For fixed-latency math, the stall ptxas places between a producer and its immediate dependent consumer is small and stable across every generation:

Dependent pairMechanismStall (SM75→SM120)
IADD3IADD3, IMADIMAD, FFMAFFMA, FADDFADDfixed (coupled math)4
IADD3IMAD, LOP3/FMUL→consumerfixed (coupled math)5 (SM75 emits 8 for the pre-store slot)
ISETP→predicated op (CC/predicate)fixed (control class)13
HMMA.16816→consumer (tensor)fixed (tensor class)11 + follow-on
LDG/LDS→consumervariable → scoreboarddst_wr_sb + matching req_bit_set
MUFU, F2I/I2F/F2F, POPC→consumervariable → scoreboard (MUFU pipe)scoreboard, not a fixed stall
cp.async group waitvariable → DEPBARLDGDEPBAR + DEPBAR.LE SBn, k

The dispatch stall is the producer→consumer dependency latency for the coupled math pipe, which is narrower than the 6-cycle result band: a same-pipe pair emits 4 (the coupled base latency), a cross-pipe pair emits 5 (one extra cycle of inter-pipe hazard penalty). The 6-cycle band is the higher-level scalar that also covers the slower CC/predicate-write path — a coupled op writing a condition-code or predicate exposes 6 + 7 = 13, the control band, which is why an ISETP→predicate edge stalls 13. The stall always counts the cycles between the two issue slots (the wait the consumer owes after the producer issues), so it is the dependency edge weight directly, not a band with a constant subtracted. The MUFU pipe (transcendentals, F2*/I2F conversions, POPC) carries the band-13 latency but delivers it through a scoreboard, not a fixed stall — the band gives the magnitude, the INSTRUCTION_TYPE gives the mechanism.

cp.async (cp.async.commit_groupLDGDEPBAR; cp.async.wait_group kDEPBAR.LE SBn, k) is the explicit partial-wait path: the count k is the number of outstanding copy groups the warp will tolerate, byte-identical from SM80 through SM120. WAR (read-then-overwrite) pairs carry no extra stall — ptxas renames the destination so the anti-dependency never reaches the schedule.

Reference simulator

decoded/sass-tools/sass_sched_sim.py is a single-warp issue/timing simulator that replays exactly this rule over a decoded stream — wait-mask gate, dispatch stall, yield-on-DRAIN, batch_t co-issue, scoreboard arm/wait — and emits a cycle-by-cycle trace plus a basic-block cycle estimate. Its --validate mode is the producer→barrier and opex self-check used above. sass_ctrl_decode.py is the control-word decoder it consumes.

Per-arch execution deltas

AxisSM75 TuringSM80–89SM90 HopperSM100/103/110 Bk-DCSM120/121 Bk-consumer
ISSUE_SLOTS / BRANCH_DELAY1 / 01 / 01 / 01 / 01 / 0
Scoreboards66666
Control word / usched/batch_tfixedidenticalidenticalidenticalidentical
INSTRUCTION_TYPE codes0–80–80–80–110–11
Distinct pipes1720263427
FP16dedicated pipefoldedfoldedfoldedfolded
FP64emulatedemulatedemulatedemulatednative pipe
TensorHMMA/IMMA (emul)+DMMA+UMMA (wgmma)+TC_1CTA/2CTA+TMEMnone
Async copy / barriers+TMA/SYNCSTMA + TMEMTMA
COUPLED_EMULATABLE forms83444427 (+3 elided)0

Cross-References