| @@ -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: | |||
| @@ -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; | |||
| @@ -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*); | |||
| @@ -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 | |||