From 6e2f4096a217f257b7bac60b28e26830f2d268fb Mon Sep 17 00:00:00 2001 From: Max Regan Date: Sun, 18 Dec 2016 23:43:41 -0800 Subject: [PATCH] gb-emu: initial commit Add a mostly non-functional Gameboy CPU and the skeleton of a Gameboy assembler intended for unit tests. --- Makefile | 81 +++ README.md | 8 + src/apps/gbasm-test.c | 79 +++ src/apps/gbasm.c | 87 +++ src/apps/gbdb.c | 525 ++++++++++++++ src/apps/sample-test.c | 20 + src/apps/tri-test.c | 65 ++ src/common.c | 10 + src/common.h | 53 ++ src/cpu.c | 1459 +++++++++++++++++++++++++++++++++++++++ src/cpu.h | 111 +++ src/gb_disas.c | 282 ++++++++ src/gb_disas.h | 19 + src/gbasm/assemble.c | 39 ++ src/gbasm/assemble.h | 17 + src/gbasm/block.h | 38 + src/gbasm/emit.c | 64 ++ src/gbasm/emitter.h | 44 ++ src/gbasm/errors.c | 56 ++ src/gbasm/errors.h | 17 + src/gbasm/gb_types.h | 78 +++ src/gbasm/opcodes.c | 284 ++++++++ src/gbasm/opcodes.h | 12 + src/gbasm/operands.c | 212 ++++++ src/gbasm/operands.h | 12 + src/gbasm/parser.c | 123 ++++ src/gbasm/parser.h | 19 + src/gbasm/types.h | 49 ++ src/tests/gbasm/fixed.c | 51 ++ src/tests/gbasm/fixed.h | 9 + src/tests/gbasm/inc.c | 41 ++ src/tests/gbasm/inc.h | 9 + src/tests/gbasm/test.h | 19 + src/tri.c | 165 +++++ src/tri.h | 58 ++ src/util.h | 24 + src/video.c | 85 +++ src/video.h | 100 +++ 38 files changed, 4424 insertions(+) create mode 100755 Makefile create mode 100644 README.md create mode 100644 src/apps/gbasm-test.c create mode 100644 src/apps/gbasm.c create mode 100644 src/apps/gbdb.c create mode 100644 src/apps/sample-test.c create mode 100644 src/apps/tri-test.c create mode 100644 src/common.c create mode 100644 src/common.h create mode 100644 src/cpu.c create mode 100644 src/cpu.h create mode 100644 src/gb_disas.c create mode 100644 src/gb_disas.h create mode 100644 src/gbasm/assemble.c create mode 100644 src/gbasm/assemble.h create mode 100644 src/gbasm/block.h create mode 100644 src/gbasm/emit.c create mode 100644 src/gbasm/emitter.h create mode 100644 src/gbasm/errors.c create mode 100644 src/gbasm/errors.h create mode 100644 src/gbasm/gb_types.h create mode 100644 src/gbasm/opcodes.c create mode 100644 src/gbasm/opcodes.h create mode 100644 src/gbasm/operands.c create mode 100644 src/gbasm/operands.h create mode 100644 src/gbasm/parser.c create mode 100644 src/gbasm/parser.h create mode 100644 src/gbasm/types.h create mode 100644 src/tests/gbasm/fixed.c create mode 100644 src/tests/gbasm/fixed.h create mode 100644 src/tests/gbasm/inc.c create mode 100644 src/tests/gbasm/inc.h create mode 100644 src/tests/gbasm/test.h create mode 100644 src/tri.c create mode 100644 src/tri.h create mode 100644 src/util.h create mode 100644 src/video.c create mode 100644 src/video.h diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..95ac891 --- /dev/null +++ b/Makefile @@ -0,0 +1,81 @@ +OUT=build +SRC_DIR=src +APP_DIR=src/apps/ + +CC=gcc + +CCFLAGS = -Wall -Werror +CCFLAGS += -std=c99 -D_POSIX_C_SOURCE=200809L +CCFLAGS += -I$(SRC_DIR) + +.PHONY: release debug + +CONFIG = $(OUT)/.config.mk + +-include $(CONFIG) + +ifeq ($(DEBUG), 1) +CCFLAGS += -DLOG_LEVEL=5 -DDEBUG +CCFLAGS += -fprofile-arcs -ftest-coverage +CCFLAGS += -O0 -ggdb +LDFLAGS += -lgcov --coverage +endif + +ifeq ($(RELEASE), 1) +CCFLAGS += -O3 +endif + +CCFLAGS += -I/usr/include/glib-2.0/ -I/usr/lib/x86_64-linux-gnu/glib-2.0/include +LDFLAGS += -lglib-2.0 + +C_SOURCES = $(shell find $(SRC_DIR) -name "*.c") +ALL_OBJS = $(patsubst $(SRC_DIR)/%.c, $(OUT)/%.o, $(C_SOURCES)) +APP_OBJS = $(filter $(OUT)/apps/%, $(ALL_OBJS)) +OBJS = $(filter-out $(APP_OBJS), $(ALL_OBJS)) +APPS = $(patsubst %.o, %, $(APP_OBJS)) + +.PHONY: all +all: gbdb gbasm tests + +.PHONY: config-debug config-release config-default +$(CONFIG): + @mkdir -p $(OUT) +config-debug: $(CONFIG) + @echo "DEBUG=1" > $(CONFIG) +config-release: $(CONFIG) + @echo "RELEASE=1" > $(CONFIG) +config-default: $(CONFIG) + @echo "" > $(CONFIG) + + +.PHONY: gbdb gbasm + +gbdb: $(OUT)/apps/gbdb +gbasm: $(OUT)/apps/gbasm + +.PHONY: gbasm-test sample-test tests + +gbasm-test: $(OUT)/apps/gbasm-test +sample-test: $(OUT)/apps/sample-test +tri-test: $(OUT)/apps/tri-test +tests: gbasm-test sample-test tri-test + +$(ALL_APPS) $(ALL_OBJS): $(CONFIG) + +$(OUT)/%.o: $(SRC_DIR)/%.c + @echo " CC $@" + @$(shell mkdir -p $(@D)) + @$(CC) -c $(CCFLAGS) -o $@ $< + +.SECONDEXPANSION: +$(APPS): $(OBJS) $$@.o + @echo " LD $@" + @$(shell mkdir -p $(@D)) + @$(CC) $^ $(LDFLAGS) -o $@ + +.PHONY: clean realclean +clean: + @rm -f $(ALL_OBJS) $(APPS) + +realclean: clean + @rm -f $(CONFIG) diff --git a/README.md b/README.md new file mode 100644 index 0000000..1701755 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +A work-in-progress Gameboy emulator with a terminal-based UI and a basic assembler. + +== Compilation == + +``` +make config-default +make +``` diff --git a/src/apps/gbasm-test.c b/src/apps/gbasm-test.c new file mode 100644 index 0000000..41b6fb0 --- /dev/null +++ b/src/apps/gbasm-test.c @@ -0,0 +1,79 @@ +#include "gbasm/assemble.h" +#include "gbasm/emitter.h" +#include "tests/gbasm/test.h" +#include "tests/gbasm/inc.h" +#include "tests/gbasm/fixed.h" +#include "common.h" + +#include /* memset */ +#include + +const static struct gbasm_tests *tests[] = { + &gbasm_fixed_tests, + &gbasm_inc_tests, +}; + +static int cmp_mem(const uint8_t *test_data, const uint8_t *expected_data, size_t len) +{ + for (size_t i = 0; i < len; i++) { + if (test_data[i] != expected_data[i]) { + g_test_message("memory contents differed at index %zu, data=%x, expected=%x", + i, test_data[i], expected_data[i]); + return -1; + } + } + + return 0; +} + +static void run_test(const void *test_data) +{ + static uint8_t program[4096]; + const struct gbasm_test *test = test_data; + struct emitter emitter; + struct buffer_emitter buffer_emitter; + char *src; + int rc; + size_t len; + + src = strdup(test->asm_source); + + memset(program, 0, sizeof(program)); + buffer_emitter_init(&emitter, &buffer_emitter, program, sizeof(program)); + + rc = gbasm_assemble(src, &emitter); + g_assert_cmpint(rc, ==, 0); + + len = test->expected_output_len; + + if (len != buffer_emitter.cursor) { + g_warning("output lengths were not equal expected %zu, actual %zu", + len, buffer_emitter.cursor); + g_test_fail(); + len = MIN(len, buffer_emitter.cursor); + } + + rc = cmp_mem(program, test->expected_output, len); + g_assert_cmpint(rc, ==, 0); + + free(src); +} + +int main(int argc, char **argv) +{ + char test_name_buff[256]; + + g_test_init(&argc, &argv, NULL); + + for (int i = 0; i < ARRAY_SIZE(tests); i++) { + for (int j = 0; j < tests[i]->num_tests; j++) { + snprintf(test_name_buff, sizeof(test_name_buff), + "/gbasm/%s/%s", + tests[i]->name, + tests[i]->tests[j]->name); + g_test_add_data_func(test_name_buff, tests[i]->tests[j], run_test); + } + } + + return g_test_run(); +} diff --git a/src/apps/gbasm.c b/src/apps/gbasm.c new file mode 100644 index 0000000..922d6de --- /dev/null +++ b/src/apps/gbasm.c @@ -0,0 +1,87 @@ +/* A simple, two-pass gameboy assembler */ + +#include +#include +#include +#include +#include +#include +#include + +#include "gbasm/emitter.h" +#include "gbasm/assemble.h" + +#define GBASM_FILE_BUFFER_SIZE (1024 * 1024) + +static char file_buffer[GBASM_FILE_BUFFER_SIZE]; + +static void usage() +{ + printf("gbasm -i -o \n" + " -h Print this usage \n" + " -i The file to assemble\n" + " -o The file to write the assembled program to\n"); +} + +int main(int argc, char **argv) +{ + struct emitter emitter; + struct fd_emitter fd_emitter; + char opt; + int in_fd = -1; + int out_fd = -1; + + while ((opt = getopt(argc, argv, "i:o:h")) != -1) { + + switch (opt) { + case 'o': + if (out_fd >= 0) { + close(out_fd); + } + + out_fd = open(optarg, O_WRONLY | O_CREAT); + if (out_fd < 0) { + fprintf(stderr, "failed to open output file %s\n", optarg); + exit(1); + } + break; + case 'i': + if (in_fd >= 0) { + close(in_fd); + } + + in_fd = open(optarg, O_RDONLY); + if (in_fd < 0) { + fprintf(stderr, "failed to open input file %s\n", optarg); + exit(1); + } + break; + case 'h': + usage(); + exit(1); + case ':': + fprintf(stderr, "Option -%c requires argument\n", optopt); + usage(); + exit(1); + case '?': + fprintf(stderr, "Unrecognized option -%c\n", optopt); + usage(); + exit(1); + } + } + + if (in_fd < 0) { + exit(1); + } else { + pread(in_fd, file_buffer, GBASM_FILE_BUFFER_SIZE - 1, 0); + file_buffer[GBASM_FILE_BUFFER_SIZE - 1] = '\0'; + } + + if (out_fd < 0) { + exit(1); + } + + fd_emitter_init(&emitter, &fd_emitter, out_fd); + + return gbasm_assemble(file_buffer, &emitter); +} diff --git a/src/apps/gbdb.c b/src/apps/gbdb.c new file mode 100644 index 0000000..cabf6ad --- /dev/null +++ b/src/apps/gbdb.c @@ -0,0 +1,525 @@ +/* + * A CLI-based tester and debugger for the Gameboy's LR35902 CPU + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "gb_disas.h" +#include "cpu.h" +#include "video.h" +#include "tri.h" + +#define INPUT_MAX_LEN 512 +#define MAX_BREAKPTS 256 /* Should be plenty for anyone */ +#define MAX_RAM_LEN (1 << 20) /* Up to 8Mb Cartridge */ +#define UNMAP_BOOTROM_ADDR 0xff50 + +typedef void (gbdb_cmd)(char *arg_string); + +static bool cpu_at_breakpoint(void); + +static uint8_t ram[MAX_RAM_LEN]; +static const char prompt[] = "gbdb >"; +static const char usage[] = + "Available commands:\n" + "load : loads the given file as the gameboy cartridge\n" + "run: runs the CPU until a breakpoint or halt is hit\n" + "break : Adds a breakpoint for the given addess\n" + "step : executes \"cycle\" instructions (default 1)\n" + "regs: dumps the state of the registers\n" + "peek: view the next instruction to run\n" + "exit: quit the program\n"; + +static const char *reg16_names[] = { + "AF", + "BC", + "DE", + "HL", + "SP", + "PC", +}; + +static const char *reg8_names[] = { + "B", + "C", + "D", + "E", + "H", + "L", + "N/A", + "A", + "F" +}; + +static struct lr35902_state cpu; +static struct gb_video video; + +static unsigned char bootrom[0x100] = {0}; + +static int bootrom_mapped = 1; +static volatile sig_atomic_t paused = 0; + + +static void break_execution_handler(int signum) +{ + paused = 1; +} + +/* + * ram_{read,write} work easily because the cartridge is mapped to 0x0000 + * + */ + +static void strip_newline(char *string) +{ + char *pos; + + if (string == NULL) + return; + + if ((pos=strchr(string, '\n')) != NULL) { + *pos = '\0'; + } +} + +static uint8_t mem_read(struct lr35902_state *cpu, uint16_t addr) +{ + + switch (addr) { + case 0 ... 0x100-1: + if (bootrom_mapped) { + return bootrom[addr]; + } else { + return ram[addr]; + } + case 0xFF40 ... 0xFF4B: + return gb_video_mem_read(&video, addr); + default: + return ram[addr]; + } +} + +static void mem_write(struct lr35902_state *cpu, uint16_t addr, uint8_t val) +{ + + switch (addr) { + case UNMAP_BOOTROM_ADDR: + if (val == 1) { + bootrom_mapped = 0; + printf("bootrom unmapped\n"); + } + break; + case 0xFF40 ... 0xFF4B: + gb_video_mem_write(&video, addr, val); + break; + case 0 ... 0x100: + if (bootrom_mapped) { + break; + } + /* Intentional fall-through */ + default: + ram[addr] = val; + } + +} + +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) { + printf("usage: load \n"); + return; + } + strip_newline(token); + dump_file = fopen(token, "w"); + if(dump_file < 0) { + printf("Failed to open mem dump file: %d\n", errno); + return; + } + fwrite(ram, MAX_RAM_LEN, 1, dump_file); + fclose(dump_file); +} + +static void init(void) +{ + lr35902_init(&cpu, mem_read, mem_write); + gb_video_init(&video); + memset(&ram, 0, MAX_RAM_LEN); + bootrom_mapped = 1; +} + +static void cycle() +{ + lr35902_cycle(&cpu); + gb_video_cycle(&video); +} + +static void show_prompt() +{ + printf("%s ", prompt); + fflush(stdout); +} + +static void step(char *arg_list) +{ + uint64_t steps; + int i = 0; + char *token; + token = strtok(arg_list, " "); + if (token == NULL) { + steps = 1; + } else { + strip_newline(token); + steps = strtol(token, NULL, 0); + } + + paused = 0; + signal(SIGINT, break_execution_handler); + + do { + cycle(); + i++; + } while (i < steps && !cpu_at_breakpoint() && !cpu.halted && !paused); + + if (i == steps) { + printf("CPU stopped after %d cycles\n", i); + } else if (cpu_at_breakpoint()) { + printf("Breakpoint hit\n"); + } else if (cpu.halted) { + printf("CPU halted\n"); + } else { + printf("Interrupted after %d cycles\n", i); + } +} + +static void regs(char *arg_list) +{ + int i; + + for(i = 0; i < NUM_LR35902_REGS_8; i++){ + if (i != LR35902_REG_HL_DEREF) { + printf("%s: 0x%02x\n", reg8_names[i], lr35902_get_reg_8(&cpu, i)); + }; + } + for(i = 0; i < NUM_LR35902_REGS_16; i++){ + printf("%s: 0x%04x\n", reg16_names[i], lr35902_get_reg_16(&cpu, i)); + } +} + +static void peek(char *arg_list) +{ + uint16_t pc = lr35902_get_reg_16(&cpu, LR35902_REG_PC); + uint8_t byte = mem_read(&cpu, pc); + printf("0x%04x:%s\n", pc, gb_byte_to_opcode(byte)); +} + +static void stats(char *arg_list) +{ + printf("Cycles: %lu\n", cpu.metrics.cycles); + printf("Retired Insructions %lu\n", cpu.metrics.retired_instrs); + printf("Memory Reads: %lu\n", cpu.metrics.mem_reads); + printf("Memory Writes: %lu\n", cpu.metrics.mem_writes); +} + +static void help(char *arg_list) +{ + printf(usage); +} + +static void mem(char *arg_list) +{ + uint16_t addr, bytes, i; + char *token = strtok(arg_list, " "); + + if (token == NULL) { + printf("usage: mem (num bytes) (format)\n"); + return; + } + + addr = strtol(token, NULL, 0); + token = strtok(NULL, " "); + if (token == NULL) { + bytes = 1; + } else { + bytes = strtol(token, NULL, 0); + } + + token = strtok(NULL, " "); + + for (i = 0; i < bytes; i++) { + //TODO: Make sure this has no side effects + int val = mem_read(&cpu, addr + i); + + if (token != NULL && token[0] == 'i') { + printf("0x%04x:%s\n", addr + i, gb_byte_to_opcode(val)); + } else if (token != NULL && token[0] == 'b') { + printf("0x%04x:0x%02x %s\n", addr + i, val, gb_byte_to_opcode(val)); + } else { + printf("0x%04x:0x%02x\n", addr + i, 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); + fread(buffer, MIN(filesize, size), 1, prog_file); + fclose(prog_file); + +} + +static void load(char *arg_list) +{ + char *token = strtok(arg_list, " "); + if (token == NULL) { + printf("usage: load \n"); + return; + } + + strip_newline(token); + load_file_to_buffer(token, ram, sizeof(ram)); +} + +static void load_bootrom(char *arg_list) +{ + char *token = strtok(arg_list, " "); + if (token == NULL) { + printf("usage: bootrom \n"); + return; + } + + strip_newline(token); + load_file_to_buffer(token, bootrom, sizeof(bootrom)); +} + + +static void quit(char *arg_list) +{ + exit(0); +} + +static void run(char *arg_list) +{ + paused = 0; + signal(SIGINT, break_execution_handler); + while(!cpu.halted && !cpu_at_breakpoint() && !paused) { + cycle(); + } + + if (cpu.halted) { + printf("CPU halted after %ld cycles\n", cpu.metrics.cycles); + } else if (paused) { + printf("Interrupted.\n"); + } else { + printf("Breakpoint hit\n"); + } +} + +static struct { + uint16_t addr; + bool active; +} breakpoints[MAX_BREAKPTS]; + +static void set_breakpoint(char *arg_string) +{ + uint16_t addr, i; + char *token = strtok(arg_string, " "); + + if (token == NULL) { + printf("usage: breakpoint add \n"); + return; + } + + addr = strtol(token, NULL, 0); + + for (i = 0; i < ARRAY_SIZE(breakpoints); i++) { + if (breakpoints[i].active == false) { + breakpoints[i].addr = addr; + breakpoints[i].active = true; + return; + } + } + + printf("maximum number of breakpoints reached\n"); +} + +static void delete_breakpoint(char *arg_string) +{ + int bkpt; + char *token = strtok(arg_string, " "); + + if (token == NULL) { + printf("usage: breakpoint rm \n"); + return; + } + + bkpt = strtol(token, NULL, 0); + + if (bkpt < 0 || bkpt >= ARRAY_SIZE(breakpoints) || !breakpoints[bkpt].active) { + printf("%d is not a valid breakpoint number\n", bkpt); + return; + } + + breakpoints[bkpt].active = false; +} + +static void display_breakpoints(char *arg_string) +{ + bool found1 = false; + int i; + + for (i = 0; i < ARRAY_SIZE(breakpoints); i++) { + if (breakpoints[i].active) { + printf("#%d: 0x%04x\n", i, breakpoints[i].addr); + found1 = true; + } + } + + if(!found1) { + printf("No breakpoints set\n"); + } +} + +static void breakpoint(char *arg_list) +{ + static bool init = false; + static struct tri commands; + + if (!init) { + memset(breakpoints, 0, sizeof(breakpoints)); + tri_init(&commands); + tri_add_string(&commands, "add", set_breakpoint); + tri_add_string(&commands, "set", set_breakpoint); + tri_add_string(&commands, "delete", delete_breakpoint); + tri_add_string(&commands, "remove", delete_breakpoint); + tri_add_string(&commands, "info", display_breakpoints); + tri_add_string(&commands, "display", display_breakpoints); + tri_add_string(&commands, "list", display_breakpoints); + init = true; + } + + char * remainder; + gbdb_cmd *cmd; + bool ambiguous = false; + remainder = strchr(arg_list, ' '); + if (remainder != NULL) { + remainder[0] = '\0'; + remainder++; + } + + cmd = tri_get_string_autocomplete(&commands, arg_list, &ambiguous); + + if(ambiguous) { + printf("ambiguous breakpoint command: '%s'\n", arg_list); + return; + } + + if (cmd == NULL) { + printf("unrecognized breakpoint command: '%s'\n", arg_list); + return; + } + + cmd(remainder); +} + +static bool breakpoint_is_at_addr(uint16_t addr) { + int i; + + for (i = 0; i < ARRAY_SIZE(breakpoints); i++) { + if (breakpoints[i].active && addr == breakpoints[i].addr) { + return true; + } + } + + return false; +} + +static bool cpu_at_breakpoint(void) +{ + return breakpoint_is_at_addr(lr35902_get_reg_16(&cpu, LR35902_REG_PC)); +} + +int main(int argc, char **argv) +{ + struct tri commands; + char line_buffer[INPUT_MAX_LEN]; + char old_buffer[INPUT_MAX_LEN]; + + tri_init(&commands); + tri_add_string(&commands, "step", step); + tri_add_string(&commands, "run", run); + tri_add_string(&commands, "regs", regs); + 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); + tri_add_string(&commands, "dump", mem_dump); + tri_add_string(&commands, "peek", peek); + tri_add_string(&commands, "help", help); + tri_add_string(&commands, "breakpoint", breakpoint); + tri_add_string(&commands, "reset", init); + + init(); + + while (1) { + bool ambiguous; + char *remainder; + gbdb_cmd* cmd; + char *cmd_string; + + show_prompt(); + fgets(line_buffer, INPUT_MAX_LEN, stdin); + if (line_buffer[0] != '\n') { + cmd_string = line_buffer; + } else { + cmd_string = old_buffer; + } + strip_newline(cmd_string); + remainder = strchr(cmd_string, ' '); + if (remainder != NULL) { + remainder[0] = '\0'; + remainder++; + } + + cmd = tri_get_string_autocomplete(&commands, cmd_string, &ambiguous); + + if (ambiguous) { + printf("ambiguous command: '%s'\n", cmd_string); + } else if (cmd == NULL) { + printf("unrecognized command: '%s'\n", cmd_string); + } else { + cmd(remainder); + } + + if (cmd_string == line_buffer) { + strncpy(old_buffer, line_buffer, INPUT_MAX_LEN); + } + } + + return 0; +} diff --git a/src/apps/sample-test.c b/src/apps/sample-test.c new file mode 100644 index 0000000..ed60f0d --- /dev/null +++ b/src/apps/sample-test.c @@ -0,0 +1,20 @@ +#include + +void test_fail(const void *data) +{ + g_test_fail(); +} + +void test_pass(const void *data) +{ +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + g_test_add_data_func("/pass", NULL, test_pass); + g_test_add_data_func("/fail", NULL, test_fail); + + return g_test_run(); +} diff --git a/src/apps/tri-test.c b/src/apps/tri-test.c new file mode 100644 index 0000000..0154bbf --- /dev/null +++ b/src/apps/tri-test.c @@ -0,0 +1,65 @@ +#include "tri.h" +#include "common.h" + +#include +#include + +#define TO_PTR(x) ((void *) (uintptr_t) (x)) +#define TO_INT(x) ((int) (uintptr_t) (x)) + +static void test_tri_get_string(void) +{ + struct tri tri; + + tri_init(&tri); + + g_assert_cmpint(tri_add_string(&tri, "a", TO_PTR(1)), ==, 0); + g_assert_cmpint(tri_add_string(&tri, "ab", TO_PTR(2)), ==, 0); + g_assert_cmpint(tri_add_string(&tri, "abc", TO_PTR(3)), ==, 0); + g_assert_cmpint(tri_add_string(&tri, "af", TO_PTR(4)), ==, 0); + g_assert_cmpint(tri_add_string(&tri, "abd", TO_PTR(5)), ==, 0); + g_assert_cmpint(tri_add_string(&tri, "$a!0123~&.`", TO_PTR(6)), ==, 0); + + g_assert_cmpint(TO_INT(tri_get_string(&tri, "a")), ==, 1); + g_assert_cmpint(TO_INT(tri_get_string(&tri, "ab")), ==, 2); + g_assert_cmpint(TO_INT(tri_get_string(&tri, "abc")), ==, 3); + g_assert_cmpint(TO_INT(tri_get_string(&tri, "af")), ==, 4); + g_assert_cmpint(TO_INT(tri_get_string(&tri, "abd")), ==, 5); + g_assert_cmpint(TO_INT(tri_get_string(&tri, "abz")), ==, 0); + g_assert_cmpint(TO_INT(tri_get_string(&tri, "ag")), ==, 0); + g_assert_cmpint(TO_INT(tri_get_string(&tri, "$a!0123~&.`")), ==, 6); + + tri_free(&tri); +} + +static void test_tri_prefix_match(void) +{ + struct tri tri; + + tri_init(&tri); + + g_assert_cmpint(tri_add_string(&tri, "pr", TO_PTR(1)), ==, 0); + g_assert_cmpint(tri_add_string(&tri, "pref", TO_PTR(2)), ==, 0); + g_assert_cmpint(tri_add_string(&tri, "Z", TO_PTR(3)), ==, 0); + g_assert_cmpint(tri_add_string(&tri, "prefix", TO_PTR(4)), ==, 0); + g_assert_cmpint(tri_add_string(&tri, "blah", TO_PTR(5)), ==, 0); + + g_assert_cmpint(TO_INT(tri_prefix_match(&tri, "p")), ==, 0); + g_assert_cmpint(TO_INT(tri_prefix_match(&tri, "pr")), ==, 1); + g_assert_cmpint(TO_INT(tri_prefix_match(&tri, "pre")), ==, 1); + g_assert_cmpint(TO_INT(tri_prefix_match(&tri, "pref")), ==, 2); + g_assert_cmpint(TO_INT(tri_prefix_match(&tri, "prefix10")), ==, 4); + g_assert_cmpint(TO_INT(tri_prefix_match(&tri, "asdfasdf")), ==, 0); + + tri_free(&tri); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + g_test_add_func("/tri/get_string", test_tri_get_string); + g_test_add_func("/tri/prefix_match", test_tri_prefix_match); + + return g_test_run(); +} diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..74144ee --- /dev/null +++ b/src/common.c @@ -0,0 +1,10 @@ +#include "common.h" + +void downcase(char *str) +{ + for (int i = 0; str[i] != '\0'; i++) { + if (str[i] >= 'A' && str[i] <= 'Z') { + str[i] += 'a' - 'A'; + } + } +} diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..540b19b --- /dev/null +++ b/src/common.h @@ -0,0 +1,53 @@ +#ifndef _COMMON_H_ +#define _COMMON_H_ + +#include +#include + +#define MIN(a,b) ((a) > (b) ? (b) : (a)) +#define MAX(a,b) ((a) < (b) ? (b) : (a)) + +#define ARRAY_SIZE(x) (sizeof((x)) / (sizeof((x)[0]))) + +/* + * Replaces any characters from A-Z with their lowercase counterpart. + * All other characters preceding '\0' are ignored + */ +void downcase(char *str); + +#ifdef DEBUG +#define DEBUG_ON 1 +#else +#define DEBUG_ON 0 +#endif + +#define QUOTE(...) #__VA_ARGS__ + +#define ASSERT(x) \ + if (!(x) && DEBUG_ON) { \ + printf("Assert at: %s:%d <%s> : %s\n", \ + __FILE__, __LINE__, \ + __func__, #x); \ + exit(1); \ + } + +#define ASSERT_MSG(x,...) \ + if(!(x) && DEBUG_ON) { \ + printf("Assert at: %s:%d <%s> : %s\n", \ + __FILE__, __LINE__, \ + __func__, #x); \ + printf(__VA_ARGS__); \ + exit(1); \ + } + +#define DEBUG_LOG(...) \ + do { \ + if (DEBUG_ON) { \ + printf("DEBUG: %s:%d <%s> : ", \ + __FILE__, __LINE__, \ + __func__); \ + printf(__VA_ARGS__); \ + } \ + } while (0) + +#endif diff --git a/src/cpu.c b/src/cpu.c new file mode 100644 index 0000000..bee043f --- /dev/null +++ b/src/cpu.c @@ -0,0 +1,1459 @@ +/* + * Author: Max Regan + * Last Modified: 11-17-2015 + */ + +#include +#include + +#include "cpu.h" +#include "common.h" + +#define WRITE_BIT(x, idx, bit) \ + do {(x) &= (~(1 << (idx)) | ((bit) << (idx)));} while(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) + +/*TODO: Timings are all totally boned */ +/* Primary instructions */ +static void nop(struct lr35902_state *cpu, uint8_t instr); +static void ld(struct lr35902_state *cpu, uint8_t instr); +static void ld_deref(struct lr35902_state *cpu, uint8_t instr); +static void ld_into_deref(struct lr35902_state *cpu, uint8_t instr); +static void ld_d8(struct lr35902_state *cpu, uint8_t instr); +static void ld_d16(struct lr35902_state *cpu, uint8_t instr); +static void ld_a16(struct lr35902_state *cpu, uint8_t instr); +static void ld_c_offset(struct lr35902_state *cpu, uint8_t instr); +static void ld_hl_d16(struct lr35902_state *cpu, uint8_t inst); +static void inc(struct lr35902_state *cpu, uint8_t instr); +static void inc_16(struct lr35902_state *cpu, uint8_t instr); +static void dec(struct lr35902_state *cpu, uint8_t instr); +static void dec_16(struct lr35902_state *cpu, uint8_t instr); +static void rlca(struct lr35902_state *cpu, uint8_t instr); +static void add(struct lr35902_state *cpu, uint8_t instr); +static void rrca(struct lr35902_state *cpu, uint8_t instr); +static void stop(struct lr35902_state *cpu, uint8_t instr); +static void rla(struct lr35902_state *cpu, uint8_t instr); +static void jr(struct lr35902_state *cpu, uint8_t instr); +static void jr_cond(struct lr35902_state *cpu, uint8_t instr); +static void rra(struct lr35902_state *cpu, uint8_t instr); +static void daa(struct lr35902_state *cpu, uint8_t instr); +static void cpl(struct lr35902_state *cpu, uint8_t instr); +static void scf(struct lr35902_state *cpu, uint8_t instr); +static void ccf(struct lr35902_state *cpu, uint8_t instr); +static void halt(struct lr35902_state *cpu, uint8_t instr); +static void adc(struct lr35902_state *cpu, uint8_t instr); +static void sub(struct lr35902_state *cpu, uint8_t instr); +static void sbc(struct lr35902_state *cpu, uint8_t instr); +static void and(struct lr35902_state *cpu, uint8_t instr); +static void xor(struct lr35902_state *cpu, uint8_t instr); +static void or(struct lr35902_state *cpu, uint8_t instr); +static void cp(struct lr35902_state *cpu, uint8_t instr); +static void ret(struct lr35902_state *cpu, uint8_t instr); +static void pop(struct lr35902_state *cpu, uint8_t instr); +static void jp(struct lr35902_state *cpu, uint8_t instr); +static void jp_a16(struct lr35902_state *cpu, uint8_t instr); +static void jp_d8(struct lr35902_state *cpu, uint8_t instr); +static void call(struct lr35902_state *cpu, uint8_t instr); +static void push(struct lr35902_state *cpu, uint8_t instr); +static void add_d8(struct lr35902_state *cpu, uint8_t instr); +static void rst(struct lr35902_state *cpu, uint8_t instr); +static void cb_prefix(struct lr35902_state *cpu, uint8_t instr); +static void adc_d8(struct lr35902_state *cpu, uint8_t instr); +static void undef(struct lr35902_state *cpu, uint8_t instr); +static void sub_d8(struct lr35902_state *cpu, uint8_t instr); +static void reti(struct lr35902_state *cpu, uint8_t instr); +static void sbc_d8(struct lr35902_state *cpu, uint8_t instr); +static void ldh(struct lr35902_state *cpu, uint8_t instr); +static void and_d8(struct lr35902_state *cpu, uint8_t instr); +static void xor_d8(struct lr35902_state *cpu, uint8_t instr); +static void di(struct lr35902_state *cpu, uint8_t instr); +static void or_d8(struct lr35902_state *cpu, uint8_t instr); +static void ei(struct lr35902_state *cpu, uint8_t instr); +static void cp_d8(struct lr35902_state *cpu, uint8_t instr); + +/* C-u M-x align-regexp \(,\) RET RET y */ +lr35902_instr lr35902_instrs[] = { + /*0x0?*/ + nop, + ld_d16, + ld_into_deref, + inc_16, + inc, + dec, + ld_d8, + rlca, + ld, + add, + ld, + dec_16, + inc, + dec, + ld_d8, + rrca, + /*0x1?*/ + stop, + ld_d16, + ld_into_deref, + inc_16, + inc, + dec, + ld_d8, + rla, + jr, + add, + ld_deref, + dec_16, + inc, + dec, + ld_d8, + rra, + /*0x2?*/ + jr_cond, + ld_d16, + ld_deref, + inc_16, + inc, + dec, + ld_d8, + daa, + jr_cond, + add, + ld_hl_d16, + dec_16, + inc, + dec, + ld_d8, + cpl, + /*0x3?*/ + jr_cond, + ld_d16, + ld_deref, + inc_16, + inc, + dec, + ld_d8, + scf, + jr_cond, + add, + ld, + dec_16, + inc, + dec, + ld_d8, + ccf, + /*0x4?*/ + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + /*0x5?*/ + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + /*0x6?*/ + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + /*0x7?*/ + ld, + ld, + ld, + ld, + ld, + ld, + halt, + ld_into_deref, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + ld, + /*0x8?*/ + add, + add, + add, + add, + add, + add, + add, + add, + adc, + adc, + adc, + adc, + adc, + adc, + adc, + adc, + /*0x9?*/ + sub, + sub, + sub, + sub, + sub, + sub, + sub, + sub, + sbc, + sbc, + sbc, + sbc, + sbc, + sbc, + sbc, + sbc, + /*0xA?*/ + and, + and, + and, + and, + and, + and, + and, + and, + xor, + xor, + xor, + xor, + xor, + xor, + xor, + xor, + /*0xB?*/ + or, + or, + or, + or, + or, + or, + or, + or, + cp, + cp, + cp, + cp, + cp, + cp, + cp, + cp, + /*0xC?*/ + ret, + pop, + jp_a16, + jp_d8, + call, + push, + add_d8, + rst, + ret, + ret, + jp_a16, + cb_prefix, + call, + call, + adc_d8, + rst, + /*0xD?*/ + ret, + pop, + jp_a16, + undef, + call, + push, + sub_d8, + rst, + ret, + reti, + jp, + undef, + call, + undef, + sbc_d8, + rst, + /*0xE?*/ + ldh, + pop, + ld_c_offset, + undef, + undef, + push, + and_d8, + rst, + add, + jp, + ld_a16, + undef, + undef, + undef, + xor_d8, + rst, + /*0xF?*/ + ldh, + pop, + ld_c_offset, + di, + undef, + push, + or_d8, + rst, + ld, + ld, + ld_a16, + ei, + undef, + undef, + cp_d8, + rst, +}; + +/* CB-prefix functions*/ +static void rlc(struct lr35902_state *cpu, uint8_t instr); +static void rrc(struct lr35902_state *cpu, uint8_t instr); +static void rl(struct lr35902_state *cpu, uint8_t instr); +static void rr(struct lr35902_state *cpu, uint8_t instr); +static void sla(struct lr35902_state *cpu, uint8_t instr); +static void sra(struct lr35902_state *cpu, uint8_t instr); +static void swap(struct lr35902_state *cpu, uint8_t instr); +static void srl(struct lr35902_state *cpu, uint8_t instr); +static void bit(struct lr35902_state *cpu, uint8_t instr); +static void res(struct lr35902_state *cpu, uint8_t instr); +static void set(struct lr35902_state *cpu, uint8_t instr); + + +static lr35902_instr lr35902_cb_instrs[] = { + /*0x0?*/ rlc, rrc, + /*0x1?*/ rl, rr, + /*0x2?*/ sla, sra, + /*0x3?*/ swap, srl, + /*0x4?*/ bit, bit, + /*0x5?*/ bit, bit, + /*0x6?*/ bit, bit, + /*0x7?*/ bit, bit, + /*0x8?*/ res, res, + /*0x9?*/ res, res, + /*0xA?*/ res, res, + /*0xB?*/ res, res, + /*0xC?*/ set, set, + /*0xD?*/ set, set, + /*0xE?*/ set, set, + /*0xF?*/ set, set, +}; + +uint16_t lr35902_get_reg_16(struct lr35902_state *cpu, lr35902_regs_16 reg) +{ + ASSERT(reg >= 0); + ASSERT_MSG(reg < NUM_LR35902_REGS_16, "reg=%d\n", reg); + return cpu->regs_16[reg]; +} + +uint8_t lr35902_get_reg_8(struct lr35902_state *cpu, lr35902_regs_8 reg) +{ + ASSERT(reg != LR35902_REG_HL_DEREF); + return cpu->regs_8[reg]; +} + +void lr35902_set_reg_16(struct lr35902_state *cpu, lr35902_regs_16 reg, + uint16_t val) +{ + cpu->regs_16[reg] = val; +} + +void lr35902_set_reg_8(struct lr35902_state *cpu, lr35902_regs_8 reg, + uint8_t val) +{ + ASSERT(reg < NUM_LR35902_REGS_8); + cpu->regs_8[reg] = val; +} + + +static void _incr_pc(struct lr35902_state *cpu) +{ + cpu->pc++; +} + +/* + * Runs any deferred events if it is their time to run + */ +static void _run_events(struct lr35902_state *cpu) +{ + int i; + for (i = 0; i < LR35902_MAX_EVENTS; i++) { + if (cpu->events[i].cycles == 1) { + cpu->events[i].run(cpu); + } + if (cpu->events[i].cycles > 0) { + cpu->events[i].cycles--; + } + } +} + +static void _schedule_event(struct lr35902_state *cpu, + lr35902_event_fn fn, uint8_t cycles) +{ + int i; + for (i = 0; i < LR35902_MAX_EVENTS; i++) { + if (cpu->events[i].cycles == 0) { + cpu->events[i].cycles = cycles; + cpu->events[i].run = fn; + return; + } + } + + _run_events(cpu); + cpu->halted=1; +} + +static void _enable_interrupts(struct lr35902_state *cpu) +{ + cpu->int_state = LR35902_INT_ON; +} + +static void _disable_interrupts(struct lr35902_state *cpu) +{ + cpu->int_state = LR35902_INT_OFF; +} + +static void _push_stack_8(struct lr35902_state *cpu, uint8_t val) +{ + cpu->mem_write(cpu, cpu->sp, val); + cpu->sp--; +} + +static void _push_stack_16(struct lr35902_state *cpu, uint16_t val) +{ + _push_stack_8(cpu, val >> 8); + _push_stack_8(cpu, val & 0xFF); +} + +static uint8_t _pop_stack_8(struct lr35902_state *cpu) +{ + uint8_t ret; + cpu->sp++; + ret = cpu->mem_read(cpu, cpu->sp); + lr35902_set_reg_16(cpu, LR35902_REG_SP, cpu->sp); + return ret; +} + +static uint16_t _pop_stack_16(struct lr35902_state *cpu) +{ + uint16_t val; + val = _pop_stack_8(cpu); + val |= _pop_stack_8(cpu) << 8; + + return val; +} + +void lr35902_init(struct lr35902_state *cpu, + lr35902_mem_read_fn mem_read, + lr35902_mem_write_fn mem_write) +{ + int i; + + cpu->mem_read = mem_read; + cpu->mem_write = mem_write; + + cpu->int_state = LR35902_INT_OFF; + cpu->stall_cycles = 0; + cpu->halted = 0; + + for (i = 0; i < ARRAY_SIZE(cpu->regs_16); i++) { + cpu->regs_16[i] = 0; + } + + for (i = 0; i < ARRAY_SIZE(cpu->events); i++) { + cpu->events[i].cycles = 0; + cpu->events[i].run = NULL; + } + + cpu->metrics.cycles = 0; + cpu->metrics.retired_instrs = 0; + cpu->metrics.mem_reads = 0; + cpu->metrics.mem_writes = 0; +} + +void lr35902_cycle(struct lr35902_state *cpu) +{ + uint8_t instr = cpu->mem_read(cpu, cpu->pc); + cpu->metrics.cycles++; + if(cpu->stall_cycles > 0){ + cpu->stall_cycles--; + return; + } + + lr35902_instrs[instr](cpu, instr); + cpu->metrics.retired_instrs++; + cpu->pc++; + _run_events(cpu); + return; +} + +static uint8_t _get_reg_8(struct lr35902_state *cpu, uint8_t reg){ + if (reg == LR35902_REG_HL_DEREF){ + return cpu->mem_read(cpu, lr35902_get_reg_16(cpu, LR35902_REG_HL)); + } else { + return cpu->regs_8[reg]; + } +} + +static void _set_reg_8(struct lr35902_state *cpu, uint8_t reg, uint8_t val){ + if (reg == LR35902_REG_HL_DEREF){ + cpu->mem_write(cpu, lr35902_get_reg_16(cpu, LR35902_REG_HL), val); + } else { + cpu->regs_8[reg] = val; + } +} + +/************************/ +/* Primary instructions */ +/************************/ +static void nop(struct lr35902_state *cpu, uint8_t instr) +{ + return; +} + +static void ld(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val; + uint8_t src_reg, dst_reg; + + if (!(instr > 0x40 && instr <= 0x7F)) { + cpu->halted = 1; + return; + } + + dst_reg = (instr >> 3) & 7; + src_reg = instr & 7; + + val = _get_reg_8(cpu, src_reg); + _set_reg_8(cpu, dst_reg, val); +} + +static void ld_deref(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val; + int incr = 0; + uint16_t hl_val; + + switch(instr) { + /** Copy to Registers **/ + case 0x0A: + cpu->a = cpu->mem_read(cpu, cpu->bc); + break; + case 0x1A: + cpu->a = cpu->mem_read(cpu, cpu->de); + break; + case 0x2A: + incr = 1; + /* Intentional fall-through */ + case 0x3A: + hl_val = lr35902_get_reg_16(cpu, LR35902_REG_HL); + val = cpu->mem_read(cpu, hl_val); + lr35902_set_reg_8(cpu, LR35902_REG_A, val); + + if (incr) { + hl_val++; + } else { + hl_val--; + } + + lr35902_set_reg_16(cpu, LR35902_REG_HL, hl_val); + break; + + /** Copy to memory **/ + case 0x22: + incr = 1; + /* Intentional fall-through */ + case 0x32: + hl_val = cpu->hl; + val = cpu->a; + cpu->mem_write(cpu, hl_val, val); + + if (incr) { + hl_val++; + } else { + hl_val--; + } + + cpu->hl = hl_val; + break; + case 0x12: + cpu->mem_write(cpu, cpu->de, cpu->a); + break; + case 0x02: + cpu->mem_write(cpu, cpu->bc, cpu->a); + break; + default: + cpu->halted = 1; + } + +} + +static void ld_into_deref(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg, a; + uint16_t addr; + + switch (instr) { + case 0x02: reg = LR35902_REG_BC; break; + case 0x12: reg = LR35902_REG_DE; break; + case 0x77: reg = LR35902_REG_HL; break; + default: + ASSERT(0); + return; + } + + a = lr35902_get_reg_8(cpu, LR35902_REG_A); + addr = lr35902_get_reg_16(cpu, reg); + cpu->mem_write(cpu, addr, a); +} + +static void ld_d8(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = (instr >> 3) & 0x7; + uint8_t val; + _incr_pc(cpu); + val = cpu->mem_read(cpu, lr35902_get_reg_16(cpu, LR35902_REG_PC)); + _set_reg_8(cpu, reg, val); +} + +static void ld_d16(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg; + uint16_t val; + + switch(instr) { + case 0x01: reg = LR35902_REG_BC; break; + case 0x11: reg = LR35902_REG_DE; break; + case 0x21: reg = LR35902_REG_HL; break; + case 0x31: reg = LR35902_REG_SP; break; + default: cpu->halted=1; return; + } + + val = cpu->mem_read(cpu, ++cpu->pc); + val |= cpu->mem_read(cpu, ++cpu->pc) << 8; + + lr35902_set_reg_16(cpu, reg, val); +} + +static void ld_a16(struct lr35902_state *cpu, uint8_t instr) +{ + uint16_t a = 0; + _incr_pc(cpu); + a = cpu->mem_read(cpu, lr35902_get_reg_16(cpu, LR35902_REG_PC)); + _incr_pc(cpu); + a |= cpu->mem_read(cpu, lr35902_get_reg_16(cpu, LR35902_REG_PC)) << 8; + + if(instr == 0xEA) { + /* Store */ + uint16_t val = lr35902_get_reg_8(cpu, LR35902_REG_A); + cpu->mem_write(cpu, a, val); + } else { + /* Load */ + lr35902_set_reg_8(cpu, LR35902_REG_A, cpu->mem_read(cpu, a)); + } + +} + +static void ld_c_offset(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val = 0; + uint16_t addr = 0; + + addr = lr35902_get_reg_8(cpu, LR35902_REG_C); + addr |= 0xff00; + + switch (instr) { + case 0xf2: + //LD A, ($FF00 + C) + val = cpu->mem_read(cpu, addr); + lr35902_set_reg_8(cpu, LR35902_REG_A, val); + break; + case 0xe2: + //LD ($FF00 + C), A + val = lr35902_get_reg_8(cpu, LR35902_REG_A); + cpu->mem_write(cpu, addr, val); + break; + default: + ASSERT(0); + } + + +} + +static void ld_hl_d16(struct lr35902_state *cpu, uint8_t inst) +{ + uint16_t addr = lr35902_get_reg_16(cpu, LR35902_REG_HL); + uint8_t val = cpu->mem_read(cpu, addr); + + _set_reg_8(cpu, LR35902_REG_A, val); + addr++; + lr35902_set_reg_16(cpu, LR35902_REG_HL, addr); +} + + +static void inc_16(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg; + uint16_t val; + + switch (instr) { + case 0x03: reg = LR35902_REG_BC; break; + case 0x13: reg = LR35902_REG_DE; break; + case 0x23: reg = LR35902_REG_HL; break; + case 0x33: reg = LR35902_REG_SP; break; + default: + ASSERT(0); + return; + } + + val = lr35902_get_reg_16(cpu, reg); + val++; + lr35902_set_reg_16(cpu, reg, val); + +} + +static void inc(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = (instr >> 3) & 0x7; + uint8_t val = _get_reg_8(cpu, reg); + val++; + _set_reg_8(cpu, reg, val); + +} + +static void dec(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = (instr >> 3) & 0x7; + uint8_t val = _get_reg_8(cpu, reg); + val--; + _set_reg_8(cpu, reg, val); + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, val == 0); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_N, 1); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_H, (val == 0xFF)); +} + +static void dec_16(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = (instr & 0xF0) >> 8; + uint16_t val; + + switch (reg) { + case 0: reg = LR35902_REG_BC; break; + case 1: reg = LR35902_REG_DE; break; + case 2: reg = LR35902_REG_HL; break; + case 3: reg = LR35902_REG_SP; break; + default: + printf("reg: %d\n", reg); + ASSERT(0); + } + + val = lr35902_get_reg_16(cpu, reg); + val--; + lr35902_set_reg_16(cpu, reg, val); + +} + + +static void rlca(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = LR35902_REG_A; + uint8_t val = _get_reg_8(cpu, reg); + uint8_t new_val = 0; + + new_val = (val << 1); + new_val |= GET_BIT(val, 7); + _set_reg_8(cpu, reg, new_val); + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, (new_val == 0)); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_C, GET_BIT(val, 7)); +} + +static void rrca(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = LR35902_REG_A; + uint8_t val = _get_reg_8(cpu, reg); + uint8_t new_val = 0; + + new_val = (val >> 1); + _set_reg_8(cpu, reg, new_val); + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, (new_val == 0)); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_C, GET_BIT(val, 7)); +} + +static void rla(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = LR35902_REG_A; + uint8_t val = _get_reg_8(cpu, reg); + uint8_t new_val = 0; + + new_val = (val << 1); + new_val |= GET_BIT(val, 7); + _set_reg_8(cpu, reg, new_val); + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, (new_val == 0)); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_C, GET_BIT(val, 7)); +} + +static void rra(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = LR35902_REG_A; + uint8_t val = _get_reg_8(cpu, reg); + uint8_t new_val = 0; + + new_val = (val >> 1); + _set_reg_8(cpu, reg, new_val); + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, (new_val == 0)); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_C, GET_BIT(val, 7)); +} + + +static void add(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val_a = _get_reg_8(cpu, LR35902_REG_A); + uint8_t val = _get_reg_8(cpu, instr & 7); + uint8_t res = val + val_a; + _set_reg_8(cpu, LR35902_REG_A, (uint8_t) res); + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, res == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_H, GET_BIT(val_a, 8)); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_C, GET_BIT(res, 4)); +} + +static void add_d8(struct lr35902_state *cpu, uint8_t instr) +{ + cpu->halted=1; +} + +static void adc(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val_a = _get_reg_8(cpu, LR35902_REG_A); + uint8_t val = _get_reg_8(cpu, instr & 7); + uint16_t res = val + val_a + cpu->c; + _set_reg_8(cpu, LR35902_REG_A, (uint8_t) res); + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, res == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_H, GET_BIT(val_a, 8)); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_C, GET_BIT(res, 4)); +} + +static void adc_d8(struct lr35902_state *cpu, uint8_t instr) +{ + cpu->halted=1; +} + +static void sub(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val_a = _get_reg_8(cpu, LR35902_REG_A); + uint8_t val = _get_reg_8(cpu, instr & 7); + val_a -= val; + _set_reg_8(cpu, LR35902_REG_A, val_a); + + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, val_a == 0); + SET_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); //TODO: No borrow from bit 4 + CLR_BIT(cpu->f, LR35902_FLAG_BIT_C); //TODO: No borrow +} + +static void sub_d8(struct lr35902_state *cpu, uint8_t instr) +{ + cpu->halted=1; +} + +static void sbc(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val_a = _get_reg_8(cpu, LR35902_REG_A); + uint8_t val = _get_reg_8(cpu, instr & 7); + val_a -= val + cpu->c; + _set_reg_8(cpu, LR35902_REG_A, val_a); + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, val_a == 0); + SET_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); //TODO: No borrow from bit 4 + CLR_BIT(cpu->f, LR35902_FLAG_BIT_C); //TODO: No borrow +} + +static void sbc_d8(struct lr35902_state *cpu, uint8_t instr) +{ + cpu->halted=1; +} + +static void and(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val_a = _get_reg_8(cpu, LR35902_REG_A); + uint8_t val = _get_reg_8(cpu, instr & 7); + val_a &= val; + _set_reg_8(cpu, LR35902_REG_A, val_a); + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, val == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + SET_BIT(cpu->f, LR35902_FLAG_BIT_H); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_C); +} + +static void and_d8(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val; + _incr_pc(cpu); + val = cpu->mem_read(cpu, lr35902_get_reg_16(cpu, LR35902_REG_PC)); + val &= lr35902_get_reg_8(cpu, LR35902_REG_A); + _set_reg_8(cpu, LR35902_REG_A, val); + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, val == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + SET_BIT(cpu->f, LR35902_FLAG_BIT_H); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_C); +} + +static void xor(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val_a = _get_reg_8(cpu, LR35902_REG_A); + uint8_t val = _get_reg_8(cpu, instr & 7); + val_a ^= val; + _set_reg_8(cpu, LR35902_REG_A, val_a); + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, val_a == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_C); +} + +static void xor_d8(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val; + _incr_pc(cpu); + val = cpu->mem_read(cpu, lr35902_get_reg_16(cpu, LR35902_REG_PC)); + val ^= lr35902_get_reg_8(cpu, LR35902_REG_A); + _set_reg_8(cpu, LR35902_REG_A, val); + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, val == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_C); +} + +static void or(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val_a = _get_reg_8(cpu, LR35902_REG_A); + uint8_t val = _get_reg_8(cpu, instr & 7); + val_a |= val; + _set_reg_8(cpu, LR35902_REG_A, val_a); + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, val_a == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_C); +} + +static void or_d8(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val; + _incr_pc(cpu); + val = cpu->mem_read(cpu, lr35902_get_reg_16(cpu, LR35902_REG_PC)); + val |= lr35902_get_reg_8(cpu, LR35902_REG_A); + _set_reg_8(cpu, LR35902_REG_A, val); + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, val == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_C); +} + +static void cp(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val_a = _get_reg_8(cpu, LR35902_REG_A); + uint8_t val = _get_reg_8(cpu, instr & 7); + val_a -= val; + /* Just test, ignore result */ + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, val_a == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_C); +} + +static void cp_d8(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val_a = _get_reg_8(cpu, LR35902_REG_A); + _incr_pc(cpu); + uint16_t pc = lr35902_get_reg_16(cpu, LR35902_REG_PC); + uint8_t val = cpu->mem_read(cpu, pc); + val_a -= val; + + /* Just test, ignore result */ + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, val_a == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_C); +} + +static void stop(struct lr35902_state *cpu, uint8_t instr) +{ + cpu->halted=1; +} + +static void jr(struct lr35902_state *cpu, uint8_t instr) +{ + uint16_t pc = lr35902_get_reg_16(cpu, LR35902_REG_PC); + uint8_t off = cpu->mem_read(cpu, pc + 1); + uint16_t off16; + + if (GET_BIT(off, 7)) { + off16 = (uint16_t) 0xFF00 | off; + } else { + off16 = off; + } + + pc += off16; + lr35902_set_reg_16(cpu, LR35902_REG_PC, pc); + _incr_pc(cpu); + +} + +static void jr_cond(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t flag = (instr >> 3) & 3; + uint16_t pc = lr35902_get_reg_16(cpu, LR35902_REG_PC); + uint16_t offset = cpu->mem_read(cpu, pc + 1); + if (GET_BIT(offset, 7)) { + offset |= 0xFF00; /* Sign extend */ + } + + switch(flag) { + case 0: + flag = GET_BIT(cpu->f, LR35902_FLAG_BIT_Z) == 0; break; + case 1: + flag = GET_BIT(cpu->f, LR35902_FLAG_BIT_Z) == 1; break; + case 2: + flag = GET_BIT(cpu->f, LR35902_FLAG_BIT_C) == 0; break; + case 3: + flag = GET_BIT(cpu->f, LR35902_FLAG_BIT_C) == 1; break; + default: + cpu->halted=1; + } + + if(flag) { + pc += offset; + lr35902_set_reg_16(cpu, LR35902_REG_PC, pc); + } + _incr_pc(cpu); +} + +static void daa(struct lr35902_state *cpu, uint8_t instr) +{ + cpu->halted=1; +} + +static void cpl(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t val = _get_reg_8(cpu, LR35902_REG_A); + val = ~val; + _set_reg_8(cpu, LR35902_REG_A, val); + + SET_BIT(cpu->f, LR35902_FLAG_BIT_N); + SET_BIT(cpu->f, LR35902_FLAG_BIT_H); +} + +static void scf(struct lr35902_state *cpu, uint8_t instr) +{ + SET_BIT(cpu->f, LR35902_FLAG_BIT_C); +} + +static void ccf(struct lr35902_state *cpu, uint8_t instr) +{ + cpu->f ^= 1 << LR35902_FLAG_BIT_C; +} + +static void halt(struct lr35902_state *cpu, uint8_t instr) +{ + cpu->halted = 1; +} + +static void ret(struct lr35902_state *cpu, uint8_t instr) +{ + uint16_t jmp_addr = _pop_stack_16(cpu); + + lr35902_set_reg_16(cpu, LR35902_REG_PC, jmp_addr - 1); +} + +static void pop(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg; + uint16_t val; + + switch(instr) { + case 0xC1: reg = LR35902_REG_BC; break; + case 0xD1: reg = LR35902_REG_DE; break; + case 0xE1: reg = LR35902_REG_HL; break; + case 0xF1: reg = LR35902_REG_AF; break; + default: + ASSERT(0); + return; + } + + val = _pop_stack_16(cpu); + + lr35902_set_reg_16(cpu, reg, val); +} + +static void jp(struct lr35902_state *cpu, uint8_t instr) +{ + cpu->halted = 1; +} + +static void jp_d8(struct lr35902_state *cpu, uint8_t instr) +{ + uint16_t pc = lr35902_get_reg_16(cpu, LR35902_REG_PC); + uint8_t jp_low = cpu->mem_read(cpu, pc + 1); + uint8_t jp_hi = cpu->mem_read(cpu, pc + 2); + uint16_t jp_addr = (jp_hi << 8) | jp_low; + + lr35902_set_reg_16(cpu, LR35902_REG_PC, jp_addr - 1); //TODO: This is a hack +} + +static void jp_a16(struct lr35902_state *cpu, uint8_t instr) +{ + uint16_t addr; + uint8_t cond; + + switch (instr) { + case 0xC2: cond = !GET_BIT(cpu->f, LR35902_FLAG_BIT_Z); break; /* JP NZ */ + case 0xD2: cond = !GET_BIT(cpu->f, LR35902_FLAG_BIT_C); break; /* JP NC */ + case 0xCA: cond = GET_BIT(cpu->f, LR35902_FLAG_BIT_Z); break; /* JP Z */ + case 0xDA: cond = GET_BIT(cpu->f, LR35902_FLAG_BIT_C); break; /* JP C */ + case 0xC3: cond = 1; break; /* JP */ + default: + ASSERT(0); + return; + } + + addr = cpu->mem_read(cpu, LR35902_REG_PC); + _incr_pc(cpu); + addr |= cpu->mem_read(cpu, LR35902_REG_PC) << 8; + _incr_pc(cpu); + + if (cond) { + lr35902_set_reg_16(cpu, LR35902_REG_PC, addr - 1); //TODO: This is a hack + } +} + +static void call(struct lr35902_state *cpu, uint8_t instr) +{ + uint16_t jmp_addr; + _push_stack_16(cpu, lr35902_get_reg_16(cpu, LR35902_REG_PC) + 3); + _incr_pc(cpu); + jmp_addr = cpu->mem_read(cpu, lr35902_get_reg_16(cpu, LR35902_REG_PC)); + _incr_pc(cpu); + jmp_addr |= (cpu->mem_read(cpu, lr35902_get_reg_16(cpu, LR35902_REG_PC))) << 8; + + lr35902_set_reg_16(cpu, LR35902_REG_PC, jmp_addr - 1); +} + +static void push(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = (instr >> 5) & 7; + uint16_t val; + + switch(instr) { + case 0xF5: reg = LR35902_REG_AF; break; + case 0xC5: reg = LR35902_REG_BC; break; + case 0xD5: reg = LR35902_REG_DE; break; + case 0xE5: reg = LR35902_REG_HL; break; + default: + ASSERT(0); + } + + val = lr35902_get_reg_16(cpu, reg); + _push_stack_16(cpu, val); +} + +static void rst(struct lr35902_state *cpu, uint8_t instr) +{ + cpu->halted=1; +} + +static void cb_prefix(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t cb_instr; + _incr_pc(cpu); + cb_instr = cpu->mem_read(cpu, lr35902_get_reg_16(cpu, LR35902_REG_PC)); + lr35902_cb_instrs[cb_instr >> 3](cpu, cb_instr); + +} + +static void undef(struct lr35902_state *cpu, uint8_t instr) +{ + cpu->halted=1; +} + +static void reti(struct lr35902_state *cpu, uint8_t instr) +{ + cpu->halted=1; +} + +static void ldh(struct lr35902_state *cpu, uint8_t instr) +{ + //TODO: This is the blocking one (0xE0) + uint16_t addr = 0; + _incr_pc(cpu); + addr = cpu->mem_read(cpu, lr35902_get_reg_16(cpu, LR35902_REG_PC)); + addr += 0xFF00; + + if(instr == 0xE0) { + /* Store */ + uint16_t val_a = lr35902_get_reg_8(cpu, LR35902_REG_A); + uint8_t val = (uint8_t) val_a & 0xFF; + cpu->mem_write(cpu, addr, val); + } else if (instr == 0xF0) { + /* Load */ + uint8_t val = cpu->mem_read(cpu, addr); + uint16_t val_a = val; + lr35902_set_reg_8(cpu, LR35902_REG_A, val_a); + } else { + cpu->halted = 1; + } + +} + +static void di(struct lr35902_state *cpu, uint8_t instr) +{ + _schedule_event(cpu, _disable_interrupts, 2); +} + +static void ei(struct lr35902_state *cpu, uint8_t instr) +{ + _schedule_event(cpu, _enable_interrupts, 2); +} + + + +/**************************/ +/* CB-prefix instructions */ +/**************************/ +static void rlc(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = instr & 7; + uint8_t val = _get_reg_8(cpu, reg); + uint8_t new_val = 0; + + new_val = (val << 1); + new_val |= GET_BIT(val, 7); + _set_reg_8(cpu, reg, new_val); + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, new_val == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_C); +} + +static void rrc(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = instr & 7; + uint8_t val = _get_reg_8(cpu, reg); + uint8_t new_val = 0; + + new_val = (val >> 1); + _set_reg_8(cpu, reg, new_val); + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, new_val == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_C, GET_BIT(val, 0)); +} + +static void rl(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = instr & 7; + uint8_t val = _get_reg_8(cpu, reg); + uint8_t new_val = 0; + + new_val = (val << 1); + new_val |= GET_BIT(val, 7); + _set_reg_8(cpu, reg, new_val); + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, new_val == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_C, GET_BIT(val, 7)); +} + +static void rr(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = instr & 7; + uint8_t val = _get_reg_8(cpu, reg); + uint8_t new_val = 0; + + new_val = (val >> 1); + _set_reg_8(cpu, reg, new_val); + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, new_val == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_C, GET_BIT(val, 0)); +} + +static void sla(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = instr & 7; + uint8_t val = _get_reg_8(cpu, reg); + uint8_t new_val = 0; + + new_val = (val << 1); + _set_reg_8(cpu, reg, new_val); + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, new_val == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_C, GET_BIT(val, 7)); +} + +static void sra(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = instr & 7; + uint8_t val = _get_reg_8(cpu, reg); + uint8_t new_val = 0; + + new_val = (val >> 1); + WRITE_BIT(new_val, 7, GET_BIT(val, 7)); + _set_reg_8(cpu, reg, new_val); + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, new_val == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_C, GET_BIT(val, 0)); +} + +static void swap(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = instr & 7; + uint8_t val = _get_reg_8(cpu, reg); + uint8_t new_val = 0; + + new_val = (val >> 4) | (val << 4); + _set_reg_8(cpu, reg, new_val); + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, new_val == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_C); +} + +static void srl(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = instr & 7; + uint8_t val = _get_reg_8(cpu, reg); + uint8_t new_val = 0; + + new_val = (val >> 1); + _set_reg_8(cpu, reg, new_val); + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, new_val == 0); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_H); + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_C, GET_BIT(val, 0)); +} + +static void bit(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = instr & 7; + uint8_t bit = (instr >> 3) & 7; + uint8_t val = _get_reg_8(cpu, reg); + + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; + + WRITE_BIT(cpu->f, LR35902_FLAG_BIT_Z, !GET_BIT(val, bit)); + CLR_BIT(cpu->f, LR35902_FLAG_BIT_N); + SET_BIT(cpu->f, LR35902_FLAG_BIT_H); +} + +static void res(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = instr & 7; + uint8_t bit = (instr >> 3) & 7; + uint8_t val = _get_reg_8(cpu, reg); + uint8_t new_val = val; + + CLR_BIT(new_val, bit); + _set_reg_8(cpu, reg, new_val); + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; +} + +static void set(struct lr35902_state *cpu, uint8_t instr) +{ + uint8_t reg = instr & 7; + uint8_t bit = (instr >> 3) & 7; + uint8_t val = _get_reg_8(cpu, reg); + uint8_t new_val = val; + + SET_BIT(new_val, bit); + _set_reg_8(cpu, reg, new_val); + cpu->stall_cycles = (reg == LR35902_REG_HL_DEREF) ? 4 : 2; +} diff --git a/src/cpu.h b/src/cpu.h new file mode 100644 index 0000000..cf90d7f --- /dev/null +++ b/src/cpu.h @@ -0,0 +1,111 @@ +/* + * A simulation of the LR35902 CPU used by the Nintendo Gameboy + * + * Author: Max Regan + * Last Modified: 11-17-2015 + */ + +#include + +#define LR35902_FLAG_BIT_C 4 +#define LR35902_FLAG_BIT_H 5 +#define LR35902_FLAG_BIT_N 6 +#define LR35902_FLAG_BIT_Z 7 + +#define LR35902_MAX_EVENTS 16 + +typedef enum { + LR35902_REG_AF= 0, + LR35902_REG_BC, + LR35902_REG_DE, + LR35902_REG_HL, + LR35902_REG_SP, + LR35902_REG_PC, + NUM_LR35902_REGS_16 +} lr35902_regs_16; + +typedef enum { + LR35902_REG_B = 0, + LR35902_REG_C = 1, + LR35902_REG_D = 2, + LR35902_REG_E = 3, + LR35902_REG_H = 4, + LR35902_REG_L = 5, + LR35902_REG_HL_DEREF = 6, //Not a real register + LR35902_REG_A = 7, //Accumulator + LR35902_REG_F = 8, //Flags + NUM_LR35902_REGS_8 +} lr35902_regs_8; + + +typedef enum { + LR35902_INT_ON, + LR35902_INT_OFF, +} lr35902_interrupt_state; + +struct lr35902_state; + +typedef uint8_t (*lr35902_mem_read_fn)(struct lr35902_state *cpu, + uint16_t address); + +typedef void (*lr35902_mem_write_fn)(struct lr35902_state *cpu, + uint16_t address, + uint8_t value); + +typedef void (*lr35902_instr)(struct lr35902_state *cpu, uint8_t instr); + +typedef void (*lr35902_cb_instr)(struct lr35902_state *cpu, uint8_t instr); + +typedef void (*lr35902_event_fn)(struct lr35902_state *cpu); + +struct lr35902_event { + uint8_t cycles; + lr35902_event_fn run; +}; + +struct lr35902_state { + union { + /* + * As a convenience, give ourselves a bunch of different ways to + * access the registers. They are carefully written below so they + * overlap correctly, Per C99, as long as uint{8,16}_t are implemented, + * there is guaranteed to be no padding and no trap representations. + */ + uint16_t regs_16[NUM_LR35902_REGS_16]; + struct { + uint16_t bc, de, hl, af, pc, sp; + }; + uint8_t regs_8[NUM_LR35902_REGS_8]; + struct { + uint8_t b, c, d, e, h, l, a, f; + }; + }; + + uint8_t stall_cycles; + uint8_t halted; + lr35902_mem_read_fn mem_read; + lr35902_mem_write_fn mem_write; + lr35902_interrupt_state int_state; + struct lr35902_event events[LR35902_MAX_EVENTS]; + + //Cool data + struct { + uint64_t cycles; + uint64_t retired_instrs; + uint64_t mem_reads; + uint64_t mem_writes; + } metrics; +}; + +void lr35902_init(struct lr35902_state *cpu, + lr35902_mem_read_fn mem_read, + lr35902_mem_write_fn mem_write); + +uint16_t lr35902_get_reg_16(struct lr35902_state *cpu, lr35902_regs_16 reg); +uint8_t lr35902_get_reg_8(struct lr35902_state *cpu, lr35902_regs_8 reg); +void lr35902_set_reg_16(struct lr35902_state *cpu, lr35902_regs_16 reg, + uint16_t val); + +void lr35902_set_reg_8(struct lr35902_state *cpu, lr35902_regs_8 reg, + uint8_t val); +void lr35902_cycle(struct lr35902_state *cpu); diff --git a/src/gb_disas.c b/src/gb_disas.c new file mode 100644 index 0000000..b250acd --- /dev/null +++ b/src/gb_disas.c @@ -0,0 +1,282 @@ +/*TODO: This is hacky and has some bugs but whatever*/ + +#include + +static const char *opcodes[256] = { + //0 + "NOP", + "LD BC,d16", + "LD (BC),A", + "INC BC", + "INC B", + "DEC B", + "LD B,d8", + "RLCA", + "LD (a16),SP", + "ADD HL,BC", + "LD A,(BC)", + "DEC BC", + "INC C", + "DEC C", + "LD C,d8", + "RRCA", + //1 + "STOP", + "LD DE,d16", + "LD (DE),A", + "INC DE", + "INC D", + "DEC D", + "LD D,d8", + "RLA", + "JR r8", + "ADD HL,DE", + "LD A,(DE)", + "DEC DE", + "INC E", + "DEC E", + "LD E,d8", + "RRA", + //2 + "JR NZ,r8", + "LD HL,d16", + "LD (HL+),A", + "INC HL", + "INC H", + "DEC H", + "LD H,d8", + "DAA", + "JR Z,r8", + "ADD HL,HL", + "LD A,(HL+)", + "DEC HL", + "INC L", + "DEC L", + "LD L,d8", + "CPL", + //3 + "JR NC,r8", + "LD SP,d16", + "LD (HL-),A", + "INC SP", + "INC (HL)", + "DEC (HL)", + "LD (HL),d8", + "SCF", + "JR C,r8", + "ADD HL,SP", + "LD A,(HL-)", + "DEC SP", + "INC A", + "DEC A", + "LD A,d8", + "CCF", + //4 + "LD B,B", + "LD B,C", + "LD B,D", + "LD B,E", + "LD B,H", + "LD B,L", + "LD B,(HL)", + "LD B,A", + "LD C,B", + "LD C,C", + "LD C,D", + "LD C,E", + "LD C,H", + "LD C,L", + "LD C,(HL)", + "LD C,A", + //5 + "LD D,B", + "LD D,C", + "LD D,D", + "LD D,E", + "LD D,H", + "LD D,L", + "LD D,(HL)", + "LD D,A", + "LD E,B", + "LD E,C", + "LD E,D", + "LD E,E", + "LD E,H", + "LD E,L", + "LD E,(HL)", + "LD E,A", + //6 + "LD H,B", + "LD H,C", + "LD H,D", + "LD H,E", + "LD H,H", + "LD H,L", + "LD H,(HL)", + "LD H,A", + "LD L,B", + "LD L,C", + "LD L,D", + "LD L,E", + "LD L,H", + "LD L,L", + "LD L,(HL)", + "LD L,A", + //7 + "LD (HL),B", + "LD (HL),C", + "LD (HL),D", + "LD (HL),E", + "LD (HL),H", + "LD (HL),L", + "HALT", + "LD (HL),A", + "LD A,B", + "LD A,C", + "LD A,D", + "LD A,E", + "LD A,H", + "LD A,L", + "LD A,(HL)", + "LD A,A", + //8 + "ADD A,B", + "ADD A,C", + "ADD A,D", + "ADD A,E", + "ADD A,H", + "ADD A,L", + "ADD A,(HL)", + "ADD A,A", + "ADC A,B", + "ADC A,C", + "ADC A,D", + "ADC A,E", + "ADC A,H", + "ADC A,L", + "ADC A,(HL)", + "ADC A,A", + //9 + "SUB B", + "SUB C", + "SUB D", + "SUB E", + "SUB H", + "SUB L", + "SUB (HL)", + "SUB A", + "SBC A,B", + "SBC A,C", + "SBC A,D", + "SBC A,E", + "SBC A,H", + "SBC A,L", + "SBC A,(HL)", + "SBC A,A", + //10 + "AND B", + "AND C", + "AND D", + "AND E", + "AND H", + "AND L", + "AND (HL)", + "AND A", + "XOR B", + "XOR C", + "XOR D", + "XOR E", + "XOR H", + "XOR L", + "XOR (HL)", + "XOR A", + //11 + "OR B", + "OR C", + "OR D", + "OR E", + "OR H", + "OR L", + "OR (HL)", + "OR A", + "CP B", + "CP C", + "CP D", + "CP E", + "CP H", + "CP L", + "CP (HL)", + "CP A", + //12 + "RET NZ", + "POP BC", + "JP NZ,a16", + "JP a16", + "CALL NZ,a16", + "PUSH BC", + "ADD A,d8", + "RST 00H", + "RET Z", + "RET", + "JP Z,a16", + "PREFIX CB", + "CALL Z,a16", + "CALL a16", + "ADC A,d8", + "RST", + //13 + "RET NC", + "POP DE", + "JP NC,a16", + "*UNDEF*", + "CALL NC,a16", + "PUSH DE", + "SUB d8", + "RST 10H", + "RET C", + "RETI", + "JP C,a16", + "UNDEF*", + "CALL C,a16", + "*UNDEF*", + "SBC A,d8", + "RST 18H", + //14 + "LDH (a8),A", + "POP HL", + "LD (C),A", + "*UNDEF*", + "*UNDEF*", + "PUSH HL", + "AND d8", + "RST 20H", + "ADD SP,r8", + "JP (HL)", + "LD (a16),A", + "*UNDEF*", + "*UNDEF*", + "*UNDEF*", + "XOR d8", + "RST 28H", + //15 + "LDH A,(a8)", + "POP AF", + "LD A,(C)", + "DI", + "*UNDEF*", + "PUSH AF", + "OR d8", + "RST 30H", + "LD HL,SP+r8", + "LD SP,HL", + "LD A,(a16)", + "EI", + "*UNDEF*", + "*UNDEF*", + "CP d8", + "RST 38H" +}; + +const char * gb_byte_to_opcode(unsigned char byte) { + return opcodes[(int) byte]; +} diff --git a/src/gb_disas.h b/src/gb_disas.h new file mode 100644 index 0000000..563c13d --- /dev/null +++ b/src/gb_disas.h @@ -0,0 +1,19 @@ + +#ifndef _GB_DISAS_H_H +#define _GB_DISAS_H_H + +const char *gb_byte_to_opcode(unsigned char byte); + +/* int main(int argc, char **argv) */ +/* { */ +/* unsigned char opcode = 0; */ +/* int bytes = 0; */ + +/* do { */ +/* bytes = read(0, &opcode, 1); */ +/* printf("(%d) [0x%x] %s\n", bytes, opcode, opcodes[opcode]); */ +/* } while (bytes > 0); */ +/* return 0; */ +/* } */ + +#endif diff --git a/src/gbasm/assemble.c b/src/gbasm/assemble.c new file mode 100644 index 0000000..21b8720 --- /dev/null +++ b/src/gbasm/assemble.c @@ -0,0 +1,39 @@ +#include + +#include "gbasm/assemble.h" +#include "gbasm/parser.h" +#include "gbasm/types.h" +#include "gbasm/errors.h" +#include "gbasm/opcodes.h" +#include "common.h" + +#define GBASM_MAX_INSTS 1024 + +struct gb_asm_prog *prog; + +int gbasm_assemble(char *program, struct emitter *emitter) +{ + struct gbasm_parsed_inst insts[GBASM_MAX_INSTS]; + int num_insts; + + num_insts = gbasm_parse_buffer(program, insts, ARRAY_SIZE(insts)); + if (num_insts < 0) { + return num_insts; + } + + DEBUG_LOG("parsed %d instructions\n", num_insts); + + for (int i = 0; i < num_insts; i++) { + DEBUG_LOG("emitting %s\n", insts[i].opcode); + const struct gbasm_op_info *info = gbasm_get_opcode_info(insts[i].opcode); + if (info == NULL) { + gbasm_unknown_opcode_error(&insts[i]); + } + + info->check(&insts[i], info); + info->emit(emitter, &insts[i]); + } + + return 0; +} + diff --git a/src/gbasm/assemble.h b/src/gbasm/assemble.h new file mode 100644 index 0000000..268a038 --- /dev/null +++ b/src/gbasm/assemble.h @@ -0,0 +1,17 @@ +#ifndef GBASM_ASSEMBLE_H +#define GBASM_ASSEMBLE_H + +#include "gbasm/emitter.h" + +/** + * Assembles an arbitrary gameboy assembly program. The assembled bytes are + * outputted using 'emitter.' The string in 'program' will be modified and + * should not be referenced after calling gb_asm_assemble. + * + * @param program: A string containing the program to assemble + * @param emitter: An emitter which will output the program on success. + * @returns zero on success, else a negative error code + */ +int gbasm_assemble(char *program, struct emitter *emitter); + +#endif diff --git a/src/gbasm/block.h b/src/gbasm/block.h new file mode 100644 index 0000000..14b1ad6 --- /dev/null +++ b/src/gbasm/block.h @@ -0,0 +1,38 @@ +#include "gbasm/emitter.h" + +#define GB_ASM_INST_MAX_LEN 3 /* I think this is right */ +#define GB_ASM_PROG_MAX_LEN 4096 + +struct gb_asm_inst { + struct gb_asm_inst *next; + + /************ + * Pass one * + ************/ + const char *str; /* The source string for the instruction */ + size_t len; /* The length of assembled instruction in bytes */ + + /************ + * Pass two * + ************/ + uint8_t raw[GB_ASM_INST_MAX_LEN]; /* The raw bytes of the assembled instruction */ + struct gb_asm_block *target; +}; + +struct gb_asm_block { + struct gb_asm_block *next; + + const char *label; + uint16_t addr; + struct gb_asm_inst *insts; +}; + +struct gb_asm_prog { + char *program; + char *instructions; + + struct gb_asm_block *blocks; + size_t num_blocks; + + struct emitter *emitter; +}; diff --git a/src/gbasm/emit.c b/src/gbasm/emit.c new file mode 100644 index 0000000..e93d6e4 --- /dev/null +++ b/src/gbasm/emit.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include + +#include "gbasm/emitter.h" +#include "common.h" + +int emit(struct emitter *emitter, const void *data, size_t size) +{ + return emitter->emit(emitter->emitter_impl, data, size); +} + +int fd_emit(void *emitter, const void *data, size_t size) +{ + struct fd_emitter *fd_emitter = emitter; + int rc; + + while ((rc = write(fd_emitter->fd, data, size)) < 0 + && (errno != EINTR || errno != EAGAIN)) { + return rc; + } + + return rc; +} + +int buffer_emit(void *emitter, const void *data, size_t size) +{ + struct buffer_emitter *buf_emit = emitter; + size_t cpy_len; + + cpy_len = MIN(size, buf_emit->buffer_size - buf_emit->cursor); + memcpy(buf_emit->buffer + buf_emit->cursor, data, cpy_len); + buf_emit->cursor += cpy_len; + + return cpy_len; +} + +int fd_emitter_init(struct emitter *emitter, + struct fd_emitter *fd_emitter, + int fd) +{ + emitter->emit = fd_emit; + emitter->emitter_impl = fd_emitter; + + fd_emitter->fd = fd; + + return 0; +} + +int buffer_emitter_init(struct emitter *emitter, + struct buffer_emitter *buffer_emitter, + uint8_t* buffer, + size_t buffer_len) +{ + emitter->emit = buffer_emit; + emitter->emitter_impl = buffer_emitter; + + buffer_emitter->buffer = buffer; + buffer_emitter->buffer_size = buffer_len; + buffer_emitter->cursor = 0; + + return 0; +} diff --git a/src/gbasm/emitter.h b/src/gbasm/emitter.h new file mode 100644 index 0000000..47cb565 --- /dev/null +++ b/src/gbasm/emitter.h @@ -0,0 +1,44 @@ +#ifndef GBASM_EMITTER_H +#define GBASM_EMITTER_H + +#include +#include + +struct emitter +{ + /* Returns the number of bytes copied on success, else returns a negative error code */ + int (*emit)(void *emitter, const void *data, size_t size); + void (*free)(void *emitter); + void *emitter_impl; +}; + +struct buffer_emitter +{ + uint8_t* buffer; + size_t buffer_size; + size_t cursor; +}; + +struct fd_emitter +{ + int fd; +}; + +int fd_emitter_init(struct emitter *emitter, + struct fd_emitter *fd_emitter, + int fd); + +int buffer_emitter_init(struct emitter *emitter, + struct buffer_emitter *buffer_emitter, + uint8_t* buffer, + size_t buffer_len); + +int emit(struct emitter *emitter, + const void *data, + size_t size); + +int emit_str(struct emitter *emitter, + const char *str); + + +#endif diff --git a/src/gbasm/errors.c b/src/gbasm/errors.c new file mode 100644 index 0000000..c7f9533 --- /dev/null +++ b/src/gbasm/errors.c @@ -0,0 +1,56 @@ +#include +#include +#include + +#include "gbasm/errors.h" +#include "gbasm/gb_types.h" +#include "gbasm/opcodes.h" +#include "gbasm/parser.h" + +static void gbasm_print_error(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + fprintf(stderr, fmt, args); +} + +void gbasm_unknown_opcode_error(const struct gbasm_parsed_inst *inst) +{ + gbasm_print_error( + "ERROR: %s:%d unrecognized opcode \"%s\"\n", + inst->file_name, inst->line_num, inst->opcode); + + exit(1); +} + + +void gbasm_too_many_args_error(const struct gbasm_parsed_inst *inst, + const struct gbasm_op_info *opcode) +{ + gbasm_print_error( + "ERROR: %s:%d too many arguments to \"%s\"\n", + inst->file_name, inst->line_num); + + exit(1); +} + +void gbasm_arg_wrong_type_error(const struct gbasm_parsed_inst *inst, + const struct gbasm_op_info *opcode) +{ + gbasm_print_error( + "ERROR: %s:%d invalid operand to \"%s\"\n", + inst->file_name, inst->line_num, inst->opcode); + + exit(1); +} + +void gbasm_too_few_args_error(const struct gbasm_parsed_inst *inst, + const struct gbasm_op_info *opcode) +{ + gbasm_print_error( + "ERROR: %s:%d not_enough_args to \"%s\"\n", + inst->file_name, inst->line_num, inst->opcode); + + exit(1); +} diff --git a/src/gbasm/errors.h b/src/gbasm/errors.h new file mode 100644 index 0000000..b1edc8d --- /dev/null +++ b/src/gbasm/errors.h @@ -0,0 +1,17 @@ +#ifndef GB_ASM_ERRORS_H +#define GB_ASM_ERRORS_H + +#include "gbasm/types.h" + +void gbasm_unknown_opcode_error(const struct gbasm_parsed_inst *inst); + +void gbasm_too_many_args_error(const struct gbasm_parsed_inst *inst, + const struct gbasm_op_info *opcode); + +void gbasm_arg_wrong_type_error(const struct gbasm_parsed_inst *inst, + const struct gbasm_op_info *opcode); + +void gbasm_too_few_args_error(const struct gbasm_parsed_inst *inst, + const struct gbasm_op_info *opcode); + +#endif diff --git a/src/gbasm/gb_types.h b/src/gbasm/gb_types.h new file mode 100644 index 0000000..2d49811 --- /dev/null +++ b/src/gbasm/gb_types.h @@ -0,0 +1,78 @@ +#ifndef GBASM_GB_TYPES_H +#define GBASM_GB_TYPES_H + +#include +#include + +#define GBASM_MAX_OPERANDS 64 +#define GBASM_MAX_INST_LEN 3 + +enum gbasm_operand_type { + GBASM_OPERAND_REG_8 = 0x01, /* A, B, etc */ + GBASM_OPERAND_REG_16 = 0x02, /* BC, DE, etc */ + GBASM_OPERAND_IMM_8 = 0x04, /* $08, $FF */ + GBASM_OPERAND_IMM_16 = 0x08, /* $FF40, $0100 */ + GBASM_OPERAND_ADDR = 0x10, /* START, */ + GBASM_OPERAND_DEREF = 0x20, /* (HL), (HL+) */ + GBASM_OPERAND_COND = 0x40, /* C, NZ, etc */ +}; + +enum gbasm_operand_r8_type { + GBASM_OPERAND_R8_A, + GBASM_OPERAND_R8_B, + GBASM_OPERAND_R8_C, + GBASM_OPERAND_R8_D, + GBASM_OPERAND_R8_E, + GBASM_OPERAND_R8_H, + GBASM_OPERAND_R8_L, +}; + +struct gbasm_operand_r8 { + enum gbasm_operand_r8_type type; +}; + +enum gbasm_operand_r16_type { + GBASM_OPERAND_R16_BC, + GBASM_OPERAND_R16_DE, + GBASM_OPERAND_R16_HL, + GBASM_OPERAND_R16_SP, + GBASM_OPERAND_R16_HL_DEREF, +}; + +struct gbasm_operand_r16 { + enum gbasm_operand_r16_type type; +}; + +struct gbasm_operand_d8 { + uint8_t value; +}; + +struct gbasm_operand_d16 { + uint16_t value; +}; + +enum gbasm_operand_cond_type { + GBASM_OPERAND_COND_C, + GBASM_OPERAND_COND_NC, + GBASM_OPERAND_COND_Z, + GBASM_OPERAND_COND_NZ, +}; + +struct gbasm_operand_cond { + enum gbasm_operand_cond_type type; +}; + +struct gbasm_operand { + + enum gbasm_operand_type type; + + union { + struct gbasm_operand_r8 r8; + struct gbasm_operand_r16 r16; + struct gbasm_operand_d8 d8; + struct gbasm_operand_d16 d16; + struct gbasm_operand_cond cond; + }; +}; + +#endif diff --git a/src/gbasm/opcodes.c b/src/gbasm/opcodes.c new file mode 100644 index 0000000..a1daab4 --- /dev/null +++ b/src/gbasm/opcodes.c @@ -0,0 +1,284 @@ +#include "gbasm/gb_types.h" +#include "gbasm/opcodes.h" +#include "gbasm/errors.h" + +#include "tri.h" +#include "common.h" + +#define MAX_OPCODE_LEN 10 /*TODO: Check that's enough */ + +static bool opcodes_initted = false; +static struct tri opcode_tri; + +bool gbasm_argtype_in_set(uint32_t argtype_set, uint32_t argtype) +{ + return !!(argtype & argtype_set); +} + +static int check_no_args(const struct gbasm_parsed_inst *inst, + const struct gbasm_op_info *op_info) +{ + if (inst->num_operands > 0) { + gbasm_too_many_args_error(inst, op_info); + } + + return 0; +} + +static size_t length_one_byte( + const struct gbasm_parsed_inst *inst, + const struct gbasm_op_info *info) +{ + return 1; +} + +#define GEN_FIXED_EMITFN(_name, _data_array) \ + size_t _name(struct emitter *emitter, \ + const struct gbasm_parsed_inst *inst) \ + { \ + const uint8_t raw[] = _data_array; \ + emit(emitter, raw, sizeof(raw)); \ + return sizeof(raw); \ + } + +GEN_FIXED_EMITFN(nop_emit, {0x00}) +GEN_FIXED_EMITFN(halt_emit, {0x76}) +GEN_FIXED_EMITFN(stop_emit, {0x10}) +GEN_FIXED_EMITFN(di_emit, {0xf3}) +GEN_FIXED_EMITFN(ei_emit, {0xfb}) +GEN_FIXED_EMITFN(rla_emit, {0x17}) +GEN_FIXED_EMITFN(rra_emit, {0x1f}) +GEN_FIXED_EMITFN(rlca_emit, {0x07}) +GEN_FIXED_EMITFN(rrca_emit, {0x0f}) +GEN_FIXED_EMITFN(daa_emit, {0x27}) +GEN_FIXED_EMITFN(scf_emit, {0x37}) +GEN_FIXED_EMITFN(reti_emit, {0xd9}) +GEN_FIXED_EMITFN(cpl_emit, {0x2f}) +GEN_FIXED_EMITFN(ccf_emit, {0x3f}) + +int inc_dec_check(const struct gbasm_parsed_inst *inst, + const struct gbasm_op_info *op_info) +{ + if (inst->num_operands > 1) { + gbasm_too_many_args_error(inst, op_info); + } + + if (inst->num_operands < 1) { + gbasm_too_few_args_error(inst, op_info); + } + + if (!gbasm_argtype_in_set(op_info->operand_types[0], inst->operands[0].type)) { + gbasm_arg_wrong_type_error(inst, op_info); + } + + return 0; +} + +size_t inc_emit(struct emitter *emitter, + const struct gbasm_parsed_inst *inst) +{ + uint8_t opcode = 0x00; + + switch (inst->operands[0].type) { + case GBASM_OPERAND_REG_8: + switch (inst->operands[0].r8.type) { + case GBASM_OPERAND_R8_A: opcode = 0x3c; break; + case GBASM_OPERAND_R8_B: opcode = 0x04; break; + case GBASM_OPERAND_R8_C: opcode = 0x0c; break; + case GBASM_OPERAND_R8_D: opcode = 0x14; break; + case GBASM_OPERAND_R8_E: opcode = 0x1c; break; + case GBASM_OPERAND_R8_H: opcode = 0x24; break; + case GBASM_OPERAND_R8_L: opcode = 0x2c; break; + } + break; + case GBASM_OPERAND_REG_16: + switch (inst->operands[0].r16.type) { + case GBASM_OPERAND_R16_BC: opcode = 0x03; break; + case GBASM_OPERAND_R16_DE: opcode = 0x13; break; + case GBASM_OPERAND_R16_HL: opcode = 0x23; break; + case GBASM_OPERAND_R16_SP: opcode = 0x33; break; + case GBASM_OPERAND_R16_HL_DEREF: opcode = 0x34; break; + } + break; + default: + ASSERT(0); + } + + emit(emitter, &opcode, 1); + + return 1; +} + +size_t dec_emit(struct emitter *emitter, + const struct gbasm_parsed_inst *inst) +{ + uint8_t opcode = 0x00; + + switch (inst->operands[0].type) { + case GBASM_OPERAND_REG_8: + switch (inst->operands[0].r8.type) { + case GBASM_OPERAND_R8_A: opcode = 0x3d; break; + case GBASM_OPERAND_R8_B: opcode = 0x05; break; + case GBASM_OPERAND_R8_C: opcode = 0x0d; break; + case GBASM_OPERAND_R8_D: opcode = 0x15; break; + case GBASM_OPERAND_R8_E: opcode = 0x1d; break; + case GBASM_OPERAND_R8_H: opcode = 0x25; break; + case GBASM_OPERAND_R8_L: opcode = 0x2d; break; + } + break; + case GBASM_OPERAND_REG_16: + switch (inst->operands[0].r16.type) { + case GBASM_OPERAND_R16_BC: opcode = 0x0b; break; + case GBASM_OPERAND_R16_DE: opcode = 0x1b; break; + case GBASM_OPERAND_R16_HL: opcode = 0x2b; break; + case GBASM_OPERAND_R16_SP: opcode = 0x3b; break; + case GBASM_OPERAND_R16_HL_DEREF: opcode = 0x35; break; + } + break; + default: + ASSERT(0); + } + + emit(emitter, &opcode, 1); + + return 1; +} + + +struct gbasm_op_info gbasm_op_infos[] = { + { + .opcode = "nop", + .operand_types = {}, + .check = check_no_args, + .length = length_one_byte, + .emit = nop_emit, + }, + { + .opcode = "halt", + .operand_types = {}, + .check = check_no_args, + .length = length_one_byte, + .emit = halt_emit, + }, + { + .opcode = "stop", + .operand_types = {}, + .check = check_no_args, + .length = length_one_byte, + .emit = stop_emit, + }, + { + .opcode = "di", + .operand_types = {}, + .check = check_no_args, + .length = length_one_byte, + .emit = di_emit, + }, + { + .opcode = "ei", + .operand_types = {}, + .check = check_no_args, + .length = length_one_byte, + .emit = ei_emit, + }, + { + .opcode = "rla", + .operand_types = {}, + .check = check_no_args, + .length = length_one_byte, + .emit = rla_emit, + }, + { + .opcode = "rra", + .operand_types = {}, + .check = check_no_args, + .length = length_one_byte, + .emit = rra_emit, + }, + { + .opcode = "rlca", + .operand_types = {}, + .check = check_no_args, + .length = length_one_byte, + .emit = rlca_emit, + }, + { + .opcode = "rrca", + .operand_types = {}, + .check = check_no_args, + .length = length_one_byte, + .emit = rrca_emit, + }, + { + .opcode = "daa", + .operand_types = {}, + .check = check_no_args, + .length = length_one_byte, + .emit = daa_emit, + }, + { + .opcode = "scf", + .operand_types = {}, + .check = check_no_args, + .length = length_one_byte, + .emit = scf_emit, + }, + { + .opcode = "reti", + .operand_types = {}, + .check = check_no_args, + .length = length_one_byte, + .emit = reti_emit, + }, + { + .opcode = "cpl", + .operand_types = {}, + .check = check_no_args, + .length = length_one_byte, + .emit = cpl_emit, + }, + { + .opcode = "ccf", + .operand_types = {}, + .check = check_no_args, + .length = length_one_byte, + .emit = ccf_emit, + }, + { + .opcode = "inc", + /* TODO: support inc (HL) */ + .operand_types = { GBASM_OPERAND_REG_8 | GBASM_OPERAND_REG_16 }, + .check = inc_dec_check, + .length = length_one_byte, + .emit = inc_emit, + }, + { + .opcode = "dec", + /* TODO: support inc (HL) */ + .operand_types = { GBASM_OPERAND_REG_8 | GBASM_OPERAND_REG_16 }, + .check = inc_dec_check, + .length = length_one_byte, + .emit = dec_emit, + }, + +}; + +static void init_opcode_tri() +{ + int i; + tri_init(&opcode_tri); + opcodes_initted = true; + + for (i = 0; i < ARRAY_SIZE(gbasm_op_infos); i++) { + tri_add_string(&opcode_tri, gbasm_op_infos[i].opcode, &gbasm_op_infos[i]); + } +} + +const struct gbasm_op_info *gbasm_get_opcode_info(const char *opcode) +{ + if (!opcodes_initted) { + init_opcode_tri(); + } + + return tri_get_string(&opcode_tri, opcode); +} diff --git a/src/gbasm/opcodes.h b/src/gbasm/opcodes.h new file mode 100644 index 0000000..060319a --- /dev/null +++ b/src/gbasm/opcodes.h @@ -0,0 +1,12 @@ +#ifndef GBASM_OPCODES_H +#define GBASM_OPCODES_H + +#include "gbasm/types.h" + +/** + * Given a downcased opcode token, gets teh gbasm_op_info structure + * which describes it. + */ +const struct gbasm_op_info *gbasm_get_opcode_info(const char *opcode); + +#endif diff --git a/src/gbasm/operands.c b/src/gbasm/operands.c new file mode 100644 index 0000000..24188cc --- /dev/null +++ b/src/gbasm/operands.c @@ -0,0 +1,212 @@ +#include "gbasm/gb_types.h" +#include "gbasm/opcodes.h" +#include "gbasm/errors.h" + +#include "tri.h" +#include "common.h" + +#include + +static bool operands_initted = false; +static struct tri operand_tri; +static struct tri prefixes_tri; + +static const struct gbasm_operand_info gbasm_operand_infos[] = { + { + .token = "a", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_REG_8, + .r8 = { .type = GBASM_OPERAND_R8_A }, + } + }, + { + .token = "b", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_REG_8, + .r8 = { .type = GBASM_OPERAND_R8_B }, + }, + }, + { + /* TODO: handle C condition code */ + .token = "c", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_REG_8, + .r8 = { .type = GBASM_OPERAND_R8_C }, + }, + }, + { + .token = "d", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_REG_8, + .r8 = { .type = GBASM_OPERAND_R8_D }, + }, + }, + { + .token = "e", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_REG_8, + .r8 = { .type = GBASM_OPERAND_R8_E }, + }, + }, + { + .token = "h", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_REG_8, + .r8 = { .type = GBASM_OPERAND_R8_H }, + }, + }, + { + .token = "l", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_REG_8, + .r8 = { .type = GBASM_OPERAND_R8_L }, + }, + }, + { + .token = "bc", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_REG_16, + .r16 = { GBASM_OPERAND_R16_BC }, + }, + }, + { + .token = "de", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_REG_16, + .r16 = { GBASM_OPERAND_R16_DE }, + }, + }, + { + .token = "hl", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_REG_16, + .r16 = { GBASM_OPERAND_R16_HL }, + }, + }, + { + .token = "(hl)", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_REG_16, + .r16 = { GBASM_OPERAND_R16_HL_DEREF }, + }, + }, + + { + .token = "sp", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_REG_16, + .r16 = { GBASM_OPERAND_R16_SP }, + }, + }, + { + .token = "nc", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_COND, + .cond = { GBASM_OPERAND_COND_NC }, + }, + }, + { + .token = "z", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_COND, + .cond = { GBASM_OPERAND_COND_Z }, + }, + }, + { + .token = "nz", + .fixed = true, + .op_info = { + .type = GBASM_OPERAND_COND, + .cond = { GBASM_OPERAND_COND_NZ }, + }, + }, +}; + +static int convert_constant(const char *token, struct gbasm_operand *info) +{ + int val = strtol(token + 1, NULL, 0); + + if (val <= 0xff || val >= -0xff) { + info->type = GBASM_OPERAND_IMM_8; + info->d8.value = val; + } else if (val <= 0xffff || val >= 0xffff) { + info->type = GBASM_OPERAND_IMM_16; + info->d16.value = val; + } else { + return -1; + } + + return 0; +} + +static const struct gbasm_operand_prefix prefixes[] = { + { + .prefix = "$", + .convert = convert_constant, + }, +}; + +static void init_opcode_tri() +{ + int i; + + tri_init(&operand_tri); + tri_init(&prefixes_tri); + + for (i = 0; i < ARRAY_SIZE(gbasm_operand_infos); i++) { + tri_add_string(&operand_tri, + gbasm_operand_infos[i].token, + (void *) &gbasm_operand_infos[i]); + } + + for (i = 0; i < ARRAY_SIZE(prefixes); i++) { + tri_add_string(&prefixes_tri, + prefixes[i].prefix, + (void *) &prefixes[i]); + } + + operands_initted = true; + +} + +int gbasm_parse_operand(const char *token, struct gbasm_operand *out) +{ + struct gbasm_operand_info *info; + struct gbasm_operand_prefix *prefix; + + if (!operands_initted) { + init_opcode_tri(); + } + + info = (struct gbasm_operand_info *) tri_get_string(&operand_tri, token); + if (info != NULL) { + if (info->fixed) { + memcpy(out, &info->op_info, sizeof(*out)); + } else { + info->convert(token, out); + } + return 0; + } + + prefix = (struct gbasm_operand_prefix *) tri_prefix_match(&prefixes_tri, token); + if (prefix != NULL) { + prefix->convert(token, out); + return 0; + } + + return -1; +} diff --git a/src/gbasm/operands.h b/src/gbasm/operands.h new file mode 100644 index 0000000..84d65f8 --- /dev/null +++ b/src/gbasm/operands.h @@ -0,0 +1,12 @@ +#ifndef GBASM_OPCODES_H +#define GBASM_OPCODES_H + +#include "gbasm/types.h" + +/** + * Given a downcased opcode token, gets teh gbasm_op_info structure + * which describes it. + */ +int gbasm_parse_operand(const char *token, struct gbasm_operand *out); + +#endif diff --git a/src/gbasm/parser.c b/src/gbasm/parser.c new file mode 100644 index 0000000..c40d866 --- /dev/null +++ b/src/gbasm/parser.c @@ -0,0 +1,123 @@ + +#include +#include +#include +#include + +#include "common.h" +#include "gbasm/parser.h" +#include "gbasm/gb_types.h" +#include "gbasm/operands.h" + +#define GBASM_MAX_TOKENS (GBASM_MAX_OPERANDS + 1) /* One for opcode */ +#define GBASM_LINE_DELIMS "\n" +#define GBASM_COMMENT_DELIMS ";" +#define GBASM_WHITESPACE_DELIMS " \t" + +/* Returns the number of tokens in the line */ +static int gbasm_tokenize_line(char *line, + char **tokens, + int max_tokens, + char **next_line) +{ + int num_tokens = 0; + char *newline; + + /* Find the next line */ + newline = strchr(line, '\n'); + if (newline != NULL) { + *newline = '\0'; + *next_line = newline + 1; + } else { + *next_line = NULL; + }; + + /* Strip any comments on this line */ + strtok(line, GBASM_COMMENT_DELIMS); + + for (tokens[num_tokens] = strtok(line, GBASM_WHITESPACE_DELIMS); + tokens[num_tokens] != NULL && num_tokens < max_tokens; + tokens[num_tokens] = strtok(NULL, GBASM_WHITESPACE_DELIMS)) + { + num_tokens++; + } + + return num_tokens; +} + +int gbasm_parse_tokens(struct gbasm_parsed_inst *inst, + const char **tokens, + int num_tokens) +{ + int token_num = 0; + int operand_num = 0; + + if (num_tokens <= 0) { + return -EINVAL; + } + + if (tokens[token_num][0] == '.') { + token_num++; + /* TODO: support labels */ + } + + if (token_num >= num_tokens) { + return 0; + } + + inst->opcode = tokens[token_num++]; + while (token_num < num_tokens) { + /* TODO error check */ + gbasm_parse_operand(tokens[token_num++], + &inst->operands[operand_num++]); + } + + return 0; +} + +int gbasm_parse_buffer(char *buffer, + struct gbasm_parsed_inst *insts, + unsigned int max_insts) +{ + char *line_tokens[GBASM_MAX_TOKENS] = { 0 }; + char *next_line, *cur; + int num_tokens; + int line_num; + int inst_num = 0; + + if (buffer != NULL) { + downcase(buffer); + } + + for (cur = buffer, line_num = 0; + cur != NULL; + cur = next_line, line_num++) { + + num_tokens = gbasm_tokenize_line(cur, + line_tokens, + GBASM_MAX_TOKENS, + &next_line); + + ASSERT_MSG(num_tokens >= 0, "failed to parse line %d", line_num); + + if (num_tokens > 0) { + gbasm_parse_tokens(&insts[inst_num], + (const char **)line_tokens, + num_tokens); + DEBUG_LOG("token: %s\n", insts[inst_num].opcode); + insts[inst_num].line_num = line_num; + + /* TODO: Fill these out */ + insts[inst_num].file_name = ""; + insts[inst_num].src_line = ""; + insts[inst_num].num_operands = num_tokens - 1; + + if (++inst_num >= max_insts) { + return -EINVAL; + } + + } + } + + return inst_num; +} diff --git a/src/gbasm/parser.h b/src/gbasm/parser.h new file mode 100644 index 0000000..cd623fc --- /dev/null +++ b/src/gbasm/parser.h @@ -0,0 +1,19 @@ +#ifndef GBASM_PARSER_H +#define GBASM_PARSER_H + +#include "gbasm/types.h" + +/** + * Parses an input assembly program into an array of parsed_instructions + * + * @param buffer: the input program, which will be modified + * @param insts: the output array of instructions + * @param max_insts: the maximum number of instructions which can be put + * into 'insts' + * @returns zero on success, else a negative error code + */ +int gbasm_parse_buffer(char *buffer, + struct gbasm_parsed_inst *insts, + unsigned int max_insts); + +#endif diff --git a/src/gbasm/types.h b/src/gbasm/types.h new file mode 100644 index 0000000..bb10afd --- /dev/null +++ b/src/gbasm/types.h @@ -0,0 +1,49 @@ +#ifndef GBASM_TYPES_H +#define GBASM_TYPES_H + +#include + +#include "gbasm/gb_types.h" +#include "gbasm/emitter.h" + +struct gbasm_parsed_inst { + const char *src_line; + const char *file_name; + const char *opcode; + int line_num; + + struct gbasm_operand operands[GBASM_MAX_OPERANDS]; + int num_operands; +}; + +struct gbasm_op_info { + char *opcode; + uint32_t operand_types[GBASM_MAX_OPERANDS]; + + int (*check)(const struct gbasm_parsed_inst *inst, + const struct gbasm_op_info *op_info); + size_t (*length)(const struct gbasm_parsed_inst *inst, + const struct gbasm_op_info *info); + size_t (*emit)(struct emitter *emitter, + const struct gbasm_parsed_inst *inst); +}; + +struct gbasm_operand_info { + + const char *token; + + bool fixed; + union { + /* Valid iff fixed == true */ + int (*convert)(const char *token, struct gbasm_operand *operand); + /* Valid iff fixed == false */ + struct gbasm_operand op_info; + }; +}; + +struct gbasm_operand_prefix { + const char *prefix; + int (*convert)(const char *token, struct gbasm_operand *operand); +}; + +#endif diff --git a/src/tests/gbasm/fixed.c b/src/tests/gbasm/fixed.c new file mode 100644 index 0000000..d20c973 --- /dev/null +++ b/src/tests/gbasm/fixed.c @@ -0,0 +1,51 @@ +#include "tests/gbasm/test.h" +#include "common.h" + +#define GEN_FIXED_TEST(_opcode, _hex)\ + uint8_t _opcode##_output[] = _hex; \ + static const struct gbasm_test _opcode = { \ + .name = #_opcode, \ + .asm_source = #_opcode, \ + .expected_output = _opcode##_output, \ + .expected_output_len = sizeof(_opcode##_output), \ + }; + +GEN_FIXED_TEST(nop, {0x00}) +GEN_FIXED_TEST(halt, {0x76}) +GEN_FIXED_TEST(stop, {0x10}) +GEN_FIXED_TEST(di, {0xf3}) +GEN_FIXED_TEST(ei, {0xfb}) +GEN_FIXED_TEST(rla, {0x17}) +GEN_FIXED_TEST(rra, {0x1f}) +GEN_FIXED_TEST(rlca, {0x07}) +GEN_FIXED_TEST(rrca, {0x0f}) +GEN_FIXED_TEST(daa, {0x27}) +GEN_FIXED_TEST(scf, {0x37}) +GEN_FIXED_TEST(reti, {0xd9}) +GEN_FIXED_TEST(cpl, {0x2f}) +GEN_FIXED_TEST(ccf, {0x3f}) + +static const struct gbasm_test *tests[] = { + &nop, + &halt, + &stop, + &di, + &ei, + &rla, + &rra, + &rrca, + &daa, + &scf, + &reti, + &cpl, + &ccf, +}; + +struct gbasm_tests gbasm_fixed_tests = { + .name = "fixed", + .num_tests = ARRAY_SIZE(tests), + .tests = tests, +}; + + + diff --git a/src/tests/gbasm/fixed.h b/src/tests/gbasm/fixed.h new file mode 100644 index 0000000..00b7a33 --- /dev/null +++ b/src/tests/gbasm/fixed.h @@ -0,0 +1,9 @@ +#ifndef GBASM_TEST_FIXED_H +#define GBASM_TEST_FIXED_H + +#include "tests/gbasm/test.h" +#include "common.h" + +extern const struct gbasm_tests gbasm_fixed_tests; + +#endif diff --git a/src/tests/gbasm/inc.c b/src/tests/gbasm/inc.c new file mode 100644 index 0000000..89e82fa --- /dev/null +++ b/src/tests/gbasm/inc.c @@ -0,0 +1,41 @@ +#include "tests/gbasm/test.h" +#include "common.h" + +/* TODO: There is probably a better way to do this */ +static const char all_src[] = + "INC A\n" + "INC B\n" + "INC C\n" + "INC D\n" + "INC E\n" + "INC H\n" + "INC L\n" + "INC BC\n" + "INC DE\n" + "INC HL\n" + "INC SP\n" + "INC (HL)\n"; + +static const uint8_t all_output[] = { + 0x3c, 0x04, 0x0c, 0x14, + 0x1c, 0x24, 0x2c, 0x03, + 0x13, 0x23, 0x33, 0x34, +}; + +static const struct gbasm_test all = { + .name = "all", + .asm_source = all_src, + .expected_output = all_output, + .expected_output_len = sizeof(all_output), +}; + +static const struct gbasm_test *tests[] = { + &all, +}; + +struct gbasm_tests gbasm_inc_tests = { + .name = "inc", + .num_tests = ARRAY_SIZE(tests), + .tests = tests, +}; + diff --git a/src/tests/gbasm/inc.h b/src/tests/gbasm/inc.h new file mode 100644 index 0000000..c7eedcb --- /dev/null +++ b/src/tests/gbasm/inc.h @@ -0,0 +1,9 @@ +#ifndef GBASM_TEST_INC_H +#define GBASM_TEST_INC_H + +#include "tests/gbasm/test.h" +#include "common.h" + +extern const struct gbasm_tests gbasm_inc_tests; + +#endif diff --git a/src/tests/gbasm/test.h b/src/tests/gbasm/test.h new file mode 100644 index 0000000..6c17da4 --- /dev/null +++ b/src/tests/gbasm/test.h @@ -0,0 +1,19 @@ +#ifndef GBASM_TEST_H +#define GBASM_TEST_H + +#include + +struct gbasm_test { + const char *name; + const char *asm_source; + const uint8_t *expected_output; + const int expected_output_len; +}; + +struct gbasm_tests { + const char *name; + int num_tests; + const struct gbasm_test **tests; +}; + +#endif diff --git a/src/tri.c b/src/tri.c new file mode 100644 index 0000000..3a10a6a --- /dev/null +++ b/src/tri.c @@ -0,0 +1,165 @@ +#include +#include +#include +#include + +#include "tri.h" +#include "common.h" + +static size_t char_to_idx(char c) +{ + unsigned char c2 = c; + return (size_t) c2; +} + +void tri_init(struct tri *tri) +{ + tri->head = malloc(sizeof(struct tri_node)); + memset(tri->head, 0, sizeof(*tri->head)); +} + +static void tri_free_node(struct tri_node *node) +{ + int i; + + if (node == NULL) { + return; + } + + for (i = 0; i < ARRAY_SIZE(node->nodes); i++) { + tri_free_node(node->nodes[i]); + } + + free(node); +} + +void tri_free(struct tri *tri) { + tri_free_node(tri->head); +} + +int tri_add_string(struct tri *tri, const char *string, void *value) +{ + struct tri_node *node = NULL; + int i = 0; + + if (string == NULL) { + return -1; + } + + if (value == NULL) { + return -1; + } + + node = tri->head; + + for (i = 0; i < strlen(string); i++) { + int idx = char_to_idx(string[i]); + + if (node->nodes[idx] == NULL) { + node->nodes[idx] = malloc(sizeof(struct tri_node)); + memset(node->nodes[idx], 0, sizeof(struct tri_node)); + } + node = node->nodes[idx]; + } + + node->value = value; + return 0; +} + +void *tri_get_string(struct tri *tri, const char *string) +{ + struct tri_node *node = NULL; + int i = 0; + + if (string == NULL) { + return NULL; + } + + node = tri->head; + + for (i = 0; i < strlen(string); i++) { + int idx = char_to_idx(string[i]); + + if (node->nodes[idx] == NULL) { + return NULL; + } + + node = node->nodes[idx]; + } + + return node->value; +} + +void *tri_get_string_autocomplete(struct tri *tri, const char *string, bool *ambiguous) +{ + struct tri_node *node = NULL; + int i = 0, j = 0; + + *ambiguous = false; + + if (string == NULL) { + return NULL; + } + + node = tri->head; + + for (i = 0; i < strlen(string); i++) { + int idx = char_to_idx(string[i]); + + if (node->nodes[idx] == NULL) { + return NULL; + } + + node = node->nodes[idx]; + } + + while(1) { + struct tri_node *next_node = NULL; + for (j = 0; j < ARRAY_SIZE(node->nodes); j++) { + + if (node->nodes[j] != NULL && next_node == NULL) { + next_node = node->nodes[j]; + } else if (node->nodes[j] != NULL && next_node != NULL) { + *ambiguous = true; + return NULL; + } + } + + if (next_node == NULL) { + return node->value; + } else { + node = next_node; + i++; + } + } + return node->value; +} + +void *tri_prefix_match(struct tri *tri, const char *string) +{ + struct tri_node *node = NULL; + void *value = NULL; + int i = 0; + + if (string == NULL) { + return NULL; + } + + node = tri->head; + + for (i = 0; i < strlen(string); i++) { + int idx = char_to_idx(string[i]); + + if (node->nodes[idx] == NULL) { + return value; + } + + node = node->nodes[idx]; + if (node->value != NULL) { + value = node->value; + } + + } + + return value; +} diff --git a/src/tri.h b/src/tri.h new file mode 100644 index 0000000..c72d7fe --- /dev/null +++ b/src/tri.h @@ -0,0 +1,58 @@ +/* + * An implementation of tries. + */ + +#ifndef _TRI_H_ +#define _TRI_H_ + +#include + +#define NUM_ALPHA_CHARS (256) + +struct tri_node { + struct tri_node *nodes[NUM_ALPHA_CHARS]; + void *value; +}; + +struct tri { + struct tri_node *head; +}; + + +/** + * Initialize a tri and set its maximum depth. + */ +void tri_init(struct tri *tri); + +/** + * Destroys an initialized tri and frees its internal structures. + */ +void tri_free(struct tri *tri); + +/** + * Add a string as a key, and a value to the tri. + */ +int tri_add_string(struct tri *tri, const char *string, void * value); + +/** + * Returns the value cooresponding to string. If the string is not in the tri, return + * NULL. + */ +void *tri_get_string(struct tri *tri, const char *string); + +/** + * Returns the value cooresponding to the longest matching prefix + */ +void *tri_prefix_match(struct tri *tri, const char *string); + +/** + * Using 'string' as a prefix, returns the value of the cooresponding string if there + * is only one that matches. + * + * If there are multiple possible matches, return NULL and set 'ambiguous' to true. If + * there are no possible matches, return NULL and set 'ambiguous' to false. + */ +void *tri_get_string_autocomplete(struct tri *tri, const char *string, bool *ambiguous); + + +#endif diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..2c79368 --- /dev/null +++ b/src/util.h @@ -0,0 +1,24 @@ +#ifndef _COMMON_H_ +#define _COMMON_H_ + +#include + +#define ARRAY_SIZE(x) (sizeof((x)) / (sizeof((x)[0])) + +#define ASSERT(x) \ + if (!(x)) { \ + printf("Assert failed at: %s:%d <%s>\n", \ + __FILE__, __LINE__, __FUNC__); \ + exit(1); \ + } + +#define ASSERT_MSG(x, msg) \ + if(!(x)) { \ + printf("Assert failed at: %s:%d <%s>\n", \ + __FILE__, __LINE__, __FUNC__); \ + printf("%s", msg); \ + exit(1); \ + } \ + + +#endif diff --git a/src/video.c b/src/video.c new file mode 100644 index 0000000..e951f15 --- /dev/null +++ b/src/video.c @@ -0,0 +1,85 @@ +#include "video.h" + +#include + +/* TODO: This whole implementation is very simple.*/ +/* TODO: Actual graphics output */ +/* TODO: Implementation of most registers */ +/* TODO: Interrupts */ + +void gb_video_init(struct gb_video *video) +{ + memset(video, 0, sizeof(*video)); +} + +void gb_video_cycle(struct gb_video *video) +{ + video->line_counter++; + + if (video->line_counter >= CYCLES_PER_LINE){ + video->line_counter = 0; + video->curline += 1; + if (video->curline > LCD_Y_MAX) { + video->curline = 0; + } + } + +} + +uint8_t gb_video_mem_read(struct gb_video *video, uint16_t addr) +{ + switch (addr) { + case 0xFF40: + return video->lcdcont; + case 0xFF41: + return video->lcdstat; + case 0xFF42: + return video->scrolly; + case 0xFF43: + return video->scrollx; + case 0xFF44: + return video->curline; + case 0xFF45: + return video->cmpline; + case 0xFF4A: + return video->wndposy; + case 0xFF4B: + return video->wndposx; + default: + return 0; + } +} + +void gb_video_mem_write(struct gb_video *video, uint16_t addr, uint8_t val) +{ + uint8_t *write_addr; + switch (addr) { + case 0xFF40: + write_addr = &video->lcdcont; + case 0xFF41: + write_addr = &video->lcdstat; + case 0xFF42: + write_addr = &video->scrolly; + case 0xFF43: + write_addr = &video->scrollx; + case 0xFF44: + write_addr = &video->curline; + case 0xFF45: + write_addr = &video->cmpline; + case 0xFF47: + write_addr = &video->bgrdpal; + case 0xFF48: + write_addr = &video->obj0pal; + case 0xFF49: + write_addr = &video->obj1pal; + case 0xFF4A: + write_addr = &video->wndposy; + case 0xFF4B: + write_addr = &video->wndposx; + default: + return; + } + + *write_addr = val; + +} diff --git a/src/video.h b/src/video.h new file mode 100644 index 0000000..4d78803 --- /dev/null +++ b/src/video.h @@ -0,0 +1,100 @@ +#ifndef VIDEO_H +#define VIDEO_H + +#define CYCLES_PER_LINE 167 +#define LCD_Y_MAX 153 + +#include + +struct gb_video { + + /* Comments ripped http://fms.komkon.org/GameBoy/Tech/Software.html */ + + /* FF40 -- LCDCONT [RW] LCD Control | when set to 1 | when set to 0 */ + /* Bit7 LCD operation | ON | OFF */ + /* Bit6 Window Tile Table address | 9C00-9FFF | 9800-9BFF */ + /* Bit5 Window display | ON | OFF */ + /* Bit4 Tile Pattern Table address | 8000-8FFF | 8800-97FF */ + /* Bit3 Background Tile Table address | 9C00-9FFF | 9800-9BFF */ + /* Bit2 Sprite size | 8x16 | 8x8 */ + /* Bit1 Color #0 transparency in the window | SOLID | TRANSPARENT */ + /* Bit0 Background display | ON | OFF */ + uint8_t lcdcont; + + /* FF41 -- LCDSTAT [RW] LCD Status | when set to 1 | when set to 0 */ + /* Bit6 Interrupt on scanline coincidence | ON | OFF */ + /* Bit5 Interrupt on controller mode 10 | ON | OFF */ + /* Bit4 Interrupt on controller mode 01 | ON | OFF */ + /* Bit3 Interrupt on controller mode 00 | ON | OFF */ + /* Bit2 Scanline coincidence flag | COINCIDENCE | NO COINCIDENCE */ + /* Bit1-0 LCD Controller mode: */ + /* 00 - Horizontal blanking impulse [VRAM 8000-9FFF can be accessed by CPU] */ + /* 01 - Vertical blanking impulse [VRAM 8000-9FFF can be accessed by CPU] */ + /* 10 - OAM FE00-FE90 is accessed by LCD controller */ + /* 11 - Both OAM FE00-FE90 and VRAM 8000-9FFF are accessed by LCD controller */ + uint8_t lcdstat; + + /* FF42 -- SCROLLY [RW] Background Vertical Scrolling */ + uint8_t scrolly; + + /* FF43 -- SCROLLX [RW] Background Horizontal Scrolling */ + uint8_t scrollx; + + /* FF44 -- CURLINE [RW] Current Scanline */ + /* This register contains the number of a screen line currently being */ + /* scanned. It can take values 0-153 where 144-153 indicate the vertical */ + /* blanking period. Writing into this register resets it. */ + uint8_t curline; + + /* FF45 -- CMPLINE [RW] Scanline Comparison */ + /* When contents of CURLINE are equal to contents of CMPLINE, scanline */ + /* coincidence flag is set in the LCD status register and an interrupt */ + /* may occur. */ + uint8_t cmpline; + + /* FF47 -- BGRDPAL [W] Background Palette */ + /* Bit7-6 Palette for color #3 | */ + /* Bit5-4 Palette for color #2 | 00 ------- 01 ------- 10 -------> 11 */ + /* Bit3-2 Palette for color #1 | lightest darkest */ + /* Bit1-0 Palette for color #0 | */ + uint8_t bgrdpal; + + /* FF48 -- OBJ0PAL [W] Sprite Palette #0 */ + /* Bit7-6 Palette for color #3 | */ + /* Bit5-4 Palette for color #2 | 00 ------- 01 ------- 10 -------> 11 */ + /* Bit3-2 Palette for color #1 | lightest darkest */ + /* Bit1-0 Palette for color #0 | */ + uint8_t obj0pal; + + /* FF49 -- OBJ1PAL [W] Sprite Palette #1 */ + /* Bit7-6 Palette for color #3 | */ + /* Bit5-4 Palette for color #2 | 00 ------- 01 ------- 10 -------> 11 */ + /* Bit3-2 Palette for color #1 | lightest darkest */ + /* Bit1-0 Palette for color #0 | */ + uint8_t obj1pal; + + /* FF4A -- WNDPOSY [RW] Window Y Position */ + /* WNDPOSY may assume values 0-143. It determines the vertical position */ + /* of the left upper corner of a window on the screen. */ + uint8_t wndposy; + + /* FF4B -- WNDPOSX [RW] Window X Position */ + /* WNDPOSX may assume values 7-166. It determines the horizontal position */ + /* of the left upper corner of a window on the screen. The real position */ + /* is WNDPOSX-7. */ + uint8_t wndposx; + + + /******************************/ + /***** Internal stuff *********/ + /******************************/ + + int line_counter; +}; + +void gb_video_init(struct gb_video *video); +void gb_video_cycle(struct gb_video *video); +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); + +#endif