Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1076d02638 | |||
| 593d9d3600 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,5 +12,4 @@ build/*
|
|||||||
doxygen/*
|
doxygen/*
|
||||||
buildbot-upload/*
|
buildbot-upload/*
|
||||||
*/config.mak
|
*/config.mak
|
||||||
*\~
|
*\~
|
||||||
__pycache__
|
|
||||||
@@ -37,7 +37,6 @@ static int64_t parse_val(const char *str);
|
|||||||
static const char prompt[] = "gbdb > ";
|
static const char prompt[] = "gbdb > ";
|
||||||
static const char usage[] =
|
static const char usage[] =
|
||||||
"Available commands:\n"
|
"Available commands:\n"
|
||||||
"load <file>: loads the given file as the gameboy cartridge\n"
|
|
||||||
"run: runs the CPU until a breakpoint or halt is hit\n"
|
"run: runs the CPU until a breakpoint or halt is hit\n"
|
||||||
"break <addr>: Adds a breakpoint for the given addess\n"
|
"break <addr>: Adds a breakpoint for the given addess\n"
|
||||||
"step <cycles>: executes \"cycle\" instructions (default 1)\n"
|
"step <cycles>: executes \"cycle\" instructions (default 1)\n"
|
||||||
@@ -48,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;
|
||||||
@@ -108,13 +108,14 @@ static void strip_newline(char *string)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void init(void)
|
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);
|
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;
|
||||||
@@ -570,31 +571,6 @@ static void mem(struct tokens **tokens)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void load(struct tokens **tokens)
|
|
||||||
{
|
|
||||||
char *token = token_next(tokens);
|
|
||||||
if (token == NULL) {
|
|
||||||
gb_error("usage: load <file>\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gb_log("loading %s\n", token);
|
|
||||||
|
|
||||||
gb_memory_load_file(memory, GB_MEMORY_CART, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void load_bootrom(struct tokens **tokens)
|
|
||||||
{
|
|
||||||
char *token = token_next(tokens);
|
|
||||||
if (token == NULL) {
|
|
||||||
gb_error("usage: bootrom <file>\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gb_memory_load_file(memory, GB_MEMORY_BOOTROM, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void quit(struct tokens **tokens)
|
static void quit(struct tokens **tokens)
|
||||||
{
|
{
|
||||||
exit(0);
|
exit(0);
|
||||||
@@ -869,9 +845,10 @@ static int load_initfile(const char *initfile_path, struct tri *commands)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, const char **argv)
|
||||||
{
|
{
|
||||||
struct tri commands;
|
struct tri commands;
|
||||||
|
const char *rom, *bootrom;
|
||||||
|
|
||||||
tri_init(&commands);
|
tri_init(&commands);
|
||||||
tri_add_string(&commands, "#", comment);
|
tri_add_string(&commands, "#", comment);
|
||||||
@@ -882,8 +859,6 @@ int main(int argc, char **argv)
|
|||||||
tri_add_string(&commands, "stats", stats);
|
tri_add_string(&commands, "stats", stats);
|
||||||
tri_add_string(&commands, "exit", quit);
|
tri_add_string(&commands, "exit", quit);
|
||||||
tri_add_string(&commands, "quit", quit);
|
tri_add_string(&commands, "quit", quit);
|
||||||
tri_add_string(&commands, "load", load);
|
|
||||||
tri_add_string(&commands, "bootrom", load_bootrom);
|
|
||||||
tri_add_string(&commands, "mem", mem);
|
tri_add_string(&commands, "mem", mem);
|
||||||
/* FIXME */
|
/* FIXME */
|
||||||
/* tri_add_string(&commands, "dump", mem_dump); */
|
/* tri_add_string(&commands, "dump", mem_dump); */
|
||||||
@@ -897,11 +872,22 @@ int main(int argc, char **argv)
|
|||||||
tri_add_string(&commands, "set", set);
|
tri_add_string(&commands, "set", set);
|
||||||
tri_add_string(&commands, "echo", echo);
|
tri_add_string(&commands, "echo", echo);
|
||||||
|
|
||||||
init();
|
|
||||||
|
|
||||||
if (argc > 1) {
|
if (argc < 3) {
|
||||||
if (load_initfile(argv[1], &commands)) {
|
gb_error("usage: %s <BOOT_ROM> <ROM> INIT_SCRIPT\n", argv[0]);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bootrom = argv[1];
|
||||||
|
rom = argv[2];
|
||||||
|
|
||||||
|
init(bootrom, rom);
|
||||||
|
|
||||||
|
if (argc > 3) {
|
||||||
|
gb_log("%s\n", argv[0]);
|
||||||
|
if (load_initfile(argv[3], &commands)) {
|
||||||
gb_error("Failed to load initfile\n");
|
gb_error("Failed to load initfile\n");
|
||||||
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
90
src/gbemu/interrupt.c
Normal 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
61
src/gbemu/interrupt.h
Normal 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
|
||||||
@@ -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,17 +20,46 @@
|
|||||||
#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};
|
||||||
static unsigned char cart_low[0x100] = {0};
|
static unsigned char cart_low[0x100] = {0};
|
||||||
static int bootrom_mapped = 1;
|
static int bootrom_mapped = 1;
|
||||||
|
|
||||||
void gb_mem_init(struct gb_memory *memory, struct gb_video *v)
|
static void load_file_to_buffer(const char *filename, uint8_t *buffer, size_t size)
|
||||||
{
|
{
|
||||||
|
//TODO: Check error codes like a real programmer :P
|
||||||
|
FILE* prog_file;
|
||||||
|
long filesize;
|
||||||
|
|
||||||
|
prog_file = fopen(filename, "r");
|
||||||
|
if(prog_file < 0) {
|
||||||
|
printf("Failed to load game file: %d\n", errno);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(prog_file, 0, SEEK_END);
|
||||||
|
filesize = ftell(prog_file);
|
||||||
|
fseek(prog_file, 0, SEEK_SET);
|
||||||
|
|
||||||
|
if (fread(buffer, MIN(filesize, size), 1, prog_file) < 0) {
|
||||||
|
printf("Failed to read filed: %d\n", errno);
|
||||||
|
}
|
||||||
|
fclose(prog_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
video = v;
|
||||||
memset(&ram, 0, MAX_RAM_LEN);
|
memset(&ram, 0, MAX_RAM_LEN);
|
||||||
bootrom_mapped = 1;
|
bootrom_mapped = 1;
|
||||||
|
load_file_to_buffer(rom_file, cart_low, sizeof(cart_low));
|
||||||
|
load_file_to_buffer(rom_file, ram, sizeof(ram));
|
||||||
|
load_file_to_buffer(bootrom_file, bootrom, sizeof(bootrom));
|
||||||
|
load_file_to_buffer(bootrom_file, ram, sizeof(ram));
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t ser_byte = 0;
|
static uint8_t ser_byte = 0;
|
||||||
@@ -80,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];
|
||||||
}
|
}
|
||||||
@@ -109,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;
|
||||||
}
|
}
|
||||||
@@ -137,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;
|
||||||
@@ -145,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;
|
||||||
@@ -169,67 +210,3 @@ void gb_mem_force_write(struct gb_memory *memory, uint16_t addr, uint8_t val)
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void load_file_to_buffer(char *filename, uint8_t *buffer, size_t size)
|
|
||||||
{
|
|
||||||
//TODO: Check error codes like a real programmer :P
|
|
||||||
FILE* prog_file;
|
|
||||||
long filesize;
|
|
||||||
|
|
||||||
prog_file = fopen(filename, "r");
|
|
||||||
if(prog_file < 0) {
|
|
||||||
printf("Failed to load game file: %d\n", errno);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fseek(prog_file, 0, SEEK_END);
|
|
||||||
filesize = ftell(prog_file);
|
|
||||||
fseek(prog_file, 0, SEEK_SET);
|
|
||||||
|
|
||||||
if (fread(buffer, MIN(filesize, size), 1, prog_file) < 0) {
|
|
||||||
printf("Failed to read filed: %d\n", errno);
|
|
||||||
}
|
|
||||||
fclose(prog_file);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int gb_memory_load_file(struct gb_memory *memory, enum gb_memory_range destination, char *filename)
|
|
||||||
{
|
|
||||||
//TODO: Check error codes like a real programmer :P
|
|
||||||
FILE* prog_file;
|
|
||||||
long filesize;
|
|
||||||
|
|
||||||
switch (destination) {
|
|
||||||
case GB_MEMORY_BOOTROM:
|
|
||||||
load_file_to_buffer(filename, bootrom, sizeof(bootrom));
|
|
||||||
load_file_to_buffer(filename, ram, sizeof(bootrom));
|
|
||||||
|
|
||||||
break;
|
|
||||||
case GB_MEMORY_CART:
|
|
||||||
load_file_to_buffer(filename, ram, sizeof(ram));
|
|
||||||
load_file_to_buffer(filename, cart_low, sizeof(cart_low));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* static void mem_dump(char *filename) */
|
|
||||||
/* { */
|
|
||||||
/* //TODO: Check error codes like a real programmer :P */
|
|
||||||
/* FILE* dump_file; */
|
|
||||||
/* char *token = strtok(filename, " "); */
|
|
||||||
|
|
||||||
/* if (token == NULL) { */
|
|
||||||
/* gb_log("usage: load <file>\n"); */
|
|
||||||
/* return; */
|
|
||||||
/* } */
|
|
||||||
/* strip_newline(token); */
|
|
||||||
/* dump_file = fopen(token, "w"); */
|
|
||||||
/* if(dump_file == NULL) { */
|
|
||||||
/* gb_log("Failed to open mem dump file: %d\n", errno); */
|
|
||||||
/* return; */
|
|
||||||
/* } */
|
|
||||||
/* fwrite(ram, MAX_RAM_LEN, 1, dump_file); */
|
|
||||||
/* fclose(dump_file); */
|
|
||||||
/* } */
|
|
||||||
|
|||||||
@@ -5,17 +5,11 @@
|
|||||||
|
|
||||||
struct gb_memory;
|
struct gb_memory;
|
||||||
struct gb_video;
|
struct gb_video;
|
||||||
|
struct gb_interrupt;
|
||||||
|
|
||||||
enum gb_memory_range {
|
void gb_mem_init(struct gb_memory *memory, struct gb_video *v,
|
||||||
GB_MEMORY_BOOTROM,
|
struct gb_interrupt *interrupt,
|
||||||
GB_MEMORY_CART,
|
const char *bootrom, const char *rom);
|
||||||
};
|
|
||||||
|
|
||||||
void gb_mem_init(struct gb_memory *memory, struct gb_video *v);
|
|
||||||
|
|
||||||
int gb_memory_load_file(struct gb_memory *memory,
|
|
||||||
enum gb_memory_range destination,
|
|
||||||
char *filename);
|
|
||||||
|
|
||||||
uint8_t gb_mem_read(struct gb_memory *mem, uint16_t addr);
|
uint8_t gb_mem_read(struct gb_memory *mem, uint16_t addr);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
class ArgumentParser(object):
|
|
||||||
|
|
||||||
def can_parse(token: str) -> bool:
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def get_name() -> str:
|
|
||||||
raise NotImplementedError()
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
from .ArgumentParser import ArgumentParser
|
|
||||||
from . import Arguments
|
|
||||||
|
|
||||||
|
|
||||||
class Address(ArgumentParser):
|
|
||||||
|
|
||||||
NAME = "Address"
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def can_parse(self, token: str) -> bool:
|
|
||||||
try:
|
|
||||||
addr = int(token, base=0)
|
|
||||||
return addr < 0x8000 and addr >= 0
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_name(self) -> str:
|
|
||||||
return Address.NAME
|
|
||||||
|
|
||||||
|
|
||||||
class Label(ArgumentParser):
|
|
||||||
|
|
||||||
NAME = "Label"
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def can_parse(self, token: str) -> bool:
|
|
||||||
if not token[0] in string.ascii_letters + ['_']:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def parse(self, token) -> Arguments.Register8:
|
|
||||||
return Arguments.Label(token)
|
|
||||||
|
|
||||||
def get_name(self) -> str:
|
|
||||||
return Label.NAME
|
|
||||||
|
|
||||||
|
|
||||||
class Register8(ArgumentParser):
|
|
||||||
|
|
||||||
NAME = "Register8"
|
|
||||||
|
|
||||||
def __init__(self, indirect: bool=False, indirect_increment: bool=False):
|
|
||||||
self.indirect = indirect
|
|
||||||
|
|
||||||
def can_parse(self, token: str) -> bool:
|
|
||||||
if token in Arguments.Register8.REGISTERS:
|
|
||||||
return True
|
|
||||||
if self.indirect and token in Arguments.Register8.REGISTERS_INDIRECT:
|
|
||||||
return True
|
|
||||||
if self.indirect_increment \
|
|
||||||
and token in Arguments.Register8.REGISTERS_INDIRECT_INCREMENT:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def parse(self, token) -> Arguments.Register8:
|
|
||||||
return Arguments.Register8(token)
|
|
||||||
|
|
||||||
def get_name(self) -> str:
|
|
||||||
return Register8.NAME
|
|
||||||
|
|
||||||
|
|
||||||
class Register16(ArgumentParser):
|
|
||||||
|
|
||||||
NAME = "Immediate8"
|
|
||||||
|
|
||||||
def __init__(self, indirect: bool=False, indirect_increment: bool=False):
|
|
||||||
self.indirect = indirect
|
|
||||||
|
|
||||||
def can_parse(self, token: str) -> bool:
|
|
||||||
return token in Arguments.Register16.REGISTERS
|
|
||||||
|
|
||||||
def to_argument(self, token: str):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def parse(self, token) -> Arguments.Register16:
|
|
||||||
return Arguments.Register16(token)
|
|
||||||
|
|
||||||
def get_name(self) -> str:
|
|
||||||
return Register16.NAME
|
|
||||||
|
|
||||||
|
|
||||||
class Immediate8(ArgumentParser):
|
|
||||||
|
|
||||||
NAME = "Immediate8"
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def can_parse(self, token: str) -> bool:
|
|
||||||
try:
|
|
||||||
addr = int(token, base=0)
|
|
||||||
return addr <= 0xFF and addr >= 0
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_name(self) -> str:
|
|
||||||
return Immediate8.NAME
|
|
||||||
|
|
||||||
class Immediate16(ArgumentParser):
|
|
||||||
|
|
||||||
NAME = "Immediate16"
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def can_parse(self, token: str) -> bool:
|
|
||||||
try:
|
|
||||||
addr = int(token, base=0)
|
|
||||||
return addr <= 0xFFFF and addr >= 0
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def parse(self, token) -> Arguments.Immediate16:
|
|
||||||
return Arguments.Immediate16(token)
|
|
||||||
|
|
||||||
def get_name(self) -> str:
|
|
||||||
return Immediate16.NAME
|
|
||||||
|
|
||||||
|
|
||||||
class Flag(ArgumentParser):
|
|
||||||
|
|
||||||
NAME = "Flag"
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def can_parse(self, token: str) -> bool:
|
|
||||||
return token in Arguments.Flag.FLAGS
|
|
||||||
|
|
||||||
def parse(self, token) -> Arguments.Immediate16:
|
|
||||||
return Arguments.Flag(token)
|
|
||||||
|
|
||||||
def get_name(self) -> str:
|
|
||||||
return Flag.NAME
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
class Argument(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Address(Argument):
|
|
||||||
|
|
||||||
NAME = "Address"
|
|
||||||
|
|
||||||
def __init__(self, value: int):
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
|
|
||||||
class Label(Argument):
|
|
||||||
|
|
||||||
NAME = "Label"
|
|
||||||
|
|
||||||
def __init__(self, value: str):
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
|
|
||||||
class Register8(Argument):
|
|
||||||
|
|
||||||
NAME = "Register8"
|
|
||||||
REGISTERS = ["A", "B", "C", "D", "E", "H", "L"]
|
|
||||||
REGISTERS_INDIRECT = ["(HL)"]
|
|
||||||
REGISTERS_INDIRECT_INCREMENT = ["(HL)", "(HL+)", "(HL-)"]
|
|
||||||
|
|
||||||
def __init__(self, value: str):
|
|
||||||
if value not in Register8.REGISTERS \
|
|
||||||
+ Register8.REGISTERS_INDIRECT_INCREMENT:
|
|
||||||
raise ValueError("Unknown Register8: {}".format(value))
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
|
|
||||||
class Register16(Argument):
|
|
||||||
|
|
||||||
NAME = "Immediate8"
|
|
||||||
|
|
||||||
REGISTERS = ["BC", "DE", "DE", "HL", "SP"]
|
|
||||||
|
|
||||||
def __init__(self, value: int):
|
|
||||||
if value not in Register16.REGISTERS:
|
|
||||||
raise ValueError("Unknown Register16: {}".format(value))
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
class Immediate8(Argument):
|
|
||||||
|
|
||||||
NAME = "Immediate8"
|
|
||||||
|
|
||||||
def __init__(self, value: int):
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
|
|
||||||
class Immediate16(Argument):
|
|
||||||
|
|
||||||
NAME = "Immediate16"
|
|
||||||
|
|
||||||
def __init__(self, value: int):
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
|
|
||||||
class Flag(Argument):
|
|
||||||
|
|
||||||
NAME = "Immediate8"
|
|
||||||
|
|
||||||
FLAGS = ["Z", "C", "NZ", "NC"]
|
|
||||||
|
|
||||||
def __init__(self, value: str):
|
|
||||||
if value not in Flag.FLAGS:
|
|
||||||
raise ValueError("Unknown Flag: {}".format(value))
|
|
||||||
self.value = value
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from .ArgumentParser import ArgumentParser
|
|
||||||
from .ArgumentParsers import Label, Address, Immediate8, Immediate16, Register8, Register16
|
|
||||||
from .Arguments import Argument
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from .instructions import Instruction
|
|
||||||
from .instructions.inc import Inc
|
|
||||||
from .instructions.dec import Dec
|
|
||||||
from .instructions.nop import Nop
|
|
||||||
from .instructions.stop import Stop
|
|
||||||
from .instructions.jr import Jr
|
|
||||||
from .instructions.ld import Ld
|
|
||||||
from .arguments import ArgumentParser, Argument
|
|
||||||
|
|
||||||
from typing import Callable, Dict, List, Optional
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
COMMENT_CHAR = '#'
|
|
||||||
LABEL_SUFFIX = ':'
|
|
||||||
|
|
||||||
GB_INSTRUCTIONS = [
|
|
||||||
Nop(),
|
|
||||||
Stop(),
|
|
||||||
Inc(),
|
|
||||||
Dec(),
|
|
||||||
Jr(),
|
|
||||||
Ld(),
|
|
||||||
]
|
|
||||||
|
|
||||||
def build_instruction_map() -> Dict[str, Instruction]:
|
|
||||||
d = {} # type: Dict[str, Instruction]
|
|
||||||
for i in GB_INSTRUCTIONS:
|
|
||||||
d[i.token] = i
|
|
||||||
return d
|
|
||||||
|
|
||||||
def try_parse_arguments(args: List[str],
|
|
||||||
arg_types: List[ArgumentParser]) -> Optional[List[Argument]]:
|
|
||||||
if len(args) != len(arg_types):
|
|
||||||
return None
|
|
||||||
|
|
||||||
out_args = []
|
|
||||||
|
|
||||||
for (arg, arg_type) in zip(args, arg_types):
|
|
||||||
try:
|
|
||||||
out_args.append(arg_type.parse(arg))
|
|
||||||
except ValueError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return out_args
|
|
||||||
|
|
||||||
def parse_line_size(instruction: Instruction,
|
|
||||||
arguments: List[str]) -> bytes:
|
|
||||||
|
|
||||||
for argtype_list in instruction.argument_specs:
|
|
||||||
args = try_parse_arguments(arguments, argtype_list)
|
|
||||||
if args is not None:
|
|
||||||
return instruction.num_bytes(args)
|
|
||||||
|
|
||||||
raise ValueError("Failed to parse line.")
|
|
||||||
|
|
||||||
def parse_line_bytes(instruction: Instruction,
|
|
||||||
arguments: List[str],
|
|
||||||
instruction_addr: int,
|
|
||||||
label_resolver: Callable[[str], int]) -> bytes:
|
|
||||||
|
|
||||||
for argtype_list in instruction.argument_specs:
|
|
||||||
args = try_parse_arguments(arguments, argtype_list)
|
|
||||||
if args is not None:
|
|
||||||
return instruction.to_bytes(args, instruction_addr, label_resolver)
|
|
||||||
|
|
||||||
raise ValueError("Failed to parse line.")
|
|
||||||
|
|
||||||
def assemble_file(infile) -> bytes:
|
|
||||||
program = infile.readlines()
|
|
||||||
return assemble(infile)
|
|
||||||
|
|
||||||
def assemble(lines: str) -> bytes:
|
|
||||||
|
|
||||||
instruction_map = build_instruction_map()
|
|
||||||
logger.debug("Instruction map: %s", instruction_map)
|
|
||||||
|
|
||||||
labels = {} # type: Dict[str, int]
|
|
||||||
program = bytes()
|
|
||||||
|
|
||||||
def label_resolver(label: str) -> int:
|
|
||||||
nonlocal labels
|
|
||||||
return labels[label]
|
|
||||||
|
|
||||||
for step in ["SIZE", "CONTENT"]:
|
|
||||||
logger.debug("Starting step: %s", step)
|
|
||||||
byte_offset = 0
|
|
||||||
|
|
||||||
for line_num, line in enumerate(lines):
|
|
||||||
# Remove comments
|
|
||||||
line = line.split(COMMENT_CHAR)[0]
|
|
||||||
|
|
||||||
# Tokenize
|
|
||||||
tokens = line.split()
|
|
||||||
|
|
||||||
if len(tokens) == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
instruction_name = tokens[0]
|
|
||||||
args = tokens[1:]
|
|
||||||
try:
|
|
||||||
instruction = instruction_map[instruction_name]
|
|
||||||
except KeyError:
|
|
||||||
if instruction_name[-1] == LABEL_SUFFIX:
|
|
||||||
if step == 'SIZE':
|
|
||||||
label = instruction_name[:-1]
|
|
||||||
logger.debug("Found label '%s' at %s", label, byte_offset)
|
|
||||||
if label in labels.keys():
|
|
||||||
raise KeyError("Label '%s' defined at %s and %s",
|
|
||||||
label, labels[label], line_num)
|
|
||||||
labels[label] = byte_offset
|
|
||||||
continue
|
|
||||||
raise KeyError("Unknown instruction \"%s\" on line %s",
|
|
||||||
instruction_name, line_num)
|
|
||||||
|
|
||||||
if step == 'CONTENT':
|
|
||||||
try:
|
|
||||||
program += parse_line_bytes(instruction, args, byte_offset, label_resolver)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError("Failed to parse line %s,\n%s", line_num, line)
|
|
||||||
|
|
||||||
byte_offset += parse_line_size(instruction, args)
|
|
||||||
if step == 'SIZE':
|
|
||||||
logger.info("Program size: %s bytes", byte_offset)
|
|
||||||
logger.debug("Found labels: %s", labels)
|
|
||||||
|
|
||||||
return program
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description= "An assembler for Gameboy assembly")
|
|
||||||
|
|
||||||
parser.add_argument("--infile", "-i", type=argparse.FileType("r"), default=sys.stdin)
|
|
||||||
parser.add_argument("--outfile", "-o", type=argparse.FileType("wb"), default=sys.stdout)
|
|
||||||
parser.add_argument("--verbose", "-v", action='store_true')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
logging.basicConfig(format="%(levelname)s: %(message)s")
|
|
||||||
logger.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
if args.verbose:
|
|
||||||
logging.basicConfig(format="%(levelname)s: %(filename)s:%(lineno)d: %(message)s")
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
program = assemble_file(args.infile)
|
|
||||||
outfile.write(program)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
from typing import Callable, List
|
|
||||||
from ..arguments import Argument, ArgumentParser
|
|
||||||
|
|
||||||
class Instruction(object):
|
|
||||||
|
|
||||||
def __init__(self, token: str, argument_specs: List[List[ArgumentParser]]):
|
|
||||||
self.token = token
|
|
||||||
self.argument_specs = argument_specs
|
|
||||||
|
|
||||||
def num_bytes(self, arguments) -> int:
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def to_bytes(self,
|
|
||||||
arguments: List[Argument],
|
|
||||||
instruction_addr: int,
|
|
||||||
label_resolver: Callable[[str], int]) -> bytes:
|
|
||||||
raise NotImplementedError()
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
from . Instruction import Instruction
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
from .Instruction import Instruction
|
|
||||||
from ..arguments.ArgumentParsers import Register8, Register16
|
|
||||||
from ..arguments import Argument
|
|
||||||
from typing import Callable, List
|
|
||||||
|
|
||||||
|
|
||||||
class Dec(Instruction):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
argtypes = [[Register8()], [Register16()]]
|
|
||||||
super().__init__("DEC", argtypes)
|
|
||||||
|
|
||||||
def num_bytes(self, arguments) -> int:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def to_bytes(self,
|
|
||||||
arguments: List[Argument],
|
|
||||||
instruction_addr: int,
|
|
||||||
label_resolver: Callable[[str], int]) -> bytes:
|
|
||||||
|
|
||||||
if len(arguments) != 1:
|
|
||||||
raise ValueError("Incorrect number of arguments")
|
|
||||||
|
|
||||||
value = arguments[0].value
|
|
||||||
|
|
||||||
if value == "BC":
|
|
||||||
return bytes([0x0B])
|
|
||||||
if value == "DE":
|
|
||||||
return bytes([0x1B])
|
|
||||||
if value == "HL":
|
|
||||||
return bytes([0x2B])
|
|
||||||
if value == "SP":
|
|
||||||
return bytes([0x3B])
|
|
||||||
if value == "A":
|
|
||||||
return bytes([0x3D])
|
|
||||||
if value == "B":
|
|
||||||
return bytes([0x05])
|
|
||||||
if value == "C":
|
|
||||||
return bytes([0x0D])
|
|
||||||
if value == "D":
|
|
||||||
return bytes([0x15])
|
|
||||||
if value == "E":
|
|
||||||
return bytes([0x1D])
|
|
||||||
if value == "H":
|
|
||||||
return bytes([0x25])
|
|
||||||
if value == "L":
|
|
||||||
return bytes([0x2D])
|
|
||||||
if value == "(HL)":
|
|
||||||
return bytes([0x35])
|
|
||||||
|
|
||||||
raise ValueError("Unknown value: {}".format(value))
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
from .Instruction import Instruction
|
|
||||||
from ..arguments.ArgumentParsers import Register8, Register16
|
|
||||||
from ..arguments import Argument
|
|
||||||
from typing import Callable, List
|
|
||||||
|
|
||||||
|
|
||||||
class Inc(Instruction):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
argtypes = [[Register8()], [Register16()]]
|
|
||||||
super().__init__("INC", argtypes)
|
|
||||||
|
|
||||||
def num_bytes(self, arguments) -> int:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def to_bytes(self,
|
|
||||||
arguments: List[Argument],
|
|
||||||
instruction_addr: int,
|
|
||||||
label_resolver: Callable[[str], int]) -> bytes:
|
|
||||||
|
|
||||||
if len(arguments) != 1:
|
|
||||||
raise ValueError("Incorrect number of arguments")
|
|
||||||
|
|
||||||
value = arguments[0].value
|
|
||||||
|
|
||||||
if value == "BC":
|
|
||||||
return bytes([0x03])
|
|
||||||
if value == "DE":
|
|
||||||
return bytes([0x13])
|
|
||||||
if value == "HL":
|
|
||||||
return bytes([0x23])
|
|
||||||
if value == "SP":
|
|
||||||
return bytes([0x33])
|
|
||||||
if value == "A":
|
|
||||||
return bytes([0x3C])
|
|
||||||
if value == "B":
|
|
||||||
return bytes([0x04])
|
|
||||||
if value == "C":
|
|
||||||
return bytes([0x0C])
|
|
||||||
if value == "D":
|
|
||||||
return bytes([0x14])
|
|
||||||
if value == "E":
|
|
||||||
return bytes([0x1C])
|
|
||||||
if value == "H":
|
|
||||||
return bytes([0x24])
|
|
||||||
if value == "L":
|
|
||||||
return bytes([0x2C])
|
|
||||||
if value == "(HL)":
|
|
||||||
return bytes([0x34])
|
|
||||||
|
|
||||||
raise ValueError("Unknown value: {}".format(value))
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
from .Instruction import Instruction
|
|
||||||
from ..arguments.ArgumentParsers import Flag, Label
|
|
||||||
from ..arguments import Argument
|
|
||||||
from typing import Callable, List
|
|
||||||
|
|
||||||
|
|
||||||
class Jr(Instruction):
|
|
||||||
|
|
||||||
INSTRUCTION_SIZE = 2
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
argtypes = [[Label()], [Flag(), Label()]]
|
|
||||||
super().__init__("JR", argtypes)
|
|
||||||
|
|
||||||
def num_bytes(self, arguments) -> int:
|
|
||||||
return Jr.INSTRUCTION_SIZE
|
|
||||||
|
|
||||||
def to_bytes(self,
|
|
||||||
arguments: List[Argument],
|
|
||||||
instruction_address: int,
|
|
||||||
label_resolver: Callable[[str], int]) -> bytes:
|
|
||||||
|
|
||||||
out_bytes = bytearray()
|
|
||||||
|
|
||||||
if len(arguments) == 1:
|
|
||||||
out_bytes.append(0x18)
|
|
||||||
dest_arg = arguments[0].value
|
|
||||||
|
|
||||||
elif len(arguments) == 2:
|
|
||||||
flag_dict = {
|
|
||||||
"NZ": 0x20,
|
|
||||||
"NC": 0x30,
|
|
||||||
"Z": 0x28,
|
|
||||||
"C": 0x38
|
|
||||||
}
|
|
||||||
flag = arguments[0].value
|
|
||||||
try:
|
|
||||||
out_bytes.append(flag_dict[flag])
|
|
||||||
except KeyError:
|
|
||||||
logger.exception("Instruction JR does not accept flag %s", arg)
|
|
||||||
dest_arg = arguments[1].value
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise ValueError("Incorrect number of arguments")
|
|
||||||
|
|
||||||
if isinstance(dest_arg, str):
|
|
||||||
label = dest_arg
|
|
||||||
dest_addr = label_resolver(label)
|
|
||||||
else:
|
|
||||||
dest_addr = dest_arg
|
|
||||||
|
|
||||||
addr_offset = dest_addr - (instruction_address + Jr.INSTRUCTION_SIZE)
|
|
||||||
if addr_offset > 127 or addr_offset < -128:
|
|
||||||
raise ValueError("JR instruction cannot jump to {} ({}), from {}, {} bytes away"
|
|
||||||
.format(label, dest_addr, instruction_address, addr_offset))
|
|
||||||
if addr_offset < 0:
|
|
||||||
addr_offset += 256
|
|
||||||
|
|
||||||
out_bytes.append(addr_offset)
|
|
||||||
return bytes(out_bytes)
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
from .Instruction import Instruction
|
|
||||||
from ..arguments.ArgumentParsers import Register16, Immediate16
|
|
||||||
from ..arguments import Argument
|
|
||||||
from typing import Callable, List
|
|
||||||
|
|
||||||
class Ld(Instruction):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
argtypes = [[Register16(), Immediate16()]]
|
|
||||||
super().__init__("LD", argtypes)
|
|
||||||
|
|
||||||
def num_bytes(self, arguments) -> int:
|
|
||||||
# the instruction_address and label_resolver are unused
|
|
||||||
return len(self.to_bytes(arguments, 0, lambda x: None))
|
|
||||||
|
|
||||||
def encode_reg_16(self,
|
|
||||||
register: Register16,
|
|
||||||
immediate: Immediate16) -> bytes:
|
|
||||||
reg_dict = {
|
|
||||||
"BC": 0x01,
|
|
||||||
"DE": 0x11,
|
|
||||||
"HL": 0x21,
|
|
||||||
"SP": 0x31
|
|
||||||
}
|
|
||||||
imm = int(immediate.value, 0)
|
|
||||||
print("immediate", imm)
|
|
||||||
out = bytearray()
|
|
||||||
out.append(reg_dict[register.value])
|
|
||||||
out.extend(imm.to_bytes(2, "little", signed=imm < 0))
|
|
||||||
return bytes(out)
|
|
||||||
|
|
||||||
def to_bytes(self,
|
|
||||||
arguments: List[Argument],
|
|
||||||
instruction_address: int,
|
|
||||||
label_resolver: Callable[[str], int]) -> bytes:
|
|
||||||
|
|
||||||
# print(arguments)
|
|
||||||
# print("Register16", type(arguments[0]))
|
|
||||||
# print("Immediate16", isinstance(arguments[1], Immediate16))
|
|
||||||
|
|
||||||
# if isinstance(arguments[0], Register16) and \
|
|
||||||
# isinstance(arguments[1], Immediate16):
|
|
||||||
return self.encode_reg_16(arguments[0], arguments[1])
|
|
||||||
|
|
||||||
raise TypeError("Unhandled argument types")
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
from .Instruction import Instruction
|
|
||||||
from ..arguments import Argument
|
|
||||||
from typing import Callable, List
|
|
||||||
|
|
||||||
|
|
||||||
class Nop(Instruction):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
argtypes = [[]]
|
|
||||||
super().__init__("NOP", argtypes)
|
|
||||||
|
|
||||||
def num_bytes(self, arguments) -> int:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def to_bytes(self,
|
|
||||||
arguments: List[Argument],
|
|
||||||
instruction_addr: int,
|
|
||||||
label_resolver: Callable[[str], int]) -> bytes:
|
|
||||||
|
|
||||||
if len(arguments) != 0:
|
|
||||||
raise ValueError("Incorrect number of arguments")
|
|
||||||
|
|
||||||
return bytes([0x00])
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
from .Instruction import Instruction
|
|
||||||
from ..arguments import Argument
|
|
||||||
from typing import Callable, List
|
|
||||||
|
|
||||||
|
|
||||||
class Stop(Instruction):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
argtypes = [[]]
|
|
||||||
super().__init__("STOP", argtypes)
|
|
||||||
|
|
||||||
def num_bytes(self, arguments) -> int:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def to_bytes(self,
|
|
||||||
arguments: List[Argument],
|
|
||||||
instruction_addr: int,
|
|
||||||
label_resolver: Callable[[str], int]) -> bytes:
|
|
||||||
|
|
||||||
if len(arguments) != 0:
|
|
||||||
raise ValueError("Incorrect number of arguments")
|
|
||||||
|
|
||||||
return bytes([0x10])
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: two_nop
|
|
||||||
program: |
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
expected:
|
|
||||||
- 0x00
|
|
||||||
- 0x00
|
|
||||||
---
|
|
||||||
name: unused_labels
|
|
||||||
program: |
|
|
||||||
start:
|
|
||||||
NOP
|
|
||||||
middle:
|
|
||||||
NOP
|
|
||||||
end:
|
|
||||||
expected:
|
|
||||||
- 0x00
|
|
||||||
- 0x00
|
|
||||||
---
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
name: two_nop
|
|
||||||
program: |
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
expected:
|
|
||||||
- 0x00
|
|
||||||
- 0x00
|
|
||||||
---
|
|
||||||
name: unused_labels
|
|
||||||
program: |
|
|
||||||
start:
|
|
||||||
NOP
|
|
||||||
middle:
|
|
||||||
NOP
|
|
||||||
end:
|
|
||||||
expected:
|
|
||||||
- 0x00
|
|
||||||
- 0x00
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
---
|
|
||||||
name: dec_a
|
|
||||||
program: |
|
|
||||||
DEC A
|
|
||||||
expected:
|
|
||||||
- 0x3D
|
|
||||||
|
|
||||||
---
|
|
||||||
name: dec_b
|
|
||||||
program: |
|
|
||||||
DEC B
|
|
||||||
expected:
|
|
||||||
- 0x05
|
|
||||||
---
|
|
||||||
name: dec_c
|
|
||||||
program: |
|
|
||||||
DEC C
|
|
||||||
expected:
|
|
||||||
- 0x0D
|
|
||||||
---
|
|
||||||
name: dec_d
|
|
||||||
program: |
|
|
||||||
DEC D
|
|
||||||
expected:
|
|
||||||
- 0x15
|
|
||||||
---
|
|
||||||
name: dec_e
|
|
||||||
program: |
|
|
||||||
DEC E
|
|
||||||
expected:
|
|
||||||
- 0x1D
|
|
||||||
---
|
|
||||||
name: dec_h
|
|
||||||
program: |
|
|
||||||
DEC H
|
|
||||||
expected:
|
|
||||||
- 0x25
|
|
||||||
---
|
|
||||||
name: dec_l
|
|
||||||
program: |
|
|
||||||
DEC L
|
|
||||||
expected:
|
|
||||||
- 0x2D
|
|
||||||
---
|
|
||||||
name: dec_(hl)
|
|
||||||
program: |
|
|
||||||
DEC (HL)
|
|
||||||
expected:
|
|
||||||
- 0x35
|
|
||||||
|
|
||||||
---
|
|
||||||
name: dec_bc
|
|
||||||
program: |
|
|
||||||
DEC BC
|
|
||||||
expected:
|
|
||||||
- 0x0B
|
|
||||||
---
|
|
||||||
name: dec_de
|
|
||||||
program: |
|
|
||||||
DEC DE
|
|
||||||
expected:
|
|
||||||
- 0x1B
|
|
||||||
---
|
|
||||||
name: dec_hl
|
|
||||||
program: |
|
|
||||||
DEC HL
|
|
||||||
expected:
|
|
||||||
- 0x2B
|
|
||||||
---
|
|
||||||
name: dec_sp
|
|
||||||
program: |
|
|
||||||
DEC SP
|
|
||||||
expected:
|
|
||||||
- 0x3B
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
---
|
|
||||||
name: inc_a
|
|
||||||
program: |
|
|
||||||
INC A
|
|
||||||
expected:
|
|
||||||
- 0x3c
|
|
||||||
|
|
||||||
---
|
|
||||||
name: inc_b
|
|
||||||
program: |
|
|
||||||
INC B
|
|
||||||
expected:
|
|
||||||
- 0x04
|
|
||||||
---
|
|
||||||
name: inc_c
|
|
||||||
program: |
|
|
||||||
INC C
|
|
||||||
expected:
|
|
||||||
- 0x0c
|
|
||||||
---
|
|
||||||
name: inc_d
|
|
||||||
program: |
|
|
||||||
INC D
|
|
||||||
expected:
|
|
||||||
- 0x14
|
|
||||||
---
|
|
||||||
name: inc_e
|
|
||||||
program: |
|
|
||||||
INC E
|
|
||||||
expected:
|
|
||||||
- 0x1c
|
|
||||||
---
|
|
||||||
name: inc_h
|
|
||||||
program: |
|
|
||||||
INC H
|
|
||||||
expected:
|
|
||||||
- 0x24
|
|
||||||
---
|
|
||||||
name: inc_l
|
|
||||||
program: |
|
|
||||||
INC L
|
|
||||||
expected:
|
|
||||||
- 0x2c
|
|
||||||
---
|
|
||||||
name: inc_(hl)
|
|
||||||
program: |
|
|
||||||
INC (HL)
|
|
||||||
expected:
|
|
||||||
- 0x34
|
|
||||||
|
|
||||||
---
|
|
||||||
name: inc_bc
|
|
||||||
program: |
|
|
||||||
INC BC
|
|
||||||
expected:
|
|
||||||
- 0x03
|
|
||||||
---
|
|
||||||
name: inc_de
|
|
||||||
program: |
|
|
||||||
INC DE
|
|
||||||
expected:
|
|
||||||
- 0x13
|
|
||||||
---
|
|
||||||
name: inc_hl
|
|
||||||
program: |
|
|
||||||
INC HL
|
|
||||||
expected:
|
|
||||||
- 0x23
|
|
||||||
---
|
|
||||||
name: inc_sp
|
|
||||||
program: |
|
|
||||||
INC SP
|
|
||||||
expected:
|
|
||||||
- 0x33
|
|
||||||
@@ -1,370 +0,0 @@
|
|||||||
---
|
|
||||||
name: jr_bkwd
|
|
||||||
program: |
|
|
||||||
label:
|
|
||||||
JR label
|
|
||||||
expected:
|
|
||||||
- 0x18
|
|
||||||
- 0xFE
|
|
||||||
---
|
|
||||||
name: jr_fwd
|
|
||||||
program: |
|
|
||||||
JR label
|
|
||||||
label:
|
|
||||||
expected:
|
|
||||||
- 0x18
|
|
||||||
- 0x00
|
|
||||||
|
|
||||||
---
|
|
||||||
name: jr_z_bkwd
|
|
||||||
program: |
|
|
||||||
label:
|
|
||||||
JR Z label
|
|
||||||
expected:
|
|
||||||
- 0x28
|
|
||||||
- 0xFE
|
|
||||||
---
|
|
||||||
name: jr_z_fwd
|
|
||||||
program: |
|
|
||||||
JR Z label
|
|
||||||
label:
|
|
||||||
expected:
|
|
||||||
- 0x28
|
|
||||||
- 0x00
|
|
||||||
|
|
||||||
---
|
|
||||||
name: jr_nz_bkwd
|
|
||||||
program: |
|
|
||||||
label:
|
|
||||||
JR NZ label
|
|
||||||
expected:
|
|
||||||
- 0x20
|
|
||||||
- 0xFE
|
|
||||||
---
|
|
||||||
name: jr_nz_fwd
|
|
||||||
program: |
|
|
||||||
JR NZ label
|
|
||||||
label:
|
|
||||||
expected:
|
|
||||||
- 0x20
|
|
||||||
- 0x00
|
|
||||||
|
|
||||||
---
|
|
||||||
name: jr_c_bkwd
|
|
||||||
program: |
|
|
||||||
label:
|
|
||||||
JR C label
|
|
||||||
expected:
|
|
||||||
- 0x38
|
|
||||||
- 0xFE
|
|
||||||
---
|
|
||||||
name: jr_c_fwd
|
|
||||||
program: |
|
|
||||||
JR C label
|
|
||||||
label:
|
|
||||||
expected:
|
|
||||||
- 0x38
|
|
||||||
- 0x00
|
|
||||||
|
|
||||||
---
|
|
||||||
name: jr_nc_bkwd
|
|
||||||
program: |
|
|
||||||
label:
|
|
||||||
JR NC label
|
|
||||||
expected:
|
|
||||||
- 0x30
|
|
||||||
- 0xFE
|
|
||||||
---
|
|
||||||
name: jr_nc_fwd
|
|
||||||
program: |
|
|
||||||
JR NC label
|
|
||||||
label:
|
|
||||||
expected:
|
|
||||||
- 0x30
|
|
||||||
- 0x00
|
|
||||||
|
|
||||||
---
|
|
||||||
# Jump backward by the maximum amount
|
|
||||||
name: jr_far_bkwd
|
|
||||||
program: |
|
|
||||||
far_label:
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
JR far_label
|
|
||||||
expected: [
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x18, 0x80 ]
|
|
||||||
|
|
||||||
---
|
|
||||||
# Jump forward by the maximum amount
|
|
||||||
name: jr_far_fwd
|
|
||||||
program: |
|
|
||||||
JR far_label
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
NOP
|
|
||||||
far_label:
|
|
||||||
expected: [ 0x18, 0x7F,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
---
|
|
||||||
name: ld_bc
|
|
||||||
program: |
|
|
||||||
LD BC 0
|
|
||||||
expected:
|
|
||||||
- 0x01
|
|
||||||
- 0x00
|
|
||||||
- 0x00
|
|
||||||
---
|
|
||||||
name: ld_de
|
|
||||||
program: |
|
|
||||||
LD DE 0
|
|
||||||
expected:
|
|
||||||
- 0x11
|
|
||||||
- 0x00
|
|
||||||
- 0x00
|
|
||||||
---
|
|
||||||
name: ld_hl
|
|
||||||
program: |
|
|
||||||
LD HL 0
|
|
||||||
expected:
|
|
||||||
- 0x21
|
|
||||||
- 0x00
|
|
||||||
- 0x00
|
|
||||||
---
|
|
||||||
name: ld_sp
|
|
||||||
program: |
|
|
||||||
LD SP 0
|
|
||||||
expected:
|
|
||||||
- 0x31
|
|
||||||
- 0x00
|
|
||||||
- 0x00
|
|
||||||
|
|
||||||
---
|
|
||||||
name: ld_16_max
|
|
||||||
program: |
|
|
||||||
LD BC 65535
|
|
||||||
expected:
|
|
||||||
- 0x01
|
|
||||||
- 0xFF
|
|
||||||
- 0xFF
|
|
||||||
---
|
|
||||||
name: ld_16_negative
|
|
||||||
program: |
|
|
||||||
LD BC -1
|
|
||||||
expected:
|
|
||||||
- 0x01
|
|
||||||
- 0xFF
|
|
||||||
- 0xFF
|
|
||||||
---
|
|
||||||
name: ld_16_min
|
|
||||||
program: |
|
|
||||||
LD BC -32768
|
|
||||||
expected:
|
|
||||||
- 0x01
|
|
||||||
- 0x00
|
|
||||||
- 0x80
|
|
||||||
---
|
|
||||||
name: ld_16_00FF
|
|
||||||
program: |
|
|
||||||
LD BC 0x00FF
|
|
||||||
expected:
|
|
||||||
- 0x01
|
|
||||||
- 0xFF
|
|
||||||
- 0x00
|
|
||||||
---
|
|
||||||
name: ld_16_FF00
|
|
||||||
program: |
|
|
||||||
LD BC 0xFF00
|
|
||||||
expected:
|
|
||||||
- 0x01
|
|
||||||
- 0x00
|
|
||||||
- 0xFF
|
|
||||||
---
|
|
||||||
name: ld_16_100
|
|
||||||
program: |
|
|
||||||
LD BC 10
|
|
||||||
expected:
|
|
||||||
- 0x01
|
|
||||||
- 0x0A
|
|
||||||
- 0x00
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
name: nop
|
|
||||||
program: |
|
|
||||||
NOP
|
|
||||||
expected:
|
|
||||||
- 0x00
|
|
||||||
---
|
|
||||||
name: stop
|
|
||||||
program: |
|
|
||||||
STOP
|
|
||||||
expected:
|
|
||||||
- 0x10
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
from gbasm.gbasm import assemble
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import os
|
|
||||||
import yaml
|
|
||||||
import pytest
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
logging.basicConfig(format="")
|
|
||||||
logging.getLogger().setLevel(logging.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
class AssembleCase(object):
|
|
||||||
|
|
||||||
def __init__(self, name: str, program: str, expected: bytes):
|
|
||||||
self.name = name
|
|
||||||
self.program = program
|
|
||||||
self.expected = expected
|
|
||||||
|
|
||||||
|
|
||||||
def find_case_files(subdir: str):
|
|
||||||
test_root = Path(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
case_root = test_root / "cases" / subdir
|
|
||||||
return case_root.glob("**/*.yaml")
|
|
||||||
|
|
||||||
def get_test_cases(subdir: str):
|
|
||||||
cases = []
|
|
||||||
files = find_case_files(subdir)
|
|
||||||
for f in files:
|
|
||||||
index = 0
|
|
||||||
with open(str(f), "r") as yaml_file:
|
|
||||||
test_descs = yaml.safe_load_all(yaml_file)
|
|
||||||
for desc in test_descs:
|
|
||||||
try:
|
|
||||||
case = AssembleCase(desc['name'], desc['program'], bytes(desc['expected']))
|
|
||||||
except TypeError:
|
|
||||||
logger.exception("Failed to parse yaml: %s", desc)
|
|
||||||
cases.append(case)
|
|
||||||
return cases
|
|
||||||
|
|
||||||
instruction_cases = get_test_cases("instructions")
|
|
||||||
@pytest.mark.parametrize("case", instruction_cases,
|
|
||||||
ids=[case.name for case in instruction_cases])
|
|
||||||
def test_assemble_instruction(case):
|
|
||||||
lines = case.program.split("\n")
|
|
||||||
assembled = assemble(lines)
|
|
||||||
assert assembled == case.expected
|
|
||||||
|
|
||||||
format_cases = get_test_cases("format")
|
|
||||||
@pytest.mark.parametrize("case", format_cases,
|
|
||||||
ids=[case.name for case in format_cases])
|
|
||||||
def test_format_instruction(case):
|
|
||||||
lines = case.program.split("\n")
|
|
||||||
assembled = assemble(lines)
|
|
||||||
assert assembled == case.expected
|
|
||||||
Reference in New Issue
Block a user