From 95a676586942a2ddd1040503754d05ca91f6e3a7 Mon Sep 17 00:00:00 2001 From: Justin Wind Date: Sun, 8 Apr 2012 15:12:52 -0700 Subject: [PATCH] initial commit --- Makefile | 18 ++ dcpu16.c | 767 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 785 insertions(+) create mode 100644 Makefile create mode 100644 dcpu16.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f3324ef --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +#!make +# +PROGRAMS = dcpu16 + +CFLAGS = -g -Wall -Wextra -pedantic -std=c99 +LDFLAGS = + +all: $(PROGRAMS) + + +as-dcpu16: as-dcpu16.o + +dcpu16: dcpu16.o + +clean: + @rm -rf $(PROGRAMS) *.o *.dSYM + +check: $(PROGRAMS) diff --git a/dcpu16.c b/dcpu16.c new file mode 100644 index 0000000..d0d3b40 --- /dev/null +++ b/dcpu16.c @@ -0,0 +1,767 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * emulates the DCPU16 system from http://0x10c.com/doc/dcpu-16.txt + * + * I couldn't remember ever implementing an emulator before, so this + * happened. As such, consider this a toy in progress. + * There are likely many improvable aspects. + * + * Justin Wind + * 2012 04 05 - implementation started + * 2012 04 06 - first functionality achieved + * + * TODO + * move cli driver to separate module + */ + +static const char * const src_id_ = "$Id$"; + +/* the target system's concept of a word */ +#define WORD DCPU16_WORD +typedef unsigned short WORD; + +#define RAM_SIZE 0x10000 +static const char regnames_[] = "ABCXYZIJ"; +struct dcpu16 { + unsigned long long cycle; /* number of cycles it took to get to current state */ + WORD reg_work_[2]; /* holding bins for literal values when decoding instructions */ + WORD reg[8]; /* system registers, a b c x y z i j */ + WORD pc; /* program counter */ + WORD sp; /* stack pointer */ + WORD o; /* overflow */ + unsigned int skip_ : 1; /* */ + WORD ram[RAM_SIZE]; /* memory */ +}; + + +static unsigned int trace_mode_ = 0; /* turn on for overly verbose internals */ + +#define WARN(...) warn(__VA_ARGS__) +static inline void warn(char *fmt, ...) __attribute__((format(printf, 1, 2))); +static inline void warn(char *fmt, ...) { + va_list ap; + + fprintf(stderr, "!!! "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + fflush(stderr); +} + + +#define TRACE(...) do { if (trace_mode_) trace(__VA_ARGS__); } while (0) +static inline void trace(char *fmt, ...) __attribute__((format(printf, 1, 2))); +static inline +void trace(char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + fprintf(stdout, "\n"); + fflush(stdout); +} + + +/* sets *v to be the destination of the value */ +/* workv is buffer to use to accumulate literal value before use */ +/* returns true if destination points to literal (id est *v should ignore writes) */ +static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WORD **v) { + WORD nextword; + unsigned int retval = 0; + + assert(value >= 0x00 && value <= 0x3f); + + /* does this value indicate a literal */ + if (value >= 0x1f) + retval = 1; + + /* if we're skipping this instruction, just advance the pc if needed */ + if (d->skip_) { + TRACE(">> SKIP decode"); + if (value == 0x1e || value == 0x1f) + d->pc++; + return retval; + } + + if (value <= 0x07) { /* register */ + *v = d->reg + value; + TRACE(">> %c (0x%04x)", + regnames_[value], + **v); + + } else if (value <= 0x0f) { /* [register] */ + *v = &(d->ram[ d->reg[(value & 0x07)] ]); + TRACE(">> [%c] [0x%04x] (0x%04x)", + regnames_[value&0x07], + d->reg[value&0x07], + **v); + + } else if (value <= 0x17) { /* [next word + register] */ + nextword = d->ram[ d->pc++ ]; + d->cycle++; + *v = &(d->ram[ nextword + d->reg[(value & 0x07)] ]); + TRACE(">> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)", + regnames_[(value & 0x07)], + nextword, + d->reg[(value & 0x07)], + **v); + + } else switch (value) { + case 0x18: /* POP / [sp++] */ + *v = &(d->ram[ d->sp++ ]); + TRACE(">> POP [0x%04x] (0x%04x)", + d->sp - 1, + **v); + break; + + case 0x19: /* PEEK / [sp] */ + *v = &(d->ram[ d->sp ]); + TRACE(">> PEEK [0x%04x] (0x%04x)", + d->sp, + **v); + break; + + case 0x1a: /* PUSH / [--sp] */ + *v = &(d->ram[ --d->sp ]); + TRACE(">> PUSH [0x%04x] (0x%04x)", + d->sp + 1, + **v); + break; + + case 0x1b: /* SP */ + *v = &(d->sp); + TRACE(">> SP (0x%04x)", + **v); + break; + + case 0x1c: /* PC */ + *v = &(d->pc); + TRACE(">> PC (0x%04x)", **v); + break; + + case 0x1d: /* O */ + *v = &(d->o); + TRACE(">> O (0x%04x)", **v); + break; + + case 0x1e: /* [next word] / [[pc++]] */ + nextword = d->ram[ d->pc++ ]; + d->cycle++; + *v = &(d->ram[ nextword ]); + TRACE(">> [nextword] [0x%04x] (0x%04x)", + nextword, + **v); + break; + + case 0x1f: /* next word (literal) / [pc++] */ + nextword = d->ram[ d->pc++ ]; + d->cycle++; + *work_v = nextword; + *v = work_v; + TRACE(">> nextword (0x%04x)", **v); + break; + + default: /* 0x20-0x3f: literal values 0x00-0x1f */ + *work_v = value & 0x1f; + *v = work_v; + TRACE(">> literal (0x%04x)", **v); + } + + return retval; +} + +#define OPCODE_BASIC_BITS (4) +#define OPCODE_BASIC_SHIFT (0) + +#define OPCODE_NBI_BITS (6) +#define OPCODE_NBI_SHIFT (4) + +#define OPCODE_FUTURE_BITS (16) +#define OPCODE_FUTURE_SHIFT (10) + +#define OPCODE_NAME_LEN 16 +struct opcode_entry { + unsigned short value; + char name[OPCODE_NAME_LEN]; + void (*impl)(struct dcpu16 *, WORD, WORD); +}; + +/* messy boilerplate for opcode handlers */ + +#define OP_IMPL(x) static void op_##x(struct dcpu16 *d, WORD val_a, WORD val_b) + +#define OP_NBI_ (void)val_b +#define OP_BASIC_ (void)value_decode(d, val_b, &d->reg_work_[0], &b) +#define OP_TYPE(op_type) WORD *a, *b;\ + unsigned int lit_a;\ + do {\ + assert(d != NULL);\ + lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\ + op_type;\ + if (d->skip_) {\ + TRACE("++ SKIPPED");\ + d->skip_ = 0;\ + return;\ + }\ + } while (0) + + +#define OP_BASIC(x) WORD *a, *b;\ + unsigned int lit_a;\ + do {\ + assert(d != NULL);\ + lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\ + value_decode(d, val_b, &d->reg_work_[1], &b);\ + if (d->skip_) {\ + TRACE("++ SKIPPED");\ + d->skip_ = 0;\ + return;\ + }\ + } while(0) + +#define OP_NBI(x) WORD *a;\ + unsigned int lit_a;\ + do {\ + assert(d != NULL);\ + lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\ + (void)val_b;\ + if (d->skip_) {\ + TRACE("++ SKIPPED");\ + d->skip_ = 0;\ + return;\ + }\ + } while(0) + + +/* extended opcodes */ + +/* + N.B. this next function currently decodes values -- id est, it is + an opcode processing terminus; however, if 'future instruction set + expansion' happens, this will probably need to behave more like + the OP_IMPL(_nbi_) function which invoked it, if those instructions + have zero or differently styled operands. +*/ +OP_IMPL(nbi__reserved_) { + OP_NBI(nbi__reserved_); + /* reserved for future expansion */ + + WORD future_opcode = (d->ram[d->pc] >> OPCODE_FUTURE_SHIFT); + WARN("reserved future opcode 0x%04x invoked", future_opcode); +} + +OP_IMPL(nbi_jsr) { + OP_NBI(nbi_jsr); + /* pushes the address of the next instruction to the stack, then sets PC to a */ + + d->ram[ --d->sp ] = d->pc; + d->pc = *a; + + d->cycle += 2; +} + +OP_IMPL(nbi__reserved2_) { + OP_NBI(nbi__reserved2_); + /* reserved */ + + WARN("reserved nbi opcode invoked"); +} + +static const struct opcode_entry opcode_nbi_entries[] = { + {0x0, "(reserved)", op_nbi__reserved_}, + {0x1, "JSR", op_nbi_jsr}, + {0x2, "(reserved)", op_nbi__reserved2_}, + {0x0, "", NULL} +}; +#define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1) + + +/* basic opcodes */ + +/* + N.B. the following function does not decode values, as the nbi + instructions only have one operand. +*/ +OP_IMPL(_nbi_) { + assert(d != NULL); + /* non-basic instruction */ + + /* don't do normal value decoding here */ + + WORD nbi_opcode = val_a; + const struct opcode_entry *e = opcode_nbi_entries; + + e = opcode_nbi_entries + ( (nbi_opcode < OPCODE_NBI_MAX) ? nbi_opcode : (OPCODE_NBI_MAX - 1) ); + + assert(e->impl != NULL); + + TRACE(">> %s 0x%04x", e->name, val_b); + e->impl(d, val_b, 0); +} + +OP_IMPL(set) { + OP_BASIC(set); + /* sets a to b */ + + /* only set non-literal target */ + if (val_a < 0x1f) { + *a = *b; + } + + d->cycle += 1; +} + +OP_IMPL(add) { + OP_BASIC(add); + /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */ + unsigned int acc = *a + *b; + + if (val_a < 0x1f) { + *a = acc; + } + d->o = acc >> 16; + + d->cycle += 2; +} + +OP_IMPL(sub) { + OP_BASIC(sub); + /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */ + unsigned int acc = *a - *b; + + if (val_a < 0x1f) { + *a = acc; + } + d->o = acc >> 16; + + d->cycle += 2; +} + +OP_IMPL(mul) { + OP_BASIC(mul); + /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */ + unsigned int acc = *a * *b; + + if (val_a < 0x1f) { + *a = acc; + } + d->o = acc >> 16; + d->cycle += 2; +} + +OP_IMPL(div) { + OP_BASIC(div); + /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */ + + if (*b == 0) { + if (val_a < 0x1f) { + *a = 0; + } + d->o = 0; + } else { + unsigned int acc = *a / *b; + + if (val_a < 0x1f) { + *a = acc; + } + + acc = (*a << 16) / *b; + d->o = acc; + } + + d->cycle += 3; +} + +OP_IMPL(mod) { + OP_BASIC(mod); + /* sets a to a%b. if b==0, sets a to 0 instead. */ + + if (*b == 0) { + if (val_a < 0x1f) { + *a = 0; + } + } else { + if (val_a < 0x1f) { + *a = *a % *b; + } + } + + d->cycle += 3; +} + +OP_IMPL(shl) { + OP_BASIC(shl); + /* sets a to a<>16)&0xffff */ + unsigned int acc = *a << *b; + + if (val_a < 0x1f) { + *a = acc; + } + d->o = acc >> 16; + + d->cycle += 2; +} + +OP_IMPL(shr) { + OP_BASIC(shr); + /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */ + unsigned int acc = *a >> *b; + + if (val_a < 0x1f) { + *a = acc; + } + d->o = (*a << 16) >> *b; + + d->cycle += 2; +} + +OP_IMPL(and) { + OP_BASIC(and); + /* sets a to a&b */ + + if (val_a < 0x1f) { + *a = *a & *b; + } + + d->cycle += 1; +} + +OP_IMPL(bor) { + OP_BASIC(bor); + /* sets a to a|b */ + + if (val_a < 0x1f) { + *a = *a | *b; + } + + d->cycle += 1; +} + +OP_IMPL(xor) { + OP_BASIC(xor); + /* sets a to a^b */ + + if (val_a < 0x1f) { + *a = *a ^ *b; + } + + d->cycle += 1; +} + +OP_IMPL(ife) { + OP_BASIC(ife); + /* performs next instruction only if a==b */ + + if (*a == *b) { + /* */ + } else { + d->skip_ = 1; + d->cycle++; + } + + d->cycle += 2; +} + +OP_IMPL(ifn) { + OP_BASIC(ifn); + /* performs next instruction only if a!=b */ + + if (*a != *b) { + /* */ + } else { + d->skip_ = 1; + d->cycle++; + } + + d->cycle += 2; +} + +OP_IMPL(ifg) { + OP_BASIC(ifg); + /* performs next instruction only if a>b */ + + if (*a > *b) { + /* */ + } else { + d->skip_ = 1; + d->cycle++; + } + + d->cycle += 2; +} + +OP_IMPL(ifb) { + OP_BASIC(ifb); + /* performs next instruction only if (a&b)!=0 */ + + if ((*a & *b) != 0) { + /* */ + } else { + d->skip_ = 1; + d->cycle++; + } + + d->cycle += 2; +} + +static const struct opcode_entry opcode_basic_entries[] = { + {0x0, "(nbi)", op__nbi_}, + {0x1, "SET", op_set }, + {0x2, "ADD", op_add }, + {0x3, "SUB", op_sub }, + {0x4, "MUL", op_mul }, + {0x5, "DIV", op_div }, + {0x6, "MOD", op_mod }, + {0x7, "SHL", op_shl }, + {0x8, "SHR", op_shr }, + {0x9, "AND", op_and }, + {0xa, "BOR", op_bor }, + {0xb, "XOR", op_xor }, + {0xc, "IFE", op_ife }, + {0xd, "IFN", op_ifn }, + {0xe, "IFG", op_ifg }, + {0xf, "IFB", op_ifb }, + {0x0, "", NULL } +}; + +void dump_value(WORD value, WORD nextword) { + if (value < 0x07) { + printf(" %c", regnames_[value]); + } else if (value < 0x0f) { + printf(" [%c]", regnames_[value & 0x07]); + } else if (value < 0x17) { + printf(" [0x%04x + %c]", nextword, regnames_[value & 0x07]); + } else switch (value) { + case 0x18: printf(" POP"); break; + case 0x19: printf(" PEEK"); break; + case 0x1a: printf(" PUSH"); break; + case 0x1b: printf(" SP"); break; + case 0x1c: printf(" PC"); break; + case 0x1d: printf(" O"); break; + case 0x1e: printf(" [0x%04x]", nextword); break; + case 0x1f: printf(" 0x%04x", nextword); break; + default: printf(" 0x%02x", value - 0x20); + } +} + +void dump_instruction(struct dcpu16 *d, WORD addr) { + WORD opcode, a, b; + unsigned int instr_len = 1; + const struct opcode_entry *e; + + opcode = (d->ram[addr] >> OPCODE_BASIC_SHIFT) & ((1 << OPCODE_BASIC_BITS) - 1); + a = (d->ram[addr] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS)) & ((1 << 6) - 1); + b = (d->ram[addr] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS + 6)) & ((1 << 6) - 1); + + assert(opcode < 0x0f); + assert(a < 0x3f); + assert(b < 0x3f); + + printf("next instr 0x%04x: %04x", addr, d->ram[addr]); + + if (opcode != 0) + { + if (a == 0x1e || a == 0x1f) { + printf(" %04x", d->ram[addr + instr_len]); + instr_len++; + } + } + if (b == 0x1e || b == 0x1f) { + printf(" %04x", d->ram[addr + instr_len]); + instr_len++; + } + + if (opcode) + e = opcode_basic_entries + opcode; + else + e = opcode_nbi_entries + ( (a < OPCODE_NBI_MAX) ? a : (OPCODE_NBI_MAX - 1) ); + printf("\n\t%s", e->name); + if (opcode != 0) { + dump_value(a, d->ram[addr + 1]); + if (a == 0x1e || a == 0x1f) + addr++; + printf(","); + } + + dump_value(b, d->ram[addr + 1]); + + printf("\n"); +} + +void dcpu16_execute_next_instruction(struct dcpu16 *d) { + WORD opcode; + WORD val_a, val_b; + const struct opcode_entry *e; + + /* fetch next instruction */ + if (d->pc > RAM_SIZE) { /* currently impossible */ + WARN("%s beyond %u", "PC", RAM_SIZE); + /* d->pc %= RAM_SIZE; */ + } + + opcode = (d->ram[ d->pc ] >> OPCODE_BASIC_SHIFT) & ((1 << OPCODE_BASIC_BITS) - 1); + val_a = (d->ram[d->pc] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS)) & ((1 << 6) - 1); + val_b = (d->ram[d->pc] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS + 6)) & ((1 << 6) - 1); + + d->pc++; + + for (e = opcode_basic_entries; e->impl; e++) { + if (e->value == opcode) { + TRACE(">> %s 0x%04x, 0x%04x", e->name, val_a, val_b); + e->impl(d, val_a, val_b); + break; + } + } +} + +static void usage(char *prog, unsigned int full) { + FILE *f = full ? stdout : stderr; + char *x = strrchr(prog, '/'); + + if (x && *(x + 1)) + prog = x + 1; + + if (full) + fprintf(f, "%s -- \n\n", + prog); + + fprintf(f, "Usage: %s\n", + prog); + + if (full) { + fprintf(f, "\nOptions:\n" + "\t-h -- this screen\n"); + + fprintf(f, "\n%78s\n", src_id_); + } +} + +static int file_load(struct dcpu16 *d, char *filename) { + FILE *f; + size_t r; + + f = fopen(filename, "rb"); + if (f == NULL) + { + fprintf(stderr, "%s(%s):%s\n", "fopen", filename, strerror(errno)); + return -1; + } + + r = fread(d->ram, sizeof(WORD), RAM_SIZE, f); + TRACE("read %zu words", r); + + if (ferror(f)) { + fprintf(stderr, "%s():%s\n", "fread", strerror(errno)); + } + + fclose(f); + return 0; +} + +static void testprog_load(struct dcpu16 *d) { + static WORD bin[] = { + 0x7c01, 0x0030, 0x7de1, 0x1000, 0x0020, 0x7803, 0x1000, 0xc00d, + 0x7dc1, 0x001a, 0xa861, 0x7c01, 0x2000, 0x2161, 0x2000, 0x8463, + 0x806d, 0x7dc1, 0x000d, 0x9031, 0x7c10, 0x0018, 0x7dc1, 0x001a, + 0x9037, 0x61c1, 0x7dc1, 0x001a, 0x0000 + }; + size_t i; + + printf("loading...\n"); + for (i = 0; i < (sizeof(bin) / sizeof(WORD)); i++) + { + printf(" %04x", bin[i]); + d->ram[i] = bin[i]; + } + printf("\nloaded 0x%04zx words\n", i - 1); +} + +static +void dump_cpu_state(struct dcpu16 *d) { + unsigned int i; + + printf("[--- cycle:0x%08llx %2s:0x%04x %2s:0x%04x %2s:0x%04x\n", + d->cycle, + "PC", d->pc, + "SP", d->sp, + "O", d->o); + printf(" "); + for (i = 0; i < 8; i++) + printf(" %c:0x%04x", regnames_[i], d->reg[i]); + printf("\n"); +} + +static +void dump_ram(struct dcpu16 *d, WORD start, WORD stop) { + unsigned int i, j; + const unsigned int n = 8; /* words per line */ + + for (i = start, j = 0; i <= stop; i++, j++) { + if (j % n == 0) + printf("0x%04x:\t", i); + printf(" %04x%s", d->ram[i], (j % n) == (n - 1) ? "\n" : ""); + } +} + + +int main(int argc, char **argv) { + struct dcpu16 *m; + int c; + char buf[512]; + + m = calloc(1, sizeof *m); + if (m == NULL) + { + fprintf(stderr, "%s:%s\n", "calloc", strerror(errno)); + exit(EX_OSERR); + } + + while ( (c = getopt(argc, argv, "ht")) != EOF ) + { + switch (c) + { + case 't': + trace_mode_ = 1; + dump_ram(m, 0, 0x001f); + testprog_load(m); + dump_ram(m, 0, 0x001f); + break; + + case 'h': + usage(argv[0], 1); + exit(EX_OK); + + default: + usage(argv[0], 0); + exit(EX_USAGE); + } + } + + if (argc - optind) + { + /* read file */ + file_load(m, argv[optind]); + } + + dump_cpu_state(m); + while (fgets(buf, sizeof buf, stdin)) { + dcpu16_execute_next_instruction(m); + dump_cpu_state(m); + if (trace_mode_) + dump_instruction(m, m->pc); + } + + free(m); + + exit(EX_OK); +} -- 2.45.2