removed old skip code
[dcpu16] / dcpu16.c
index 26c594e02b60e524f596afcda0490d8e697456e2..772aec84917cd407884f7a281a6e0ed3be547d29 100644 (file)
--- a/dcpu16.c
+++ b/dcpu16.c
@@ -1,5 +1,5 @@
-#include <stdio.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
 #include <string.h>
@@ -7,6 +7,8 @@
 #include <assert.h>
 #include <sysexits.h>
 
+#include "dcpu16.h"
+
 /*
  *  emulates the DCPU16 system from http://0x10c.com/doc/dcpu-16.txt
  *  
  *    2012 04 05 - implementation started
  *    2012 04 06 - first functionality achieved
  *    2012 04 09 - minor cleanups
+ *    2012 04 10 - moved cli to separate module
+ *    2012 04 12 - added basic callback support for address accesses
  *
  *  TODO
- *    move cli driver to separate module
+ *    change api to print into buffers rather than stdio
  *    drop checks for assigning to literals -- it won't affect anything anyhow
- *    debug short literal decoding
+ *    refactor opcode functiontables into switch statements
  */
 
 static const char * const src_id_ = "$Id$";
 
-/* the target system's concept of a word */
-#define WORD DCPU16_WORD
-typedef unsigned short WORD;
+#define OPCODE_BASIC_BITS   4
+#define OPCODE_OPERAND_BITS 6
 
-#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;     /* skip execution of next instruction */
-    WORD ram[RAM_SIZE];         /* memory */
-};
-
-
-static unsigned int trace_mode_ = 0; /* spew overly verbose internals */
 
+/* some default warning and debug reporting functions, which can be overridden by clients */
 #define WARN(...) do { if (warn_cb_) warn_cb_(__VA_ARGS__); } while (0)
 static inline void warn_(char *fmt, ...) __attribute__((format(printf, 1, 2)));
 static inline
 void warn_(char *fmt, ...) {
     va_list ap;
 
-    fprintf(stderr, "!!! ");
+    fprintf(stderr, "[warning] ");
     va_start(ap, fmt);
     vfprintf(stderr, fmt, ap);
     va_end(ap);
@@ -65,30 +55,60 @@ void dcpu16_warn_cb_set(void (*fn)(char *fmt, ...)) {
     warn_cb_ = fn;
 }
 
-#define TRACE(...) do { if (trace_cb_ && trace_mode_) trace_cb_(__VA_ARGS__); } while (0)
+#ifdef DEBUG
+#define TRACE(...) do { if (trace_cb_) trace_cb_(__VA_ARGS__); } while (0)
 static inline void trace_(char *fmt, ...) __attribute__((format(printf, 1, 2)));
 static inline
 void trace_(char *fmt, ...) {
     va_list ap;
 
+    fprintf(stdout, "[debug] ");
     va_start(ap, fmt);
     vfprintf(stdout, fmt, ap);
     va_end(ap);
     fprintf(stdout, "\n");
     fflush(stdout);
 }
-static void (*trace_cb_)(char *fmt, ...) = trace_;
+#else /* DEBUG */
+#define TRACE(...) do {} while(0)
+#endif /* DEBUG */
+static void (*trace_cb_)(char *fmt, ...) =
+#ifdef DEBUG
+    trace_
+#else /* DEBUG */
+    NULL
+#endif
+    ;
 void dcpu16_trace_cb_set(void (*fn)(char *fmt, ...)) {
     trace_cb_ = fn;
 }
 
 
+/*  acct_event_
+ *  invokes callbacks for specified event
+ */
+static inline
+void acct_event_(struct dcpu16 *vm, dcpu16_acct_event_ ev, DCPU16_WORD addr) {
+    struct dcpu16_acct_cb *cb = vm->cb_table_;
+    size_t i;
+
+    for (i = 0; i < vm->cb_table_entries_; i++) {
+        if ( (cb[i].mask & ev) )
+            cb[i].fn(ev, addr);
+    }
+}
+
 
-/* 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;
+/*  value_decode_
+ * sets *v to be the destination of the value
+ * advances d->pc if necessary
+ * workv is buffer to use to accumulate literal value before use, one exists for either potential instruction operand
+ * e_addr is for accounting callback
+ * returns true if destination points to literal (id est *v should ignore writes)
+ */
+static
+unsigned int value_decode_(struct dcpu16 *d, DCPU16_WORD value, DCPU16_WORD *work_v, DCPU16_WORD **v, DCPU16_WORD *e_addr) {
+    DCPU16_WORD nextword;
     unsigned int retval = 0;
 
     assert(value <= 0x3f);
@@ -97,14 +117,6 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
     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)",
@@ -117,6 +129,7 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
               regnames_[value&0x07],
               d->reg[value&0x07],
               **v);
+        *e_addr = d->reg[(value & 0x07)];
 
     } else if (value <= 0x17) { /* [next word + register] */
         nextword = d->ram[ d->pc++ ];
@@ -127,6 +140,7 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
               nextword,
               d->reg[(value & 0x07)],
               **v);
+        *e_addr = nextword + d->reg[(value & 0x07)];
 
     } else switch (value) {
         case 0x18: /* POP / [sp++] */
@@ -134,6 +148,7 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
         TRACE(">>     POP [0x%04x] (0x%04x)",
               d->sp - 1,
               **v);
+        *e_addr = d->sp - 1;
         break;
 
         case 0x19: /* PEEK / [sp] */
@@ -141,6 +156,7 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
         TRACE(">>     PEEK [0x%04x] (0x%04x)",
               d->sp,
               **v);
+        *e_addr = d->sp;
         break;
 
         case 0x1a: /* PUSH / [--sp] */
@@ -148,6 +164,7 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
         TRACE(">>     PUSH [0x%04x] (0x%04x)",
               d->sp + 1,
               **v);
+        *e_addr = d->sp + 1;
         break;
 
         case 0x1b: /* SP */
@@ -173,6 +190,7 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
         TRACE(">>     [nextword] [0x%04x] (0x%04x)",
               nextword,
               **v);
+        *e_addr = nextword;
         break;
 
         case 0x1f: /* next word (literal) / [pc++] */
@@ -192,42 +210,37 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
     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);
+    void (*impl)(struct dcpu16 *, DCPU16_WORD, DCPU16_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_IMPL(x) static void op_##x(struct dcpu16 *d, DCPU16_WORD val_a, DCPU16_WORD val_b)
 
-#define OP_NBI_ (void)val_b, (void)b
-#define OP_BASIC_ (void)value_decode(d, val_b, &d->reg_work_[0], &b)
-#define OP_TYPE(op_type) WORD *a, *b;\
+#define OP_TYPE(op_type) DCPU16_WORD *a, *b;\
     unsigned int lit_a;\
+    DCPU16_WORD ev_a_addr = 0, ev_b_addr = 0;\
     do {\
-        lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
+        lit_a = value_decode_(d, val_a, &d->reg_work_[0], &a, &ev_a_addr);\
         op_type;\
-        if (d->skip_) {\
-            TRACE("++ SKIPPED");\
-            d->skip_ = 0;\
-            return;\
-        }\
     } while (0)
+#define OP_NBI_ (void)val_b, (void)b, (void)ev_b_addr
+#define OP_BASIC_ (void)value_decode_(d, val_b, &d->reg_work_[0], &b, &ev_b_addr)
 #define OP_BASIC(x) OP_TYPE(OP_BASIC_)
 #define OP_NBI(x) OP_TYPE(OP_NBI_)
 
+/*
+    accounting helpers, these fire off the related callbacks for memory reads,
+    memory writes, and execution of reserved instructions
+ */
+#define ACCT_R(addr) do { acct_event_(d, DCPU16_ACCT_EV_READ, addr); } while (0)
+#define ACCT_W(addr) do { acct_event_(d, DCPU16_ACCT_EV_WRITE, addr); } while (0)
+#define ACCT_ILL(addr) do { acct_event_(d, DCPU16_ACCT_EV_NOP, addr); } while (0)
 
 /* extended opcodes */
 
@@ -242,18 +255,24 @@ OP_IMPL(nbi__reserved_) {
     OP_NBI(nbi__reserved_);
     /* reserved for future expansion */
 
-    WORD future_opcode = (d->ram[d->pc] >> OPCODE_FUTURE_SHIFT);
+    DCPU16_WORD future_opcode = (d->ram[d->pc] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_BITS));
     WARN("reserved future opcode 0x%04x invoked", future_opcode);
+
+    ACCT_ILL(d->pc);
 }
 
 OP_IMPL(nbi_jsr) {
     OP_NBI(nbi_jsr);
     /* pushes the address of the next instruction to the stack, then sets PC to a */
 
+    ACCT_R(ev_a_addr);
+
     d->ram[ --d->sp ] = d->pc;
     d->pc = *a;
 
     d->cycle += 2;
+
+    ACCT_W(d->sp + 1);
 }
 
 OP_IMPL(nbi__reserved2_) {
@@ -261,6 +280,8 @@ OP_IMPL(nbi__reserved2_) {
     /* reserved */
 
     WARN("reserved nbi opcode invoked");
+
+    ACCT_ILL(d->pc);
 }
 
 static const struct opcode_entry opcode_nbi_entries[] = {
@@ -283,7 +304,7 @@ OP_IMPL(_nbi_) {
 
     /* don't do normal value decoding here */
 
-    WORD nbi_opcode = val_a;
+    DCPU16_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) );
@@ -298,8 +319,11 @@ OP_IMPL(set) {
     OP_BASIC(set);
     /* sets a to b */
 
+    ACCT_R(ev_b_addr);
+    ACCT_W(ev_a_addr);
+
     /* only set non-literal target */
-    if (val_a < 0x1f) {
+    if (!lit_a) {
         *a = *b;
     }
 
@@ -311,12 +335,17 @@ OP_IMPL(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) {
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    if (!lit_a) {
         *a = acc;
     }
-    d->o = acc >> 16;
+    d->o = (acc > 0xffff);
 
     d->cycle += 2;
+
+    ACCT_W(ev_a_addr);
 }
 
 OP_IMPL(sub) {
@@ -324,12 +353,16 @@ OP_IMPL(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) {
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    if (!lit_a) {
         *a = acc;
     }
-    d->o = acc >> 16;
-
+    d->o = (acc > 0xffff);
     d->cycle += 2;
+
+    ACCT_W(ev_a_addr);
 }
 
 OP_IMPL(mul) {
@@ -337,26 +370,34 @@ OP_IMPL(mul) {
     /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
     unsigned int acc = *a * *b;
 
-    if (val_a < 0x1f) {
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    if (!lit_a) {
         *a = acc;
     }
     d->o = acc >> 16;
     d->cycle += 2;
+
+    ACCT_W(ev_a_addr);
 }
 
 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. */
 
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
     if (*b == 0) {
-        if (val_a < 0x1f) {
+        if (!lit_a) {
             *a = 0;
         }
         d->o = 0;
     } else {
         unsigned int acc = *a / *b;
 
-        if (val_a < 0x1f) {
+        if (!lit_a) {
             *a = acc;
         }
 
@@ -365,23 +406,30 @@ OP_IMPL(div) {
     }
 
     d->cycle += 3;
+
+    ACCT_W(ev_a_addr);
 }
 
 OP_IMPL(mod) {
     OP_BASIC(mod);
     /* sets a to a%b. if b==0, sets a to 0 instead. */
 
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
     if (*b == 0) {
-        if (val_a < 0x1f) {
+        if (!lit_a) {
             *a = 0;
         }
     } else {
-        if (val_a < 0x1f) {
+        if (!lit_a) {
             *a = *a % *b;
         }
     }
 
     d->cycle += 3;
+
+    ACCT_W(ev_a_addr);
 }
 
 OP_IMPL(shl) {
@@ -389,12 +437,17 @@ OP_IMPL(shl) {
     /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
     unsigned int acc = *a << *b;
 
-    if (val_a < 0x1f) {
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    if (!lit_a) {
         *a = acc;
     }
     d->o = acc >> 16;
 
     d->cycle += 2;
+
+    ACCT_W(ev_a_addr);
 }
 
 OP_IMPL(shr) {
@@ -402,44 +455,64 @@ OP_IMPL(shr) {
     /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
     unsigned int acc = *a >> *b;
 
-    if (val_a < 0x1f) {
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    if (!lit_a) {
         *a = acc;
     }
     d->o = (*a << 16) >> *b;
 
     d->cycle += 2;
+
+    ACCT_W(ev_a_addr);
 }
 
 OP_IMPL(and) {
     OP_BASIC(and);
     /* sets a to a&b */
 
-    if (val_a < 0x1f) {
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    if (!lit_a) {
         *a = *a & *b;
     }
 
     d->cycle += 1;
+
+    ACCT_W(ev_a_addr);
 }
 
 OP_IMPL(bor) {
     OP_BASIC(bor);
     /* sets a to a|b */
 
-    if (val_a < 0x1f) {
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    if (!lit_a) {
         *a = *a | *b;
     }
 
     d->cycle += 1;
+
+    ACCT_W(ev_a_addr);
 }
 
 OP_IMPL(xor) {
     OP_BASIC(xor);
     /* sets a to a^b */
 
-    if (val_a < 0x1f) {
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    if (!lit_a) {
         *a = *a ^ *b;
     }
 
+    ACCT_W(ev_a_addr);
+
     d->cycle += 1;
 }
 
@@ -447,6 +520,9 @@ OP_IMPL(ife) {
     OP_BASIC(ife);
     /* performs next instruction only if a==b */
 
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
     if (*a == *b) {
         /* */
     } else {
@@ -461,6 +537,9 @@ OP_IMPL(ifn) {
     OP_BASIC(ifn);
     /* performs next instruction only if a!=b */
 
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
     if (*a != *b) {
         /* */
     } else {
@@ -475,6 +554,9 @@ OP_IMPL(ifg) {
     OP_BASIC(ifg);
     /* performs next instruction only if a>b */
 
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
     if (*a > *b) {
         /* */
     } else {
@@ -489,6 +571,9 @@ OP_IMPL(ifb) {
     OP_BASIC(ifb);
     /* performs next instruction only if (a&b)!=0 */
 
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
     if ((*a & *b) != 0) {
         /* */
     } else {
@@ -519,12 +604,13 @@ static const struct opcode_entry opcode_basic_entries[] = {
     {0x0, "", NULL }
 };
 
-void dump_value(WORD value, WORD nextword) {
-    if (value < 0x07) {
+static inline
+void dump_operand_value_(DCPU16_WORD value, DCPU16_WORD nextword) {
+    if (value <= 0x07) {
         printf(" %c", regnames_[value]);
-    } else if (value < 0x0f) {
+    } else if (value <= 0x0f) {
         printf(" [%c]", regnames_[value & 0x07]);
-    } else if (value < 0x17) {
+    } else if (value <= 0x17) {
         printf(" [0x%04x + %c]", nextword, regnames_[value & 0x07]);
     } else switch (value) {
         case 0x18: printf(" POP"); break;
@@ -539,219 +625,219 @@ void dump_value(WORD value, WORD nextword) {
     }
 }
 
-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]);
+/* split a word into the parts of an instruction, and determine how many words it takes up in total */
+static inline
+void instruction_decode_(struct dcpu16 *d, DCPU16_WORD addr, DCPU16_WORD *opcode, DCPU16_WORD *a, DCPU16_WORD *b, DCPU16_WORD *instr_len) {
+    *opcode = d->ram[addr] & ((1 << OPCODE_BASIC_BITS) - 1);
+    *a = (d->ram[addr] >> OPCODE_BASIC_BITS) & ((1 << OPCODE_OPERAND_BITS) - 1);
+    *b = (d->ram[addr] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_BITS)) & ((1 << OPCODE_OPERAND_BITS) - 1);
+    if (instr_len) {
+        *instr_len = 1;
+        /* both basic and nbi opcodes use their b operand */
+        if ( (*b >= 0x10 && *b <= 0x17) || *b == 0x1e || *b == 0x1f )
+            *instr_len += 1;
+        /* but only basic uses a */
+        if (*opcode
+        &&  ((*a >= 0x10 && *a <= 0x17) || *a == 0x1e || *a == 0x1f) )
+                *instr_len += 1;
+    }
+}
 
-    if (opcode != 0)
-    {
-        if (a == 0x1e || a == 0x1f) {
-            printf(" %04x", d->ram[addr + instr_len]);
-            instr_len++;
+/*  dcpu16_disassemble_print
+    print the words of the instruction at addr, followed by its assembly representation
+    returns the length of the instruction in words
+ */
+DCPU16_WORD dcpu16_disassemble_print(struct dcpu16 *d, DCPU16_WORD addr) {
+    DCPU16_WORD opcode, a, b, instr_len, i;
+    const struct opcode_entry *e;
+    unsigned int indent = 0;
+    unsigned int partial = 0;
+
+    if (!d) return 0;
+
+    /*
+        Check the previous instruction, to see if this one should be
+        indented.  This check isn't foolproof, as preceeding addresses
+        could be data which happen to match instructions..
+    */
+    for (i = 3; i; i--) {
+        instruction_decode_(d, addr - i, &opcode, &a, &b, &instr_len);
+        if (instr_len > i)
+            partial++;
+        if (instr_len == i && opcode >= 0xc) {
+            indent++;
+            break;
         }
     }
-    if (b == 0x1e || b == 0x1f) {
-        printf(" %04x", d->ram[addr + instr_len]);
-        instr_len++;
-    }
+
+    /* now get what we're really interested in */
+    instruction_decode_(d, addr, &opcode, &a, &b, &instr_len);
 
     if (opcode)
         e = opcode_basic_entries + opcode;
     else
         e = opcode_nbi_entries + ( (a < OPCODE_NBI_MAX) ? a : (OPCODE_NBI_MAX - 1) );
 
-    printf("%s%s ; %s",
+    /* show the raw words */
+    printf("%04x", d->ram[addr]);
+    for (i = 1; i < instr_len; i++) {
+        printf(" %04x", d->ram[addr + i]);
+    }
+
+    /* align things neatly, show the instruction */
+    printf("%s%s ;%s%s%s",
            instr_len < 3 ? "     " : "",
            instr_len < 2 ? "     " : "",
+           partial ? "*" : " ",
+           indent ? "  " : "",
            e->name);
-    if (opcode != 0) {
-        dump_value(a, d->ram[addr + 1]);
-        if (a == 0x1e || a == 0x1f)
+
+    /* show the operands */
+    i = 0;
+    if (opcode) {
+        dump_operand_value_(a, d->ram[addr + 1]);
+        if ((a >= 0x10 && a <= 0x17) || a == 0x1e || a == 0x1f)
             addr++;
         printf(",");
     }
 
-    dump_value(b, d->ram[addr + 1]);
+    if (opcode || a)
+        dump_operand_value_(b, d->ram[addr + 1]);
 
-    printf("\n");
+    return instr_len;
 }
 
-void dcpu16_execute_next_instruction(struct dcpu16 *d) {
-    WORD opcode;
-    WORD val_a, val_b;
+/* execute the next instruction */
+void dcpu16_step(struct dcpu16 *d) {
+    DCPU16_WORD opcode, a, b, instr_len;
     const struct opcode_entry *e;
 
-    /* decode opcode and invoke */
+    if (!d) return;
 
-    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);
+    /*
+        PC is advanced while decoding the operands by the opcode functions.
+        Things like this could be organized a little better..
+    */
+    instruction_decode_(d, d->pc, &opcode, &a, &b, NULL);
 
-    d->pc++;
+    d->pc++; /* all instructions take at least one word */
 
     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);
+            TRACE(">> %s 0x%04x, 0x%04x", e->name, a, b);
+            e->impl(d, a, 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"
-                   "\t-t -- test mode, load demo program\n"
-                   "\t-v -- verbose execution tracing\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));
+    /* and jump over next instr if needed */
+    if (d->skip_) {
+        instruction_decode_(d, d->pc, &opcode, &a, &b, &instr_len);
+        d->pc += instr_len;
+        d->skip_ = 0;
+        TRACE("++ SKIPPED %x words", instr_len);
     }
-
-    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;
+/*
+    print the current state of the machine
+    shows current cycle count, registers, and next instruction
+*/
+void dcpu16_state_print(struct dcpu16 *d) {
+    unsigned int 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);
-}
+    if (!d) return;
 
-static
-void dump_cpu_state(struct dcpu16 *d) {
-    unsigned int i;
+    printf("  ");
+    for (i = 0; i < 8; i++)
+        printf("  %c:0x%04x", regnames_[i], d->reg[i]);
+    printf("\n");
 
-    printf("---- cycle:0x%08llx %2s:0x%04x %2s:0x%04x %2s:0x%04x\n",
+    printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
            d->cycle,
-           "PC", d->pc,
+           "O", d->o,
            "SP", d->sp,
-           "O", d->o);
-    printf(" ");
-    for (i = 0; i < 8; i++)
-        printf("  %c:0x%04x", regnames_[i], d->reg[i]);
+           "PC", d->pc,
+           "PC");
+
+    dcpu16_disassemble_print(d, d->pc);
     printf("\n");
 }
 
-static
-void dump_ram(struct dcpu16 *d, WORD start, WORD stop) {
+/*  dcpu16_dump_ram
+ *  print raw ram contents from start to stop
+ */
+void dcpu16_dump_ram(struct dcpu16 *d, DCPU16_WORD start, DCPU16_WORD end) {
     unsigned int i, j;
     const unsigned int n = 8; /* words per line */
 
-    for (i = start, j = 0; i <= stop; i++, j++) {
+    if (!d) return;
+
+    for (i = start, j = 0; i <= end; i++, j++) {
         if (j % n == 0)
             printf("0x%04x:\t", i);
         printf(" %04x%s", d->ram[i], (j % n) == (n - 1) ? "\n" : "");
     }
+    if ((j % n) != (n - 1))
+        printf("\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);
+/*  dcpu16_acct_add
+ *  Register callback fn to be triggered whenever event matching any events
+ *  in bitwise mask occur.
+ */
+int dcpu16_acct_add(struct dcpu16 *vm, dcpu16_acct_event_ mask, void (*fn)(dcpu16_acct_event_, DCPU16_WORD)) {
+    struct dcpu16_acct_cb cb;
+
+    cb.mask = mask;
+    cb.fn = fn;
+
+    if (vm->cb_table_entries_ == vm->cb_table_allocated_) {
+        size_t new_entries = vm->cb_table_allocated_ + 32;
+        void *tmp_ptr = realloc(vm->cb_table_, new_entries * sizeof *(vm->cb_table_));
+        if (tmp_ptr == NULL) {
+            fprintf(stderr, "%s():%s", "realloc", strerror(errno));
+            return -1;
+        }
+        vm->cb_table_ = tmp_ptr;
+        vm->cb_table_allocated_ += 32;
     }
 
-    while ( (c = getopt(argc, argv, "htv")) != EOF )
-    {
-        switch (c)
-        {
-            case 'v':
-            trace_mode_ = 1;
-            break;
+    memcpy(vm->cb_table_ + vm->cb_table_entries_, &cb, sizeof cb);
+    vm->cb_table_entries_++;
 
-            case 't':
-            dump_ram(m, 0, 0x001f);
-            testprog_load(m);
-            dump_ram(m, 0, 0x001f);
-            break;
+    return 0;
+}
 
-            case 'h':
-            usage(argv[0], 1);
-            exit(EX_OK);
+/*  dcpu16_reset
+ *  resets a dcpu16 instance to initial state
+ */
+void dcpu16_reset(struct dcpu16 *d) {
+    if (!d) return;
 
-            default:
-            usage(argv[0], 0);
-            exit(EX_USAGE);
-        }
-    }
+    memset(d, 0, sizeof *d);
+}
 
-    if (argc - optind)
-    {
       /* read file */
-        file_load(m, argv[optind]);
-    }
+/*  dcpu16_new
+ *  allocate a new dcpu16 instance
+ */
+struct dcpu16 *dcpu16_new(void) {
+    struct dcpu16 *vm;
 
-    dump_cpu_state(m);
-    dump_instruction(m, m->pc);
-    while (fgets(buf, sizeof buf, stdin)) {
-        dcpu16_execute_next_instruction(m);
-        dump_cpu_state(m);
-        dump_instruction(m, m->pc);
-    }
+    vm = calloc(1, sizeof *vm);
+    if (vm == NULL)
+        WARN("%s: %s(%zu): %s", __func__, "calloc", strerror(errno));
 
-    free(m);
+    return vm;
+}
+
+/*  dcpu16_delete
+ *  release a dcpu16 instance
+ */
+void dcpu16_delete(struct dcpu16 **vm) {
+    if (!vm || !*vm) return;
 
-    exit(EX_OK);
+    free(*vm);
+    *vm = NULL;
 }