From 948df06a75baaa931c7bbd9fc7ee2f9120034346 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Mon, 27 Apr 2026 17:39:41 -0700 Subject: [PATCH] librecomp: auto-increment RSP DMA addresses after each transfer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Real RSP DMA hardware advances SP_MEM_ADDR and SP_DRAM_ADDR by (length + 1) after each transfer (verified against Ares' n64/rsp/dma.cpp: pbusAddress += 8 / dramAddress += 8 per 8-byte chunk). The runtime's DO_DMA_READ / DO_DMA_WRITE macros didn't replicate this, so any ucode that fires DMAs in a loop without re-writing SP_MEM_ADDR/SP_DRAM_ADDR each iteration would reload the same DMEM region forever. Observed as Pokemon Stadium's aspMain hanging in its dispatch loop: the L_10EC <-> L_11B4 DMA pump (which walks through the audio command stream chunk by chunk on real hardware) collapsed into a tight no-progress cycle. With the auto-increment in place the dispatch makes forward progress and aspMain advances past the hang point. (A downstream UnhandledJumpTarget then surfaces — a separate issue with the recompiler's static indirect-branch-target list, not this layer.) Diff verified via diff_aspmain.py against the standalone Ares oracle. Co-Authored-By: Claude Opus 4.7 (1M context) --- librecomp/include/librecomp/rsp.hpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/librecomp/include/librecomp/rsp.hpp b/librecomp/include/librecomp/rsp.hpp index 748558c..970cdef 100644 --- a/librecomp/include/librecomp/rsp.hpp +++ b/librecomp/include/librecomp/rsp.hpp @@ -91,8 +91,27 @@ static inline void RSP_MEM_H_STORE(uint32_t offset, uint32_t addr, uint32_t val) #define SET_DMA_MEM(mem_addr) dma_mem_address = (mem_addr) #define SET_DMA_DRAM(dram_addr) dma_dram_address = (dram_addr) -#define DO_DMA_READ(rd_len) dma_rdram_to_dmem(rdram, dma_mem_address, dma_dram_address, (rd_len)) -#define DO_DMA_WRITE(wr_len) dma_dmem_to_rdram(rdram, dma_mem_address, dma_dram_address, (wr_len)) +// Real RSP DMA hardware auto-increments SP_MEM_ADDR and SP_DRAM_ADDR +// by (length + 1) after each transfer. Tight loops like aspMain's +// L_10EC <-> L_11B4 DMA pump (and any other ucode that fires DMAs in a +// loop without re-writing SP_MEM_ADDR / SP_DRAM_ADDR each iteration) +// rely on this to walk through the audio command stream chunk by +// chunk. Without the increment the same DMEM region gets reloaded +// forever — observed as Stadium's aspMain hanging in the dispatch +// loop on a never-advancing command word. Verified against Ares' +// rsp/dma.cpp (pbusAddress += 8 / dramAddress += 8 per 8-byte chunk). +#define DO_DMA_READ(rd_len) do { \ + uint32_t _rsp_dma_inc = (uint32_t)(rd_len) + 1; \ + dma_rdram_to_dmem(rdram, dma_mem_address, dma_dram_address, (rd_len)); \ + dma_mem_address += _rsp_dma_inc; \ + dma_dram_address += _rsp_dma_inc; \ +} while (0) +#define DO_DMA_WRITE(wr_len) do { \ + uint32_t _rsp_dma_inc = (uint32_t)(wr_len) + 1; \ + dma_dmem_to_rdram(rdram, dma_mem_address, dma_dram_address, (wr_len)); \ + dma_mem_address += _rsp_dma_inc; \ + dma_dram_address += _rsp_dma_inc; \ +} while (0) static inline void dma_rdram_to_dmem(uint8_t* rdram, uint32_t dmem_addr, uint32_t dram_addr, uint32_t rd_len) { rd_len += 1; // Read length is inclusive