diff options
| author | gbc dev <gbc@localhost> | 2026-07-04 22:11:10 +0200 |
|---|---|---|
| committer | gbc dev <gbc@localhost> | 2026-07-04 22:11:10 +0200 |
| commit | bc48df9f7162a4a816fdc0edaa6ed08304008c9c (patch) | |
| tree | 30129f7655c177a78e62505c55243a5c0540126f /src/gb.c | |
| parent | scaffolding: build system, cartridge loader with MBC1/2/3/5, core types (diff) | |
| download | sl0pboy-bc48df9f7162a4a816fdc0edaa6ed08304008c9c.tar.gz sl0pboy-bc48df9f7162a4a816fdc0edaa6ed08304008c9c.tar.xz sl0pboy-bc48df9f7162a4a816fdc0edaa6ed08304008c9c.zip | |
core emulator: SM83 CPU, MMU, timer, PPU, CGB support
Passes blargg cpu_instrs (all 11), instr_timing, mem_timing.
Diffstat (limited to 'src/gb.c')
| -rw-r--r-- | src/gb.c | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/src/gb.c b/src/gb.c new file mode 100644 index 0000000..9ed2521 --- /dev/null +++ b/src/gb.c @@ -0,0 +1,201 @@ +#include "gb.h" +#include "ppu.h" +#include "timer.h" +#include "cpu.h" +#include <string.h> +#include <stdio.h> + +void gb_request_interrupt(GB *gb, u8 flag) { + gb->iff |= flag; +} + +// ---------- Joypad ---------- +static u8 joyp_read(GB *gb) { + u8 r = 0xCF | gb->joyp_sel; + // sel bit4=directions, bit5=buttons ; 0 = selected. Pressed = 0. + if (!(gb->joyp_sel & 0x10)) { // directions + if (gb->buttons & BTN_RIGHT) r &= ~0x01; + if (gb->buttons & BTN_LEFT) r &= ~0x02; + if (gb->buttons & BTN_UP) r &= ~0x04; + if (gb->buttons & BTN_DOWN) r &= ~0x08; + } + if (!(gb->joyp_sel & 0x20)) { // buttons + if (gb->buttons & BTN_A) r &= ~0x01; + if (gb->buttons & BTN_B) r &= ~0x02; + if (gb->buttons & BTN_SELECT) r &= ~0x04; + if (gb->buttons & BTN_START) r &= ~0x08; + } + return r; +} + +// ---------- OAM DMA ---------- +static void oam_dma(GB *gb, u8 val) { + gb->dma = val; + u16 src = val << 8; + for (int i = 0; i < 0xA0; i++) + gb->ppu.oam[i] = gb_read(gb, src + i); +} + +// ---------- HDMA (CGB) ---------- +static void hdma_do_block(GB *gb) { + for (int i = 0; i < 0x10; i++) { + u8 v = gb_read(gb, gb->hdma_src++); + ppu_write(gb, 0x8000 + (gb->hdma_dst++ & 0x1FFF), v); + } +} + +// ---------- Bus ---------- +u8 gb_read(GB *gb, u16 addr) { + if (addr < 0x8000) return cart_read(&gb->cart, addr); + if (addr < 0xA000) return ppu_read(gb, addr); + if (addr < 0xC000) return cart_read(&gb->cart, addr); + if (addr < 0xD000) return gb->wram[addr - 0xC000]; + if (addr < 0xE000) { + int bank = gb->cgb_mode ? (gb->wram_bank ? gb->wram_bank : 1) : 1; + return gb->wram[bank * 0x1000 + (addr - 0xD000)]; + } + if (addr < 0xFE00) return gb_read(gb, addr - 0x2000); // echo + if (addr < 0xFEA0) return ppu_read(gb, addr); + if (addr < 0xFF00) return 0xFF; // unusable + + // IO + if (addr == 0xFF00) return joyp_read(gb); + if (addr == 0xFF01) return gb->sb; + if (addr == 0xFF02) return gb->sc | 0x7E; + if (addr >= 0xFF04 && addr <= 0xFF07) return timer_read(gb, addr); + if (addr == 0xFF0F) return gb->iff | 0xE0; + if (addr >= 0xFF10 && addr <= 0xFF3F) return 0xFF; // APU (stub) + if (addr >= 0xFF40 && addr <= 0xFF4B) { + if (addr == 0xFF46) return gb->dma; + return ppu_read_reg(gb, addr); + } + if (addr == 0xFF4D) return gb->cgb_mode ? (gb->key1 | 0x7E) : 0xFF; + if (addr == 0xFF4F) return gb->cgb_mode ? (gb->ppu.vram_bank | 0xFE) : 0xFF; + if (addr == 0xFF51 && gb->cgb_mode) return gb->hdma_src >> 8; + if (addr == 0xFF52 && gb->cgb_mode) return gb->hdma_src & 0xFF; + if (addr == 0xFF53 && gb->cgb_mode) return gb->hdma_dst >> 8; + if (addr == 0xFF54 && gb->cgb_mode) return gb->hdma_dst & 0xFF; + if (addr == 0xFF55 && gb->cgb_mode) + return gb->hdma_active ? gb->hdma_len : 0xFF; + if ((addr == 0xFF68 || addr == 0xFF69 || addr == 0xFF6A || addr == 0xFF6B) + && gb->cgb_mode) return ppu_read_reg(gb, addr); + if (addr == 0xFF70) return gb->cgb_mode ? (gb->wram_bank | 0xF8) : 0xFF; + + if (addr >= 0xFF80 && addr < 0xFFFF) return gb->hram[addr - 0xFF80]; + if (addr == 0xFFFF) return gb->ie; + return 0xFF; +} + +void gb_write(GB *gb, u16 addr, u8 val) { + if (addr < 0x8000) { cart_write(&gb->cart, addr, val); return; } + if (addr < 0xA000) { ppu_write(gb, addr, val); return; } + if (addr < 0xC000) { cart_write(&gb->cart, addr, val); return; } + if (addr < 0xD000) { gb->wram[addr - 0xC000] = val; return; } + if (addr < 0xE000) { + int bank = gb->cgb_mode ? (gb->wram_bank ? gb->wram_bank : 1) : 1; + gb->wram[bank * 0x1000 + (addr - 0xD000)] = val; return; + } + if (addr < 0xFE00) { gb_write(gb, addr - 0x2000, val); return; } + if (addr < 0xFEA0) { ppu_write(gb, addr, val); return; } + if (addr < 0xFF00) return; + + if (addr == 0xFF00) { gb->joyp_sel = val & 0x30; return; } + if (addr == 0xFF01) { gb->sb = val; return; } + if (addr == 0xFF02) { + gb->sc = val; + if ((val & 0x81) == 0x81) { + // transfer with internal clock: log & complete instantly + if (gb->serial_log) { fputc(gb->sb, stderr); fflush(stderr); } + gb->sb = 0xFF; + gb->sc &= ~0x80; + gb_request_interrupt(gb, INT_SERIAL); + } + return; + } + if (addr >= 0xFF04 && addr <= 0xFF07) { timer_write(gb, addr, val); return; } + if (addr == 0xFF0F) { gb->iff = val & 0x1F; return; } + if (addr >= 0xFF10 && addr <= 0xFF3F) return; // APU stub + if (addr >= 0xFF40 && addr <= 0xFF4B) { + if (addr == 0xFF46) { oam_dma(gb, val); return; } + ppu_write_reg(gb, addr, val); + return; + } + if (addr == 0xFF4D && gb->cgb_mode) { gb->key1 = (gb->key1 & 0x80) | (val & 1); return; } + if (addr == 0xFF4F && gb->cgb_mode) { gb->ppu.vram_bank = val & 1; return; } + if (addr == 0xFF51 && gb->cgb_mode) { gb->hdma_src = (gb->hdma_src & 0xFF) | (val << 8); return; } + if (addr == 0xFF52 && gb->cgb_mode) { gb->hdma_src = (gb->hdma_src & 0xFF00) | (val & 0xF0); return; } + if (addr == 0xFF53 && gb->cgb_mode) { gb->hdma_dst = (gb->hdma_dst & 0xFF) | ((val & 0x1F) << 8); return; } + if (addr == 0xFF54 && gb->cgb_mode) { gb->hdma_dst = (gb->hdma_dst & 0xFF00) | (val & 0xF0); return; } + if (addr == 0xFF55 && gb->cgb_mode) { + int len = ((val & 0x7F) + 1) * 0x10; + if (val & 0x80) { + // HDMA (mode 1): transfer during hblank + gb->hdma_len = val & 0x7F; + gb->hdma_active = true; + } else { + if (gb->hdma_active) { + // stopping an active hblank dma + gb->hdma_active = false; + gb->hdma_len = 0xFF; + } else { + // GDMA: transfer all now + for (int i = 0; i < len; i++) { + u8 v = gb_read(gb, gb->hdma_src++); + ppu_write(gb, 0x8000 + (gb->hdma_dst++ & 0x1FFF), v); + } + } + } + return; + } + if ((addr >= 0xFF68 && addr <= 0xFF6B) && gb->cgb_mode) { ppu_write_reg(gb, addr, val); return; } + if (addr == 0xFF70 && gb->cgb_mode) { gb->wram_bank = val & 0x07; return; } + + if (addr >= 0xFF80 && addr < 0xFFFF) { gb->hram[addr - 0xFF80] = val; return; } + if (addr == 0xFFFF) { gb->ie = val; return; } +} + +// advance subsystems by t-cycles +void gb_tick(GB *gb, int tcycles) { + // in double-speed mode CPU/timer run 2x, but PPU stays at normal rate. + int ppu_cycles = gb->double_speed ? tcycles / 2 : tcycles; + timer_tick(gb, tcycles); + ppu_tick(gb, ppu_cycles); + gb->cycles += tcycles; +} + +// HDMA one block per hblank; called by ppu when entering hblank +void gb_hdma_hblank(GB *gb) { + if (!gb->hdma_active) return; + hdma_do_block(gb); + if (gb->hdma_len == 0) { + gb->hdma_active = false; + gb->hdma_len = 0xFF; + } else { + gb->hdma_len--; + } +} + +void gb_init(GB *gb) { + memset(gb, 0, sizeof(*gb)); + gb_reset(gb); +} + +void gb_reset(GB *gb) { + CPU *c = &gb->cpu; + gb->cgb_mode = gb->cart.cgb; + // post-boot register state + c->pc = 0x0100; + c->sp = 0xFFFE; + if (gb->cgb_mode) { + c->af = 0x1180; c->bc = 0x0000; c->de = 0xFF56; c->hl = 0x000D; + } else { + c->af = 0x01B0; c->bc = 0x0013; c->de = 0x00D8; c->hl = 0x014D; + } + c->ime = false; + gb->wram_bank = 1; + gb->joyp_sel = 0x30; + gb->iff = 0xE1; + gb->hdma_len = 0xFF; + ppu_reset(gb); + gb->timer.div = 0x1800; +} |
