aboutsummaryrefslogtreecommitdiffstats
path: root/src/gb.c
diff options
context:
space:
mode:
authorgbc dev <gbc@localhost>2026-07-04 22:11:10 +0200
committergbc dev <gbc@localhost>2026-07-04 22:11:10 +0200
commitbc48df9f7162a4a816fdc0edaa6ed08304008c9c (patch)
tree30129f7655c177a78e62505c55243a5c0540126f /src/gb.c
parentscaffolding: build system, cartridge loader with MBC1/2/3/5, core types (diff)
downloadsl0pboy-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.c201
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;
+}