From 8e13f8c30b83a9f51bbbbc3e2b7cef20d3583186 Mon Sep 17 00:00:00 2001 From: Nathaniel Walizer Date: Mon, 31 Mar 2025 20:15:59 -0700 Subject: [PATCH] Various NMI and reading PPU fixes - NMI still imperfect - Passes vast majority of bisqwit's ppu_read_buffer tests --- src/cart.c | 7 +--- src/f6502.c | 84 +++++++++++++++++++++++++++--------------------- src/linux/port.c | 6 +++- src/nes.c | 37 +++++++++++++++------ src/ppu.c | 3 +- src/ppu.h | 20 ++++++------ 6 files changed, 94 insertions(+), 63 deletions(-) diff --git a/src/cart.c b/src/cart.c index 9367470..0c939fa 100644 --- a/src/cart.c +++ b/src/cart.c @@ -102,16 +102,11 @@ int nes_cart_load_mem(void* data, int size, nes* sys) { mem->ppu.bank[page] = chr_page(&mem->ppu, page); } - // PPU Bank 8 - 11: Mirrored VRAM + // PPU Bank 8 - 11 (+ 12 - 15): Mirrored VRAM nes_ppu_set_mirroring( &mem->ppu, hdr->flags_6 & ines_Flag_Vert_Mirror ); - // PPU Bank 12 - 15: Unused & Palette - for (int page = 12; page < 16; ++page) { - mem->ppu.bank[page] = mem->ppu.pal_bank; - } - if (mem->mapper.init) { status = mem->mapper.init(&mem->mapper, hdr, mem); diff --git a/src/f6502.c b/src/f6502.c index 1433246..dfd1440 100644 --- a/src/f6502.c +++ b/src/f6502.c @@ -69,28 +69,27 @@ static inline uint8_t f6502_read(nes_Memory* mem, // TODO: Set nametable to 0 when in VBlank?? - return val; - } + mem->ppu.bus = (val & ~ppu_Status_Open_Bus_Mask) | + (mem->ppu.bus & ppu_Status_Open_Bus_Mask); + } break; case ppu_reg_oam_data: - return mem->ppu.oam[mem->ppu.oam_addr]; + mem->ppu.bus = mem->ppu.oam[mem->ppu.oam_addr]; + break; case ppu_reg_data: - { addr = mem->ppu.addr & 0x3FFFU; + addr = mem->ppu.addr & 0x3FFFU; mem->ppu.addr += mem->ppu.addr_inc; - uint8_t data = mem->ppu.bank[addr >> 10][addr & 0x3FFU]; - if (addr >= 0x3F00) { + if (addr >= NES_PPU_PAL_START) { + mem->ppu.bus = mem->ppu.pal_ram[addr & 0x1FU]; addr &= 0x2FFFU; - mem->ppu.data = mem->ppu.bank[addr >> 10][addr & 0x3FFU]; - return data; - } else { - uint8_t val = mem->ppu.data; - mem->ppu.data = data; - return val; + } else { + mem->ppu.bus = mem->ppu.data; } + mem->ppu.data = mem->ppu.bank[addr >> 10][addr & 0x3FFU]; + break; } - } - break; + return mem->ppu.bus; case 0x4000: switch (addr & 0x1FU) { @@ -136,6 +135,17 @@ static inline uint16_t f6502_read16(nes_Memory* mem, ((uint16_t)f6502_read(mem, addr + 1) << 8)); } + +typedef struct { + uint16_t clocks; + uint16_t interrupt; +} write_ret; + +#define INT_TRIGGERED(x) (x >> 16) +#define CLOCKS(x) ((uint16_t)x) + +#define TRIGGER_INT (1U << 16) + static inline int f6502_write(nes_Memory* mem, uint16_t addr, uint8_t val) { int ret = 0; @@ -163,9 +173,7 @@ static inline int f6502_write(nes_Memory* mem, f6502_set_IRQ(core, irq); // Interrupt [may have been] triggered - // We're not strictly counting cycles, - // so just overload the counter - ret = 1000; + ret = TRIGGER_INT; } #endif mem->ram[addr] = val; @@ -178,6 +186,14 @@ static inline int f6502_write(nes_Memory* mem, case 0x2000: switch (addr & 0x7U) { case ppu_reg_ctrl: + if ( (mem->ppu.status & ppu_Status_VBlank) && + !(mem->ppu.ctrl & ppu_Control_VBlank) && + (val & ppu_Control_VBlank)) { + f6502_Core* core = container_of(mem, f6502_Core, memory); + /* Trigger NMI if VBlank was set */ + f6502_set_NMI(core, 1); + ret = TRIGGER_INT; + } mem->ppu.ctrl &= ppu_Control_Nametable_Mask; mem->ppu.ctrl |= (val & ~ppu_Control_Nametable_Mask); mem->ppu.t &= ~(ppu_Control_Nametable_Mask << 10); @@ -233,25 +249,19 @@ static inline int f6502_write(nes_Memory* mem, addr = mem->ppu.addr & 0x3FFFU; if (addr >= NES_PPU_CHR_SIZE || mem->ppu.chr_ram) { LOGI("PPU W %04X < %02X", addr, val); - mem->ppu.bank[addr >> 10][addr & 0x3FFU] = val; if (addr >= NES_PPU_PAL_START) { - // Copy to render reference + // Copy to palette RAM addr &= 0x1FU; - uint8_t* pal = mem->ppu.palette; + mem->ppu.pal_ram[addr] = val; if (0 != (addr & 0x3)) { - pal[addr] = val & 0x3FU; - } - - // Memory-mapped mirroring - addr |= 0x300U; - for (int mirror = 0; mirror < 0x100; mirror += 0x20) { - mem->ppu.pal_bank[addr + mirror] = val; - } - if (0 == (addr & 3)) { - for (int mirror = 0; mirror < 0x100; mirror += 0x20) { - mem->ppu.pal_bank[(addr ^ 0x10U) + mirror] = val; - } + // Copy to render reference + mem->ppu.palette[addr] = val & 0x3FU; + } else { + // Mirror + mem->ppu.pal_ram[addr ^ 0x10] = val; } + } else { + mem->ppu.bank[addr >> 10][addr & 0x3FFU] = val; } } else { LOGE("PPU W %04X < %02X : INVALID", addr, val); @@ -259,6 +269,7 @@ static inline int f6502_write(nes_Memory* mem, mem->ppu.addr += mem->ppu.addr_inc; break; } + mem->ppu.bus = val; break; case 0x4000: @@ -533,7 +544,7 @@ static inline int f6502_check_interrupts(f6502_Core* core) { #define BIF_HCF() \ if (pc_prev == PC + 2) { \ ++PC; \ - clocks_elapsed = -clocks_elapsed; \ + clocks_elapsed += INT_TRIGGER; \ goto step_done; \ } #else @@ -647,7 +658,8 @@ static inline int f6502_check_interrupts(f6502_Core* core) { } \ A = res; \ } -#define ST(reg, a) clocks_elapsed += f6502_write(&core->memory, (a), reg) +#define ST(reg, a) \ + clocks_elapsed += f6502_write(&core->memory, (a), reg) static int f6502_do_step(f6502_Core* core, int clocks) { @@ -1712,8 +1724,8 @@ int f6502_step(f6502_Core* core, int clocks) { } */ clocks_elapsed += f6502_check_interrupts(core); - clocks_elapsed += f6502_do_step(core, - clocks - clocks_elapsed); + clocks = f6502_do_step(core, clocks - clocks_elapsed); + clocks_elapsed += CLOCKS(clocks); core->clocks += clocks_elapsed; return clocks_elapsed; } diff --git a/src/linux/port.c b/src/linux/port.c index d663980..f63283d 100644 --- a/src/linux/port.c +++ b/src/linux/port.c @@ -15,9 +15,11 @@ #include "port.h" #include "save.h" -#define DEBUG "Port" +#define NESE_DEBUG "Port" #include "log.h" +//#define NESE_TURBO + /* * OS-specific file operations @@ -321,6 +323,7 @@ int nese_frame_ready(void* plat_data) { // TODO: Perform more actions +#ifndef NESE_TURBO if (0 == status) { plat->t_target += FRAME_TIME_NS; int64_t slept_ns = time_sleep_until(plat->t_target); @@ -330,6 +333,7 @@ int nese_frame_ready(void* plat_data) { printf("Out of sync: %d\n", (int)slept_ns); } } +#endif // NESE_TURBO return status; } diff --git a/src/nes.c b/src/nes.c index 90add48..ae4337f 100644 --- a/src/nes.c +++ b/src/nes.c @@ -32,12 +32,6 @@ void nes_done(nes* sys) { static int nes_vsync(nes* sys, void* plat) { int status = 0; - sys->core.memory.ppu.status |= ppu_Status_VBlank; - if (sys->core.memory.ppu.ctrl & ppu_Control_VBlank) { - LOGD("VBlank NMI"); - f6502_set_NMI(&sys->core, 1); - } - nes_Memory* mem = &sys->core.memory; if (0 == status && NULL != mem->mapper.vsync) { mem->mapper.vsync(&mem->mapper); @@ -81,6 +75,7 @@ static int nes_hsync(nes* sys, void* plat) { sys->ppu.scanline = nes_ppu_prerender_line; } + // Beginning of the next line switch (sys->ppu.scanline) { case nes_ppu_prerender_line: { nes_PPU_Memory* mem = &sys->core.memory.ppu; @@ -94,7 +89,7 @@ static int nes_hsync(nes* sys, void* plat) { int pal_idx = ((mem->addr & 0x3F00U) == 0x3F00U) ? (mem->addr & 0x1FU) : 0; // Don't use the rendering palette (masked transparency) - status = nese_frame_start(plat, mem->pal_bank[0x300 + pal_idx] & 0x3FU); + status = nese_frame_start(plat, mem->pal_ram[pal_idx] & 0x3FU); } break; @@ -110,21 +105,23 @@ static int nes_hsync(nes* sys, void* plat) { return status; } +#define vblank_set_dot (1U) #define mapper_hsync_dot (300U) #define render_dot (256U) -#define vblank_clear_dot (320U) +#define vblank_clear_dot (341U) int nes_loop(nes* sys, void* plat) { int status = 0; int dot = 0; bool mapper_hsync = false; bool vbl_clear = false; + bool vbl_set = false; while (0 == status) { int target_dot = nes_ppu_scanline_dots; if ( nes_ppu_frame_end_line - 1 == sys->ppu.scanline && - sys->core.memory.ppu.status & ppu_Status_VBlank) { + (sys->core.memory.ppu.status & ppu_Status_VBlank)) { target_dot = vblank_clear_dot; vbl_clear = true; } @@ -137,6 +134,13 @@ int nes_loop(nes* sys, void* plat) { mapper_hsync = true; } + if ( vbl_set || + ( nes_ppu_vblank_line == sys->ppu.scanline && + dot < vblank_set_dot )) { + target_dot = vblank_set_dot; + vbl_set = true; + } + if ( sys->ppu.hit_line == sys->ppu.scanline && ( sys->core.memory.ppu.mask & (ppu_Mask_Sprite | ppu_Mask_Back)) && @@ -145,7 +149,7 @@ int nes_loop(nes* sys, void* plat) { if (dot >= hit_dot) { LOGD("Line %d sprite 0 hit @ %d", sys->ppu.hit_line, hit_dot); sys->core.memory.ppu.status |= ppu_Status_Hit; - } else { + } else if (target_dot > hit_dot) { target_dot = hit_dot; } } @@ -176,6 +180,15 @@ int nes_loop(nes* sys, void* plat) { dot += 3 * f6502_step(&sys->core, cpu_cycles); } + if (vbl_set && dot >= vblank_set_dot) { + sys->core.memory.ppu.status |= ppu_Status_VBlank; + if (sys->core.memory.ppu.ctrl & ppu_Control_VBlank) { + LOGD("VBlank NMI"); + f6502_set_NMI(&sys->core, 1); + } + vbl_set = false; + } + if (vbl_clear && dot >= vblank_clear_dot) { sys->core.memory.ppu.status &= ~ppu_Status_VBlank; vbl_clear = false; @@ -191,6 +204,10 @@ int nes_loop(nes* sys, void* plat) { if (dot >= nes_ppu_scanline_dots) { dot -= nes_ppu_scanline_dots; status = nes_hsync(sys, plat); + if ( nes_ppu_vblank_line == sys->ppu.scanline && + dot >= vblank_set_dot ) { + vbl_set = true; + } } } diff --git a/src/ppu.c b/src/ppu.c index 6a5227d..e0902f5 100644 --- a/src/ppu.c +++ b/src/ppu.c @@ -30,7 +30,8 @@ static const uint8_t mirror_schemes[][4] = { void nes_ppu_set_mirroring(nes_PPU_Memory* mem, nes_Nametable_Mirroring mirror) { for (int i = 0; i < 4; ++i) { - mem->bank[nes_PPU_Nametable_Bank_Index + i] = + mem->bank[nes_PPU_Nametable_Bank_Index + i + 4] = + mem->bank[nes_PPU_Nametable_Bank_Index + i] = vram_page(mem, (int)mirror_schemes[mirror][i]); } } diff --git a/src/ppu.h b/src/ppu.h index 0b16ee4..2ff9571 100644 --- a/src/ppu.h +++ b/src/ppu.h @@ -97,19 +97,21 @@ typedef struct { uint8_t mask; uint8_t status; uint8_t oam_addr; - uint16_t addr; // aka "v" - uint8_t data; - uint8_t x; // Fine X scroll - uint16_t t; // temp VRAM addr - uint8_t latch; // aka "w" - TODO: Could this be a flag? - uint8_t addr_inc; // Auto-increment (1 or 32) + uint16_t addr; // aka "v" + uint8_t data; // Next incremented data val (NOT last value) + uint8_t x; // Fine X scroll + uint16_t t; // temp VRAM addr + uint8_t latch; // aka "w" - TODO: Could this be a flag? + uint8_t addr_inc; // Auto-increment (1 or 32) + uint8_t unused[2]; // 2 B unused + uint8_t bus; // Last value transmitted (NOT next data val) // Static memory banks - int n_chr_banks; + uint8_t n_chr_banks; // TODO: Changed from int; consequences? uint8_t palette[32]; // Rendering palette with transparency masked - uint8_t vram[NES_PPU_VRAM_SIZE]; - uint8_t pal_bank[NES_CHR_ROM_PAGE_SIZE]; // Raw palette data mirrored in banks 12-15, also pal + uint8_t pal_ram[32]; // Raw palette data in RAM uint8_t oam[NES_PPU_OAM_SIZE]; + uint8_t vram[NES_PPU_VRAM_SIZE]; } nes_PPU_Memory;