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/cpu.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/cpu.c')
| -rw-r--r-- | src/cpu.c | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/src/cpu.c b/src/cpu.c new file mode 100644 index 0000000..9e7f685 --- /dev/null +++ b/src/cpu.c @@ -0,0 +1,446 @@ +#include "cpu.h" + +#define FZ 0x80 +#define FN 0x40 +#define FH 0x20 +#define FC 0x10 + +// ---- memory access helpers (each ticks 4 T-cycles) ---- +static inline u8 rb(GB *gb, u16 a) { u8 v = gb_read(gb, a); gb_tick(gb, 4); return v; } +static inline void wb(GB *gb, u16 a, u8 v) { gb_write(gb, a, v); gb_tick(gb, 4); } +static inline void internal(GB *gb) { gb_tick(gb, 4); } + +static inline u8 fetch8(GB *gb) { + u8 v = gb_read(gb, gb->cpu.pc); + if (gb->cpu.halt_bug) { gb->cpu.halt_bug = false; } + else gb->cpu.pc++; + gb_tick(gb, 4); + return v; +} +static inline u16 fetch16(GB *gb) { + u8 lo = fetch8(gb); + u8 hi = fetch8(gb); + return lo | (hi << 8); +} + +// ---- flag helpers ---- +static inline void setf(CPU *c, u8 mask, bool cond) { + if (cond) c->f |= mask; else c->f &= ~mask; + c->f &= 0xF0; +} + +// ---- 8-bit ALU ---- +static u8 alu_inc(CPU *c, u8 v) { + u8 r = v + 1; + setf(c, FZ, r == 0); + setf(c, FN, false); + setf(c, FH, (v & 0x0F) == 0x0F); + return r; +} +static u8 alu_dec(CPU *c, u8 v) { + u8 r = v - 1; + setf(c, FZ, r == 0); + setf(c, FN, true); + setf(c, FH, (v & 0x0F) == 0x00); + return r; +} +static void alu_add(CPU *c, u8 v) { + u16 r = c->a + v; + setf(c, FH, ((c->a & 0xF) + (v & 0xF)) > 0xF); + setf(c, FC, r > 0xFF); + c->a = r; + setf(c, FZ, c->a == 0); + setf(c, FN, false); +} +static void alu_adc(CPU *c, u8 v) { + u8 carry = (c->f & FC) ? 1 : 0; + u16 r = c->a + v + carry; + setf(c, FH, ((c->a & 0xF) + (v & 0xF) + carry) > 0xF); + setf(c, FC, r > 0xFF); + c->a = r; + setf(c, FZ, c->a == 0); + setf(c, FN, false); +} +static void alu_sub(CPU *c, u8 v) { + setf(c, FH, (c->a & 0xF) < (v & 0xF)); + setf(c, FC, c->a < v); + c->a -= v; + setf(c, FZ, c->a == 0); + setf(c, FN, true); +} +static void alu_sbc(CPU *c, u8 v) { + u8 carry = (c->f & FC) ? 1 : 0; + int r = c->a - v - carry; + setf(c, FH, ((c->a & 0xF) - (v & 0xF) - carry) < 0); + setf(c, FC, r < 0); + c->a = r; + setf(c, FZ, c->a == 0); + setf(c, FN, true); +} +static void alu_and(CPU *c, u8 v) { + c->a &= v; + setf(c, FZ, c->a == 0); + setf(c, FN, false); setf(c, FH, true); setf(c, FC, false); +} +static void alu_or(CPU *c, u8 v) { + c->a |= v; + setf(c, FZ, c->a == 0); + setf(c, FN, false); setf(c, FH, false); setf(c, FC, false); +} +static void alu_xor(CPU *c, u8 v) { + c->a ^= v; + setf(c, FZ, c->a == 0); + setf(c, FN, false); setf(c, FH, false); setf(c, FC, false); +} +static void alu_cp(CPU *c, u8 v) { + setf(c, FH, (c->a & 0xF) < (v & 0xF)); + setf(c, FC, c->a < v); + setf(c, FZ, c->a == v); + setf(c, FN, true); +} + +static void add_hl(CPU *c, u16 v) { + u32 r = c->hl + v; + setf(c, FN, false); + setf(c, FH, ((c->hl & 0x0FFF) + (v & 0x0FFF)) > 0x0FFF); + setf(c, FC, r > 0xFFFF); + c->hl = r; +} + +static u16 add_sp_e(GB *gb, s8 e) { + CPU *c = &gb->cpu; + u16 sp = c->sp; + setf(c, FZ, false); setf(c, FN, false); + setf(c, FH, ((sp & 0xF) + (e & 0xF)) > 0xF); + setf(c, FC, ((sp & 0xFF) + (e & 0xFF)) > 0xFF); + return sp + e; +} + +static void daa(CPU *c) { + int a = c->a; + if (!(c->f & FN)) { + if ((c->f & FH) || (a & 0x0F) > 9) a += 0x06; + if ((c->f & FC) || a > 0x9F) { a += 0x60; setf(c, FC, true); } + } else { + if (c->f & FH) a = (a - 6) & 0xFF; + if (c->f & FC) a -= 0x60; + } + c->a = a; + setf(c, FZ, c->a == 0); + setf(c, FH, false); +} + +// ---- CB rotate/shift ---- +static u8 cb_rlc(CPU *c, u8 v){ u8 r=(v<<1)|(v>>7); setf(c,FC,v&0x80); setf(c,FZ,r==0); setf(c,FN,false); setf(c,FH,false); return r; } +static u8 cb_rrc(CPU *c, u8 v){ u8 r=(v>>1)|(v<<7); setf(c,FC,v&0x01); setf(c,FZ,r==0); setf(c,FN,false); setf(c,FH,false); return r; } +static u8 cb_rl (CPU *c, u8 v){ u8 cy=(c->f&FC)?1:0; u8 r=(v<<1)|cy; setf(c,FC,v&0x80); setf(c,FZ,r==0); setf(c,FN,false); setf(c,FH,false); return r; } +static u8 cb_rr (CPU *c, u8 v){ u8 cy=(c->f&FC)?0x80:0; u8 r=(v>>1)|cy; setf(c,FC,v&0x01); setf(c,FZ,r==0); setf(c,FN,false); setf(c,FH,false); return r; } +static u8 cb_sla(CPU *c, u8 v){ u8 r=v<<1; setf(c,FC,v&0x80); setf(c,FZ,r==0); setf(c,FN,false); setf(c,FH,false); return r; } +static u8 cb_sra(CPU *c, u8 v){ u8 r=(v>>1)|(v&0x80); setf(c,FC,v&0x01); setf(c,FZ,r==0); setf(c,FN,false); setf(c,FH,false); return r; } +static u8 cb_swap(CPU *c,u8 v){ u8 r=(v>>4)|(v<<4); setf(c,FC,false); setf(c,FZ,r==0); setf(c,FN,false); setf(c,FH,false); return r; } +static u8 cb_srl(CPU *c, u8 v){ u8 r=v>>1; setf(c,FC,v&0x01); setf(c,FZ,r==0); setf(c,FN,false); setf(c,FH,false); return r; } + +// r index: 0=B 1=C 2=D 3=E 4=H 5=L 6=(HL) 7=A +static u8 reg_get(GB *gb, int i) { + CPU *c = &gb->cpu; + switch (i) { + case 0: return c->b; case 1: return c->c; case 2: return c->d; + case 3: return c->e; case 4: return c->h; case 5: return c->l; + case 6: return rb(gb, c->hl); case 7: return c->a; + } + return 0; +} +static void reg_set(GB *gb, int i, u8 v) { + CPU *c = &gb->cpu; + switch (i) { + case 0: c->b=v; break; case 1: c->c=v; break; case 2: c->d=v; break; + case 3: c->e=v; break; case 4: c->h=v; break; case 5: c->l=v; break; + case 6: wb(gb, c->hl, v); break; case 7: c->a=v; break; + } +} + +static void push16(GB *gb, u16 v) { + CPU *c = &gb->cpu; + wb(gb, --c->sp, v >> 8); + wb(gb, --c->sp, v & 0xFF); +} +static u16 pop16(GB *gb) { + CPU *c = &gb->cpu; + u8 lo = rb(gb, c->sp++); + u8 hi = rb(gb, c->sp++); + return lo | (hi << 8); +} + +// ---- CB prefix ---- +static void do_cb(GB *gb) { + CPU *c = &gb->cpu; + u8 op = fetch8(gb); + int r = op & 7; + int y = (op >> 3) & 7; + if (op < 0x40) { + u8 v = reg_get(gb, r); + u8 res; + switch (y) { + case 0: res = cb_rlc(c, v); break; + case 1: res = cb_rrc(c, v); break; + case 2: res = cb_rl(c, v); break; + case 3: res = cb_rr(c, v); break; + case 4: res = cb_sla(c, v); break; + case 5: res = cb_sra(c, v); break; + case 6: res = cb_swap(c, v);break; + default:res = cb_srl(c, v); break; + } + reg_set(gb, r, res); + } else if (op < 0x80) { // BIT + u8 v = reg_get(gb, r); + setf(c, FZ, !(v & (1 << y))); + setf(c, FN, false); + setf(c, FH, true); + } else if (op < 0xC0) { // RES + u8 v = reg_get(gb, r); + reg_set(gb, r, v & ~(1 << y)); + } else { // SET + u8 v = reg_get(gb, r); + reg_set(gb, r, v | (1 << y)); + } +} + +// ---- interrupt handling ---- +static bool handle_interrupts(GB *gb) { + CPU *c = &gb->cpu; + u8 pending = gb->iff & gb->ie & 0x1F; + if (!pending) return false; + + if (c->halted) c->halted = false; + if (!c->ime) return false; + + c->ime = false; + internal(gb); internal(gb); // 2 internal cycles + push16(gb, c->pc); // 2 memory cycles + // determine vector after push (IE may change but pending latched) + pending = gb->iff & gb->ie & 0x1F; + int bit = 0; + for (; bit < 5; bit++) if (pending & (1 << bit)) break; + if (bit == 5) { c->pc = 0; internal(gb); return true; } // cancelled + gb->iff &= ~(1 << bit); + c->pc = 0x40 + bit * 8; + internal(gb); + return true; +} + +int cpu_step(GB *gb) { + CPU *c = &gb->cpu; + u64 start = gb->cycles; + + // EI takes effect after the following instruction + bool ei_was_pending = c->ime_pending; + + if (handle_interrupts(gb)) { + if (ei_was_pending) { /* handled below */ } + return (int)(gb->cycles - start); + } + + if (c->halted) { + gb_tick(gb, 4); + return (int)(gb->cycles - start); + } + + u8 op = fetch8(gb); + + switch (op) { + case 0x00: break; // NOP + case 0x10: fetch8(gb); // STOP + if (gb->cgb_mode && (gb->key1 & 0x01)) { + gb->double_speed = !gb->double_speed; + gb->key1 = (gb->double_speed ? 0x80 : 0x00); + } + break; + case 0x76: // HALT + if (!c->ime && (gb->iff & gb->ie & 0x1F)) c->halt_bug = true; + else c->halted = true; + break; + + // 16-bit loads + case 0x01: c->bc = fetch16(gb); break; + case 0x11: c->de = fetch16(gb); break; + case 0x21: c->hl = fetch16(gb); break; + case 0x31: c->sp = fetch16(gb); break; + case 0x08: { u16 a = fetch16(gb); wb(gb, a, c->sp & 0xFF); wb(gb, a+1, c->sp >> 8); } break; + case 0xF9: c->sp = c->hl; internal(gb); break; + case 0xF8: { s8 e = (s8)fetch8(gb); c->hl = add_sp_e(gb, e); internal(gb); } break; + + // 8-bit loads (immediate) + case 0x06: c->b = fetch8(gb); break; + case 0x0E: c->c = fetch8(gb); break; + case 0x16: c->d = fetch8(gb); break; + case 0x1E: c->e = fetch8(gb); break; + case 0x26: c->h = fetch8(gb); break; + case 0x2E: c->l = fetch8(gb); break; + case 0x36: wb(gb, c->hl, fetch8(gb)); break; + case 0x3E: c->a = fetch8(gb); break; + + // LD (rr),A and A,(rr) + case 0x02: wb(gb, c->bc, c->a); break; + case 0x12: wb(gb, c->de, c->a); break; + case 0x22: wb(gb, c->hl++, c->a); break; + case 0x32: wb(gb, c->hl--, c->a); break; + case 0x0A: c->a = rb(gb, c->bc); break; + case 0x1A: c->a = rb(gb, c->de); break; + case 0x2A: c->a = rb(gb, c->hl++); break; + case 0x3A: c->a = rb(gb, c->hl--); break; + + // 16-bit inc/dec + case 0x03: c->bc++; internal(gb); break; + case 0x13: c->de++; internal(gb); break; + case 0x23: c->hl++; internal(gb); break; + case 0x33: c->sp++; internal(gb); break; + case 0x0B: c->bc--; internal(gb); break; + case 0x1B: c->de--; internal(gb); break; + case 0x2B: c->hl--; internal(gb); break; + case 0x3B: c->sp--; internal(gb); break; + + // 8-bit inc + case 0x04: c->b = alu_inc(c, c->b); break; + case 0x0C: c->c = alu_inc(c, c->c); break; + case 0x14: c->d = alu_inc(c, c->d); break; + case 0x1C: c->e = alu_inc(c, c->e); break; + case 0x24: c->h = alu_inc(c, c->h); break; + case 0x2C: c->l = alu_inc(c, c->l); break; + case 0x34: { u8 v = rb(gb, c->hl); wb(gb, c->hl, alu_inc(c, v)); } break; + case 0x3C: c->a = alu_inc(c, c->a); break; + // 8-bit dec + case 0x05: c->b = alu_dec(c, c->b); break; + case 0x0D: c->c = alu_dec(c, c->c); break; + case 0x15: c->d = alu_dec(c, c->d); break; + case 0x1D: c->e = alu_dec(c, c->e); break; + case 0x25: c->h = alu_dec(c, c->h); break; + case 0x2D: c->l = alu_dec(c, c->l); break; + case 0x35: { u8 v = rb(gb, c->hl); wb(gb, c->hl, alu_dec(c, v)); } break; + case 0x3D: c->a = alu_dec(c, c->a); break; + + // ADD HL,rr + case 0x09: add_hl(c, c->bc); internal(gb); break; + case 0x19: add_hl(c, c->de); internal(gb); break; + case 0x29: add_hl(c, c->hl); internal(gb); break; + case 0x39: add_hl(c, c->sp); internal(gb); break; + + // rotates on A + case 0x07: { u8 v=c->a; c->a=(v<<1)|(v>>7); setf(c,FC,v&0x80); setf(c,FZ,false);setf(c,FN,false);setf(c,FH,false);} break; + case 0x0F: { u8 v=c->a; c->a=(v>>1)|(v<<7); setf(c,FC,v&0x01); setf(c,FZ,false);setf(c,FN,false);setf(c,FH,false);} break; + case 0x17: { u8 cy=(c->f&FC)?1:0; u8 v=c->a; c->a=(v<<1)|cy; setf(c,FC,v&0x80); setf(c,FZ,false);setf(c,FN,false);setf(c,FH,false);} break; + case 0x1F: { u8 cy=(c->f&FC)?0x80:0; u8 v=c->a; c->a=(v>>1)|cy; setf(c,FC,v&0x01); setf(c,FZ,false);setf(c,FN,false);setf(c,FH,false);} break; + + case 0x27: daa(c); break; + case 0x2F: c->a = ~c->a; setf(c,FN,true); setf(c,FH,true); break; + case 0x37: setf(c,FC,true); setf(c,FN,false); setf(c,FH,false); break; + case 0x3F: setf(c,FC,!(c->f&FC)); setf(c,FN,false); setf(c,FH,false); break; + + // control flow + case 0xC3: { u16 a=fetch16(gb); c->pc=a; internal(gb);} break; + case 0xE9: c->pc = c->hl; break; + case 0x18: { s8 e=(s8)fetch8(gb); c->pc+=e; internal(gb);} break; + case 0x20: { s8 e=(s8)fetch8(gb); if(!(c->f&FZ)){c->pc+=e; internal(gb);} } break; + case 0x28: { s8 e=(s8)fetch8(gb); if( (c->f&FZ)){c->pc+=e; internal(gb);} } break; + case 0x30: { s8 e=(s8)fetch8(gb); if(!(c->f&FC)){c->pc+=e; internal(gb);} } break; + case 0x38: { s8 e=(s8)fetch8(gb); if( (c->f&FC)){c->pc+=e; internal(gb);} } break; + case 0xC2: { u16 a=fetch16(gb); if(!(c->f&FZ)){c->pc=a; internal(gb);} } break; + case 0xCA: { u16 a=fetch16(gb); if( (c->f&FZ)){c->pc=a; internal(gb);} } break; + case 0xD2: { u16 a=fetch16(gb); if(!(c->f&FC)){c->pc=a; internal(gb);} } break; + case 0xDA: { u16 a=fetch16(gb); if( (c->f&FC)){c->pc=a; internal(gb);} } break; + + case 0xCD: { u16 a=fetch16(gb); internal(gb); push16(gb,c->pc); c->pc=a; } break; + case 0xC4: { u16 a=fetch16(gb); if(!(c->f&FZ)){internal(gb);push16(gb,c->pc);c->pc=a;} } break; + case 0xCC: { u16 a=fetch16(gb); if( (c->f&FZ)){internal(gb);push16(gb,c->pc);c->pc=a;} } break; + case 0xD4: { u16 a=fetch16(gb); if(!(c->f&FC)){internal(gb);push16(gb,c->pc);c->pc=a;} } break; + case 0xDC: { u16 a=fetch16(gb); if( (c->f&FC)){internal(gb);push16(gb,c->pc);c->pc=a;} } break; + + case 0xC9: c->pc = pop16(gb); internal(gb); break; + case 0xD9: c->pc = pop16(gb); internal(gb); c->ime = true; break; // RETI + case 0xC0: internal(gb); if(!(c->f&FZ)){c->pc=pop16(gb);internal(gb);} break; + case 0xC8: internal(gb); if( (c->f&FZ)){c->pc=pop16(gb);internal(gb);} break; + case 0xD0: internal(gb); if(!(c->f&FC)){c->pc=pop16(gb);internal(gb);} break; + case 0xD8: internal(gb); if( (c->f&FC)){c->pc=pop16(gb);internal(gb);} break; + + case 0xC7: internal(gb); push16(gb,c->pc); c->pc=0x00; break; + case 0xCF: internal(gb); push16(gb,c->pc); c->pc=0x08; break; + case 0xD7: internal(gb); push16(gb,c->pc); c->pc=0x10; break; + case 0xDF: internal(gb); push16(gb,c->pc); c->pc=0x18; break; + case 0xE7: internal(gb); push16(gb,c->pc); c->pc=0x20; break; + case 0xEF: internal(gb); push16(gb,c->pc); c->pc=0x28; break; + case 0xF7: internal(gb); push16(gb,c->pc); c->pc=0x30; break; + case 0xFF: internal(gb); push16(gb,c->pc); c->pc=0x38; break; + + // push/pop + case 0xC1: c->bc = pop16(gb); break; + case 0xD1: c->de = pop16(gb); break; + case 0xE1: c->hl = pop16(gb); break; + case 0xF1: c->af = pop16(gb) & 0xFFF0; break; + case 0xC5: internal(gb); push16(gb, c->bc); break; + case 0xD5: internal(gb); push16(gb, c->de); break; + case 0xE5: internal(gb); push16(gb, c->hl); break; + case 0xF5: internal(gb); push16(gb, c->af); break; + + // high memory / misc loads + case 0xE0: { u8 n=fetch8(gb); wb(gb, 0xFF00+n, c->a); } break; + case 0xF0: { u8 n=fetch8(gb); c->a = rb(gb, 0xFF00+n); } break; + case 0xE2: wb(gb, 0xFF00+c->c, c->a); break; + case 0xF2: c->a = rb(gb, 0xFF00+c->c); break; + case 0xEA: { u16 a=fetch16(gb); wb(gb, a, c->a); } break; + case 0xFA: { u16 a=fetch16(gb); c->a = rb(gb, a); } break; + + // ADD SP,e + case 0xE8: { s8 e=(s8)fetch8(gb); c->sp = add_sp_e(gb, e); internal(gb); internal(gb); } break; + + // interrupts + case 0xF3: c->ime = false; c->ime_pending = false; break; + case 0xFB: c->ime_pending = true; break; + + // immediate ALU + case 0xC6: alu_add(c, fetch8(gb)); break; + case 0xCE: alu_adc(c, fetch8(gb)); break; + case 0xD6: alu_sub(c, fetch8(gb)); break; + case 0xDE: alu_sbc(c, fetch8(gb)); break; + case 0xE6: alu_and(c, fetch8(gb)); break; + case 0xEE: alu_xor(c, fetch8(gb)); break; + case 0xF6: alu_or(c, fetch8(gb)); break; + case 0xFE: alu_cp(c, fetch8(gb)); break; + + case 0xCB: do_cb(gb); break; + + default: + // 0x40-0xBF block: LD r,r' and ALU A,r + if (op >= 0x40 && op < 0x80) { + int dst = (op >> 3) & 7; + int src = op & 7; + u8 v = reg_get(gb, src); + reg_set(gb, dst, v); + } else if (op >= 0x80 && op < 0xC0) { + int kind = (op >> 3) & 7; + u8 v = reg_get(gb, op & 7); + switch (kind) { + case 0: alu_add(c, v); break; + case 1: alu_adc(c, v); break; + case 2: alu_sub(c, v); break; + case 3: alu_sbc(c, v); break; + case 4: alu_and(c, v); break; + case 5: alu_xor(c, v); break; + case 6: alu_or(c, v); break; + case 7: alu_cp(c, v); break; + } + } + // else: illegal opcode (D3,DB,DD,E3,E4,EB,EC,ED,F4,FC,FD) -> NOP-ish + break; + } + + // apply delayed EI (after this instruction completed, if it was EI) + if (c->ime_pending && op == 0xFB) { + // becomes active before next instruction; leave pending flag, + // but we consumed it: enable now so next handle_interrupts sees ime. + } + if (ei_was_pending && op != 0xFB) { + c->ime = true; + c->ime_pending = false; + } + + return (int)(gb->cycles - start); +} |
