__attribute__((hot)) static inline void ppu_write(struct nes_state *state, uint32_t offset, uint8_t value) { struct ppu_state *ppu = &state->ppu; switch(offset & 0x7) { case 0: { ppu->reg_ctrl = value; ppu->temp_addr = (ppu->temp_addr & 0xf3ff) | ((value & 0x03) << 10); } break; case 1: { ppu->reg_mask = value; } break; // case 2: // ONLY READ case 3: { ppu->oam_addr = value; } break; case 4: { ppu->oam[ppu->oam_addr++] = value; } break; case 5: { if(ppu->write_latch == 0) { ppu->fine_x = value & 0x07; ppu->temp_addr = (ppu->temp_addr & ~0x001f) | (value >> 3); ppu->write_latch = 1; } else { ppu->temp_addr = (ppu->temp_addr & ~0x73e0) | ((value & 0x07) << 12) | ((value & 0xf8) << 2); ppu->write_latch = 0; } } break; case 6: { if(ppu->write_latch == 0) { ppu->temp_addr = (ppu->temp_addr & 0x00ff) | ((value & 0x3f) << 8); ppu->write_latch = 1; } else { ppu->temp_addr = (ppu->temp_addr & 0xff00) | value; ppu->vram_addr = ppu->temp_addr; ppu->write_latch = 0; } } break; case 7: { uint32_t addr = ppu->vram_addr; switch(addr) { case 0x0000 ... 0x1fff: { state->mapper_function.chr_write(state, addr, value); } break; case 0x2000 ... 0x3eff: { state->mapper_function.ciram_write(state, addr, value); } break; case 0x3f00 ... 0x3fff: { uint32_t pal_addr = addr & 0x1f; if((pal_addr & 3) == 0) { pal_addr &= ~0x10; } ppu->palette[pal_addr] = value; } break; } ppu->vram_addr += (ppu->reg_ctrl & PPU_CTRL_VRAM_INCREMENT) ? 32 : 1; } break; } ppu->open_bus = value; return; } __attribute__((hot)) static inline uint8_t ppu_read(struct nes_state *state, uint32_t offset) { struct ppu_state *ppu = &state->ppu; uint8_t result = ppu->open_bus; switch(offset & 0x7) { case 2: { result &= 0x1f; result |= ppu->reg_status & 0xe0; // if(ppu->scanline == 241 && ppu->dot == 1) { // NMI suppression: reading $2002 on the exact dot vblank is set suppresses NMI // state->cpu.nmi_pending = 0; // } ppu->reg_status &= ~PPU_STATUS_VBLANK; ppu->write_latch = 0; } break; case 4: { result = ppu->oam[ppu->oam_addr]; } break; case 7: { uint32_t addr = ppu->vram_addr; switch(addr) { case 0x0000 ... 0x1fff: { result = ppu->vram_read_buffer; ppu->vram_read_buffer = state->mapper_function.chr_read(state, addr); } break; case 0x2000 ... 0x3eff: { result = ppu->vram_read_buffer; ppu->vram_read_buffer = state->mapper_function.ciram_read(state, addr); } break; case 0x3f00 ... 0x3fff: { uint32_t pal_addr = addr & 0x1f; if((pal_addr & 0x13) == 0x10) { pal_addr &= ~0x10; } result = ppu->palette[pal_addr] & 0x3f; result |= ppu->open_bus & 0xc0; ppu->vram_read_buffer = state->mapper_function.ciram_read(state, addr - 0x1000); } break; } ppu->vram_addr += (ppu->reg_ctrl & PPU_CTRL_VRAM_INCREMENT) ? 32 : 1; } break; } // ppu->open_bus = result; return result; } __attribute__((hot)) static inline void ppu_dma_4014(struct nes_state *state, uint8_t page) { uint32_t base = page << 8; // Add 1 or 2 idle cycles depending on current CPU cycle uint8_t idle_cycles = (state->cpu.cycles & 1) ? 1 : 2; for(uint8_t i = 0; i < idle_cycles; i++) { state->cpu.cycles++; ppu_tick(state); apu_tick(state); } for(uint32_t i = 0; i < 256; i++) { uint32_t addr = base + i; state->cpu.cycles++; ppu_tick(state); apu_tick(state); uint8_t value = state->ram[addr & 0x07ff]; // NOTE(peter): was; memory_read_dma(state, addr); state->cpu.cycles++; ppu_tick(state); apu_tick(state); ppu_write(state, 4, value); } }