overkill sio cruft, not keeping.

This commit is contained in:
ed
2026-08-03 10:12:06 -04:00
parent 9eb696ece8
commit 8282f8e902
7 changed files with 958 additions and 37 deletions
+6 -6
View File
@@ -33,8 +33,6 @@ enum {
#define pad0_(btn_id) (btn_id << Pad0)
#define pad1_(btn_id) (btn_id << Pad1)
void pad_init(U4 mode) asm("PadInit");
U4 pad_read(U4 id) asm("PadRead");
/* ============================================================
* SIO0 raw controller polling surface (Phase 1 scaffolding)
@@ -130,10 +128,12 @@ enum {
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 */
#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
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) */
+3
View File
@@ -22,6 +22,7 @@ WORD_COUNT(call_reg, 1)
WORD_COUNT(call_addr, 1)
WORD_COUNT(branch_le_zero, 1)
WORD_COUNT(branch_equal, 1)
WORD_COUNT(branch_ne, 1)
WORD_COUNT(add_ui, 1)
WORD_COUNT(set_lt_u, 1)
WORD_COUNT(set_lt_s, 1)
@@ -29,7 +30,9 @@ WORD_COUNT(set_lt_si, 1)
WORD_COUNT(set_lt_ui, 1)
WORD_COUNT(load_word, 1)
WORD_COUNT(load_half_u, 1)
WORD_COUNT(load_byte_u, 1)
WORD_COUNT(store_word, 1)
WORD_COUNT(store_byte, 1)
WORD_COUNT(add_ui_self, 1)
WORD_COUNT(add_u_self, 1)
WORD_COUNT(add_u, 1)
+14
View File
@@ -63,3 +63,17 @@ 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)
@@ -35,5 +35,165 @@ enum {
atom_offset_bounds_chk_floor_f3_face_exit = _atom_offset_bounds_chk_floor_f3_face_exit,
};
// --- atom: pad_sio_step (398 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
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: pad_apply_input (60 words) ---
#define _atom_offset_dpad_left_exit_dpad_left 6
#define _atom_offset_dpad_right_exit_dpad_right 6
#define _atom_offset_dead_zone_low_check_dead_low_active 8
#define _atom_offset_dead_zone_high_check_dead_high_active 15
#define _atom_offset_dead_zone_skip_exit_stick 23
#define _atom_offset_end_low_exit_stick 11
enum {
atom_offset_dpad_left_exit_dpad_left = _atom_offset_dpad_left_exit_dpad_left,
atom_offset_dpad_right_exit_dpad_right = _atom_offset_dpad_right_exit_dpad_right,
atom_offset_dead_zone_low_check_dead_low_active = _atom_offset_dead_zone_low_check_dead_low_active,
atom_offset_dead_zone_high_check_dead_high_active = _atom_offset_dead_zone_high_check_dead_high_active,
atom_offset_dead_zone_skip_exit_stick = _atom_offset_dead_zone_skip_exit_stick,
atom_offset_end_low_exit_stick = _atom_offset_end_low_exit_stick,
};
// --- 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
+40 -7
View File
@@ -125,10 +125,14 @@ typedef Struct_(SMemory) {
U4 pad_state;
PadState pad[2]; /* raw_sio_pad_poll_20260802 — per-port */
PadSioInit pad_sio_init; /* raw_sio_pad_poll_20260802 — boot init */
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)
@@ -201,8 +205,6 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
{
TapeBuilder tb = tb_make(slice_ut_arr(smem.MemTape));
smem.pad_state = pad_read(0);
if (0) // Pad Input
{
if (pad0_signal_(Pad_Left)) {
@@ -217,8 +219,16 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
if (1) // Pad Input (Tape version)
{
tb.used = 0; tb_scope_run(& tb) {
tb_emit(& tb, pad_input_demo);
tb_data(& tb, smem.pad_state);
/* 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]));
/* 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));
}
@@ -454,13 +464,26 @@ int main(void)
}
TapeBuilder tb = tb_make(slice_ut_arr(smem.MemTape)); {
reset_graph(0);
register U4* io_base_addr rgcc(R_IO_BaseAddr) = u4_r(IO_BASE_ADDR);
register DoubleBuffer* screen_buf rgcc(R_ScreenBuf) = & smem.screen_buf;
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;
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]));
}
pad_init(0);
}
while (1) {
gknown S4* active_buf_id = & smem.active_buf_id;
@@ -472,3 +495,13 @@ 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;
}
+728 -6
View File
@@ -2,11 +2,11 @@
# include "duffle/gen/duffle.macs.h"
# include "duffle/gen/duffle.offsets.h"
# include "duffle/atom_dsl.h"
# include "duffle/pad.h"
# include "duffle/lottes_tape.h"
# include "duffle/mips.h"
# include "duffle/gte.h"
# include "duffle/gp.h"
# include "duffle/pad.h"
# include "duffle/word_count.metadata.h"
# include "psyq.h"
# include "gen/hello_joypad.offsets.h"
@@ -106,6 +106,36 @@ 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
@@ -382,32 +412,724 @@ internal MipsAtom_(sync_primitive_arena) atom_info(atom_bind(Binds_SyncPrimitive
* (Phase 2 fills the body.)
*/
internal MipsAtom_(pad_sio_init) atom_info(atom_phase(pad_init)
, atom_reads(R_T5, R_PadSioBase)
, atom_writes(R_T5)
, 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(),
};
/* ----- 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]. (Phase 3 fills the body.)
* 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)
) {
/* 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(),
};
/* ----- pad_apply_input -----
* Reads pad[0].buttons + pad[0].left_x; applies the input-semantics
* deltas to cube_rot.y + floor_rot.y. (Phase 4 fills the body.)
* 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):
* cube delta = (0x80 - left_x) >> 2 (range approx -32..+32)
* floor delta = (0x80 - left_x) >> 5 (range approx -4..+4)
* - D-pad + analog deltas add when used together.
*
* Convention (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).
*
* 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.
*/
internal MipsAtom_(pad_apply_input) atom_info(atom_bind(Binds_PadApplyInput)
, atom_reads(R_T0, R_T1, R_T2, R_T5)
, atom_reads(R_T0, R_T1, R_T2, R_T3, R_T4, R_T5, R_TapePtr)
, atom_writes(R_T1, R_T2)
) {
/* 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 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,
/* 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)),
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)),
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)),
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)),
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)),
/* Dead-zone check: skip analog if left_x in [0x70, 0x90] inclusive.
* Outside dead zone on LOW side: left_x < 0x70 (strictly).
* set_lt_u(R_T4, R_T3, R_T4=0x70) → R_T4 = (left_x < 0x70) ? 1 : 0. */
add_ui(R_T4, R_0, 0x70),
set_lt_u(R_T4, R_T3, R_T4),
branch_ne(R_T4, R_0, atom_offset(dead_zone_low_check, dead_low_active)),
add_ui(R_T4, R_0, 0x80), /* BD-slot: pre-load 0x80 for dead_low_active */
atom_label(dead_check_upper)
/* left_x >= 0x70 → check upper bound. */
load_byte_u(R_T3, R_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 */
/* 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,
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,
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),
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)),
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)),
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(),
};
+7 -18
View File
@@ -180,29 +180,18 @@ function link-modules { param([string[]]$link_modules, [string] $elf, [string[]
$link_args += ($f_link_pass_through_prefix + $f_link_mapfile + $map)
$link_args += ($f_link_pass_through_prefix + $f_link_start_group)
# raw_sio_pad_poll_20260802 — Task 5.1c surgical library-list trim.
# The 16 removed entries (c2, card, cd, comb, ds, gs, gun, hmd, math,
# mcrd, mcx, press, sio, snd, spu, tap) had LOAD lines in the map but
# ZERO .o files pulled in — they were unused. The 5 kept libraries
# (api, c, etc, gpu, gte) are required by the C-side calls in
# hello_joypad.c (reset_graph, draw_sync, vsync, etc.).
$libraries = @(
"api",
"c",
"c2",
"card",
"cd",
"comb",
"ds",
"etc",
"gpu",
"gs",
"gte",
"gun",
"hmd",
"math",
"mcrd",
"mcx",
"pad",
"press",
"sio",
"snd",
"spu",
"tap"
"gte"
)
foreach ($lib in $libraries) {
$link_args += ($f_link_lib + $lib)