31 changed files with 2689 additions and 822 deletions
+1
View File
@@ -19,3 +19,4 @@ toolchain/lfs
toolchain/lpeg toolchain/lpeg
scratch scratch
toolchain/libpsn00b
+36 -35
View File
@@ -74,41 +74,7 @@
] ]
}, },
{ {
"name": "Debug: Hello GTE Psy-Q!", "name": "Debug: Hello GTE!",
"type": "gdb",
"request": "attach",
"target": "localhost:3333",
"remote": true,
"cwd": "${workspaceRoot}/build",
"valuesFormatting": "parseText",
"registerLimit": "1-32",
"frameFilters": false,
"showDevDebugOutput": false,
"printCalls": false,
"stopAtConnect": true,
"gdbpath": "gdb-multiarch",
"windows": {
"gdbpath": "gdb-multiarch.exe"
},
"osx": {
"gdbpath": "gdb"
},
"executable": "${workspaceRoot}/build/hello_gte.elf",
"setupCommands": [
{ "text": "set mi-async off" },
{ "text": "set remotetimeout 0" },
{ "text": "set logging file build/gen/hello_gte.gdb.log" },
{ "text": "set logging redirect on" }
],
"autorun": [
"monitor reset shellhalt",
"load hello_gte.elf",
"tbreak main",
"continue"
]
},
{
"name": "Debug: Hello GTE Psy-Q! (atoms debug — DWARF-injected)",
"type": "gdb", "type": "gdb",
"request": "attach", "request": "attach",
"target": "localhost:3333", "target": "localhost:3333",
@@ -141,6 +107,41 @@
"tbreak main", "tbreak main",
"continue" "continue"
] ]
},
{
"name": "Debug: Hello Joypad!",
"type": "gdb",
"request": "attach",
"target": "localhost:3333",
"remote": true,
"cwd": "${workspaceRoot}",
"valuesFormatting": "parseText",
"registerLimit": "1-32",
"frameFilters": false,
"showDevDebugOutput": false,
"printCalls": false,
"stopAtConnect": true,
"gdbpath": "gdb-multiarch",
"windows": {
"gdbpath": "gdb-multiarch.exe"
},
"osx": {
"gdbpath": "gdb"
},
"executable": "${workspaceRoot}/build/hello_joypad.dwarf-injected.elf",
"setupCommands": [
{ "text": "set mi-async off" },
{ "text": "set remotetimeout 0" },
{ "text": "set logging file build/gen/hello_joypad.gdb.log" },
{ "text": "set logging redirect on" }
],
"autorun": [
"monitor reset shellhalt",
"load build/hello_joypad.dwarf-injected.elf",
"source scripts/gdb/gdb_tape_atoms.gdb",
"tbreak main",
"continue"
]
} }
] ]
} }
+10 -9
View File
@@ -135,16 +135,17 @@ enum { false = 0, true = 1, true_overflow, };
typedef void Proc_(VoidFn) (void); typedef void Proc_(VoidFn) (void);
#define kilo(n) (C_(U4, n) << 10) #define kilo(n) (C_(U4, n) << 10)
#define mega(n) (C_(U4, n) << 20) #define mega(n) (C_(U4, n) << 20)
#define giga(n) (C_(U4, n) << 30) #define giga(n) (C_(U4, n) << 30)
#define tera(n) (C_(U4, n) << 40) #define tera(n) (C_(U4, n) << 40)
#define null C_(U4, 0)
#define nullptr C_(void*, 0)
#define O_(type, field) (C_(U4, & C_(type*,0)->field))
#define OT_(field) O_(typeof_ptr(& field), filed)) #define null C_(U4, 0)
#define S_(data) C_(U4, sizeof(data)) #define nullptr C_(void*, 0)
#define O_(type, field) C_(U4, & C_(type*,0)->field)
#define OA_(type, member, idx) C_(U4, & C_(type*,0)->member[idx])
#define OT_(field) O_(typeof_ptr(& field), filed))
#define S_(data) C_(U4, sizeof(data))
#define sop_1(op,a,b) C_(U1, s1_(a) op s1_(b)) #define sop_1(op,a,b) C_(U1, s1_(a) op s1_(b))
#define sop_2(op,a,b) C_(U2, s2_(a) op s2_(b)) #define sop_2(op,a,b) C_(U2, s2_(a) op s2_(b))
+14 -22
View File
@@ -50,17 +50,13 @@
#define asm_words(...) m_expand(glue(GCC_ASM_INL_, GCC_ASM_COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__)) #define asm_words(...) m_expand(glue(GCC_ASM_INL_, GCC_ASM_COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__))
// Very nasty macro expansion. See the Cruft pragma region after all the DSL defines // Very nasty macro expansion. See the Cruft pragma region after all the DSL defines
/* reg_str(n) — Stringify an integer register id into the GCC asm /* reg_str(n) — Stringify an integer register id into the GCC asm string form (e.g. 12 → "$12").
* string form (e.g. 12 → "$12"). Use this anywhere GCC's parser * Use this anywhere GCC's parser expects a literal string identifying a register: clobber lists,
* expects a literal string identifying a register: clobber lists, * asm templates, etc. The two-level macro is the standard preprocessor idiom for forcing one level of expansion before stringify —
* asm templates, etc. The two-level macro is the standard preprocessor * without it, `#n` would stringify the macro name `R_T4` to `"R_T4"` instead of expanding `R_T4` to its value first.
* idiom for forcing one level of expansion before stringify — without
* it, `#n` would stringify the macro name `R_T4` to `"R_T4"` instead
* of expanding `R_T4` to its value first.
* *
* For declaring a register variable bound to a specific GPR, use the * For declaring a register variable bound to a specific GPR, use the `rgcc(n)` bundle from gcc_asm.h instead —
* `rgcc(n)` bundle from gcc_asm.h instead — it adds the `__asm__()` * it adds the `__asm__()` qualifier around the string.
* qualifier around the string.
* *
* register V3_S2* p0 __asm__(reg_str(R_T4)) = ...; // verbose * register V3_S2* p0 __asm__(reg_str(R_T4)) = ...; // verbose
* register V3_S2* p0 rgcc(R_T4) = ...; // bundled * register V3_S2* p0 rgcc(R_T4) = ...; // bundled
@@ -85,7 +81,7 @@
* - The string "$12" is derived from it via reg_str, so they cannot drift apart. * - The string "$12" is derived from it via reg_str, so they cannot drift apart.
* - Spelling `__asm__(reg_str(R_T4_Code))` at every call site is noise. * - Spelling `__asm__(reg_str(R_T4_Code))` at every call site is noise.
* *
* tmpl defined in dsl.h (the token-paste glue). * tmpl defined in dsl.h (token-paste glue).
* rgcc define here (gcc_asm.h) because the `__asm__` keyword is GCC-specific. * rgcc define here (gcc_asm.h) because the `__asm__` keyword is GCC-specific.
* Anyone porting to a different compiler's asm dialect overrides rgcc, * Anyone porting to a different compiler's asm dialect overrides rgcc,
* and the integer→string derivation in rlit can be retargeted in one place. * and the integer→string derivation in rlit can be retargeted in one place.
@@ -94,12 +90,10 @@
* ------------------------------------------------------------------------ */ * ------------------------------------------------------------------------ */
#define rgcc(n) __asm__(rlit(n)) #define rgcc(n) __asm__(rlit(n))
/* rgcc_ref(n) — GCC operand-reference form "%N". Not currently used /* rgcc_ref(n) — GCC operand-reference form "%N". Not currently used by the placeholder-pun macros
* by the placeholder-pun macros (the .word bodies are fully baked * (the .word bodies are fully baked at compile time and have no runtime operand references),
* at compile time and have no runtime operand references), but kept * but kept here for completeness in case a future asm template needs to refer to a runtime input by position.
* here for completeness in case a future asm template needs to refer * Mirror of rgcc but produces "%N" instead of "$N". */
* to a runtime input by position. Mirror of rgcc but produces "%N"
* instead of "$N". */
#define rgcc_ref_(n) "%" #n #define rgcc_ref_(n) "%" #n
#define rgcc_ref(n) rgcc_ref_(n) #define rgcc_ref(n) rgcc_ref_(n)
@@ -147,11 +141,9 @@
9, 8, 7, 6, 5, 4, 3, 2, 1, 0)) 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
/* --- 2. String Concatenation Helpers --- * /* --- 2. String Concatenation Helpers --- *
* NOTE: we use `%0`, `%1`, ... not `%c0`, `%c1`, ... because GCC's * NOTE: we use `%0`, `%1`, ... not `%c0`, `%c1`, ... because GCC's asm-parser rejects `%cN` in this position with "invalid use of '%c'".
* asm-parser rejects `%cN` in this position with "invalid use of '%c'". * The `%cN` form is for printing *character* constants; for arbitrary integer immediates (the only kind `"i"(...)` produces),
* The `%cN` form is for printing *character* constants; for arbitrary * the plain `%N` form is the right one. Both expand to the bare immediate.
* integer immediates (the only kind `"i"(...)` produces), the plain
* `%N` form is the right one. Both expand to the bare immediate.
*/ */
#define GCC_ASM_W1 "%0" #define GCC_ASM_W1 "%0"
#define GCC_ASM_W2 GCC_ASM_W1 ", %1" #define GCC_ASM_W2 GCC_ASM_W1 ", %1"
+291 -243
View File
@@ -39,8 +39,8 @@
/* ============================================================================ /* ============================================================================
* Hardware MMIO Addresses * Hardware MMIO Addresses
* ============================================================================ * ============================================================================
* PSX GPU has two 32-bit ports in the I/O register region at KSEG2 * PSX GPU has two 32-bit ports in the I/O register region at KSEG2 0x1F800000+.
* 0x1F800000+. GP0 (offset 0x10) is the data port (commands + params). * GP0 (offset 0x10) is the data port (commands + params).
* GP1 (offset 0x14) is the control port (status, ctrl writes). * GP1 (offset 0x14) is the control port (status, ctrl writes).
* ============================================================================ */ * ============================================================================ */
/* IO base address (KSEG2 0x1F800000+ for the I/O register region). /* IO base address (KSEG2 0x1F800000+ for the I/O register region).
@@ -49,18 +49,18 @@
* `lui $reg, 0x1F80` (1 word) then `sw $data, GPIO_PORT*_OFFSET($reg)` (1 word). * `lui $reg, 0x1F80` (1 word) then `sw $data, GPIO_PORT*_OFFSET($reg)` (1 word).
* Mirrors the `IO_BASE_ADDR equ 0x1F80` + `gpio_port0 equ 0x1810` pattern from graphics_hello/gp.s. */ * Mirrors the `IO_BASE_ADDR equ 0x1F80` + `gpio_port0 equ 0x1810` pattern from graphics_hello/gp.s. */
enum { enum {
IO_BASE_ADDR = 0x1F800000, /* full 32-bit I/O region base */ IO_BASE_ADDR = 0x1F800000, /* full 32-bit I/O region base */
IO_BASE_ADDR_HI16 = 0x1F80, /* fits in a single `lui $reg, 0x1F80` */ IO_BASE_ADDR_HI16 = 0x1F80, /* fits in a single `lui $reg, 0x1F80` */
/* Offsets from IO_BASE_ADDR to each port. Used by tape-side macros /* Offsets from IO_BASE_ADDR to each port. Used by tape-side macros
* that pin a register to IO_BASE_ADDR and access ports via offsets: * that pin a register to IO_BASE_ADDR and access ports via offsets:
* sw $data, GPIO_PORT0_OFFSET($io_base) ; write GP0 * sw $data, GPIO_PORT0_OFFSET($io_base) ; write GP0
* sw $data, GPIO_PORT1_OFFSET($io_base) ; write GP1 */ * sw $data, GPIO_PORT1_OFFSET($io_base) ; write GP1 */
GPIO_PORT0_OFFSET = 0x1810, GPIO_PORT0_OFFSET = 0x1810,
GPIO_PORT1_OFFSET = 0x1814, GPIO_PORT1_OFFSET = 0x1814,
HW_GP0_ADDR = (IO_BASE_ADDR_HI16 << 16) | GPIO_PORT0_OFFSET, HW_GP0_ADDR = (IO_BASE_ADDR_HI16 << 16) | GPIO_PORT0_OFFSET,
HW_GP1_ADDR = (IO_BASE_ADDR_HI16 << 16) | GPIO_PORT1_OFFSET, HW_GP1_ADDR = (IO_BASE_ADDR_HI16 << 16) | GPIO_PORT1_OFFSET,
}; };
#define HW_GP0 C_(U4 V_*, HW_GP0_ADDR) #define HW_GP0 C_(U4 V_*, HW_GP0_ADDR)
@@ -73,66 +73,64 @@ enum {
* GP0 command byte constants + Layer 1 (GPU bitfield shifts) * GP0 command byte constants + Layer 1 (GPU bitfield shifts)
* ============================================================================ * ============================================================================
* 8-bit GP0 opcodes (the upper byte of a primitive's first word). These are the BYTE only. * 8-bit GP0 opcodes (the upper byte of a primitive's first word). These are the BYTE only.
* The layer-1 bitfield-layout constants live in the same enum block so the encoder can reference them by name.
* NO macro body past this point uses a raw shift or raw mask. * NO macro body past this point uses a raw shift or raw mask.
* Every shift/width/mask is named here, named once.
* Mirrors the OPCODE_SHIFT / RS_SHIFT / REG_MASK convention from mips.h. * Mirrors the OPCODE_SHIFT / RS_SHIFT / REG_MASK convention from mips.h.
* ============================================================================ */ * ============================================================================ */
enum { enum {
gp0_cmd_Nop = 0x00, gp0_cmd_Nop = 0x00,
/* Cache management */ /* Cache management */
gp0_cmd_ClearCache = 0x01, gp0_cmd_ClearCache = 0x01,
gp0_cmd_FillVram = 0x02, gp0_cmd_FillVram = 0x02,
gp0_cmd_CopyVram = 0x80, gp0_cmd_CopyVram = 0x80,
gp0_cmd_CopyVramChained = 0x81, gp0_cmd_CopyVramChained = 0x81,
gp0_cmd_ReadVram = 0xC0, gp0_cmd_ReadVram = 0xC0,
/* Polygons */ /* Polygons */
gp0_cmd_poly_f3 = 0x20, /* Flat Triangle */ gp0_cmd_poly_f3 = 0x20, /* Flat Triangle */
gp0_cmd_poly_ft3 = 0x24, /* Flat Textured Triangle */ gp0_cmd_poly_ft3 = 0x24, /* Flat Textured Triangle */
gp0_cmd_poly_g3 = 0x30, /* Gouraud Triangle */ gp0_cmd_poly_g3 = 0x30, /* Gouraud Triangle */
gp0_cmd_poly_gt3 = 0x34, /* Gouraud Textured Tri */ gp0_cmd_poly_gt3 = 0x34, /* Gouraud Textured Tri */
gp0_cmd_poly_f4 = 0x28, /* Flat Quad */ gp0_cmd_poly_f4 = 0x28, /* Flat Quad */
gp0_cmd_poly_ft4 = 0x2C, /* Flat Textured Quad */ gp0_cmd_poly_ft4 = 0x2C, /* Flat Textured Quad */
gp0_cmd_poly_g4 = 0x38, /* Gouraud Quad */ gp0_cmd_poly_g4 = 0x38, /* Gouraud Quad */
gp0_cmd_poly_gt4 = 0x3C, /* Gouraud Textured Quad */ gp0_cmd_poly_gt4 = 0x3C, /* Gouraud Textured Quad */
/* Lines */ /* Lines */
gp0_cmd_line_f2 = 0x40, gp0_cmd_line_f2 = 0x40,
gp0_cmd_line_g2 = 0x50, gp0_cmd_line_g2 = 0x50,
/* Sprites + Tiles + Rects */ /* Sprites + Tiles + Rects */
gp0_cmd_sprt_1 = 0x64, gp0_cmd_sprt_1 = 0x64,
gp0_cmd_sprt_8 = 0x74, gp0_cmd_sprt_8 = 0x74,
gp0_cmd_sprt_16 = 0x7C, gp0_cmd_sprt_16 = 0x7C,
gp0_cmd_tile_1 = 0x60, gp0_cmd_tile_1 = 0x60,
gp0_cmd_tile_8 = 0x68, gp0_cmd_tile_8 = 0x68,
gp0_cmd_tile_16 = 0x70, gp0_cmd_tile_16 = 0x70,
/* State setters (not drawing primitives; set render context). */ /* State setters (not drawing primitives; set render context). */
gp0_cmd_DrawModeSetting = 0xE1, /* TPage / draw-mode (semi-trans, dither, etc.) */ gp0_cmd_DrawModeSetting = 0xE1, /* TPage / draw-mode (semi-trans, dither, etc.) */
gp0_cmd_SetTextureWindow = 0xE2, gp0_cmd_SetTextureWindow = 0xE2,
gp0_cmd_SetDrawArea_TopLeft = 0xE3, gp0_cmd_SetDrawArea_TopLeft = 0xE3,
gp0_cmd_SetDrawArea_BotRight = 0xE4, gp0_cmd_SetDrawArea_BotRight = 0xE4,
gp0_cmd_SetDrawOffset = 0xE5, gp0_cmd_SetDrawOffset = 0xE5,
gp0_cmd_SetMaskBit = 0xE6, gp0_cmd_SetMaskBit = 0xE6,
/* bitfield shifts / widths / masks ---- /* bitfield shifts / widths / masks ----
* Generic GP0/GP1 command byte (upper 8 bits of every word sent to either port). */ * Generic GP0/GP1 command byte (upper 8 bits of every word sent to either port). */
gp0_cmd_shift = 24, gp0_cmd_shift = 24,
gp0_cmd_width = 8, gp0_cmd_width = 8,
gp0_cmd_mask = 0xFF, gp0_cmd_mask = 0xFF,
/* Color word layout (lives in Poly_F3.color, Poly_G4.c0..c3, etc.): /* Color word layout (lives in Poly_F3.color, Poly_G4.c0..c3, etc.):
* bits 31..24 = command byte * bits 31..24 = command byte
* bits 23..16 = BLUE * bits 23..16 = BLUE
* bits 15..08 = GREEN * bits 15..08 = GREEN
* bits 07..00 = RED (PSX GPU is BGR, NOT RGB) */ * bits 07..00 = RED (PSX GPU is BGR, NOT RGB) */
gp0_color_cmd_shift = 24, gp0_color_cmd_width = 8, gp0_color_cmd_mask = 0xFF, gp0_color_cmd_shift = 24, gp0_color_cmd_width = 8, gp0_color_cmd_mask = 0xFF,
gp0_color_blue_shift = 16, gp0_color_blue_width = 8, gp0_color_blue_mask = 0xFF, gp0_color_blue_shift = 16, gp0_color_blue_width = 8, gp0_color_blue_mask = 0xFF,
gp0_color_green_shift = 8, gp0_color_green_width = 8, gp0_color_green_mask = 0xFF, gp0_color_green_shift = 8, gp0_color_green_width = 8, gp0_color_green_mask = 0xFF,
gp0_color_red_shift = 0, gp0_color_red_width = 8, gp0_color_red_mask = 0xFF, gp0_color_red_shift = 0, gp0_color_red_width = 8, gp0_color_red_mask = 0xFF,
}; };
/* ============================================================================ /* ============================================================================
@@ -171,10 +169,13 @@ enum {
#define gp0_word_poly_gt4(r,g,b) enc_color_word(gp0_cmd_poly_gt4, (r),(g),(b)) #define gp0_word_poly_gt4(r,g,b) enc_color_word(gp0_cmd_poly_gt4, (r),(g),(b))
/* Cache management — bare-cmd words (no color/range payload). */ /* Cache management — bare-cmd words (no color/range payload). */
#define gp0_word_clear_cache() enc_gp0_cmd_word(gp0_cmd_ClearCache) #define gp0_word_clear_cache() enc_gp0_cmd_word(gp0_cmd_ClearCache)
#define gp0_word_fill_vram() enc_gp0_cmd_word(gp0_cmd_FillVram) #define gp0_word_fill_vram() enc_gp0_cmd_word(gp0_cmd_FillVram)
#define gp0_word_copy_vram() enc_gp0_cmd_word(gp0_cmd_CopyVram) #define gp0_word_copy_vram() enc_gp0_cmd_word(gp0_cmd_CopyVram)
#define gp0_word_read_vram() enc_gp0_cmd_word(gp0_cmd_ReadVram) #define gp0_word_read_vram() enc_gp0_cmd_word(gp0_cmd_ReadVram)
/* NOP — bare-cmd word (no effect; used as DR_ENV padding). */
#define gp0_word_nop() enc_gp0_cmd_word(gp0_cmd_Nop)
/* ============================================================================ /* ============================================================================
* GP1 command byte constants + Layer 1 (display-mode + range + draw-area bitfield shifts) * GP1 command byte constants + Layer 1 (display-mode + range + draw-area bitfield shifts)
@@ -184,58 +185,57 @@ enum {
* (cmd byte in the upper 8 bits via `enc_gp0_cmd(cmd)`). * (cmd byte in the upper 8 bits via `enc_gp0_cmd(cmd)`).
* ============================================================================ */ * ============================================================================ */
enum { enum {
gp1_cmd_Reset = 0x00, gp1_cmd_Reset = 0x00,
gp1_cmd_ResetCmdBuffer = 0x01, gp1_cmd_ResetCmdBuffer = 0x01,
gp1_cmd_AcknowledgeIRQ = 0x02, gp1_cmd_AcknowledgeIRQ = 0x02,
gp1_cmd_DisplayEnable = 0x03, gp1_cmd_DisplayEnable = 0x03,
gp1_cmd_DMADirection = 0x04, gp1_cmd_DMADirection = 0x04,
gp1_cmd_StartDisplayArea = 0x05, gp1_cmd_StartDisplayArea = 0x05,
gp1_cmd_HorizontalDisplayRange = 0x06, gp1_cmd_HorizontalDisplayRange = 0x06,
gp1_cmd_VerticalDisplayRange = 0x07, gp1_cmd_VerticalDisplayRange = 0x07,
gp1_cmd_DisplayMode = 0x08, gp1_cmd_DisplayMode = 0x08,
/* Note: GP1 only has commands 0x00..0x08. /* Note: GP1 only has commands 0x00..0x08.
* The state-setter commands (SetTextureWindow, * SetDrawArea*, SetDrawOffset, SetMaskBit) * The state-setter commands (SetTextureWindow, * SetDrawArea*, SetDrawOffset, SetMaskBit)
* live in the GP0 enum as * 0xE1..0xE6. * live in the GP0 enum as * 0xE1..0xE6.
* DrawArea word builders are below as GP0s * macros (since they emit GP0 commands). */ * DrawArea word builders are below as GP0s * macros (since they emit GP0 commands). */
/* ---- Display-mode payload flags (per PSX-SPX §"GP1 Display Mode"). /* ---- Display-mode payload flags (per PSX-SPX §"GP1 Display Mode").
* Bit positions match the encoder shifts below; values are the * Bit positions match the encoder shifts below; values are the *payload* bits only (cmd byte is OR'd in by enc_gp1_disp_mode_word). */
* *payload* bits only (the cmd byte is OR'd in by enc_gp1_disp_mode_word). */ gp1_disp_HRes_256 = 0x0,
gp1_disp_HRes_256 = 0x0, gp1_disp_HRes_320 = 0x1,
gp1_disp_HRes_320 = 0x1, gp1_disp_HRes_512 = 0x2,
gp1_disp_HRes_512 = 0x2, gp1_disp_HRes_640 = 0x3,
gp1_disp_HRes_640 = 0x3, gp1_disp_VRes_240 = 0x0,
gp1_disp_VRes_240 = 0x0, gp1_disp_VRes_480 = 0x1,
gp1_disp_VRes_480 = 0x1, gp1_disp_Color15 = 0x0,
gp1_disp_Color15 = 0x0, gp1_disp_Color24 = 0x1,
gp1_disp_Color24 = 0x1, gp1_disp_VInterlace = 0x1,
gp1_disp_VInterlace = 0x1,
/* ---- Layer 1: GP1 display-mode + range + draw-area shifts/masks ---- */ /* ---- Layer 1: GP1 display-mode + range + draw-area shifts/masks ---- */
gp1_disp_hres_shift = 0, gp1_disp_hres_width = 2, gp1_disp_hres_mask = 0x3, gp1_disp_hres_shift = 0, gp1_disp_hres_width = 2, gp1_disp_hres_mask = 0x3,
gp1_disp_vres_shift = 2, gp1_disp_vres_width = 1, gp1_disp_vres_mask = 0x1, gp1_disp_vres_shift = 2, gp1_disp_vres_width = 1, gp1_disp_vres_mask = 0x1,
gp1_disp_color_shift = 4, gp1_disp_color_width = 1, gp1_disp_color_mask = 0x1, gp1_disp_color_shift = 4, gp1_disp_color_width = 1, gp1_disp_color_mask = 0x1,
gp1_disp_interlace_shift = 5, gp1_disp_interlace_width = 1, gp1_disp_interlace_mask = 0x1, gp1_disp_interlace_shift = 5, gp1_disp_interlace_width = 1, gp1_disp_interlace_mask = 0x1,
/* GP1 horizontal display range: bits 0..11 = X2, bits 12..23 = X1 */ /* GP1 horizontal display range: bits 0..11 = X2, bits 12..23 = X1 */
gp1_hrange_x1_shift = 12, gp1_hrange_x1_width = 12, gp1_hrange_x1_mask = 0xFFF, gp1_hrange_x1_shift = 12, gp1_hrange_x1_width = 12, gp1_hrange_x1_mask = 0xFFF,
gp1_hrange_x2_shift = 0, gp1_hrange_x2_width = 12, gp1_hrange_x2_mask = 0xFFF, gp1_hrange_x2_shift = 0, gp1_hrange_x2_width = 12, gp1_hrange_x2_mask = 0xFFF,
/* GP1 vertical display range: bits 0..9 = Y2, bits 10..19 = Y1 */ /* GP1 vertical display range: bits 0..9 = Y2, bits 10..19 = Y1 */
gp1_vrange_y1_shift = 10, gp1_vrange_y1_width = 10, gp1_vrange_y1_mask = 0x3FF, gp1_vrange_y1_shift = 10, gp1_vrange_y1_width = 10, gp1_vrange_y1_mask = 0x3FF,
gp1_vrange_y2_shift = 0, gp1_vrange_y2_width = 10, gp1_vrange_y2_mask = 0x3FF, gp1_vrange_y2_shift = 0, gp1_vrange_y2_width = 10, gp1_vrange_y2_mask = 0x3FF,
/* GP1 draw area (top-left or bottom-right): bits 0..9 = X, bits 10..19 = Y /* GP1 draw area (top-left or bottom-right): bits 0..9 = X, bits 10..19 = Y
* (10-bit signed — caller pre-signs and masks with the named mask) */ * (10-bit signed — caller pre-signs and masks with the named mask) */
gp1_draw_x_shift = 0, gp1_draw_x_width = 10, gp1_draw_x_mask = 0x3FF, gp1_draw_x_shift = 0, gp1_draw_x_width = 10, gp1_draw_x_mask = 0x3FF,
gp1_draw_y_shift = 10, gp1_draw_y_width = 10, gp1_draw_y_mask = 0x3FF, gp1_draw_y_shift = 10, gp1_draw_y_width = 10, gp1_draw_y_mask = 0x3FF,
}; };
/* ---- Layer 1.5: GP1 per-field encoders ---- */ /* ---- Layer 1.5: GP1 per-field encoders ---- */
#define enc_gp1_disp_hres(h) (((h) & gp1_disp_hres_mask) << gp1_disp_hres_shift) #define enc_gp1_disp_hres(h) (((h) & gp1_disp_hres_mask) << gp1_disp_hres_shift)
#define enc_gp1_disp_vres(v) (((v) & gp1_disp_vres_mask) << gp1_disp_vres_shift) #define enc_gp1_disp_vres(v) (((v) & gp1_disp_vres_mask) << gp1_disp_vres_shift)
#define enc_gp1_disp_color(c) (((c) & gp1_disp_color_mask) << gp1_disp_color_shift) #define enc_gp1_disp_color(c) (((c) & gp1_disp_color_mask) << gp1_disp_color_shift)
#define enc_gp1_disp_interlace(i) (((i) & gp1_disp_interlace_mask << gp1_disp_interlace_shift) #define enc_gp1_disp_interlace(i) (((i) & gp1_disp_interlace_mask) << gp1_disp_interlace_shift)
#define enc_gp1_hrange_x1(x1) (((x1) & gp1_hrange_x1_mask) << gp1_hrange_x1_shift) #define enc_gp1_hrange_x1(x1) (((x1) & gp1_hrange_x1_mask) << gp1_hrange_x1_shift)
#define enc_gp1_hrange_x2(x2) (((x2) & gp1_hrange_x2_mask) << gp1_hrange_x2_shift) #define enc_gp1_hrange_x2(x2) (((x2) & gp1_hrange_x2_mask) << gp1_hrange_x2_shift)
@@ -255,6 +255,11 @@ enum {
#define enc_gp0_draw_area_br_word(x, y) (enc_gp0_cmd(gp0_cmd_SetDrawArea_BotRight) | enc_gp1_draw_x(x) | enc_gp1_draw_y(y)) #define enc_gp0_draw_area_br_word(x, y) (enc_gp0_cmd(gp0_cmd_SetDrawArea_BotRight) | enc_gp1_draw_x(x) | enc_gp1_draw_y(y))
/* ---- Layer 3: GP1 semantic word builders ---- */ /* ---- Layer 3: GP1 semantic word builders ---- */
#define gp1_word_Reset() enc_gp0_cmd_word(gp1_cmd_Reset)
#define gp1_word_ResetCmdBuffer() enc_gp0_cmd_word(gp1_cmd_ResetCmdBuffer)
#define gp1_word_AcknowledgeIRQ() enc_gp0_cmd_word(gp1_cmd_AcknowledgeIRQ)
#define gp1_word_StartDisplayArea() enc_gp0_cmd_word(gp1_cmd_StartDisplayArea)
#define gp1_word_display_enable(on) (enc_gp0_cmd(gp1_cmd_DisplayEnable) | ((on) & 1)) #define gp1_word_display_enable(on) (enc_gp0_cmd(gp1_cmd_DisplayEnable) | ((on) & 1))
#define gp1_word_display_disable() gp1_word_display_enable(0) #define gp1_word_display_disable() gp1_word_display_enable(0)
#define gp1_word_display_mode_320x240_15bit_ntsc enc_gp1_disp_mode_word(gp1_disp_HRes_320, gp1_disp_VRes_240, gp1_disp_Color15, 0) #define gp1_word_display_mode_320x240_15bit_ntsc enc_gp1_disp_mode_word(gp1_disp_HRes_320, gp1_disp_VRes_240, gp1_disp_Color15, 0)
@@ -279,31 +284,36 @@ enum {
#define gp1_word_display_enabled enc_gp0_cmd_word(gp1_cmd_DisplayEnable) #define gp1_word_display_enabled enc_gp0_cmd_word(gp1_cmd_DisplayEnable)
#define gp1_word_display_disabled (enc_gp0_cmd_word(gp1_cmd_DisplayEnable) | 1) #define gp1_word_display_disabled (enc_gp0_cmd_word(gp1_cmd_DisplayEnable) | 1)
#define gp1_word_DisplayOn() gp1_word_display_enable(0)
#define gp1_word_DisplayOff() gp1_word_display_enable(1)
/* ---- DMA direction (2-bit payload on DMADirection cmd 0x04) ---- */ /* ---- DMA direction (2-bit payload on DMADirection cmd 0x04) ---- */
enum { enum {
gp1_dma_dir_Off = 0, gp1_dma_dir_Off = 0,
gp1_dma_dir_FIFO = 1, gp1_dma_dir_FIFO = 1,
gp1_dma_dir_CPU_to_GPU = 2, gp1_dma_dir_CPU_to_GPU = 2,
gp1_dma_dir_GPUREAD_to_CPU = 3, gp1_dma_dir_GPUREAD_to_CPU = 3,
}; };
#define gp1_word_dma_direction(dir) (enc_gp0_cmd(gp1_cmd_DMADirection) | ((dir) & 0x3)) #define gp1_word_dma_direction(dir) (enc_gp0_cmd(gp1_cmd_DMADirection) | ((dir) & 0x3))
#define gp1_word_dma_to_gpu() gp1_word_dma_direction(gp1_dma_dir_CPU_to_GPU)
#define gp1_word_dma_read_cpu() gp1_word_dma_direction(gp1_dma_dir_GPUREAD_to_CPU)
/* ---- Standard display ranges (NTSC + PAL pre-baked) ---- */ /* ---- Standard display ranges (NTSC + PAL pre-baked) ---- */
/* Horizontal range values are in video clock units (8 units/pixel); vertical range values are scanline numbers. */ /* Horizontal range values are in video clock units (8 units/pixel); vertical range values are scanline numbers. */
enum { enum {
/* NTSC horizontal range: X1=608, X2=3168 */ /* NTSC horizontal range: X1=608, X2=3168 */
gp1_hrange_NTSC_x1 = 0x260, gp1_hrange_NTSC_x1 = 0x260,
gp1_hrange_NTSC_x2 = 0xC60, gp1_hrange_NTSC_x2 = 0xC60,
/* PAL horizontal range (same as NTSC for most CRTs) */ /* PAL horizontal range (same as NTSC for most CRTs) */
gp1_hrange_PAL_x1 = 0x260, gp1_hrange_PAL_x1 = 0x260,
gp1_hrange_PAL_x2 = 0xC60, gp1_hrange_PAL_x2 = 0xC60,
/* NTSC vertical range: Y1=24, Y2=264 */ /* NTSC vertical range: Y1=24, Y2=264 */
gp1_vrange_NTSC_y1 = 24, gp1_vrange_NTSC_y1 = 24,
gp1_vrange_NTSC_y2 = 264, gp1_vrange_NTSC_y2 = 264,
/* PAL vertical range: Y1=24, Y2=504 */ /* PAL vertical range: Y1=24, Y2=504 */
gp1_vrange_PAL_y1 = 24, gp1_vrange_PAL_y1 = 24,
gp1_vrange_PAL_y2 = 504, gp1_vrange_PAL_y2 = 504,
}; };
#define gp1_word_horizontal_range_ntsc enc_gp1_hrange_word(gp1_hrange_NTSC_x1, gp1_hrange_NTSC_x2) #define gp1_word_horizontal_range_ntsc enc_gp1_hrange_word(gp1_hrange_NTSC_x1, gp1_hrange_NTSC_x2)
@@ -314,14 +324,49 @@ enum {
/* ---- Draw-mode setting (TPage / draw-area allowance) ---- */ /* ---- Draw-mode setting (TPage / draw-area allowance) ---- */
/* The "drawing enabled" word is the standard post-init state. */ /* The "drawing enabled" word is the standard post-init state. */
enum { enum {
gp0_DrawMode_DrawToDispBit = 10, /* Per psx-spx, the standard 0xE1 layout has dfe at bit 10. But libpsyx's PutDrawEnv
* uses bit 19 (in the "unused" 14-23 range) for dfe in the DR_ENV code[0] — and the
* PSX hardware honors bit 19 in the DR_ENV context (not bit 10). So we need a
* separate bit definition for the DR_ENV-specific DrawMode. */
gp0_DrawMode_DrawToDispBit = 10, // standard psx-spx bit 10 (dfe)
gp0_DrawMode_DR_ENV_DrawToDispBit = 19, // libpsyx DR_ENV code[0] (dfe in DR_ENV context)
gp0_DrawMode_DR_ENV_isbgBit = 19, // libpsyx uses bit 19 for isbg too
}; };
#define gp0_word_draw_mode_drawing_allowed (enc_gp0_cmd(gp0_cmd_DrawModeSetting) | (1 << gp0_DrawMode_DrawToDispBit)) #define gp0_word_draw_mode_drawing_allowed (enc_gp0_cmd(gp0_cmd_DrawModeSetting) | (1 << gp0_DrawMode_DrawToDispBit))
/* ---- DrawArea pre-baked at origin (0,0) and full screen (320x240) ---- */ /* DR_ENV-specific DrawMode variants (libpsyx SetDrawEnv layout).
* The DR_ENV is a 16-word packet emitted at boot by gp_screen_init's ac_put_draw_env_demo
* atom component. Within the DR_ENV, the 0xE1 command is reused in three different bit
* configurations:
* code[0] = `gp0_word_draw_mode_drawing_allowed` (dfe=1; standard post-init state)
* code[6] = `gp0_word_dr_env_bg_color_cmd(isbg, r, g, b)` (initial-bg-color path)
* code[7] = `gp0_word_dr_env_draw_mode(isbg)` (isbg-flag path)
* Bits 0-23 of the 0xE1 word are the payload; bits 24-31 are the cmd byte (0xE1). */
#define gp0_word_dr_env_bg_color_cmd(isbg, r, g, b) (enc_gp0_cmd(gp0_cmd_DrawModeSetting) | (1 << gp0_DrawMode_DrawToDispBit) | ((isbg) ? gp0_dr_env_isbg_bit : 0) | enc_gp0_color_r(r) | enc_gp0_color_g(g) | enc_gp0_color_b(b))
#define gp0_word_dr_env_draw_mode(isbg) (enc_gp0_cmd(gp0_cmd_DrawModeSetting) | (1 << gp0_DrawMode_DrawToDispBit) | ((isbg) ? gp0_dr_env_isbg_bit : 0))
/* State-setter bare-cmd words (no immediate payload; the GPU uses the current state machine already programmed). */
#define gp0_word_set_texture_window() enc_gp0_cmd_word(gp0_cmd_SetTextureWindow)
#define gp0_word_set_draw_offset() enc_gp0_cmd_word(gp0_cmd_SetDrawOffset)
#define gp0_word_set_mask_bit() enc_gp0_cmd_word(gp0_cmd_SetMaskBit)
/* DR_ENV code[5] Mask (0xE6 cmd + isbg bit). The isbg bit is set so the GPU knows the auto-clear path is active (paired with code[6] + code[7]). */
#define gp0_word_dr_env_mask() (gp0_word_set_mask_bit() | gp0_dr_env_isbg_bit)
/* DR_ENV pre-baked constants (libpsyx PutDrawEnv layout).
* DR_ENV is a 16-word packet: tag = (length << 24) | addr, where length = 15 (15 code words follow) and addr = 0 (chain to nothing). */
enum {
PolyTag_len_bits = 8,
PolyTag_addr_bits = 24,
gp0_dr_env_tag = (15 << 24) | 0x00FFFFFF,
gp0_dr_env_isbg_bit = (1 << gp0_DrawMode_DR_ENV_isbgBit),
};
/* ---- DrawArea at origin (0,0) and full screen (320x240) ---- */
#define gp0_word_draw_area_top_left_origin enc_gp0_draw_area_tl_word(0, 0) #define gp0_word_draw_area_top_left_origin enc_gp0_draw_area_tl_word(0, 0)
#define gp0_word_draw_area_bottom_right_320x240 enc_gp0_draw_area_br_word(320, 240) #define gp0_word_draw_area_bottom_right_320x240 enc_gp0_draw_area_br_word(319, 239)
#define gp0_word_draw_area_bottom_right_640x480 enc_gp0_draw_area_br_word(640, 480) #define gp0_word_draw_area_bottom_right_640x480 enc_gp0_draw_area_br_word(639, 479)
#pragma endregion GPU Ports & Commands #pragma endregion GPU Ports & Commands
@@ -332,9 +377,9 @@ enum {
* Read from HW_GP1; the lower bits are DMA-block-size (variable-width). * Read from HW_GP1; the lower bits are DMA-block-size (variable-width).
* ============================================================================ */ * ============================================================================ */
enum { enum {
gp1_Status_BitReady = 31, gp1_Status_BitReady = 31,
gp1_Status_BitSendingDMA = 25, gp1_Status_BitSendingDMA = 25,
gp1_Status_DMABlockSizeShift = 0, gp1_Status_DMABlockSizeShift = 0,
}; };
#define gp1_status_is_ready() ((HW_GP1[0] >> gp1_Status_BitReady) & 1) #define gp1_status_is_ready() ((HW_GP1[0] >> gp1_Status_BitReady) & 1)
@@ -360,10 +405,10 @@ typedef Struct_(RGB8) { B1 r; B1 g; B1 b; };
#define rgb8(r,g,b) ((RGB8){r,g,b}) #define rgb8(r,g,b) ((RGB8){r,g,b})
/* ---------- PolyTag (the OT-link header; 1 word) ---------- */ /* ---------- PolyTag (the OT-link header; 1 word) ---------- */
enum { // enum {
PolyTag_len_bits = 8, // PolyTag_len_bits = 8,
PolyTag_addr_bits = 24, // PolyTag_addr_bits = 24,
}; // };
typedef Struct_(PolyTag) { typedef Struct_(PolyTag) {
union { union {
U4 code; U4 code;
@@ -387,95 +432,95 @@ typedef Struct_(PolyTag) {
/* ---------- Poly_F3 (Flat Triangle; 5 words) ---------- */ /* ---------- Poly_F3 (Flat Triangle; 5 words) ---------- */
typedef Struct_(Poly_F3) { typedef Struct_(Poly_F3) {
U4 tag; U4 tag;
RGB8 color; RGB8 color;
B1 code; B1 code;
union { union {
struct { V2_S2 p0; V2_S2 p1; V2_S2 p2; }; struct { V2_S2 p0; V2_S2 p1; V2_S2 p2; };
A3_V2_S2 points; A3_V2_S2 points;
}; };
}; };
/* ---------- Poly_F4 (Flat Quad; 6 words) ---------- */ /* ---------- Poly_F4 (Flat Quad; 6 words) ---------- */
typedef Struct_(Poly_F4) { typedef Struct_(Poly_F4) {
U4 tag; U4 tag;
RGB8 color; RGB8 color;
B1 code; B1 code;
union { union {
struct { V2_S2 p0; V2_S2 p1; V2_S2 p2; V2_S2 p3; }; struct { V2_S2 p0; V2_S2 p1; V2_S2 p2; V2_S2 p3; };
A4_V2_S2 points; A4_V2_S2 points;
}; };
}; };
/* ---------- Poly_G3 (Gouraud Triangle; 7 words) ---------- */ /* ---------- Poly_G3 (Gouraud Triangle; 7 words) ---------- */
typedef Struct_(Poly_G3) { typedef Struct_(Poly_G3) {
U4 tag; RGB8 c0; B1 code; U4 tag; RGB8 c0; B1 code;
V2_S2 p0; RGB8 c1; B1 pad1; V2_S2 p0; RGB8 c1; B1 pad1;
V2_S2 p1; RGB8 c2; B1 pad2; V2_S2 p1; RGB8 c2; B1 pad2;
V2_S2 p2; V2_S2 p2;
}; };
/* ---------- Poly_G4 (Gouraud Quad; 9 words) ---------- */ /* ---------- Poly_G4 (Gouraud Quad; 9 words) ---------- */
typedef Struct_(Poly_G4) { typedef Struct_(Poly_G4) {
U4 tag; RGB8 c0; B1 code; U4 tag; RGB8 c0; B1 code;
V2_S2 p0; RGB8 c1; B1 pad1; V2_S2 p0; RGB8 c1; B1 pad1;
V2_S2 p1; RGB8 c2; B1 pad2; V2_S2 p1; RGB8 c2; B1 pad2;
V2_S2 p2; RGB8 c3; B1 pad3; V2_S2 p2; RGB8 c3; B1 pad3;
V2_S2 p3; V2_S2 p3;
}; };
/* ---------- Poly_FT3 (Flat Textured Triangle; placeholder layout) ---------- */ /* ---------- Poly_FT3 (Flat Textured Triangle; placeholder layout) ---------- */
/* TODO(Ed): verify the textured-variant layout against PSX-SPX when needed. */ /* TODO(Ed): verify the textured-variant layout against PSX-SPX when needed. */
typedef Struct_(Poly_FT3) { typedef Struct_(Poly_FT3) {
U4 tag; U4 tag;
RGB8 color; RGB8 color;
B1 code; B1 code;
U4 tpage; U4 tpage;
U4 clut; U4 clut;
V2_S2 p0; U1 u0; U1 v0; V2_S2 p0; U1 u0; U1 v0;
V2_S2 p1; U1 u1; U1 v1; V2_S2 p1; U1 u1; U1 v1;
V2_S2 p2; U1 u2; U1 v2; V2_S2 p2; U1 u2; U1 v2;
}; };
/* ---------- Poly_FT4 (Flat Textured Quad) ---------- */ /* ---------- Poly_FT4 (Flat Textured Quad) ---------- */
typedef Struct_(Poly_FT4) { typedef Struct_(Poly_FT4) {
U4 tag; U4 tag;
RGB8 color; RGB8 color;
B1 code; B1 code;
U4 tpage; U4 tpage;
U4 clut; U4 clut;
V2_S2 p0; U1 u0; U1 v0; V2_S2 p0; U1 u0; U1 v0;
V2_S2 p1; U1 u1; U1 v1; V2_S2 p1; U1 u1; U1 v1;
V2_S2 p2; U1 u2; U1 v2; V2_S2 p2; U1 u2; U1 v2;
V2_S2 p3; U1 u3; U1 v3; V2_S2 p3; U1 u3; U1 v3;
}; };
/* ---------- Poly_GT3 (Gouraud Textured Triangle) ---------- */ /* ---------- Poly_GT3 (Gouraud Textured Triangle) ---------- */
typedef Struct_(Poly_GT3) { typedef Struct_(Poly_GT3) {
U4 tag; RGB8 c0; B1 code; U4 tag; RGB8 c0; B1 code;
V2_S2 p0; RGB8 c1; B1 pad1; V2_S2 p0; RGB8 c1; B1 pad1;
V2_S2 p1; RGB8 c2; B1 pad2; V2_S2 p1; RGB8 c2; B1 pad2;
V2_S2 p2; V2_S2 p2;
U4 tpage; U4 tpage;
U4 clut; U4 clut;
V2_S2 tp0; U1 u0; U1 v0; V2_S2 tp0; U1 u0; U1 v0;
V2_S2 tp1; U1 u1; U1 v1; V2_S2 tp1; U1 u1; U1 v1;
V2_S2 tp2; U1 u2; U1 v2; V2_S2 tp2; U1 u2; U1 v2;
}; };
/* ---------- Poly_GT4 (Gouraud Textured Quad) ---------- */ /* ---------- Poly_GT4 (Gouraud Textured Quad) ---------- */
typedef Struct_(Poly_GT4) { typedef Struct_(Poly_GT4) {
U4 tag; RGB8 c0; B1 code; U4 tag; RGB8 c0; B1 code;
V2_S2 p0; RGB8 c1; B1 pad1; V2_S2 p0; RGB8 c1; B1 pad1;
V2_S2 p1; RGB8 c2; B1 pad2; V2_S2 p1; RGB8 c2; B1 pad2;
V2_S2 p2; RGB8 c3; B1 pad3; V2_S2 p2; RGB8 c3; B1 pad3;
V2_S2 p3; V2_S2 p3;
U4 tpage; U4 tpage;
U4 clut; U4 clut;
V2_S2 tp0; U1 u0; U1 v0; V2_S2 tp0; U1 u0; U1 v0;
V2_S2 tp1; U1 u1; U1 v1; V2_S2 tp1; U1 u1; U1 v1;
V2_S2 tp2; U1 u2; U1 v2; V2_S2 tp2; U1 u2; U1 v2;
V2_S2 tp3; U1 u3; U1 v3; V2_S2 tp3; U1 u3; U1 v3;
}; };
/* ---------- Primitive setters (C-level) ---------- /* ---------- Primitive setters (C-level) ----------
@@ -510,26 +555,29 @@ typedef Struct_(Poly_GT4) {
* bits 12..31 = reserved (zero) * bits 12..31 = reserved (zero)
* ============================================================================ */ * ============================================================================ */
enum { enum {
/* ---- Layer 1: TPage bitfield shifts / widths / masks ---- */ /* ---- Layer 1: TPage bitfield shifts / widths / masks ---- */
gp0_tpage_x_shift = 0, gp0_tpage_x_width = 4, gp0_tpage_x_mask = 0xF, gp0_tpage_x_shift = 0, gp0_tpage_x_width = 4, gp0_tpage_x_mask = 0xF,
gp0_tpage_y_shift = 4, gp0_tpage_y_width = 1, gp0_tpage_y_mask = 0x1, gp0_tpage_y_shift = 4, gp0_tpage_y_width = 1, gp0_tpage_y_mask = 0x1,
gp0_tpage_semi_trans_shift = 5, gp0_tpage_semi_trans_width = 2, gp0_tpage_semi_trans_mask = 0x3, gp0_tpage_semi_trans_shift = 5, gp0_tpage_semi_trans_width = 2, gp0_tpage_semi_trans_mask = 0x3,
gp0_tpage_color_depth_shift = 7, gp0_tpage_color_depth_width = 2, gp0_tpage_color_depth_mask = 0x3, gp0_tpage_color_depth_shift = 7, gp0_tpage_color_depth_width = 2, gp0_tpage_color_depth_mask = 0x3,
gp0_tpage_dither_shift = 9, gp0_tpage_dither_width = 1, gp0_tpage_dither_mask = 0x1, gp0_tpage_dither_shift = 9, gp0_tpage_dither_width = 1, gp0_tpage_dither_mask = 0x1,
gp0_tpage_draw_to_disp_shift = 10, gp0_tpage_draw_to_disp_width = 1, gp0_tpage_draw_to_disp_mask = 0x1, gp0_tpage_draw_to_disp_shift = 10, gp0_tpage_draw_to_disp_width = 1, gp0_tpage_draw_to_disp_mask = 0x1,
gp0_tpage_tex_disable_shift = 11, gp0_tpage_tex_disable_width = 1, gp0_tpage_tex_disable_mask = 0x1, gp0_tpage_tex_disable_shift = 11, gp0_tpage_tex_disable_width = 1, gp0_tpage_tex_disable_mask = 0x1,
/* TPage color-depth payload values (NOT bit positions — these go in /* TPage color-depth payload values (NOT bit positions — these go in
* the 2-bit field at gp0_tpage_color_depth_shift). */ * the 2-bit field at gp0_tpage_color_depth_shift). */
gp0_tpage_color_4bpp = 0x0, gp0_tpage_color_4bpp = 0x0,
gp0_tpage_color_8bpp = 0x1, gp0_tpage_color_8bpp = 0x1,
gp0_tpage_color_16bpp = 0x2, gp0_tpage_color_16bpp = 0x2,
/* TPage semi-transparency mode payload values (NOT bit positions). */ /* Default TPage value libpsyx's SetDefDrawEnv writes (matches the `li v1, 10; sh v1, 20(v0)` sequence at C11_only.elf:0x8001273C). */
gp0_tpage_semi_trans_none = 0x0, gp0_tpage_default = 10,
gp0_tpage_semi_trans_alpha = 0x1,
gp0_tpage_semi_trans_add = 0x2, /* TPage semi-transparency mode payload values (NOT bit positions). */
gp0_tpage_semi_trans_sub = 0x3, gp0_tpage_semi_trans_none = 0x0,
gp0_tpage_semi_trans_alpha = 0x1,
gp0_tpage_semi_trans_add = 0x2,
gp0_tpage_semi_trans_sub = 0x3,
}; };
/* ---- Layer 1.5: TPage per-field encoders. Mirrors enc_gte_sf/mx/v in gte.h. ---- */ /* ---- Layer 1.5: TPage per-field encoders. Mirrors enc_gte_sf/mx/v in gte.h. ---- */
@@ -543,19 +591,19 @@ enum {
/* ---- Layer 2: TPage composite encoder. Mirrors enc_gte_cmdw in gte.h ---- */ /* ---- Layer 2: TPage composite encoder. Mirrors enc_gte_cmdw in gte.h ---- */
#define enc_gp0_tpage_word(x, y, semi_trans, color_depth, dither, draw_to_disp, tex_disable) \ #define enc_gp0_tpage_word(x, y, semi_trans, color_depth, dither, draw_to_disp, tex_disable) \
(enc_gp0_tpage_x(x) \ (enc_gp0_tpage_x(x) \
| enc_gp0_tpage_y(y) \ | enc_gp0_tpage_y(y) \
| enc_gp0_tpage_semi_trans(semi_trans) \ | enc_gp0_tpage_semi_trans(semi_trans) \
| enc_gp0_tpage_color_depth(color_depth) \ | enc_gp0_tpage_color_depth(color_depth) \
| enc_gp0_tpage_dither(dither) \ | enc_gp0_tpage_dither(dither) \
| enc_gp0_tpage_draw_to_disp(draw_to_disp) \ | enc_gp0_tpage_draw_to_disp(draw_to_disp) \
| enc_gp0_tpage_tex_disable(tex_disable)) | enc_gp0_tpage_tex_disable(tex_disable))
typedef Struct_(TexturePage) { U4 raw; }; typedef Struct_(TexturePage) { U4 raw; };
/* ---- Layer 3: TPage semantic word builder ---- */ /* ---- Layer 3: TPage semantic word builder ---- */
#define gp0_word_tpage(x, y, semi_trans, color_depth, dither, draw_to_disp, tex_disable) \ #define gp0_word_tpage(x, y, semi_trans, color_depth, dither, draw_to_disp, tex_disable) \
enc_gp0_tpage_word((x), (y), (semi_trans), (color_depth), (dither), (draw_to_disp), (tex_disable)) enc_gp0_tpage_word((x), (y), (semi_trans), (color_depth), (dither), (draw_to_disp), (tex_disable))
#pragma endregion TPage #pragma endregion TPage
#pragma region CLUT #pragma region CLUT
@@ -569,12 +617,12 @@ typedef Struct_(TexturePage) { U4 raw; };
* bits 24..31 = command byte — 0x20 (4bpp load) or 0x25 (8bpp load) * bits 24..31 = command byte — 0x20 (4bpp load) or 0x25 (8bpp load)
* ============================================================================ */ * ============================================================================ */
enum { enum {
/* ---- Layer 1: CLUT bitfield shifts / widths / masks ---- */ /* ---- Layer 1: CLUT bitfield shifts / widths / masks ---- */
gp0_clut_y_shift = 0, gp0_clut_y_width = 6, gp0_clut_y_mask = 0x3F, gp0_clut_y_shift = 0, gp0_clut_y_width = 6, gp0_clut_y_mask = 0x3F,
gp0_clut_x_shift = 6, gp0_clut_x_width = 9, gp0_clut_x_mask = 0x1FF, gp0_clut_x_shift = 6, gp0_clut_x_width = 9, gp0_clut_x_mask = 0x1FF,
/* CLUT-load cmd-byte variants — the upper byte of the GP0 word. */ /* CLUT-load cmd-byte variants — the upper byte of the GP0 word. */
gp0_clut_cmd_Load4bpp = 0x20, gp0_clut_cmd_Load4bpp = 0x20,
gp0_clut_cmd_Load8bpp = 0x25, gp0_clut_cmd_Load8bpp = 0x25,
}; };
/* ---- Layer 1.5: CLUT per-field encoders ---- */ /* ---- Layer 1.5: CLUT per-field encoders ---- */
@@ -615,26 +663,26 @@ enum {
* Stoppped for now at the struct + enum level. * Stoppped for now at the struct + enum level.
* ============================================================================ */ * ============================================================================ */
enum { enum {
tim_file_id_magic = 0x10, tim_file_id_magic = 0x10,
tim_type_4bpp = 0x00, tim_type_4bpp = 0x00,
tim_type_8bpp = 0x01, tim_type_8bpp = 0x01,
tim_type_16bpp = 0x02, tim_type_16bpp = 0x02,
tim_type_32bpp = 0x03, tim_type_32bpp = 0x03,
tim_type_mixed = 0x04, tim_type_mixed = 0x04,
tim_flag_has_clut = 0x08, tim_flag_has_clut = 0x08,
}; };
typedef Struct_(TIM_Header) { typedef Struct_(TIM_Header) {
U4 file_id; /* always 0x10 = "TIM" magic */ U4 file_id; /* always 0x10 = "TIM" magic */
U4 version; /* ignored; always 0 */ U4 version; /* ignored; always 0 */
U4 flags; /* bits 0..2 = type, bit 3 = has_clut */ U4 flags; /* bits 0..2 = type, bit 3 = has_clut */
}; };
typedef Struct_(TIM_SectionHeader) { typedef Struct_(TIM_SectionHeader) {
U4 section_length; /* bytes in this section including this header */ U4 section_length; /* bytes in this section including this header */
U2 org_x; /* origin in VRAM */ U2 org_x; /* origin in VRAM */
U2 org_y; U2 org_y;
U2 width; /* width in pixels */ U2 width; /* width in pixels */
U2 height; /* height in pixels */ U2 height; /* height in pixels */
}; };
#pragma endregion TIM File Format #pragma endregion TIM File Format
View File
+82 -126
View File
@@ -17,9 +17,8 @@
* gte_lw_v0_xy(base) (gte + lw + v0 + xy) * gte_lw_v0_xy(base) (gte + lw + v0 + xy)
* load_upper_i (load-upper + immediate, unique verb) * load_upper_i (load-upper + immediate, unique verb)
* *
* Vendor mnemonics (gte_mtc2, gte_mfc2, gte_lwc2, gte_swc2, etc.) are * Vendor mnemonics (gte_mtc2, gte_mfc2, gte_lwc2, gte_swc2, etc.) are NOT in this header.
* NOT in this header. They live in the opt-in `gte_vendor_sym.h` for * They are in the opt-in `gte_vendor_sym.h` for users who prefer the textbook MIPS assembly mnemonics.
* users who prefer the textbook MIPS assembly mnemonics.
* ============================================================================ */ * ============================================================================ */
#ifdef INTELLISENSE_DIRECTIVES #ifdef INTELLISENSE_DIRECTIVES
@@ -34,20 +33,16 @@
* gte.h — Geometry Transformation Engine (COP2) for the PS1 * gte.h — Geometry Transformation Engine (COP2) for the PS1
* ============================================================================ * ============================================================================
* *
* Hand-rolled DSL for emitting GTE/MIPS instruction words as raw `.word` * Hand-rolled DSL for emitting GTE/MIPS instruction words as raw `.word` constants from C.
* constants from C. No GCC inline-assembly string syntax in the code body. * No GCC inline-assembly string syntax in the code body.
* *
* STYLE NOTES * STYLE NOTES
* ----------- * -----------
* - Per-field encoders are named `enc_gte_<field>(value)` and each one * - Per-field encoders are named `enc_gte_<field>(value)` and each one self-masks its argument before shifting.
* self-masks its argument before shifting. Mirrors the `enc_op / enc_rs * Mirrors the `enc_op / enc_rs / enc_rt / ...` family in mips.h.
* / enc_rt / ...` family in mips.h. * - The composite `enc_gte_cmdw(sf, mx, v, cv, lm, cmd)` is a flat OR of the per-field encoders, plus the COP2/CO base.
* - The composite `enc_gte_cmdw(sf, mx, v, cv, lm, cmd)` is a flat OR of * - Pre-baked shortcuts (`gte_cmd_rtpt`, `gte_cmd_rtps`, …) are defined for the common cases so call sites read like assembly source.
* the per-field encoders, plus the COP2/CO base. * - All register/field values are enums (not `#define`s) so they show up in debugger symbol tables and IDE autocomplete.
* - Pre-baked shortcuts (`gte_cmd_rtpt`, `gte_cmd_rtps`, …) are defined
* for the common cases so call sites read like assembly source.
* - All register/field values are enums (not `#define`s) so they show up
* in debugger symbol tables and IDE autocomplete.
* *
* SEE ALSO * SEE ALSO
* -------- * --------
@@ -58,8 +53,7 @@
/* --- GTE Data Registers (Coprocessor 2) --- /* --- GTE Data Registers (Coprocessor 2) ---
* Preprocessor-visible integer ids for the COP2 data register file. * Preprocessor-visible integer ids for the COP2 data register file.
* Each enum value is bound to a parallel `_Code` `#define` so the * Each enum value is bound to a parallel `_Code` `#define` so the preprocessor can stringify the integer (for `reg_str`/`rgcc` paths).
* preprocessor can stringify the integer (for `reg_str`/`rgcc` paths).
* Same pattern as the GPR `_Code` set in mips.h. */ * Same pattern as the GPR `_Code` set in mips.h. */
#define C2_VXY0_Code 0 #define C2_VXY0_Code 0
#define C2_VZ0_Code 1 #define C2_VZ0_Code 1
@@ -192,10 +186,8 @@ enum {
/* --- GTE Control Register Indices (for ctc2/cfc2) --- /* --- GTE Control Register Indices (for ctc2/cfc2) ---
* Preprocessor-visible integer ids for the COP2 control register file. * Preprocessor-visible integer ids for the COP2 control register file.
* Each enum value is bound to a parallel `_Code` `#define` so the * Each enum value is bound to a parallel `_Code` `#define` so the preprocessor can stringify the integer (for `reg_str`/`rgcc` paths).
* preprocessor can stringify the integer (for `reg_str`/`rgcc` paths). * Same pattern as the GPR `_Code` set in mips.h. Note: indices 21-23 are reserved/unused on real hardware, so there's a gap. */
* Same pattern as the GPR `_Code` set in mips.h. Note: indices 21-23
* are reserved/unused on real hardware, so there's a gap. */
#define gte_cr_RT11_Code 0 #define gte_cr_RT11_Code 0
#define gte_cr_RT12_Code 1 /* packed with RT13 in bits 16..31 */ #define gte_cr_RT12_Code 1 /* packed with RT13 in bits 16..31 */
#define gte_cr_RT13_Code 2 /* packed with RT22 in bits 16..31 */ #define gte_cr_RT13_Code 2 /* packed with RT22 in bits 16..31 */
@@ -223,8 +215,9 @@ enum {
#define gte_cr_RFC_Code 27 #define gte_cr_RFC_Code 27
#define gte_cr_GFC_Code 28 #define gte_cr_GFC_Code 28
#define gte_cr_BFC_Code 29 #define gte_cr_BFC_Code 29
#define gte_cr_OFX_Code 30 #define gte_cr_OFX_Code 24
#define gte_cr_OFY_Code 31 #define gte_cr_OFY_Code 25
#define gte_cr_H_Code 26
enum { enum {
gte_cr_RT11 = gte_cr_RT11_Code, gte_cr_RT12 = gte_cr_RT12_Code, gte_cr_RT13 = gte_cr_RT13_Code, gte_cr_RT11 = gte_cr_RT11_Code, gte_cr_RT12 = gte_cr_RT12_Code, gte_cr_RT13 = gte_cr_RT13_Code,
@@ -246,21 +239,16 @@ enum { _C2_OPS_ = 0
/* COP2 transfer sub-opcodes (5-bit field in the `rs` slot of enc_gte_tx). /* COP2 transfer sub-opcodes (5-bit field in the `rs` slot of enc_gte_tx).
* *
* Spans the 2x2 {From, To} × {Data, Control} register classes that the * Spans the 2x2 {From, To} × {Data, Control} register classes that the GTE exposes:
* GTE exposes:
*
* bit 1 (0x02): register class — 0 = data, 1 = control * bit 1 (0x02): register class — 0 = data, 1 = control
* bit 2 (0x04): direction — 0 = read, 1 = write * bit 2 (0x04): direction — 0 = read, 1 = write
* *
* The values 0x00 (sub_mfc2) and 0x04 (sub_mtc2) are the same 5-bit * The values 0x00 (sub_mfc2) and 0x04 (sub_mtc2) are the same 5-bit numbers as the general MIPS `cop_mf` / `cop_mt` defined in mips.h
* numbers as the general MIPS `cop_mf` / `cop_mt` defined in mips.h * (which target the data register file on any coprocessor).
* (which target the data register file on any coprocessor). They are * They are re-aliased here so the four-way table reads like the spec mnemonics (MFC2 / CFC2 / MTC2 / CTC2)
* re-aliased here so the four-way table reads like the spec mnemonics * and so the encoding lives next to its only consumer (this header).
* (MFC2 / CFC2 / MTC2 / CTC2) and so the encoding lives next to its
* only consumer (this header).
* *
* Vendor mnemonic aliases (gte_mfc2 / gte_mtc2 / gte_cfc2 / gte_ctc2) * Vendor mnemonic aliases (gte_mfc2 / gte_mtc2 / gte_cfc2 / gte_ctc2) live in gte_vendor_sym.h. */
* live in gte_vendor_sym.h. */
enum { _C2_TX_SUBS_ = 0 enum { _C2_TX_SUBS_ = 0
, sub_mfc2 = 0x00 /* MFC2: Move From Coprocessor 2 data reg */ , sub_mfc2 = 0x00 /* MFC2: Move From Coprocessor 2 data reg */
, sub_cfc2 = 0x02 /* CFC2: Copy From Coprocessor 2 ctrl reg */ , sub_cfc2 = 0x02 /* CFC2: Copy From Coprocessor 2 ctrl reg */
@@ -270,11 +258,11 @@ enum { _C2_TX_SUBS_ = 0
/* COP2 (GTE) Transfer Format: mfc2 / cfc2 / mtc2 / ctc2 rt, rd /* COP2 (GTE) Transfer Format: mfc2 / cfc2 / mtc2 / ctc2 rt, rd
* Layout: [op_cop2:6][sub:5][rt:5][rd:5][0:11] * Layout: [op_cop2:6][sub:5][rt:5][rd:5][0:11]
* - sub: one of sub_mfc2 / sub_cfc2 / sub_mtc2 / sub_ctc2 * - sub: one of sub_mfc2 / sub_cfc2 / sub_mtc2 / sub_ctc2
* - rt: GPR source/dest * - rt: GPR source/dest
* - rd: COP2 register index (0..31): * - rd: COP2 register index (0..31):
* data class → C2_VXY0_Code..C2_LZCR_Code (gte_in_v0_xy..gte_math_accum2 aliases) * data class → C2_VXY0_Code..C2_LZCR_Code (gte_in_v0_xy..gte_math_accum2 aliases)
* ctrl class → gte_cr_RT11_Code..gte_cr_OFY_Code */ * ctrl class → gte_cr_RT11_Code..gte_cr_OFY_Code */
#define enc_gte_tx(sub, rt, rd) (enc_op(op_cop2) | enc_rs(sub) | enc_rt(rt) | enc_rd(rd)) #define enc_gte_tx(sub, rt, rd) (enc_op(op_cop2) | enc_rs(sub) | enc_rt(rt) | enc_rd(rd))
@@ -314,8 +302,8 @@ enum { _C2_TX_SUBS_ = 0
* `swc2` is redundant when we're already inside the `gte_` namespace. * `swc2` is redundant when we're already inside the `gte_` namespace.
* gte_lw rt, base, off → lwc2 rt, off(base) * gte_lw rt, base, off → lwc2 rt, off(base)
* gte_sw rt, base, off → swc2 rt, off(base) * gte_sw rt, base, off → swc2 rt, off(base)
* For the typical user-facing vector-level load (xy + z as two * For the typical user-facing vector-level load (xy + z as two instructions),
* instructions), use the higher-level `gte_load_vN` macros below. */ * use the higher-level `gte_load_vN` macros below. */
#define gte_lw(rt, base, off) enc_gte_lw(rt, base, off) #define gte_lw(rt, base, off) enc_gte_lw(rt, base, off)
#define gte_sw(rt, base, off) enc_gte_sw(rt, base, off) #define gte_sw(rt, base, off) enc_gte_sw(rt, base, off)
@@ -323,13 +311,12 @@ enum { _C2_TX_SUBS_ = 0
* Opcode is always MIPS_OP_COP2, RS is always 1 (CO). * Opcode is always MIPS_OP_COP2, RS is always 1 (CO).
* The lower 25 bits are the GTE-specific command payload. * The lower 25 bits are the GTE-specific command payload.
* *
* The granular `enc_gte_<field>(x)` macros below mirror the `enc_op`/`enc_rs` * The granular `enc_gte_<field>(x)` macros below mirror the `enc_op`/`enc_rs` pattern in mips.h:
* pattern in mips.h: each one self-masks and shifts its own field, so a * Each one self-masks and shifts its own field, so a caller can build up a GTE command piece by piece
* caller can build up a GTE command piece by piece (handy for state-driven * (handy for state-driven MVMVA emitters that vary one field at a time).
* MVMVA emitters that vary one field at a time).
* *
* `ENC_GTE_CMD` is the all-in-one convenience for emitting a full command * `ENC_GTE_CMD` is the all-in-one convenience for emitting a full command word in one go.
* word in one go. It just ORs the per-field encoders together. */ * It just ORs the per-field encoders together. */
#define gte_cmd_base (enc_op(op_cop2) | (1 << 25)) #define gte_cmd_base (enc_op(op_cop2) | (1 << 25))
/* Per-field encoders. Each one does (value & mask) << shift on its own. */ /* Per-field encoders. Each one does (value & mask) << shift on its own. */
@@ -359,12 +346,13 @@ enum { _C2_TX_SUBS_ = 0
* Decomposition (per the `enc_gte_<field>` definitions above): * Decomposition (per the `enc_gte_<field>` definitions above):
* gte_cmdw_<name> = gte_cmd_base | enc_gte_cmd(<cmd>) * gte_cmdw_<name> = gte_cmd_base | enc_gte_cmd(<cmd>)
* The SF/MX/V/CV/LM fields are all zero in the common cases * The SF / MX / V / CV / LM fields are all zero in the common cases
* (standard rotation-matrix, no scaling factor, V0 vector, translation vector, no clamp), * (standard rotation-matrix, no scaling factor, V0 vector, translation vector, no clamp),
* so the only varying bits are the `cmd` field. * so the only varying bits are the `cmd` field.
* *
* Naming follows the file's convention: `gte_cmd_*` is the raw 6-bit `cmd` field id, `gte_cmdw_*` * Naming convention:
* is the fully-encoded 32-bit instruction word ready to drop into a `.word` directive. * - `gte_cmd_*` : Raw 6-bit `cmd` field id
* - `gte_cmdw_* : 32-bit instruction word ready to drop into a `.word` directive.
* *
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* PsyQ-compatibility note (RTPS/RTPT): * PsyQ-compatibility note (RTPS/RTPT):
@@ -380,7 +368,7 @@ enum { _C2_TX_SUBS_ = 0
* `nclip` ends up wrong, and the triangle is culled. * `nclip` ends up wrong, and the triangle is culled.
* *
* So for RTPS and RTPT we OR-in the `0x28` "PsyQ compat" pattern to match the working bit pattern everyone has shipped for 25 years. * So for RTPS and RTPT we OR-in the `0x28` "PsyQ compat" pattern to match the working bit pattern everyone has shipped for 25 years.
* NCLIP/OP/MVMVA stay spec-clean — their reserved bits really are zero in the original PsyQ source. * NCLIP / OP / MVMVA stay spec-clean — their reserved bits really are zero in the original PsyQ source.
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
#define gte_cmdw_psyq_compat (1u << 21 | enc_gte_sf(gte_sf_integer)) #define gte_cmdw_psyq_compat (1u << 21 | enc_gte_sf(gte_sf_integer))
@@ -413,20 +401,16 @@ enum { _C2_TX_SUBS_ = 0
/** /**
* @brief Loads a single SVECTOR to GTE vector register V0 * @brief Loads a single SVECTOR to GTE vector register V0
*
* @details Loads values from an SVECTOR struct to GTE data registers C2_VXY0 * @details Loads values from an SVECTOR struct to GTE data registers C2_VXY0
* (XY at offset 0) and C2_VZ0 (Z at offset 4) using `lwc2`. * (XY at offset 0) and C2_VZ0 (Z at offset 4) using `lwc2`.
* *
* Uses string-style GCC inline asm with `%0` substitution because the * Uses string-style GCC inline asm with `%0` substitution because the base register `r0` is a runtime GPR chosen by the compiler.
* base register `r0` is a runtime GPR chosen by the compiler.
* It cannot be encoded into a static `.word` constant. * It cannot be encoded into a static `.word` constant.
* *
* Usage: * Usage: asm_gte_load_v0(svector_ptr);
* asm_gte_load_v0(svector_ptr);
*/ */
/* lwc2 encoding helpers parameterized on the base GPR. /* lwc2 encoding helpers parameterized on the base GPR.
*
* gte_lw_v0_xy(base) → lwc2 $0, 0(base) ; C2_VXY0 * gte_lw_v0_xy(base) → lwc2 $0, 0(base) ; C2_VXY0
* gte_lw_v0_z(base) → lwc2 $1, 4(base) ; C2_VZ0 * gte_lw_v0_z(base) → lwc2 $1, 4(base) ; C2_VZ0
* gte_lw_v1_xy(base) → lwc2 $2, 0(base) ; C2_VXY1 * gte_lw_v1_xy(base) → lwc2 $2, 0(base) ; C2_VXY1
@@ -435,8 +419,7 @@ enum { _C2_TX_SUBS_ = 0
* gte_lw_v2_z(base) → lwc2 $5, 4(base) ; C2_VZ2 * gte_lw_v2_z(base) → lwc2 $5, 4(base) ; C2_VZ2
* *
* `base` is the GPR number to bake into the .word constant's `rs` field. * `base` is the GPR number to bake into the .word constant's `rs` field.
* These are pure compile-time integers; the C compiler constant-folds * These are pure compile-time integers; the C compiler constant-folds them into .word directives. */
* them into .word directives. */
enum { enum {
GTE_Z_Offset = 4 GTE_Z_Offset = 4
@@ -459,8 +442,8 @@ enum {
* gte_load_v0(p_in_12, R_T4); // R_T4 = 12, base is $12 * gte_load_v0(p_in_12, R_T4); // R_T4 = 12, base is $12
* *
* Then `"r"(r_ptr)` inside the asm binds to $12 (the only register `p_in_12` can live in), * Then `"r"(r_ptr)` inside the asm binds to $12 (the only register `p_in_12` can live in),
* which is exactly the register the .word constants expect. A `"$12"` clobber would conflict with the register-variable binding * which is exactly the register the .word constants expect.
* ("asm specifier for variable conflicts with asm clobber list"), so we omit it. * A `"$12"` clobber would conflict with the register-variable binding ("asm specifier for variable conflicts with asm clobber list"), so we omit it.
* The other ABI-clobbers ($2/$8/$9/$31) stay because the GTE instructions don't touch caller-saved GPRs but the kernel does treat them as volatile. * The other ABI-clobbers ($2/$8/$9/$31) stay because the GTE instructions don't touch caller-saved GPRs but the kernel does treat them as volatile.
* *
* WHICH REGISTER TO PICK * WHICH REGISTER TO PICK
@@ -499,10 +482,8 @@ enum {
/* gte_load_v0v1v2(p0, p1, p2, b0, b1, b2) — prelude to gte_cmd_rtpt. /* gte_load_v0v1v2(p0, p1, p2, b0, b1, b2) — prelude to gte_cmd_rtpt.
* *
* Loads all three GTE input vectors (6 words) from three separate pointers, * Loads all three GTE input vectors (6 words) from three separate pointers, one per GTE vector register,
* one per GTE vector register, each loaded from its own base GPR. * each loaded from its own base GPR. Caller must bind each `pN` to `bN` via a register variable.
* Caller must bind each `pN` to `bN` via a register variable.
*
* register V3_S2* p0 rgcc(R_T4) = verts[0].ptr; // → __asm__("$12") * register V3_S2* p0 rgcc(R_T4) = verts[0].ptr; // → __asm__("$12")
* register V3_S2* p1 rgcc(R_T5) = verts[1].ptr; // → __asm__("$13") * register V3_S2* p1 rgcc(R_T5) = verts[1].ptr; // → __asm__("$13")
* register V3_S2* p2 rgcc(R_T6) = verts[2].ptr; // → __asm__("$14") * register V3_S2* p2 rgcc(R_T6) = verts[2].ptr; // → __asm__("$14")
@@ -521,29 +502,20 @@ enum {
/** /**
* @brief Rotate, Translate and Perspective Triple (23 cycles) * @brief Rotate, Translate and Perspective Triple (23 cycles)
* * @details Performs rotation, translation and perspective calculation of three vertices at once.
* @details Performs rotation, translation and perspective calculation of three * The equation performed is the same as gte_rtps() only repeated three times for each vertex.
* vertices at once. The equation performed is the same as gte_rtps() only * The result of the first vertex is stored in GTE data register C2_SXY0, the second vector in C2_SXY1 then C2_SXY2.
* repeated three times for each vertex. The result of the first vertex is
* stored in GTE data register C2_SXY0, the second vector in C2_SXY1 then
* C2_SXY2.
* *
* Encoder-style emission (no inline-asm strings in the code body): * Encoder-style emission (no inline-asm strings in the code body):
* 1. Two `nop` words fill the COP2 pipeline latency — the GTE * 1. Two `nop` words fill the COP2 pipeline latency — the GTE takes ~8 cycles per perspective divide,
* takes ~8 cycles per perspective divide, and the nops let any * and the nops let any preceding lwc2/swc2 retire before RTPT starts reading its inputs from V0/V1/V2.
* preceding lwc2/swc2 retire before RTPT starts reading its * 2. The RTPT command word itself is `gte_cmdw_rtpt` (see the pre-baked encoders above) —
* inputs from V0/V1/V2. * `0x0280030` decoded as `op_cop2` | CO(1) | cmd=RTPT, with all SF/MX/V/CV/LM fields zero
* 2. The RTPT command word itself is `gte_cmdw_rtpt` (see the * (standard rotation, no scaling, V0 vector, translation vector, no clamp).
* pre-baked encoders above) — `0x0280030` decoded as
* `op_cop2` | CO(1) | cmd=RTPT, with all SF/MX/V/CV/LM fields
* zero (standard rotation, no scaling, V0 vector, translation
* vector, no clamp).
* *
* Clobbers the caller-saved GPRs via `clbr_volatile_gprs` (per the kernel * Clobbers the caller-saved GPRs via `clbr_volatile_gprs` (per the kernel ABI)
* ABI) plus the standard "memory" barrier. Does not clobber any COP2 * plus the standard "memory" barrier. Does not clobber any COP2 data/control register —
* data/control register — those have to be saved by the caller if * those have to be saved by the caller if they need to survive across the call (RTPT writes SXY0..2, SZ0..3, OTZ, MAC0..3, IR0..3, etc.).
* they need to survive across the call (RTPT writes SXY0..2, SZ0..3,
* OTZ, MAC0..3, IR0..3, etc.).
*/ */
#define gte_rtpt() \ #define gte_rtpt() \
asm volatile( \ asm volatile( \
@@ -559,32 +531,24 @@ enum {
/** /**
* @brief Normal clipping (8 cycles) * @brief Normal clipping (8 cycles)
* * @details Computes the sign of three screen coordinates (C2_SXY0-2) used for backface culling.
* @details Computes the sign of three screen coordinates (C2_SXY0-2) used for * If the value of C2_MAC0 is negative, the coordinates are inverted and thus the triangle is back facing.
* backface culling. If the value of C2_MAC0 is negative, the coordinates are
* inverted and thus the triangle is back facing.
* *
* The following equation is performed when executing this GTE command: * The following equation is performed when executing this GTE command:
*
* MAC0 = SX0*SY1 + SX1*SY2 + SX2*SY0 - SX0*SY2 - SX1*SY0 - SX2*SY1 * MAC0 = SX0*SY1 + SX1*SY2 + SX2*SY0 - SX0*SY2 - SX1*SY0 - SX2*SY1
*
* Encoder-style emission (no inline-asm strings in the code body): * Encoder-style emission (no inline-asm strings in the code body):
* 1. Two `nop` words fill the COP2 pipeline latency - the GTE * 1. Two `nop` words fill the COP2 pipeline latency
* pipeline takes a few cycles per op, and the nops let any * - the GTE pipeline takes a few cycles per op, and the nops let any preceding
* preceding lwc2/swc2/RTPT retire before NCLIP starts reading * lwc2/swc2/RTPT retire before NCLIP starts reading its inputs from SXY0/SXY1/SXY2.
* its inputs from SXY0/SXY1/SXY2. * 2. The NCLIP command word itself is `gte_cmdw_nclip` (see the pre-baked encoders above)
* 2. The NCLIP command word itself is `gte_cmdw_nclip` (see the * - `0x01400006` decoded as `op_cop2` | CO(1) | cmd=NCLIP, with all SF/MX/V/CV/LM fields zero.
* pre-baked encoders above) - `0x01400006` decoded as * NCLIP is spec-clean in the original PsyQ source (unlike RTPS/RTPT which carry the `gte_cmdw_psyq_compat` quirk),
* `op_cop2` | CO(1) | cmd=NCLIP, with all SF/MX/V/CV/LM fields * so `gte_cmdw_nclip` does NOT OR in any reserved bits.
* zero. NCLIP is spec-clean in the original PsyQ source
* (unlike RTPS/RTPT which carry the `gte_cmdw_psyq_compat`
* quirk), so `gte_cmdw_nclip` does NOT OR in any reserved bits.
* *
* Clobbers the caller-saved GPRs via `clbr_volatile_gprs` (per the kernel * Clobbers the caller-saved GPRs via `clbr_volatile_gprs` (per the kernel ABI) plus the standard "memory" barrier.
* ABI) plus the standard "memory" barrier. Does not clobber any COP2 * Does not clobber any COP2 data/control register.
* data/control register - those have to be saved by the caller if * Those have to be saved by the caller if they need to survive across the call (NCLIP writes MAC0 only;
* they need to survive across the call (NCLIP writes MAC0 only; it * it is purely a sign-of-double-product computation on SXY0..2).
* is purely a sign-of-double-product computation on SXY0..2).
*/ */
#define gte_nclip() \ #define gte_nclip() \
asm volatile( \ asm volatile( \
@@ -610,13 +574,10 @@ enum {
"cop2 0x0158002D;") "cop2 0x0158002D;")
/* asm_gte_matrix_set_rotation(r0) /* asm_gte_matrix_set_rotation(r0)
* Loads the 3x3 rotation matrix at `r0` into the GTE's rotation-matrix control registers (RT11..RT22, indices 0..4) via ctc2.
* *
* Loads the 3x3 rotation matrix at `r0` into the GTE's rotation-matrix * Memory layout at r0: five contiguous 32-bit words (offsets 0..16), each holding two packed 16-bit matrix elements.
* control registers (RT11..RT22, indices 0..4) via ctc2. * The first 1.5 rows of a standard PSX SDK MATRIX struct (where each row is laid out as
*
* Memory layout at r0: five contiguous 32-bit words (offsets 0..16),
* each holding two packed 16-bit matrix elements. The first 1.5 rows
* of a standard PSX SDK MATRIX struct (where each row is laid out as
* [RT_xx, RT_xy] | [RT_xz, pad] | ...). * [RT_xx, RT_xy] | [RT_xz, pad] | ...).
* *
* Generated MIPS (mirrors the source macro): * Generated MIPS (mirrors the source macro):
@@ -631,27 +592,22 @@ enum {
* ctc2 $13, $3 ; → C2_RT21 * ctc2 $13, $3 ; → C2_RT21
* ctc2 $14, $4 ; → C2_RT22 * ctc2 $14, $4 ; → C2_RT22
* *
* Same contract as gte_load_v0: caller MUST bind `r0` to $12 via a * Same contract as gte_load_v0: caller MUST bind `r0` to $12 via a register variable (`rgcc(R_T4)`) for the `lw $12, off(...)`
* register variable (`rgcc(R_T4)`) for the `lw $12, off(...)` * instructions to read from the right base. The `"r"(r0)` constraint alone doesn't force a specific GPR — it just lets GCC pick one.
* instructions to read from the right base. The `"r"(r0)` constraint * The .word constants here bake R_T4/R_T5/R_T6 into the `rs` field of each lw, so the lw instructions will
* alone doesn't force a specific GPR — it just lets GCC pick one. * only do the right thing if $12 / $13 / $14 hold the matrix base at runtime.
* The .word constants here bake R_T4/R_T5/R_T6 into the `rs` field
* of each lw, so the lw instructions will only do the right thing
* if $12/$13/$14 hold the matrix base at runtime.
* *
* M3_S2* m = ...; * M3_S2* m = ...;
* register M3_S2* m_in_12 rgcc(R_T4) = m; * register M3_S2* m_in_12 rgcc(R_T4) = m;
* asm_gte_matrix_set_rotation(m_in_12); * asm_gte_matrix_set_rotation(m_in_12);
* *
* We clobber $12/$13/$14 (the ones we use as scratch inside the * We clobber $12/$13/$14 (the ones we use as scratch inside the inline asm)
* inline asm) plus the system clobbers; we don't clobber `r0` because * plus the system clobbers; we don't clobber `r0` because the `rgcc` binding already says "this variable lives in $12".
* the `rgcc` binding already says "this variable lives in $12".
* *
* WARNING: Incomplete by design. The source macro only writes RT11..RT22 * WARNING: Incomplete by design. The source macro only writes RT11..RT22 (5 of 9 rotation elements);
* (5 of 9 rotation elements); RT23 and the entire RT3x row are left * RT23 and the entire RT3x row are left untouched.
* untouched. Real libpsn00b SetRotMatrix writes all 9. Use only when the * Real libpsn00b SetRotMatrix writes all 9. Use only when the GTE's remaining rotation entries are already correct,
* GTE's remaining rotation entries are already correct, or you will * or you will get stale-RT2x/RT3x artifacts in RTPS/RTPT/MVMVA output.
* get stale-RT2x/RT3x artifacts in RTPS/RTPT/MVMVA output.
*/ */
#define asm_gte_matrix_set_rotation(r0) \ #define asm_gte_matrix_set_rotation(r0) \
asm volatile( \ asm volatile( \
View File
+74 -106
View File
@@ -10,10 +10,10 @@
# include "gen/duffle.offsets.h" # include "gen/duffle.offsets.h"
#endif #endif
typedef U4 const MipsCode; typedef U4 const MipsCode; // Underlying type to mips asm words.
typedef Slice_(MipsCode); typedef Slice_(MipsCode);
typedef Slice_MipsCode MipsAtom;
typedef U4 const MipsAtom; // Underlying type to an array of mips asm words that must terminate with an ac_yield.
#define MipsAtom_(sym) MipsCode sym [] align_(4) = #define MipsAtom_(sym) MipsCode sym [] align_(4) =
// Used for components with no args (e.g., ac_load_tri_indices) or identifier-args (hardcoded register names). // Used for components with no args (e.g., ac_load_tri_indices) or identifier-args (hardcoded register names).
@@ -23,63 +23,78 @@ typedef Slice_MipsCode MipsAtom;
#define MipsAtomComp_(sym) MipsCode sym [] align_(4) = #define MipsAtomComp_(sym) MipsCode sym [] align_(4) =
// Used for components with value-args (e.g., ac_format_f3_color). // Used for components with value-args (e.g., ac_format_f3_color).
// FI_ MipsAtom ac_X(args) MipsAtomComp_Proc_(ac_X, { body }) // FI_ Slice_MipsCode ac_X(args) MipsAtomComp_Proc_(ac_X, { body })
// expands to: // expands to:
// FI_ MipsAtom ac_X(args) { MipsCode ac_X[] align_(4) = { body }; return slice_from_array(MipsCode, ac_X); } // FI_ Slice_MipsCode ac_X(args) { MipsCode ac_X[] align_(4) = { body }; return slice_from_array(MipsCode, ac_X); }
#define MipsAtomComp_Proc_(sym, ...) { MipsCode sym [] align_(4) = __VA_ARGS__; return slice_from_array(MipsCode, sym); } #define MipsAtomComp_Proc_(sym, ...) { MipsCode sym [] align_(4) = __VA_ARGS__; return slice_from_array(MipsCode, sym); }
// Auto-generated component macros (<module>/gen/<dir>/<dir>.macs.h) are included manually by the unity build. // Auto-generated component macros (<module>/gen/<dir>/<dir>.macs.h) are included manually by the unity build.
/* Register aliases */ /* Register aliases */
enum { enum {
R_AtomJmp = R_T9 atom_reg, /* debug-visible; tape yield handshake scratch */ R_AtomJmp = R_T8 atom_reg, /* debug-visible; tape yield handshake scratch */
R_TapePtr = R_T8 atom_reg, /* The Instruction Stream Pointer */ R_TapePtr = R_T9 atom_reg, /* The Instruction Stream Pointer */
R_InCursor = R_T4,
R_PrimCursor = R_T7 atom_reg atom_type(U4 *), /* VRAM output cursor (primitive buffer) */
R_FaceCursor = R_T4 atom_reg atom_type(V4_S2 *), /* Cube face-index cursor (V4_S2*); floor context switches to V3_S2* via atom_phase */
R_VertBase = R_T5 atom_reg atom_type(V3_S2 *), /* Base address of the vertex array */
R_OtBase = R_T6 atom_reg atom_type(U4 *), /* Base address of the Ordering Table */
/* Stringification codes for the GCC inline assembler clobber lists. */ /* Stringification codes for the GCC inline assembler clobber lists. */
#define R_TapePtr_Code R_T8_Code #define R_AtomJmp_Code R_T8_Code
#define R_InCursor_Code R_T4_Code #define R_TapePtr_Code R_T9_Code
#define R_PrimCursor_Code R_T7_Code // R_InCursor = R_T4,
#define R_FaceCursor_Code R_T4_Code // #define R_InCursor_Code R_T4_Code
#define R_VertBase_Code R_T5_Code
#define R_OtBase_Code R_T6_Code // Reserved Registers (Callee-saved):
// - R_T9: Holds the Tape Ptr which we need to increment
// If we hit a wall with register allocations we can clobber V0 & V1 (return values), defering as opt-in by user.
// - R_RA: Not sure??
// Needed by ac_yield but can be used as atom scratch:
// - R_T8: Will be used as the atom jump register.
// All allocatable registers for mips atoms:
R_TScratchVolatile = R_AT, // This one is reserved for psuedo instructions, but you can technically use it.
R_TScratch0 = R_T0,
R_TScratch1 = R_T1,
R_TScratch2 = R_T2,
R_TScratch3 = R_T3,
R_TScratch4 = R_T4,
R_TScratch5 = R_T5,
R_TScratch6 = R_T6,
R_TScratch7 = R_T7,
R_TScratch8 = R_T8,
R_TScratch10 = R_V0,
R_TScratch11 = R_V1,
// Note(Ed): We can technically clobber these, but don't unless we hit a bottleneck.
// R_TScratch12 = R_A0,
// R_TScratch13 = R_A1,
// R_TScratch14 = R_A3,
// TODO(Ed): Review S0-S7, they are technically avaialble, we just have to snapshot them at the ABI boundary.
// TODO(Ed): This is technically a waste of cycles for most work? so maybe only do this for expensive atoms on-demand or atom phases.
// TODO(Ed): Sort out the other available registers... (Not sure how much is left avail)
}; };
#pragma region Tape Drive #pragma region Tape Drive
/* --------------------------------------------------------------------------- /* ---------------------------------------------------------------------------
* TAPE DRIVE ABI & REGISTER ALIASES (the enum moved earlier; see below) * TAPE DRIVE ABI & REGISTER ALIASES (the enum moved earlier; see below)
* ---------------------------------------------------------------------------*/ * ---------------------------------------------------------------------------*/
typedef Slice_(MipsAtom); typedef Slice_MipsAtom Tape;
/* The 'Exit' Atom */ /* The 'Exit' Atom */
atom_dbg_skip MipsAtom_(tape_exit) { jump_reg(rret_addr), nop }; atom_dbg_skip MipsAtom_(tape_exit) { jump_reg(rret_addr), nop };
//TODO(Ed): Do we backup R_S0-7 here? Have it in a heavier tape run as a opt-in? Same with V0-1 and A0-3?
/* Generalized Tape Engine Runner */ /* Generalized Tape Engine Runner */
NI_ void tape_run(Slice_MipsCode tape) { register U4* tp rgcc(R_TapePtr) = u4_r(tape.ptr); asm volatile( FI_ void tape_run(Tape tape) { register U4* tape_ptr rgcc(R_TapePtr) = u4_r(tape.ptr); asm volatile(
asm_words( asm_words(
add_ui( R_SP, R_SP, -MipsStackAlignment) /* Allocate stack space */ load_word( R_AtomJmp, R_TapePtr, 0) /* Bootstrap the first jump */
, store_word( R_RA, R_SP, 0) /* Safely backup $ra to the stack */ , add_ui_self(R_TapePtr, S_(MipsAtom)) /* Advance tape */
, load_word( R_AtomJmp, R_TapePtr, 0) /* Bootstrap the first jump */ , call_reg( R_AtomJmp) /* jalr $t9 */
, add_ui_self(R_TapePtr, S_(MipsCode)) /* Advance tape */ , nop /* Branch delay slot */
, call_reg( R_AtomJmp) /* jalr $t9 */
, nop /* Branch delay slot */
, load_word( R_RA, R_SP, 0) /* Restore $ra from stack */
, add_ui_self(R_SP, MipsStackAlignment) /* Deallocate stack space */
) )
asm_rpins, r_use(tp) asm_rpins, r_use(tape_ptr)
asm_clobber: asm_clobber:
rlit(R_AT) rlit(R_AT),
, rlit(R_V0), rlit(R_V1) rlit(R_V0), rlit(R_V1),
, rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3) rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3), rlit(R_T4),
/* Tell GCC the tape engine owns and destroys the workspace registers */ rlit(R_T5), rlit(R_T6), rlit(R_T7), rlit(R_T8),
, rlit(R_PrimCursor), rlit(R_FaceCursor), rlit(R_VertBase), rlit(R_OtBase) clb_mem_drain
, rlit(R_T9)
, clb_mem_drain
); } ); }
typedef Relative_(FArena) Struct_(TapeBuilder) { U4 ptr; U4 capacity; U4 used; }; typedef Relative_(FArena) Struct_(TapeBuilder) { U4 ptr; U4 capacity; U4 used; };
@@ -87,13 +102,17 @@ FI_ void tb_init(TapeBuilder* tb, FArena* arena) { tb->ptr = arena->start
FI_ TapeBuilder tb_make_old( FArena* arena) { return (TapeBuilder){ arena->start, 0 }; } FI_ TapeBuilder tb_make_old( FArena* arena) { return (TapeBuilder){ arena->start, 0 }; }
FI_ TapeBuilder tb_make(Slice mem) { return (TapeBuilder){ mem.ptr, mem.len, 0 }; } FI_ TapeBuilder tb_make(Slice mem) { return (TapeBuilder){ mem.ptr, mem.len, 0 }; }
#define tb_emit_(tb, atom) tb_emit(tb, atom)
FI_ void tb_emit(TapeBuilder* tb, MipsCode* atom) { u4_r(tb->ptr)[tb->used] = u4_(atom); ++ tb->used; } FI_ void tb_emit(TapeBuilder* tb, MipsCode* atom) { u4_r(tb->ptr)[tb->used] = u4_(atom); ++ tb->used; }
FI_ void tb_data(TapeBuilder* tb, U4 data) { u4_r(tb->ptr)[tb->used] = u4_(data); ++ tb->used; } FI_ void tb_data(TapeBuilder* tb, U4 data) { u4_r(tb->ptr)[tb->used] = u4_(data); ++ tb->used; }
#define tb_emit_(atom) tb_emit(& tb, atom)
#define tb_data_(field, data) tb_data(& tb, u4_(data))
FI_ Slice_MipsCode tb_end (TapeBuilder* tb) { tb_emit(tb,tape_exit); return (Slice_MipsCode){ C_(U4*,tb->ptr), tb->used }; } FI_ Tape tb_end (TapeBuilder* tb) { tb_emit(tb,tape_exit); return (Tape){ C_(U4*,tb->ptr), tb->used }; }
FI_ Slice_MipsCode tb_slice(TapeBuilder tb) { return (Slice_MipsCode){ C_(U4*,tb.ptr), tb.used }; } FI_ Tape tb_slice(TapeBuilder tb) { return (Tape){ C_(U4*,tb.ptr), tb.used }; }
#define tb_scope(tb) for(U4 tbs_once=0;tbs_once==0;++tbs_once,tb_emit(tb,tape_exit)) #define tb_scope(tb) for(U4 tbs_once=0;tbs_once==0;++tbs_once,tb_emit(tb,tape_exit))
FI_ void tb_scope_run_end(TapeBuilder* tb) { tb_emit(tb,tape_exit); tape_run(tb_slice(tb[0])); }
#define tb_scope_run(tb) for(U4 tbs_once=0;tbs_once==0;++tbs_once,tb_scope_run_end(tb))
#pragma endregion Tape Drive #pragma endregion Tape Drive
@@ -110,6 +129,17 @@ atom_dbg_skip MipsAtomComp_(ac_yield) {
jump_reg( R_AtomJmp), nop, jump_reg( R_AtomJmp), nop,
}; };
enum {
R_PrimCursor = R_T7 atom_reg atom_type(U4*), /* VRAM output cursor (primitive buffer) */
R_FaceCursor = R_T4 atom_reg atom_type(V4_S2*), /* Cube face-index cursor (V4_S2*); floor context switches to V3_S2* via atom_phase */
R_VertBase = R_T5 atom_reg atom_type(V3_S2*), /* Base address of the vertex array */
R_OtBase = R_T6 atom_reg atom_type(U4*), /* Base address of the Ordering Table */
#define R_PrimCursor_Code R_T7_Code
#define R_FaceCursor_Code R_T4_Code
#define R_VertBase_Code R_T5_Code
#define R_OtBase_Code R_T6_Code
};
/* Words: 3; Loads 3 S2 indices from the face array */ /* Words: 3; Loads 3 S2 indices from the face array */
atom_dbg_skip MipsAtomComp_(ac_load_tri_indices) { atom_dbg_skip MipsAtomComp_(ac_load_tri_indices) {
load_half_u(R_T0, R_FaceCursor, 0 * S_(S2)), load_half_u(R_T0, R_FaceCursor, 0 * S_(S2)),
@@ -156,7 +186,7 @@ MipsAtomComp_(ac_insert_ot_tag_g4) {
/* Words: 3; Emits one (cmd|color) word to R_PrimCursor at the given /* Words: 3; Emits one (cmd|color) word to R_PrimCursor at the given
* byte offset. Internal helper used by the *_format_*_color macros. */ * byte offset. Internal helper used by the *_format_*_color macros. */
FI_ MipsAtom ac_pack_color_word(U4 off, U4 cmd, U1 r, U1 g, U1 b) FI_ Slice_MipsCode ac_pack_color_word(U4 off, U4 cmd, U1 r, U1 g, U1 b)
atom_dbg_skip MipsAtomComp_Proc_(ac_pack_color_word, { atom_dbg_skip MipsAtomComp_Proc_(ac_pack_color_word, {
load_upper_i(R_AT, (cmd) << 8 | (b)), load_upper_i(R_AT, (cmd) << 8 | (b)),
or_i_self( R_AT, ((g) << 8) | (r)), or_i_self( R_AT, ((g) << 8) | (r)),
@@ -165,7 +195,7 @@ atom_dbg_skip MipsAtomComp_Proc_(ac_pack_color_word, {
/* Words: 3; Emits the F3 command+color word (cmd byte | BLUE | GREEN | RED) /* Words: 3; Emits the F3 command+color word (cmd byte | BLUE | GREEN | RED)
* Args: _r, _g, _b are 8-bit RGB byte values (not raw 16-bit fields). */ * Args: _r, _g, _b are 8-bit RGB byte values (not raw 16-bit fields). */
FI_ MipsAtom ac_format_f3_color(U1 r, U1 g, U1 b) FI_ Slice_MipsCode ac_format_f3_color(U1 r, U1 g, U1 b)
atom_dbg_skip MipsAtomComp_Proc_(ac_format_f3_color, { mac_pack_color_word(O_(Poly_F3,color), gp0_cmd_poly_f3, r, g, b) }) atom_dbg_skip MipsAtomComp_Proc_(ac_format_f3_color, { mac_pack_color_word(O_(Poly_F3,color), gp0_cmd_poly_f3, r, g, b) })
/* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices to the F3. /* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices to the F3.
@@ -178,7 +208,7 @@ atom_dbg_skip MipsAtomComp_(ac_gte_store_f3) {
/* Words: 12; Emits the four (code|color) words of a Poly_G4. /* Words: 12; Emits the four (code|color) words of a Poly_G4.
* Args: rN,gN,bN are 8-bit RGB byte values for each of the 4 vertices. */ * Args: rN,gN,bN are 8-bit RGB byte values for each of the 4 vertices. */
FI_ MipsAtom ac_format_g4_color( FI_ Slice_MipsCode ac_format_g4_color(
U1 r0, U1 g0, U1 b0, U1 r0, U1 g0, U1 b0,
U1 r1, U1 g1, U1 b1, U1 r1, U1 g1, U1 b1,
U1 r2, U1 g2, U1 b2, U1 r2, U1 g2, U1 b2,
@@ -232,7 +262,7 @@ FI_ void atombuilder_end(MipsAtomBuilder_R ab) {
mem_bump(ab->start, ab->capacity, & ab->used, S_(ac_yield)); mem_bump(ab->start, ab->capacity, & ab->used, S_(ac_yield));
} }
#define mipsatom_from_builder(ab) (MipsAtom){ab.start, ab.used} #define mipsatom_from_builder(ab) (Slice_MipsCode){ab.start, ab.used}
#pragma endregion Mips Atom Builder #pragma endregion Mips Atom Builder
@@ -286,66 +316,4 @@ internal MipsAtom_(set_gte_world) atom_info(
mac_yield() mac_yield()
}; };
/* DIAGNOSTIC 1: Pure tape loop test */
internal MipsAtom_(diag_yield) { mac_yield() };
/* DIAGNOSTIC 2: Pure memory test (No GTE). Draws a fixed cyan triangle. */
internal MipsAtom_(diag_color) {
store_word( R_0, R_T7, 0),
load_upper_i(R_AT, gp0_cmd_poly_f3 << 8 | 0xFF), /* High: MipsCode Poly_F3(0x20) + Color B:FF */
or_i_self( R_AT, 0xFF00), /* Low: Color G:FF, R:00 (Cyan) */
store_word( R_AT, R_T7, 4),
/* Fake coordinates - Swapped winding order to prevent GPU culling! */
load_upper_i(R_AT, 0x0010), or_i_self(R_AT, 0x0010), store_word(R_AT, R_T7, 8), /* (16, 16) */
load_upper_i(R_AT, 0x0050), or_i_self(R_AT, 0x0010), store_word(R_AT, R_T7, 12), /* (80, 16) */
load_upper_i(R_AT, 0x0010), or_i_self(R_AT, 0x0050), store_word(R_AT, R_T7, 16), /* (16, 80) */
add_ui( R_T1, R_0, 10),
shift_lleft_self(R_T1, S_(U4)/2),
add_u_self( R_T1, R_T6),
load_word( R_AT, R_T1, 0),
load_upper_i(R_V0, (S_(Poly_F3)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits),
store_word( R_AT, R_T7, 0),
shift_lleft(R_AT, R_T7, S_(PolyTag_len_bits)), shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)),
or_u_self( R_AT, R_V0),
store_word( R_AT, R_T1, 0),
add_ui(R_T7, R_T7, 20),
mac_yield()
};
/* DIAGNOSTIC 3: Pure GTE test (No Memory Writes) */
internal MipsAtom_(diag_gte) {
/* Load 3 indices */
load_half_u(R_T0, R_T4, 0),
load_half_u(R_T1, R_T4, 2),
load_half_u(R_T2, R_T4, 4),
/* Load Vertices into GTE */
shift_lleft( R_AT, R_T0, 3), add_u( R_AT, R_AT, R_T5),
load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4),
gte_mv_to_data_r(R_V0, C2_VXY0), gte_mv_to_data_r(R_V1, C2_VZ0),
shift_lleft( R_AT, R_T1, 3), add_u(R_AT, R_AT, R_T5),
load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4),
gte_mv_to_data_r(R_V0, C2_VXY1), gte_mv_to_data_r(R_V1, C2_VZ1),
shift_lleft(R_AT, R_T2, 3), add_u(R_AT, R_AT, R_T5),
load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4),
gte_mv_to_data_r(R_V0, C2_VXY2), gte_mv_to_data_r(R_V1, C2_VZ2),
/* Run Math */
nop2, gte_cmdw_rtpt,
nop2, gte_cmdw_nclip,
nop2,
/* Advance Face Cursor and Yield */
add_ui(R_T4, R_T4, 8),
mac_yield()
};
#pragma endregion Baked Mips Atoms #pragma endregion Baked Mips Atoms
+3
View File
@@ -11,6 +11,7 @@ enum {
v3s2_byteoff = 3, // log2(8), used with shift_left_logical op for index via byte offset. v3s2_byteoff = 3, // log2(8), used with shift_left_logical op for index via byte offset.
}; };
typedef Array_(U1, 2);
typedef Array_(U4, 2); typedef Array_(U4, 2);
typedef Array_(S2, 2); typedef Array_(S2, 2);
typedef Array_(S2, 3); typedef Array_(S2, 3);
@@ -22,6 +23,7 @@ typedef S2 A3x3_S2[3][3];
typedef Struct_(Extent2_S2) { S2 width; S2 height; }; typedef Struct_(Extent2_S2) { S2 width; S2 height; };
typedef Struct_(Extent2_S4) { S4 width; S4 height; }; typedef Struct_(Extent2_S4) { S4 width; S4 height; };
typedef Struct_(V2_U1) { U1 x; U1 y; };
typedef Struct_(V2_S2) { S2 x; S2 y; }; typedef Struct_(V2_S2) { S2 x; S2 y; };
typedef Struct_(V2_S4) { S4 x; S4 y; }; typedef Struct_(V2_S4) { S4 x; S4 y; };
typedef Struct_(V3_S2) { S2 x; S2 y; S2 z; S2 pad; }; typedef Struct_(V3_S2) { S2 x; S2 y; S2 z; S2 pad; };
@@ -37,6 +39,7 @@ typedef Struct_(Rect_S4) { S4 x; S4 y; S4 width; S4 height; };
typedef Struct_(M3_S2) { A3x3_S2 m; A3_S4 t; }; typedef Struct_(M3_S2) { A3x3_S2 m; A3_S4 t; };
typedef Array_(V2_S2, 2);
typedef Array_(V2_S2, 3); typedef Array_(V2_S2, 3);
typedef Array_(V2_S2, 4); typedef Array_(V2_S2, 4);
+2 -2
View File
@@ -67,8 +67,8 @@ typedef Slice_(B1);
#define slice_end(slice) ((slice).ptr + (slice).len) #define slice_end(slice) ((slice).ptr + (slice).len)
#define S_slice(s) ((s).len * S_((s).ptr[0])) #define S_slice(s) ((s).len * S_((s).ptr[0]))
#define slice_ut(ptr,len) slice_ut_(u4_(ptr), u4_(len)) #define slice_ut(ptr,len) slice_ut_(u4_(ptr), u4_(len))
#define slice_ut_arr(a) slice_ut_(u4_(a), S_(a)) #define slice_ut_arr(a) slice_ut_(u4_(a), S_(a))
#define slice_to_ut(s) slice_ut_(u4_((s).ptr), S_slice(s)) #define slice_to_ut(s) slice_ut_(u4_((s).ptr), S_slice(s))
#define slice_iter(container, iter) (T_((container).ptr) iter = (container).ptr; iter != slice_end(container); ++ iter) #define slice_iter(container, iter) (T_((container).ptr) iter = (container).ptr; iter != slice_end(container); ++ iter)
+6 -9
View File
@@ -336,10 +336,10 @@ enum { _BitOffsets = 0
/* Logic Opcodes */ /* Logic Opcodes */
#define and_u(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_and) #define and_u(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_and)
#define or_u(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_or) #define or_u(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_or)
#define xor_u(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_xor) #define xor_u(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_xor)
#define nor_u(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_nor) #define nor_u(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_nor)
#define or_u_self(rd_rs, rt) enc_r(op_special, (rd_rs), (rt), (rd_rs), 0, fc_or) #define or_u_self(rd_rs, rt) enc_r(op_special, (rd_rs), (rt), (rd_rs), 0, fc_or)
@@ -444,18 +444,15 @@ enum { _BitOffsets = 0
#define load_imm_1w_s0(rt, imm) add_si((rt)), R_0, (imm)) #define load_imm_1w_s0(rt, imm) add_si((rt)), R_0, (imm))
/* load_imm_2w — unconditional 2-word `li` form: `lui` + (ori | addi). /* load_imm_2w — unconditional 2-word `li` form: `lui` + (ori | addi).
*
* Granular companion to `load_imm`: skips the compile-time range checks and always emits 2 .words. Use this when: * Granular companion to `load_imm`: skips the compile-time range checks and always emits 2 .words. Use this when:
* - you know `imm` is > 0xFFFF (otherwise you're wasting a word), OR * - you know `imm` is > 0xFFFF (otherwise you're wasting a word), OR
* - `imm` is not a compile-time constant and you want predictable * - `imm` is not a compile-time constant and you want predictable 2-word emission without the `__builtin_constant_p` branches.
* 2-word emission without the `__builtin_constant_p` branches.
* *
* The lo16 strategy is still chosen at expansion time on the lo half: * The lo16 strategy is still chosen at expansion time on the lo half:
* lo16 in 0x0000..0x7FFF → addi (sign-ext is harmless, the lui already cleared bits 15..0) * lo16 in 0x0000..0x7FFF → addi (sign-ext is harmless, the lui already cleared bits 15..0)
* lo16 in 0x8000..0xFFFF → ori (zero-extends to preserve the intended bit pattern) * lo16 in 0x8000..0xFFFF → ori (zero-extends to preserve the intended bit pattern)
* *
* For situations where you need to bypass even this choice * For situations where you need to bypass even this choice (e.g. to force a specific encoding for a known discontiguous high/low pair),
* (e.g. to force a specific encoding for a known discontiguous high/low pair),
* see `load_imm_2w_ori_forced` and `load_imm_2w_addi_forced` below. * see `load_imm_2w_ori_forced` and `load_imm_2w_addi_forced` below.
* Statement-level (not expression-level): emits its own `asm volatile(...)`. * Statement-level (not expression-level): emits its own `asm volatile(...)`.
*/ */
+73
View File
@@ -0,0 +1,73 @@
#ifdef INTELLISENSE_DIRECTIVES
# pragma once
# include "dsl.h"
#endif
/* PSX button bit positions — 1:1 with PSX-SPX docs at docs/psx-spx/docs/controllersandmemorycards.md:405-421.
* Wire is active-low (0 = pressed).
* The decoder atom computes buttons = (~raw_buttons) & 0xFFFF; the active-low-to-active-high inversion is applied bit-by-bit. */
enum {
Bit_(Pad_Select, 0),
Bit_(Pad_L3, 1),
Bit_(Pad_R3, 2),
Bit_(Pad_Start, 3),
Bit_(Pad_Up, 4),
Bit_(Pad_Right, 5),
Bit_(Pad_Down, 6),
Bit_(Pad_Left, 7),
Bit_(Pad_L2, 8),
Bit_(Pad_R2, 9),
Bit_(Pad_L1, 10),
Bit_(Pad_R1, 11),
Bit_(Pad_Triangle, 12),
Bit_(Pad_Circle, 13),
Bit_(Pad_Cross, 14),
Bit_(Pad_Square, 15),
};
enum {
PadId_Offset = 4,
Pad0 = 0 << PadId_Offset,
Pad1 = 1 << PadId_Offset,
};
#define pad0_(btn_id) (btn_id << Pad0)
#define pad1_(btn_id) (btn_id << Pad1)
/* ============================================================
* BIOS pad-buffer subsystem: docs/psx-spx/docs/kernelbios.md (B(12h) + B(13h))
* ============================================================ */
enum {
PAD_BIOS_RAW_SIZE = 0x22,
};
typedef Struct_(PadBiosRaw) {
U1 bytes[PAD_BIOS_RAW_SIZE];
};
typedef Enum_(U4, PadStatus) {
PadStatus_Disconnected,
PadStatus_Digital,
PadStatus_AnalogStick,
PadStatus_AnalogPad,
PadStatus_Unsupported,
PadStatus_Pending,
PadStatus_Invalid,
};
/* PadState — per-port normalized runtime state.
* Field order is chosen so that the 4 axes (left_x, left_y, right_x, right_y)
* form a contiguous 4-byte block at offset 8, allowing a single `store_word` to clear-or-write all 4 axes in one MIPS instruction.
* The struct size stays 12 bytes (unchanged from the prior order,
* which left the C compiler to insert 1 byte of trailing pad to reach the 4-byte struct alignment). */
typedef Struct_(PadState) {
PadStatus status; /* offset 0, size 4 (U4) */
U2 buttons; /* offset 4, size 2 */
U1 id; /* offset 6, size 1 */
U1 pad; /* offset 7, size 1 — explicit pad to align the axes block */
U1 left_x; /* offset 8, size 1 — store_word target (4-byte aligned) */
U1 left_y; /* offset 9, size 1 */
U1 right_x; /* offset 10, size 1 */
U1 right_y; /* offset 11, size 1 */
};
View File
+4
View File
@@ -22,6 +22,7 @@ WORD_COUNT(call_reg, 1)
WORD_COUNT(call_addr, 1) WORD_COUNT(call_addr, 1)
WORD_COUNT(branch_le_zero, 1) WORD_COUNT(branch_le_zero, 1)
WORD_COUNT(branch_equal, 1) WORD_COUNT(branch_equal, 1)
WORD_COUNT(branch_ne, 1)
WORD_COUNT(add_ui, 1) WORD_COUNT(add_ui, 1)
WORD_COUNT(set_lt_u, 1) WORD_COUNT(set_lt_u, 1)
WORD_COUNT(set_lt_s, 1) WORD_COUNT(set_lt_s, 1)
@@ -29,7 +30,9 @@ WORD_COUNT(set_lt_si, 1)
WORD_COUNT(set_lt_ui, 1) WORD_COUNT(set_lt_ui, 1)
WORD_COUNT(load_word, 1) WORD_COUNT(load_word, 1)
WORD_COUNT(load_half_u, 1) WORD_COUNT(load_half_u, 1)
WORD_COUNT(load_byte_u, 1)
WORD_COUNT(store_word, 1) WORD_COUNT(store_word, 1)
WORD_COUNT(store_byte, 1)
WORD_COUNT(add_ui_self, 1) WORD_COUNT(add_ui_self, 1)
WORD_COUNT(add_u_self, 1) WORD_COUNT(add_u_self, 1)
WORD_COUNT(add_u, 1) WORD_COUNT(add_u, 1)
@@ -37,6 +40,7 @@ WORD_COUNT(or_i, 1)
WORD_COUNT(or_i_self, 1) WORD_COUNT(or_i_self, 1)
WORD_COUNT(or_u, 1) WORD_COUNT(or_u, 1)
WORD_COUNT(or_u_self, 1) WORD_COUNT(or_u_self, 1)
WORD_COUNT(nor_u, 1)
WORD_COUNT(shift_lleft, 1) WORD_COUNT(shift_lleft, 1)
WORD_COUNT(shift_lleft_self, 1) WORD_COUNT(shift_lleft_self, 1)
WORD_COUNT(shift_lright, 1) WORD_COUNT(shift_lright, 1)
+1 -1
View File
@@ -122,7 +122,7 @@ global SMemory smem;
extern SMemory smem; extern SMemory smem;
// TODO(Ed): // TODO(Ed):
FI_ U4* spad_warm(MipsAtom atom) { FI_ U4* spad_warm(Slice_MipsCode atom) {
return nullptr; return nullptr;
} }
+77 -13
View File
@@ -16,6 +16,68 @@
#pragma region Baked Atoms #pragma region Baked Atoms
/* DIAGNOSTIC 1: Pure tape loop test */
internal MipsAtom_(diag_yield) { mac_yield() };
/* DIAGNOSTIC 2: Pure memory test (No GTE). Draws a fixed cyan triangle. */
internal MipsAtom_(diag_color) {
store_word( R_0, R_T7, 0),
load_upper_i(R_AT, gp0_cmd_poly_f3 << 8 | 0xFF), /* High: MipsCode Poly_F3(0x20) + Color B:FF */
or_i_self( R_AT, 0xFF00), /* Low: Color G:FF, R:00 (Cyan) */
store_word( R_AT, R_T7, 4),
/* Fake coordinates - Swapped winding order to prevent GPU culling! */
load_upper_i(R_AT, 0x0010), or_i_self(R_AT, 0x0010), store_word(R_AT, R_T7, 8), /* (16, 16) */
load_upper_i(R_AT, 0x0050), or_i_self(R_AT, 0x0010), store_word(R_AT, R_T7, 12), /* (80, 16) */
load_upper_i(R_AT, 0x0010), or_i_self(R_AT, 0x0050), store_word(R_AT, R_T7, 16), /* (16, 80) */
add_ui( R_T1, R_0, 10),
shift_lleft_self(R_T1, S_(U4)/2),
add_u_self( R_T1, R_T6),
load_word( R_AT, R_T1, 0),
load_upper_i(R_V0, (S_(Poly_F3)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits),
store_word( R_AT, R_T7, 0),
shift_lleft(R_AT, R_T7, S_(PolyTag_len_bits)), shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)),
or_u_self( R_AT, R_V0),
store_word( R_AT, R_T1, 0),
add_ui(R_T7, R_T7, 20),
mac_yield()
};
/* DIAGNOSTIC 3: Pure GTE test (No Memory Writes) */
internal MipsAtom_(diag_gte) {
/* Load 3 indices */
load_half_u(R_T0, R_T4, 0),
load_half_u(R_T1, R_T4, 2),
load_half_u(R_T2, R_T4, 4),
/* Load Vertices into GTE */
shift_lleft( R_AT, R_T0, 3), add_u( R_AT, R_AT, R_T5),
load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4),
gte_mv_to_data_r(R_V0, C2_VXY0), gte_mv_to_data_r(R_V1, C2_VZ0),
shift_lleft( R_AT, R_T1, 3), add_u(R_AT, R_AT, R_T5),
load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4),
gte_mv_to_data_r(R_V0, C2_VXY1), gte_mv_to_data_r(R_V1, C2_VZ1),
shift_lleft(R_AT, R_T2, 3), add_u(R_AT, R_AT, R_T5),
load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4),
gte_mv_to_data_r(R_V0, C2_VXY2), gte_mv_to_data_r(R_V1, C2_VZ2),
/* Run Math */
nop2, gte_cmdw_rtpt,
nop2, gte_cmdw_nclip,
nop2,
/* Advance Face Cursor and Yield */
add_ui(R_T4, R_T4, 8),
mac_yield()
};
typedef Struct_(Binds_CubeTri) { typedef Struct_(Binds_CubeTri) {
U4 PrimCursor; U4 PrimCursor;
V4_S2* FaceCursor; V4_S2* FaceCursor;
@@ -116,20 +178,22 @@ MipsAtom_(floor_f3_face) atom_info(atom_phase(floor_f3)
/* Culling (Branch forward if Backface) */ /* Culling (Branch forward if Backface) */
gte_mv_from_data_r(R_T0, C2_MAC0), gte_mv_from_data_r(R_T0, C2_MAC0),
nop, branch_le_zero(R_T0, atom_offset(culling, floor_f3_face_exit)), nop, // required gte -> cpu load-delay slot. nop, branch_le_zero(R_T0, atom_offset(culling, floor_f3_face_exit)), nop, // required gte -> cpu load-delay slot.
/* Format Primitive */ /* Format Primitive */
mac_gte_store_f3(), mac_gte_store_f3(),
/* Calculate Depth */ /* Calculate Depth */
gte_avg_sort_z3, gte_avg_sort_z3,
gte_mv_from_data_r(R_T1, C2_OTZ), gte_mv_from_data_r(R_T1, C2_OTZ),
/* Bounds Check OTZ < 2048 (Branch forward to skip insertion) */ /* Bounds Check OTZ < 2048 (Branch forward to skip insertion) */
add_ui( R_AT, R_0, OrderingTbl_Len), add_ui( R_AT, R_0, OrderingTbl_Len),
set_lt_u( R_AT, R_T1, R_AT), set_lt_u( R_AT, R_T1, R_AT),
branch_equal(R_AT, R_0, atom_offset(bounds_chk, floor_f3_face_exit)), nop, branch_equal(R_AT, R_0, atom_offset(bounds_chk, floor_f3_face_exit)), nop,
mac_format_f3_color(0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white) mac_format_f3_color(0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white)
mac_insert_ot_tag_f3(), /* Insert into Ordering Table Linked List */ mac_insert_ot_tag_f3(), /* Insert into Ordering Table Linked List */
add_ui_self(R_PrimCursor, S_(Poly_F3)), /* Advance Prim Cursor (5 words) */ add_ui_self(R_PrimCursor, S_(Poly_F3)), /* Advance Prim Cursor (5 words) */
// Note(Ed): No bounds checking, should be checked before atom runs. // Note(Ed): No bounds checking, should be checked before atom runs.
// end: branch(bounds_chk)
// end: branch(culling)
/* Advance Input Cursor & Yield (Both branch targets land here) */ /* Advance Input Cursor & Yield (Both branch targets land here) */
atom_label(floor_f3_face_exit) atom_label(floor_f3_face_exit)
+71
View File
@@ -0,0 +1,71 @@
#ifdef INTELLISENSE_DIRECTIVES
#pragma once
#endif
// Auto-generated by ps1_meta.lua — DO NOT EDIT
// Source: C:\projects\Pikuma\ps1\code\hello_joypad\hello_joypad.tape.c
// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)
#ifndef WORD_COUNT
#define WORD_COUNT(name, count) enum { words_##name = (count) };
#endif
/* atom_dbg_skip */
#define mac_load_v2s2(rs_x, rs_y, r_base, offset) \
load_half( rs_x, r_base, O_(V3_S2,x)) \
, load_half( rs_y, r_base, O_(V3_S2,y))
WORD_COUNT(mac_load_v2s2, 2)
/* atom_dbg_skip */
#define mac_store_v2s2(rt_x, rt_y, base, offset) \
store_half(rt_x, base, offset + O_(V2_S2,x)) \
, store_half(rt_y, base, offset + O_(V2_S2,y))
WORD_COUNT(mac_store_v2s2, 2)
/* atom_dbg_skip */
#define mac_store_rects2(rt_x, rt_y, rt_width, rt_height, base, offset) \
store_half(rt_x, base, offset + O_(Rect_S2,x)) \
, store_half(rt_y, base, offset + O_(Rect_S2,y)) \
, store_half(rt_width, base, offset + O_(Rect_S2,width)) \
, store_half(rt_height, base, offset + O_(Rect_S2,height))
WORD_COUNT(mac_store_rects2, 4)
/* atom_dbg_skip */
#define mac_store_rgb8(rr, rg, rb, base, offset) \
store_byte(rr, base, offset + O_(DrawEnv,initial_bg_color.r)) \
, store_byte(rg, base, offset + O_(DrawEnv,initial_bg_color.g)) \
, store_byte(rb, base, offset + O_(DrawEnv,initial_bg_color.b))
WORD_COUNT(mac_store_rgb8, 3)
#define mac_gcmd_push(cmd, reg_transfer, reg_base, port) \
load_upper_i(reg_transfer, cmd >> 16) \
, or_i_self( reg_transfer, cmd & 0xFFFF) \
, store_word( reg_transfer, reg_base, port)
WORD_COUNT(mac_gcmd_push, 3)
#define mac_put_disp_env(reg_transfer, reg_base, port) \
mac_gcmd_push(gp0_word_draw_area_top_left_origin, reg_transfer, reg_base, port) \
, mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port) \
, mac_gcmd_push(gp0_word_set_mask_bit(), reg_transfer, reg_base, port) \
, mac_gcmd_push(gp0_word_draw_area_top_left_origin, reg_transfer, reg_base, port) \
, mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port)
WORD_COUNT(mac_put_disp_env, 15)
#define mac_put_draw_env(reg_transfer, reg_base, port) \
mac_gcmd_push(gp0_dr_env_tag, reg_transfer, reg_base, port) /* tag (length=15 << 24, addr=0) — packet header for the DR_ENV sequence. The GPU needs this to recognize the next 15 words as a DR_ENV packet and trigger the isbg auto-clear. */ \
, mac_gcmd_push(gp0_word_draw_mode_drawing_allowed, reg_transfer, reg_base, port) /* code[0] DrawMode (dfe=1, dtd=0, tpage=0) */ \
, mac_gcmd_push(gp0_word_set_texture_window(), reg_transfer, reg_base, port) /* code[1] TextureWindow (tw=(0,0)) */ \
, mac_gcmd_push(enc_gp0_draw_area_tl_word(0, ScreenRes_Y), reg_transfer, reg_base, port) /* code[2] DrawArea top-left (clip.x=0, clip.y=ScreenRes_Y=240) */ \
, mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port) /* code[3] DrawArea bottom-right (clip.x+w=320, clip.y+h=480) */ \
, mac_gcmd_push(gp0_word_set_draw_offset(), reg_transfer, reg_base, port) /* code[4] DrawOffset (ofs=(0,0)) — bare-cmd word; the GPU uses the current state machine. */ \
, mac_gcmd_push(gp0_word_dr_env_mask(), reg_transfer, reg_base, port) /* code[5] Mask (dtd=0, dfe=1, isbg=1) — 0xE6 cmd + isbg bit. */ \
, mac_gcmd_push(gp0_word_dr_env_bg_color_cmd(1, 7, 7, 7), reg_transfer, reg_base, port) /* code[6] Initial-bg-color + auto-clear (isbg=1, r=7, g=7, b=7). */ \
, mac_gcmd_push(gp0_word_dr_env_draw_mode(1), reg_transfer, reg_base, port) /* code[7] Re-assert DrawMode with isbg=1 (isbg-flag set; the 0xE1 cmd byte plus isbg only). */ /* code[8..10] Padding (NOP — GPU discards; the DR_ENV requires 16 words total). */ \
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port) \
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port) \
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port) /* code[11..12] TextureWindow bottom-right (tw.x+tw.w=0, tw.y+tw.h=0) — libpsyx emits twice. */ \
, mac_gcmd_push(gp0_word_set_texture_window(), reg_transfer, reg_base, port) \
, mac_gcmd_push(gp0_word_set_texture_window(), reg_transfer, reg_base, port) /* code[13..14] Padding (NOP) — completes the 16-word packet. */ \
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port) \
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port)
WORD_COUNT(mac_put_draw_env, 48)
@@ -25,5 +25,49 @@ enum {
atom_offset_bounds_chk_floor_f3_face_exit = _atom_offset_bounds_chk_floor_f3_face_exit, atom_offset_bounds_chk_floor_f3_face_exit = _atom_offset_bounds_chk_floor_f3_face_exit,
}; };
// --- atom: pad_bios_snapshot (78 words) ---
#define _atom_offset_snap_root_skip_disconnected 8
#define _atom_offset_disconnected_snap_end 60
#define _atom_offset_case_2_id_dispatch 8
#define _atom_offset_pending_snap_end 50
#define _atom_offset_id_dispatch_try_analog_stick 11
#define _atom_offset_id_dispatch_snap_end 37
#define _atom_offset_try_analog_stick_try_analog_pad 12
#define _atom_offset_analog_stick_snap_end 23
#define _atom_offset_try_analog_pad_try_unsupported 11
#define _atom_offset_analog_pad_snap_end 9
enum {
atom_offset_snap_root_skip_disconnected = _atom_offset_snap_root_skip_disconnected,
atom_offset_disconnected_snap_end = _atom_offset_disconnected_snap_end,
atom_offset_case_2_id_dispatch = _atom_offset_case_2_id_dispatch,
atom_offset_pending_snap_end = _atom_offset_pending_snap_end,
atom_offset_id_dispatch_try_analog_stick = _atom_offset_id_dispatch_try_analog_stick,
atom_offset_id_dispatch_snap_end = _atom_offset_id_dispatch_snap_end,
atom_offset_try_analog_stick_try_analog_pad = _atom_offset_try_analog_stick_try_analog_pad,
atom_offset_analog_stick_snap_end = _atom_offset_analog_stick_snap_end,
atom_offset_try_analog_pad_try_unsupported = _atom_offset_try_analog_pad_try_unsupported,
atom_offset_analog_pad_snap_end = _atom_offset_analog_pad_snap_end,
};
// --- atom: pad_apply_input (60 words) ---
#define _atom_offset_dpad_left_exit_dpad_left 6
#define _atom_offset_dpad_right_exit_dpad_right 6
#define _atom_offset_dead_zone_low_check_dead_low_active 8
#define _atom_offset_dead_zone_high_check_dead_high_active 15
#define _atom_offset_dead_zone_skip_exit_stick 23
#define _atom_offset_end_low_exit_stick 11
enum {
atom_offset_dpad_left_exit_dpad_left = _atom_offset_dpad_left_exit_dpad_left,
atom_offset_dpad_right_exit_dpad_right = _atom_offset_dpad_right_exit_dpad_right,
atom_offset_dead_zone_low_check_dead_low_active = _atom_offset_dead_zone_low_check_dead_low_active,
atom_offset_dead_zone_high_check_dead_high_active = _atom_offset_dead_zone_high_check_dead_high_active,
atom_offset_dead_zone_skip_exit_stick = _atom_offset_dead_zone_skip_exit_stick,
atom_offset_end_low_exit_stick = _atom_offset_end_low_exit_stick,
};
#pragma endregion hello_joypad.tape #pragma endregion hello_joypad.tape
+145 -30
View File
@@ -13,6 +13,7 @@
#include "duffle/mips.h" #include "duffle/mips.h"
#include "duffle/gp.h" #include "duffle/gp.h"
#include "duffle/gte.h" #include "duffle/gte.h"
#include "duffle/pad.h"
# include "duffle/gen/duffle.macs.h" # include "duffle/gen/duffle.macs.h"
# include "duffle/gen/duffle.offsets.h" # include "duffle/gen/duffle.offsets.h"
@@ -20,11 +21,16 @@
#include "duffle/lottes_tape.h" #include "duffle/lottes_tape.h"
#include "duffle/word_count.metadata.h" #include "duffle/word_count.metadata.h"
#include "psyq.h"
# include "gen/hello_joypad.macs.h"
# include "gen/hello_joypad.offsets.h" # include "gen/hello_joypad.offsets.h"
#include "hello_joypad.h" #include "hello_joypad.h"
#include "psyq.c"
#include "hello_joypad.tape.c" #include "hello_joypad.tape.c"
typedef U4 OrderingTable_Buffer[OrderingTbl_Len]; typedef U4 OrderingTable_Buffer[OrderingTbl_Len];
typedef Array_(OrderingTable_Buffer, 2); typedef Array_(OrderingTable_Buffer, 2);
@@ -99,6 +105,7 @@ typedef Struct_(Ent_Floor) {
A2_V3_S2 faces; A2_V3_S2 faces;
}; };
enum { enum {
Scratchpad_Len = 1024, Scratchpad_Len = 1024,
MemTape_Len = 512, MemTape_Len = 512,
@@ -116,16 +123,14 @@ typedef Struct_(SMemory) {
Ent_Cube cube; Ent_Cube cube;
Ent_Floor floor; Ent_Floor floor;
PadBiosRaw pad_raw[2];
PadState pad[2];
U4_V scratchpad; // d-cache U4_V scratchpad; // d-cache
}; };
global SMemory smem; global SMemory smem;
extern SMemory smem; extern SMemory smem;
// TODO(Ed):
FI_ U4* spad_warm(MipsAtom atom) {
return nullptr;
}
I_ B1* prim__alloc(U4 type_width, Str8 type_name) { I_ B1* prim__alloc(U4 type_width, Str8 type_name) {
gknown PrimitiveArena* pa = & smem.primitives; gknown PrimitiveArena* pa = & smem.primitives;
gknown B1* buf = (B1*) r_(smem.primitives.buf)[smem.active_buf_id]; gknown B1* buf = (B1*) r_(smem.primitives.buf)[smem.active_buf_id];
@@ -136,6 +141,77 @@ I_ B1* prim__alloc(U4 type_width, Str8 type_name) {
} }
#define prim_alloc(type) (type*)prim__alloc(S_(type), slit( stringify(type))) #define prim_alloc(type) (type*)prim__alloc(S_(type), slit( stringify(type)))
/* Uses ONE 8-byte frame allocated via the compiler's standard prologue.
* The 4 wasted-arg words for B(12h) InitPAD2 live at [SP+0..15] but are not explicitly allocated.
* The compiler handles the MIPS O32 "wasted stack" convention for us by treating the B-call as a 4-arg call.
*
* The buffer pointers are passed as arguments so the compiler keeps them in callee-saved registers;
* The B(12h) asm volatile block does NOT clobber those registers (it clobbers only the volatile GPRs + the B-table arg registers explicitly).
* The C-level writes after the call re-load the pointers from their callee-saved homes.
*
* The clobber list for both B-calls names the full BIOS destroy set documented in kernelbios.md:167-174 (R1..R15, R24..R25, R31, HI/LO).
* The kernel-ABI "volatile GPRs" subset is clb_system; the rest of the destroy set is enumerated explicitly here. */
NI_ void pad_bios_init_start(PadBiosRaw* raw0, PadBiosRaw* raw1)
{
/* Pin raw0 + raw1 to $a0 + $a1 via rgcc; the B(12h) call uses these directly.
* The `(void)` casts mark them as unread after the call so the compiler doesn't need to move them back. */
register PadBiosRaw* p0 rgcc(R_A0) = raw0;
register PadBiosRaw* p1 rgcc(R_A1) = raw1;
(void)p0; (void)p1;
// TODO(Ed): Properly annotate the raw values in the inline asm instructions.
// Use enums.
/* B(12h) InitPAD2(raw0, 0x22, raw1, 0x22)
* $a0 = raw0 (rgcc-bound; survives the sequence below)
* $a1 = raw1 (preserved into $a2 before $a1 is overwritten)
* $a2 = raw1 (moved from $a1; survives $a1's overwrite)
* $a3 = 0x22 (immediate)
* $t1 = 0x12 (function number)
* $t2 = 0xB0 (BIOS B-table address) */
asm volatile(
asm_words(
or_u( rarg_2, rarg_1, rdiscard), /* $a2 = $a1 = raw1 */
add_ui( rarg_1, rdiscard, 0x22), /* $a1 = 0x22 */
add_ui( rarg_3, rdiscard, 0x22), /* $a3 = 0x22 */
add_ui( rtmp_1, rdiscard, 0x12), /* $t1 = 0x12 */
add_ui( rtmp_2, rdiscard, 0xB0), /* $t2 = 0xB0 */
call_reg(rtmp_2), /* jalr $t2, $ra */
nop /* BD slot */
)
asm_rpins, r_use(p0), r_use(p1)
asm_clobber:
rlit(R_AT),
rlit(R_V0), rlit(R_V1),
rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3), rlit(R_T4),
rlit(R_T5), rlit(R_T6), rlit(R_T7), rlit(R_T8), rlit(R_T9),
rlit(R_RA),
clb_mem_drain
);
/* The C-level writes re-load the pointers via the parameter names and write 0xFF to each
* buffer's status byte to mark the initial-state hazard documented in kernelbios.md:1621-1624. */
u1_v(raw0)[0] = 0xFF;
u1_v(raw1)[0] = 0xFF;
/* B(13h) StartPAD2() — no args. The BIOS preserves $sp. */
asm volatile(
asm_words(
add_ui( rtmp_1, rdiscard, 0x13), /* $t1 = 0x13 */
add_ui( rtmp_2, rdiscard, 0xB0), /* $t2 = 0xB0 (re-load) */
call_reg(rtmp_2), /* jalr $t2, $ra */
nop /* BD slot */
)
asm_clobber:
rlit(R_AT),
rlit(R_V0), rlit(R_V1),
rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3), rlit(R_T4),
rlit(R_T5), rlit(R_T6), rlit(R_T7), rlit(R_T8), rlit(R_T9),
rlit(R_RA),
clb_mem_drain
);
}
void gp_screen_init_c11(DoubleBuffer* screen_buf, S4* active_buf_id) void gp_screen_init_c11(DoubleBuffer* screen_buf, S4* active_buf_id)
{ {
reset_graph(0); reset_graph(0);
@@ -162,10 +238,6 @@ void gp_screen_init_c11(DoubleBuffer* screen_buf, S4* active_buf_id)
// Initialize and setup the GTE geometry offsets // Initialize and setup the GTE geometry offsets
geom_init(); geom_init();
// NOTE: geom_set_offset/geom_set_screen are kept as-is (the libgte versions
// are known to be broken in this PSYQ 4.7 build — see report 2026-07-09).
// The user's research wants the C-side non-tape reference to work as a
// known-good baseline for comparison against the tape.
geom_set_offset(ScreenRes_CenterX, ScreenRes_CenterY); geom_set_offset(ScreenRes_CenterX, ScreenRes_CenterY);
geom_set_screen(ScreenZ); geom_set_screen(ScreenZ);
@@ -184,12 +256,41 @@ void gp_display_frame(DoubleBuffer* screen_buf, S4* active_buf_id, U4* ordering_
active_buf_id[0] = ! active_buf_id[0]; // Swap current buffer active_buf_id[0] = ! active_buf_id[0]; // Swap current buffer
} }
void render(void) {
}
GCC_OPTIMIZATION_DISABLE GCC_OPTIMIZATION_DISABLE
void update(PrimitiveArena* pa, U4* ordering_buf) void update(PrimitiveArena* pa, U4* ordering_buf)
{ {
TapeBuilder tb = tb_make(slice_ut_arr(smem.MemTape));
if (0) // Pad Input (dead — kept for the source-as-written record; references the deleted `pad_state` field)
{
(void)Pad_Left; (void)Pad_Right; /* suppress unused-token warnings */
if (false) {
smem.cube.rot.y += 30;
smem.floor.rot.y += 5;
}
if (false) {
smem.cube.rot.y -= 30;
smem.floor.rot.y -= 5;
}
}
if (1) // Pad Input (Tape version)
{
tb.used = 0; tb_scope_run(& tb) {
/* BIOS-owned polling: per-frame snapshot of both ports. */
tb_emit_(pad_bios_snapshot);
tb_data_(raw, & smem.pad_raw[0]);
tb_data_(state, & smem.pad[0]);
tb_emit_(pad_bios_snapshot);
tb_data_(raw, & smem.pad_raw[1]);
tb_data_(state, & smem.pad[1]);
/* Per-frame rotation apply: consume pad[0].buttons + pad[0].left_x */
tb_emit_(pad_apply_input);
tb_data_(state, & smem.pad[0]);
tb_data_(cube_rot, & smem.cube.rot);
tb_data_(floor_rot, & smem.floor.rot);
}
}
orderingtbl_clear_reverse(ordering_buf, OrderingTbl_Len); orderingtbl_clear_reverse(ordering_buf, OrderingTbl_Len);
// Update the position based on acceleration and velocity // Update the position based on acceleration and velocity
@@ -213,7 +314,6 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
A2_S2 p; //??? A2_S2 p; //???
S4 flag; //???? S4 flag; //????
TapeBuilder tb = tb_make(slice_ut_arr(smem.MemTape));
// Draw Cube // Draw Cube
if (0) if (0)
@@ -285,7 +385,7 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
} }
tape_run(tb_slice(tb)); tape_run(tb_slice(tb));
smem.cube.rot.y += 30; // smem.cube.rot.y += 30;
} }
// Draw Floor // Draw Floor
if (0) if (0)
@@ -376,7 +476,7 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
tape_run(tb_slice(tb));// Fire off the tape. tape_run(tb_slice(tb));// Fire off the tape.
// C-side state (pa->used) has already been updated by the tape! // C-side state (pa->used) has already been updated by the tape!
smem.floor.rot.y += 5; // smem.floor.rot.y += 5;
} }
// --- TAPE DIAGNOSTICS --- // --- TAPE DIAGNOSTICS ---
if (0) if (0)
@@ -397,27 +497,42 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
} }
GCC_OPTIMIZATION_ENABLE GCC_OPTIMIZATION_ENABLE
void render(void) {
}
int main(void) int main(void)
{ {
smem = (SMemory){0}; smem = (SMemory){0};
smem.scratchpad = C_(U4_V, 0x1F800000); smem.scratchpad = C_(U4_V, 0x1F800000);
smem.primitives.used = 0; // smem.primitives.used = 0;
ent_cube128_init(& smem.cube.verts, & smem.cube.faces); { // smem.active_buf_id = 0;
Ent_Cube* cube = & smem.cube; /*Persistent Entity Setup*/{
cube->rot = v3s2(0, 0, 0); ent_cube128_init(& smem.cube.verts, & smem.cube.faces); {
// cube->pos = v3s4(0, 0, 900); Ent_Cube* cube = & smem.cube;
cube->scale = v3s4_fp_one(); cube->rot = v3s2(0, 0, 0);
cube->accel = v3s4(0, 1, 0); cube->scale = v3s4_fp_one();
cube->pos = v3s4(0, -400, 1800); cube->accel = v3s4(0, 1, 0);
cube->pos = v3s4(0, -400, 1800);
}
ent_floor_init(& smem.floor.verts, & smem.floor.faces); {
Ent_Floor* floor = & smem.floor;
floor->rot = v3s2(0, 0, 0);
floor->pos = v3s4(0, 450, 1800);
floor->scale = v3s4_fp_one();
}
} }
ent_floor_init(& smem.floor.verts, & smem.floor.faces); { TapeBuilder tb = tb_make(slice_ut_arr(smem.MemTape)); {
Ent_Floor* floor = & smem.floor; reset_graph(0);
floor->rot = v3s2(0, 0, 0); /* Direct BIOS: poll both ports during VBlank. */
floor->pos = v3s4(0, 450, 1800); pad_bios_init_start(& smem.pad_raw[0], & smem.pad_raw[1]);
floor->scale = v3s4_fp_one(); /* Pinned registers for the GPU init atom. */
register U4* io_base_addr rgcc(R_IO_BaseAddr) = u4_r(IO_BASE_ADDR);
register DoubleBuffer* screen_buf rgcc(R_ScreenBuf) = & smem.screen_buf;
tb.used = 0; tb_scope_run(& tb) {
tb_emit(& tb, screen_env_init);
tb_emit(& tb, gp_screen_init);
}
} }
// gknown gp_screen_init();
gp_screen_init_c11(& smem.screen_buf, & smem.active_buf_id);
while (1) { while (1) {
gknown S4* active_buf_id = & smem.active_buf_id; gknown S4* active_buf_id = & smem.active_buf_id;
gknown U4* ordering_buf = r_(smem.ordering_tbl)[active_buf_id[0]]; gknown U4* ordering_buf = r_(smem.ordering_tbl)[active_buf_id[0]];
+7 -100
View File
@@ -3,6 +3,7 @@
# include "duffle/dsl.h" # include "duffle/dsl.h"
# include "duffle/math.h" # include "duffle/math.h"
# include "duffle/gp.h" # include "duffle/gp.h"
# include "duffle/pad.h"
#endif #endif
enum { enum {
@@ -10,107 +11,13 @@ enum {
OrderingTbl_Len = 2048 OrderingTbl_Len = 2048
}; };
typedef Struct_(DrawEnv_Packed) { U4 tag; U4 code[15]; }; enum {
typedef Struct_(DrawEnv) { ScreenRes_X = 320,
Rect_S2 clip_area; ScreenRes_Y = 240,
A2_S2 drawing_offset; ScreenZ = 320,
Rect_S2 texture_window; ScreenRes_CenterX = (ScreenRes_X >> 1),
S2 texture_page; ScreenRes_CenterY = (ScreenRes_Y >> 1),
B1 flag_dither;
B1 flag_draw_on_display;
B1 enable_auto_clear;
RGB8 initial_bg_color;
DrawEnv_Packed dr_env; // reserved
}; };
typedef Struct_(DisplayEnv) {
Rect_S2 display_area;
Rect_S2 screen;
B1 vinterlace;
B1 color24;
B1 pad0;
B1 pad1;
};
typedef Array_(DrawEnv, 2);
typedef Array_(DisplayEnv, 2);
typedef Struct_(DoubleBuffer) {
A2_DrawEnv draw;
A2_DisplayEnv display;
};
#define ScreenRes_X 320
#define ScreenRes_Y 240
#define ScreenZ 320
#define ScreenRes_CenterX (ScreenRes_X >> 1)
#define ScreenRes_CenterY (ScreenRes_Y >> 1)
DisplayEnv* displayenv_init(DisplayEnv* env, S4 x, S4 y, S4 w, S4 h) __asm__("SetDefDispEnv");
DrawEnv* drawenv_init (DrawEnv* env, S4 x, S4 y, S4 w, S4 h) __asm__("SetDefDrawEnv");
DisplayEnv* displayenv_put(DisplayEnv* env) __asm__("PutDispEnv");
DrawEnv* drawenv_put (DrawEnv* env) __asm__("PutDrawEnv");
U4 geom_init(void) __asm__("InitGeom");
void geom_set_offset(U4 x, U4 y) __asm__("SetGeomOffset");
void geom_set_screen(U4 h) __asm__("SetGeomScreen");
U4* orderingtbl_clear_reverse(U4* ot, U4 len) __asm__("ClearOTagR");
U4 reset_graph(U4 mode) __asm__("ResetGraph");
void set_display_enabled(U4 mask) __asm__("SetDispMask");
U4 draw_sync(U4 mode) __asm__("DrawSync");
U4 vsync(U4 mode) __asm__("VSync");
void draw_orderingtbl(U4* buf) __asm__("DrawOTag");
typedef Struct_(Tile) {
U4 tag;
RGB8 color;
B1 code;
Rect_S2 rect;
};
/*
Linear Algebra
*/
M3_S2* m3s2_rotation (V3_S2* vec, M3_S2* mat) __asm__("RotMatrix");
M3_S2* m3s2_translation(M3_S2* mat, V3_S4* vec) __asm__("TransMatrix");
M3_S2* m3s2_scale (M3_S2* mat, V3_S4* vec) __asm__("ScaleMatrix");
// Rotation, Translation, Perspective
S4 rtp_v3s2_raw(V3_S2* vec, S4* xy, S4* pp, S4* flag) __asm__("RotTransPers");
FI_ S4 rtp_v3s2(V3_S2* vec, V2_S2* xy, A2_S2* pp, S4* flag) { return rtp_v3s2_raw(vec, C_(S4*R_, & xy->x), C_(S4*R_, pp), r_(flag)); }
S4 rtp_avg_nclip_a3_v3s2_raw(V3_S2* v0, V3_S2* v1, V3_S2* v2, S4* xy1, S4* xy2, S4* xy3, S4* pp, S4* otz, S4* flag) __asm__("RotAverageNclip3");
FI_ S4 rtp_avg_nclip_a3_v3s2(
V3_S2* v0, V3_S2* v1, V3_S2* v2,
V2_S2* xy0, V2_S2* xy1, V2_S2* xy2,
A2_S2* pp, S4* otz, S4* flag
){
return rtp_avg_nclip_a3_v3s2_raw(
v0, v1, v2,
C_(S4*R_, xy0), C_(S4*R_, xy1), C_(S4*R_, xy2),
C_(S4*R_, pp), C_(S4*R_, otz), C_(S4*R_, flag)
);
}
S4 rtp_avg_nclip_a4_v3s2_raw(V3_S2* v0, V3_S2* v1, V3_S2* v2, V3_S2* v3, S4* xy1, S4* xy2, S4* xy3, S4* xy4, S4* pp, S4* otz, S4* flag) __asm__("RotAverageNclip4");
FI_ S4 rtp_avg_nclip_a4_v3s2(
V3_S2* v0, V3_S2* v1, V3_S2* v2, V3_S2* v3,
V2_S2* xy0, V2_S2* xy1, V2_S2* xy2, V2_S2* xy3,
A2_S2* pp, S4* otz, S4* flag
){
return rtp_avg_nclip_a4_v3s2_raw(
v0, v1, v2, v3,
C_(S4*R_, xy0), C_(S4*R_, xy1), C_(S4*R_, xy2), C_(S4*R_, xy3),
C_(S4*R_, pp), C_(S4*R_, otz), C_(S4*R_, flag)
);
}
void gte_matrix_set_rotation (M3_S2* mat) __asm__("SetRotMatrix");
void gte_matrix_set_translation(M3_S2* mat) __asm__("SetTransMatrix");
enum { enum {
fp_one = (1 << 12), fp_one = (1 << 12),
+566 -15
View File
@@ -3,19 +3,267 @@
# include "duffle/gen/duffle.offsets.h" # include "duffle/gen/duffle.offsets.h"
# include "duffle/atom_dsl.h" # include "duffle/atom_dsl.h"
# include "duffle/lottes_tape.h" # include "duffle/lottes_tape.h"
# include "duffle/mips.h"
# include "duffle/gte.h"
# include "duffle/gp.h"
# include "duffle/pad.h"
# include "duffle/word_count.metadata.h" # include "duffle/word_count.metadata.h"
# include "psyq.h"
# include "gen/hello_joypad.offsets.h" # include "gen/hello_joypad.offsets.h"
# include "gen/hello_joypad.macs.h"
# include "hello_joypad.h" # include "hello_joypad.h"
#endif #endif
#pragma region MACs (Mips Atom components) #pragma region MACs (Mips Atom components)
FI_ Slice_MipsCode ac_load_v2s2(U4 rs_x, U4 rs_y, U4 r_base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_load_v2s2, {
load_half( rs_x, r_base, O_(V3_S2,x)),
load_half( rs_y, r_base, O_(V3_S2,y)),
})
FI_ Slice_MipsCode ac_store_v2s2(U4 rt_x, U4 rt_y, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_v2s2, {
store_half(rt_x, base, offset + O_(V2_S2,x)),
store_half(rt_y, base, offset + O_(V2_S2,y)),
})
FI_ Slice_MipsCode ac_store_rects2(U4 rt_x, U4 rt_y, U4 rt_width, U4 rt_height, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_rects2, {
store_half(rt_x, base, offset + O_(Rect_S2,x)),
store_half(rt_y, base, offset + O_(Rect_S2,y)),
store_half(rt_width, base, offset + O_(Rect_S2,width)),
store_half(rt_height, base, offset + O_(Rect_S2,height)),
})
FI_ Slice_MipsCode ac_store_rgb8(U1 rr, U1 rg, U1 rb, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_rgb8, {
store_byte(rr, base, offset + O_(DrawEnv,initial_bg_color.r)),
store_byte(rg, base, offset + O_(DrawEnv,initial_bg_color.g)),
store_byte(rb, base, offset + O_(DrawEnv,initial_bg_color.b)),
})
FI_ Slice_MipsCode ac_gcmd_push(U4 cmd, U4 reg_transfer, U4 reg_base, U2 port)
MipsAtomComp_Proc_(ac_gcmd_push, {
load_upper_i(reg_transfer, cmd >> 16),
or_i_self( reg_transfer, cmd & 0xFFFF),
store_word( reg_transfer, reg_base, port),
})
FI_ Slice_MipsCode ac_put_disp_env(U4 reg_transfer, U4 reg_base, U2 port)
MipsAtomComp_Proc_(ac_put_disp_env, {
// Emits 5 GP0 commands for buffer 0 (display_area = (0,0,320,240)).
// Sequence per libpsyx PutDispEnv: DrawArea TL → DrawArea BR → Mask → DrawArea TL → DrawArea BR
mac_gcmd_push(gp0_word_draw_area_top_left_origin, reg_transfer, reg_base, port),
mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port),
mac_gcmd_push(gp0_word_set_mask_bit(), reg_transfer, reg_base, port),
mac_gcmd_push(gp0_word_draw_area_top_left_origin, reg_transfer, reg_base, port),
mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port),
})
FI_ Slice_MipsCode ac_put_draw_env(U4 reg_transfer, U4 reg_base, U2 port)
MipsAtomComp_Proc_(ac_put_draw_env, {
/*
* ORIGIN: each code word corresponds to the EXACT value libpsyx's PutDrawEnv function would compute for the same DrawEnv settings.
* References:
* - libpsyx source: `toolchain/psyq-4_7/lib/libgpu.a` (binary, function `PutDrawEnv`)
* - PSX-SPX doc: https://problemkaputt.de/psx-spx.htm#gputdrawingcommands
* - PSYQ SDK: `setdrawenv` / `makelongdr_env` source
* - NOCASH PSX spec: §"GP0(E1h) Draw Mode setting" through §"DR_ENV"
*
* The 16-word format is documented in the PSYQ SDK manual and on NOCASH's PSX-spec.txt. The libpsyx reference is at:
* ./toolchain/psyq-4_7/lib/libgpu.a
* (binary; the PutDrawEnv implementation builds the 16-word DR_ENV from the user's DRAWENV struct and emits it via GP0 GPU commands.)
*
* Word indices (libpsyx PutDrawEnv / SetDrawEnv order):
* tag = (length << 24) | addr — 16-word packet (1 tag + 15 code)
* code[0] = DrawMode (dfe=1, dtd=0, tpage=0) — must come first per libpsyx
* code[1] = TextureWindow (tw=(0,0)) — bare-cmd word; GPU uses current state
* code[2] = DrawArea top-left (clip.x=0, clip.y=240)
* code[3] = DrawArea bottom-right (clip.x+w=320, clip.y+h=480)
* code[4] = DrawOffset (ofs=(0,0)) — bare-cmd word
* code[5] = Mask (dtd=0, dfe=1, isbg=1) — 0xE6 cmd + isbg bit
* code[6] = Initial-bg-color (isbg=1, r=7, g=7, b=7)
* code[7] = DrawMode (isbg=1, tpage=0) — re-asserts DrawMode with isbg
* code[8..10] = padding (NOP) — 3 words to fill the packet
* code[11..12] = TextureWindow bottom-right — defaults to (0,0,0,0)
* code[13..14] = padding (NOP) — completes the 16-word packet
*/
mac_gcmd_push(gp0_dr_env_tag, reg_transfer, reg_base, port), /* tag (length=15 << 24, addr=0) — packet header for the DR_ENV sequence. The GPU needs this to recognize the next 15 words as a DR_ENV packet and trigger the isbg auto-clear. */
mac_gcmd_push(gp0_word_draw_mode_drawing_allowed, reg_transfer, reg_base, port), /* code[0] DrawMode (dfe=1, dtd=0, tpage=0) */
mac_gcmd_push(gp0_word_set_texture_window(), reg_transfer, reg_base, port), /* code[1] TextureWindow (tw=(0,0)) */
mac_gcmd_push(enc_gp0_draw_area_tl_word(0, ScreenRes_Y), reg_transfer, reg_base, port), /* code[2] DrawArea top-left (clip.x=0, clip.y=ScreenRes_Y=240) */
mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port), /* code[3] DrawArea bottom-right (clip.x+w=320, clip.y+h=480) */
mac_gcmd_push(gp0_word_set_draw_offset(), reg_transfer, reg_base, port), /* code[4] DrawOffset (ofs=(0,0)) — bare-cmd word; the GPU uses the current state machine. */
mac_gcmd_push(gp0_word_dr_env_mask(), reg_transfer, reg_base, port), /* code[5] Mask (dtd=0, dfe=1, isbg=1) — 0xE6 cmd + isbg bit. */
mac_gcmd_push(gp0_word_dr_env_bg_color_cmd(1, 7, 7, 7), reg_transfer, reg_base, port), /* code[6] Initial-bg-color + auto-clear (isbg=1, r=7, g=7, b=7). */
mac_gcmd_push(gp0_word_dr_env_draw_mode(1), reg_transfer, reg_base, port), /* code[7] Re-assert DrawMode with isbg=1 (isbg-flag set; the 0xE1 cmd byte plus isbg only). */
/* code[8..10] Padding (NOP — GPU discards; the DR_ENV requires 16 words total). */
mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port),
mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port),
mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port),
/* code[11..12] TextureWindow bottom-right (tw.x+tw.w=0, tw.y+tw.h=0) — libpsyx emits twice. */
mac_gcmd_push(gp0_word_set_texture_window(), reg_transfer, reg_base, port),
mac_gcmd_push(gp0_word_set_texture_window(), reg_transfer, reg_base, port),
/* code[13..14] Padding (NOP) — completes the 16-word packet. */
mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port),
mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port),
})
#pragma endregion MACs #pragma endregion MACs
#pragma region Baked Atoms #pragma region Baked Atoms
/* DIAGNOSTIC 1: Pure tape loop test */
internal MipsAtom_(diag_yield) { mac_yield() };
/* DIAGNOSTIC 2: Pure memory test (No GTE). Draws a fixed cyan triangle. */
internal MipsAtom_(diag_color) {
store_word( R_0, R_T7, 0),
load_upper_i(R_AT, gp0_cmd_poly_f3 << 8 | 0xFF), /* High: MipsCode Poly_F3(0x20) + Color B:FF */
or_i_self( R_AT, 0xFF00), /* Low: Color G:FF, R:00 (Cyan) */
store_word( R_AT, R_T7, 4),
/* Fake coordinates - Swapped winding order to prevent GPU culling! */
load_upper_i(R_AT, 0x0010), or_i_self(R_AT, 0x0010), store_word(R_AT, R_T7, 8), /* (16, 16) */
load_upper_i(R_AT, 0x0050), or_i_self(R_AT, 0x0010), store_word(R_AT, R_T7, 12), /* (80, 16) */
load_upper_i(R_AT, 0x0010), or_i_self(R_AT, 0x0050), store_word(R_AT, R_T7, 16), /* (16, 80) */
add_ui( R_T1, R_0, 10),
shift_lleft_self(R_T1, S_(U4)/2),
add_u_self( R_T1, R_T6),
load_word( R_AT, R_T1, 0),
load_upper_i(R_V0, (S_(Poly_F3)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits),
store_word( R_AT, R_T7, 0),
shift_lleft(R_AT, R_T7, S_(PolyTag_len_bits)), shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)),
or_u_self( R_AT, R_V0),
store_word( R_AT, R_T1, 0),
add_ui(R_T7, R_T7, 20),
mac_yield()
};
/* DIAGNOSTIC 3: Pure GTE test (No Memory Writes) */
internal MipsAtom_(diag_gte) {
/* Load 3 indices */
load_half_u(R_T0, R_T4, 0),
load_half_u(R_T1, R_T4, 2),
load_half_u(R_T2, R_T4, 4),
/* Load Vertices into GTE */
shift_lleft( R_AT, R_T0, 3), add_u( R_AT, R_AT, R_T5),
load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4),
gte_mv_to_data_r(R_V0, C2_VXY0), gte_mv_to_data_r(R_V1, C2_VZ0),
shift_lleft( R_AT, R_T1, 3), add_u(R_AT, R_AT, R_T5),
load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4),
gte_mv_to_data_r(R_V0, C2_VXY1), gte_mv_to_data_r(R_V1, C2_VZ1),
shift_lleft(R_AT, R_T2, 3), add_u(R_AT, R_AT, R_T5),
load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4),
gte_mv_to_data_r(R_V0, C2_VXY2), gte_mv_to_data_r(R_V1, C2_VZ2),
/* Run Math */
nop2, gte_cmdw_rtpt,
nop2, gte_cmdw_nclip,
nop2,
/* Advance Face Cursor and Yield */
add_ui(R_T4, R_T4, 8),
mac_yield()
};
enum {
R_ScreenX = R_T5 atom_reg atom_type(U2),
R_ScreenY = R_T6 atom_reg atom_type(U2),
R_ScreenBuf = R_T7 atom_reg, /* Caller-pinned: & smem.screen_buf */
#define R_ScreenBuf_Code R_T7_Code
};
//screen_env_init. Mirrors the libpsyx's SetDefDispEnv + SetDefDrawEnv + the manual enable_auto_clear / initial_bg_color writes.
internal MipsAtom_(screen_env_init) atom_info(atom_phase(screen_init)
, atom_reads(R_T0, R_ScreenX, R_ScreenY, R_ScreenBuf)
, atom_writes(R_T0, R_ScreenX, R_ScreenY)
) {
/* display[0] = (0, 0, 320, 240); rest of struct zeroed. */
add_ui(R_ScreenX, R_0, ScreenRes_X), add_ui(R_ScreenY, R_0, ScreenRes_Y),
mac_store_v2s2(R_ScreenX, R_ScreenY, R_ScreenBuf, O_(DisplayEnv,display_area.width) + OA_(DoubleBuffer,display,0)),
store_word(R_0, R_ScreenBuf, O_(DisplayEnv,display_area) + OA_(DoubleBuffer,display,0)),
store_word(R_0, R_ScreenBuf, O_(DisplayEnv,screen) + OA_(DoubleBuffer,display,0)),
store_word(R_0, R_ScreenBuf, O_(DisplayEnv,vinterlace) + OA_(DoubleBuffer,display,0)),
/* display[1] = (0, 240, 320, 240); rest of struct zeroed. */
mac_store_rects2(R_0, R_ScreenY, R_ScreenX, R_ScreenY, R_ScreenBuf, O_(DisplayEnv,display_area) + OA_(DoubleBuffer,display,1)),
store_word(R_0, R_ScreenBuf, O_(DisplayEnv,screen) + OA_(DoubleBuffer,display,1)),
store_word(R_0, R_ScreenBuf, O_(DisplayEnv,vinterlace) + OA_(DoubleBuffer,display,1)),
mac_store_rects2(R_0, R_ScreenY, R_ScreenX, R_ScreenY, R_ScreenBuf, O_(DrawEnv,clip_area) + OA_(DoubleBuffer,draw,0)), /* draw[0].clip_area = (0, 240, 320, 240). C11's SetDefDrawEnv writes clip.y = y_arg. */
mac_store_v2s2( R_0, R_ScreenY, R_ScreenBuf, O_(DrawEnv,drawing_offset[0]) + OA_(DoubleBuffer,draw,0)), /* draw[0].drawing_offset[0] = (0, 240); C11 passes y_arg as ofs. */
mac_store_v2s2(R_ScreenX, R_ScreenY, R_ScreenBuf, O_(DrawEnv,clip_area.width) + OA_(DoubleBuffer,draw,1)),
/* draw[0].texture_window = (0, 0, 0, 0); two word-zeroes cover the full 8-byte tw field. */
store_word(R_0, R_ScreenBuf, O_(DrawEnv,texture_window.x) + OA_(DoubleBuffer,draw,0)),
store_word(R_0, R_ScreenBuf, O_(DrawEnv,texture_window.width) + OA_(DoubleBuffer,draw,0)),
store_word(R_0, R_ScreenBuf, O_(DrawEnv,drawing_offset[0].x) + OA_(DoubleBuffer,draw,1)),
store_word(R_0, R_ScreenBuf, O_(DrawEnv,texture_window.x) + OA_(DoubleBuffer,draw,1)),
store_word(R_0, R_ScreenBuf, O_(DrawEnv,texture_window.width) + OA_(DoubleBuffer,draw,1)),
/* draw[0].texture_page = 10 (gp0_tpage_default). C11 SetDefDrawEnv at C11_only.elf:0x8001273C writes the same 0x0A. . */
add_ui(R_T0, R_0, gp0_tpage_default),
store_half(R_T0, R_ScreenBuf, O_(DrawEnv,texture_page) + OA_(DoubleBuffer,draw,0)),
store_half(R_T0, R_ScreenBuf, O_(DrawEnv,texture_page) + OA_(DoubleBuffer,draw,1)),
/* draw[0] control bytes: flag_dither=1, flag_draw_on_display=1 (the dfe bit per psx-spx; libpsyx sets it via `SetDefDrawEnv`'s conditional at C11_only.elf:0x80012728), enable_auto_clear=1. Each byte is named;
* the previous `store_word(R_0, ..., +20)` overwrote all four with zero. */
add_ui(R_T0, R_0, 1),
store_byte(R_T0, R_ScreenBuf, O_(DrawEnv,flag_dither) + OA_(DoubleBuffer,draw,0)),
store_byte(R_T0, R_ScreenBuf, O_(DrawEnv,flag_draw_on_display) + OA_(DoubleBuffer,draw,0)),
store_byte(R_T0, R_ScreenBuf, O_(DrawEnv,enable_auto_clear) + OA_(DoubleBuffer,draw,0)),
store_byte(R_T0, R_ScreenBuf, O_(DrawEnv,flag_dither) + OA_(DoubleBuffer,draw,1)),
store_byte(R_T0, R_ScreenBuf, O_(DrawEnv,flag_draw_on_display) + OA_(DoubleBuffer,draw,1)),
store_byte(R_T0, R_ScreenBuf, O_(DrawEnv,enable_auto_clear) + OA_(DoubleBuffer,draw,1)),
/* draw[0].initial_bg_color = (r=7, g=7, b=7). */
add_ui(R_T0, R_0, 7),
mac_store_rgb8(R_T0,R_T0,R_T0, R_ScreenBuf, O_(DrawEnv,initial_bg_color) + OA_(DoubleBuffer,draw,0)),
mac_store_rgb8(R_T0,R_T0,R_T0, R_ScreenBuf, O_(DrawEnv,initial_bg_color) + OA_(DoubleBuffer,draw,1)),
mac_yield(),
};
enum {
R_IO_BaseAddr = R_T4 atom_reg, /* Caller-pinned: IO_BASE_ADDR = 0x1F800000 */
#define R_IO_BaseAddr_Code R_T4_Code
};
internal MipsAtom_(gp_screen_init) atom_info(atom_phase(screen_init), atom_reads(R_IO_BaseAddr)) {
store_word(R_0, R_IO_BaseAddr, GPIO_PORT1_OFFSET), /* GP1(00h) Reset */
mac_gcmd_push(gp1_word_ResetCmdBuffer(), R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET), /* GP1(01h) ClearFIFO */
mac_gcmd_push(gp1_word_AcknowledgeIRQ(), R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET), /* GP1(02h) AckIRQ */
mac_gcmd_push(gp1_word_DisplayOn(), R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET), /* GP1(03h) Display ON */
mac_gcmd_push(gp1_word_dma_to_gpu(), R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET), /* GP1(04h) DMADirection=2 (CPU→GPU). libpsyx's per-frame PutDrawEnv/DrawOTag use DMA2; without this the DMA queue never drains. */
mac_gcmd_push(gp1_word_StartDisplayArea(), R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET), /* GP1(05h) StartDisplayArea (X=0, Y=0) */
/* GP1: DisplayMode + Display Ranges */
mac_gcmd_push(gp1_word_display_mode_320x240_15bit_ntsc, R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET),
mac_gcmd_push(gp1_word_horizontal_range_ntsc, R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET),
mac_gcmd_push(gp1_word_vertical_range_ntsc, R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET),
/* GTE: SetGeomOffset (OFX, OFY) — ScreenRes_CenterX, ScreenRes_CenterY. */
load_upper_i(R_T5, ScreenRes_CenterX), gte_mv_to_ctrl_r(R_T5, gte_cr_OFX_Code),
load_upper_i(R_T5, ScreenRes_CenterY), gte_mv_to_ctrl_r(R_T5, gte_cr_OFY_Code),
/* GTE: SetGeomScreen (H) — CR26 (per PSX-SPX / libpsyx), value is the raw projection-plane distance, NOT shifted. */
add_ui(R_T5, R_0, ScreenZ), gte_mv_to_ctrl_r(R_T5, gte_cr_H_Code),
/* GP1: DisplayEnable — bit 0 = 0 (Display ON). */
mac_gcmd_push(gp1_word_DisplayOn(), R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET),
mac_yield(),
};
typedef Struct_(Binds_CubeTri) { typedef Struct_(Binds_CubeTri) {
U4 PrimCursor; U4 PrimCursor;
V4_S2* FaceCursor; V4_S2* FaceCursor;
@@ -24,7 +272,7 @@ typedef Struct_(Binds_CubeTri) {
}; };
internal MipsAtom_(rbind_cube_g4_face) atom_info(atom_bind(Binds_CubeTri), atom_phase(cube_g4) internal MipsAtom_(rbind_cube_g4_face) atom_info(atom_bind(Binds_CubeTri), atom_phase(cube_g4)
, atom_reads(R_TapePtr) , atom_reads(R_TapePtr)
, atom_writes(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase) , atom_writes(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase, R_TapePtr)
){ ){
/* Pop 4 arguments from the tape directly into the workspace registers */ /* Pop 4 arguments from the tape directly into the workspace registers */
load_word(R_PrimCursor, R_TapePtr, O_(Binds_CubeTri,PrimCursor)), load_word(R_PrimCursor, R_TapePtr, O_(Binds_CubeTri,PrimCursor)),
@@ -91,7 +339,7 @@ typedef Struct_(Binds_FloorTri) {
internal internal
MipsAtom_(rbind_floor_f3_face) atom_info(atom_bind(Binds_FloorTri), atom_phase(floor_f3) MipsAtom_(rbind_floor_f3_face) atom_info(atom_bind(Binds_FloorTri), atom_phase(floor_f3)
, atom_reads(R_TapePtr) , atom_reads(R_TapePtr)
, atom_writes(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase) , atom_writes(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase, R_TapePtr)
){ ){
/* Pop 4 arguments from the tape directly into the workspace registers */ /* Pop 4 arguments from the tape directly into the workspace registers */
load_word(R_PrimCursor, R_TapePtr, O_(Binds_FloorTri,PrimCursor)), load_word(R_PrimCursor, R_TapePtr, O_(Binds_FloorTri,PrimCursor)),
@@ -116,20 +364,22 @@ MipsAtom_(floor_f3_face) atom_info(atom_phase(floor_f3)
/* Culling (Branch forward if Backface) */ /* Culling (Branch forward if Backface) */
gte_mv_from_data_r(R_T0, C2_MAC0), gte_mv_from_data_r(R_T0, C2_MAC0),
nop, branch_le_zero(R_T0, atom_offset(culling, floor_f3_face_exit)), nop, // required gte -> cpu load-delay slot. nop, branch_le_zero(R_T0, atom_offset(culling, floor_f3_face_exit)), nop, // required gte -> cpu load-delay slot.
/* Format Primitive */ /* Format Primitive */
mac_gte_store_f3(), mac_gte_store_f3(),
/* Calculate Depth */ /* Calculate Depth */
gte_avg_sort_z3, gte_avg_sort_z3,
gte_mv_from_data_r(R_T1, C2_OTZ), gte_mv_from_data_r(R_T1, C2_OTZ),
/* Bounds Check OTZ < 2048 (Branch forward to skip insertion) */ /* Bounds Check OTZ < 2048 (Branch forward to skip insertion) */
add_ui( R_AT, R_0, OrderingTbl_Len), add_ui( R_AT, R_0, OrderingTbl_Len),
set_lt_u( R_AT, R_T1, R_AT), set_lt_u( R_AT, R_T1, R_AT),
branch_equal(R_AT, R_0, atom_offset(bounds_chk, floor_f3_face_exit)), nop, branch_equal(R_AT, R_0, atom_offset(bounds_chk, floor_f3_face_exit)), nop,
mac_format_f3_color(0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white) mac_format_f3_color(0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white)
mac_insert_ot_tag_f3(), /* Insert into Ordering Table Linked List */ mac_insert_ot_tag_f3(), /* Insert into Ordering Table Linked List */
add_ui_self(R_PrimCursor, S_(Poly_F3)), /* Advance Prim Cursor (5 words) */ add_ui_self(R_PrimCursor, S_(Poly_F3)), /* Advance Prim Cursor (5 words) */
// Note(Ed): No bounds checking, should be checked before atom runs. // Note(Ed): No bounds checking, should be checked before atom runs.
// end: branch(bounds_chk)
// end: branch(culling)
/* Advance Input Cursor & Yield (Both branch targets land here) */ /* Advance Input Cursor & Yield (Both branch targets land here) */
atom_label(floor_f3_face_exit) atom_label(floor_f3_face_exit)
@@ -151,4 +401,305 @@ internal MipsAtom_(sync_primitive_arena) atom_info(atom_bind(Binds_SyncPrimitive
mac_yield() mac_yield()
}; };
/* ----- pad_bios_snapshot -----
* Per-frame snapshot of one BIOS pad buffer into PadState.
* Decoder (branch ladder on raw[0] status + raw[1] id):
* 1. raw[0] == 0xFF -> Disconnected (buttons=0, axes=0x80)
* 2. raw[0]==0 && raw[1]==0 -> Pending (buttons=0, axes=0x80)
* 3. raw[1] == 0x41 -> Digital (buttons normalized; axes=0x80)
* 4. raw[1] == 0x53 -> AnalogStick (buttons normalized; axes from raw[4..7])
* 5. raw[1] in 0x7x -> AnalogPad (buttons normalized; axes from raw[4..7])
* 6. else -> Unsupported (buttons=0, axes=0x80)
*
* Buttons normalization: byte_swap16((~raw_buttons) & 0xFFFF).
* raw_buttons = load_half_u(raw, 2) = raw[2] | (raw[3] << 8).
* byte_swap16(x) = (x >> 8) | (x << 8); nor(x, R_0) = ~x. store_half truncates to 16 bits so the upper-16 mask is implicit in the store.
*
* Register use (atom-local; no wave-context touched):
* R_T0 = raw base (kept throughout; axes loads read raw[4..7] from R_T0)
* R_T1 = state base (kept throughout; all stores go through R_T1)
* R_T2 = raw[0] status (alive across the disc/pending/id dispatch, then dead)
* R_T3 = raw[1] id (alive across the id dispatch, then dead)
* R_T4 = scratch (shifts, compares, immediate loads, store values)
* R_T5 = scratch (parallel lui+ori for the 0x80808080 axes constant + byte-swap target)
*/
enum {
R_PadRaw = R_T0 atom_reg atom_type(U1),
R_PadState = R_T1 atom_reg,
R_RawStatus = R_T2 atom_reg,
R_RawId = R_T3 atom_reg,
};
typedef Struct_(Binds_PadBiosSnapshot) {
PadBiosRaw* raw;
PadState* state;
};
internal MipsAtom_(pad_bios_snapshot) atom_info(atom_bind(Binds_PadBiosSnapshot)
, atom_reads( R_PadRaw, R_PadState, R_RawStatus, R_RawId, R_T4, R_T5, R_TapePtr)
, atom_writes(R_PadRaw, R_PadState, R_RawStatus, R_RawId, R_T4, R_T5, R_TapePtr)
) {
/* === Bind consumption: T0 = raw, T1 = state, advance R_TapePtr by 8. */
load_word(R_PadRaw, R_TapePtr, O_(Binds_PadBiosSnapshot,raw)),
load_word(R_PadState, R_TapePtr, O_(Binds_PadBiosSnapshot,state)),
add_ui_self( R_TapePtr, S_(Binds_PadBiosSnapshot)),
/* === Read raw[0] (status) + raw[1] (id) */
load_byte_u(R_RawStatus, R_PadRaw, 0),
load_byte_u(R_RawId, R_PadRaw, 1),
atom_label(snap_root) /* === Case 1: Disconnected (status == 0xFF). */
add_ui(R_T4, R_0, 0xFF), branch_ne(R_RawStatus, R_T4, atom_offset(snap_root, skip_disconnected)),
/* BD-slot: pre-compute PadStatus_Disconnected. Branch reads R_T4=0xFF in EX before this WB completes.
* If branch NOT taken (fall through to pending/id_dispatch), R_T4 is overwritten by the next case body's add_ui — harmless. */
atom_label(disconnected) /* === Disconnected body. */
/* R_T4 = PadStatus_Disconnected from snap_root BD-slot. */
store_word(R_T4, R_PadState, O_(PadState,status)),
store_half(R_0, R_PadState, O_(PadState,buttons)),
/* axes = 0x80808080 (centered) — single sw writes the 4-byte axes block at offset 8 (left_x, left_y, right_x, right_y). */
load_upper_i(R_T4, 0x8080), or_i_self(R_T4, 0x8080),
store_word( R_T4, R_PadState, O_(PadState,left_x)),
store_byte( R_RawId, R_PadState, O_(PadState,id)),
branch_equal(R_0, R_0, atom_offset(disconnected, snap_end)), nop,
// TODO(Ed): Lua metaprogram: Support jump instruction here..
// jump(atom_offset(disconnected, snap_end)), nop,
atom_label(skip_disconnected)
/* === Case 2: Pending (status == 0 && id == 0)
* Combined check: if (status | id) != 0 then skip to id_dispatch.
* Falls through to the Pending case only when both are zero. */
or_u_self(R_RawStatus, R_RawId), branch_ne(R_RawStatus, R_0, atom_offset(case_2, id_dispatch)),
/* BD-slot: pre-compute PadStatus_Pending. Branch reads R_RawStatus in EX before this WB completes.
* If branch NOT taken (fall through to id_dispatch), R_T4 is overwritten by the digital/analog body add_ui — harmless. */
atom_label(pending) /* === Pending body */
/* R_T4 = PadStatus_Pending from case_2 BD-slot. */
store_word(R_T4, R_PadState, O_(PadState,status)),
store_half(R_0, R_PadState, O_(PadState,buttons)),
/* axes = 0x80808080 (centered) — single sw writes the 4-byte axes block at offset 8 (left_x, left_y, right_x, right_y). */
load_upper_i(R_T4, 0x8080), or_i_self(R_T4, 0x8080),
store_word( R_T4, R_PadState, O_(PadState,left_x)),
store_byte( R_RawId, R_PadState, O_(PadState,id)),
branch_equal(R_0, R_0, atom_offset(pending, snap_end)), nop,
// TODO(Ed): Lua metaprogram: Support jump instruction here..
// jump(atom_offset(pending, snap_end)), nop,
atom_label(id_dispatch) /* === Case 3-6: ID dispatch */
add_ui(R_T4, R_0, 0x41), branch_ne(R_RawId, R_T4, atom_offset(id_dispatch, try_analog_stick)),
/* BD-slot: pre-compute PadStatus_Digital. Branch reads R_RawId in EX before this WB completes.
* If branch NOT taken (fall through to try_analog_stick), R_T4 is overwritten by the analog body add_ui. */
/* === Digital body (status, buttons normalize, axes=0x80, id, branch. */
/* R_T4 = PadStatus_Digital from id_dispatch BD-slot. */
store_word( R_T4, R_PadState, O_(PadState,status)),
load_half_u(R_T4, R_PadRaw, 2 * S_(U1)),
/* Fill R_T4's load-delay slot with the 0x80808080 axes constant into R_T5
* (R_T5 is dead on this path; it's only consumed at the analog_pad range check). */
load_upper_i(R_T5, 0x8080), or_i_self(R_T5, 0x8080),
nor_u( R_T4, R_T4, R_0), /* raw_buttons is already in host bit order; no swap needed */
store_half( R_T4, R_PadState, O_(PadState,buttons)),
/* axes = 0x80808080 (centered) — single sw writes the 4-byte axes block at offset 8 (left_x, left_y, right_x, right_y). */
store_word( R_T5, R_PadState, O_(PadState,left_x)),
add_ui( R_T4, R_0, 0x41),
store_byte( R_T4, R_PadState, O_(PadState,id)),
branch_equal(R_0, R_0, atom_offset(id_dispatch, snap_end)), nop,
// TODO(Ed): Lua metaprogram: Support jump instruction here..
// jump(atom_offset(id_dispatch, snap_end)), nop,
atom_label(try_analog_stick) /* === Case 4: AnalogStick (id == 0x53)*/
add_ui(R_T4, R_0, 0x53), branch_ne(R_RawId, R_T4, atom_offset(try_analog_stick, try_analog_pad)),
/* BD-slot: pre-compute PadStatus_AnalogStick. Branch reads R_RawId in EX before this WB completes.
* If branch NOT taken (fall through to try_analog_pad), R_T4 is overwritten by the analog_pad body add_ui. */
atom_label(analog_stick) /* === AnalogStick body
* Axes are loaded as two halfwords: raw[6..7] → left_xy (sh at offset 8), raw[4..5] → right_xy (sh at offset 10).
* R_T5 holds left_xy / id-value in turn (it's dead on this path — only consumed at the analog_pad range check). */
/* R_T4 = PadStatus_AnalogStick from try_analog_stick BD-slot. */
store_word( R_T4, R_PadState, O_(PadState,status)),
load_half_u( R_T4, R_PadRaw, 2 * S_(U1)), /* R_T4 = raw_buttons */
load_half_u( R_T5, R_PadRaw, 6 * S_(U1)), /* R_T5 = left_xy; fills R_T4's load-delay slot (doesn't read R_T4) */
nor_u( R_T4, R_T4, R_0), /* R_T4 = ~raw_buttons */
store_half( R_T4, R_PadState, O_(PadState,buttons)),
load_half_u( R_T4, R_PadRaw, 4 * S_(U1)), /* R_T4 = right_xy; fills R_T5's load-delay slot */
store_half( R_T5, R_PadState, O_(PadState,left_x)), /* R_T5 settled, store left_xy */
store_half( R_T4, R_PadState, O_(PadState,right_x)),
add_ui( R_T5, R_0, 0x53), /* R_T5 = id value (clobbers left_xy, already stored) */
store_byte( R_T5, R_PadState, O_(PadState,id)),
branch_equal(R_0, R_0, atom_offset(analog_stick, snap_end)), nop,
// TODO(Ed): Lua metaprogram: Support jump instruction here..
// jump(atom_offset(analog_stick, snap_end)), nop,
atom_label(try_analog_pad) /* === Case 5-6: AnalogPad (id & 0xF0 == 0x70) */
and_i( R_T4, R_RawId, 0xF0),
add_ui( R_T5, R_0, 0x70),
branch_ne(R_T4, R_T5, atom_offset(try_analog_pad, try_unsupported)),
/* BD-slot: pre-compute PadStatus_AnalogPad. Branch reads R_T4 in EX before this WB completes.
* If branch NOT taken (fall through to try_unsupported), R_T4 is overwritten by the unsupported body add_ui. */
atom_label(analog_pad) /* === AnalogPad body
* Same shape as AnalogStick with AnalogPad status. R_T5 holds left_xy (it's dead on this path). */
/* R_T4 = PadStatus_AnalogPad from try_analog_pad BD-slot. */
store_word( R_T4, R_PadState, O_(PadState,status)),
load_half_u(R_T4, R_PadRaw, 2 * S_(U1)), /* R_T4 = raw_buttons */
load_half_u(R_T5, R_PadRaw, 6 * S_(U1)), /* R_T5 = left_xy; fills R_T4's load-delay slot */
nor_u( R_T4, R_T4, R_0), /* R_T4 = ~raw_buttons */
store_half( R_T4, R_PadState, O_(PadState,buttons)),
load_half_u(R_T4, R_PadRaw, 4 * S_(U1)), /* R_T4 = right_xy; fills R_T5's load-delay slot */
store_half( R_T5, R_PadState, O_(PadState,left_x)), /* R_T5 settled, store left_xy */
store_half( R_T4, R_PadState, O_(PadState,right_x)),
store_byte( R_RawId, R_PadState, O_(PadState,id)),
branch_equal(R_0, R_0, atom_offset(analog_pad, snap_end)), nop,
// TODO(Ed): Lua metaprogram: Support jump instruction here..
// jump(atom_offset(analog_pad, snap_end)), nop,
atom_label(try_unsupported) /* === Case 7: Unsupported — fall through from the AnalogPad range-check miss. */
add_ui( R_T4, R_0, PadStatus_Unsupported),
store_word(R_T4, R_PadState, O_(PadState,status)),
store_half(R_0, R_PadState, O_(PadState,buttons)),
/* axes = 0x80808080 (centered) — single sw writes the 4-byte axes block at offset 8 (left_x, left_y, right_x, right_y). */
load_upper_i(R_T4, 0x8080), or_i_self(R_T4, 0x8080),
store_word( R_T4, R_PadState, O_(PadState,left_x)),
add_ui( R_T4, R_0, 0xFF), /* 0xFF sentinel: "unknown id" */
store_byte( R_T4, R_PadState, O_(PadState,id)),
/* Fall through to snap_end. */
atom_label(snap_end)
mac_yield(),
};
/* ----- pad_apply_input -----
* Reads pad[0].buttons + pad[0].left_x;
* Applies the input-semantics deltas to cube_rot.y + floor_rot.y:
* - D-pad Left: cube_rot.y += 30, floor_rot.y += 5
* - D-pad Right: cube_rot.y -= 30, floor_rot.y -= 5
* - Analog stick X (dead zone 0x70..0x90):
* cube delta = (0x80 - left_x) >> 2 (range approx -32..+32)
* floor delta = (0x80 - left_x) >> 5 (range approx -4..+4)
* - D-pad + analog deltas add when used together.
*
* Convention:
* pad_state = 0 means no buttons active.
* The fail-safe zero-button value flows through unchanged, so a disconnected/fresh pad produces no rotation.
* The branch_le_zero pattern below matches the existing pad_input_demo convention (atom body lines 248/257).
*
* Signed-delta trick:
* load_byte_u zero-extends left_x to 32 bits; sub_u from 0x80 wraps to a SIGNED two's-complement value in the negative range;
* shift_aright (sra) then correctly sign-extends the shift for both positive (left_x < 0x80) and negative (left_x > 0x80) cases.
* Digital pads publish left_x = 0x80 → delta = 0 → no rotation, so the analog step is naturally a no-op for digital controllers.
*/
typedef Struct_(Binds_PadApplyInput) {
PadState* state;
V3_S2* cube_rot;
V3_S2* floor_rot;
};
enum {
R_PadStateT5 = R_T5 atom_reg,
R_CubeRot = R_T1 atom_reg,
R_FloorRot = R_T2 atom_reg,
};
internal MipsAtom_(pad_apply_input) atom_info(atom_bind(Binds_PadApplyInput)
, atom_reads(R_T0, R_CubeRot, R_FloorRot, R_T3, R_T4, R_PadStateT5, R_TapePtr)
, atom_writes( R_CubeRot, R_FloorRot)
) {
/* Pop Binds from tape (state, cube_rot, floor_rot) */
load_word(R_PadStateT5, R_TapePtr, O_(Binds_PadApplyInput,state)),
load_word(R_CubeRot, R_TapePtr, O_(Binds_PadApplyInput,cube_rot)),
load_word(R_FloorRot, R_TapePtr, O_(Binds_PadApplyInput,floor_rot)),
add_ui_self( R_TapePtr, S_(Binds_PadApplyInput)),
/* Load pad[0].buttons into R_T0. */
load_word(R_T0, R_PadStateT5, O_(PadState,buttons)), nop,
// Note(Ed): Potential op with delay slot?
/* D-pad Left: cube_rot.y += 30, floor_rot.y += 5. */
and_i(R_T3, R_T0, pad0_(Pad_Left)), branch_le_zero(R_T3, atom_offset(dpad_left, exit_dpad_left)),
load_half( R_T4, R_CubeRot, O_(V3_S2,y)), /* BD-slot */
load_half( R_T3, R_FloorRot, O_(V3_S2,y)),
add_si( R_T4, R_T4, 30),
add_si( R_T3, R_T3, 5),
store_half(R_T4, R_CubeRot, O_(V3_S2,y)),
store_half(R_T3, R_FloorRot, O_(V3_S2,y)),
atom_label(exit_dpad_left)
/* D-pad Right: cube_rot.y -= 30, floor_rot.y -= 5. */
and_i(R_T3, R_T0, pad0_(Pad_Right)), branch_le_zero(R_T3, atom_offset(dpad_right, exit_dpad_right)),
load_half( R_T4, R_CubeRot, O_(V3_S2,y)), /* BD-slot */
load_half( R_T3, R_FloorRot, O_(V3_S2,y)),
add_si( R_T4, R_T4, -30),
add_si( R_T3, R_T3, -5),
store_half(R_T4, R_CubeRot, O_(V3_S2,y)),
store_half(R_T3, R_FloorRot, O_(V3_S2,y)),
atom_label(exit_dpad_right)
/* Analog left-stick X: dead zone 0x70..0x90.
* Cube delta = (0x80 - left_x) >> 2; floor delta = (0x80 - left_x) >> 5. */
load_byte_u(R_T3, R_PadStateT5, O_(PadState,left_x)),
/* Dead-zone check: skip analog if left_x in [0x70, 0x90] inclusive. Outside dead zone on LOW side: left_x < 0x70 (strictly).
* set_lt_u(R_T4, R_T3, R_T4=0x70) → R_T4 = (left_x < 0x70) ? 1 : 0. */
add_ui(R_T4, R_0, 0x70), set_lt_u(R_T4, R_T3, R_T4), branch_ne(R_T4, R_0, atom_offset(dead_zone_low_check, dead_low_active)),
add_ui(R_T4, R_0, 0x80), /* BD-slot: pre-load 0x80 for dead_low_active */
atom_label(dead_check_upper)
/* left_x >= 0x70 → check upper bound. */
load_byte_u(R_T3, R_PadStateT5, O_(PadState,left_x)), /* reload */
add_ui( R_T4, R_0, 0x90),
/* R_T4 = (0x90 < left_x) ? 1 : 0 → (left_x > 0x90) ? 1 : 0 */
set_lt_u(R_T4, R_T4, R_T3), branch_ne(R_T4, R_0, atom_offset(dead_zone_high_check, dead_high_active)),
add_ui( R_T4, R_0, 0x80), /* BD-slot: pre-load 0x80 for dead_high_active */
branch_equal(R_0, R_0, atom_offset(dead_zone_skip, exit_stick)), nop,
/* Fall-through = left_x in [0x70, 0x90] (dead zone); skip analog entirely. */
// TODO(Ed): Lua metaprogram: Support jump instruction here..
// jump(atom_offset(dead_zone_skip, exit_stick)), nop,
atom_label(dead_low_active)
/* R_T3 = left_x (from line 632 lbu; not clobbered between dead_zone_low_check branch + its BD-slot `add_ui R_T4, 0x80`).
* The earlier `load_byte_u(R_T3, ...)` reload was redundant and introduced a load-use hazard on the next `sub_u`.
* R_T4 = 0x80 from the BD-slot of `dead_zone_low_check`'s branch_ne. */
sub_u( R_T3, R_T4, R_T3), /* R_T3 = 0x80 - left_x */
/* delta = 0x80 - left_x (positive). */
/* R_T4 = cube_delta */
shift_aright(R_T4, R_T3, 2),
load_half( R_T0, R_CubeRot, O_(V3_S2,y)),
nop,
add_u( R_T0, R_T0, R_T4),
store_half( R_T0, R_CubeRot, O_(V3_S2,y)),
/* R_T4 = floor_delta — moved into the load-delay slot of the floor load below (fills the 1-instruction gap;
* doesn't read R_T0; R_T4 settles by the subsequent add_u). */
load_half( R_T0, R_FloorRot, O_(V3_S2,y)),
shift_aright(R_T4, R_T3, 5),
add_u( R_T0, R_T0, R_T4),
store_half( R_T0, R_FloorRot, O_(V3_S2,y)),
branch_equal(R_0, R_0, atom_offset(end_low, exit_stick)), nop,
// TODO(Ed): Lua metaprogram: Support jump instruction here..
// jump(atom_offset(end_low, exit_stick)), nop,
atom_label(dead_high_active)
/* R_T3 = left_x (from line 641 lbu in dead_check_upper; not clobbered between dead_zone_high_check branch + its BD-slot `add_ui R_T4, 0x80`).
* The earlier `load_byte_u(R_T3, ...)` reload was redundant and introduced a load-use hazard on the next `sub_u`.
* R_T4 = 0x80 from the BD-slot of `dead_zone_high_check`'s branch_ne. */
sub_u( R_T3, R_T4, R_T3),
/* delta = 0x80 - left_x (signed negative). */
shift_aright(R_T4, R_T3, 2), /* R_T4 = cube_delta (signed) */
load_half( R_T0, R_CubeRot, O_(V3_S2,y)),
nop,
add_u( R_T0, R_T0, R_T4),
store_half( R_T0, R_CubeRot, O_(V3_S2,y)),
/* R_T4 = floor_delta (signed) — moved into the load-delay slot of the floor load below. */
load_half( R_T0, R_FloorRot, O_(V3_S2,y)),
shift_aright(R_T4, R_T3, 5),
add_u( R_T0, R_T0, R_T4),
store_half( R_T0, R_FloorRot, O_(V3_S2,y)),
atom_label(exit_stick)
mac_yield(),
};
#pragma endregion Baked Atoms #pragma endregion Baked Atoms
+3
View File
@@ -0,0 +1,3 @@
#ifdef INTELLISENSE_DIRECTIVES
# include "psyq.h"
#endif
+103
View File
@@ -0,0 +1,103 @@
#ifdef INTELLISENSE_DIRECTIVES
# pragma once
# include "duffle/dsl.h"
# include "duffle/math.h"
# include "duffle/gp.h"
#endif
typedef Struct_(DrawEnv_Packed) { U4 tag; U4 code[15]; };
typedef Struct_(DrawEnv) {
Rect_S2 clip_area;
V2_S2 drawing_offset[2];
Rect_S2 texture_window;
S2 texture_page;
B1 flag_dither;
B1 flag_draw_on_display;
B1 enable_auto_clear;
RGB8 initial_bg_color;
DrawEnv_Packed dr_env; // reserved
};
typedef Struct_(DisplayEnv) {
Rect_S2 display_area;
Rect_S2 screen;
B1 vinterlace;
B1 color24;
B1 pad0;
B1 pad1;
};
typedef Array_(DrawEnv, 2);
typedef Array_(DisplayEnv, 2);
typedef Struct_(DoubleBuffer) {
A2_DrawEnv draw;
A2_DisplayEnv display;
};
DisplayEnv* displayenv_init(DisplayEnv* env, S4 x, S4 y, S4 w, S4 h) asm("SetDefDispEnv");
DrawEnv* drawenv_init (DrawEnv* env, S4 x, S4 y, S4 w, S4 h) asm("SetDefDrawEnv");
DisplayEnv* displayenv_put(DisplayEnv* env) asm("PutDispEnv");
DrawEnv* drawenv_put (DrawEnv* env) asm("PutDrawEnv");
U4 geom_init(void) asm("InitGeom");
void geom_set_offset(U4 x, U4 y) asm("SetGeomOffset");
void geom_set_screen(U4 h) asm("SetGeomScreen");
U4* orderingtbl_clear_reverse(U4* ot, U4 len) asm("ClearOTagR");
U4 reset_graph(U4 mode) asm("ResetGraph");
void set_display_enabled(U4 mask) asm("SetDispMask");
U4 draw_sync(U4 mode) asm("DrawSync");
U4 vsync(U4 mode) asm("VSync");
void draw_orderingtbl(U4* buf) asm("DrawOTag");
typedef Struct_(Tile) {
U4 tag;
RGB8 color;
B1 code;
Rect_S2 rect;
};
/*
Linear Algebra
*/
M3_S2* m3s2_rotation (V3_S2* vec, M3_S2* mat) asm("RotMatrix");
M3_S2* m3s2_translation(M3_S2* mat, V3_S4* vec) asm("TransMatrix");
M3_S2* m3s2_scale (M3_S2* mat, V3_S4* vec) asm("ScaleMatrix");
// Rotation, Translation, Perspective
S4 rtp_v3s2_raw(V3_S2* vec, S4* xy, S4* pp, S4* flag) asm("RotTransPers");
FI_ S4 rtp_v3s2(V3_S2* vec, V2_S2* xy, A2_S2* pp, S4* flag) { return rtp_v3s2_raw(vec, C_(S4*R_, & xy->x), C_(S4*R_, pp), r_(flag)); }
S4 rtp_avg_nclip_a3_v3s2_raw(V3_S2* v0, V3_S2* v1, V3_S2* v2, S4* xy1, S4* xy2, S4* xy3, S4* pp, S4* otz, S4* flag) asm("RotAverageNclip3");
FI_ S4 rtp_avg_nclip_a3_v3s2(
V3_S2* v0, V3_S2* v1, V3_S2* v2,
V2_S2* xy0, V2_S2* xy1, V2_S2* xy2,
A2_S2* pp, S4* otz, S4* flag
){
return rtp_avg_nclip_a3_v3s2_raw(
v0, v1, v2,
C_(S4*R_, xy0), C_(S4*R_, xy1), C_(S4*R_, xy2),
C_(S4*R_, pp), C_(S4*R_, otz), C_(S4*R_, flag)
);
}
S4 rtp_avg_nclip_a4_v3s2_raw(V3_S2* v0, V3_S2* v1, V3_S2* v2, V3_S2* v3, S4* xy1, S4* xy2, S4* xy3, S4* xy4, S4* pp, S4* otz, S4* flag) asm("RotAverageNclip4");
FI_ S4 rtp_avg_nclip_a4_v3s2(
V3_S2* v0, V3_S2* v1, V3_S2* v2, V3_S2* v3,
V2_S2* xy0, V2_S2* xy1, V2_S2* xy2, V2_S2* xy3,
A2_S2* pp, S4* otz, S4* flag
){
return rtp_avg_nclip_a4_v3s2_raw(
v0, v1, v2, v3,
C_(S4*R_, xy0), C_(S4*R_, xy1), C_(S4*R_, xy2), C_(S4*R_, xy3),
C_(S4*R_, pp), C_(S4*R_, otz), C_(S4*R_, flag)
);
}
void gte_matrix_set_rotation (M3_S2* mat) asm("SetRotMatrix");
void gte_matrix_set_translation(M3_S2* mat) asm("SetTransMatrix");
+659
View File
@@ -0,0 +1,659 @@
#if 0 /* ac_pad_sio_write_pad_state — superseded by pad_bios_snapshot */
/* ============================================================
* raw_sio_pad_poll_20260802 — superseded by bios_pad_buffer_snapshot_20260803.
* The doomed raw-SIO production atoms (ac_pad_sio_write_pad_state,
* pad_sio_init, pad_sio_step, pad_sio_diag_pin, pad_sio_diag_byte_exchange)
* reference symbols that were removed from code/duffle/pad.h during
* Phase 1. Each is wrapped in a narrow `#if 0` so the C compile skips
* the body while the source-as-written text stays in place for the
* Phase 5.1 deletion pass. The wrap is removed (and the bodies are
* deleted) by Phase 5.1 of this track.
* ============================================================ */
* Writes the per-port PadState in 5 instructions plus 4 store_word calls (status,
* buttons, left_x/y/right_x/right_y packed, attempt). The provisional decode publishes
* 0x0000FFFF buttons + centered axes on every path until response-byte decode lands.
*
* Args:
* status_val - the PadSioStatus enum value to publish
* state_ptr_reg - the PadState* base (R_PadState at the call site)
* scratch_reg - scratch register for the value being stored (e.g., R_T0)
*
* Emits 9 instructions (status/buttons/axes/attempt stores plus the
* two-instruction zero-extended buttons load).
*/
FI_ Slice_MipsCode ac_pad_sio_write_pad_state(U4 status_val, U4 state_ptr_reg, U4 scratch_reg)
MipsAtomComp_Proc_(ac_pad_sio_write_pad_state, {
add_ui(scratch_reg, R_0, status_val),
store_word(scratch_reg, state_ptr_reg, O_(PadState,status)),
/* FIX 2026-08-02: buttons = 0x0000FFFF = "no buttons pressed" in
* libetc convention. Build it with LUI + ORI so addiu does not
* sign-extend 0xFFFF to 0xFFFFFFFF. */
load_upper_i(scratch_reg, 0x0000),
or_i(scratch_reg, scratch_reg, 0xFFFF),
store_word(scratch_reg, state_ptr_reg, O_(PadState,buttons)),
add_ui(scratch_reg, R_0, 0x80808080),
store_word(scratch_reg, state_ptr_reg, O_(PadState,left_x)),
add_ui(scratch_reg, R_0, 0),
store_word(scratch_reg, state_ptr_reg, O_(PadState,attempt))
})
#endif /* end ac_pad_sio_write_pad_state wrap */
/* ----- pad_sio_init -----
* Boot-time SIO0 init. Caller pins R_T6 = sio_base_addr0.
* Issues SIO CTRL=0x0040 (reset), MODE=0x000D, BAUD=0x0088.
* (Phase 2 fills the body.)
*/
#if 0 /* pad_sio_init — superseded by pad_bios_init_start (Phase 1.3) */
internal MipsAtom_(pad_sio_init) atom_info(atom_phase(pad_init)
, atom_reads(R_T5, R_T6)
, atom_writes(R_T5, R_T6)
) {
/* FIX 2026-08-02: explicitly load the KSEG1 base into R_T6 at the top of
* the atom body. The rgcc(R_PadSioBase) binding in main() pins R_T6 = base
* when main() runs, but $12 is caller-saved per the O32 ABI — when tape_run
* is invoked, R_T6 is fair game. The atom body cannot rely on the value. */
load_upper_i(R_T6, pad_IO_KSEG1_BASE >> 16), /* R_T6 high 16 = 0xBF80 */
or_i(R_T6, R_T6, pad_IO_KSEG1_BASE & 0xFFFF), /* R_T6 = 0xBF800000 */
/* SIO CTRL = 0x0040 (reset) */
add_ui(R_T5, R_0, pad_SIO_CTRL_RESET),
store_half(R_T5, R_T6, pad_SIO_CTRL_OFFSET),
/* SIO MODE = 0x000D (MUL1, 8-bit, no parity, idle-high) */
add_ui(R_T5, R_0, pad_SIO_MODE_INIT),
store_half(R_T5, R_T6, pad_SIO_MODE_OFFSET),
/* SIO BAUD = 0x0088 (~250 kHz) */
add_ui(R_T5, R_0, pad_SIO_BAUD_INIT),
store_half(R_T5, R_T6, pad_SIO_BAUD_OFFSET),
mac_yield(),
};
#endif /* end pad_sio_init wrap */
/* ----- pad_sio_step -----
* Per-frame bounded raw-SIO transaction. Reads PadState pointers + SIO
* base addresses from Binds_PadSioStep; writes per-port status +
* buttons + axes into smem.pad[0..1].
* Body shape (per spec §"Transaction model (per port, per pad_sio_step)"):
* port 0: CTRL=CLEANUP → settle → CTRL=port-select → settle → exchange 5
* bytes (addr + 0x42 0x00 0x00 0x00) → decode → write PadState[0]
* → CTRL=CLEANUP.
* port 1: swap scratch regs (sio_base_addr1 → R_PadSioBase, state1 →
* R_PadState) → mirror port 0 sequence.
*
* Bounded-loop semantics: every countdown is wrapped in
* add_ui_self(R_T1, -1) + branch_ne(R_T1, R_0, ...)
* with a known maximum (pad_SIO_SETTLE_BEFORE_TX=1000, pad_SIO_SETTLE_AFTER_TX=2000,
* pad_SIO_WAIT_BUDGET=4096). The static-analysis pass currently reports
* has_loops = true; the follow-up metaprogram track that learns modeled-bounded
* loops is out of scope here (per spec §"Risks").
*
* Scratch register strategy:
* R_PadStatus = R_T4 — RESERVED for port-1 swap (holds state1)
* R_PadCountdown = R_T5 — RESERVED for port-1 swap (holds sio_base_addr1)
* R_T0 — byte-exchange value + STAT read (clobbered freely)
* R_T1 — countdown budget (clobbered freely)
* R_PadState = R_T7 — PadState* (preserved for PadState writes)
* R_PadSioBase = R_T6 — SIO base (preserved through the port)
*
* Response decode (Task 3.1 teaching scope):
* - status = PadSioStatus_Digital (hardcoded)
* - buttons = 0xFFFF (no buttons pressed in the provisional libetc
* convention; full response-byte decode is follow-up)
* - axes = 0x80808080 (centered: left_x=0x80, left_y=0x80,
* right_x=0x80, right_y=0x80)
* - attempt = 0
* - DualShock handshake (0x43 0x01 → 0x44 0x01 0x03 → 0x43 0x00) is
* follow-up scope; the hardcoded digital decode is a placeholder.
*
* Both ports raise /CS (CTRL = pad_SIO_CTRL_CLEANUP) before exit. Both ports
* treat response timeout as PadSioStatus_Disconnected per the spec §"Failure
* handling" + the canonical per-port timeout semantics.
*/
#if 0 /* pad_sio_step — superseded by pad_bios_snapshot (Phase 2.1) */
internal MipsAtom_(pad_sio_step) atom_info(atom_bind(Binds_PadSioStep)
, atom_reads(R_TapePtr, R_PadSioBase, R_PadState, R_PadStatus, R_PadCountdown)
, atom_writes(R_PadStatus, R_PadCountdown)
) {
/* FIX 2026-08-02: explicitly load KSEG1 base into R_PadSioBase (R_T6) at the
* top. The rgcc() binding in main() does NOT survive the tape_run call
* because R_T6 is caller-saved per the O32 ABI. The pad_sio_init atom
* (also in the per-frame tape) reloads R_T6 separately. */
load_upper_i(R_PadSioBase, pad_IO_KSEG1_BASE >> 16),
or_i(R_PadSioBase, R_PadSioBase, pad_IO_KSEG1_BASE & 0xFFFF),
/* Pop Binds from tape (in Binds_PadSioStep declaration order) */
load_word(R_PadState, R_TapePtr, O_(Binds_PadSioStep,state0)),
load_word(R_PadStatus, R_TapePtr, O_(Binds_PadSioStep,state1)), /* reserved for port-1 swap */
load_word(R_PadSioBase, R_TapePtr, O_(Binds_PadSioStep,sio_base_addr0)),
load_word(R_PadCountdown, R_TapePtr, O_(Binds_PadSioStep,sio_base_addr1)), /* reserved for port-1 swap */
add_ui_self(R_TapePtr, S_(Binds_PadSioStep)),
/* ============== PORT 0 TRANSACTION ============== */
/* Use R_T0 (byte value / STAT read) + R_T1 (countdown) as scratch.
* R_PadStatus (state1) + R_PadCountdown (sio_base_addr1) are preserved
* through the port-0 body and swapped into R_PadSioBase + R_PadState
* at atom_offset(port1_start, ...) below. */
/* 1. Cleanup: CTRL = 0x0010 (raise /CS, clear stale status) */
add_ui(R_T0, R_0, pad_SIO_CTRL_CLEANUP),
store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET),
/* Bounded by pad_SIO_SETTLE_BEFORE_TX = 1000 iterations. */
add_ui(R_T1, R_0, pad_SIO_SETTLE_BEFORE_TX),
atom_label(settle_pre_port0)
nop, /* BD slot */
add_ui_self(R_T1, -1),
branch_ne(R_T1, R_0, atom_offset(settle_pre_port0, settle_pre_port0)),
/* 2. Port-select: CTRL = 0x0003 (TX enable + DTR /CS) for port 0 */
add_ui(R_T0, R_0, pad_SIO_CTRL_TX_ENABLE),
or_i(R_T0, R_T0, pad_SIO_CTRL_DTR_CS), /* set /CS line low */
store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET),
/* Bounded by pad_SIO_SETTLE_AFTER_TX = 2000 iterations. */
add_ui(R_T1, R_0, pad_SIO_SETTLE_AFTER_TX),
atom_label(settle_post_port0)
nop,
add_ui_self(R_T1, -1),
branch_ne(R_T1, R_0, atom_offset(settle_post_port0, settle_post_port0)),
/* 3. Address byte (0x01) — send + RX-ready wait + read response + RX-drain confirmation */
add_ui(R_T0, R_0, pad_PROTO_ADDR),
store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ack0_port0)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_ne(R_T0, R_0, atom_offset(wait_ack0_port0, ack0_received_port0)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ack0_port0)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ack0_port0, wait_ack0_port0)),
/* RX timeout → mark disconnected; skip to port 1 */
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port0_from_ack0)
branch_equal(R_0, R_0, atom_offset(skip_port0_from_ack0, port1_start)),
atom_label(ack0_received_port0)
/* Read open-bus response byte 0 — discard per docs/psx-spx §controllersandmemorycards.md */
load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
/* Confirm RX FIFO drained before sending byte 1. Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ackrel0_port0)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_equal(R_T0, R_0, atom_offset(wait_ackrel0_port0, ack_released_port0)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ackrel0_port0)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel0_port0, wait_ackrel0_port0)),
/* RX-drain timeout → disconnected; skip to port 1 */
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port0_from_ackrel0)
branch_equal(R_0, R_0, atom_offset(skip_port0_from_ackrel0, port1_start)),
atom_label(ack_released_port0)
/* === Byte 1 (port 0): send 0x42 (cmd read) + RX-ready wait + read response + RX-drain confirmation === */
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T0, R_0, pad_PROTO_CMD_READ),
store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ack1_port0)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_ne(R_T0, R_0, atom_offset(wait_ack1_port0, ack1_received_port0)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ack1_port0)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ack1_port0, wait_ack1_port0)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port0_from_ack1)
branch_equal(R_0, R_0, atom_offset(skip_port0_from_ack1, port1_start)),
atom_label(ack1_received_port0)
/* Read response ID byte — discarded for teaching scope (decode hardcoded). */
load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
/* RX FIFO drain wait. Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ackrel1_port0)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_equal(R_T0, R_0, atom_offset(wait_ackrel1_port0, ack_released1_port0)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ackrel1_port0)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel1_port0, wait_ackrel1_port0)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port0_from_ackrel1)
branch_equal(R_0, R_0, atom_offset(skip_port0_from_ackrel1, port1_start)),
atom_label(ack_released1_port0)
/* === Byte 2 (port 0): send 0x00 + RX-ready wait + read response + RX-drain confirmation === */
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T0, R_0, 0x00),
store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ack2_port0)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_ne(R_T0, R_0, atom_offset(wait_ack2_port0, ack2_received_port0)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ack2_port0)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ack2_port0, wait_ack2_port0)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port0_from_ack2)
branch_equal(R_0, R_0, atom_offset(skip_port0_from_ack2, port1_start)),
atom_label(ack2_received_port0)
load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ackrel2_port0)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_equal(R_T0, R_0, atom_offset(wait_ackrel2_port0, ack_released2_port0)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ackrel2_port0)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel2_port0, wait_ackrel2_port0)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port0_from_ackrel2)
branch_equal(R_0, R_0, atom_offset(skip_port0_from_ackrel2, port1_start)),
atom_label(ack_released2_port0)
/* === Byte 3 (port 0): send 0x00 + RX-ready wait + read response + RX-drain confirmation === */
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T0, R_0, 0x00),
store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ack3_port0)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_ne(R_T0, R_0, atom_offset(wait_ack3_port0, ack3_received_port0)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ack3_port0)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ack3_port0, wait_ack3_port0)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port0_from_ack3)
branch_equal(R_0, R_0, atom_offset(skip_port0_from_ack3, port1_start)),
atom_label(ack3_received_port0)
load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ackrel3_port0)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_equal(R_T0, R_0, atom_offset(wait_ackrel3_port0, ack_released3_port0)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ackrel3_port0)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel3_port0, wait_ackrel3_port0)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port0_from_ackrel3)
branch_equal(R_0, R_0, atom_offset(skip_port0_from_ackrel3, port1_start)),
atom_label(ack_released3_port0)
/* === Byte 4 (FINAL, port 0): send 0x00 + RX-not-empty wait + read final byte === */
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T0, R_0, 0x00),
store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_rx4_port0)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_ne(R_T0, R_0, atom_offset(wait_rx4_port0, rx4_received_port0)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_rx4_port0)
branch_ne(R_T1, R_0, atom_offset(continue_wait_rx4_port0, wait_rx4_port0)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port0_from_rx4)
branch_equal(R_0, R_0, atom_offset(skip_port0_from_rx4, port1_start)),
atom_label(rx4_received_port0)
load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), /* discard final byte */
/* === RESPONSE DECODE (hardcoded for teaching scope) ===
* Per the plan §"Phase 3 task 3.1" + spec §"Architecture":
* - Full decode (buttons/axes from response bytes) is follow-up scope.
* - Teaching scope: hardcode digital poll response.
* status = PadSioStatus_Digital
* buttons = 0x0000FFFF (no buttons pressed — placeholder)
* axes = 0x80808080 (left_x=0x80, left_y=0x80, right_x=0x80, right_y=0x80)
* attempt = 0
*/
atom_label(decode_port0)
mac_pad_sio_write_pad_state(PadSioStatus_Digital, R_PadState, R_T0),
/* /CS cleanup: raise /CS, clear stale status before exiting port 0. */
add_ui(R_T0, R_0, pad_SIO_CTRL_CLEANUP),
store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET),
/* ============== PORT 1 SETUP ============== */
/* Swap: R_PadCountdown holds sio_base_addr1; R_PadStatus holds state1. */
atom_label(port1_start)
add_u(R_PadSioBase, R_0, R_PadCountdown), /* sio_base_addr1 → R_PadSioBase */
add_u(R_PadState, R_0, R_PadStatus), /* state1 → R_PadState */
/* ============== PORT 1 TRANSACTION (mirror of port 0) ============== */
/* R_PadStatus + R_PadCountdown are no longer reserved (port 1 is the
* last transaction); we still use R_T0/R_T1 as scratch to match port 0. */
/* 1. Cleanup: CTRL = 0x0010 (raise /CS, clear stale status) */
add_ui(R_T0, R_0, pad_SIO_CTRL_CLEANUP),
store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET),
/* Bounded by pad_SIO_SETTLE_BEFORE_TX = 1000 iterations. */
add_ui(R_T1, R_0, pad_SIO_SETTLE_BEFORE_TX),
atom_label(settle_pre_port1)
nop,
add_ui_self(R_T1, -1),
branch_ne(R_T1, R_0, atom_offset(settle_pre_port1, settle_pre_port1)),
/* 2. Port-select: CTRL = 0x0003 | (1 << 13) (port 1 select) */
add_ui(R_T0, R_0, pad_SIO_CTRL_TX_ENABLE),
or_i(R_T0, R_T0, pad_SIO_CTRL_DTR_CS),
or_i(R_T0, R_T0, 1 << 13), /* port 1 select bit (CTRL bit 13 = port select) */
store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET),
/* Bounded by pad_SIO_SETTLE_AFTER_TX = 2000 iterations. */
add_ui(R_T1, R_0, pad_SIO_SETTLE_AFTER_TX),
atom_label(settle_post_port1)
nop,
add_ui_self(R_T1, -1),
branch_ne(R_T1, R_0, atom_offset(settle_post_port1, settle_post_port1)),
/* 3. Address byte (0x01) — send + RX-ready wait + read response + RX-drain confirmation */
add_ui(R_T0, R_0, pad_PROTO_ADDR),
store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ack0_port1)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_ne(R_T0, R_0, atom_offset(wait_ack0_port1, ack0_received_port1)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ack0_port1)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ack0_port1, wait_ack0_port1)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port1_from_ack0)
branch_equal(R_0, R_0, atom_offset(skip_port1_from_ack0, end_atom)),
atom_label(ack0_received_port1)
load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ackrel0_port1)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_equal(R_T0, R_0, atom_offset(wait_ackrel0_port1, ack_released_port1)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ackrel0_port1)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel0_port1, wait_ackrel0_port1)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port1_from_ackrel0)
branch_equal(R_0, R_0, atom_offset(skip_port1_from_ackrel0, end_atom)),
atom_label(ack_released_port1)
/* === Byte 1 (port 1): send 0x42 (cmd read) + RX-ready wait + read response + RX-drain confirmation === */
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T0, R_0, pad_PROTO_CMD_READ),
store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ack1_port1)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_ne(R_T0, R_0, atom_offset(wait_ack1_port1, ack1_received_port1)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ack1_port1)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ack1_port1, wait_ack1_port1)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port1_from_ack1)
branch_equal(R_0, R_0, atom_offset(skip_port1_from_ack1, end_atom)),
atom_label(ack1_received_port1)
load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ackrel1_port1)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_equal(R_T0, R_0, atom_offset(wait_ackrel1_port1, ack_released1_port1)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ackrel1_port1)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel1_port1, wait_ackrel1_port1)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port1_from_ackrel1)
branch_equal(R_0, R_0, atom_offset(skip_port1_from_ackrel1, end_atom)),
atom_label(ack_released1_port1)
/* === Byte 2 (port 1): send 0x00 + RX-ready wait + read response + RX-drain confirmation === */
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T0, R_0, 0x00),
store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ack2_port1)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_ne(R_T0, R_0, atom_offset(wait_ack2_port1, ack2_received_port1)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ack2_port1)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ack2_port1, wait_ack2_port1)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port1_from_ack2)
branch_equal(R_0, R_0, atom_offset(skip_port1_from_ack2, end_atom)),
atom_label(ack2_received_port1)
load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ackrel2_port1)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_equal(R_T0, R_0, atom_offset(wait_ackrel2_port1, ack_released2_port1)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ackrel2_port1)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel2_port1, wait_ackrel2_port1)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port1_from_ackrel2)
branch_equal(R_0, R_0, atom_offset(skip_port1_from_ackrel2, end_atom)),
atom_label(ack_released2_port1)
/* === Byte 3 (port 1): send 0x00 + RX-ready wait + read response + RX-drain confirmation === */
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T0, R_0, 0x00),
store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ack3_port1)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_ne(R_T0, R_0, atom_offset(wait_ack3_port1, ack3_received_port1)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ack3_port1)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ack3_port1, wait_ack3_port1)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port1_from_ack3)
branch_equal(R_0, R_0, atom_offset(skip_port1_from_ack3, end_atom)),
atom_label(ack3_received_port1)
load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_ackrel3_port1)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_equal(R_T0, R_0, atom_offset(wait_ackrel3_port1, ack_released3_port1)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_ackrel3_port1)
branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel3_port1, wait_ackrel3_port1)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port1_from_ackrel3)
branch_equal(R_0, R_0, atom_offset(skip_port1_from_ackrel3, end_atom)),
atom_label(ack_released3_port1)
/* === Byte 4 (FINAL, port 1): send 0x00 + RX-not-empty wait + read final byte === */
/* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */
add_ui(R_T0, R_0, 0x00),
store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(wait_rx4_port1)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_ne(R_T0, R_0, atom_offset(wait_rx4_port1, rx4_received_port1)),
add_ui_self(R_T1, -1),
atom_label(continue_wait_rx4_port1)
branch_ne(R_T1, R_0, atom_offset(continue_wait_rx4_port1, wait_rx4_port1)),
mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0),
atom_label(skip_port1_from_rx4)
branch_equal(R_0, R_0, atom_offset(skip_port1_from_rx4, end_atom)),
atom_label(rx4_received_port1)
load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), /* discard final byte */
/* === RESPONSE DECODE (port 1) === */
atom_label(decode_port1)
mac_pad_sio_write_pad_state(PadSioStatus_Digital, R_PadState, R_T0),
/* /CS cleanup: raise /CS, clear stale status before exiting port 1. */
add_ui(R_T0, R_0, pad_SIO_CTRL_CLEANUP),
store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET),
atom_label(end_atom)
mac_yield(),
};
#endif /* end pad_sio_step wrap */
/* ----- pad_sio_diag_pin -----
* Per-frame diagnostic counter. The caller binds R_DiagPinScratch to
* scratch_for_atom_diag_pin for temporary gdb verification.
*/
#if 0 /* pad_sio_diag_pin — superseded (raw-SIO phase removed) */
internal MipsAtom_(pad_sio_diag_pin) atom_info(atom_phase(pad_init)
, atom_reads(R_T0, R_T1, R_DiagPinScratch)
, atom_writes(R_T0, R_T1, R_DiagPinScratch)
) {
/* FIX 2026-08-02: explicitly reload R_DiagPinScratch (R_T3 = $t3). Caller-saved
* per O32 ABI; the rgcc binding in main() does not survive tape_run. */
load_upper_i(R_DiagPinScratch, 0x8001),
or_i(R_DiagPinScratch, R_DiagPinScratch, 0xC800),
/* High half = 0xD1A6; low half increments once per atom invocation. */
load_word(R_T1, R_DiagPinScratch, 0),
nop,
add_ui(R_T1, R_T1, 1),
and_i(R_T0, R_T1, 0xFFFF),
load_upper_i(R_T1, 0xD1A6),
or_i(R_T1, R_T1, 0),
or_u(R_T1, R_T1, R_T0),
store_word(R_T1, R_DiagPinScratch, 0),
mac_yield(),
};
#endif /* end pad_sio_diag_pin wrap */
/* ----- pad_sio_diag_byte_exchange -----
* Temporary two-byte wire probe: sends 0x01 and 0x42, then stores the
* open-bus byte and response ID in scratch_for_atom_diag_pin.
*/
#if 0 /* pad_sio_diag_byte_exchange — superseded (raw-SIO phase removed) */
internal MipsAtom_(pad_sio_diag_byte_exchange) atom_info(atom_phase(pad_init)
, atom_reads(R_T0, R_T1, R_T2, R_PadSioBase, R_DiagPinScratch)
, atom_writes(R_T0, R_T1, R_T2, R_PadSioBase, R_DiagPinScratch)
) {
/* FIX 2026-08-02: explicitly reload R_DiagPinScratch (R_T3 = $t3). Caller-saved
* per O32 ABI; the rgcc binding in main() does not survive tape_run. */
load_upper_i(R_DiagPinScratch, 0x8001),
or_i(R_DiagPinScratch, R_DiagPinScratch, 0xC800),
/* FIX 2026-08-02: explicitly load KSEG1 base into R_PadSioBase (R_T6) at the
* top. The rgcc() binding in main() does NOT survive the tape_run call
* because R_T6 is caller-saved per the O32 ABI. */
load_upper_i(R_PadSioBase, pad_IO_KSEG1_BASE >> 16),
or_i(R_PadSioBase, R_PadSioBase, pad_IO_KSEG1_BASE & 0xFFFF),
add_ui(R_T0, R_0, pad_SIO_CTRL_CLEANUP),
store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET),
add_ui(R_T0, R_0, pad_SIO_CTRL_TX_ENABLE),
or_i(R_T0, R_T0, pad_SIO_CTRL_DTR_CS),
store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET),
add_ui(R_T0, R_0, pad_PROTO_ADDR),
store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(diag_wait_ack0)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_ne(R_T0, R_0, atom_offset(diag_wait_ack0, diag_ack0_done)),
add_ui_self(R_T1, -1),
branch_ne(R_T1, R_0, atom_offset(diag_wait_ack0, diag_wait_ack0)),
add_ui(R_T0, R_0, 0xDEADAC01),
store_word(R_T0, R_DiagPinScratch, 0),
branch_equal(R_0, R_0, atom_offset(diag_timeout_ack0, diag_timeout)),
atom_label(diag_ack0_done)
load_byte_u(R_T2, R_PadSioBase, pad_SIO_DATA_OFFSET),
add_ui(R_T0, R_0, pad_PROTO_CMD_READ),
store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET),
atom_label(diag_wait_ack1)
load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET),
nop,
and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY),
branch_ne(R_T0, R_0, atom_offset(diag_wait_ack1, diag_ack1_done)),
add_ui_self(R_T1, -1),
branch_ne(R_T1, R_0, atom_offset(diag_wait_ack1, diag_wait_ack1)),
add_ui(R_T0, R_0, 0xDEADAC02),
store_word(R_T0, R_DiagPinScratch, 0),
branch_equal(R_0, R_0, atom_offset(diag_timeout_ack1, diag_timeout)),
atom_label(diag_ack1_done)
load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET),
nop,
shift_lleft(R_T0, R_T0, 8),
or_u(R_T2, R_T2, R_T0),
store_word(R_T2, R_DiagPinScratch, 0),
atom_label(diag_success)
branch_equal(R_0, R_0, atom_offset(diag_success, diag_done)),
nop,
atom_label(diag_timeout_ack0)
add_ui(R_T0, R_0, 0xDEADAC01),
store_word(R_T0, R_DiagPinScratch, 0),
atom_label(diag_timeout_ack1)
add_ui(R_T0, R_0, 0xDEADAC02),
store_word(R_T0, R_DiagPinScratch, 0),
atom_label(diag_timeout)
add_ui(R_T0, R_0, 0xDEADACFF),
store_word(R_T0, R_DiagPinScratch, 0),
atom_label(diag_done)
add_ui(R_T0, R_0, pad_SIO_CTRL_CLEANUP),
store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET),
mac_yield(),
};
#endif /* end pad_sio_diag_byte_exchange wrap */
+7 -1
View File
@@ -7,12 +7,12 @@ A rest from the usual.
## Dependencies ## Dependencies
I will be programming from a Windows 11 machine (may eventually try this on the Steam Deck...): I will be programming from a Windows 11 machine (may eventually try this on the Steam Deck...):
![system_info](./docs/assets/system_info.png)
[armips](https://github.com/Kingcom/armips) [armips](https://github.com/Kingcom/armips)
* Supports doing bare-metal assembly for the ps1 * Supports doing bare-metal assembly for the ps1
* `scoop install armips` or just clone and build.. * `scoop install armips` or just clone and build..
* Was used early in the course. Now I just use an macro asm dsl in C11.
[luajit-2.1](https://github.com/LuaJIT/LuaJIT.git) [luajit-2.1](https://github.com/LuaJIT/LuaJIT.git)
@@ -73,3 +73,9 @@ scoop install luajit
![hello_psyq!](./docs/assets/pcsx-redux_2025-08-05_23-01-19.png) ![hello_psyq!](./docs/assets/pcsx-redux_2025-08-05_23-01-19.png)
![cube!](./docs/assets/pcsx-redux_2025-10-11_03-04-01.png) ![cube!](./docs/assets/pcsx-redux_2025-10-11_03-04-01.png)
![cube and floor!](./docs/assets/pcsx-redux_2026-07-10_22-47-02.png) ![cube and floor!](./docs/assets/pcsx-redux_2026-07-10_22-47-02.png)
Win 11 machine:
![system_info](./docs/assets/system_info.png)
Still haven't gotten around to trying this on linux...
+7 -18
View File
@@ -180,29 +180,18 @@ function link-modules { param([string[]]$link_modules, [string] $elf, [string[]
$link_args += ($f_link_pass_through_prefix + $f_link_mapfile + $map) $link_args += ($f_link_pass_through_prefix + $f_link_mapfile + $map)
$link_args += ($f_link_pass_through_prefix + $f_link_start_group) $link_args += ($f_link_pass_through_prefix + $f_link_start_group)
# raw_sio_pad_poll_20260802 — Task 5.1c surgical library-list trim.
# The 16 removed entries (c2, card, cd, comb, ds, gs, gun, hmd, math,
# mcrd, mcx, press, sio, snd, spu, tap) had LOAD lines in the map but
# ZERO .o files pulled in — they were unused. The 5 kept libraries
# (api, c, etc, gpu, gte) are required by the C-side calls in
# hello_joypad.c (reset_graph, draw_sync, vsync, etc.).
$libraries = @( $libraries = @(
"api", "api",
"c", "c",
"c2",
"card",
"cd",
"comb",
"ds",
"etc", "etc",
"gpu", "gpu",
"gs", "gte"
"gte",
"gun",
"hmd",
"math",
"mcrd",
"mcx",
"pad",
"press",
"sio",
"snd",
"spu",
"tap"
) )
foreach ($lib in $libraries) { foreach ($lib in $libraries) {
$link_args += ($f_link_lib + $lib) $link_args += ($f_link_lib + $lib)
+23 -15
View File
@@ -925,13 +925,21 @@ function M.tokenize_body(body)
if c == BYTE_COMMA then break end if c == BYTE_COMMA then break end
if c == BYTE_NEWLINE then break end if c == BYTE_NEWLINE then break end
if c == BYTE_SEMI then break end if c == BYTE_SEMI then break end
-- Line-comment '// ... \n' (0x2F 0x2F): skip to (and past) the next newline, or to end-of-body.
if c == BYTE_SLASH and body:byte(scan + 1) == BYTE_SLASH then
local nl = M.find_byte(body, BYTE_NEWLINE, scan)
scan = nl and (nl + 1) or (len + 1)
-- Block-comment '/* ... */' (0x2F 0x2A): skip to (and past) the matching '*/', or to end-of-body.
elseif c == BYTE_SLASH and body:byte(scan + 1) == BYTE_STAR then
local close = body:find("*/", scan + 2, true)
scan = close and (close + 2) or (len + 1)
-- Group opener bytes (consume the balanced group via the matching reader): '(' = 0x28, '{' = 0x7B, '[' = 0x5B. -- Group opener bytes (consume the balanced group via the matching reader): '(' = 0x28, '{' = 0x7B, '[' = 0x5B.
if c == BYTE_OPEN_PAREN then local _, a = M.read_parens (body, scan); scan = a elseif c == BYTE_OPEN_PAREN then local _, a = M.read_parens (body, scan); scan = a
elseif c == BYTE_OPEN_BRACE then local _, a = M.read_braces (body, scan); scan = a elseif c == BYTE_OPEN_BRACE then local _, a = M.read_braces (body, scan); scan = a
elseif c == BYTE_OPEN_BRACK then local _, a = M.read_brackets (body, scan); scan = a elseif c == BYTE_OPEN_BRACK then local _, a = M.read_brackets (body, scan); scan = a
-- String-literal byte ('"' = 0x22 or '\'' = 0x27): skip past the quoted region in one shot. -- String-literal byte ('"' = 0x22 or '\'' = 0x27): skip past the quoted region in one shot.
elseif c == BYTE_DQUOTE or c == BYTE_SQUOTE then elseif c == BYTE_DQUOTE or c == BYTE_SQUOTE then
scan = M.skip_str_or_cmt(body, scan) + 1 scan = (M.skip_str_or_cmt(body, scan) or scan) + 1
else else
scan = scan + 1 scan = scan + 1
end end
@@ -1316,13 +1324,13 @@ M.OPERAND_READ_POSITIONS = {
["sub_s"] = {1, 2, 3}, ["sub_s"] = {1, 2, 3},
["sub_u"] = {1, 2, 3}, ["sub_u"] = {1, 2, 3},
["and_i"] = {1, 2}, ["and_i"] = {1, 2},
["and_u"] = {1, 2, 3}, ["and"] = {1, 2, 3},
["or_i"] = {1, 2}, ["or_i"] = {1, 2},
["or_i_self"] = {1}, ["or_i_self"] = {1},
["or_u"] = {1, 2, 3}, ["or"] = {1, 2, 3},
["or_u_self"] = {1, 2}, ["or_self"] = {1, 2},
["xor_i"] = {1, 2}, ["xor_i"] = {1, 2},
["xor_u"] = {1, 2, 3}, ["xor"] = {1, 2, 3},
["slt_s"] = {1, 2, 3}, ["slt_s"] = {1, 2, 3},
["slt_u"] = {1, 2, 3}, ["slt_u"] = {1, 2, 3},
["slt_si"] = {1, 2}, ["slt_si"] = {1, 2},
@@ -1469,14 +1477,14 @@ M.INSTRUCTION_LATENCY = {
-- CPU ALU (single-cycle R3000A ops) -- CPU ALU (single-cycle R3000A ops)
["nop"] = 1, ["nop"] = 1,
["nop2"] = 2, ["nop2"] = 2,
["add_ui"] = 1, ["add_ui_self"] = 1, ["add_ui"] = 1, ["add_ui_self"] = 1,
["add_s"] = 1, ["add_si"] = 1, ["add_s"] = 1, ["add_si"] = 1,
["add_u"] = 1, ["add_u_self"] = 1, ["add_u"] = 1, ["add_u_self"] = 1,
["sub_u"] = 1, ["sub_s"] = 1, ["sub_u"] = 1, ["sub_s"] = 1,
["and_i"] = 1, ["and_u"] = 1, ["and_i"] = 1, ["and"] = 1,
["or_i"] = 1, ["or_i_self"] = 1, ["or_i"] = 1, ["or_i_self"] = 1,
["or_u"] = 1, ["or_u_self"] = 1, ["or_u"] = 1, ["or_u_self"] = 1,
["xor_i"] = 1, ["xor_u"] = 1, ["xor_i"] = 1, ["xor_u"] = 1,
["nor_u"] = 1, ["nor_u"] = 1,
["shift_lleft"] = 1, ["shift_lleft_self"] = 1, ["shift_lleft"] = 1, ["shift_lleft_self"] = 1,
["shift_lright"] = 1, ["shift_lright"] = 1,
@@ -1952,7 +1960,7 @@ M.GPR_VALUE_RULES = {
-- Present register-form self variants. They are included here so a -- Present register-form self variants. They are included here so a
-- known value is not needlessly lost when these encoders are used. -- known value is not needlessly lost when these encoders are used.
add_u_self = { op = "add_u", dest = 1, sources = {1, 2}, }, add_u_self = { op = "add_u", dest = 1, sources = {1, 2}, },
or_u_self = { op = "or_u", dest = 1, sources = {1, 2}, }, or_u_self = { op = "or", dest = 1, sources = {1, 2}, },
shift_lleft_self = { op = "shift_lleft", dest = 1, source = 1, immediate = 2, }, shift_lleft_self = { op = "shift_lleft", dest = 1, source = 1, immediate = 2, },
} }
+262 -1
View File
@@ -193,7 +193,7 @@ M.DWARF_LINE_OPS = {
DW_LNE_set_address = 2, -- spec: §6.2.5.3 DW_LNE_set_address = 2, -- spec: §6.2.5.3
-- Standard opcode header (§6.2.5.1) -- Standard opcode header (§6.2.5.1)
-- opcode_base + line_range are 1-byte header fields; hex so they map -- opcode_base + line_range are 1-byte header fields; hex so they map
-- directly to their position in the line-program header byte sequence. -- directly to the line-program header byte sequence.
-- line_base stays signed decimal (=-5) since 0xFB obscures the spec semantics. -- line_base stays signed decimal (=-5) since 0xFB obscures the spec semantics.
opcode_base = 0x0D, opcode_base = 0x0D,
line_base = -5, line_base = -5,
@@ -204,6 +204,44 @@ M.DWARF_LINE_OPS = {
set_address_payload_size = 0x05, -- size = sub_opcode(1) + addr(4) set_address_payload_size = 0x05, -- size = sub_opcode(1) + addr(4)
} }
-- ----------------------------------------------------------------------------
-- DWARF5 .debug_line (per DWARF5 spec §6.2.4 — Line Number Program Header)
-- ----------------------------------------------------------------------------
-- All offsets are zero-based wire offsets from the start of the unit body
-- (i.e. AFTER unit_length has been read and unit_length bytes skipped past unit_length's 4 bytes).
--
-- The DWARF3/4 line-program format differs:
-- - It omits `address_size` (DWARF3 §6.2.4) + `segment_selector_size` (DWARF5 §6.2.4).
-- - It uses null-terminated string lists for `include_directories` + `file_names`
-- (vs. DWARF5's format_count + fields-list shape).
-- These are documented inline at each parse site in read_line_unit_file_table below.
--- spec: DWARF5 spec §6.2.4 (Line Number Program Header — version >= 5)
M.DWARF5_DEBUG_LINE = {
-- Header fields (zero-based, AFTER unit_length has been read).
version_offset_post_il = 0x00, -- 2-byte LE; expected = 5
addr_size_offset = 0x02, -- 1 byte; expected = 4
seg_size_offset = 0x03, -- 1 byte; expected = 0
header_length_offset = 0x04, -- 4-byte LE; length of program-header content that follows
program_header_start = 0x08, -- first byte of program-header content (after the 8 fixed bytes)
-- Per-form byte widths (used when reading directory / file-name entries).
form_addr_bytes = 0x04, -- DW_FORM_addr (32-bit) | DW_FORM_data4
form_strp_bytes = 0x04, -- DW_FORM_line_strp / DW_FORM_strp / DW_FORM_strp_sup
form_data16_bytes = 0x10, -- DW_FORM_data16 (MD5)
-- DWARF5 form codes (subset used in line-program directory + file tables).
form_line_strp = 0x1A, -- DWARF5 §7.5.6 — DW_FORM_line_strp (4-byte offset into .debug_line_str)
form_string = 0x08, -- DWARF4-compatible fallback (inline null-terminated; not in .debug_line_str)
form_udata = 0x0F, -- DW_FORM_udata (ULEB)
form_data16 = 0x18, -- DW_FORM_data16 (16-byte MD5; gcc emits this for split debug info)
-- DWARF5 content-tag codes (DW_LNCT_* from §6.2.4.1 + §6.2.4.2).
lnct_path = 0x01,
lnct_directory_index = 0x02,
lnct_md5 = 0x05, -- gcc with MD5 in file name table (rare)
}
-- ════════════════════════════════════════════════════════════════════════════ -- ════════════════════════════════════════════════════════════════════════════
-- I/O helpers: little-endian byte read/write -- I/O helpers: little-endian byte read/write
-- ════════════════════════════════════════════════════════════════════════════ -- ════════════════════════════════════════════════════════════════════════════
@@ -787,6 +825,229 @@ function M.sleb128_size(n)
return bytes return bytes
end end
-- ════════════════════════════════════════════════════════════════════════════
-- DWARF5 line-program file-table reader
-- ════════════════════════════════════════════
--- Read every line-program unit in `.debug_line` and produce one entry per file across all units.
--- Returns three parallel maps keyed by 1-based file index.
---
--- Wire format notes:
--- * The `.debug_line` section may contain MULTIPLE line-program units
--- File indices are 1-based, **per unit**; we concatenate all units and the index ranges from 1..N₁ in unit 1, N₁+1..N₁+N₂ in unit 2, etc.
--- Per-unit indices (the way gcc emits them, and the way `DW_LNS_set_file` references them in the line program)
--- are returned via the `basename_to_index` map only when the unit boundary happens to align with the metaprogram's per-atom
--- `inv.call_file` (true today for hello_joypad — the C unit is the LAST unit, and atom-side file indices fit 1-based).
--- * Per spec, the `.debug_line_str` section (DWARF5 §7.5.6) holds the strings referenced by `DW_FORM_line_strp`.
--- The legacy DWARF3 format embeds strings directly with null terminators. This helper handles BOTH.
--- * File entries may have multiple forms (gcc -gdwarf-5 with `DW_LNCT_directory_index`
--- emits 2 forms: path + dir_index). The helper supports:
--- - DW_FORM_line_strp (DWARF5; offset into .debug_line_str)
--- - DW_FORM_string (DWARF4-compat; inline null-terminated in .debug_line)
--- - DW_FORM_udata (ULEB128)
--- - DW_FORM_data16 (16-byte MD5; ignored — skip the form's bytes)
--- * Symlink-canonicalisation: each path's `paths[i]` is stored verbatim from the wire
--- (mixed `/` and `\` accepted; the basename is taken via the last path separator). Caller normalises as needed.
---
--- Behavior on failure: writes to stderr and returns nil.
--- Helpers consumed by `passes/dwarf_injection.lua::init_file_index_lookup(elf_path)` calls this once at pass start to populate the module-level `basename_to_index` map;
--- downstream `resolve_provenance_file_index(path)` consumers
--- (which replaced the former hardcoded `ATOM_SOURCE_FILE_INDEX` + `PROVENANCE_BASENAME_TO_FILE_INDEX` table per `conductor/tracks/dwarf_file_index_lookup_20260731/`)
--- consult the map directly.
---
--- @param elf_path string -- absolute path to the post-link ELF (typically the gcc-emitted `.elf` BEFORE dwarf_injector's splice;
--- both shapes work since the splice preserves `.debug_line`)
--- @return table|nil, table|nil, table|nil
--- basename_to_index: { [basename] = 1-based-per-unit-file-index, ... }
--- basenames: { [1-based-per-unit-file-index] = basename, ... }
--- paths: { [1-based-per-unit-file-index] = full path (mixed slashes), ... }
function M.read_line_unit_file_table(elf_path)
local sections = M.read_elf_sections(elf_path, { ".debug_line", ".debug_line_str" })
local line = sections[".debug_line"]
local lstr = sections[".debug_line_str"] or ""
if not line or line == "" then
io.stderr:write("[elf_dwarf.read_line_unit_file_table] no .debug_line section in: " .. tostring(elf_path) .. "\n")
return nil
end
local basenames = {}
local basename_to_index = {}
local paths = {}
--- Read one form-code's bytes from `buf` at position `p` according to `form`.
--- Returns (value, after) where `value` is:
--- * the resolved string (DW_FORM_line_strp / DW_FORM_string)
--- * the ULEB128 number (DW_FORM_udata)
--- * nil + skip-bytes (DW_FORM_data16; we don't surface the MD5)
local function read_form(buf, lstr_buf, p, form)
if form == M.DWARF5_DEBUG_LINE.form_line_strp then
local strp = M.read_u32_le(buf, p)
local end_pos = lstr_buf:find("\0", strp + 1, true) or (#lstr_buf + 1)
return lstr_buf:sub(strp + 1, end_pos - 1), p + M.DWARF5_DEBUG_LINE.form_strp_bytes
elseif form == M.DWARF5_DEBUG_LINE.form_string then
local nul = buf:find("\0", p + 1, true) or (#buf + 1)
return buf:sub(p + 1, nul - 1), nul
elseif form == M.DWARF5_DEBUG_LINE.form_udata then
local v, after = M.read_uleb128_at(buf, p)
return v, after
elseif form == M.DWARF5_DEBUG_LINE.form_data16 then
return nil, p + M.DWARF5_DEBUG_LINE.form_data16_bytes
else
-- Unsupported form in a directory/file-table entry: best-effort skip.
-- We do NOT stderr-write because the crt0.s DWARF5 line unit (gcc-as emitted) uses DW_FORM_addr (0x01) for what is effectively a path entry,
-- which is non-standard.
-- The C-unit's DWARF3 paths are read via the parallel DWARF3 path and never see this error.
-- Callers should consult `basename_to_index` for the paths they care about and ignore this unit if it produced none.
return nil, p
end
end
--- Parse one DWARF-version-3-style unit (DWARF3/4 line program; gcc default in the PS1 toolchain still emits DWARF3 for line programs in `-g` mode).
--- Layout: null-terminated directory list, then path(null) + dir_idx(ULEB) + time(ULEB) + size(ULEB) file entries terminated by an empty null.
--- `content_start` = zero-based wire offset of the first byte of program-header content (after version + header_length fields).
--- @return unit_basenames { [idx_in_unit_1_based] = basename }
--- @return unit_paths { [idx_in_unit_1_based] = full path }
local function parse_dwarf3_unit(buf, content_start, body_end)
local up = content_start
-- 5 fixed bytes: min_insn, default_is, line_base (signed), line_range, opcode_base
up = up + 5
local opcode_base = buf:byte(content_start + 5)
up = up + (opcode_base - 1) -- std_opcode_lengths
local dirs = {}
while up < body_end do
local nul = buf:find("\0", up + 1, true) or (body_end + 1)
if nul > body_end then break end
local len = nul - up - 1
if len == 0 then up = nul break end
dirs[#dirs + 1] = buf:sub(up + 1, nul - 1)
up = nul
end
local unit_basenames = {}
local unit_paths = {}
while up < body_end do
local nul = buf:find("\0", up + 1, true) or (body_end + 1)
if nul > body_end or nul == up + 1 then up = nul break end
local path = buf:sub(up + 1, nul - 1)
up = nul
local didx, up_next = M.read_uleb128_at(buf, up); up = up_next
local _time, up_next2 = M.read_uleb128_at(buf, up); up = up_next2
local _size, up_next3 = M.read_uleb128_at(buf, up); up = up_next3
local idx = #unit_basenames + 1
local bs = path:match("[^/\\]+$") or path
unit_paths[idx] = path
unit_basenames[idx] = bs
dirs[1] = dirs[1] or "" -- safety: gcc emits "" sentinel dir at 0
if didx > 0 and dirs[didx] then
unit_paths[idx] = dirs[didx] .. "/" .. path
end
end
return unit_basenames, unit_paths
end
--- Parse one DWARF-version-5-style unit (DWARF5 line program; used by modern gcc with `-gdwarf-5`).
--- `content_start` is the first byte of program-header content (after the 8 fixed bytes version+addr_size+seg_size+header_length).
--- @return same shape as parse_dwarf3_unit
local function parse_dwarf5_unit(buf, lstr_buf, content_start, body_end)
local up = content_start
-- 6 fixed bytes: min_insn, max_ops_per_insn, default_is, line_base, line_range, opcode_base
up = up + 6
local opcode_base = buf:byte(content_start + 6)
up = up + (opcode_base - 1) -- std_opcode_lengths
-- directories
local dir_format_count, after = M.read_uleb128_at(buf, up); up = after
local dir_formats = {}
for i = 1, dir_format_count do
local f, a2 = M.read_uleb128_at(buf, up); up = a2
dir_formats[i] = f
end
local dir_count, a3 = M.read_uleb128_at(buf, up); up = a3
local dirs = {}
for i = 1, dir_count do
local combined = ""
for j = 1, dir_format_count do
local v, a4 = read_form(buf, lstr_buf, up, dir_formats[j])
up = a4
if j == 1 and type(v) == "string" then combined = v end
end
dirs[i] = combined
end
-- file names
local file_format_count, after2 = M.read_uleb128_at(buf, up); up = after2
local file_formats = {}
for i = 1, file_format_count do
local f, a2 = M.read_uleb128_at(buf, up); up = a2
file_formats[i] = f
end
local file_count, a3 = M.read_uleb128_at(buf, up); up = a3
local unit_basenames = {}
local unit_paths = {}
for i = 1, file_count do
local combined = ""
local didx = 0
for j = 1, file_format_count do
local v, a4 = read_form(buf, lstr_buf, up, file_formats[j])
up = a4
if j == 1 and type(v) == "string" then combined = v end
if j == 2 and type(v) == "number" then didx = v end
end
local idx = #unit_basenames + 1
local bs = combined:match("[^/\\]+$") or combined
unit_paths[idx] = combined
unit_basenames[idx] = bs
if didx > 0 and dirs[didx] then
unit_paths[idx] = dirs[didx] .. "/" .. combined
end
end
return unit_basenames, unit_paths
end
--- Walk every line-program unit in the section.
local p = 0
local section_end = #line
while p + 4 <= section_end do
local unit_length = M.read_u32_le(line, p)
if unit_length == 0xFFFFFFFF then
io.stderr:write("[elf_dwarf.read_line_unit_file_table] 64-bit DWARF (initial-length 0xFFFFFFFF); not supported\n")
return nil
end
local body_start = p + 4
local body_end = p + 4 + unit_length
if body_end > section_end then break end
local version = M.read_u16_le(line, body_start)
local unit_basenames, unit_paths
if version >= 5 then
-- DWARF5 header: version(2) + addr_size(1) + seg_size(1) + header_length(4) + content
local header_length_offset = body_start + 6 -- past version(2) + addr_size(1) + seg_size(1) - wait that's wrong; past hdr len is at +6
local content_start = body_start + 8 -- past version(2) + addr_size(1) + seg_size(1) + header_length(4)
unit_basenames, unit_paths = parse_dwarf5_unit(line, lstr, content_start, body_end)
elseif version >= 2 then
-- DWARF2/3/4 header: version(2) + header_length(4) + content
local content_start = body_start + 6 -- past version(2) + header_length(4)
unit_basenames, unit_paths = parse_dwarf3_unit(line, content_start, body_end)
else
io.stderr:write(string.format("[elf_dwarf.read_line_unit_file_table] unsupported DWARF version %d (offset 0x%x)\n", version, p))
p = body_end
goto continue
end
-- Per-unit 1-based file indices are aligned with `inv.call_file` values because the metaprogram emits `DW_LNS_set_file` with the per-unit index.
-- When multiple units are present (crt0.s + C unit), the per-unit index in each unit matches the metaprogram's intent (gcc always sets file in unit-local terms).
-- We therefore store directly without global re-indexing; the caller is responsible for knowing which unit the file-index applies to.
-- For DWARF3 (C unit is the unit that matters for atom line tables), this matches.
-- For DWARF5 (crt0.s + C unit), each carries its own per-unit file-table map;
-- the atom-side DW_LNS_set_file(N) refers to the C unit's indices, NOT crt0.s's.
-- Since the C unit is the one with full include_directories + 12 entries, we can use it directly.
for idx, bs in pairs(unit_basenames) do
basenames[idx] = bs
paths[idx] = unit_paths[idx]
basename_to_index[bs] = idx
end
p = body_end
::continue::
end
return basename_to_index, basenames, paths
end
-- ════════════════════════════════════════════════════════════════════════════ -- ════════════════════════════════════════════════════════════════════════════
-- I/O helpers: atoms source-map + native directory glob -- I/O helpers: atoms source-map + native directory glob
-- ════════════════════════════════════════════════════════════════════════════ -- ════════════════════════════════════════════════════════════════════════════
+4 -4
View File
@@ -4,7 +4,7 @@
--- Scanner owns `declaration_comment` and `debug_skip` on each declaration record; this pass projects both forward. --- Scanner owns `declaration_comment` and `debug_skip` on each declaration record; this pass projects both forward.
--- ---
--- Reads the pre-scanned SourceScan payload from `duffle.scan_source` for `MipsAtomComp_(ac_X)` and `MipsAtomComp_Proc_(ac_X, { body })` declarations, --- Reads the pre-scanned SourceScan payload from `duffle.scan_source` for `MipsAtomComp_(ac_X)` and `MipsAtomComp_Proc_(ac_X, { body })` declarations,
--- then resolves the function-args string from the preceding `FI_ MipsAtom ac_X(...)` declaration via a backward walk. --- then resolves the function-args string from the preceding `FI_ Slice_MipsCode ac_X(...)` declaration via a backward walk.
--- ---
--- Emits one `<dir_basename>.macs.h` per source with `#define mac_X(sig) \` macros plus `WORD_COUNT(mac_X, N)` entries for downstream offset computation. --- Emits one `<dir_basename>.macs.h` per source with `#define mac_X(sig) \` macros plus `WORD_COUNT(mac_X, N)` entries for downstream offset computation.
--- ---
@@ -29,7 +29,7 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
-- Atom component declaration identifiers. -- Atom component declaration identifiers.
local ATOM_COMP_PROC = "MipsAtomComp_Proc_" local ATOM_COMP_PROC = "MipsAtomComp_Proc_"
local MIPS_ATOM = "MipsAtom" -- prefix on the function declaration that wraps an AtomComp_Proc_ local MIPS_ATOM = "Slice_MipsCode" -- prefix on the function declaration that wraps an AtomComp_Proc_
-- Component-name prefixes. -- Component-name prefixes.
local AC_PREFIX = "ac_" -- arg to MipsAtomComp_(ac_X); the X is the atom name local AC_PREFIX = "ac_" -- arg to MipsAtomComp_(ac_X); the X is the atom name
@@ -97,9 +97,9 @@ local M = {}
--- Returns the args string (e.g., `"U4 off, U4 code, U1 r, U1 g, U1 b"`) or nil if no function declaration is found. --- Returns the args string (e.g., `"U4 off, U4 code, U1 r, U1 g, U1 b"`) or nil if no function declaration is found.
--- ---
--- Convention: function form is --- Convention: function form is
--- `FI_ MipsAtom ac_X(args) MipsAtomComp_Proc_(ac_X, { body })` --- `FI_ Slice_MipsCode ac_X(args) MipsAtomComp_Proc_(ac_X, { body })`
--- We find the LAST occurrence of `"ac_X("` before `before_pos` and extract the args from inside the parens. --- We find the LAST occurrence of `"ac_X("` before `before_pos` and extract the args from inside the parens.
--- We then verify the preceding context ends with `MipsAtom` --- We then verify the preceding context ends with `Slice_MipsCode`
--- (the function-decl keyword with possible qualifiers between). --- (the function-decl keyword with possible qualifiers between).
--- ---
--- @param source string --- @param source string
+111 -69
View File
@@ -71,9 +71,15 @@ local DW_LNE_set_address = DWARF_LINE_OPS.DW_LNE_set_address
local DW_RLE_end_of_list = DWARF5_RNGLISTS.end_of_list local DW_RLE_end_of_list = DWARF5_RNGLISTS.end_of_list
local DW_RLE_start_length = DWARF5_RNGLISTS.start_length local DW_RLE_start_length = DWARF5_RNGLISTS.start_length
-- File index 11 in the existing main line unit is hello_gte_tape.c. -- File-index lookup for the existing main line unit (Unit 2).
-- The injector extends that unit rather than appending an unreferenced unit. -- Populated at pass start by `init_file_index_lookup(elf_path)` from the runtime ELF (see `elf_dwarf.read_line_unit_file_table`).
local ATOM_SOURCE_FILE_INDEX = 11 -- The hardcoded indices and the `PROVENANCE_BASENAME_TO_FILE_INDEX` table that previously lived here were retired in `conductor/tracks/dwarf_file_index_lookup_20260731/`
-- (red of the
-- `TODO(Ed): Remove this HARDCODE` from line 156); the runtime lookup reads the
-- actual gcc-emitted `.debug_line` file table instead.
local _file_index_by_basename = nil -- [basename] = 1-based line-table file index
local _file_path_by_index = nil -- [1-based index] = full source path (diagnostics / future consumers)
local _default_atom_source_index = nil -- any valid index used in opaque-row fallbacks
-- RR_<R_Name> debug-visible variables come from the merged register_alias_registry filtered to aliases whose code is a valid MIPS GPR 0..31 -- RR_<R_Name> debug-visible variables come from the merged register_alias_registry filtered to aliases whose code is a valid MIPS GPR 0..31
-- (see collect_per_source_registries + by_alias in build_inserted_children). -- (see collect_per_source_registries + by_alias in build_inserted_children).
@@ -98,9 +104,8 @@ local ABBREV_INLINED_SUBROUTINE = 0x6C -- 108: DW_TAG_inlined_subroutine with
-- (each field transitions from tape memory to GPR at load_pc + 8 = MIPS I load-delay slot boundary). -- (each field transitions from tape memory to GPR at load_pc + 8 = MIPS I load-delay slot boundary).
local ABBREV_BIND_VAR_LOCLIST = 0x6D -- 109: DW_TAG_variable no children + DW_AT_type = ref4 + DW_AT_location = sec_offset local ABBREV_BIND_VAR_LOCLIST = 0x6D -- 109: DW_TAG_variable no children + DW_AT_type = ref4 + DW_AT_location = sec_offset
-- Typed-view pointer_type (for the synthetic V4_S2* / V3_S2* / U4* / void* chains). -- Typed-view pointer_type (for the synthetic V4_S2* / V3_S2* / U4* / void* chains).
-- MUST be a fresh abbrev code in the appended table — emitting uleb128(9) collides with GCC's -- MUST be a fresh abbrev code in the appended table — emitting uleb128(9) collides with GCC's existing abbrev 9
-- existing abbrev 9 (a pointer_type that carries DW_AT_byte_size + DW_AT_type), so gdb misparses -- (a pointer_type that carries DW_AT_byte_size + DW_AT_type), so gdb misparses our 4-byte ref4 as (byte_size, type[0..2]) and lands the cursor mid-attribute.
-- our 4-byte ref4 as (byte_size, type[0..2]) and lands the cursor mid-attribute.
local ABBREV_TYPED_VIEW_POINTER = 0x6E -- 110: DW_TAG_pointer_type no children + DW_AT_type = ref4 (typed-view / U4 / void chain) local ABBREV_TYPED_VIEW_POINTER = 0x6E -- 110: DW_TAG_pointer_type no children + DW_AT_type = ref4 (typed-view / U4 / void chain)
-- DWARF5 §7.7.3 loclist opcodes. -- DWARF5 §7.7.3 loclist opcodes.
@@ -153,46 +158,66 @@ local DW_AT_inline = 0x20 -- DWARF5 §7.7.1: DW_AT_inline (used by a
local DW_AT_decl_file = 0x3A -- DWARF5 §7.7.1: DW_AT_decl_file (1-based file index into the CU's file table) local DW_AT_decl_file = 0x3A -- DWARF5 §7.7.1: DW_AT_decl_file (1-based file index into the CU's file table)
local DW_AT_decl_line = 0x3B -- DWARF5 §7.7.1: DW_AT_decl_line local DW_AT_decl_line = 0x3B -- DWARF5 §7.7.1: DW_AT_decl_line
-- TODO(Ed): Remove this HARDCODE -- Replaced the hardcoded `ATOM_SOURCE_FILE_INDEX = 11` and the `PROVENANCE_BASENAME_TO_FILE_INDEX` table below with a runtime lookup
-- File index lookup table for the existing main line unit (Unit 2). -- (`init_file_index_lookup` + `resolve_provenance_file_index`) that reads the actual `.debug_line` file table from the post-link ELF.
-- Provenance paths come back with mixed slashes; we normalize to basename and look up against the line unit's actual file table.
-- Current scope has two provenance basenames: hello_gte_tape.c (the atom's call site) and lottes_tape.h (the component definition). --- Populate the module-level file-index lookup table from the `.debug_line` section of the post-link ELF pointed at by `elf_path`.
-- Both live in the existing gcc-generated line unit; their 1-based indices are stable across rebuilds because the include order --- This MUST be called exactly once at pass start (from `M.run`) before any `resolve_provenance_file_index` invocation;
-- in code/gte_hello/hello_gte.c determines the unit's file table. --- downstream callers handle a nil table as "no file info available; fall back to errors".
-- gcc only adds a file to the line table when it has actual line-number entries; ---
-- headers that are pure macros/typedefs (dsl.h, memory.h, math.h, mips.h, gp.h, gte.h, etc.) never appear. --- The lookup uses `elf_dwarf.read_line_unit_file_table` (which parses both DWARF3 and DWARF5 line-program units —
-- lottes_tape.h is the FIRST include that emits line entries (MipsAtomComp_ declarations), so it is the FIRST entry after the primary file. --- the crt0.s assembler-side DWARF5 unit may emit non-standard form codes for paths and is intentionally skipped).
local PROVENANCE_BASENAME_TO_FILE_INDEX = { --- @param elf_path string|nil
["hello_joypad.tape.c"] = ATOM_SOURCE_FILE_INDEX, -- = 11 local function init_file_index_lookup(elf_path)
["hello_gte.tape.c"] = ATOM_SOURCE_FILE_INDEX, -- = 11 if not elf_path or elf_path == "" then return end
["lottes_tape.h"] = 2, local b2i, _basenames, paths = elf_dwarf.read_line_unit_file_table(elf_path)
} if type(b2i) ~= "table" or type(paths) ~= "table" then
io.stderr:write("[dwarf_injection] read_line_unit_file_table returned no file table for: " .. tostring(elf_path) .. "\n")
return
end
_file_index_by_basename = b2i
_file_path_by_index = paths
-- Pick any valid index for the opaque-row fallbacks at lines 466 + 570
-- (both sites legitimately want "any file index"; gdb resolves whatever index we emit to whatever that file's line happens to be).
for idx in pairs(paths) do
_default_atom_source_index = idx
break
end
end
--- Resolve an absolute provenance path to the line-unit file index used by the emitting line program. --- Resolve an absolute provenance path to the line-unit file index used by the emitting line program.
--- Normalizes mixed `/` and `\` separators to a basename and looks it up against the known file table. --- Normalizes mixed `/` and `\` separators to a basename and looks it up against the runtime-computed file table populated by `init_file_index_lookup`.
--- ---
--- Fails loudly on an unknown provenance basename: adding a new component source file requires extending --- Fails loudly on an unknown provenance basename: adding a new component source file will produce a clear error message naming the missing basename and listing the .debug_line file table contents,
--- `PROVENANCE_BASENAME_TO_FILE_INDEX` so the line-program emission contract stays explicit. --- so the user can either confirm the gcc include order, the unity-root, or the `.debug_line` file table contents.
--- Silent fallback to ATOM_SOURCE_FILE_INDEX would mask the new-file case by misattributing component rows to the atom's source file. --- Silent fallback would mask the new-file case by misattributing component rows to an arbitrary source file.
--- @param path string -- absolute provenance path (e.g. "C:/.../lottes_thttps://www.youtube.com/watch?v=ORM4yLkdKx8ape.h" or "C:\\...\\lottes_tape.h") --- @param path string -- absolute provenance path (mixed slashes accepted)
--- @return integer -- 1-based line-unit file index --- @return integer -- 1-based line-unit file index
local function resolve_provenance_file_index(path) local function resolve_provenance_file_index(path)
if _file_index_by_basename == nil then
error("[dwarf_injection] resolve_provenance_file_index called before init_file_index_lookup. "
.. "Is M.run being entered correctly (with --elf)?")
end
if path == nil or path == "" then if path == nil or path == "" then
error("[dwarf_injection] resolve_provenance_file_index: empty path") error("[dwarf_injection] resolve_provenance_file_index: empty path")
end end
-- Normalize backslashes → forward slashes (paths arrive with mixed separators from the provenance file: forward slashes from Lua's io.lines; -- Normalize backslashes → forward slashes (paths arrive with mixed separators from the provenance file).
-- backslashes if the input ever round-trips through Windows shell expansion).
local normalized = path:gsub("\\", "/") local normalized = path:gsub("\\", "/")
-- Take the last path component (the basename). -- Take the last path component (the basename).
local basename = normalized:match("([^/]+)$") or normalized local basename = normalized:match("([^/]+)$") or normalized
local idx = PROVENANCE_BASENAME_TO_FILE_INDEX[basename] local idx = _file_index_by_basename[basename]
if idx == nil then if idx ~= nil then return idx end
error(string.format( -- Last-resort exact-path match (handles paths that don't reduce to a known basename).
"[dwarf_injection] resolve_provenance_file_index: unknown provenance basename '%s' (from '%s'). " for i, p in pairs(_file_path_by_index) do
.. "Extend PROVENANCE_BASENAME_TO_FILE_INDEX in passes/dwarf_injection.lua.", if p and p:gsub("\\", "/") == normalized then return i end
basename, path))
end end
return idx -- Build an error message listing the known basenames for fast diagnostics.
local known = {}
for k in pairs(_file_index_by_basename) do known[#known + 1] = k end
table.sort(known)
error(string.format("[dwarf_injection] resolve_provenance_file_index: unknown provenance basename '%s' (from '%s'). "
.. "Known basenames in the .debug_line file table (%d): %s"
, basename, path, #known, table.concat(known, ", ")))
end end
local DW_FORM_addr = 0x01 local DW_FORM_addr = 0x01
@@ -463,7 +488,7 @@ local function build_atom_sequence(atom)
if atom.debug_skip then if atom.debug_skip then
return table.concat({ return table.concat({
set_address(atom.addr), set_address(atom.addr),
set_file(ATOM_SOURCE_FILE_INDEX), set_file(resolve_provenance_file_index(atom.src_path)),
advance_line(atom.entries[1].line - 1), advance_line(atom.entries[1].line - 1),
negate_stmt(), negate_stmt(),
copy_op(), copy_op(),
@@ -510,10 +535,9 @@ local function build_atom_sequence(atom)
-- * If the invocation's body has any NESTED invocations (parent_id == top_inv.id), the body's -- * If the invocation's body has any NESTED invocations (parent_id == top_inv.id), the body's
-- first content is the call_line of the earliest nested invocation (by start_pos). -- first content is the call_line of the earliest nested invocation (by start_pos).
-- * Otherwise (only RAW words in the body), it's the line of the first raw word = body_lines[1]. -- * Otherwise (only RAW words in the body), it's the line of the first raw word = body_lines[1].
-- This is the value the multi-row PC's body_lines[1] row must reference for source-order display: -- This is the value the multi-row PC's body_lines[1] row must reference for source-order display: `anc.body_lines[1]` is the line of the FIRST WORD
-- `anc.body_lines[1]` is the line of the FIRST WORD (which for an outer whose body starts with a -- (which for an outer whose body starts with a nested expansion is inside the inner's expansion = wrong for display purposes);
-- nested expansion is inside the inner's expansion = wrong for display purposes); `anc.body_first_line` -- `anc.body_first_line` is the body's first content line in the parent's source (= correct for display).
-- is the body's first content line in the parent's source (= correct for display).
local body_first_line_of = {} local body_first_line_of = {}
for _, top_inv in ipairs(invs) do for _, top_inv in ipairs(invs) do
local earliest_nested_call_line = nil local earliest_nested_call_line = nil
@@ -567,7 +591,7 @@ local function build_atom_sequence(atom)
parts[#parts + 1] = copy_op() parts[#parts + 1] = copy_op()
end end
local call_file_idx = ATOM_SOURCE_FILE_INDEX local call_file_idx = resolve_provenance_file_index(atom.src_path)
-- --- Atom entry (idx 1) ------------------------------------------------- -- --- Atom entry (idx 1) -------------------------------------------------
local entry_1 = atom.entries[1] local entry_1 = atom.entries[1]
@@ -576,13 +600,11 @@ local function build_atom_sequence(atom)
-- If atom entry 1 starts inside an invocation, walk the ancestry and emit a call-site row + (when applicable) -- If atom entry 1 starts inside an invocation, walk the ancestry and emit a call-site row + (when applicable)
-- a body_lines[1] row for every active ancestor. For a non-nested invocation this is just the one pair; -- a body_lines[1] row for every active ancestor. For a non-nested invocation this is just the one pair;
-- for nested invocations this emits the outer call-site + body_lines[1] rows BEFORE the inner pair so the debugger displays -- for nested invocations this emits the outer call-site + body_lines[1] rows BEFORE the inner pair so the debugger displays
-- the outer body line at the inner's first word -- the outer body line at the inner's first word (PROBLEM B fix).
-- (PROBLEM B fix).
-- --
-- A marked OUTERMOST ancestor's body_lines[1] row is suppressed at this PC (the existing full-skip -- A marked OUTERMOST ancestor's body_lines[1] row is suppressed at this PC (the existing full-skip contract is preserved for the marked outer range);
-- contract is preserved for the marked outer range); its call-site row IS still emitted as a -- Its call-site row IS still emitted as a statement.
-- statement. Marked INNER ancestors always emit their body_lines[1] row with is_stmt=false -- Marked INNER ancestors always emit their body_lines[1] row with is_stmt=false (the per-invocation `want_body = not inv.debug_skip` predicate).
-- (the per-invocation `want_body = not inv.debug_skip` predicate).
if #entry_1_ancestry == 0 then if #entry_1_ancestry == 0 then
-- RAW word at atom entry: single call-site row, always a statement target. -- RAW word at atom entry: single call-site row, always a statement target.
emit_row(call_file_idx, entry_1.line, true) emit_row(call_file_idx, entry_1.line, true)
@@ -590,9 +612,8 @@ local function build_atom_sequence(atom)
-- Atom starts in an invocation. Walk the ancestry outermost-first. -- Atom starts in an invocation. Walk the ancestry outermost-first.
-- Each ancestor emits one call-site row (statement) and one body_lines[1] row -- Each ancestor emits one call-site row (statement) and one body_lines[1] row
-- (statement iff unmarked; suppressed for marked outermost). -- (statement iff unmarked; suppressed for marked outermost).
-- The body_lines[1] row references body_first_line_of[anc.id] (= the body's first content -- The body_lines[1] row references body_first_line_of[anc.id] (= the body's first content line in the parent's source),
-- line in the parent's source), NOT anc.body_lines[1] (= the line of the first WORD, -- NOT anc.body_lines[1] (= the line of the first WORD, which is wrong when the outer's body starts with a nested call).
-- which is wrong when the outer's body starts with a nested call).
for ai, anc in ipairs(entry_1_ancestry) do for ai, anc in ipairs(entry_1_ancestry) do
assert(anc.body_lines, "missing body_lines: emitter did not run emission-model") assert(anc.body_lines, "missing body_lines: emitter did not run emission-model")
assert(anc.body_lines[1] ~= nil assert(anc.body_lines[1] ~= nil
@@ -617,20 +638,18 @@ local function build_atom_sequence(atom)
if inv and idx == inv.start_pos + 1 then if inv and idx == inv.start_pos + 1 then
-- First word of the innermost active invocation (PROBLEM B fix — nested-display rule). -- First word of the innermost active invocation (PROBLEM B fix — nested-display rule).
-- Walk the active ancestry outermost-first; for each ancestor emit a call-site row -- Walk the active ancestry outermost-first; for each ancestor emit a call-site row (statement) + a body_lines[1] row.
-- (statement) + a body_lines[1] row. The inner-most invocation's call-site + body pair -- The inner-most invocation's call-site + body pair become the LAST two rows in the sequence.
-- become the LAST two rows in the sequence. Marked outermost ancestors suppress their -- Marked outermost ancestors suppress their body_lines[1] row at this PC (the existing full-skip contract is preserved for the marked outer range);
-- body_lines[1] row at this PC (the existing full-skip contract is preserved for the -- all OTHER ancestors emit body_lines[1] with is_stmt = not debug_skip.
-- marked outer range); all OTHER ancestors emit body_lines[1] with is_stmt = not debug_skip.
-- --
-- This re-emits the outer ancestor's call-site + body rows at the inner's first word PC -- This re-emits the outer ancestor's call-site + body rows at the inner's first word PC
-- for debugger context: source-level stepping now shows the outer body line (not the -- for debugger context: source-level stepping now shows the outer body line
-- inner body line) when stepping into the inner. PROBLEM B fix. -- (not the inner body line) when stepping into the inner. PROBLEM B fix.
-- The body_lines[1] row references body_first_line_of[anc.id] (= the body's first content -- The body_lines[1] row references body_first_line_of[anc.id] (= the body's first content line in the parent's source),
-- line in the parent's source), NOT anc.body_lines[1] (= the line of the first WORD, -- NOT anc.body_lines[1] (= the line of the first WORD, which is wrong when the outer's body starts with a nested call:
-- which is wrong when the outer's body starts with a nested call: gdb 12.1 picks the -- gdb 12.1 picks the displayed line as the LAST row at the same PC in byte-stream order,
-- displayed line as the LAST row at the same PC in byte-stream order, so the disc=1 row's -- so the disc=1 row's value matters for what's shown when stepping into the nested case).
-- value matters for what's shown when stepping into the nested case).
local ancestry = ancestry_idx[idx] local ancestry = ancestry_idx[idx]
for ai, anc in ipairs(ancestry) do for ai, anc in ipairs(ancestry) do
assert(anc.body_lines, "missing body_lines: emitter did not run emission-model") assert(anc.body_lines, "missing body_lines: emitter did not run emission-model")
@@ -649,10 +668,9 @@ local function build_atom_sequence(atom)
-- Subsequent body word of the innermost active invocation: `body_lines[k]` is indexed by the 1-based offset of this word inside the invocation. -- Subsequent body word of the innermost active invocation: `body_lines[k]` is indexed by the 1-based offset of this word inside the invocation.
-- Both `idx` (1-based DWARF entry index) and `inv.start_pos` (0-based emitted-word position stamped at `emit_invoke_begin`) come from the same -- Both `idx` (1-based DWARF entry index) and `inv.start_pos` (0-based emitted-word position stamped at `emit_invoke_begin`) come from the same
-- monotonic counter, so `idx - inv.start_pos` is exactly the 1-based k (the first word of the invocation has `idx == inv.start_pos + 1`, hence `k == 1`). -- monotonic counter, so `idx - inv.start_pos` is exactly the 1-based k (the first word of the invocation has `idx == inv.start_pos + 1`, hence `k == 1`).
-- atom_dbg_step_ux_20260725: `want_body = not inv.debug_skip`. The previous `want = not marked_idx[idx]` -- atom_dbg_step_ux_20260725: `want_body = not inv.debug_skip`.
-- (which suppressed ALL body rows when any ancestor was marked) is replaced by the per-invocation -- The previous `want = not marked_idx[idx]` (which suppressed ALL body rows when any ancestor was marked) is replaced by the per-invocation predicate.
-- predicate. Marked invocations emit non-statement body rows at every body word; unmarked -- Marked invocations emit non-statement body rows at every body word; unmarked invocations emit statement body rows.
-- invocations emit statement body rows.
assert(inv.body_lines, "missing body_lines: emitter did not run emission-model") assert(inv.body_lines, "missing body_lines: emitter did not run emission-model")
local words_into = idx - inv.start_pos local words_into = idx - inv.start_pos
assert(inv.body_lines[words_into] ~= nil assert(inv.body_lines[words_into] ~= nil
@@ -706,7 +724,9 @@ local function build_atom_table(corpus, addrs)
local atoms_by_name = corpus.atoms_by_name or {} local atoms_by_name = corpus.atoms_by_name or {}
-- Per-atom ingest. Returns nil if the atom is absent from the corpus; the caller skips it via the `if atom then ...` guard. -- Per-atom ingest. Returns nil if the atom is absent from the corpus; the caller skips it via the `if atom then ...` guard.
local function ingest_atom(name, info) -- `src_path` is the absolute source path that declared this atom; the build_atom_table iteration below threads `src.path` through.
-- This is consumed by `build_atom_sequence::set_file(...)` for opaque-row fallbacks + raw-word rows (atoms where no invocation ancestry exists).
local function ingest_atom(name, info, src_path)
local atom_record = atoms_by_name[name] local atom_record = atoms_by_name[name]
if not atom_record then return nil end if not atom_record then return nil end
@@ -732,6 +752,7 @@ local function build_atom_table(corpus, addrs)
words = #word_events, words = #word_events,
entries = entries, entries = entries,
debug_skip = atom_record.debug_skip == true, debug_skip = atom_record.debug_skip == true,
src_path = src_path or "",
} }
-- Consume invocation records from `atom.paths.invocations`. It is the single producer of per-invocation body_lines, per-invocation debug_skip, -- Consume invocation records from `atom.paths.invocations`. It is the single producer of per-invocation body_lines, per-invocation debug_skip,
@@ -755,9 +776,26 @@ local function build_atom_table(corpus, addrs)
end end
local out = {} local out = {}
for name, info in pairs(addrs) do -- Walk every source's atom list (which preserves source order + per-source src_path).
local atom = ingest_atom(name, info) -- Cross-ref with the nm symbol table; atoms absent from `addrs` are skipped (an atom
if atom then out[#out + 1] = atom end -- declared in source but not emitted as a symbol is a metaprogram or atom-info bug, not
-- a source-correlation bug — emit_no_emit would catch it upstream).
for _, src in ipairs((corpus and corpus.source_order) or {}) do
local src_path = src.path or ""
for _, atom_rec in ipairs(((src.scan or {}).atoms) or {}) do
local info = addrs[atom_rec.name or atom_rec.raw_name]
if info then
local atom = ingest_atom(atom_rec.name or atom_rec.raw_name, info, src_path)
if atom then out[#out + 1] = atom end
end
end
for _, atom_rec in ipairs(((src.scan or {}).raw_atoms) or {}) do
local info = addrs[atom_rec.name or atom_rec.raw_name]
if info then
local atom = ingest_atom(atom_rec.name or atom_rec.raw_name, info, src_path)
if atom then out[#out + 1] = atom end
end
end
end end
table.sort(out, function(a, b) return a.addr < b.addr end) table.sort(out, function(a, b) return a.addr < b.addr end)
return out return out
@@ -2188,6 +2226,10 @@ function M.run(ctx)
-- reading them just returns "" which is the "missing" case the builder handles. -- reading them just returns "" which is the "missing" case the builder handles.
".debug_loc", ".debug_loclists", ".debug_loc", ".debug_loclists",
}) })
-- Resolve the per-file line-table indices from the same .debug_line bytes;
-- this MUST run before any atom sequence is emitted (build_atom_sequence below
-- calls resolve_provenance_file_index when populating call-site / body rows).
init_file_index_lookup(elf_path)
-- Skip state lives in `corpus.atoms_by_name[*].debug_skip` (whole-atom) and `atom.paths.invocations[*].debug_skip` (per-invocation). -- Skip state lives in `corpus.atoms_by_name[*].debug_skip` (whole-atom) and `atom.paths.invocations[*].debug_skip` (per-invocation).
-- `corpus` is the sole canonical source projection. -- `corpus` is the sole canonical source projection.
local corpus = (ctx.shared and ctx.shared.corpus) or {} local corpus = (ctx.shared and ctx.shared.corpus) or {}