spu: implement SPU IRQ + CAUSE.IP2 combinational + mid-dispatch audio pump - #102
spu: implement SPU IRQ + CAUSE.IP2 combinational + mid-dispatch audio pump#102Alexbeav wants to merge 3 commits into
Conversation
Add spu_dma_read() for DMA4 read-direction transfers (SPUCNT transfer
mode 3). Previously the ch4 SPU read path wrote zeros — this is a
critical gap for multi-EXE titles that stash cross-EXE state in SPU RAM.
Proven: Gran Turismo carries its GAMESTATUS block ('GTos' magic +
CRC-CCITT over ~46KB) through SPU RAM across GTMENU->GTMAIN Exec
transitions. The zero stub failed the checksum, causing the game to
chain-exec gtos.exe (cold-boot fallback — presenting as the race-load
'reset to intro FMV' bug). With the fix, Arcade race loads and plays.
Also emit AUDIO_EV_DMA_READ event for cross-title probing and wire the
write path's audio_trace_event for symmetry.
… pump Three related hardware-correctness fixes that together resolve busy-wait deadlocks during sequenced audio sync: 1. SPU IRQ (spu.c): Real hardware raises I_STAT bit 9 when any SPU-RAM access touches the address in 0x1F801DA4, gated by SPUCNT.6. Adds triggers for FIFO writes, DMA4 r/w transfers, and voice ADPCM block fetches. Acknowledge via SPUCNT.6 clear. 2. CAUSE.IP2 combinational (interrupts.c, memory.c, traps.c): IP2 is a live mirror of the INTC line on hardware, not a latched bit. Refresh at every I_STAT ack, I_MASK write, IRQ raise, and HLE context restore. Prevents phantom IP2 from spinning the kernel exception dispatcher. 3. Mid-dispatch audio pump (interrupts.c, main.cpp): The SPU is autonomous -- it keeps consuming samples during CPU busy-waits. Pump audio from the VBlank edge (guest-cycle-budgeted) so a guest waiting on the SPU IRQ doesn't starve SPU time. Validated on Medal of Honor Underground (SLUS-01270): resolves Mission 1 weapon-draw freeze where the game busy-waits on kernel TestEvent for SPU IRQ delivery during voice playback.
|
Hello! Apologies for taking the time to check on this. Very grateful for the contributions. The CAUSE.IP2 finding was very valuable. I checked it against the Beetle oracle and it holds: IP2 mirrors the interrupt line and we were latching it and never clearing it. I've merged that, your SPU RAM DMA readback, and the mid-dispatch audio pump as part of #104, which also implements the reverb/noise/sweeps that #103 needed, so I'm closing this as superseded rather than stale. You're credited as co-author on the commit As a heads up, two changes I made on the way, for transparency: there was a second place latching IP2 that this missed (psx_interpreter.c, the interpreter delivery path), and the audio pump needed gating — calling it unconditionally from the VBlank edge bypasses our turbo-load mute, which would have played time-compressed audio on every load. I also couldn't reproduce the MOHU/Gran Turismo validation since we don't have those discs, so I verified the hardware behavior instead (the BIOS boot was actually a good candidate to test against). Going to close this PR for now since the work got folded into 104, but wanted to follow up and thank you and ensure you know you're properly attributed on the work carried forward there. Thank you and take care! |
…inational Closes the four gaps named in issue mstan#103 and lands the separable, verified parts of PR mstan#102. See docs/internal/SPU_FIDELITY_103.md for the per-piece disposition and the oracle-verification queue. SPU DSP (issue mstan#103), clean-room from hardware documentation: - Reverb: the documented 22050 Hz engine — vLIN/vRIN input, same-side and crossed different-side IIR reflections, 4-tap comb early echo, both all-pass filters, vLOUT/vROUT output, and the work-area address advance. Gated on SPUCNT bit 7 for WRITES only; reads, IRQ checks, address advance and output always run, as on hardware. Addressing is confined to [mBASE, 0x80000). Per-voice sends from EON, CD send from SPUCNT bits 0+2. - Noise: 16-bit LFSR with the documented parity feedback, clocked at the SPUCNT bits 8-13 rate; NON voices substitute it for their ADPCM sample. - Volume sweeps: all 24 voice L/R plus main L/R. Bit 15 selects sweep vs direct; sweep is a live envelope on the same rate machinery as ADSR, and a guest read of a sweeping register returns the live level. - Capture buffers: CD L/R and voice 1/3 output written to SPU RAM, so a game that parks the IRQ address there still gets its interrupt. - SPU IRQ (I_STAT bit 9): checked at every SPU RAM access class — FIFO, DMA both directions, ADPCM block fetch, capture writes, reverb work-area accesses — gated on SPUCNT bit 6, mirrored in SPUSTAT bit 6, acked by clearing SPUCNT bit 6. The CD-only fast path in spu_render was removed rather than extended: the SPU now has per-frame work that must run in every path, and an FMV (XA audio with zero active voices) is exactly the CD-with-reverb case. This is a clean-room implementation from the psx-spx/nocash register map and documented algorithm. Beetle's source was deliberately not consulted: it is GPL-2.0-or-later and this project is PolyForm Noncommercial. PR mstan#16 was parked for exactly that reason and PR mstan#13's reverb was separately wrong (it gated on bit 15 of dAPF1, an address offset, instead of SPUCNT bit 7). The one place this knowingly deviates from hardware is the 22.05 -> 44.1 kHz reconstruction filter, which the documentation does not specify; it is isolated in a single function and flagged for oracle comparison. CAUSE.IP2 (from PR mstan#102): IP2 is combinational on R3000A — it mirrors the interrupt line and falls the instant the guest acks I_STAT or masks the source. It was being OR'd in at delivery and never cleared, in TWO places (the compiled path and psx_interpreter.c, the latter missed by PR mstan#102), leaving a phantom pending interrupt that can spin a kernel dispatcher looping on CAUSE.IP & SR.IM. psx_irq_refresh_cause_ip2() is now the sole writer of bit 10, refreshed at raise, I_STAT ack, I_MASK write, HLE context restore and power-on. Verified against the Beetle oracle's irq.cpp/cpu.cpp rather than asserted, and covered by test_cause_ip2_combinational. Mid-dispatch audio pump (from PR mstan#102), with a fix: routed through a gate that mirrors sdl_audio_update's mute/sink decision. Pumping unconditionally from the VBlank edge, as submitted, would have pushed real audio during every turbo-load hard mute and defeated the freeze-in-place mute model. SPU RAM DMA readback (from PR mstan#102): DMA4 in the SPU->RAM direction wrote literal zeros; SPU RAM is readable memory and titles carry state through it across an Exec boundary. Also fixes an unrelated build race found on the way: tools/embed_spirv.py wrote its intermediate SPIR-V to <source>.spv, so the two runtime targets that embed the same shaders raced on one path and one deleted the file the other was reading. Co-Authored-By: Alexandros Mandravillis <Alexbeav@live.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Summary
Problem
Three gaps in the SPU/IRQ subsystem caused busy-wait deadlocks during sequenced audio synchronisation:
Validation
Hardware basis