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:
79
src/apps/gbasm-test.c
Normal file
79
src/apps/gbasm-test.c
Normal 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
87
src/apps/gbasm.c
Normal 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
525
src/apps/gbdb.c
Normal 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
20
src/apps/sample-test.c
Normal 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
65
src/apps/tri-test.c
Normal 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
10
src/common.c
Normal 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
53
src/common.h
Normal 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
|
||||
111
src/cpu.h
Normal file
111
src/cpu.h
Normal 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
282
src/gb_disas.c
Normal 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
19
src/gb_disas.h
Normal 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
39
src/gbasm/assemble.c
Normal 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
17
src/gbasm/assemble.h
Normal 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
38
src/gbasm/block.h
Normal 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
64
src/gbasm/emit.c
Normal 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
44
src/gbasm/emitter.h
Normal 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
56
src/gbasm/errors.c
Normal 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
17
src/gbasm/errors.h
Normal 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
78
src/gbasm/gb_types.h
Normal 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
284
src/gbasm/opcodes.c
Normal 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
12
src/gbasm/opcodes.h
Normal 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
212
src/gbasm/operands.c
Normal 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
12
src/gbasm/operands.h
Normal 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
123
src/gbasm/parser.c
Normal 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
19
src/gbasm/parser.h
Normal 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
49
src/gbasm/types.h
Normal 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
51
src/tests/gbasm/fixed.c
Normal 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
9
src/tests/gbasm/fixed.h
Normal 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
41
src/tests/gbasm/inc.c
Normal 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
9
src/tests/gbasm/inc.h
Normal 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
19
src/tests/gbasm/test.h
Normal 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
165
src/tri.c
Normal 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
58
src/tri.h
Normal 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
24
src/util.h
Normal 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
85
src/video.c
Normal 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
100
src/video.h
Normal 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
|
||||
Reference in New Issue
Block a user