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 Instruction Encoding (128-bit)

Recovered from the decoded per-architecture SASS instruction tables in nvdisasm V13.1.115 (CUDA 13.1), processed by a parser that handles all 13 tables cleanly, and round-trip-validated against real ptxas output: a table-driven 128-bit decoder reproduces nvdisasm's own opcode, operands, and guard predicate on 368 real instructions across 8 architectures at 100% (tooling and worked example below).

Every post-Volta SASS instruction is a 128-bit word, even though the decoder header reports WORD_SIZE 64 (a generic header artifact present in every table). The true width is confirmed three ways: the highest referenced bit is 124, the alternate dot-mask representation is exactly 128 characters, and the relocator R_CUDA_INSTRUCTION128 exists. The instruction word splits into a low opcode + guard region, a middle operand region, and a high scheduling-control word.

Word layout

 bit 127 126 | 125 ........ 105 | 104 | 103 102 | 101 .. 92 | 91 | 90 ........ 16 | 15 | 14 .. 12 | 11 .. 0
 +----------+-----------------+-----+---------+-----------+----+--------------+----+---------+--------+
 | reserved | CONTROL / SCHED | gap | pm_pred | reserved  |OPC | OPERAND REGION|Pg_ |  Pg     | OPCODE |
 | (unused) |     WORD        |     | (perf)  | (unused)  |hi  | regs/imm/mod  |not | guard   | low 12 |
 +----------+-----------------+-----+---------+-----------+----+--------------+----+---------+--------+

The scheduling word is one contiguous 21-bit field at bits 125:105 in every class: the decoder names it opex and exposes its sub-fields (below). Only bits 127:126 are reserved at the top of the word — bit 125 is the high bit of the scheduling field, not a reserved bit.

  • Opcode — a 13-bit field split {bit 91} ++ {bits 11:0}: the low 12 bits are the primary opcode, bit 91 is the single high extension bit. Literals are 10–13 bits, zero-extended. Verified on real SASS: a Turing STG decodes with bit 91 = 1 (13-bit 0x1986), while its neighbouring BAR.SYNC decodes with bit 91 = 0 (legacy 0xb1d). The low 12 bits are themselves a contiguous integer assembled from four sub-fields — reading high-to-low, {11:9} (3) ++ {8} (1) ++ {7:5} (3) ++ {4:0} (5), a 3+1+3+5 packing identical in every decoded class on every arch. Exception: on SM75 (Turing) only, 69 distinct legacy control/barrier classes carry a pure 12-bit opcode whose bit-91 extension is fixed at 0 (the class is fully identified by {11:0} alone) — a broad set including BAR, BRA, BRX, BSSY, BSYNC, BPT, B2R, AL2P, S2R, NOP, EXIT, VOTE, SHFL, MEMBAR, DEPBAR, YIELD, JMP, KILL, LEPC, MATCH and others. (Bit 91 is still a physically present field in these classes; it simply carries no opcode information. SM80+ uses the 13-bit form universally.)
  • Guard predicatePg@14:12 (3-bit, P0..PT) with Pg_not@15 (inversion). Present on every instruction; the same slot carries UPg for uniform-datapath classes.
  • Operand region — bits 16:91: register ports, immediates, constant-bank addresses, and operand modifiers (below).
  • Control / scheduling word — bits 102:125, architecturally stable Turing→Blackwell (below). The scheduling/opex sub-region occupies a single 21-bit slot at 125:105, identical in every decoded class on every arch (610+ classes per arch all place it at exactly 125:105); pm_pred@103:102 is carved from the otherwise-unused 104:102 window on SM90+.
  • Never-encoded bits92:101 and 104 are unused by any field, and the only reserved bits at the top of the word are 127:126. (Earlier drafts marked 125:127 reserved; bit 125 is in fact the high bit of the scheduling field — the same bit the empirical .reuse read recovers from {125:122}.)

The scheduling-control word (bits 102–125)

The whole 125:105 span is a single decoder field (opex); the rows below are the named sub-fields the decoder tiles across it. The scoreboard fields (req_bit_set / src_rel_sb / dst_wr_sb) fill 121:110, leaving the high 125:122 and low 109:105 groups for the opex batch_t/usched_info/.reuse packing — which is why opex is listed split.

FieldBitsWidthRole
opex125:122, 109:1059 (split)packs batch_t + usched_info (stall/yield) + per-operand .reuse flags, LUT-compressed via TABLES_opex_N
req_bit_set121:1166scoreboard wait mask — which of 6 dependency barriers to wait on
src_rel_sb115:1133read/release scoreboard (variable-latency source barrier; 7 = none)
dst_wr_sb112:1103write scoreboard (destination barrier; 7 = none)
pm_pred103:1022perf-monitor predicate — first introduced at SM90 (absent on SM75/SM80)

opex/req_bit_set/src_rel_sb/dst_wr_sb are stable from Turing; only pm_pred is a Hopper-era addition. The dynamic meaning of these fields — coupled vs decoupled issue, scoreboard signalling, the wait mask — is the Execution Model; this page covers only their bit placement.

opex = batch_t<<5 | usched_info, confirmed directly from the TABLES_opex_0 LUT: (batch_t, usched_info) → opex gives (1,1)→33, (2,1)→65, (3,1)→97, i.e. 33 = 1·32+1, 65 = 2·32+1. So usched_info (the stall/yield enum) is the low 5 bits {109:105} and batch_t the high 3 bits {124:122}.

.reuse is folded into opex, not a free-standing bit. Operand-reuse flags ride in the high bits via TABLES_opex_N(batch_t, usched_info, reuse_a, reuse_b, …); the variant index N encodes reuse arity (opex_5 = 2-source reuse for ISETP; opex_4 = 3-source for IADD3/IMAD). The fold is context-dependent — for some usched_info values reuse lands in the bit-122 group, for others it perturbs lower bits — which is why reverse-mapping reuse means inverting the LUT, not masking a bit. Empirically, reading the raw bits {125:122} recovers .reuse cleanly: across 280 real instructions it matched nvdisasm's .reuse annotation 3/3 with 0 false positives (e.g. IMAD.WIDE R2, R5.reuse, … → bit 122 set, the Ra reuse slot).

Register file

FileRangeEncoding
GPRR0..R254 + RZ(255)8-bit, byte-aligned ports (R254 reserved → OOR_REG_ERROR)
UniformUR0..UR62 + URZ(63)6-bit, in the low 6 bits of a GPR byte-slot (top 2 bits select form)
PredicateP0..P6 + PT(7)3-bit
Uniform predicateUP0..UP6 + UPT(7)3-bit
Convergence barrierB0..B154-bit barReg (in BSSY/BMOV); value 15 = no barrier present
Special (S2R)SR0..SR255 (named: SR_LANEID, SR_CLOCK, SR_TID, …)8-bit SRa@79:72

Operand ports (SM90 reference)

FieldBitsWRole
Rd23:168dest GPR
Ra31:248srcA GPR
Rb39:328srcB GPR
Rc71:648srcC GPR
URd21:166uniform dest (low 6b of Rd byte)
Ra_URb37:326srcB as GPR-or-UR (low 6b of Rb byte)
Ra_URc69:646srcC as GPR-or-UR (low 6b of Rc byte)
Pu83:813dest predicate 1
Pnz89:873source-predicate slot (carries Pp)
cop86:843dest-predicate 2 slot (carries Pv)
UPq/UPq_not79:77/803/1uniform predicate + inversion
SRa79:728special-register index
barReg19:164convergence-barrier descriptor

The Ra_URb/Ra_URc ports are a single physical field that decodes as GPR or uniform reg depending on instruction form: register form uses all 8 bits as a GPR; uniform/const form uses the low 6 as a UR.

Decode by bit-range + source, never by slot name. A BITS_…_<name> field's role is set by its RHS source operand, not its incidental name: the slot literally named cop@86:84 carries the second dest-predicate Pv in ISETP; Pnz@89:87 carries source predicate Pp; input_reg_sz_32_dist@90 carries Pp@not in BSSY.

Immediates and constant-bank addressing

  • Immediate shapes (dominant): Sb_offset@53:40 (14-bit const/mem offset, 426 uses), Ra_offset@63:32 (32-bit, 209 uses) and @63:40 (24-bit mem offset). Split (non-contiguous) immediates exist: sImm@{81:34, 23:16} (56-bit), uimm8@{76:72, 66:64}.
  • Constant bank c[bank][offset]: Sb_bank@58:54 (5-bit, banks 0–31) + Sb_offset@53:40 (14-bit), generated by ConstBankAddress2 and patched by the R_CUDA_CONST_FIELD* relocators.
  • PC-relative branch targets use SCALE 4 (16-byte instruction alignment) — e.g. BSSY's Sa@63:34.

Operand modifiers

1-bit flags near each operand's port; the opcode disambiguates shared bits:

ModifierBits
@negate (−x)72 (Ra), 63 (Sb/Rb/URb/Rc), 75 (Rc), 84
@absolute (|x|)73 (Ra), 62 (Sb/Sc/Rc), 83, 74
@invert (~x, int logic)75, 72, 63, 30
@not (!P, predicate)15 (Pg), 90 (Pp), 71 (Pr), 80/27 (UPq)

Bits 63/72/75 are shared between negate / invert / absolute — the opcode selects the meaning.

Relocators (R_CUDA_*)

A 116-entry R_CUDA_* relocator table, byte-identical across all 13 arches — a stable ELF ABI contract (ELF_ABI 0x33, version 7), since relocations are a linker property, not an arch-encoding one. Families:

  • AbsoluteR_CUDA_32/64 (bit 0), ABS{16,24,32}_<off> at 20/23/26/32, ABS47_34, plus split ABS55_16_34 = {16,8}+{34,47} and ABS56_16_34 = {16,8}+{34,48}.
  • Constant-bankCONST_FIELD<w>_<off>: width-19 → class ConstBankAddress2, width-21/22 → ConstBankAddress0 (the two const-bank addressing modes). CONST_FIELD19_28 = {28,18}+{26,1} (split).
  • DescriptorQUERY_DESC21_37; function descriptor FUNC_DESC* (tag fdesc, LO/HI/byte-lane).
  • Texture/sampler/surfaceTEX_HEADER_INDEX, SAMP_HEADER_INDEX, TEX_SLOT, SAMP_SLOT, SURF_SLOT, TEX_BINDLESSOFF13_*.
  • PC-relativePCREL_IMM24_{23,26} (24-bit branch displacement).
  • Byte-laneR_CUDA_8_{0..56} / G8_* / UNIFIED_8_* (one byte lane each, written at bit 0).
  • MiscUNIFIED* (tag unified), G32/G64, YIELD_OPCODE9_0, INSTRUCTION64/INSTRUCTION128.

Worked decode of one real instruction

A table-driven decoder (decoded/sass-tools/sass_decode.py) reads a raw 128-bit word straight out of a cubin's .text section and reconstructs every field from this model. Take the Hopper instruction ISETP.GE.AND P0, PT, R5, UR4, PT emitted by ptxas — its raw little-endian word is lo = 0x0000000405007c0c, hi = 0x000fda000bf06270, so the 128-bit word is hi<<64 | lo.

FieldBitsRaw valueMeaning
opcode{91}++{11:0}0x1c0cbit 91 = 1, low 12 = 0xc0c → class isetp__RUR_RUR_EX (the uniform-Rb compare-EX form)
Pg / Pg_not14:12 / 157 / 0guard @PT (unconditional)
Ra31:245source A = R5
Ra_URb37:324source B = UR4 (low-6 bits read as a uniform register: RHS source is URb)
Pu83:810dest predicate P0 — encoded straight (not 7−P0)
cop (Pv)86:847second dest predicate PT (unwritten), straight
sco78:766compare op = GE
stall108:10513issue stall 13 cycles
yield1090yield bit clear
req_bit_set121:1160no scoreboard wait
dst_wr_sb/src_rel_sb112:110/115:1137/7no scoreboard armed/released
opex{125:122}++{109:105}13batch_t=0, usched_info=13 (= stall 13, no group-end); bit 125 = 0 here

Reassembled: @PT ISETP.GE.AND P0, PT, R5, UR4, PT with a 13-cycle stall — byte-identical to nvdisasm's own disassembly.

Round-trip validation against nvdisasm

The decoder is validated by re-decoding real ptxas output and scoring every field against nvdisasm -c's disassembly (harness: decoded/sass-tools/sass_roundtrip.py). Across 368 real instructions on eight architectures (sm_75 sm_80 sm_86 sm_89 sm_90 sm_90a sm_100 sm_120, CUDA 13.1 / nvdisasm V13.1.115):

Field checkedMatch rate
opcode / mnemonic family100% (368/368)
destination GPR (Rd)100%
source GPRs (Ra/Rb/Rc, UR-aware)100%
guard predicate (Pg/Pg_not)100%

The scheduling word is cross-checked semantically against data dependencies: every scoreboard-wait mask (req_bit_set) pairs with an earlier producer that armed exactly that write barrier — e.g. LDG.E R0 arming write-barrier 2, its consumer VIADD R7, R0, … waiting on bit 2; LDG.E R6 → barrier 3 → FFMA R9, R6 waiting on bit 3. .reuse annotations were matched 3/3 from bits {125:122} with 0 false positives over 277 non-reuse instructions.

Two model fields the round-trip corrected: dest predicates do not use the 7−N complement — that map applies only to the load/store source-guard slot (below) — and a naive opcode lexer silently drops .-bearing mnemonics like HFMA2.MMA/LOP3.LUT (fixed in the parser — the per-arch opcode counts above are the corrected totals).

Encoding oddities (decoder pitfalls)

  1. Destination predicates encode straight; the 7−N complement belongs to one source predicate slot, not the dest ports. A 7−N complement map (P0→7, P1→6, … PT→0) does exist in the tables, but it applies only to the source guard-type predicate of the load/store family — the predicate that gates the access in LDG/LD/LDS/STG/… (a source-predicate slot in the Rc-region at bits 66:64, with its inversion at bit 67). Its always-true default @PT therefore encodes as all-zeros (7−7 = 0), the common case. The destination predicate ports (Pu@83:81, cop/Pv@86:84) read the operand directly, with no complement — they are pushed into the operand as the raw field value. Confirmed on real ISETP: ISETP.GT P1 encodes Pu=1, ISETP.GE P0 encodes Pu=0 — the encoded value equals the displayed predicate. (Earlier docs that applied a blanket 7−N to dest predicates were wrong; the complement is real but confined to that one source-guard slot.)
  2. Header mislabeling. Every table reports ARCHITECTURE "Volta", PROCESSOR_ID Volta, WORD_SIZE 64 regardless of true arch — a generic decoder-header artifact. True width is 128 bits; true arch is the per-class content.
  3. SM75 dual opcode form — 69 distinct legacy control/barrier classes carry a pure 12-bit opcode whose bit-91 extension is fixed at 0 (the bit is physically present but carries no opcode information); verified by round-trip against real Turing SASS.
  4. Zero-width fieldsBITS_0_Sb=Sb is a real feature: an operand named in the syntax that contributes 0 encoding bits (implicit/aliased operand).
  5. Bit-name reuse — the same physical bit serves different roles across families (bit 90 = input_reg_sz_32_dist in IMAD but Pp@not in BSSY; bit 63 = negate/invert/immediate by class). No global bit→role map exists.

Per-arch counts (parser-validated)

Opcode-entry counts include mnemonics with . in the name (e.g. HFMA2.MMA, LOP3.LUT), which a naive [\w]+ opcode lexer drops; the figures below count every OPCODES directive across all operand sub-forms.

archclassesopcode entriesdistinct BITS_* fields
SM75898186299
SM809621986109
SM86/87/889992062108
SM8910492162112
SM90/90a11682410115
SM1009752028121
SM1039712020123
SM1109011880118
SM120/12110122096112

Cross-References