# How to Add a New Instruction This guide shows you how to add a new instruction from bottom (Assembly IR) to top (Logical IR). **Note**: Python API is decoupled via a separate wrapper layer and is not directly affected by these changes. **Instruction definitions and costs** use the [DEF_T system](#chapter-1-assembly-ir-hardware-layer): they live in `hardware/src/gfx/GfxXXX/GfxXXXInstructions.def` (and optionally `GfxXXXFormats.def` in the same folder), using `DEF_T` for standalone instructions or `DEF_BATCH` for groups sharing a format. Tablegen generates `*_init.inc`, `*_costs.inc`, and `*_operands.inc`; the architecture `.cpp`/`.hpp` files only include those. Do not add DEF_T or cost tables manually in the `.cpp`. --- ## Quick checklist: adding an instruction (as few edits as possible) | Goal | What to do | |------|------------| | **Assembly-only instruction** | Add `DEF_T(Class, "mnemonic", .format = X, .flags = {...})` or an entry inside a `DEF_BATCH` in `hardware/src/gfx/GfxXXX/GfxXXXInstructions.def`. Rebuild. | | **Group of related instructions** | Use `DEF_BATCH(.format = X, ...)` to share the format across entries. Each entry is `Class, "mnemonic"` with optional per-entry `.flags`, `.cost`, `.operand_fields`, `.logical`. | | **Used from Logical IR / Rocisa** | Add `.logical = "LogicalName"` to the `DEF_T` / `DEF_BATCH` entry. Tablegen auto-generates the `setGfxXXXLogicalToArchMap()` — no manual `.cpp` edit needed. Also add an entry in `tools/tablegen/LogicalInstructionDefs.inc`. | | **Non-default cost** | Add `.cost = {cycle, latency}` to the `DEF_T` or `DEF_BATCH` entry. No .cpp edit. | | **Modifier-dependent cost** (e.g. matrix fmt) | Add `.costOverride = { { MatrixFmtModifiers(FP4, FP4), cycle, latency } }` alongside `.cost`. | | **Operand requirements** (e.g. 4 SGPRs, 8 SGPRs) | Define `.fields` in the format (e.g., `DEF_FORMAT(TENSOR, .fields = { {S0, ..., sgpr, 128}, {S1, ..., sgpr, 256} })`) or override per-instruction via `.operand_fields = { {D0, .size=64} }`. Tablegen generates `*_operands.inc`. No manual .hpp edit. | | **Dual-encoding operand override** | Add `.alt_operand_fields = { ... }` for the promoted encoding (e.g. VOP3 form of a VOP2 instruction). | | **New flag or format** | Add flag in `include/stinkytofu/hardware/Flags.def` and/or format in `hardware/src/gfx/GfxXXX/GfxXXXFormats.def`; then use in DEF_T / DEF_BATCH. | | **New instruction flag** | Add flag in `include/stinkytofu/hardware/Flags.def` and use in DEF_T / DEF_BATCH `.flags`. | **Single place to add a normal instruction:** `hardware/src/gfx/GfxXXX/GfxXXXInstructions.def` — one `DEF_T` or one entry in an existing `DEF_BATCH`. Rebuild. If it must be visible to Logical IR, add `.logical = "Name"` in the same `.def` entry and add the Logical def in `LogicalInstructionDefs.inc`. --- ## Chapter 1: Assembly IR (Hardware Layer) To add a new StinkyTofu assembly IR, you'll need to access the following files: ```bash include/stinkytofu/hardware/Flags.def # Instruction flags (optional) hardware/src/gfx/GfxXXX/GfxXXXInstructions.def # Instruction defs + costs + operands (DEF_T / DEF_BATCH) hardware/src/gfx/GfxXXX/GfxXXXFormats.def # Format definitions (if new format needed) hardware/src/gfx/GfxXXX/GfxXXX.cpp # Rocisa maps (only if not using .logical in .def) ``` Here we will add the instruction `s_wait_tensorcnt` step by step. ### Step 1. Add a flag (Optional) Add a flag in `include/stinkytofu/hardware/Flags.def` for a new instruction type if needed. ```c++ MACRO(IF_WaitTensorCnt) ``` ### Step 2. Use the flag in DEF_T (Optional) If you added a new flag in Step 1, use it in the instruction's `.flags` in the .def file. Tablegen will emit `DEF_T("mnemonic", IF_WaitTensorCnt)` in the generated _init.inc. No separate struct needed. **Note**: If your instruction is commutative (e.g., `v_add_f32` where `a+b = b+a`), mark it as such: ```c++ struct FloatAddInst : GfxInstDef { FloatAddInst() { hwInstDesc.flags.set(IF_Commutative); // Allow operand swapping in optimizer } }; ``` ### Step 3. Add definition and optional cost in the architecture .def file `s_wait_tensorcnt` is a Gfx1250 feature. Add it to `hardware/src/gfx/Gfx1250/Gfx1250Instructions.def` (not in the .cpp). Use `DEF_T(ClassName, "mnemonic", ...)` or add an entry inside a `DEF_BATCH(...)`. Both accept the same optional fields: `.format`, `.flags`, `.cost`, `.logical`, `.operand_fields`. The tablegen generates the init and cost tables; the .cpp only includes the generated files. #### DEF_T — single instruction Example (minimal — uses arch default cost). Use a flag that exists in `include/stinkytofu/hardware/Flags.def` (without the `IF_` prefix, e.g. `WaitTensorCnt`): ```c DEF_T(SWaitTensorcntInst, "s_wait_tensorcnt", .format = SOPP, .flags = {WaitTensorCnt}, .logical = "SWaitTensorcnt" ) ``` Example with explicit cost (cycle, latency): ```c DEF_T(SWaitTensorcntInst, "s_wait_tensorcnt", .format = SOPP, .flags = {WaitTensorCnt}, .cost = {1, 1}, .logical = "SWaitTensorcnt" ) ``` #### DEF_BATCH — group of instructions sharing a format When several instructions share the same format, use `DEF_BATCH` to avoid repeating `.format` on every entry. Shared header fields (`.format`, `.flags`, etc.) are inherited by all entries; per-entry fields override or extend them (flags are additive). ```c DEF_BATCH(.format = SOPP_WAIT16, SWaitLoadcntInst, "s_wait_loadcnt", .flags = {WaitCnt}, SWaitStorecntInst, "s_wait_storecnt", .flags = {WaitCnt}, SWaitTensorcntInst, "s_wait_tensorcnt", .flags = {WaitTensorCnt}, .logical = "SWaitTensorcnt", SWaitAluInst, "s_wait_alu", .flags = {HasSideEffect}, .operand_fields = { {S0, .type=wait_alu} }, ) ``` Each entry is: `ClassName, "mnemonic"` followed by optional per-entry fields (`.flags`, `.cost`, `.logical`, `.operand_fields`, `.costOverride`). Entries must **not** redefine `.format` — that comes from the batch header. Real-world example with load instructions, costs, and operand overrides: ```c DEF_BATCH(.format = MUBUF_LOAD, BufferLoadU8Inst, "buffer_load_u8", .cost = {12, 108}, .logical = "BufferLoadU8", BufferLoadB32Inst, "buffer_load_b32", .cost = {12, 108}, .logical = "BufferLoadB32", BufferLoadB128Inst, "buffer_load_b128", .cost = {12, 116}, .logical = "BufferLoadB128", .operand_fields = { {D0, .size=128} }, ) ``` Then rebuild so tablegen runs (e.g. `cmake --build .`). See [Architecture Overview](architecture.md) for full syntax and format inheritance. ### Step 4. Rocisa LogicalToArch mapping (automatic via `.logical`) If you added `.logical = "LogicalName"` to the `DEF_T` or `DEF_BATCH` entry in Step 3, the tablegen **auto-generates** the `setGfxXXXLogicalToArchMap()` function. No manual `.cpp` edit is needed. Multiple logical names can map to the same mnemonic: ```c SMovB32Inst, "s_mov_b32", .logical = {"SMovB32", "SSetMask"}, ``` If you do **not** use `.logical` in the `.def`, you can still add the mapping manually in `hardware/src/gfx/Gfx1250/Gfx1250.cpp` inside `setGfx1250LogicalToArchMap()`: ```c++ {"SWaitTensorcnt", "s_wait_tensorcnt"}, ``` Prefer `.logical` in the `.def` — it keeps the mapping next to the definition. --- ## Chapter 2: Logical IR (High-Level IR Layer) ### Step 5: Add Logical IR Definition Add to `shared/stinkytofu/tools/tablegen/LogicalInstructionDefs.inc`: ```c++ {"SWaitTensorcnt", // className "s_wait_tensorcnt", // mnemonic (must match assembly) "Wait for tensor operations to complete", // comment 0, // numSrcs (no source operands) false, // hasDest (no destination) "Synchronization", // category false, // supportsDPP false, // supportsSDWA false, // hasDS false}, // isCommutative ``` ### Step 6: Add Hardware Metadata (Costs + Operand Requirements) #### 6a. Instruction Costs Costs are defined in the **architecture .def file** and **arch.cmake**, not in the .cpp. - **Default cost**: Each arch sets a default in `arch.cmake` (ARCH_DEFAULT_CYCLE, ARCH_DEFAULT_LATENCY). E.g. Gfx1250 uses 1,1. Instructions without `.cost` in the .def use that default. - **Override**: In `hardware/src/gfx/GfxXXX/GfxXXXInstructions.def`, add `.cost = {cycle, latency}` to the instruction's `DEF_T(...)` or `DEF_BATCH` entry. Tablegen emits only non-default costs into `GfxXXX_costs.inc`; the generated block applies them. Example — standalone `DEF_T` with cost: ```c DEF_T(WaitTensorCntInst, "s_wait_tensorcnt", .format = SOPP, .flags = {SALU}, .cost = {1, 1} ) ``` Example — inside a `DEF_BATCH`: ```c DEF_BATCH(.format = MUBUF_LOAD, BufferLoadU8Inst, "buffer_load_u8", .cost = {12, 108}, .logical = "BufferLoadU8", BufferLoadB32Inst, "buffer_load_b32", .cost = {12, 108}, .logical = "BufferLoadB32", ) ``` ##### Modifier-dependent cost overrides (`.costOverride`) Matrix instructions can have different costs depending on the data format. Use `.costOverride` to specify alternate `{cycle, latency}` for specific format combinations: ```c DEF_T(VWmmaF3216x16x128F8f6f4Inst, "v_wmma_f32_16x16x128_f8f6f4", .format = WMMA, .cost = {5, 8}, .costOverride = { { MatrixFmtModifiers(FP4, FP4), 3, 4 } }, .operand_fields = { {S0, .size=512}, {S1, .size=512} } ) ``` Inside a `DEF_BATCH`: ```c DEF_BATCH(.format = MXWMMA_SCALE16, VWmmaScale16F3216x16x128F8f6f4Inst, "v_wmma_scale16_f32_16x16x128_f8f6f4", .cost = {6, 8}, .costOverride = { { MatrixFmtModifiers(FP4, FP4), 4, 4 } }, ) ``` Do **not** add cost entries to any array in `GfxXXX.cpp`; they are generated from the .def. See [Architecture Overview](architecture.md). #### 6b. Add Operand Requirements (Optional) If your instruction has specific register width or type requirements (e.g., `tensor_load_to_lds` requires 4 SGPRs for src0, 8 SGPRs for src1), define them via format-level `.fields` in `GfxXXXFormats.def` or per-instruction `.operand_fields` overrides in `GfxXXXInstructions.def`. Tablegen generates `GfxXXX_operands.inc`; each `GfxXXX.hpp` already includes it and applies requirements to the MCID table. **You do not edit the .hpp.** Example — format-level fields in `Gfx1250Formats.def` (applies to all instructions using this format): ```c DEF_FORMAT(TENSOR, .unit = TensorUnit, .encoding = {64}, .maxOperands = 4, .fields = { {S0, vaddr0, sgpr_srg0, sgpr, 128}, {S1, vaddr1, sgpr_srg1, sgpr, 256}, {S2, vaddr2, sgpr_srg2, sreg, 128}, {S3, vaddr3, sgpr_addr, sreg, 128}, }, ) ``` Example — per-instruction override with `DEF_T`: ```c DEF_T(BufferLoadB128Inst, "buffer_load_b128", .format = MUBUF_LOAD, .operand_fields = { {D0, .size=128} } ) ``` Example — per-instruction override inside `DEF_BATCH`: ```c DEF_BATCH(.format = MUBUF_LOAD, BufferLoadB32Inst, "buffer_load_b32", .cost = {12, 108}, .logical = "BufferLoadB32", BufferLoadB128Inst, "buffer_load_b128", .cost = {12, 116}, .logical = "BufferLoadB128", .operand_fields = { {D0, .size=128} }, ) ``` ##### Dual-encoding operand overrides (`.alt_operand_fields`) Instructions with dual encodings (e.g. VOP2 primary + VOP3 promoted) may need different operand layouts for each encoding. Use `.alt_operand_fields` for the promoted encoding: ```c DEF_T(VCndmaskB32Inst, "v_cndmask_b32", .format = VOP2, .logical = "VCndMaskB32", .operand_fields = { {D0, vdst, vgpr, 32}, {S0, src0, src, 32}, {S1, vsrc1, vgpr, 32}, {S2, src2, vcc, 64}, }, .alt_operand_fields = { {D0, vdst, vgpr, 32}, {S0, src0, src, 32}, {S1, src1, src, 32}, {S2, src2, sreg, 64}, }, ) ``` **When to add**: Instruction requires non-default register sizes or types for the IR verifier. Single-register instructions (32-bit) usually need no override. ### Step 7: Run Tablegen Regenerate IR classes and bindings: ```bash cd build cmake .. make tablegen_generated ``` This auto-generates: - `LogicalInstructions_generated.hpp` - C++ IR classes - `LogicalOpcodes_generated.inc` - Opcode enums - `StinkyBuilder_*.inc` - C++ builder methods - `IRMnemonics_generated.inc` - Mnemonic -> ASM mappings - `RocisaGfx1250Mappings.inc` - Rocisa -> ASM opcode mappings The mnemonic mapping automatically enables Logical IR -> Assembly IR conversion. --- ## Quick Reference: Instruction Metadata Locations Instruction **definitions** and **costs** live in `.def` files; tablegen generates `.inc` files that the `.cpp` includes. Rocisa mappings stay in the `.cpp`. ### Per-architecture layout | Metadata Type | Location | What to Modify | |---------------|----------|----------------| | **Definitions + costs** (DEF_T / DEF_BATCH) | `hardware/src/gfx/GfxXXX/GfxXXXInstructions.def` | Add `DEF_T(...)` or entry in `DEF_BATCH(...)` with `.format`, `.flags`, `.cost`, `.logical` | | **Formats** (optional) | `hardware/src/gfx/GfxXXX/GfxXXXFormats.def` | Add `DEF_FORMAT` if needed | | **Generated** (do not edit) | `hardware/generated/GfxXXX_init.inc`, `GfxXXX_costs.inc`, `GfxXXX_operands.inc` | Produced by tablegen from .def | | **Rocisa LogicalToArch map** | Auto-generated from `.logical` in .def (or manually in `GfxXXX.cpp`) | Prefer `.logical = "Name"` in the DEF_T / DEF_BATCH entry | | **Operand requirements** | `GfxXXXFormats.def` / `GfxXXXInstructions.def` | Define `.fields` in format or `.operand_fields` / `.alt_operand_fields` per-instruction; tablegen -> `*_operands.inc` (included by GfxXXX.hpp) | | **Modifier-dependent costs** | `GfxXXXInstructions.def` | `.costOverride = { { MatrixFmtModifiers(...), cycle, latency } }` alongside `.cost` | ### Gfx1250 Edit `hardware/src/gfx/Gfx1250/Gfx1250Instructions.def` (and optionally `Gfx1250Formats.def` in same folder), then rebuild. Use `DEF_T` for standalone instructions or `DEF_BATCH` for groups sharing a format. Add `.logical` in the `.def` to auto-generate the Rocisa LogicalToArch map. The corresponding `Gfx1250.cpp` only holds manually-added Rocisa maps if needed. Instruction definitions and costs come from the .def files and generated .inc files. ### See also - [Architecture Overview](architecture.md) -- design and data flow for .def -> tablegen -> .inc.