phase1.0a: sio_tick(int cycles) signature + cycle-paced macro stub

Pure signature change. No behavior change. Foundation for Phase 1.0b/c/d
which will incrementally add cycle-paced SIO byte timing.

- sio_tick(void) -> sio_tick(int cycles)
- legacy body ignores cycles via (void)cycles, runs original access-paced
  IRQ countdown verbatim
- SIO_MODEL_CYCLE_PACED macro defined and defaults to 0; reserved for
  future cycle-paced model (no #if branches engaged in this commit)
- callers updated to pass 0: sio_read, sio_write end, sio_card_burst_drain
  loop body in sio.c; mmio_read32/16/8 I_STAT-read paths in memory.c;
  interp_check_interrupts in psx_interpreter.c

Validation against committed baseline 8b0fc34: identical state at t=20s
(tx_writes=976, exception_entries=1215) and t=30s (tx_writes=5352);
pad polling armed 5768/5768 no_ack=0; 4x CROSS press succeeds; card txn
distribution matches baseline (10 txns reach bytes=15 ts=10 READ_DATA,
matching the SELECT-deassert fix from the previous commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-04-29 16:24:53 -07:00
parent 8b0fc34714
commit 5670423d22
4 changed files with 27 additions and 12 deletions
+8 -3
View File
@@ -16,9 +16,14 @@ void sio_init(void);
uint32_t sio_read(uint32_t addr);
void sio_write(uint32_t addr, uint32_t value);
/* Advance SIO timing by one step. Called from psx_check_interrupts().
* Fires pending IRQ7 after the transfer delay expires. */
void sio_tick(void);
/* Advance SIO timing.
*
* Phase 1.0a signature change. The cycles arg is reserved for a future
* cycle-paced model (SIO_MODEL_CYCLE_PACED=1). With the default macro
* setting (SIO_MODEL_CYCLE_PACED=0) the cycles arg is ignored and the
* body behaves identically to the prior tick-once-per-call semantics —
* fires pending IRQ7 after the transfer delay expires. */
void sio_tick(int cycles);
/* Update pad button state. Buttons use PS1 convention: 0=pressed, 1=released.
Bit layout: SELECT, L3, R3, START, UP, RIGHT, DOWN, LEFT,
+3 -3
View File
@@ -217,7 +217,7 @@ static uint32_t mmio_read32(uint32_t addr) {
return ram_size_reg;
}
/* Interrupts: 0x1F801070, 0x1F801074 */
if (addr == 0x1F801070u) { sio_tick(); return i_stat; }
if (addr == 0x1F801070u) { sio_tick(0); return i_stat; }
if (addr == 0x1F801074u) return i_mask;
/* DMA: 0x1F801080..0x1F8010FF */
if (addr >= 0x1F801080u && addr <= 0x1F8010FFu) {
@@ -306,7 +306,7 @@ static uint16_t mmio_read16(uint32_t addr) {
return (uint16_t)sio_read(addr);
}
/* Interrupts */
if (addr == 0x1F801070u) { sio_tick(); return (uint16_t)i_stat; }
if (addr == 0x1F801070u) { sio_tick(0); return (uint16_t)i_stat; }
if (addr == 0x1F801074u) return (uint16_t)i_mask;
/* Timers: 0x1F801100..0x1F80112F */
if (addr >= 0x1F801100u && addr <= 0x1F80112Fu) {
@@ -362,7 +362,7 @@ static void mmio_write16(uint32_t addr, uint16_t val) {
static uint8_t mmio_read8(uint32_t addr) {
/* Interrupts: 0x1F801070..0x1F801077 (I_STAT, I_MASK) */
if (addr >= 0x1F801070u && addr <= 0x1F801077u) {
if (addr < 0x1F801074u) sio_tick();
if (addr < 0x1F801074u) sio_tick(0);
uint32_t val = (addr < 0x1F801074u) ? i_stat : i_mask;
return (uint8_t)(val >> (8 * (addr & 3)));
}
+3 -2
View File
@@ -513,8 +513,9 @@ static void exec_one(CPUState* cpu) {
/* ---- Interrupt injection ---- */
static void interp_check_interrupts(CPUState* cpu) {
/* SIO tick. */
sio_tick();
/* SIO tick. cycles=0 in legacy mode (Phase 1.0a signature change;
* body ignores cycles, behaves identically to original). */
sio_tick(0);
/* VBlank / timer scheduling. */
s_vblank_count++;
+13 -4
View File
@@ -905,7 +905,7 @@ uint32_t sio_read(uint32_t addr) {
* a tight BIOS polling loop within a single function. Advancing on
* register access ensures the IRQ fires in time for the BIOS's
* "clear, delay, check" card detection sequence. */
sio_tick();
sio_tick(0);
switch (addr) {
case 0x1F801040: /* SIO_RX_DATA */
@@ -1082,7 +1082,7 @@ void sio_write(uint32_t addr, uint32_t value) {
/* Advance delayed IRQ after write processing (not before).
* This ensures CTRL ACK writes clear the old IRQ before the
* pending one fires. */
sio_tick();
sio_tick(0);
}
void sio_get_freeze_diag(int *out_irq_pending, int *out_irq_countdown,
@@ -1159,7 +1159,7 @@ static int sio_card_burst_drain(int max_iters) {
reason = 2;
break;
}
sio_tick();
sio_tick(0);
}
if (iters == max_iters) reason = 3;
@@ -1188,7 +1188,16 @@ void sio_get_burst_stats(uint64_t out[10]) {
out[9] = 0;
}
void sio_tick(void) {
/* Phase 1.0a: SIO_MODEL_CYCLE_PACED defaulting to 0 keeps the legacy
* access-paced behavior unchanged. The cycles arg on sio_tick is
* reserved for a future cycle-paced model and is ignored under
* macro=0. */
#ifndef SIO_MODEL_CYCLE_PACED
#define SIO_MODEL_CYCLE_PACED 0
#endif
void sio_tick(int cycles) {
(void)cycles; /* reserved for cycle-paced model (Phase 1.0c+) */
if (sio_irq_pending && sio_irq_countdown > 0) {
sio_irq_countdown--;
if (sio_irq_countdown == 0) {