interrupt: first-pass implementation of interrupts

Only manual and V-blank interrupts work, for now. This implements
enough to make the EI and DI parts of Blargg's Interrupt test pass.
This commit is contained in:
2018-09-20 20:55:51 -07:00
parent 593d9d3600
commit 1076d02638
10 changed files with 240 additions and 23 deletions

View File

@@ -47,6 +47,7 @@ static const char usage[] =
static struct lr35902_ops cpu_ops; static struct lr35902_ops cpu_ops;
static struct lr35902_state cpu; static struct lr35902_state cpu;
static struct gb_video video; static struct gb_video video;
static struct gb_interrupt interrupt;
static void *memory = NULL; static void *memory = NULL;
static int paused_breakpoint = 0; static int paused_breakpoint = 0;
@@ -111,9 +112,10 @@ static void init(const char *bootrom, const char *rom)
{ {
cpu_ops.undef_d3 = breakpoint_cb; cpu_ops.undef_d3 = breakpoint_cb;
lr35902_init(&cpu, memory, &cpu_ops); gb_interrupt_init(&interrupt, &cpu);
gb_video_init(&video, memory); lr35902_init(&cpu, &interrupt, memory, &cpu_ops);
gb_mem_init(memory, &video, bootrom, rom); gb_video_init(&video, memory, &interrupt);
gb_mem_init(memory, &video, &interrupt, bootrom, rom);
config.log_fd = -1; config.log_fd = -1;
config.trace_fd = -1; config.trace_fd = -1;

View File

@@ -10,6 +10,16 @@
#define ARRAY_SIZE(x) (sizeof((x)) / (sizeof((x)[0]))) #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. * Replaces any characters from A-Z with their lowercase counterpart.
* All other characters preceding '\0' are ignored * All other characters preceding '\0' are ignored

View File

@@ -10,15 +10,6 @@
#include "gbemu/cpu.h" #include "gbemu/cpu.h"
#include "common/common.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_ADD(a, b) ((((a) & 0xf) + ((b) & 0xf)) > 0xf)
#define CALC_H_SUB(a, b) (((a) & 0xf) < ((b) & 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_ADD_8(a, b) (0xff - (a) < (b))
#define CALC_C_SUB(a, b) ((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] = { static int cpu_reg16_to_idx[NUM_LR35902_REGS_16] = {
[LR35902_REG_BC] = 0, [LR35902_REG_BC] = 0,
[LR35902_REG_DE] = 1, [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, void lr35902_init(struct lr35902_state *cpu,
struct gb_interrupt *interrupt,
struct gb_memory *memory, struct gb_memory *memory,
const struct lr35902_ops *ops) const struct lr35902_ops *ops)
{ {
int i; int i;
cpu->interrupt = interrupt;
cpu->memory = memory; cpu->memory = memory;
cpu->undef_d3 = ops->undef_d3; cpu->undef_d3 = ops->undef_d3;
@@ -1638,7 +1633,7 @@ int lr35902_cycle(struct lr35902_state *cpu)
break; break;
case 0xd9: /* RETI */ case 0xd9: /* RETI */
RET(cpu); RET(cpu);
cpu->int_state = LR35902_INT_ON; gb_interrupt_ime_set(cpu->interrupt);
break; break;
case 0xda: /* JP C, a16 */ case 0xda: /* JP C, a16 */
val_16 = gb_mem_read(cpu->memory, cpu->pc++); 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); cpu->a = gb_mem_read(cpu->memory, 0xFF00 + cpu->c);
break; break;
case 0xf3: /* DI */ case 0xf3: /* DI */
/*TODO: implement me */ gb_interrupt_ime_clear(cpu->interrupt);
break;
case 0xf4: /* UNDEF */ case 0xf4: /* UNDEF */
break; break;
case 0xf5: /* PUSH AF */ case 0xf5: /* PUSH AF */
@@ -1776,7 +1772,8 @@ int lr35902_cycle(struct lr35902_state *cpu)
cpu->a = gb_mem_read(cpu->memory, val_16); cpu->a = gb_mem_read(cpu->memory, val_16);
break; break;
case 0xfb: /* EI */ case 0xfb: /* EI */
/* TODO: implement me */ gb_log("EI\n");
gb_interrupt_ime_set(cpu->interrupt);
break; break;
case 0xfc: /* UNDEF */ case 0xfc: /* UNDEF */
ASSERT(0); ASSERT(0);
@@ -1794,3 +1791,33 @@ int lr35902_cycle(struct lr35902_state *cpu)
cpu->metrics.cycles += cycles + 1; cpu->metrics.cycles += cycles + 1;
return 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;
}

View File

@@ -9,6 +9,8 @@
#define GB_CPU_H #define GB_CPU_H
#include "gbemu/memory.h" #include "gbemu/memory.h"
#include "gbemu/cpu.h"
#include "gbemu/interrupt.h"
#include <stdint.h> #include <stdint.h>
@@ -70,6 +72,7 @@ struct lr35902_event {
struct lr35902_state { struct lr35902_state {
struct gb_memory *memory; struct gb_memory *memory;
struct gb_interrupt *interrupt;
union { union {
/* /*
@@ -122,6 +125,7 @@ struct lr35902_ops {
}; };
void lr35902_init(struct lr35902_state *cpu, void lr35902_init(struct lr35902_state *cpu,
struct gb_interrupt *interrupt,
struct gb_memory *memory, struct gb_memory *memory,
const struct lr35902_ops *ops); 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); int lr35902_cycle(struct lr35902_state *cpu);
void lr35902_interrupt(struct lr35902_state *cpu, enum gb_interrupt_kind kind);
#endif #endif

90
src/gbemu/interrupt.c Normal file
View File

@@ -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;
}

61
src/gbemu/interrupt.h Normal file
View File

@@ -0,0 +1,61 @@
#ifndef INTERRUPT_H
#define INTERRUPT_H
#include <stdint.h>
#include <stdbool.h>
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

View File

@@ -4,6 +4,7 @@
#include "gbemu/memory.h" #include "gbemu/memory.h"
#include "gbemu/video.h" #include "gbemu/video.h"
#include "gbemu/cpu.h"
#include "common/common.h" #include "common/common.h"
@@ -19,6 +20,7 @@
#define UNMAP_BOOTROM_ADDR 0xff50 #define UNMAP_BOOTROM_ADDR 0xff50
static struct gb_video *video; static struct gb_video *video;
static struct gb_interrupt *interrupt;
static uint8_t ram[MAX_RAM_LEN]; static uint8_t ram[MAX_RAM_LEN];
static unsigned char bootrom[0x100] = {0}; 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); 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) const char *bootrom_file, const char *rom_file)
{ {
interrupt = i;
video = v; video = v;
memset(&ram, 0, MAX_RAM_LEN); memset(&ram, 0, MAX_RAM_LEN);
bootrom_mapped = 1; 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); return gb_serial_read(memory, addr);
case 0xFF40 ... 0xFF4B: case 0xFF40 ... 0xFF4B:
return gb_video_mem_read(video, addr); return gb_video_mem_read(video, addr);
case 0xFF0F:
return gb_interrupt_if_read(interrupt);
case 0xFFFF:
return gb_interrupt_ie_read(interrupt);
default: default:
return ram[addr]; return ram[addr];
} }
@@ -136,8 +143,12 @@ void gb_mbc3_write(struct gb_memory *memory, uint16_t addr, uint8_t val)
case 0xFF40 ... 0xFF4B: case 0xFF40 ... 0xFF4B:
gb_video_mem_write(video, addr, val); gb_video_mem_write(video, addr, val);
break; break;
case 0xFF0F:
gb_interrupt_if_write(interrupt, val);
break;
case 0xFFFF: case 0xFFFF:
gb_log("Writing to interrupt mask: 0x%02x\n", val); gb_interrupt_ie_write(interrupt, val);
break;
default: default:
ram[addr] = val; ram[addr] = val;
} }
@@ -164,6 +175,12 @@ void gb_mem_write(struct gb_memory *memory, uint16_t addr, uint8_t val)
case 0xFF40 ... 0xFF4B: case 0xFF40 ... 0xFF4B:
gb_video_mem_write(video, addr, val); gb_video_mem_write(video, addr, val);
return; return;
case 0xFF0F:
gb_interrupt_if_write(interrupt, val);
return;
case 0xFFFF:
gb_interrupt_ie_write(interrupt, val);
return;
case 0x8000 ... 0x87FF: case 0x8000 ... 0x87FF:
case 0x9800 ... 0x9BFF: case 0x9800 ... 0x9BFF:
break; break;
@@ -172,9 +189,6 @@ void gb_mem_write(struct gb_memory *memory, uint16_t addr, uint8_t val)
return; return;
} }
break; break;
case 0xFFFF:
gb_log("Writing to interrupt mask: 0x%02x\n", val);
break;
} }
ram[addr] = val; ram[addr] = val;

View File

@@ -5,8 +5,10 @@
struct gb_memory; struct gb_memory;
struct gb_video; struct gb_video;
struct gb_interrupt;
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 *interrupt,
const char *bootrom, const char *rom); const char *bootrom, const char *rom);
uint8_t gb_mem_read(struct gb_memory *mem, uint16_t addr); uint8_t gb_mem_read(struct gb_memory *mem, uint16_t addr);

View File

@@ -1,5 +1,6 @@
#include "gbemu/video.h" #include "gbemu/video.h"
#include "gbemu/memory.h" #include "gbemu/memory.h"
#include "gbemu/interrupt.h"
#include "common/common.h" #include "common/common.h"
#include "common/bmp.h" #include "common/bmp.h"
@@ -9,7 +10,6 @@
/* TODO: This whole implementation is very simple.*/ /* TODO: This whole implementation is very simple.*/
/* TODO: Actual graphics output */ /* TODO: Actual graphics output */
/* TODO: Implementation of most registers */ /* TODO: Implementation of most registers */
/* TODO: Interrupts */
static struct bmp *bmp; static struct bmp *bmp;
@@ -28,12 +28,13 @@ static uint8_t color_tbl[4] = {
0, 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)); memset(video, 0, sizeof(*video));
video->debug_logging = 0; video->debug_logging = 0;
video->memory = memory; video->memory = memory;
video->interrupt = interrupt;
bmp = bmp_new(BMP_GRAYSCALE, BUFFER_WIDTH, BUFFER_HEIGHT); 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; video->curline += 1;
if (video->curline > LCD_Y_MAX) { if (video->curline > LCD_Y_MAX) {
video->curline = 0; video->curline = 0;
gb_interrupt_clear(video->interrupt, GB_INT_VBLANK);
if (video->lcdcont & (1 << 7)) { if (video->lcdcont & (1 << 7)) {
screenshot_count++; screenshot_count++;
if (screenshot_count % 30 == 0) { if (screenshot_count % 30 == 0) {
gb_video_screenshot(video); gb_video_screenshot(video);
} }
} }
} else if (video->curline == 144) {
gb_interrupt_set(video->interrupt, GB_INT_VBLANK);
} }
} }
} }

View File

@@ -95,9 +95,10 @@ struct gb_video {
int debug_logging; int debug_logging;
struct gb_memory *memory; 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); void gb_video_cycle(struct gb_video *video, int cycles);
uint8_t gb_video_mem_read(struct gb_video *video, uint16_t addr); 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); void gb_video_mem_write(struct gb_video *video, uint16_t addr, uint8_t val);