From c44a0a8b74b9e6852d55f4bf7c1fed1a75bcd8fd Mon Sep 17 00:00:00 2001 From: Nathaniel Walizer Date: Tue, 11 Mar 2025 18:45:05 -0700 Subject: [PATCH] Add PPU register R/W --- src/f6502.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++++--- src/nes.c | 26 ++++++++++---- src/port.h | 1 + src/ppu.h | 50 ++++++++++++++++++++++++-- 4 files changed, 165 insertions(+), 14 deletions(-) diff --git a/src/f6502.c b/src/f6502.c index 4e0e4b5..8a664b7 100644 --- a/src/f6502.c +++ b/src/f6502.c @@ -42,17 +42,40 @@ static inline uint8_t f6502_read(nes_Memory* mem, return mem->rom_bank[(addr - 0x8000U) >> 13][addr & 0x1FFFFU]; } - switch (addr & 0x6000) { + switch (addr & 0x6000U) { case 0x0000: return mem->ram[addr & 0x7FFU]; case 0x2000: - // TODO: PPU Reg - return 0; + switch(addr & 0x7) { + case ppu_reg_status: + { uint8_t val = mem->ppu.status; + + mem->ppu.status &= ~ppu_Status_VBlank; + + mem->ppu.latch = 0; + + // TODO: Set nametable to 0 when in VBlank?? + + return val; + } + + case ppu_reg_oam_data: + return mem->ppu.oam[mem->ppu.oam_addr++]; + + case ppu_reg_data: + { addr = mem->ppu.addr & 0x3FFFU; + mem->ppu.addr += mem->ppu.addr_inc; + uint8_t val = mem->ppu.data; + mem->ppu.data = mem->ppu.bank[addr >> 10][addr & 0x3FFU]; + return val; + } + } + break; case 0x4000: // TODO: APU Reg - return 0; + break; case 0x6000: if (mem->sram_bank) { @@ -108,7 +131,76 @@ static inline bool f6502_write(nes_Memory* mem, break; case 0x2000: - // TODO: PPU + switch (addr & 0x7U) { + case ppu_reg_ctrl: + mem->ppu.ctrl &= ppu_Control_Nametable_Mask; + mem->ppu.ctrl |= (val & ~ppu_Control_Nametable_Mask); + mem->ppu.t &= ~(ppu_Control_Nametable_Mask << 10); + mem->ppu.t |= ((uint16_t)val & ppu_Control_Nametable_Mask) << 10; + mem->ppu.addr_inc = (val & ppu_Control_VRAM_Inc) ? 32 : 1; + // TODO: Trigger NMI if VBlank status is set? + break; + + case ppu_reg_mask: + mem->ppu.mask = val; + break; + + case ppu_reg_oam_addr: + mem->ppu.oam_addr = val; + break; + + case ppu_reg_oam_data: + mem->ppu.oam[(int)mem->ppu.oam_addr++] = val; + break; + + case ppu_reg_scroll: + if (mem->ppu.latch) { + mem->ppu.t &= 0b0000110000011111U; + mem->ppu.t |= (uint16_t)(val & 0b00000111U) << 12; + mem->ppu.t |= (uint16_t)(val & 0b11111000U) << 2; + } else { + mem->ppu.t &= ~(0b11111U); + mem->ppu.t |= (val & 0b11111000U) >> 3; + mem->ppu.x = (val & 0b111U); + } + mem->ppu.latch = !mem->ppu.latch; + break; + + case ppu_reg_addr: + if (mem->ppu.latch) { + mem->ppu.t &= 0xFF00U; + mem->ppu.t |= val; + mem->ppu.addr = (mem->ppu.t & 0x3FFFU); + // Keep ctrl & t nametable in sync + mem->ppu.ctrl &= ~ppu_Control_Nametable_Mask; + mem->ppu.ctrl |= ((val >> 10) & ppu_Control_Nametable_Mask); + } else { + mem->ppu.t &= 0x00FFU; + mem->ppu.t |= (uint16_t)(val & 0x3FU) << 8; + } + mem->ppu.latch = !mem->ppu.latch; + break; + + case ppu_reg_data: + // Disallow CHR ROM writes + if (addr >= NES_PPU_CHR_SIZE || mem->ppu.chr_ram) { + addr = mem->ppu.addr; + mem->ppu.bank[addr >> 10][addr & 0x3FFU] = val; + if (addr >= NES_PPU_PAL_START) { + addr = 0x300U | (addr & 0x1FU); + 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; + } + } + } + } + mem->ppu.addr += mem->ppu.addr_inc; + break; + } break; case 0x4000: diff --git a/src/nes.c b/src/nes.c index b88daa4..492024a 100644 --- a/src/nes.c +++ b/src/nes.c @@ -39,6 +39,16 @@ static int nes_hsync(nes* sys) { // TODO: PPU draw line if visible // TODO: PPU update regs + // TODO: This belongs in nes_ppu_drawline() +/* + if ( sys->ppu.scanline >= nes_ppu_visible_line && + sys->ppu.scanline < nes_ppu_postrender_line) { + // TODO: Pass scanline buffer + nese_line_ready(NULL, sys->ppu.scanline - + nes_ppu_visible_line); + } +*/ + sys->ppu.scanline++; if (nes_ppu_frame_end_line == sys->ppu.scanline) { sys->ppu.scanline = 0; @@ -71,14 +81,16 @@ int nes_loop(nes* sys) { int dots_remaining = nes_ppu_scanline_dots - dot; int cpu_cycles = (dots_remaining + 2) / 3; dot += 3 * f6502_step(&sys->core, cpu_cycles); - dot -= nes_ppu_scanline_dots; - // TODO: Validate dot >= 0? - nes_Memory* mem = &sys->core.memory; - if (NULL != mem->mapper.hsync) { - mem->mapper.hsync(&mem->mapper); - } - status = nes_hsync(sys); + // It's possible we returned due to a pending interrupt. + if (dot >= nes_ppu_scanline_dots) { + dot -= nes_ppu_scanline_dots; + nes_Memory* mem = &sys->core.memory; + if (NULL != mem->mapper.hsync) { + mem->mapper.hsync(&mem->mapper); + } + status = nes_hsync(sys); + } } return status; diff --git a/src/port.h b/src/port.h index aa0f2e5..0bfb2e3 100644 --- a/src/port.h +++ b/src/port.h @@ -9,6 +9,7 @@ void* nese_map_file(FILE* file, int size); int nese_unmap_file(void* addr, int size); +int nese_line_ready(uint8_t* buffer, int line); int nese_frame_ready(); int nese_update_input(nes_Input*); diff --git a/src/ppu.h b/src/ppu.h index 1d1b2d4..0abc57f 100644 --- a/src/ppu.h +++ b/src/ppu.h @@ -11,6 +11,38 @@ #define nes_ppu_vblank_line (242U) #define nes_ppu_frame_end_line (262U) + +typedef enum { + ppu_reg_ctrl = 0, + ppu_reg_mask, + ppu_reg_status, + ppu_reg_oam_addr, + ppu_reg_oam_data, + ppu_reg_scroll, + ppu_reg_addr, + ppu_reg_data, +} ppu_reg_offset; + +typedef enum { + ppu_Control_Nametable_Mask = 0b00000011, + ppu_Control_Scroll_Page_X = 0b00000001, + ppu_Control_Scroll_Page_Y = 0b00000010, + ppu_Control_VRAM_Inc = 0b00000100, + ppu_Control_Sprite_Bank = 0b00001000, + ppu_Control_Back_Bank = 0b00010000, + ppu_Control_Sprite_Size = 0b00100000, + ppu_Control_Master = 0b01000000, + ppu_Control_VBlank = 0b10000000, +} nes_ppu_Control; + +typedef enum { + ppu_Status_Open_Bus_Mask = 0b00011111, + ppu_Status_Overflow = 0b00100000, + ppu_Status_Hit = 0b01000000, + ppu_Status_VBlank = 0b10000000, +} nes_ppu_Status; + + #define NES_CHR_ROM_PAGE_SIZE (0x0400U) #define NES_VRAM_PAGE_SIZE (0x0400U) @@ -18,11 +50,25 @@ #define NES_PPU_VRAM_SIZE (0x1000U) #define NES_PPU_RAM_SIZE (0x4000U) #define NES_PPU_OAM_SIZE (64U * 4U) +#define NES_PPU_PAL_START (0x3F00U) -typedef struct __attribute__ ((__packed__)) { +typedef struct { + // Registers &c. + uint8_t ctrl; + 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) + + // Memory banks uint8_t* chr; int n_chr_banks; - uint8_t* chr_ram; // Could this be a flag? + uint8_t* chr_ram; // TODO: Could this be a flag? uint8_t* bank[16]; uint8_t vram[NES_PPU_VRAM_SIZE]; uint8_t pal_bank[NES_CHR_ROM_PAGE_SIZE]; // Mirrored in banks 12-15, also pal