--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sysexits.h>
+
+/*
+ * 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 <justin.wind@gmail.com>
+ * 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<<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(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);
+}