Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 92 additions & 1 deletion runner/src/snes/dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ void dma_free(Dma* dma) {
}

void dma_reset(Dma* dma) {
dma->hdmaPendingInit = 0;
for(int i = 0; i < 8; i++) {
dma->channel[i].bAdr = 0xff;
dma->channel[i].aAdr = 0xffff;
Expand Down Expand Up @@ -337,6 +338,91 @@ static void dma_transferByte(Dma* dma, uint16_t aAdr, uint8_t aBank, uint8_t bAd
#endif
}

/* HDMA uses a table descriptor per active channel. It is separate from the
* regular-DMA cycle engine because the host invokes it at scanline edges. */
uint64_t dma_hdmaMasterEstimate(Dma* dma) {
static const uint8_t bytesPerUnit[8] = {1, 2, 2, 4, 4, 4, 2, 4};
uint64_t perLine = 0;
bool any = false;
for (int i = 0; i < 8; i++) {
DmaChannel* ch = &dma->channel[i];
if (!ch->hdmaActive) continue;
any = true;
perLine += 8u + 8u * bytesPerUnit[ch->mode & 7];
if (ch->indirect) perLine += 16u;
}
return any ? (18u + perLine) * 224u + 128u : 0;
}

static void dma_hdmaLoadDescriptor(Dma* dma, DmaChannel* ch) {
ch->repCount = snes_read(dma->snes, (ch->aBank << 16) | ch->tableAdr++);
ch->terminated = ch->repCount == 0;
if (ch->indirect && !ch->terminated) {
ch->size = snes_read(dma->snes, (ch->aBank << 16) | ch->tableAdr++);
ch->size |= snes_read(dma->snes, (ch->aBank << 16) | ch->tableAdr++) << 8;
}
ch->doTransfer = !ch->terminated;
}

void dma_initHdma(Dma* dma) {
for (int i = 0; i < 8; i++) {
DmaChannel* ch = &dma->channel[i];
if (!ch->hdmaActive) continue;
dma->hdmaPendingInit &= (uint8_t)~(1u << i);
ch->tableAdr = ch->aAdr;
dma_hdmaLoadDescriptor(dma, ch);
ch->offIndex = 0;
}
}

void dma_primeHdmaFirstLine(Dma* dma) {
for (int i = 0; i < 8; i++) {
DmaChannel* ch = &dma->channel[i];
if (!ch->hdmaActive || ch->terminated || !ch->doTransfer) continue;
DmaChannel save = *ch;
int len = transferLength[ch->mode & 7];
for (int j = 0; j < len; j++) {
uint8_t bAdr = (uint8_t)(ch->bAdr + bAdrOffsets[ch->mode & 7][j]);
if (ch->indirect)
dma_transferByte(dma, ch->size++, ch->indBank, bAdr, false, i);
else
dma_transferByte(dma, ch->tableAdr++, ch->aBank, bAdr, false, i);
}
*ch = save;
}
}

void dma_doHdma(Dma* dma) {
for (int i = 0; i < 8; i++) {
DmaChannel* ch = &dma->channel[i];
if (!ch->hdmaActive) continue;
if (dma->hdmaPendingInit & (1u << i)) {
dma->hdmaPendingInit &= (uint8_t)~(1u << i);
ch->tableAdr = ch->aAdr;
dma_hdmaLoadDescriptor(dma, ch);
ch->offIndex = 0;
continue;
}
if (ch->terminated) continue;

if (ch->doTransfer) {
int len = transferLength[ch->mode & 7];
for (int j = 0; j < len; j++) {
uint8_t bAdr = (uint8_t)(ch->bAdr + bAdrOffsets[ch->mode & 7][j]);
if (ch->indirect)
dma_transferByte(dma, ch->size++, ch->indBank, bAdr, false, i);
else
dma_transferByte(dma, ch->tableAdr++, ch->aBank, bAdr, false, i);
}
}

ch->repCount--;
ch->doTransfer = (ch->repCount & 0x80) != 0;
if ((ch->repCount & 0x7f) == 0)
dma_hdmaLoadDescriptor(dma, ch);
}
}

bool dma_cycle(Dma* dma) {
if(dma->dmaBusy) {
dma_doDma(dma);
Expand All @@ -348,7 +434,12 @@ bool dma_cycle(Dma* dma) {
void dma_startDma(Dma* dma, uint8_t val, bool hdma) {
for(int i = 0; i < 8; i++) {
if(hdma) {
dma->channel[i].hdmaActive = val & (1 << i);
bool now_on = (val & (1 << i)) != 0;
if(now_on && !dma->channel[i].hdmaActive)
dma->hdmaPendingInit |= (uint8_t)(1u << i);
else if(!now_on)
dma->hdmaPendingInit &= (uint8_t)~(1u << i);
dma->channel[i].hdmaActive = now_on;
} else {
dma->channel[i].dmaActive = val & (1 << i);
}
Expand Down
8 changes: 8 additions & 0 deletions runner/src/snes/dma.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ typedef struct DmaChannel {

struct Dma {
Snes* snes;
/* Transient state for channels enabled between frame initializations. Keep
* this before channel so dma_saveload's serialized channel region remains
* byte-for-byte compatible with existing saves. */
uint8_t hdmaPendingInit;
DmaChannel channel[8];
uint32_t dmaTimer;
bool dmaBusy;
Expand All @@ -51,6 +55,10 @@ void dma_reset(Dma* dma);
uint8_t dma_read(Dma* dma, uint16_t adr); // 43x0-43xf
void dma_write(Dma* dma, uint16_t adr, uint8_t val); // 43x0-43xf
void dma_doDma(Dma* dma);
void dma_initHdma(Dma* dma);
void dma_doHdma(Dma* dma);
void dma_primeHdmaFirstLine(Dma* dma);
uint64_t dma_hdmaMasterEstimate(Dma* dma);
bool dma_cycle(Dma* dma);
void dma_startDma(Dma* dma, uint8_t val, bool hdma);
void dma_saveload(Dma *dma, SaveLoadInfo *sli);
Expand Down
14 changes: 8 additions & 6 deletions runner/src/snes/interp816.c
Original file line number Diff line number Diff line change
Expand Up @@ -488,8 +488,10 @@ static uint32_t interp816_adrIdy(Interp816* cpu, uint32_t* low, bool write) {
uint8_t adr = interp816_readOpcode(cpu);
if(cpu->dp & 0xff) cpu->cyclesUsed++; // dpr not 0: 1 extra cycle
uint16_t pointer = interp816_readWord(cpu, (cpu->dp + adr) & 0xffff, (cpu->dp + adr + 1) & 0xffff);
if(write && (!cpu->xf || ((pointer >> 8) != ((pointer + cpu->y) >> 8)))) cpu->cyclesUsed++;
// x = 0 or page crossed, with writing opcode: 1 extra cycle
bool crossed = (pointer >> 8) != ((pointer + cpu->y) >> 8);
if(write ? (!cpu->xf || crossed) : crossed) cpu->cyclesUsed++;
// Reads pay only on a page crossing. Stores retain their existing 16-bit
// index/page-crossing penalty.
*low = ((cpu->db << 16) + pointer + cpu->y) & 0xffffff;
return ((cpu->db << 16) + pointer + cpu->y + 1) & 0xffffff;
}
Expand Down Expand Up @@ -533,16 +535,16 @@ static uint32_t interp816_adrAbs(Interp816* cpu, uint32_t* low) {

static uint32_t interp816_adrAbx(Interp816* cpu, uint32_t* low, bool write) {
uint16_t adr = interp816_readOpcodeWord(cpu);
if(write && (!cpu->xf || ((adr >> 8) != ((adr + cpu->x) >> 8)))) cpu->cyclesUsed++;
// x = 0 or page crossed, with writing opcode: 1 extra cycle
bool crossed = (adr >> 8) != ((adr + cpu->x) >> 8);
if(write ? (!cpu->xf || crossed) : crossed) cpu->cyclesUsed++;
*low = ((cpu->db << 16) + adr + cpu->x) & 0xffffff;
return ((cpu->db << 16) + adr + cpu->x + 1) & 0xffffff;
}

static uint32_t interp816_adrAby(Interp816* cpu, uint32_t* low, bool write) {
uint16_t adr = interp816_readOpcodeWord(cpu);
if(write && (!cpu->xf || ((adr >> 8) != ((adr + cpu->y) >> 8)))) cpu->cyclesUsed++;
// x = 0 or page crossed, with writing opcode: 1 extra cycle
bool crossed = (adr >> 8) != ((adr + cpu->y) >> 8);
if(write ? (!cpu->xf || crossed) : crossed) cpu->cyclesUsed++;
*low = ((cpu->db << 16) + adr + cpu->y) & 0xffffff;
return ((cpu->db << 16) + adr + cpu->y + 1) & 0xffffff;
}
Expand Down
58 changes: 55 additions & 3 deletions runner/src/snes/interp_bridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,8 @@ static int s_lle_sched_depth = 0;
static int s_lle_unwind_active = 0;
static uint32_t s_lle_unwind_pc24 = 0;
static int s_lle_unwind_owner_depth = 0;
static int s_lle_unwind_is_deadline = 0;
static int s_lle_next_unwind_is_deadline = 0;
static uint32_t s_lle_resume_pc24 = 0;
static int s_lle_wai_yield = 0;
static int s_lle_quiescent_yield = 0;
Expand Down Expand Up @@ -522,9 +524,13 @@ void interp_bridge_set_master_deadline(uint64_t master_clock) {
}

int interp_bridge_lle_master_deadline_reached(const CpuState *cpu) {
return cpu && s_lle_sched_depth > 0 && s_interp_bounce_owner_depth > 0 &&
s_lle_master_deadline != 0 &&
cpu->master_cycles >= s_lle_master_deadline;
const int reached =
cpu && s_lle_sched_depth > 0 && s_interp_bounce_owner_depth > 0 &&
s_lle_master_deadline != 0 &&
cpu->master_cycles >= s_lle_master_deadline;
if (reached)
s_lle_next_unwind_is_deadline = 1;
return reached;
}

RecompReturn interp_bridge_lle_yield_unwind(CpuState *cpu, uint32 resume_pc24) {
Expand All @@ -538,6 +544,8 @@ RecompReturn interp_bridge_lle_yield_unwind(CpuState *cpu, uint32 resume_pc24) {
s_lle_unwind_active = 1;
s_lle_unwind_pc24 = resume_pc24 & 0xFFFFFFu;
s_lle_unwind_owner_depth = s_interp_bounce_owner_depth;
s_lle_unwind_is_deadline = s_lle_next_unwind_is_deadline;
s_lle_next_unwind_is_deadline = 0;
return (RecompReturn)RECOMP_RETURN_LLE_UNWIND_BASE;
}

Expand Down Expand Up @@ -1891,6 +1899,15 @@ static int _interp_run_core(CpuState *cpu, uint32_t entry_pc24,
if (_air != RECOMP_RETURN_NORMAL) {
if (s_lle_unwind_active) {
if (s_lle_unwind_owner_depth == s_interp_bridge_depth) {
if (s_lle_unwind_is_deadline) {
s_lle_resume_pc24 = s_lle_unwind_pc24;
s_lle_unwind_active = 0;
s_lle_unwind_owner_depth = 0;
s_lle_unwind_is_deadline = 0;
sync_interp_to_cpu(&in, cpu);
bridge_apu_flush(cpu);
return 1;
}
if (getenv("SNESRECOMP_YIELD_STACK_DIAG") &&
snes_frame_counter >= 5390) {
fprintf(stderr,
Expand All @@ -1911,6 +1928,7 @@ static int _interp_run_core(CpuState *cpu, uint32_t entry_pc24,
* switch runs byte-exact. */
s_lle_unwind_active = 0;
s_lle_unwind_owner_depth = 0;
s_lle_unwind_is_deadline = 0;
sync_cpu_to_interp(cpu, &in);
in.k = (uint8)((s_lle_unwind_pc24 >> 16) & 0xFF);
in.pc = (uint16)(s_lle_unwind_pc24 & 0xFFFF);
Expand Down Expand Up @@ -2081,6 +2099,32 @@ static int _interp_run_core(CpuState *cpu, uint32_t entry_pc24,
return 0;
}

/* Each bridge run installs a stable interpreted-function name so debug write
* attribution does not leak an enclosing AOT frame. */
extern const char *g_last_recomp_func;
#define INTERP_SCOPE_NAMES 4096u
static char s_interp_scope_names[INTERP_SCOPE_NAMES][20];
static uint32_t s_interp_scope_pc[INTERP_SCOPE_NAMES];
static uint8_t s_interp_scope_used[INTERP_SCOPE_NAMES];

static const char *interp_scope_name(uint32_t pc24) {
pc24 &= 0xFFFFFFu;
uint32_t slot = (pc24 * 2654435761u) & (INTERP_SCOPE_NAMES - 1u);
for (uint32_t probe = 0; probe < INTERP_SCOPE_NAMES; probe++) {
uint32_t i = (slot + probe) & (INTERP_SCOPE_NAMES - 1u);
if (!s_interp_scope_used[i]) {
s_interp_scope_used[i] = 1;
s_interp_scope_pc[i] = pc24;
snprintf(s_interp_scope_names[i], sizeof(s_interp_scope_names[i]),
"interp@$%06X", (unsigned)pc24);
return s_interp_scope_names[i];
}
if (s_interp_scope_pc[i] == pc24)
return s_interp_scope_names[i];
}
return "interp@(table full)";
}

/* Wrapper: mark the interp tier as APU-driving for the whole run (nesting-safe
* save/restore) so rtl_accumulate_apu_catchup skips the per-touch synthetic
* estimate — the core advances the SPC per opcode instead. */
Expand All @@ -2105,11 +2149,17 @@ static int interp_bridge_run_ex2(CpuState *cpu, uint32_t entry_pc24,
s_interp_owner_exit_s = s_exit;
s_interp_owner_exit_valid = 1;
s_interp_bridge_depth++;
const char *_saved_func = g_last_recomp_func;
const char *_scope_name = interp_scope_name(entry_pc24);
g_last_recomp_func = _scope_name;
RecompStackPush(_scope_name);
int _r = _interp_run_core(cpu, entry_pc24, s_exit, out_landing,
out_return_pc, yield_pc,
yield_flag_addr, yield_flag_value,
reset_cap_on_bounce, stop_pcs, n_stop,
stop_on_rti);
RecompStackPop();
g_last_recomp_func = _saved_func;
s_interp_bridge_depth--;
s_interp_owner_exit_s = _saved_owner_exit_s;
s_interp_owner_exit_valid = _saved_owner_exit_valid;
Expand All @@ -2121,6 +2171,8 @@ static int interp_bridge_run_ex2(CpuState *cpu, uint32_t entry_pc24,
if (s_lle_unwind_active) {
s_lle_unwind_active = 0;
s_lle_unwind_owner_depth = 0;
s_lle_unwind_is_deadline = 0;
s_lle_next_unwind_is_deadline = 0;
fprintf(stderr, "[interp_bridge] stale LLE yield unwind cleared "
"at scheduler exit (pc=$%06X)\n",
(unsigned)s_lle_unwind_pc24);
Expand Down
12 changes: 11 additions & 1 deletion runner/src/snes/snes.c
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@ static void snes_advance_beam(Snes *snes, uint32_t clocks, bool check_irq) {
uint32_t v = snes->vPos;
while (clocks) {
uint32_t span = 1364u - h;
/* Stop at HBlank so the HDMA transfer occurs at its hardware edge rather
* than after the rest of the scanline has already been consumed. */
if (check_irq && v < 225u && h < 1024u && span > 1024u - h)
span = 1024u - h;
if (span > clocks) span = clocks;

/* Automatic joypad polling begins at vblank and keeps HVBJOY.0 asserted
Expand All @@ -332,10 +336,16 @@ static void snes_advance_beam(Snes *snes, uint32_t clocks, bool check_irq) {

h += span;
clocks -= span;
if (check_irq && v < 225u && h == 1024u)
dma_doHdma(snes->dma);
if (h >= 1364u) {
h = 0;
v++;
if (v >= 262u) v = 0;
if (v >= 262u) {
v = 0;
if (check_irq)
dma_initHdma(snes->dma);
}
}
}
snes->hPos = (uint16_t)h;
Expand Down
4 changes: 4 additions & 0 deletions tests/dma/hdma_timing_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ void RtlApuLock(void) {}
void RtlApuUnlock(void) {}
void rtl_sync_apu_to_cpu_locked(void) {}
void RtlApuWrite(uint16_t adr, uint8_t val) { (void)adr; (void)val; }
uint8_t rtl_apu_port_observers_read(uint16_t adr, uint8_t value) {
(void)adr;
return value;
}
void audio_trace_on_cpu_port_read(uint8_t port, uint8_t value) {
(void)port;
(void)value;
Expand Down
9 changes: 7 additions & 2 deletions tests/run_c_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,27 @@ echo "=== PPU sprite limits ==="
"$OUT/ppu_sprite_limit_test"

echo "=== DMA / HDMA ==="
"$CC" -std=c11 -Wall -Wextra -Werror -O1 \
# sdd1.c is intentionally linked with dma.c; its disabled-path condition is
# not type-limits clean under the harness's stricter -Werror policy.
"$CC" -std=c11 -Wall -Wextra -Werror -Wno-error=type-limits -O1 \
-DSNESRECOMP_REVERSE_DEBUG=0 \
-I "$ROOT/runner/src" -I "$ROOT/runner/src/snes" \
"$ROOT/tests/dma/hdma_test.c" \
"$ROOT/runner/src/snes/dma.c" \
"$ROOT/runner/src/snes/sdd1.c" \
-o "$OUT/hdma_test"
"$OUT/hdma_test"

"$CC" -std=c11 -Wall -Wextra -Werror -O1 \
-Wno-error=parentheses -Wno-error=unused-variable \
-Wno-error=unused-const-variable \
-Wno-error=unused-const-variable -Wno-error=type-limits \
-DSNESRECOMP_REVERSE_DEBUG=0 \
-ffunction-sections -fdata-sections \
-I "$ROOT/tests/dma" -I "$ROOT/runner/src" -I "$ROOT/runner/src/snes" \
"$ROOT/tests/dma/hdma_timing_test.c" \
"$ROOT/runner/src/snes/dma.c" \
"$ROOT/runner/src/snes/snes.c" \
"$ROOT/runner/src/snes/sdd1.c" \
-Wl,--gc-sections -o "$OUT/hdma_timing_test"
"$OUT/hdma_timing_test"

Expand Down Expand Up @@ -165,6 +169,7 @@ echo "=== runtime dispatch ==="
"$ROOT/tests/runtime_dispatch/known_lle_entry_test.c" \
"$ROOT/runner/src/cpu_state.c" \
"$ROOT/runner/src/snes/cart.c" \
"$ROOT/runner/src/snes/sdd1.c" \
"$ROOT/runner/src/snes/cx4.c" \
"$ROOT/runner/src/snes/dsp1.c" \
"$ROOT/runner/src/snes/dsp1_hle.c" \
Expand Down