| @@ -42,17 +42,40 @@ static inline uint8_t f6502_read(nes_Memory* mem, | |||||
| return mem->rom_bank[(addr - 0x8000U) >> 13][addr & 0x1FFFFU]; | return mem->rom_bank[(addr - 0x8000U) >> 13][addr & 0x1FFFFU]; | ||||
| } | } | ||||
| switch (addr & 0x6000) { | |||||
| switch (addr & 0x6000U) { | |||||
| case 0x0000: | case 0x0000: | ||||
| return mem->ram[addr & 0x7FFU]; | return mem->ram[addr & 0x7FFU]; | ||||
| case 0x2000: | 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: | case 0x4000: | ||||
| // TODO: APU Reg | // TODO: APU Reg | ||||
| return 0; | |||||
| break; | |||||
| case 0x6000: | case 0x6000: | ||||
| if (mem->sram_bank) { | if (mem->sram_bank) { | ||||
| @@ -108,7 +131,76 @@ static inline bool f6502_write(nes_Memory* mem, | |||||
| break; | break; | ||||
| case 0x2000: | 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; | break; | ||||
| case 0x4000: | case 0x4000: | ||||
| @@ -39,6 +39,16 @@ static int nes_hsync(nes* sys) { | |||||
| // TODO: PPU draw line if visible | // TODO: PPU draw line if visible | ||||
| // TODO: PPU update regs | // 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++; | sys->ppu.scanline++; | ||||
| if (nes_ppu_frame_end_line == sys->ppu.scanline) { | if (nes_ppu_frame_end_line == sys->ppu.scanline) { | ||||
| sys->ppu.scanline = 0; | sys->ppu.scanline = 0; | ||||
| @@ -71,14 +81,16 @@ int nes_loop(nes* sys) { | |||||
| int dots_remaining = nes_ppu_scanline_dots - dot; | int dots_remaining = nes_ppu_scanline_dots - dot; | ||||
| int cpu_cycles = (dots_remaining + 2) / 3; | int cpu_cycles = (dots_remaining + 2) / 3; | ||||
| dot += 3 * f6502_step(&sys->core, cpu_cycles); | 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; | return status; | ||||
| @@ -9,6 +9,7 @@ | |||||
| void* nese_map_file(FILE* file, int size); | void* nese_map_file(FILE* file, int size); | ||||
| int nese_unmap_file(void* addr, 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_frame_ready(); | ||||
| int nese_update_input(nes_Input*); | int nese_update_input(nes_Input*); | ||||
| @@ -11,6 +11,38 @@ | |||||
| #define nes_ppu_vblank_line (242U) | #define nes_ppu_vblank_line (242U) | ||||
| #define nes_ppu_frame_end_line (262U) | #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_CHR_ROM_PAGE_SIZE (0x0400U) | ||||
| #define NES_VRAM_PAGE_SIZE (0x0400U) | #define NES_VRAM_PAGE_SIZE (0x0400U) | ||||
| @@ -18,11 +50,25 @@ | |||||
| #define NES_PPU_VRAM_SIZE (0x1000U) | #define NES_PPU_VRAM_SIZE (0x1000U) | ||||
| #define NES_PPU_RAM_SIZE (0x4000U) | #define NES_PPU_RAM_SIZE (0x4000U) | ||||
| #define NES_PPU_OAM_SIZE (64U * 4U) | #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; | uint8_t* chr; | ||||
| int n_chr_banks; | 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* bank[16]; | ||||
| uint8_t vram[NES_PPU_VRAM_SIZE]; | uint8_t vram[NES_PPU_VRAM_SIZE]; | ||||
| uint8_t pal_bank[NES_CHR_ROM_PAGE_SIZE]; // Mirrored in banks 12-15, also pal | uint8_t pal_bank[NES_CHR_ROM_PAGE_SIZE]; // Mirrored in banks 12-15, also pal | ||||