- 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); | 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( | nes_ppu_set_mirroring( | ||||
| &mem->ppu, hdr->flags_6 & ines_Flag_Vert_Mirror | &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) { | if (mem->mapper.init) { | ||||
| status = mem->mapper.init(&mem->mapper, | status = mem->mapper.init(&mem->mapper, | ||||
| hdr, mem); | hdr, mem); | ||||
| @@ -69,28 +69,27 @@ static inline uint8_t f6502_read(nes_Memory* mem, | |||||
| // TODO: Set nametable to 0 when in VBlank?? | // 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: | 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: | case ppu_reg_data: | ||||
| { addr = mem->ppu.addr & 0x3FFFU; | |||||
| addr = mem->ppu.addr & 0x3FFFU; | |||||
| mem->ppu.addr += mem->ppu.addr_inc; | 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; | 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: | case 0x4000: | ||||
| switch (addr & 0x1FU) { | switch (addr & 0x1FU) { | ||||
| @@ -136,6 +135,17 @@ static inline uint16_t f6502_read16(nes_Memory* mem, | |||||
| ((uint16_t)f6502_read(mem, addr + 1) << 8)); | ((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, | static inline int f6502_write(nes_Memory* mem, | ||||
| uint16_t addr, uint8_t val) { | uint16_t addr, uint8_t val) { | ||||
| int ret = 0; | int ret = 0; | ||||
| @@ -163,9 +173,7 @@ static inline int f6502_write(nes_Memory* mem, | |||||
| f6502_set_IRQ(core, irq); | f6502_set_IRQ(core, irq); | ||||
| // Interrupt [may have been] triggered | // Interrupt [may have been] triggered | ||||
| // We're not strictly counting cycles, | |||||
| // so just overload the counter | |||||
| ret = 1000; | |||||
| ret = TRIGGER_INT; | |||||
| } | } | ||||
| #endif | #endif | ||||
| mem->ram[addr] = val; | mem->ram[addr] = val; | ||||
| @@ -178,6 +186,14 @@ static inline int f6502_write(nes_Memory* mem, | |||||
| case 0x2000: | case 0x2000: | ||||
| switch (addr & 0x7U) { | switch (addr & 0x7U) { | ||||
| case ppu_reg_ctrl: | 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 &= ppu_Control_Nametable_Mask; | ||||
| mem->ppu.ctrl |= (val & ~ppu_Control_Nametable_Mask); | mem->ppu.ctrl |= (val & ~ppu_Control_Nametable_Mask); | ||||
| mem->ppu.t &= ~(ppu_Control_Nametable_Mask << 10); | 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; | addr = mem->ppu.addr & 0x3FFFU; | ||||
| if (addr >= NES_PPU_CHR_SIZE || mem->ppu.chr_ram) { | if (addr >= NES_PPU_CHR_SIZE || mem->ppu.chr_ram) { | ||||
| LOGI("PPU W %04X < %02X", addr, val); | LOGI("PPU W %04X < %02X", addr, val); | ||||
| mem->ppu.bank[addr >> 10][addr & 0x3FFU] = val; | |||||
| if (addr >= NES_PPU_PAL_START) { | if (addr >= NES_PPU_PAL_START) { | ||||
| // Copy to render reference | |||||
| // Copy to palette RAM | |||||
| addr &= 0x1FU; | addr &= 0x1FU; | ||||
| uint8_t* pal = mem->ppu.palette; | |||||
| mem->ppu.pal_ram[addr] = val; | |||||
| if (0 != (addr & 0x3)) { | 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 { | } else { | ||||
| LOGE("PPU W %04X < %02X : INVALID", addr, val); | 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; | mem->ppu.addr += mem->ppu.addr_inc; | ||||
| break; | break; | ||||
| } | } | ||||
| mem->ppu.bus = val; | |||||
| break; | break; | ||||
| case 0x4000: | case 0x4000: | ||||
| @@ -533,7 +544,7 @@ static inline int f6502_check_interrupts(f6502_Core* core) { | |||||
| #define BIF_HCF() \ | #define BIF_HCF() \ | ||||
| if (pc_prev == PC + 2) { \ | if (pc_prev == PC + 2) { \ | ||||
| ++PC; \ | ++PC; \ | ||||
| clocks_elapsed = -clocks_elapsed; \ | |||||
| clocks_elapsed += INT_TRIGGER; \ | |||||
| goto step_done; \ | goto step_done; \ | ||||
| } | } | ||||
| #else | #else | ||||
| @@ -647,7 +658,8 @@ static inline int f6502_check_interrupts(f6502_Core* core) { | |||||
| } \ | } \ | ||||
| A = res; \ | 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) { | 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_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; | core->clocks += clocks_elapsed; | ||||
| return clocks_elapsed; | return clocks_elapsed; | ||||
| } | } | ||||
| @@ -15,9 +15,11 @@ | |||||
| #include "port.h" | #include "port.h" | ||||
| #include "save.h" | #include "save.h" | ||||
| #define DEBUG "Port" | |||||
| #define NESE_DEBUG "Port" | |||||
| #include "log.h" | #include "log.h" | ||||
| //#define NESE_TURBO | |||||
| /* | /* | ||||
| * OS-specific file operations | * OS-specific file operations | ||||
| @@ -321,6 +323,7 @@ int nese_frame_ready(void* plat_data) { | |||||
| // TODO: Perform more actions | // TODO: Perform more actions | ||||
| #ifndef NESE_TURBO | |||||
| if (0 == status) { | if (0 == status) { | ||||
| plat->t_target += FRAME_TIME_NS; | plat->t_target += FRAME_TIME_NS; | ||||
| int64_t slept_ns = time_sleep_until(plat->t_target); | 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); | printf("Out of sync: %d\n", (int)slept_ns); | ||||
| } | } | ||||
| } | } | ||||
| #endif // NESE_TURBO | |||||
| return status; | return status; | ||||
| } | } | ||||
| @@ -32,12 +32,6 @@ void nes_done(nes* sys) { | |||||
| static int nes_vsync(nes* sys, void* plat) { | static int nes_vsync(nes* sys, void* plat) { | ||||
| int status = 0; | 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; | nes_Memory* mem = &sys->core.memory; | ||||
| if (0 == status && NULL != mem->mapper.vsync) { | if (0 == status && NULL != mem->mapper.vsync) { | ||||
| mem->mapper.vsync(&mem->mapper); | mem->mapper.vsync(&mem->mapper); | ||||
| @@ -81,6 +75,7 @@ static int nes_hsync(nes* sys, void* plat) { | |||||
| sys->ppu.scanline = nes_ppu_prerender_line; | sys->ppu.scanline = nes_ppu_prerender_line; | ||||
| } | } | ||||
| // Beginning of the next line | |||||
| switch (sys->ppu.scanline) { | switch (sys->ppu.scanline) { | ||||
| case nes_ppu_prerender_line: | case nes_ppu_prerender_line: | ||||
| { nes_PPU_Memory* mem = &sys->core.memory.ppu; | { 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) ? | int pal_idx = ((mem->addr & 0x3F00U) == 0x3F00U) ? | ||||
| (mem->addr & 0x1FU) : 0; | (mem->addr & 0x1FU) : 0; | ||||
| // Don't use the rendering palette (masked transparency) | // 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; | } break; | ||||
| @@ -110,21 +105,23 @@ static int nes_hsync(nes* sys, void* plat) { | |||||
| return status; | return status; | ||||
| } | } | ||||
| #define vblank_set_dot (1U) | |||||
| #define mapper_hsync_dot (300U) | #define mapper_hsync_dot (300U) | ||||
| #define render_dot (256U) | #define render_dot (256U) | ||||
| #define vblank_clear_dot (320U) | |||||
| #define vblank_clear_dot (341U) | |||||
| int nes_loop(nes* sys, void* plat) { | int nes_loop(nes* sys, void* plat) { | ||||
| int status = 0; | int status = 0; | ||||
| int dot = 0; | int dot = 0; | ||||
| bool mapper_hsync = false; | bool mapper_hsync = false; | ||||
| bool vbl_clear = false; | bool vbl_clear = false; | ||||
| bool vbl_set = false; | |||||
| while (0 == status) { | while (0 == status) { | ||||
| int target_dot = nes_ppu_scanline_dots; | int target_dot = nes_ppu_scanline_dots; | ||||
| if ( nes_ppu_frame_end_line - 1 == sys->ppu.scanline && | 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; | target_dot = vblank_clear_dot; | ||||
| vbl_clear = true; | vbl_clear = true; | ||||
| } | } | ||||
| @@ -137,6 +134,13 @@ int nes_loop(nes* sys, void* plat) { | |||||
| mapper_hsync = true; | 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 && | if ( sys->ppu.hit_line == sys->ppu.scanline && | ||||
| ( sys->core.memory.ppu.mask & | ( sys->core.memory.ppu.mask & | ||||
| (ppu_Mask_Sprite | ppu_Mask_Back)) && | (ppu_Mask_Sprite | ppu_Mask_Back)) && | ||||
| @@ -145,7 +149,7 @@ int nes_loop(nes* sys, void* plat) { | |||||
| if (dot >= hit_dot) { | if (dot >= hit_dot) { | ||||
| LOGD("Line %d sprite 0 hit @ %d", sys->ppu.hit_line, hit_dot); | LOGD("Line %d sprite 0 hit @ %d", sys->ppu.hit_line, hit_dot); | ||||
| sys->core.memory.ppu.status |= ppu_Status_Hit; | sys->core.memory.ppu.status |= ppu_Status_Hit; | ||||
| } else { | |||||
| } else if (target_dot > hit_dot) { | |||||
| 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); | 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) { | if (vbl_clear && dot >= vblank_clear_dot) { | ||||
| sys->core.memory.ppu.status &= ~ppu_Status_VBlank; | sys->core.memory.ppu.status &= ~ppu_Status_VBlank; | ||||
| vbl_clear = false; | vbl_clear = false; | ||||
| @@ -191,6 +204,10 @@ int nes_loop(nes* sys, void* plat) { | |||||
| if (dot >= nes_ppu_scanline_dots) { | if (dot >= nes_ppu_scanline_dots) { | ||||
| dot -= nes_ppu_scanline_dots; | dot -= nes_ppu_scanline_dots; | ||||
| status = nes_hsync(sys, plat); | 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, | void nes_ppu_set_mirroring(nes_PPU_Memory* mem, | ||||
| nes_Nametable_Mirroring mirror) { | nes_Nametable_Mirroring mirror) { | ||||
| for (int i = 0; i < 4; ++i) { | 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]); | vram_page(mem, (int)mirror_schemes[mirror][i]); | ||||
| } | } | ||||
| } | } | ||||
| @@ -97,19 +97,21 @@ typedef struct { | |||||
| uint8_t mask; | uint8_t mask; | ||||
| uint8_t status; | uint8_t status; | ||||
| uint8_t oam_addr; | 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 | // 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 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 oam[NES_PPU_OAM_SIZE]; | ||||
| uint8_t vram[NES_PPU_VRAM_SIZE]; | |||||
| } nes_PPU_Memory; | } nes_PPU_Memory; | ||||