- NMI still imperfect - Passes vast majority of bisqwit's ppu_read_buffer testsv2
| @@ -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); | |||
| @@ -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; | |||
| } | |||
| @@ -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; | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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]); | |||
| } | |||
| } | |||
| @@ -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; | |||