2 Commits
py-asm ... wip

Author SHA1 Message Date
1076d02638 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.
2018-09-20 20:55:51 -07:00
593d9d3600 gbdb,memory: require memory files on initialization
This will make it a little bit easier to set up the proper MBC when
they are implemented.
2018-09-11 21:15:14 -07:00
33 changed files with 287 additions and 1488 deletions

3
.gitignore vendored
View File

@@ -12,5 +12,4 @@ build/*
doxygen/*
buildbot-upload/*
*/config.mak
*\~
__pycache__
*\~

View File

@@ -37,7 +37,6 @@ static int64_t parse_val(const char *str);
static const char prompt[] = "gbdb > ";
static const char usage[] =
"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"
"break <addr>: Adds a breakpoint for the given addess\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_state cpu;
static struct gb_video video;
static struct gb_interrupt interrupt;
static void *memory = NULL;
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;
lr35902_init(&cpu, memory, &cpu_ops);
gb_video_init(&video, memory);
gb_mem_init(memory, &video);
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;
@@ -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)
{
exit(0);
@@ -869,9 +845,10 @@ static int load_initfile(const char *initfile_path, struct tri *commands)
return 0;
}
int main(int argc, char **argv)
int main(int argc, const char **argv)
{
struct tri commands;
const char *rom, *bootrom;
tri_init(&commands);
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, "exit", 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);
/* FIXME */
/* 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, "echo", echo);
init();
if (argc > 1) {
if (load_initfile(argv[1], &commands)) {
if (argc < 3) {
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");
exit(1);
}
}

View File

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

View File

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

View File

@@ -9,6 +9,8 @@
#define GB_CPU_H
#include "gbemu/memory.h"
#include "gbemu/cpu.h"
#include "gbemu/interrupt.h"
#include <stdint.h>
@@ -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

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/video.h"
#include "gbemu/cpu.h"
#include "common/common.h"
@@ -19,17 +20,46 @@
#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};
static unsigned char cart_low[0x100] = {0};
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;
memset(&ram, 0, MAX_RAM_LEN);
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;
@@ -80,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];
}
@@ -109,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;
}
@@ -137,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;
@@ -145,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;
@@ -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); */
/* } */

View File

@@ -5,17 +5,11 @@
struct gb_memory;
struct gb_video;
struct gb_interrupt;
enum gb_memory_range {
GB_MEMORY_BOOTROM,
GB_MEMORY_CART,
};
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);
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);

View File

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

View File

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

View File

@@ -1,7 +0,0 @@
class ArgumentParser(object):
def can_parse(token: str) -> bool:
raise NotImplementedError()
def get_name() -> str:
raise NotImplementedError()

View File

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

View File

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

View File

@@ -1,3 +0,0 @@
from .ArgumentParser import ArgumentParser
from .ArgumentParsers import Label, Address, Immediate8, Immediate16, Register8, Register16
from .Arguments import Argument

View File

@@ -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()

View File

@@ -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()

View File

@@ -1 +0,0 @@
from . Instruction import Instruction

View File

@@ -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))

View File

@@ -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))

View File

@@ -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)

View File

@@ -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")

View File

@@ -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])

View File

@@ -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])

View File

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

View File

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

View File

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

View File

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

View File

@@ -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, ]

View File

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

View File

@@ -1,12 +0,0 @@
---
name: nop
program: |
NOP
expected:
- 0x00
---
name: stop
program: |
STOP
expected:
- 0x10

View File

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