diff --git a/src/apps/gbdb.c b/src/apps/gbdb.c index b312560..f21348b 100644 --- a/src/apps/gbdb.c +++ b/src/apps/gbdb.c @@ -47,6 +47,7 @@ static const char usage[] = static struct lr35902_ops cpu_ops; static struct lr35902_state cpu; static struct gb_video video; +static struct gb_interrupt interrupt; static void *memory = NULL; static int paused_breakpoint = 0; @@ -111,9 +112,10 @@ static void init(const char *bootrom, const char *rom) { cpu_ops.undef_d3 = breakpoint_cb; - lr35902_init(&cpu, memory, &cpu_ops); - gb_video_init(&video, memory); - gb_mem_init(memory, &video, bootrom, rom); + gb_interrupt_init(&interrupt, &cpu); + lr35902_init(&cpu, &interrupt, memory, &cpu_ops); + gb_video_init(&video, memory, &interrupt); + gb_mem_init(memory, &video, &interrupt, bootrom, rom); config.log_fd = -1; config.trace_fd = -1; diff --git a/src/common/common.h b/src/common/common.h index 353cd53..c89b795 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -10,6 +10,16 @@ #define ARRAY_SIZE(x) (sizeof((x)) / (sizeof((x)[0]))) +#define SET_BIT(x, idx) do {(x) |= (1 << (idx));} while (0) +#define CLR_BIT(x, idx) do {(x) &= ~(1 << (idx));} while (0) +#define GET_BIT(x, bit) (((x) >> (bit)) & 1) +#define WRITE_BIT(x, idx, bit) \ + do { \ + (x) &= ~(1 << (idx)); \ + (x) |= (bit) << (idx); \ + } while(0) + + /* * Replaces any characters from A-Z with their lowercase counterpart. * All other characters preceding '\0' are ignored diff --git a/src/gbemu/cpu.c b/src/gbemu/cpu.c index c1ff4b3..ccb7d0e 100644 --- a/src/gbemu/cpu.c +++ b/src/gbemu/cpu.c @@ -10,15 +10,6 @@ #include "gbemu/cpu.h" #include "common/common.h" -#define SET_BIT(x, idx) do {(x) |= (1 << (idx));} while (0) -#define CLR_BIT(x, idx) do {(x) &= ~(1 << (idx));} while (0) -#define GET_BIT(x, bit) (((x) >> (bit)) & 1) -#define WRITE_BIT(x, idx, bit) \ - do { \ - (x) &= ~(1 << (idx)); \ - (x) |= (bit) << (idx); \ - } while(0) - #define CALC_H_ADD(a, b) ((((a) & 0xf) + ((b) & 0xf)) > 0xf) #define CALC_H_SUB(a, b) (((a) & 0xf) < ((b) & 0xf)) @@ -30,6 +21,8 @@ #define CALC_C_ADD_8(a, b) (0xff - (a) < (b)) #define CALC_C_SUB(a, b) ((a) < (b)) +static void lr35902_handle_pending(struct lr35902_state *cpu); + static int cpu_reg16_to_idx[NUM_LR35902_REGS_16] = { [LR35902_REG_BC] = 0, [LR35902_REG_DE] = 1, @@ -79,11 +72,13 @@ void lr35902_set_reg_8(struct lr35902_state *cpu, lr35902_regs_8 reg, } void lr35902_init(struct lr35902_state *cpu, + struct gb_interrupt *interrupt, struct gb_memory *memory, const struct lr35902_ops *ops) { int i; + cpu->interrupt = interrupt; cpu->memory = memory; cpu->undef_d3 = ops->undef_d3; @@ -1638,7 +1633,7 @@ int lr35902_cycle(struct lr35902_state *cpu) break; case 0xd9: /* RETI */ RET(cpu); - cpu->int_state = LR35902_INT_ON; + gb_interrupt_ime_set(cpu->interrupt); break; case 0xda: /* JP C, a16 */ val_16 = gb_mem_read(cpu->memory, cpu->pc++); @@ -1738,7 +1733,8 @@ int lr35902_cycle(struct lr35902_state *cpu) cpu->a = gb_mem_read(cpu->memory, 0xFF00 + cpu->c); break; case 0xf3: /* DI */ - /*TODO: implement me */ + gb_interrupt_ime_clear(cpu->interrupt); + break; case 0xf4: /* UNDEF */ break; case 0xf5: /* PUSH AF */ @@ -1776,7 +1772,8 @@ int lr35902_cycle(struct lr35902_state *cpu) cpu->a = gb_mem_read(cpu->memory, val_16); break; case 0xfb: /* EI */ - /* TODO: implement me */ + gb_log("EI\n"); + gb_interrupt_ime_set(cpu->interrupt); break; case 0xfc: /* UNDEF */ ASSERT(0); @@ -1794,3 +1791,33 @@ int lr35902_cycle(struct lr35902_state *cpu) cpu->metrics.cycles += cycles + 1; return cycles + 1; } + +void lr35902_interrupt(struct lr35902_state *cpu, enum gb_interrupt_kind kind) +{ + uint16_t addr; + + gb_log("Interrupt! (%d)\n", kind); + + switch (kind) { + case GB_INT_VBLANK: + addr = 0x40; + break; + case GB_INT_LCD_STAT: + addr = 0x48; + break; + case GB_INT_TIMER: + addr = 0x50; + break; + case GB_INT_SERIAL: + addr = 0x58; + break; + case GB_INT_JOYPAD: + addr = 0x60; + break; + default: + ASSERT(0); + } + + PUSH_16(cpu, cpu->pc); + cpu->pc = addr; +} diff --git a/src/gbemu/cpu.h b/src/gbemu/cpu.h index 0b382f3..913b552 100644 --- a/src/gbemu/cpu.h +++ b/src/gbemu/cpu.h @@ -9,6 +9,8 @@ #define GB_CPU_H #include "gbemu/memory.h" +#include "gbemu/cpu.h" +#include "gbemu/interrupt.h" #include @@ -70,6 +72,7 @@ struct lr35902_event { struct lr35902_state { struct gb_memory *memory; + struct gb_interrupt *interrupt; union { /* @@ -122,6 +125,7 @@ struct lr35902_ops { }; void lr35902_init(struct lr35902_state *cpu, + struct gb_interrupt *interrupt, struct gb_memory *memory, const struct lr35902_ops *ops); @@ -141,4 +145,6 @@ void lr35902_set_reg_8(struct lr35902_state *cpu, int lr35902_cycle(struct lr35902_state *cpu); +void lr35902_interrupt(struct lr35902_state *cpu, enum gb_interrupt_kind kind); + #endif diff --git a/src/gbemu/interrupt.c b/src/gbemu/interrupt.c new file mode 100644 index 0000000..14b3236 --- /dev/null +++ b/src/gbemu/interrupt.c @@ -0,0 +1,90 @@ +#include "gbemu/interrupt.h" +#include "gbemu/cpu.h" + +#include "common/common.h" + +void gb_interrupt_init(struct gb_interrupt *interrupt, struct lr35902_state *cpu) +{ + interrupt->flags = 0; + interrupt->enable = 0; + + interrupt->cpu = cpu; +} + +static void fire_if_pending(struct gb_interrupt *interrupt) +{ + uint8_t active_interrupts = interrupt->flags & interrupt->enable; + enum gb_interrupt_kind kind; + + if (!interrupt->master_enable) { + return; + } + + if (!active_interrupts) { + return; + } + + kind = __builtin_ctz(active_interrupts); + ASSERT(kind >= GB_INT_MIN); + ASSERT(kind <= GB_INT_MAX); + + interrupt->master_enable = false; + CLR_BIT(interrupt->flags, kind); + lr35902_interrupt(interrupt->cpu, kind); +} + +int gb_interrupt_set(struct gb_interrupt *interrupt, enum gb_interrupt_kind kind) +{ + uint8_t old = interrupt->flags; + uint8_t new = interrupt->flags; + + ASSERT(kind >= GB_INT_MIN); + ASSERT(kind <= GB_INT_MAX); + + SET_BIT(new, kind); + + fire_if_pending(interrupt); +} + +int gb_interrupt_clear(struct gb_interrupt *interrupt, enum gb_interrupt_kind kind) +{ + ASSERT(kind >= GB_INT_MIN); + ASSERT(kind <= GB_INT_MAX); + CLR_BIT(interrupt->flags, kind); +} + +uint8_t gb_interrupt_if_read(struct gb_interrupt *interrupt) +{ + return interrupt->flags; +} + +void gb_interrupt_if_write(struct gb_interrupt *interrupt, uint8_t val) +{ + interrupt->flags = val; + fire_if_pending(interrupt); +} + +uint8_t gb_interrupt_ie_read(struct gb_interrupt *interrupt) +{ + return interrupt->enable; +} + +void gb_interrupt_ie_write(struct gb_interrupt *interrupt, uint8_t val) +{ + uint8_t active_interrupts = interrupt->flags & interrupt->enable; + enum gb_interrupt_kind kind; + + interrupt->enable = val; +} + +void gb_interrupt_ime_set(struct gb_interrupt *interrupt) +{ + interrupt->master_enable = true; + + fire_if_pending(interrupt); +} + +void gb_interrupt_ime_clear(struct gb_interrupt *interrupt) +{ + interrupt->master_enable = false; +} diff --git a/src/gbemu/interrupt.h b/src/gbemu/interrupt.h new file mode 100644 index 0000000..dce19cb --- /dev/null +++ b/src/gbemu/interrupt.h @@ -0,0 +1,61 @@ +#ifndef INTERRUPT_H +#define INTERRUPT_H + +#include +#include + +struct lr35902_state; + +enum gb_interrupt_kind { + GB_INT_MIN = 0, + GB_INT_VBLANK = 0, + GB_INT_LCD_STAT = 1, + GB_INT_TIMER = 2, + GB_INT_SERIAL = 3, + GB_INT_JOYPAD = 4, + GB_INT_MAX, +}; + +struct gb_interrupt { + uint8_t flags; /* IF */ + uint8_t enable; /* IE */ + bool master_enable; /* IME */ + + struct lr35902_state *cpu; +}; + +void gb_interrupt_init(struct gb_interrupt *interrupt, struct lr35902_state *cpu); + +/* + * The "hardware" interface to setting/clearing interrupts + */ +int gb_interrupt_set(struct gb_interrupt *interrupt, enum gb_interrupt_kind kind); + +int gb_interrupt_clear(struct gb_interrupt *interrupt, enum gb_interrupt_kind kind); + +/* + * IF - Interrupt Flags + */ + +uint8_t gb_interrupt_if_read(struct gb_interrupt *interrupt); + +void gb_interrupt_if_write(struct gb_interrupt *interrupt, uint8_t val); + +/* + * IE - Interrupt Enable + */ + +uint8_t gb_interrupt_ie_read(struct gb_interrupt *interrupt); + +void gb_interrupt_ie_write(struct gb_interrupt *interrupt, uint8_t val); + + +/* + * IME - Interrupt Master Enable + */ + +void gb_interrupt_ime_set(struct gb_interrupt *interrupt); + +void gb_interrupt_ime_clear(struct gb_interrupt *interrupt); + +#endif diff --git a/src/gbemu/memory.c b/src/gbemu/memory.c index 4843320..cb3066f 100644 --- a/src/gbemu/memory.c +++ b/src/gbemu/memory.c @@ -4,6 +4,7 @@ #include "gbemu/memory.h" #include "gbemu/video.h" +#include "gbemu/cpu.h" #include "common/common.h" @@ -19,6 +20,7 @@ #define UNMAP_BOOTROM_ADDR 0xff50 static struct gb_video *video; +static struct gb_interrupt *interrupt; static uint8_t ram[MAX_RAM_LEN]; static unsigned char bootrom[0x100] = {0}; @@ -47,9 +49,10 @@ static void load_file_to_buffer(const char *filename, uint8_t *buffer, size_t si fclose(prog_file); } -void gb_mem_init(struct gb_memory *memory, struct gb_video *v, +void gb_mem_init(struct gb_memory *memory, struct gb_video *v, struct gb_interrupt *i, const char *bootrom_file, const char *rom_file) { + interrupt = i; video = v; memset(&ram, 0, MAX_RAM_LEN); bootrom_mapped = 1; @@ -107,6 +110,10 @@ uint8_t gb_mem_read(struct gb_memory *memory, uint16_t addr) return gb_serial_read(memory, addr); case 0xFF40 ... 0xFF4B: return gb_video_mem_read(video, addr); + case 0xFF0F: + return gb_interrupt_if_read(interrupt); + case 0xFFFF: + return gb_interrupt_ie_read(interrupt); default: return ram[addr]; } @@ -136,8 +143,12 @@ void gb_mbc3_write(struct gb_memory *memory, uint16_t addr, uint8_t val) case 0xFF40 ... 0xFF4B: gb_video_mem_write(video, addr, val); break; + case 0xFF0F: + gb_interrupt_if_write(interrupt, val); + break; case 0xFFFF: - gb_log("Writing to interrupt mask: 0x%02x\n", val); + gb_interrupt_ie_write(interrupt, val); + break; default: ram[addr] = val; } @@ -164,6 +175,12 @@ void gb_mem_write(struct gb_memory *memory, uint16_t addr, uint8_t val) case 0xFF40 ... 0xFF4B: gb_video_mem_write(video, addr, val); return; + case 0xFF0F: + gb_interrupt_if_write(interrupt, val); + return; + case 0xFFFF: + gb_interrupt_ie_write(interrupt, val); + return; case 0x8000 ... 0x87FF: case 0x9800 ... 0x9BFF: break; @@ -172,9 +189,6 @@ void gb_mem_write(struct gb_memory *memory, uint16_t addr, uint8_t val) return; } break; - case 0xFFFF: - gb_log("Writing to interrupt mask: 0x%02x\n", val); - break; } ram[addr] = val; diff --git a/src/gbemu/memory.h b/src/gbemu/memory.h index 5a8f43f..8e019d8 100644 --- a/src/gbemu/memory.h +++ b/src/gbemu/memory.h @@ -5,8 +5,10 @@ struct gb_memory; struct gb_video; +struct gb_interrupt; void gb_mem_init(struct gb_memory *memory, struct gb_video *v, + struct gb_interrupt *interrupt, const char *bootrom, const char *rom); uint8_t gb_mem_read(struct gb_memory *mem, uint16_t addr); diff --git a/src/gbemu/video.c b/src/gbemu/video.c index 3b42767..a34f074 100644 --- a/src/gbemu/video.c +++ b/src/gbemu/video.c @@ -1,5 +1,6 @@ #include "gbemu/video.h" #include "gbemu/memory.h" +#include "gbemu/interrupt.h" #include "common/common.h" #include "common/bmp.h" @@ -9,7 +10,6 @@ /* TODO: This whole implementation is very simple.*/ /* TODO: Actual graphics output */ /* TODO: Implementation of most registers */ -/* TODO: Interrupts */ static struct bmp *bmp; @@ -28,12 +28,13 @@ static uint8_t color_tbl[4] = { 0, }; -void gb_video_init(struct gb_video *video, struct gb_memory *memory) +void gb_video_init(struct gb_video *video, struct gb_memory *memory, struct gb_interrupt *interrupt) { memset(video, 0, sizeof(*video)); video->debug_logging = 0; video->memory = memory; + video->interrupt = interrupt; bmp = bmp_new(BMP_GRAYSCALE, BUFFER_WIDTH, BUFFER_HEIGHT); } @@ -114,12 +115,15 @@ void gb_video_cycle(struct gb_video *video, int cycles) video->curline += 1; if (video->curline > LCD_Y_MAX) { video->curline = 0; + gb_interrupt_clear(video->interrupt, GB_INT_VBLANK); if (video->lcdcont & (1 << 7)) { screenshot_count++; if (screenshot_count % 30 == 0) { gb_video_screenshot(video); } } + } else if (video->curline == 144) { + gb_interrupt_set(video->interrupt, GB_INT_VBLANK); } } } diff --git a/src/gbemu/video.h b/src/gbemu/video.h index 413b49b..a8d2fbb 100644 --- a/src/gbemu/video.h +++ b/src/gbemu/video.h @@ -95,9 +95,10 @@ struct gb_video { int debug_logging; struct gb_memory *memory; + struct gb_interrupt *interrupt; }; -void gb_video_init(struct gb_video *video, struct gb_memory *memory); +void gb_video_init(struct gb_video *video, struct gb_memory *memory, struct gb_interrupt *interrupt); void gb_video_cycle(struct gb_video *video, int cycles); uint8_t gb_video_mem_read(struct gb_video *video, uint16_t addr); void gb_video_mem_write(struct gb_video *video, uint16_t addr, uint8_t val);