diff --git a/code/duffle/gen/duffle.macs.h b/code/duffle/gen/duffle.macs.h index 1c78eed..218ed79 100644 --- a/code/duffle/gen/duffle.macs.h +++ b/code/duffle/gen/duffle.macs.h @@ -61,7 +61,7 @@ WORD_COUNT(mac_gte_load_tri_verts, 18) , load_word( R_AT, R_T1, O_(PolyTag,code)) /* AT = old_ot_head */ \ , load_upper_i(R_V0, (S_(Poly_F3)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits) /* V0 = (5 - 1) << 24 = 4 << 24 */ \ , mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)) /* Strip upper 8 bits (length from prev cell) → keep only low 24 */ \ -, or( R_AT, R_AT, R_V0) /* Merge length */ \ +, or_u( R_AT, R_AT, R_V0) /* Merge length */ \ , store_word( R_AT, R_PrimCursor, O_(PolyTag,code)) /* prim->tag = packed(prim_length, old_addr) */ \ , shift_lleft( R_AT, R_PrimCursor, S_(PolyTag_len_bits)) /* AT = (prim_length << 24) | old_addr */ \ , shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)) \ @@ -76,7 +76,7 @@ WORD_COUNT(mac_insert_ot_tag_f3, 11) , load_word( R_AT, R_T1, O_(PolyTag,code)) /* AT = old_ot_head */ \ , load_upper_i(R_V0, (S_(Poly_G4)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits) /* V0 = (9 - 1) << 24 = 8 << 24 */ \ , mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)) /* Strip upper 8 bits (length from prev cell) → keep only low 24 */ \ -, or( R_AT, R_AT, R_V0) /* Merge length */ \ +, or_u( R_AT, R_AT, R_V0) /* Merge length */ \ , store_word( R_AT, R_PrimCursor, O_(PolyTag,code)) /* prim->tag = packed(prim_length, old_addr) */ \ , shift_lleft( R_AT, R_PrimCursor, S_(PolyTag_len_bits)) /* AT = (prim_length << 24) | old_addr */ \ , shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)) \ diff --git a/code/duffle/gp.tape.c b/code/duffle/gp.tape.c new file mode 100644 index 0000000..e69de29 diff --git a/code/duffle/gte.tape.c b/code/duffle/gte.tape.c new file mode 100644 index 0000000..e69de29 diff --git a/code/duffle/lottes_tape.h b/code/duffle/lottes_tape.h index 363ed82..8f70ae6 100644 --- a/code/duffle/lottes_tape.h +++ b/code/duffle/lottes_tape.h @@ -10,10 +10,10 @@ # include "gen/duffle.offsets.h" #endif -typedef U4 const MipsCode; +typedef U4 const MipsCode; // Underlying type to mips asm words. 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) = // Used for components with no args (e.g., ac_load_tri_indices) or identifier-args (hardcoded register names). @@ -23,9 +23,9 @@ typedef Slice_MipsCode MipsAtom; #define MipsAtomComp_(sym) MipsCode sym [] align_(4) = // 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: -// 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); } // Auto-generated component macros (/gen//.macs.h) are included manually by the unity build. @@ -59,12 +59,12 @@ enum { 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_TScratch10 = R_V0, - // R_TScratch10 = R_V1, - // R_TScratch11 = R_A0, - // R_TScratch12 = R_A1, - // R_TScratch13 = R_A3, + // 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) @@ -74,25 +74,27 @@ enum { /* --------------------------------------------------------------------------- * TAPE DRIVE ABI & REGISTER ALIASES (the enum moved earlier; see below) * ---------------------------------------------------------------------------*/ +typedef Slice_(MipsAtom); typedef Slice_MipsAtom Tape; /* The 'Exit' Atom */ 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 */ -FI_ void tape_run(Slice_MipsCode tape) { register U4* tape_ptr 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( load_word( R_AtomJmp, R_TapePtr, 0) /* Bootstrap the first jump */ - , add_ui_self(R_TapePtr, S_(MipsCode)) /* Advance tape */ + , add_ui_self(R_TapePtr, S_(MipsAtom)) /* Advance tape */ , call_reg( R_AtomJmp) /* jalr $t9 */ , nop /* Branch delay slot */ ) asm_rpins, r_use(tape_ptr) asm_clobber: - rlit(R_AT) - , 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) - , clb_mem_drain + 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), + clb_mem_drain ); } typedef Relative_(FArena) Struct_(TapeBuilder) { U4 ptr; U4 capacity; U4 used; }; @@ -100,12 +102,13 @@ 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(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_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_ Slice_MipsCode tb_slice(TapeBuilder tb) { 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_ 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)) FI_ void tb_scope_run_end(TapeBuilder* tb) { tb_emit(tb,tape_exit); tape_run(tb_slice(tb[0])); } @@ -159,7 +162,7 @@ MipsAtomComp_(ac_insert_ot_tag_f3) { load_word( R_AT, R_T1, O_(PolyTag,code)), // AT = old_ot_head load_upper_i(R_V0, (S_(Poly_F3)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits), // V0 = (5 - 1) << 24 = 4 << 24 mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)), // Strip upper 8 bits (length from prev cell) → keep only low 24 - or( R_AT, R_AT, R_V0), // Merge length + or_u( R_AT, R_AT, R_V0), // Merge length store_word( R_AT, R_PrimCursor, O_(PolyTag,code)), // prim->tag = packed(prim_length, old_addr) shift_lleft( R_AT, R_PrimCursor, S_(PolyTag_len_bits)), // AT = (prim_length << 24) | old_addr shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)), @@ -174,7 +177,7 @@ MipsAtomComp_(ac_insert_ot_tag_g4) { load_word( R_AT, R_T1, O_(PolyTag,code)), // AT = old_ot_head load_upper_i(R_V0, (S_(Poly_G4)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits), // V0 = (9 - 1) << 24 = 8 << 24 mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)), // Strip upper 8 bits (length from prev cell) → keep only low 24 - or( R_AT, R_AT, R_V0), // Merge length + or_u( R_AT, R_AT, R_V0), // Merge length store_word( R_AT, R_PrimCursor, O_(PolyTag,code)), // prim->tag = packed(prim_length, old_addr) shift_lleft( R_AT, R_PrimCursor, S_(PolyTag_len_bits)), // AT = (prim_length << 24) | old_addr shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)), @@ -183,7 +186,7 @@ MipsAtomComp_(ac_insert_ot_tag_g4) { /* Words: 3; Emits one (cmd|color) word to R_PrimCursor at the given * 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, { load_upper_i(R_AT, (cmd) << 8 | (b)), or_i_self( R_AT, ((g) << 8) | (r)), @@ -192,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) * 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) }) /* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices to the F3. @@ -205,7 +208,7 @@ atom_dbg_skip MipsAtomComp_(ac_gte_store_f3) { /* 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. */ -FI_ MipsAtom ac_format_g4_color( +FI_ Slice_MipsCode ac_format_g4_color( U1 r0, U1 g0, U1 b0, U1 r1, U1 g1, U1 b1, U1 r2, U1 g2, U1 b2, @@ -259,7 +262,7 @@ FI_ void atombuilder_end(MipsAtomBuilder_R ab) { 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 @@ -313,66 +316,4 @@ internal MipsAtom_(set_gte_world) atom_info( 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 diff --git a/code/duffle/math.h b/code/duffle/math.h index c1ffcd7..5fcec87 100644 --- a/code/duffle/math.h +++ b/code/duffle/math.h @@ -11,6 +11,7 @@ enum { 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_(S2, 2); 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_S4) { S4 width; S4 height; }; +typedef Struct_(V2_U1) { U1 x; U1 y; }; typedef Struct_(V2_S2) { S2 x; S2 y; }; typedef Struct_(V2_S4) { S4 x; S4 y; }; typedef Struct_(V3_S2) { S2 x; S2 y; S2 z; S2 pad; }; diff --git a/code/duffle/memory.h b/code/duffle/memory.h index 88aefe9..f7b432f 100644 --- a/code/duffle/memory.h +++ b/code/duffle/memory.h @@ -67,8 +67,8 @@ typedef Slice_(B1); #define slice_end(slice) ((slice).ptr + (slice).len) #define S_slice(s) ((s).len * S_((s).ptr[0])) -#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(ptr,len) slice_ut_(u4_(ptr), u4_(len)) +#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_iter(container, iter) (T_((container).ptr) iter = (container).ptr; iter != slice_end(container); ++ iter) diff --git a/code/duffle/mips.h b/code/duffle/mips.h index 53e1d33..a603781 100644 --- a/code/duffle/mips.h +++ b/code/duffle/mips.h @@ -336,10 +336,10 @@ enum { _BitOffsets = 0 /* Logic Opcodes */ -#define and(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_and) -#define or(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_or) -#define xor(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_xor) -#define nor(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_nor) +#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 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 or_u_self(rd_rs, rt) enc_r(op_special, (rd_rs), (rt), (rd_rs), 0, fc_or) diff --git a/code/duffle/pad.h b/code/duffle/pad.h index d5ebb20..0086edc 100644 --- a/code/duffle/pad.h +++ b/code/duffle/pad.h @@ -3,24 +3,26 @@ # 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_L2, 0), - Bit_(Pad_R2, 1), - Bit_(Pad_L1, 2), - Bit_(Pad_R1, 3), - Bit_(Pad_Triangle, 4), - Bit_(Pad_Circle, 5), - Bit_(Pad_Cross, 6), - Bit_(Pad_Square, 7), - Bit_(Pad_Select, 8), - Bit_(Unused_PadI, 9), - Bit_(Unused_PadJ, 10), - Bit_(Pad_Start, 11), - Bit_(Pad_Up, 12), - Bit_(Pad_Right, 13), - Bit_(Pad_Down, 14), - Bit_(Pad_Left, 15), + 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 { @@ -33,111 +35,39 @@ enum { #define pad0_(btn_id) (btn_id << Pad0) #define pad1_(btn_id) (btn_id << Pad1) - /* ============================================================ - * SIO0 raw controller polling surface (Phase 1 scaffolding) - * Source of truth: docs/psx-spx/docs/serialinterfacessio.md - * docs/psx-spx/docs/controllersandmemorycards.md - * https://github.com/Lameguy64/PSn00bSDK (spi.c) + * BIOS pad-buffer subsystem: docs/psx-spx/docs/kernelbios.md (B(12h) + B(13h)) * ============================================================ */ enum { - /* SIO0 register offsets relative to IOBASE 0xBF800000 */ - pad_SIO_DATA_OFFSET = 0x1040, /* 8-bit data (TX write / RX read) */ - pad_SIO_STAT_OFFSET = 0x1044, /* 16-bit status */ - pad_SIO_MODE_OFFSET = 0x1048, /* 16-bit mode */ - pad_SIO_CTRL_OFFSET = 0x104A, /* 16-bit control */ - pad_SIO_BAUD_OFFSET = 0x104E, /* 16-bit baud */ - - /* SIO_CTRL bit flags */ - pad_SIO_CTRL_RESET = 0x0040, /* reset most SIO registers */ - pad_SIO_CTRL_TX_ENABLE = 0x0001, /* TX enable */ - pad_SIO_CTRL_DTR_CS = 0x0002, /* DTR drives /CS on controller */ - pad_SIO_CTRL_ACK = 0x0010, /* acknowledge / clear status */ - pad_SIO_CTRL_DSR_IRQ = 0x1000, /* enable /ACK IRQ (unused; polling) */ - - /* SIO_STAT bit flags */ - pad_SIO_STAT_TX_READY = 0x0001, - pad_SIO_STAT_RX_NOT_EMPTY = 0x0002, - pad_SIO_STAT_TX_IDLE = 0x0004, - pad_SIO_STAT_DSR_ACK = 0x0080, - - /* SIO init values (per docs/psx-spx/docs/serialinterfacessio.md) */ - pad_SIO_MODE_INIT = 0x000D, /* MUL1, 8-bit, no parity, idle-high */ - pad_SIO_BAUD_INIT = 0x0088, /* ~250 kHz standard rate */ - pad_SIO_CTRL_CLEANUP = 0x0010, /* raise /CS + clear stale status */ - - /* Controller protocol bytes (per docs/psx-spx/docs/controllersandmemorycards.md) */ - pad_PROTO_ADDR = 0x01, - pad_PROTO_CMD_READ = 0x42, - pad_PROTO_CMD_ENTERCFG = 0x43, - pad_PROTO_CMD_SETLED = 0x44, - pad_PROTO_PREFIX = 0x5A, - pad_PROTO_PREFIX_QUIRK = 0x00, /* DualShock: Analog-button swap after cfg */ - - /* Controller response ID nibble (low 4 bits of ID byte) */ - pad_PROTO_ID_DIGITAL = 0x41, - pad_PROTO_ID_ANALOG_STK = 0x53, - pad_PROTO_ID_ANALOG = 0x73, - pad_PROTO_ID_CONFIG = 0xF3, - - /* Per-state constants */ - pad_SIO_SETTLE_BEFORE_TX = 1000, /* iterations after CTRL=0x0010 */ - pad_SIO_SETTLE_AFTER_TX = 2000, /* iterations after CTRL=port_select */ - pad_SIO_WAIT_BUDGET = 4096, /* max iterations per /ACK or RX wait */ - pad_SIO_CFG_MAX_ATTEMPTS = 3, /* DualShock config retries before reject */ + PAD_BIOS_RAW_SIZE = 0x22, +}; +typedef Struct_(PadBiosRaw) { + U1 bytes[PAD_BIOS_RAW_SIZE]; }; -/* Pad status enum (per-port outcome) */ -typedef Enum_(U4, PadSioStatus) { - PadSioStatus_Disconnected, - PadSioStatus_Digital, - PadSioStatus_AnalogStick, - PadSioStatus_Analog, - PadSioStatus_ConfigRejected, - PadSioStatus_Invalid, - PadSioStatus_TxTimeout, - PadSioStatus_RxTimeout, - PadSioStatus_AckTimeout, +typedef Enum_(U4, PadStatus) { + PadStatus_Disconnected, + PadStatus_Digital, + PadStatus_AnalogStick, + PadStatus_AnalogPad, + PadStatus_Unsupported, + PadStatus_Pending, + PadStatus_Invalid, }; -/* PadState — per-port runtime state */ +/* 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) { - PadSioStatus status; - U4 buttons; - U1 left_x; - U1 left_y; - U1 right_x; - U1 right_y; - U4 attempt; + 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 */ }; - -/* PadSioInit — boot-time C-side pointer table passed into the tape */ -typedef Struct_(PadSioInit) { - U4 sio_base_addr[2]; - PadState* pad_state_ptr[2]; -}; - -/* Register aliases for the new atoms (post-2026-07-10 ABI) */ -#ifndef atom_reg -#define atom_reg /* atom_reg: opt the preceding enum entry into the DWARF registry */ -#endif - -enum { - R_PadSioBase = R_T6 atom_reg, /* caller-pinned IO_BASE_ADDR */ - R_PadState = R_T7 atom_reg, /* caller-pinned PadState* */ - R_PadStatus = R_T4 atom_reg, /* scratch for status reads */ - R_PadCountdown = R_T5 atom_reg, /* scratch for countdown budget */ - R_DiagPinScratch = R_T3 atom_reg, /* raw_sio_pad_poll_20260802 — diag scratch address */ -#define R_PadSioBase_Code R_T6_Code -#define R_PadState_Code R_T7_Code -#define R_PadStatus_Code R_T4_Code -#define R_PadCountdown_Code R_T5_Code -#define R_DiagPinScratch_Code R_T3_Code -}; - -/* Address of SIO0 block in KSEG1 (the PS1-side uncached mirror) */ -enum { - pad_IO_KSEG1_BASE = 0xBF800000, -}; - diff --git a/code/duffle/pad.tape.c b/code/duffle/pad.tape.c new file mode 100644 index 0000000..e69de29 diff --git a/code/duffle/word_count.metadata.h b/code/duffle/word_count.metadata.h index a4ca955..2229268 100644 --- a/code/duffle/word_count.metadata.h +++ b/code/duffle/word_count.metadata.h @@ -40,6 +40,7 @@ WORD_COUNT(or_i, 1) WORD_COUNT(or_i_self, 1) WORD_COUNT(or_u, 1) WORD_COUNT(or_u_self, 1) +WORD_COUNT(nor_u, 1) WORD_COUNT(shift_lleft, 1) WORD_COUNT(shift_lleft_self, 1) WORD_COUNT(shift_lright, 1) diff --git a/code/hello_gte/hello_gte.c b/code/hello_gte/hello_gte.c index 72a5206..36de40e 100644 --- a/code/hello_gte/hello_gte.c +++ b/code/hello_gte/hello_gte.c @@ -122,7 +122,7 @@ global SMemory smem; extern SMemory smem; // TODO(Ed): -FI_ U4* spad_warm(MipsAtom atom) { +FI_ U4* spad_warm(Slice_MipsCode atom) { return nullptr; } diff --git a/code/hello_gte/hello_gte.tape.c b/code/hello_gte/hello_gte.tape.c index 655f95b..6a69826 100644 --- a/code/hello_gte/hello_gte.tape.c +++ b/code/hello_gte/hello_gte.tape.c @@ -16,6 +16,68 @@ #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) { U4 PrimCursor; V4_S2* FaceCursor; diff --git a/code/hello_joypad/gen/hello_joypad.macs.h b/code/hello_joypad/gen/hello_joypad.macs.h index dc2b9b5..137868b 100644 --- a/code/hello_joypad/gen/hello_joypad.macs.h +++ b/code/hello_joypad/gen/hello_joypad.macs.h @@ -9,6 +9,12 @@ #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)) \ @@ -63,17 +69,3 @@ WORD_COUNT(mac_put_disp_env, 15) , mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port) WORD_COUNT(mac_put_draw_env, 48) -#define mac_pad_sio_write_pad_state(status_val, state_ptr_reg, scratch_reg) \ - 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)) -WORD_COUNT(mac_pad_sio_write_pad_state, 9) - diff --git a/code/hello_joypad/gen/hello_joypad.offsets.h b/code/hello_joypad/gen/hello_joypad.offsets.h index 3ceff60..f9831b6 100644 --- a/code/hello_joypad/gen/hello_joypad.offsets.h +++ b/code/hello_joypad/gen/hello_joypad.offsets.h @@ -5,16 +5,6 @@ #pragma region hello_joypad.tape -// --- atom: pad_input_demo (24 words) --- - -#define _atom_offset_pad_left_exit_pad_left 6 -#define _atom_offset_pad_right_exit_pad_right 6 - -enum { - atom_offset_pad_left_exit_pad_left = _atom_offset_pad_left_exit_pad_left, - atom_offset_pad_right_exit_pad_right = _atom_offset_pad_right_exit_pad_right, -}; - // --- atom: cube_g4_face (77 words) --- #define _atom_offset_cull_cube_g4_face_exit 42 @@ -35,126 +25,30 @@ enum { atom_offset_bounds_chk_floor_f3_face_exit = _atom_offset_bounds_chk_floor_f3_face_exit, }; -// --- atom: pad_sio_step (398 words) --- +// --- atom: pad_bios_snapshot (88 words) --- -#define _atom_offset_settle_pre_port0_settle_pre_port0 -3 -#define _atom_offset_settle_post_port0_settle_post_port0 -3 -#define _atom_offset_wait_ack0_port0_ack0_received_port0 12 -#define _atom_offset_continue_wait_ack0_port0_wait_ack0_port0 -6 -#define _atom_offset_skip_port0_from_ack0_port1_start 160 -#define _atom_offset_wait_ackrel0_port0_ack_released_port0 12 -#define _atom_offset_continue_wait_ackrel0_port0_wait_ackrel0_port0 -6 -#define _atom_offset_skip_port0_from_ackrel0_port1_start 142 -#define _atom_offset_wait_ack1_port0_ack1_received_port0 12 -#define _atom_offset_continue_wait_ack1_port0_wait_ack1_port0 -6 -#define _atom_offset_skip_port0_from_ack1_port1_start 123 -#define _atom_offset_wait_ackrel1_port0_ack_released1_port0 12 -#define _atom_offset_continue_wait_ackrel1_port0_wait_ackrel1_port0 -6 -#define _atom_offset_skip_port0_from_ackrel1_port1_start 105 -#define _atom_offset_wait_ack2_port0_ack2_received_port0 12 -#define _atom_offset_continue_wait_ack2_port0_wait_ack2_port0 -6 -#define _atom_offset_skip_port0_from_ack2_port1_start 86 -#define _atom_offset_wait_ackrel2_port0_ack_released2_port0 12 -#define _atom_offset_continue_wait_ackrel2_port0_wait_ackrel2_port0 -6 -#define _atom_offset_skip_port0_from_ackrel2_port1_start 68 -#define _atom_offset_wait_ack3_port0_ack3_received_port0 12 -#define _atom_offset_continue_wait_ack3_port0_wait_ack3_port0 -6 -#define _atom_offset_skip_port0_from_ack3_port1_start 49 -#define _atom_offset_wait_ackrel3_port0_ack_released3_port0 12 -#define _atom_offset_continue_wait_ackrel3_port0_wait_ackrel3_port0 -6 -#define _atom_offset_skip_port0_from_ackrel3_port1_start 31 -#define _atom_offset_wait_rx4_port0_rx4_received_port0 12 -#define _atom_offset_continue_wait_rx4_port0_wait_rx4_port0 -6 -#define _atom_offset_skip_port0_from_rx4_port1_start 12 -#define _atom_offset_settle_pre_port1_settle_pre_port1 -3 -#define _atom_offset_settle_post_port1_settle_post_port1 -3 -#define _atom_offset_wait_ack0_port1_ack0_received_port1 12 -#define _atom_offset_continue_wait_ack0_port1_wait_ack0_port1 -6 -#define _atom_offset_skip_port1_from_ack0_end_atom 160 -#define _atom_offset_wait_ackrel0_port1_ack_released_port1 12 -#define _atom_offset_continue_wait_ackrel0_port1_wait_ackrel0_port1 -6 -#define _atom_offset_skip_port1_from_ackrel0_end_atom 142 -#define _atom_offset_wait_ack1_port1_ack1_received_port1 12 -#define _atom_offset_continue_wait_ack1_port1_wait_ack1_port1 -6 -#define _atom_offset_skip_port1_from_ack1_end_atom 123 -#define _atom_offset_wait_ackrel1_port1_ack_released1_port1 12 -#define _atom_offset_continue_wait_ackrel1_port1_wait_ackrel1_port1 -6 -#define _atom_offset_skip_port1_from_ackrel1_end_atom 105 -#define _atom_offset_wait_ack2_port1_ack2_received_port1 12 -#define _atom_offset_continue_wait_ack2_port1_wait_ack2_port1 -6 -#define _atom_offset_skip_port1_from_ack2_end_atom 86 -#define _atom_offset_wait_ackrel2_port1_ack_released2_port1 12 -#define _atom_offset_continue_wait_ackrel2_port1_wait_ackrel2_port1 -6 -#define _atom_offset_skip_port1_from_ackrel2_end_atom 68 -#define _atom_offset_wait_ack3_port1_ack3_received_port1 12 -#define _atom_offset_continue_wait_ack3_port1_wait_ack3_port1 -6 -#define _atom_offset_skip_port1_from_ack3_end_atom 49 -#define _atom_offset_wait_ackrel3_port1_ack_released3_port1 12 -#define _atom_offset_continue_wait_ackrel3_port1_wait_ackrel3_port1 -6 -#define _atom_offset_skip_port1_from_ackrel3_end_atom 31 -#define _atom_offset_wait_rx4_port1_rx4_received_port1 12 -#define _atom_offset_continue_wait_rx4_port1_wait_rx4_port1 -6 -#define _atom_offset_skip_port1_from_rx4_end_atom 12 +#define _atom_offset_snap_root_skip_disconnected 10 +#define _atom_offset_disconnected_snap_end 68 +#define _atom_offset_case_2_id_dispatch 10 +#define _atom_offset_pending_snap_end 56 +#define _atom_offset_id_dispatch_try_analog_stick 13 +#define _atom_offset_id_dispatch_snap_end 41 +#define _atom_offset_try_analog_stick_try_analog_pad 14 +#define _atom_offset_analog_stick_snap_end 25 +#define _atom_offset_try_analog_pad_try_unsupported 13 +#define _atom_offset_analog_pad_snap_end 9 enum { - atom_offset_settle_pre_port0_settle_pre_port0 = _atom_offset_settle_pre_port0_settle_pre_port0, - atom_offset_settle_post_port0_settle_post_port0 = _atom_offset_settle_post_port0_settle_post_port0, - atom_offset_wait_ack0_port0_ack0_received_port0 = _atom_offset_wait_ack0_port0_ack0_received_port0, - atom_offset_continue_wait_ack0_port0_wait_ack0_port0 = _atom_offset_continue_wait_ack0_port0_wait_ack0_port0, - atom_offset_skip_port0_from_ack0_port1_start = _atom_offset_skip_port0_from_ack0_port1_start, - atom_offset_wait_ackrel0_port0_ack_released_port0 = _atom_offset_wait_ackrel0_port0_ack_released_port0, - atom_offset_continue_wait_ackrel0_port0_wait_ackrel0_port0 = _atom_offset_continue_wait_ackrel0_port0_wait_ackrel0_port0, - atom_offset_skip_port0_from_ackrel0_port1_start = _atom_offset_skip_port0_from_ackrel0_port1_start, - atom_offset_wait_ack1_port0_ack1_received_port0 = _atom_offset_wait_ack1_port0_ack1_received_port0, - atom_offset_continue_wait_ack1_port0_wait_ack1_port0 = _atom_offset_continue_wait_ack1_port0_wait_ack1_port0, - atom_offset_skip_port0_from_ack1_port1_start = _atom_offset_skip_port0_from_ack1_port1_start, - atom_offset_wait_ackrel1_port0_ack_released1_port0 = _atom_offset_wait_ackrel1_port0_ack_released1_port0, - atom_offset_continue_wait_ackrel1_port0_wait_ackrel1_port0 = _atom_offset_continue_wait_ackrel1_port0_wait_ackrel1_port0, - atom_offset_skip_port0_from_ackrel1_port1_start = _atom_offset_skip_port0_from_ackrel1_port1_start, - atom_offset_wait_ack2_port0_ack2_received_port0 = _atom_offset_wait_ack2_port0_ack2_received_port0, - atom_offset_continue_wait_ack2_port0_wait_ack2_port0 = _atom_offset_continue_wait_ack2_port0_wait_ack2_port0, - atom_offset_skip_port0_from_ack2_port1_start = _atom_offset_skip_port0_from_ack2_port1_start, - atom_offset_wait_ackrel2_port0_ack_released2_port0 = _atom_offset_wait_ackrel2_port0_ack_released2_port0, - atom_offset_continue_wait_ackrel2_port0_wait_ackrel2_port0 = _atom_offset_continue_wait_ackrel2_port0_wait_ackrel2_port0, - atom_offset_skip_port0_from_ackrel2_port1_start = _atom_offset_skip_port0_from_ackrel2_port1_start, - atom_offset_wait_ack3_port0_ack3_received_port0 = _atom_offset_wait_ack3_port0_ack3_received_port0, - atom_offset_continue_wait_ack3_port0_wait_ack3_port0 = _atom_offset_continue_wait_ack3_port0_wait_ack3_port0, - atom_offset_skip_port0_from_ack3_port1_start = _atom_offset_skip_port0_from_ack3_port1_start, - atom_offset_wait_ackrel3_port0_ack_released3_port0 = _atom_offset_wait_ackrel3_port0_ack_released3_port0, - atom_offset_continue_wait_ackrel3_port0_wait_ackrel3_port0 = _atom_offset_continue_wait_ackrel3_port0_wait_ackrel3_port0, - atom_offset_skip_port0_from_ackrel3_port1_start = _atom_offset_skip_port0_from_ackrel3_port1_start, - atom_offset_wait_rx4_port0_rx4_received_port0 = _atom_offset_wait_rx4_port0_rx4_received_port0, - atom_offset_continue_wait_rx4_port0_wait_rx4_port0 = _atom_offset_continue_wait_rx4_port0_wait_rx4_port0, - atom_offset_skip_port0_from_rx4_port1_start = _atom_offset_skip_port0_from_rx4_port1_start, - atom_offset_settle_pre_port1_settle_pre_port1 = _atom_offset_settle_pre_port1_settle_pre_port1, - atom_offset_settle_post_port1_settle_post_port1 = _atom_offset_settle_post_port1_settle_post_port1, - atom_offset_wait_ack0_port1_ack0_received_port1 = _atom_offset_wait_ack0_port1_ack0_received_port1, - atom_offset_continue_wait_ack0_port1_wait_ack0_port1 = _atom_offset_continue_wait_ack0_port1_wait_ack0_port1, - atom_offset_skip_port1_from_ack0_end_atom = _atom_offset_skip_port1_from_ack0_end_atom, - atom_offset_wait_ackrel0_port1_ack_released_port1 = _atom_offset_wait_ackrel0_port1_ack_released_port1, - atom_offset_continue_wait_ackrel0_port1_wait_ackrel0_port1 = _atom_offset_continue_wait_ackrel0_port1_wait_ackrel0_port1, - atom_offset_skip_port1_from_ackrel0_end_atom = _atom_offset_skip_port1_from_ackrel0_end_atom, - atom_offset_wait_ack1_port1_ack1_received_port1 = _atom_offset_wait_ack1_port1_ack1_received_port1, - atom_offset_continue_wait_ack1_port1_wait_ack1_port1 = _atom_offset_continue_wait_ack1_port1_wait_ack1_port1, - atom_offset_skip_port1_from_ack1_end_atom = _atom_offset_skip_port1_from_ack1_end_atom, - atom_offset_wait_ackrel1_port1_ack_released1_port1 = _atom_offset_wait_ackrel1_port1_ack_released1_port1, - atom_offset_continue_wait_ackrel1_port1_wait_ackrel1_port1 = _atom_offset_continue_wait_ackrel1_port1_wait_ackrel1_port1, - atom_offset_skip_port1_from_ackrel1_end_atom = _atom_offset_skip_port1_from_ackrel1_end_atom, - atom_offset_wait_ack2_port1_ack2_received_port1 = _atom_offset_wait_ack2_port1_ack2_received_port1, - atom_offset_continue_wait_ack2_port1_wait_ack2_port1 = _atom_offset_continue_wait_ack2_port1_wait_ack2_port1, - atom_offset_skip_port1_from_ack2_end_atom = _atom_offset_skip_port1_from_ack2_end_atom, - atom_offset_wait_ackrel2_port1_ack_released2_port1 = _atom_offset_wait_ackrel2_port1_ack_released2_port1, - atom_offset_continue_wait_ackrel2_port1_wait_ackrel2_port1 = _atom_offset_continue_wait_ackrel2_port1_wait_ackrel2_port1, - atom_offset_skip_port1_from_ackrel2_end_atom = _atom_offset_skip_port1_from_ackrel2_end_atom, - atom_offset_wait_ack3_port1_ack3_received_port1 = _atom_offset_wait_ack3_port1_ack3_received_port1, - atom_offset_continue_wait_ack3_port1_wait_ack3_port1 = _atom_offset_continue_wait_ack3_port1_wait_ack3_port1, - atom_offset_skip_port1_from_ack3_end_atom = _atom_offset_skip_port1_from_ack3_end_atom, - atom_offset_wait_ackrel3_port1_ack_released3_port1 = _atom_offset_wait_ackrel3_port1_ack_released3_port1, - atom_offset_continue_wait_ackrel3_port1_wait_ackrel3_port1 = _atom_offset_continue_wait_ackrel3_port1_wait_ackrel3_port1, - atom_offset_skip_port1_from_ackrel3_end_atom = _atom_offset_skip_port1_from_ackrel3_end_atom, - atom_offset_wait_rx4_port1_rx4_received_port1 = _atom_offset_wait_rx4_port1_rx4_received_port1, - atom_offset_continue_wait_rx4_port1_wait_rx4_port1 = _atom_offset_continue_wait_rx4_port1_wait_rx4_port1, - atom_offset_skip_port1_from_rx4_end_atom = _atom_offset_skip_port1_from_rx4_end_atom, + 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) --- @@ -175,25 +69,5 @@ enum { atom_offset_end_low_exit_stick = _atom_offset_end_low_exit_stick, }; -// --- atom: pad_sio_diag_byte_exchange (53 words) --- - -#define _atom_offset_diag_wait_ack0_diag_ack0_done 5 -#define _atom_offset_diag_wait_ack0_diag_wait_ack0 -6 -#define _atom_offset_diag_timeout_ack0_diag_timeout 24 -#define _atom_offset_diag_wait_ack1_diag_ack1_done 5 -#define _atom_offset_diag_wait_ack1_diag_wait_ack1 -6 -#define _atom_offset_diag_timeout_ack1_diag_timeout 11 -#define _atom_offset_diag_success_diag_done 7 - -enum { - atom_offset_diag_wait_ack0_diag_ack0_done = _atom_offset_diag_wait_ack0_diag_ack0_done, - atom_offset_diag_wait_ack0_diag_wait_ack0 = _atom_offset_diag_wait_ack0_diag_wait_ack0, - atom_offset_diag_timeout_ack0_diag_timeout = _atom_offset_diag_timeout_ack0_diag_timeout, - atom_offset_diag_wait_ack1_diag_ack1_done = _atom_offset_diag_wait_ack1_diag_ack1_done, - atom_offset_diag_wait_ack1_diag_wait_ack1 = _atom_offset_diag_wait_ack1_diag_wait_ack1, - atom_offset_diag_timeout_ack1_diag_timeout = _atom_offset_diag_timeout_ack1_diag_timeout, - atom_offset_diag_success_diag_done = _atom_offset_diag_success_diag_done, -}; - #pragma endregion hello_joypad.tape diff --git a/code/hello_joypad/hello_joypad.c b/code/hello_joypad/hello_joypad.c index 0c396d7..0cc4dac 100644 --- a/code/hello_joypad/hello_joypad.c +++ b/code/hello_joypad/hello_joypad.c @@ -123,25 +123,13 @@ typedef Struct_(SMemory) { Ent_Cube cube; Ent_Floor floor; - U4 pad_state; - - PadState pad[2]; /* raw_sio_pad_poll_20260802 — per-port */ - PadSioInit pad_sio_init; /* raw_sio_pad_poll_20260802 — boot init */ + PadBiosRaw pad_raw[2]; + PadState pad[2]; U4_V scratchpad; // d-cache }; global SMemory smem; extern SMemory smem; -U4 scratch_for_atom_diag_pin; /* raw_sio_pad_poll_20260802 — diag atom scratch */ - -#define pad0_signal_(btn_id) smem.pad_state & pad0_(btn_id) -#define pad1_signal_(btn_id) smem.pad_state & pad1_(btn_id) - - -// TODO(Ed): -FI_ U4* spad_warm(MipsAtom atom) { - return nullptr; -} I_ B1* prim__alloc(U4 type_width, Str8 type_name) { gknown PrimitiveArena* pa = & smem.primitives; @@ -153,6 +141,80 @@ I_ B1* prim__alloc(U4 type_width, Str8 type_name) { } #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 + ); + + /* BIOS clobbered $a0..$a3, $ra, and the volatile GPRs above. + * The compiler keeps raw0 + raw1 in callee-saved registers (or the outer frame's saved slots) + * because the asm volatile blocks only clobber the volatile GPRs above. + * 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) { reset_graph(0); @@ -197,21 +259,19 @@ void gp_display_frame(DoubleBuffer* screen_buf, S4* active_buf_id, U4* ordering_ active_buf_id[0] = ! active_buf_id[0]; // Swap current buffer } -void render(void) { -} - GCC_OPTIMIZATION_DISABLE void update(PrimitiveArena* pa, U4* ordering_buf) { TapeBuilder tb = tb_make(slice_ut_arr(smem.MemTape)); - if (0) // Pad Input + if (0) // Pad Input (dead — kept for the source-as-written record; references the deleted `pad_state` field) { - if (pad0_signal_(Pad_Left)) { + (void)Pad_Left; (void)Pad_Right; /* suppress unused-token warnings */ + if (false) { smem.cube.rot.y += 30; smem.floor.rot.y += 5; } - if (pad0_signal_(Pad_Right)) { + if (false) { smem.cube.rot.y -= 30; smem.floor.rot.y -= 5; } @@ -219,18 +279,18 @@ void update(PrimitiveArena* pa, U4* ordering_buf) if (1) // Pad Input (Tape version) { tb.used = 0; tb_scope_run(& tb) { - /* Per-frame SIO0 poll: pad_sio_step polls both ports; pad_apply_input applies rotation */ - tb_emit(& tb, pad_sio_init); - tb_emit(& tb, pad_sio_step); - tb_data(& tb, u4_(& smem.pad[0])); - tb_data(& tb, u4_(& smem.pad[1])); - tb_data(& tb, u4_(smem.pad_sio_init.sio_base_addr[0])); - tb_data(& tb, u4_(smem.pad_sio_init.sio_base_addr[1])); + /* BIOS-owned polling: per-frame snapshot of both ports. */ + tb_emit_(pad_bios_snapshot); + tb_data_(Binds_PadBiosSnapshot.raw, & smem.pad_raw[0]); + tb_data_(Binds_PadBiosSnapshot.state, & smem.pad[0]); + tb_emit_(pad_bios_snapshot); + tb_data_(Binds_PadBiosSnapshot.raw, & smem.pad_raw[1]); + tb_data_(Binds_PadBiosSnapshot.state, & smem.pad[1]); /* Per-frame rotation apply: consume pad[0].buttons + pad[0].left_x */ - tb_emit(& tb, pad_apply_input); - tb_data(& tb, u4_(& smem.pad[0])); - tb_data(& tb, u4_(& smem.cube.rot)); - tb_data(& tb, u4_(& smem.floor.rot)); + tb_emit_(pad_apply_input); + tb_data_(Binds_PadApplyInput.state, & smem.pad[0]); + tb_data_(Binds_PadApplyInput.cube_rot, & smem.cube.rot); + tb_data_(Binds_PadApplyInput.floor_rot, & smem.floor.rot); } } @@ -440,6 +500,9 @@ void update(PrimitiveArena* pa, U4* ordering_buf) } GCC_OPTIMIZATION_ENABLE +void render(void) { +} + int main(void) { smem = (SMemory){0}; @@ -450,7 +513,6 @@ int main(void) ent_cube128_init(& smem.cube.verts, & smem.cube.faces); { Ent_Cube* cube = & smem.cube; cube->rot = v3s2(0, 0, 0); - // cube->pos = v3s4(0, 0, 900); cube->scale = v3s4_fp_one(); cube->accel = v3s4(0, 1, 0); cube->pos = v3s4(0, -400, 1800); @@ -464,25 +526,14 @@ int main(void) } TapeBuilder tb = tb_make(slice_ut_arr(smem.MemTape)); { reset_graph(0); - pad_sio_init_setup(& smem.pad_sio_init, & smem.pad[0], & smem.pad[1]); - /* raw_sio_pad_poll_20260802 — pin the SIO base for the boot atom. - * Use the KSEG1 constant directly (not via pad_sio_init.sio_base_addr[0]) - * because rgcc binds the register to the variable's storage, not the - * value-at-call-site. Reading the field at tape-emit time would require - * an extra load, which the atom body can't do implicitly. */ - register U4 pad_io_base rgcc(R_PadSioBase) = pad_IO_KSEG1_BASE; - register U4* io_base_addr rgcc(R_IO_BaseAddr) = u4_r(IO_BASE_ADDR); - register U4* r_diag_scratch rgcc(R_DiagPinScratch) = & scratch_for_atom_diag_pin; - register DoubleBuffer* screen_buf rgcc(R_ScreenBuf) = & smem.screen_buf; + /* Direct BIOS: poll both ports during VBlank. */ + pad_bios_init_start(& smem.pad_raw[0], & smem.pad_raw[1]); + /* 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); - tb_emit(& tb, pad_sio_init); - tb_emit(& tb, pad_sio_step); /* initial baseline */ - tb_data(& tb, u4_(& smem.pad[0])); - tb_data(& tb, u4_(& smem.pad[1])); - tb_data(& tb, u4_(smem.pad_sio_init.sio_base_addr[0])); - tb_data(& tb, u4_(smem.pad_sio_init.sio_base_addr[1])); } } while (1) { @@ -495,13 +546,3 @@ int main(void) }; return 0; } - -/* raw_sio_pad_poll_20260802 — populate the boot-time pad SIO context. - * Populates the two PadState pointers + the KSEG1 SIO base address. */ -void pad_sio_init_setup(PadSioInit* init, PadState* s0, PadState* s1) -{ - init->sio_base_addr[0] = pad_IO_KSEG1_BASE; - init->sio_base_addr[1] = pad_IO_KSEG1_BASE; - init->pad_state_ptr[0] = s0; - init->pad_state_ptr[1] = s1; -} diff --git a/code/hello_joypad/hello_joypad.h b/code/hello_joypad/hello_joypad.h index 4189084..4b24989 100644 --- a/code/hello_joypad/hello_joypad.h +++ b/code/hello_joypad/hello_joypad.h @@ -3,6 +3,7 @@ # include "duffle/dsl.h" # include "duffle/math.h" # include "duffle/gp.h" +# include "duffle/pad.h" #endif enum { @@ -10,40 +11,16 @@ enum { OrderingTbl_Len = 2048 }; -#define ScreenRes_X 320 -#define ScreenRes_Y 240 -#define ScreenZ 320 -#define ScreenRes_CenterX (ScreenRes_X >> 1) -#define ScreenRes_CenterY (ScreenRes_Y >> 1) +enum { + ScreenRes_X = 320, + ScreenRes_Y = 240, + ScreenZ = 320, + ScreenRes_CenterX = (ScreenRes_X >> 1), + ScreenRes_CenterY = (ScreenRes_Y >> 1), +}; enum { fp_one = (1 << 12), }; #define v3s4_fp_one() v3s4(fp_one, fp_one, fp_one) - -#ifdef INTELLISENSE_DIRECTIVES -# include "duffle/pad.h" -#endif - -/* ============================================================ - * Raw SIO0 pad subsystem (raw_sio_pad_poll_20260802) - * ============================================================ */ - -typedef Struct_(Binds_PadSioStep) { - PadState* state0; - PadState* state1; - U4 sio_base_addr0; - U4 sio_base_addr1; -}; - -typedef Struct_(Binds_PadApplyInput) { - PadState* state; - V3_S2* cube_rot; - V3_S2* floor_rot; -}; - -/* Populates smem.pad_sio_init with the two PadState pointers and the - * KSEG1 SIO0 base. The caller (main()) runs this once at boot before - * the first tb_emit(pad_sio_init). */ -void pad_sio_init_setup(PadSioInit* init, PadState* s0, PadState* s1); diff --git a/code/hello_joypad/hello_joypad.tape.c b/code/hello_joypad/hello_joypad.tape.c index d0fa8e6..d711651 100644 --- a/code/hello_joypad/hello_joypad.tape.c +++ b/code/hello_joypad/hello_joypad.tape.c @@ -16,33 +16,37 @@ #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_ MipsAtom ac_store_v2s2(U4 rt_x, U4 rt_y, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_v2s2, { +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_ MipsAtom 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, { +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_ MipsAtom ac_store_rgb8(U1 rr, U1 rg, U1 rb, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_rgb8, { +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_ MipsAtom ac_gcmd_push(U4 cmd, U4 reg_transfer, U4 reg_base, U2 port) +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_ MipsAtom ac_put_disp_env(U4 reg_transfer, U4 reg_base, U2 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 @@ -53,7 +57,7 @@ MipsAtomComp_Proc_(ac_put_disp_env, { mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port), }) -FI_ MipsAtom ac_put_draw_env(U4 reg_transfer, U4 reg_base, U2 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. @@ -106,40 +110,72 @@ MipsAtomComp_Proc_(ac_put_draw_env, { mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port), }) -/* ac_pad_sio_write_pad_state - * raw_sio_pad_poll_20260802 — Task 3.1 helper component. - * 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_ MipsAtom 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)) -}) - #pragma endregion MACs #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), @@ -228,47 +264,6 @@ internal MipsAtom_(gp_screen_init) atom_info(atom_phase(screen_init), atom_reads mac_yield(), }; -enum { - R_PadInState = R_T4 atom_reg atom_type(U4), - R_PadSignal = R_T0 atom_reg atom_type(U4), - R_CubeRot = R_T1 atom_reg atom_type(V3_S2*), - R_FloorRot = R_T2 atom_reg atom_type(V3_S2*), -}; -typedef Struct_(Binds_PadInputDemo) { - U4 pad_state; - V3_S2* cube_rot; - V3_S2* floor_rot; -}; -internal MipsAtom_(pad_input_demo) atom_info(atom_bind(Binds_PadInputDemo) -, atom_reads(R_PadInState, R_CubeRot, R_FloorRot) -, atom_writes(R_CubeRot, R_FloorRot) -) { - load_word(R_PadInState, R_TapePtr, O_(Binds_PadInputDemo,pad_state)), - load_word(R_CubeRot, R_TapePtr, O_(Binds_PadInputDemo,cube_rot)), - load_word(R_FloorRot, R_TapePtr, O_(Binds_PadInputDemo,floor_rot)), - add_ui_self( R_TapePtr, S_(Binds_PadInputDemo)), - - and_i(R_PadSignal, R_PadInState, pad0_(Pad_Left)), branch_le_zero(R_PadSignal, atom_offset(pad_left, exit_pad_left)), - load_half( R_T5, R_CubeRot, O_(V3_S2,y)), // BD-Slot occupied - load_half( R_T6, R_FloorRot, O_(V3_S2,y)), - add_si( R_T5, R_T5, 30), - add_si( R_T6, R_T6, 5), - store_half(R_T5, R_CubeRot, O_(V3_S2,y)), - store_half(R_T6, R_FloorRot, O_(V3_S2,y)), - atom_label(exit_pad_left) - - and_i(R_PadSignal, R_PadInState, pad0_(Pad_Right)), branch_le_zero(R_PadSignal, atom_offset(pad_right, exit_pad_right)), - load_half( R_T5, R_CubeRot, O_(V3_S2,y)), // BD-Slot occupied - load_half( R_T6, R_FloorRot, O_(V3_S2,y)), - add_si( R_T5, R_T5, -30), - add_si( R_T6, R_T6, -5), - store_half(R_T5, R_CubeRot, O_(V3_S2,y)), - store_half(R_T6, R_FloorRot, O_(V3_S2,y)), - atom_label(exit_pad_right) - - mac_yield(), -}; - typedef Struct_(Binds_CubeTri) { U4 PrimCursor; V4_S2* FaceCursor; @@ -406,517 +401,167 @@ internal MipsAtom_(sync_primitive_arena) atom_info(atom_bind(Binds_SyncPrimitive mac_yield() }; -/* ----- 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.) +/* ----- 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) */ -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(), +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, }; - -/* ----- 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. - */ -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) +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) ) { - /* 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), + /* === Bind consumption (3 words): 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)), - /* 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)), + /* === Read raw[0] (status) + raw[1] (id) — 3 words. */ + load_byte_u(R_RawStatus, R_PadRaw, 0), + load_byte_u(R_RawId, R_PadRaw, 1), - /* ============== 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. */ +atom_label(snap_root) /* === Case 1: Disconnected (status == 0xFF) — 3 words. */ + add_ui(R_T4, R_0, 0xFF), branch_ne(R_RawStatus, R_T4, atom_offset(snap_root, skip_disconnected)), nop, - /* 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)), +atom_label(disconnected) /* === Disconnected body — 9 words. */ + add_ui(R_T4, R_0, PadStatus_Disconnected), + 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) - /* 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)), + /* === Case 2: Pending (status == 0 && id == 0) — 3 words. + * 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)), nop, - /* 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), +atom_label(pending) /* === Pending body — 9 words. */ + add_ui( R_T4, R_0, PadStatus_Pending), + 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, - /* 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(id_dispatch) /* === Case 3-6: ID dispatch — 3 words (id == 0x41 check). */ + add_ui(R_T4, R_0, 0x41), branch_ne(R_RawId, R_T4, atom_offset(id_dispatch, try_analog_stick)), nop, -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), + /* === Digital body — 15 words (status, buttons normalize, axes=0x80, id, branch). */ + add_ui( R_T4, R_0, PadStatus_Digital), + store_word( R_T4, R_PadState, O_(PadState,status)), + load_half_u(R_T4, R_PadRaw, 2 * S_(U1)), + 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)), - /* 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)), + /* 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, 0x41), + store_byte( R_T4, R_PadState, O_(PadState,id)), -atom_label(ack_released_port0) + 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, - /* === 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(try_analog_stick) /* === Case 4: AnalogStick (id == 0x53) — 3 words dispatch. */ + add_ui(R_T4, R_0, 0x53), + branch_ne(R_RawId, R_T4, atom_offset(try_analog_stick, try_analog_pad)), nop, -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), +atom_label(analog_stick) /* === AnalogStick body — 21 words. + * Axes are loaded as two halfwords: raw[6..7] → left_xy (sh at offset 8), raw[4..5] → right_xy (sh at offset 10). + * Each lhu is followed by a nop in its MIPS-I load delay slot; the sh consumes the halfword safely. */ + add_ui( R_T4, R_0, PadStatus_AnalogStick), + store_word( R_T4, R_PadState, O_(PadState,status)), + load_half_u( R_T4, R_PadRaw, 2 * S_(U1)), + 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)), + load_half_u( R_T4, R_PadRaw, 6 * S_(U1)), /* raw[6..7] = left_x | (left_y << 8) */ + store_half( R_T4, R_PadState, O_(PadState,left_x)), /* sh at offset 8 → left_x @ 8, left_y @ 9 */ + load_half_u( R_T4, R_PadRaw, 4 * S_(U1)), /* raw[4..5] = right_x | (right_y << 8) */ + store_half( R_T4, R_PadState, O_(PadState,right_x)), /* sh at offset 10 → right_x @ 10, right_y @ 11 */ + add_ui( R_T4, R_0, 0x53), + store_byte( R_T4, 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, - /* 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(try_analog_pad) /* === Case 5-6: AnalogPad (id & 0xF0 == 0x70) — 4 words dispatch. */ + 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)), nop, -atom_label(ack_released1_port0) +atom_label(analog_pad) /* === AnalogPad body — 21 words (same shape as AnalogStick with AnalogPad status). */ + add_ui( R_T4, R_0, PadStatus_AnalogPad), + store_word( R_T4, R_PadState, O_(PadState,status)), + load_half_u(R_T4, R_PadRaw, 2 * S_(U1)), + 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)), + load_half_u(R_T4, R_PadRaw, 6 * S_(U1)), /* raw[6..7] = left_x | (left_y << 8) */ + store_half( R_T4, R_PadState, O_(PadState,left_x)), /* sh at offset 8 → left_x @ 8, left_y @ 9 */ + load_half_u(R_T4, R_PadRaw, 4 * S_(U1)), /* raw[4..5] = right_x | (right_y << 8) */ + store_half( R_T4, R_PadState, O_(PadState,right_x)), /* sh at offset 10 → right_x @ 10, right_y @ 11 */ + store_byte( R_RawId, R_PadState, O_(PadState,id)), - /* === 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)), + 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(ack2_received_port0) - load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), +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. */ - /* 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) +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 per spec §"Input semantics": + * Reads pad[0].buttons + pad[0].left_x; + * Applies the input-semantics deltas to cube_rot.y + floor_rot.y per spec §"Input semantics": * - 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): @@ -925,211 +570,119 @@ atom_label(end_atom) * - D-pad + analog deltas add when used together. * * Convention (per spec line 137 + Task 3.1b buttons-fix): - * 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). + * 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. + * 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_T1, R_T2, R_T3, R_T4, R_T5, R_TapePtr) -, atom_writes(R_T1, R_T2) +, 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_T5, R_TapePtr, O_(Binds_PadApplyInput,state)), - load_word(R_T1, R_TapePtr, O_(Binds_PadApplyInput,cube_rot)), - load_word(R_T2, R_TapePtr, O_(Binds_PadApplyInput,floor_rot)), - add_ui_self(R_TapePtr, S_(Binds_PadApplyInput)), + 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. The following nop retires the MIPS-I - * load delay before the first and_i consumes R_T0. */ - load_word(R_T0, R_T5, O_(PadState,buttons)), - nop, + /* 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. - * branch_le_zero SKIPS the apply block when the button bit is - * NOT set in R_T0 (matches pad_input_demo convention). */ - 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_T1, O_(V3_S2,y)), /* BD-slot */ - load_half( R_T3, R_T2, O_(V3_S2,y)), + /* 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_T1, O_(V3_S2,y)), - store_half(R_T3, R_T2, O_(V3_S2,y)), + 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_T1, O_(V3_S2,y)), /* BD-slot */ - load_half( R_T3, R_T2, O_(V3_S2,y)), + 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_T1, O_(V3_S2,y)), - store_half(R_T3, R_T2, O_(V3_S2,y)), + 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 (per spec line 89). * Cube delta = (0x80 - left_x) >> 2; floor delta = (0x80 - left_x) >> 5. */ - load_byte_u(R_T3, R_T5, O_(PadState,left_x)), + 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). + /* 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 */ + 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_T5, O_(PadState,left_x)), /* reload */ - add_ui(R_T4, R_0, 0x90), - set_lt_u(R_T4, R_T4, R_T3), /* R_T4 = (0x90 < left_x) ? 1 : 0 → (left_x > 0x90) ? 1 : 0 */ - 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 */ + 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. */ - branch_equal(R_0, R_0, atom_offset(dead_zone_skip, exit_stick)), - nop, + // TODO(Ed): Lua metaprogram: Support jump instruction here.. + // jump(atom_offset(dead_zone_skip, exit_stick)), nop, atom_label(dead_low_active) - /* R_T4 = 0x80 (from first BD-slot). delta = 0x80 - left_x (positive). */ - load_byte_u(R_T3, R_T5, O_(PadState,left_x)), - sub_u(R_T3, R_T4, R_T3), /* R_T3 = 0x80 - left_x */ - shift_aright(R_T4, R_T3, 2), /* R_T4 = cube_delta */ - load_half(R_T0, R_T1, O_(V3_S2,y)), - add_u(R_T0, R_T0, R_T4), - store_half(R_T0, R_T1, O_(V3_S2,y)), - shift_aright(R_T4, R_T3, 5), /* R_T4 = floor_delta */ - load_half(R_T0, R_T2, O_(V3_S2,y)), - add_u(R_T0, R_T0, R_T4), - store_half(R_T0, R_T2, O_(V3_S2,y)), - branch_equal(R_0, R_0, atom_offset(end_low, exit_stick)), - nop, + load_byte_u(R_T3, R_PadStateT5, O_(PadState,left_x)), +// add_ui(R_T4, R_0, 0x80), // R_T4 = 0x80 (from BD-slot: dead_zone_low_check). + 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)), + add_u( R_T0, R_T0, R_T4), + store_half( R_T0, R_CubeRot, O_(V3_S2,y)), + /* R_T4 = floor_delta */ + shift_aright(R_T4, R_T3, 5), + load_half( R_T0, R_FloorRot, O_(V3_S2,y)), + 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_T4 = 0x80 (from second BD-slot). delta = 0x80 - left_x (signed negative). */ - load_byte_u(R_T3, R_T5, O_(PadState,left_x)), - sub_u(R_T3, R_T4, R_T3), + load_byte_u(R_T3, R_PadStateT5, O_(PadState,left_x)), +// add_ui(R_T4, R_0, 0x80), // R_T4 = 0x80 (from BD-slot: dead_zone_high_check) + 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_T1, O_(V3_S2,y)), - add_u(R_T0, R_T0, R_T4), - store_half(R_T0, R_T1, O_(V3_S2,y)), + load_half( R_T0, R_CubeRot, O_(V3_S2,y)), + add_u( R_T0, R_T0, R_T4), + store_half( R_T0, R_CubeRot, O_(V3_S2,y)), + shift_aright(R_T4, R_T3, 5), /* R_T4 = floor_delta (signed) */ - load_half(R_T0, R_T2, O_(V3_S2,y)), - add_u(R_T0, R_T0, R_T4), - store_half(R_T0, R_T2, O_(V3_S2,y)), + load_half( R_T0, R_FloorRot, O_(V3_S2,y)), + add_u( R_T0, R_T0, R_T4), + store_half( R_T0, R_FloorRot, O_(V3_S2,y)), + atom_label(exit_stick) - - mac_yield(), -}; - -/* ----- pad_sio_diag_pin ----- - * Per-frame diagnostic counter. The caller binds R_DiagPinScratch to - * scratch_for_atom_diag_pin for temporary gdb verification. - */ -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(R_T1, R_T1, R_T0), - store_word(R_T1, R_DiagPinScratch, 0), - mac_yield(), -}; - -/* ----- 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. - */ -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(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(), }; diff --git a/code/hello_joypad/sio.tape.c b/code/hello_joypad/sio.tape.c new file mode 100644 index 0000000..22144eb --- /dev/null +++ b/code/hello_joypad/sio.tape.c @@ -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 */ diff --git a/scripts/duffle.lua b/scripts/duffle.lua index 3ac84cb..0d97ac5 100644 --- a/scripts/duffle.lua +++ b/scripts/duffle.lua @@ -920,18 +920,26 @@ function M.tokenize_body(body) while scan <= len do local c = body:byte(scan) -- Terminator bytes (delimit a token at the top level): ',' = 0x2C, '\n' = 0x0A, ';' = 0x3B. - -- These also appear as separators between argument lists inside the parens/braces/brackets, + -- These also appear as separators between argument lists inside the parens/braces/brackets, -- so we stop the scan when we hit any of them. if c == BYTE_COMMA then break end if c == BYTE_NEWLINE 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. - 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_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. 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 scan = scan + 1 end @@ -1316,13 +1324,13 @@ M.OPERAND_READ_POSITIONS = { ["sub_s"] = {1, 2, 3}, ["sub_u"] = {1, 2, 3}, ["and_i"] = {1, 2}, - ["and_u"] = {1, 2, 3}, + ["and"] = {1, 2, 3}, ["or_i"] = {1, 2}, ["or_i_self"] = {1}, - ["or_u"] = {1, 2, 3}, - ["or_u_self"] = {1, 2}, + ["or"] = {1, 2, 3}, + ["or_self"] = {1, 2}, ["xor_i"] = {1, 2}, - ["xor_u"] = {1, 2, 3}, + ["xor"] = {1, 2, 3}, ["slt_s"] = {1, 2, 3}, ["slt_u"] = {1, 2, 3}, ["slt_si"] = {1, 2}, @@ -1469,14 +1477,14 @@ M.INSTRUCTION_LATENCY = { -- CPU ALU (single-cycle R3000A ops) ["nop"] = 1, ["nop2"] = 2, - ["add_ui"] = 1, ["add_ui_self"] = 1, - ["add_s"] = 1, ["add_si"] = 1, - ["add_u"] = 1, ["add_u_self"] = 1, - ["sub_u"] = 1, ["sub_s"] = 1, - ["and_i"] = 1, ["and_u"] = 1, - ["or_i"] = 1, ["or_i_self"] = 1, - ["or_u"] = 1, ["or_u_self"] = 1, - ["xor_i"] = 1, ["xor_u"] = 1, + ["add_ui"] = 1, ["add_ui_self"] = 1, + ["add_s"] = 1, ["add_si"] = 1, + ["add_u"] = 1, ["add_u_self"] = 1, + ["sub_u"] = 1, ["sub_s"] = 1, + ["and_i"] = 1, ["and"] = 1, + ["or_i"] = 1, ["or_i_self"] = 1, + ["or_u"] = 1, ["or_u_self"] = 1, + ["xor_i"] = 1, ["xor_u"] = 1, ["nor_u"] = 1, ["shift_lleft"] = 1, ["shift_lleft_self"] = 1, ["shift_lright"] = 1, @@ -1952,7 +1960,7 @@ M.GPR_VALUE_RULES = { -- Present register-form self variants. They are included here so a -- known value is not needlessly lost when these encoders are used. 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, }, } diff --git a/scripts/passes/components.lua b/scripts/passes/components.lua index 5009cbd..711bcde 100644 --- a/scripts/passes/components.lua +++ b/scripts/passes/components.lua @@ -4,7 +4,7 @@ --- 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, ---- 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 `.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. 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. 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. --- --- 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 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). --- --- @param source string