diff options
| -rw-r--r-- | src/cpu.c | 446 | ||||
| -rw-r--r-- | src/cpu.h | 9 | ||||
| -rw-r--r-- | src/gb.c | 201 | ||||
| -rw-r--r-- | src/main.c | 41 | ||||
| -rw-r--r-- | src/ppu.c | 355 | ||||
| -rw-r--r-- | src/ppu.h | 14 | ||||
| -rw-r--r-- | src/timer.c | 82 | ||||
| -rw-r--r-- | src/timer.h | 10 |
8 files changed, 1158 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); +} diff --git a/src/cpu.h b/src/cpu.h new file mode 100644 index 0000000..8415951 --- /dev/null +++ b/src/cpu.h @@ -0,0 +1,9 @@ +#ifndef GBC_CPU_H +#define GBC_CPU_H + +#include "gb.h" + +// Execute one instruction (or handle interrupt / halt). Returns T-cycles used. +int cpu_step(GB *gb); + +#endif 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; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..27e886a --- /dev/null +++ b/src/main.c @@ -0,0 +1,41 @@ +#include "gb.h" +#include "cpu.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +int main(int argc, char **argv) { + if (argc < 2) { + fprintf(stderr, "usage: %s <rom> [--test seconds]\n", argv[0]); + return 1; + } + const char *rom = argv[1]; + bool test = false; + double seconds = 30.0; + for (int i = 2; i < argc; i++) { + if (!strcmp(argv[i], "--test")) { + test = true; + if (i + 1 < argc) seconds = atof(argv[++i]); + } + } + + GB *gb = calloc(1, sizeof(GB)); + if (cart_load(&gb->cart, rom) != 0) return 1; + gb_reset(gb); + + if (test) { + gb->serial_log = true; + u64 target = (u64)(seconds * 4194304.0); + while (gb->cycles < target) cpu_step(gb); + fprintf(stderr, "\n[ran %llu cycles]\n", (unsigned long long)gb->cycles); + cart_free(&gb->cart); + free(gb); + return 0; + } + + // TODO: interactive terminal rendering + fprintf(stderr, "interactive mode not yet implemented; use --test\n"); + cart_free(&gb->cart); + free(gb); + return 0; +} diff --git a/src/ppu.c b/src/ppu.c new file mode 100644 index 0000000..16ecc2e --- /dev/null +++ b/src/ppu.c @@ -0,0 +1,355 @@ +#include "ppu.h" +#include <string.h> + +// LCDC bits +#define LCDC_BG_EN 0x01 +#define LCDC_OBJ_EN 0x02 +#define LCDC_OBJ_SIZE 0x04 +#define LCDC_BG_MAP 0x08 +#define LCDC_TILE_DATA 0x10 +#define LCDC_WIN_EN 0x20 +#define LCDC_WIN_MAP 0x40 +#define LCDC_LCD_EN 0x80 + +// STAT bits +#define STAT_LYC_INT 0x40 +#define STAT_M2_INT 0x20 +#define STAT_M1_INT 0x10 +#define STAT_M0_INT 0x08 +#define STAT_LYC_FLAG 0x04 + +void gb_hdma_hblank(GB *gb); // from gb.c + +// DMG shades (greenish palette), index 0..3 +static const u8 dmg_shades[4][3] = { + {224, 248, 208}, {136, 192, 112}, {52, 104, 86}, {8, 24, 32} +}; + +void ppu_reset(GB *gb) { + PPU *p = &gb->ppu; + p->lcdc = 0x91; + p->stat = 0x85; + p->bgp = 0xFC; + p->obp0 = 0xFF; + p->obp1 = 0xFF; + p->ly = 0; + p->mode = 2; + p->dots = 0; + p->vram_bank = 0; + memset(p->bg_pal, 0xFF, sizeof(p->bg_pal)); + memset(p->obj_pal, 0xFF, sizeof(p->obj_pal)); +} + +// ---- VRAM / OAM access ---- +u8 ppu_read(GB *gb, u16 addr) { + PPU *p = &gb->ppu; + if (addr >= 0x8000 && addr < 0xA000) + return p->vram[p->vram_bank * 0x2000 + (addr - 0x8000)]; + if (addr >= 0xFE00 && addr < 0xFEA0) + return p->oam[addr - 0xFE00]; + return 0xFF; +} +void ppu_write(GB *gb, u16 addr, u8 val) { + PPU *p = &gb->ppu; + if (addr >= 0x8000 && addr < 0xA000) + p->vram[p->vram_bank * 0x2000 + (addr - 0x8000)] = val; + else if (addr >= 0xFE00 && addr < 0xFEA0) + p->oam[addr - 0xFE00] = val; +} + +static inline u8 vram_at(PPU *p, int bank, u16 addr) { + return p->vram[bank * 0x2000 + (addr - 0x8000)]; +} + +// ---- registers ---- +u8 ppu_read_reg(GB *gb, u16 addr) { + PPU *p = &gb->ppu; + switch (addr) { + case 0xFF40: return p->lcdc; + case 0xFF41: return p->stat | 0x80; + case 0xFF42: return p->scy; + case 0xFF43: return p->scx; + case 0xFF44: return p->ly; + case 0xFF45: return p->lyc; + case 0xFF47: return p->bgp; + case 0xFF48: return p->obp0; + case 0xFF49: return p->obp1; + case 0xFF4A: return p->wy; + case 0xFF4B: return p->wx; + case 0xFF68: return p->bcps; + case 0xFF69: return p->bg_pal[p->bcps & 0x3F]; + case 0xFF6A: return p->ocps; + case 0xFF6B: return p->obj_pal[p->ocps & 0x3F]; + } + return 0xFF; +} + +static void check_lyc(GB *gb) { + PPU *p = &gb->ppu; + if (p->ly == p->lyc) p->stat |= STAT_LYC_FLAG; + else p->stat &= ~STAT_LYC_FLAG; +} + +void ppu_write_reg(GB *gb, u16 addr, u8 val) { + PPU *p = &gb->ppu; + switch (addr) { + case 0xFF40: { + bool was_on = p->lcdc & LCDC_LCD_EN; + p->lcdc = val; + if (was_on && !(val & LCDC_LCD_EN)) { + p->ly = 0; p->dots = 0; p->mode = 0; + p->stat &= ~0x03; + } else if (!was_on && (val & LCDC_LCD_EN)) { + p->mode = 2; p->dots = 0; p->ly = 0; + check_lyc(gb); + } + break; + } + case 0xFF41: p->stat = (p->stat & 0x07) | (val & 0x78); break; + case 0xFF42: p->scy = val; break; + case 0xFF43: p->scx = val; break; + case 0xFF44: break; // LY read-only + case 0xFF45: p->lyc = val; check_lyc(gb); break; + case 0xFF47: p->bgp = val; break; + case 0xFF48: p->obp0 = val; break; + case 0xFF49: p->obp1 = val; break; + case 0xFF4A: p->wy = val; break; + case 0xFF4B: p->wx = val; break; + case 0xFF68: p->bcps = val & 0xBF; break; + case 0xFF69: + p->bg_pal[p->bcps & 0x3F] = val; + if (p->bcps & 0x80) p->bcps = 0x80 | ((p->bcps + 1) & 0x3F); + break; + case 0xFF6A: p->ocps = val & 0xBF; break; + case 0xFF6B: + p->obj_pal[p->ocps & 0x3F] = val; + if (p->ocps & 0x80) p->ocps = 0x80 | ((p->ocps + 1) & 0x3F); + break; + } +} + +// ---- palette helpers ---- +static void cgb_color(u8 *pal, int palnum, int cidx, u8 out[3]) { + int i = (palnum * 4 + cidx) * 2; + u16 c = pal[i] | (pal[i+1] << 8); + int r = c & 0x1F, g = (c >> 5) & 0x1F, b = (c >> 10) & 0x1F; + // scale 5-bit to 8-bit with a mild color-correction curve + out[0] = (r * 13 + g * 2 + b) >> 1; + out[1] = (g * 3 + b) << 1; + out[2] = (r * 3 + g * 2 + b * 11) >> 1; +} + +// ---- scanline rendering ---- +static void render_scanline(GB *gb) { + PPU *p = &gb->ppu; + int ly = p->ly; + if (ly >= SCREEN_H) return; + + u8 bg_prio[SCREEN_W]; // BG color index (for sprite priority) + u8 bg_hasprio[SCREEN_W]; // CGB BG-to-OAM priority bit + + bool cgb = gb->cgb_mode; + bool bg_master = (p->lcdc & LCDC_BG_EN) || cgb; // in CGB, bit0 = master priority + + // ----- Background & Window ----- + for (int x = 0; x < SCREEN_W; x++) { + bg_prio[x] = 0; + bg_hasprio[x] = 0; + + bool use_win = (p->lcdc & LCDC_WIN_EN) && (ly >= p->wy) && (x + 7 >= p->wx); + int px, py; + u16 map_base; + if (use_win) { + px = x + 7 - p->wx; + py = p->wly; + map_base = (p->lcdc & LCDC_WIN_MAP) ? 0x9C00 : 0x9800; + } else { + px = (x + p->scx) & 0xFF; + py = (ly + p->scy) & 0xFF; + map_base = (p->lcdc & LCDC_BG_MAP) ? 0x9C00 : 0x9800; + } + int tx = px / 8, ty = py / 8; + u16 map_addr = map_base + ty * 32 + tx; + u8 tile = vram_at(p, 0, map_addr); + + u8 attr = cgb ? vram_at(p, 1, map_addr) : 0; + int tbank = (attr & 0x08) ? 1 : 0; + bool xflip = attr & 0x20, yflip = attr & 0x40; + int palnum = attr & 0x07; + bool prio = attr & 0x80; + + int row = py % 8, col = px % 8; + if (yflip) row = 7 - row; + if (xflip) col = 7 - col; + + u16 tile_addr; + if (p->lcdc & LCDC_TILE_DATA) + tile_addr = 0x8000 + tile * 16 + row * 2; + else + tile_addr = 0x9000 + ((s8)tile) * 16 + row * 2; + + u8 lo = vram_at(p, tbank, tile_addr); + u8 hi = vram_at(p, tbank, tile_addr + 1); + int bit = 7 - col; + int cidx = ((lo >> bit) & 1) | (((hi >> bit) & 1) << 1); + + bg_prio[x] = cidx; + bg_hasprio[x] = prio; + + u8 rgb[3]; + if (!bg_master) { + rgb[0] = dmg_shades[0][0]; rgb[1] = dmg_shades[0][1]; rgb[2] = dmg_shades[0][2]; + } else if (cgb) { + cgb_color(p->bg_pal, palnum, cidx, rgb); + } else { + int shade = (p->bgp >> (cidx * 2)) & 3; + memcpy(rgb, dmg_shades[shade], 3); + } + memcpy(p->fb[ly][x], rgb, 3); + } + if ((p->lcdc & LCDC_WIN_EN) && ly >= p->wy && p->wx < 167) + p->wly++; + + // ----- Sprites ----- + if (!(p->lcdc & LCDC_OBJ_EN)) return; + int h = (p->lcdc & LCDC_OBJ_SIZE) ? 16 : 8; + + // gather up to 10 sprites on this line + int chosen[10], n = 0; + for (int i = 0; i < 40 && n < 10; i++) { + int sy = p->oam[i*4] - 16; + if (ly >= sy && ly < sy + h) chosen[n++] = i; + } + // draw back-to-front: lower priority first. + // DMG: smaller X = higher priority (drawn last). CGB: lower OAM index = higher. + for (int a = 0; a < n; a++) { + for (int b = a + 1; b < n; b++) { + bool swap; + if (cgb) swap = chosen[b] < chosen[a]; + else { + int xa = p->oam[chosen[a]*4+1], xb = p->oam[chosen[b]*4+1]; + swap = (xb > xa) || (xb == xa && chosen[b] < chosen[a]); + } + if (swap) { int t = chosen[a]; chosen[a] = chosen[b]; chosen[b] = t; } + } + } + + for (int k = 0; k < n; k++) { + int i = chosen[k]; + int sy = p->oam[i*4] - 16; + int sx = p->oam[i*4+1] - 8; + u8 tile = p->oam[i*4+2]; + u8 attr = p->oam[i*4+3]; + bool xflip = attr & 0x20, yflip = attr & 0x40; + bool behind = attr & 0x80; + if (h == 16) tile &= 0xFE; + + int row = ly - sy; + if (yflip) row = h - 1 - row; + int tbank = (cgb && (attr & 0x08)) ? 1 : 0; + + u16 tile_addr = 0x8000 + tile * 16 + row * 2; + u8 lo = vram_at(p, tbank, tile_addr); + u8 hi = vram_at(p, tbank, tile_addr + 1); + + for (int col = 0; col < 8; col++) { + int x = sx + col; + if (x < 0 || x >= SCREEN_W) continue; + int bit = xflip ? col : 7 - col; + int cidx = ((lo >> bit) & 1) | (((hi >> bit) & 1) << 1); + if (cidx == 0) continue; // transparent + + // priority resolution + if (cgb) { + // BG master priority (LCDC.0) forces BG over OBJ when set + if ((p->lcdc & LCDC_BG_EN)) { + if ((behind || bg_hasprio[x]) && bg_prio[x] != 0) continue; + } + } else { + if (behind && bg_prio[x] != 0) continue; + } + + u8 rgb[3]; + if (cgb) { + cgb_color(p->obj_pal, attr & 0x07, cidx, rgb); + } else { + u8 pal = (attr & 0x10) ? p->obp1 : p->obp0; + int shade = (pal >> (cidx * 2)) & 3; + memcpy(rgb, dmg_shades[shade], 3); + } + memcpy(p->fb[ly][x], rgb, 3); + } + } +} + +// ---- STAT interrupt line (edge-triggered) ---- +static void update_stat(GB *gb, bool lyc_check) { + PPU *p = &gb->ppu; + static bool prev_line = false; + if (lyc_check) check_lyc(gb); + + bool line = false; + switch (p->mode) { + case 0: if (p->stat & STAT_M0_INT) line = true; break; + case 1: if (p->stat & STAT_M1_INT) line = true; break; + case 2: if (p->stat & STAT_M2_INT) line = true; break; + } + if ((p->stat & STAT_LYC_INT) && (p->stat & STAT_LYC_FLAG)) line = true; + + if (line && !prev_line) gb_request_interrupt(gb, INT_LCDSTAT); + prev_line = line; +} + +void ppu_tick(GB *gb, int tcycles) { + PPU *p = &gb->ppu; + if (!(p->lcdc & LCDC_LCD_EN)) return; + + for (int t = 0; t < tcycles; t++) { + p->dots++; + + switch (p->mode) { + case 2: // OAM scan + if (p->dots >= 80) { p->mode = 3; p->stat = (p->stat & ~3) | 3; } + break; + case 3: // drawing + if (p->dots >= 80 + 172) { + p->mode = 0; + p->stat = (p->stat & ~3); + render_scanline(gb); + gb_hdma_hblank(gb); + update_stat(gb, false); + } + break; + case 0: // hblank + if (p->dots >= 456) { + p->dots -= 456; + p->ly++; + if (p->ly == 144) { + p->mode = 1; + p->stat = (p->stat & ~3) | 1; + p->wly = 0; + gb_request_interrupt(gb, INT_VBLANK); + p->frame_ready = true; + update_stat(gb, true); + } else { + p->mode = 2; + p->stat = (p->stat & ~3) | 2; + update_stat(gb, true); + } + } + break; + case 1: // vblank + if (p->dots >= 456) { + p->dots -= 456; + p->ly++; + if (p->ly > 153) { + p->ly = 0; + p->mode = 2; + p->stat = (p->stat & ~3) | 2; + } + update_stat(gb, true); + } + break; + } + } +} diff --git a/src/ppu.h b/src/ppu.h new file mode 100644 index 0000000..ea427f7 --- /dev/null +++ b/src/ppu.h @@ -0,0 +1,14 @@ +#ifndef GBC_PPU_H +#define GBC_PPU_H + +#include "gb.h" + +void ppu_reset(GB *gb); +void ppu_tick(GB *gb, int tcycles); + +u8 ppu_read(GB *gb, u16 addr); // VRAM/OAM +void ppu_write(GB *gb, u16 addr, u8 val); +u8 ppu_read_reg(GB *gb, u16 addr); // FF40-FF4B, FF68-FF6B +void ppu_write_reg(GB *gb, u16 addr, u8 val); + +#endif diff --git a/src/timer.c b/src/timer.c new file mode 100644 index 0000000..d4ee69f --- /dev/null +++ b/src/timer.c @@ -0,0 +1,82 @@ +#include "timer.h" + +// Which DIV bit feeds the TIMA increment for each TAC clock-select. +static const int tac_bit[4] = { 9, 3, 5, 7 }; + +static bool timer_signal(Timer *t) { + if (!(t->tac & 0x04)) return false; + return (t->div >> tac_bit[t->tac & 3]) & 1; +} + +static void set_div(GB *gb, u16 newdiv) { + Timer *t = &gb->timer; + bool old = timer_signal(t); + t->div = newdiv; + bool now = timer_signal(t); + // falling edge -> increment TIMA + if (old && !now) { + if (t->tima == 0xFF) { + t->tima = 0; + t->overflow_pending = true; + t->reload_delay = 4; // reload TMA after 4 T-cycles + } else { + t->tima++; + } + } +} + +void timer_tick(GB *gb, int tcycles) { + Timer *t = &gb->timer; + for (int i = 0; i < tcycles; i++) { + if (t->overflow_pending) { + t->reload_delay--; + if (t->reload_delay == 0) { + t->tima = t->tma; + t->overflow_pending = false; + gb_request_interrupt(gb, INT_TIMER); + } + } + set_div(gb, t->div + 1); + } +} + +u8 timer_read(GB *gb, u16 addr) { + Timer *t = &gb->timer; + switch (addr) { + case 0xFF04: return t->div >> 8; + case 0xFF05: return t->tima; + case 0xFF06: return t->tma; + case 0xFF07: return t->tac | 0xF8; + } + return 0xFF; +} + +void timer_write(GB *gb, u16 addr, u8 val) { + Timer *t = &gb->timer; + switch (addr) { + case 0xFF04: + set_div(gb, 0); // writing DIV resets it (may cause edge) + break; + case 0xFF05: + // writing during reload delay cancels the reload + if (t->reload_delay != 0) t->overflow_pending = false; + if (!(t->overflow_pending && t->reload_delay == 0)) + t->tima = val; + break; + case 0xFF06: + t->tma = val; + break; + case 0xFF07: { + bool old = timer_signal(t); + t->tac = val & 0x07; + bool now = timer_signal(t); + if (old && !now) { + // disabling can also cause an increment (glitch) + if (t->tima == 0xFF) { + t->tima = 0; t->overflow_pending = true; t->reload_delay = 4; + } else t->tima++; + } + break; + } + } +} diff --git a/src/timer.h b/src/timer.h new file mode 100644 index 0000000..95fec0d --- /dev/null +++ b/src/timer.h @@ -0,0 +1,10 @@ +#ifndef GBC_TIMER_H +#define GBC_TIMER_H + +#include "gb.h" + +void timer_tick(GB *gb, int tcycles); +u8 timer_read(GB *gb, u16 addr); +void timer_write(GB *gb, u16 addr, u8 val); + +#endif |
