gb-emu: initial commit

Add a mostly non-functional Gameboy CPU and the skeleton
of a Gameboy assembler intended for unit tests.
This commit is contained in:
2016-12-18 23:43:41 -08:00
commit 6e2f4096a2
38 changed files with 4424 additions and 0 deletions

81
Makefile Executable file
View File

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

8
README.md Normal file
View File

@@ -0,0 +1,8 @@
A work-in-progress Gameboy emulator with a terminal-based UI and a basic assembler.
== Compilation ==
```
make config-default
make
```

79
src/apps/gbasm-test.c Normal file
View File

@@ -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 <string.h> /* memset */
#include <glib.h>
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();
}

87
src/apps/gbasm.c Normal file
View File

@@ -0,0 +1,87 @@
/* A simple, two-pass gameboy assembler */
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#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 <file> -o <file>\n"
" -h Print this usage \n"
" -i <input file> The file to assemble\n"
" -o <output file> 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);
}

525
src/apps/gbdb.c Normal file
View File

@@ -0,0 +1,525 @@
/*
* A CLI-based tester and debugger for the Gameboy's LR35902 CPU
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <stdbool.h>
#include <signal.h>
#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 <file>: loads the given file as the gameboy cartridge\n"
"run: runs the CPU until a breakpoint or halt is hit\n"
"break <addr>: Adds a breakpoint for the given addess\n"
"step <cycles>: executes \"cycle\" instructions (default 1)\n"
"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 <file>\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 <addr> (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 <file>\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 <file>\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 <addr>\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 <bkpt num>\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;
}

20
src/apps/sample-test.c Normal file
View File

@@ -0,0 +1,20 @@
#include <glib.h>
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();
}

65
src/apps/tri-test.c Normal file
View File

@@ -0,0 +1,65 @@
#include "tri.h"
#include "common.h"
#include <glib.h>
#include <stdint.h>
#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();
}

10
src/common.c Normal file
View File

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

53
src/common.h Normal file
View File

@@ -0,0 +1,53 @@
#ifndef _COMMON_H_
#define _COMMON_H_
#include <stdlib.h>
#include <stdio.h>
#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

1459
src/cpu.c Normal file

File diff suppressed because it is too large Load Diff

111
src/cpu.h Normal file
View File

@@ -0,0 +1,111 @@
/*
* A simulation of the LR35902 CPU used by the Nintendo Gameboy
*
* Author: Max Regan
* Last Modified: 11-17-2015
*/
#include <stdint.h>
#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);

282
src/gb_disas.c Normal file
View File

@@ -0,0 +1,282 @@
/*TODO: This is hacky and has some bugs but whatever*/
#include <stdio.h>
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];
}

19
src/gb_disas.h Normal file
View File

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

39
src/gbasm/assemble.c Normal file
View File

@@ -0,0 +1,39 @@
#include <stdlib.h>
#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;
}

17
src/gbasm/assemble.h Normal file
View File

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

38
src/gbasm/block.h Normal file
View File

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

64
src/gbasm/emit.c Normal file
View File

@@ -0,0 +1,64 @@
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#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;
}

44
src/gbasm/emitter.h Normal file
View File

@@ -0,0 +1,44 @@
#ifndef GBASM_EMITTER_H
#define GBASM_EMITTER_H
#include <stddef.h>
#include <stdint.h>
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

56
src/gbasm/errors.c Normal file
View File

@@ -0,0 +1,56 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#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);
}

17
src/gbasm/errors.h Normal file
View File

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

78
src/gbasm/gb_types.h Normal file
View File

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

284
src/gbasm/opcodes.c Normal file
View File

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

12
src/gbasm/opcodes.h Normal file
View File

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

212
src/gbasm/operands.c Normal file
View File

@@ -0,0 +1,212 @@
#include "gbasm/gb_types.h"
#include "gbasm/opcodes.h"
#include "gbasm/errors.h"
#include "tri.h"
#include "common.h"
#include <string.h>
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;
}

12
src/gbasm/operands.h Normal file
View File

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

123
src/gbasm/parser.c Normal file
View File

@@ -0,0 +1,123 @@
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <stdbool.h>
#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;
}

19
src/gbasm/parser.h Normal file
View File

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

49
src/gbasm/types.h Normal file
View File

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

51
src/tests/gbasm/fixed.c Normal file
View File

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

9
src/tests/gbasm/fixed.h Normal file
View File

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

41
src/tests/gbasm/inc.c Normal file
View File

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

9
src/tests/gbasm/inc.h Normal file
View File

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

19
src/tests/gbasm/test.h Normal file
View File

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

165
src/tri.c Normal file
View File

@@ -0,0 +1,165 @@
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#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;
}

58
src/tri.h Normal file
View File

@@ -0,0 +1,58 @@
/*
* An implementation of tries.
*/
#ifndef _TRI_H_
#define _TRI_H_
#include <stdbool.h>
#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

24
src/util.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef _COMMON_H_
#define _COMMON_H_
#include <stdio.h>
#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

85
src/video.c Normal file
View File

@@ -0,0 +1,85 @@
#include "video.h"
#include <string.h>
/* 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;
}

100
src/video.h Normal file
View File

@@ -0,0 +1,100 @@
#ifndef VIDEO_H
#define VIDEO_H
#define CYCLES_PER_LINE 167
#define LCD_Y_MAX 153
#include <stdint.h>
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