merge
[dcpu16] / dcpu16.c
index 2a59d24e1cca31f5e161163fc8578d14fdcdc968..0b1d391e58261f46332fa914aeda4673abe889df 100644 (file)
--- a/dcpu16.c
+++ b/dcpu16.c
@@ -11,6 +11,7 @@
 
 /*
  *  emulates the DCPU16 system from http://0x10c.com/doc/dcpu-16.txt
+ *  currently emulates '1.7' spec from http://pastebin.com/Q4JvQvnM
  *  
  *  I couldn't remember ever implementing an emulator before, so this
  *  happened.  As such, consider this a toy in progress.
  *  
  *  Justin Wind <justin.wind@gmail.com>
  *    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 05 05 - start of v1.7 revisions
+ *    2012 05 08 - v1.7 revisions mostly complete
  *
  *  TODO
- *    drop checks for assigning to literals -- it won't affect anything anyhow
- *    debug short literal decoding
- *    add callbacks queues for set/get, attach your own filters
- *      such as a display
+ *    !! v1.7 bit-shift and signed opcodes need to be reviewed/finished
+ *    !! v1.7 hardware interface needs to be finished
+ *    !! v1.7 interrupts need to be finished
+ *    change api to print into buffers rather than stdio
+ *    let callbacks determine whether to override events, or just observe
+ *    sort init callbacks by base addr, to call in-order
+ *    make all callbacks register addr range of interest
  */
 
 static const char * const src_id_ = "$Id$";
 
-#define WORD DCPU16_WORD
+#define OPCODE_BASIC_BITS   5
+#define OPCODE_OPERAND_B_BITS 5
+#define OPCODE_OPERAND_A_BITS 6
+
+const char * const dcpu16_reg_names[] = {
+    "A",
+    "B",
+    "C",
+    "X",
+    "Y",
+    "Z",
+    "I",
+    "J",
+    "PC",
+    "SP",
+    "EX",
+    "IA",
+    NULL
+};
+
+#define MSG_(__level__, __vm__, ...) do { ((__vm__) ? ((struct dcpu16 *)(__vm__))->msg_cb_ : dcpu16_msg_)((__level__), __VA_ARGS__); } while (0)
+#define MSG_INFO(__vm__,...) MSG_(DCPU16_MSG_INFO, __vm__, __VA_ARGS__)
+#define MSG_ERROR(__vm__,...) MSG_(DCPU16_MSG_ERROR, __vm__, __VA_ARGS__)
+#ifdef DEBUG
+#define MSG_DEBUG(__vm__,...) MSG_(DCPU16_MSG_DEBUG, __vm__, __VA_ARGS__)
+#else /* DEBUG */
+#define MSG_DEBUG(__vm__,...) do {} while (0)
+#endif /* DEBUG */
+#ifdef DEBUG_DECODE
+#define MSG_DEBUG_DECODE (DCPU16_MSG_DEBUG + 2)
+#endif /* DEBUG_DECODE
 
-static const char regnames_[] = "ABCXYZIJ";
+/* messages could be sent nowhere */
+static void msg_null_(unsigned int l, char *fmt, ...) { (void)l, (void)fmt; }
 
-/* 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)));
+/* messages default to standard streams */
+static void msg_default_(unsigned int, char *, ...) __attribute__((format(printf, 2, 3)));
 static inline
-void warn_(char *fmt, ...) {
+void msg_default_(unsigned int l, char *fmt, ...) {
+    static const char * const msg_tag_[] = { "info", "error", "debug" };
+    FILE *f = (l <= DCPU16_MSG_INFO) ? stderr : stdout;
     va_list ap;
 
-    fprintf(stderr, "[warning] ");
+    if (l < sizeof msg_tag_ / sizeof *msg_tag_)
+        fprintf(f, "[%s] ", msg_tag_[l]);
+    else
+        fprintf(f, "[%u] ", l);
+
     va_start(ap, fmt);
-    vfprintf(stderr, fmt, ap);
+    vfprintf(f, fmt, ap);
     va_end(ap);
-    fprintf(stderr, "\n");
-    fflush(stderr);
+
+    fprintf(f, "\n");
+
+    fflush(f);
+}
+
+/*  dcpu16 message callback
+ *  This function pointer is copied into newly instantiated dcpu16 structures,
+ *  and is invoked directly for messages independant of a dcpu16 context.
+ */
+dcpu16_msg_cb_t *dcpu16_msg_ = msg_default_;
+
+/* set a new default message callback */
+/* returns the previous setting */
+dcpu16_msg_cb_t *dcpu16_msg_set_default(dcpu16_msg_cb_t *msg_cb) {
+    dcpu16_msg_cb_t *r = dcpu16_msg_;
+    dcpu16_msg_ = msg_cb ? msg_cb : msg_null_;
+    return r;
 }
-static void (*warn_cb_)(char *fmt, ...) = warn_;
-void dcpu16_warn_cb_set(void (*fn)(char *fmt, ...)) {
-    warn_cb_ = fn;
+/* set a new callback */
+dcpu16_msg_cb_t *dcpu16_msg_set(struct dcpu16 *vm, dcpu16_msg_cb_t *msg_cb) {
+    dcpu16_msg_cb_t *r = vm->msg_cb_;
+    vm->msg_cb_ = msg_cb ? msg_cb : msg_null_;
+    return r;
 }
 
-#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)));
+/*  acct_event_
+ *  invokes callbacks for specified event
+ */
 static inline
-void trace_(char *fmt, ...) {
-    va_list ap;
+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) )
+            if (addr >= cb[i].addr_l && addr <= cb[i].addr_h)
+                cb[i].fn(vm, ev, addr, cb[i].data);
+    }
+}
 
-    fprintf(stdout, "[debug] ");
-    va_start(ap, fmt);
-    vfprintf(stdout, fmt, ap);
-    va_end(ap);
-    fprintf(stdout, "\n");
-    fflush(stdout);
+
+/* add an entry to the interrupt queue */
+static
+int interrupt_enqueue_(struct dcpu16 *vm, DCPU16_WORD message) {
+    vm->interrupts_[vm->interrupts_tail_] = message;
+    vm->interrupts_tail_ += 1;
+    vm->interrupts_tail_ %= DCPU16_INTERRUPT_QUEUE_SIZE;
+
+    if (vm->interrupts_tail_ == vm->interrupts_head_) {
+        vm->on_fire_ = 1;
+        MSG_INFO(vm, "interrupt queue overflow (system is now on fire)");
+        return -1;
+    }
+
+    return 0;
 }
-#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;
+
+static
+DCPU16_WORD interrupt_dequeue_(struct dcpu16 *vm) {
+    DCPU16_WORD message;
+
+    if (vm->interrupts_tail_ == vm->interrupts_head_) {
+        MSG_INFO(vm, "interrupt underflow");
+        return 0;
+    }
+
+    message = vm->interrupts_[vm->interrupts_head_];
+    vm->interrupts_head_ += 1;
+    vm->interrupts_head_ %= DCPU16_INTERRUPT_QUEUE_SIZE;
+
+    return message;
+}
+
+inline
+void dcpu16_cycle_inc(struct dcpu16 *vm, unsigned int n) {
+    size_t i;
+
+    while (n--) {
+        /* new cycle */
+        vm->cycle_ += 1;
+        MSG_DEBUG(vm, "%s>> starting cycle %llu", __func__, vm->cycle_);
+
+        /* signal interested cycle hooks */
+        acct_event_(vm, DCPU16_ACCT_EV_CYCLE, vm->reg[DCPU16_REG_PC]);
+
+        /* signal attached hardware */
+        for (i = 0; i < vm->hw_table_entries_; i++) {
+            MSG_DEBUG(vm, "%s>> notifying %s", __func__, vm->hw_table_[i].mod->name_);
+            if (vm->hw_table_[i].mod->cycle)
+                vm->hw_table_[i].mod->cycle(vm, &vm->hw_table_[i]);
+        }
+    }
 }
 
 /*  value_decode_
- * 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)
+ * sets *v to be the address of the represented value
+ * value_is_a is 0 for b, 1 for a, alters behavior of some operands
+ * value_data is 'nextword' for this operand, ignored if unused
+ * workv is buffer to use to accumulate literal value into, before use. one exists for either potential instruction operand
+ * e_addr is set to a referenced address, for accounting callback
+ * pc_adjust is set to how to change the program counter
+ * stack_adjust is set to how to change the stack pointer
+ * cycle_adjust is set to number of cycles spent looking up operand
+ *
+ * zero all adjustables before decoding first operand, and pass in these values when
+ * decoding next operand..
+ *
  */
-static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WORD **v) {
-    WORD nextword;
-    unsigned int retval = 0;
-
+#define EWHAT_NONE  (0)
+#define EWHAT_REG   (1<<1)
+#define EWHAT_RAM   (1<<2)
+static inline
+void value_decode_(struct dcpu16 *vm, DCPU16_WORD value, unsigned int value_is_a, DCPU16_WORD value_data,
+                  DCPU16_WORD *work_v, DCPU16_WORD **v, DCPU16_WORD *e_addr, enum dcpu16_register_indexes *e_reg, unsigned int *e_what,
+                  short *pc_adjust, short *sp_adjust, unsigned int *cycle_adjust) {
     assert(value <= 0x3f);
 
-    /* does this value indicate a literal */
-    if (value >= 0x1f)
-        retval = 1;
+    DCPU16_WORD pc = (DCPU16_WORD)(vm->reg[DCPU16_REG_PC] + *pc_adjust),
+                sp = (DCPU16_WORD)(vm->reg[DCPU16_REG_SP] + *sp_adjust);
 
-    /* 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;
-    }
+    (void)pc;
+
+#ifdef DEBUG_DECODE
+    MSG_(MSG_DEBUG_DECODE, vm ,"%s>> is_a:%u pc:0x%04x sp:0x%04x value_data:0x%04x\n", 
+                  __func__,
+                  value_is_a,
+                  pc,
+                  sp,
+                  value_data);
+#endif /* DEBUG_DECODE */
 
     if (value <= 0x07) { /* register */
-        *v = d->reg + value;
-        TRACE(">>     %c (0x%04x)",
-              regnames_[value],
+        *e_what = EWHAT_REG;
+        *e_reg = value & 0x07;
+        *v = vm->reg + *e_reg;
+
+#ifdef DEBUG_DECODE
+        MSG_(MSG_DEBUG_DECODE, vm, "%s>>     %s (0x%04x)",
+              __func__,
+              dcpu16_reg_names[value],
               **v);
+#endif /* DEBUG_DECODE */
 
-    } 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);
+        return;
+    }
 
-    } 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)],
+    if (value <= 0x0f) { /* [register] */
+        *e_what = EWHAT_RAM;
+        *e_addr = vm->reg[value & 0x07];
+        *v = &(vm->ram[ *e_addr ]);
+        acct_event_(vm, DCPU16_ACCT_EV_REG_READ, value & 0x07);
+
+#ifdef DEBUG_DECODE
+        MSG_(MSG_DEBUG_DECODE, vm, "%s>>     [%s] [0x%04x] (0x%04x)",
+              __func__,
+              dcpu16_reg_names[value & 0x07],
+              vm->reg[value & 0x07],
               **v);
+#endif /* DEBUG_DECODE */
 
-    } else switch (value) {
-        case 0x18: /* POP / [sp++] */
-        *v = &(d->ram[ d->sp++ ]);
-        TRACE(">>     POP [0x%04x] (0x%04x)",
-              d->sp - 1,
+        return;
+    }
+
+    if (value <= 0x17) { /* [next word + register] */
+        acct_event_(vm, DCPU16_ACCT_EV_REG_WRITE, DCPU16_REG_PC);
+        *pc_adjust += 1; /* consume next word */
+        *cycle_adjust += 1;
+        *e_what = EWHAT_RAM;
+        *e_addr = value_data + vm->reg[value & 0x07];
+        acct_event_(vm, DCPU16_ACCT_EV_REG_READ, value & 0x07);
+        *v = vm->ram + *e_addr;
+
+#ifdef DEBUG_DECODE
+        MSG_(MSG_DEBUG_DECODE, vm, "%s>>     [nextword + %s] [0x%04x + 0x%04x] (0x%04x)",
+              __func__,
+              dcpu16_reg_names[value & 0x07],
+              value_data,
+              vm->reg[value & 0x07],
               **v);
+#endif /* DEBUG_DECODE */
+
+        return;
+    }
+
+    switch (value) {
+        case 0x18: /* PUSH/[--SP] or POP/[SP++] */
+        *e_what = EWHAT_RAM;
+        acct_event_(vm, DCPU16_ACCT_EV_REG_READ, DCPU16_REG_SP);
+        acct_event_(vm, DCPU16_ACCT_EV_REG_WRITE, DCPU16_REG_SP);
+        if (value_is_a == 0) { /* b */
+            *v = &(vm->ram[sp - 1]);
+            *sp_adjust -= 1;
+            *e_addr = sp - 1;
+
+#ifdef DEBUG_DECODE
+            MSG_(MSG_DEBUG_DECODE, vm, "%s>>     PUSH [0x%04x] (0x%04x)", __func__, sp - 1, **v);
+#endif /* DEBUG_DECODE */
+
+        } else { /* a */
+            *v = &(vm->ram[sp]);
+            *sp_adjust += 1;
+            *e_addr = sp;
+
+#ifdef DEBUG_DECODE
+            MSG_(MSG_DEBUG_DECODE, vm, "%s>>     POP [0x%04x] (0x%04x)", __func__, sp, **v);
+#endif /* DEBUG_DECODE */
+
+        }
         break;
 
-        case 0x19: /* PEEK / [sp] */
-        *v = &(d->ram[ d->sp ]);
-        TRACE(">>     PEEK [0x%04x] (0x%04x)",
-              d->sp,
+        case 0x19: /* PEEK/[SP] */
+        acct_event_(vm, DCPU16_ACCT_EV_REG_READ, DCPU16_REG_SP);
+        *e_what = EWHAT_RAM;
+        *v = &(vm->ram[sp]);
+        *e_addr = sp;
+
+#ifdef DEBUG_DECODE
+        MSG_(MSG_DEBUG_DECODE, vm, "%s>>     PEEK [0x%04x] (0x%04x)",
+              __func__,
+              sp,
               **v);
+#endif /* DEBUG_DECODE */
         break;
 
-        case 0x1a: /* PUSH / [--sp] */
-        *v = &(d->ram[ --d->sp ]);
-        TRACE(">>     PUSH [0x%04x] (0x%04x)",
-              d->sp + 1,
+        case 0x1a: /* PICK n */
+        acct_event_(vm, DCPU16_ACCT_EV_REG_READ, DCPU16_REG_SP);
+        *e_what = EWHAT_RAM;
+        *pc_adjust += 1;
+        *cycle_adjust += 1;
+        *e_addr = sp + value_data;
+        *v = vm->ram + *e_addr;
+
+#ifdef DEBUG_DECODE
+        MSG_(MSG_DEBUG_DECODE, vm, "%s>>     PICK 0x%04x [0x%04x] (0x%04x)",
+              __func__,
+              value_data,
+              sp + value_data,
               **v);
+#endif /* DEBUG_DECODE */
         break;
 
         case 0x1b: /* SP */
-        *v = &(d->sp);
-        TRACE(">>     SP (0x%04x)",
+        *e_reg = DCPU16_REG_SP;
+        *e_what = EWHAT_REG;
+        *v = &(vm->reg[DCPU16_REG_SP]);
+
+#ifdef DEBUG_DECODE
+        MSG_(MSG_DEBUG_DECODE, vm, "%s>>     %s (0x%04x)",
+              __func__,
+              dcpu16_reg_names[DCPU16_REG_SP],
               **v);
+#endif /* DEBUG_DECODE */
         break;
 
         case 0x1c: /* PC */
-        *v = &(d->pc);
-        TRACE(">>     PC (0x%04x)", **v);
+        *e_reg = DCPU16_REG_PC;
+        *e_what = EWHAT_REG;
+        *v = &(vm->reg[DCPU16_REG_PC]);
+
+#ifdef DEBUG_DECODE
+        MSG_(MSG_DEBUG_DECODE, vm, "%s>>     %s (0x%04x)",
+              __func__,
+              dcpu16_reg_names[DCPU16_REG_PC],
+              **v);
+#endif /* DEBUG_DECODE */
         break;
 
-        case 0x1d: /* O */
-        *v = &(d->o);
-        TRACE(">>     O (0x%04x)", **v);
+        case 0x1d: /* EX */
+        *e_reg = DCPU16_REG_EX;
+        *e_what = EWHAT_REG;
+        *v = &(vm->reg[DCPU16_REG_EX]);
+
+#ifdef DEBUG_DECODE
+        MSG_(MSG_DEBUG_DECODE, vm, "%s>>     %s (0x%04x)",
+              __func__,
+              dcpu16_reg_names[DCPU16_REG_EX],
+              **v);
+#endif /* DEBUG_DECODE */
         break;
 
         case 0x1e: /* [next word] / [[pc++]] */
-        nextword = d->ram[ d->pc++ ];
-        d->cycle++;
-        *v = &(d->ram[ nextword ]);
-        TRACE(">>     [nextword] [0x%04x] (0x%04x)",
-              nextword,
+        *e_what = EWHAT_RAM;
+        acct_event_(vm, DCPU16_ACCT_EV_REG_WRITE, DCPU16_REG_PC);
+        *pc_adjust += 1;
+        *cycle_adjust += 1;
+        *e_addr = value_data;
+        *v = vm->ram + *e_addr;
+
+#ifdef DEBUG_DECODE
+        MSG_(MSG_DEBUG_DECODE, vm, "%s>>     [nextword] [0x%04x] (0x%04x)",
+              __func__,
+              value_data,
               **v);
+#endif /* DEBUG_DECODE */
         break;
 
         case 0x1f: /* next word (literal) / [pc++] */
-        nextword = d->ram[ d->pc++ ];
-        d->cycle++;
-        *work_v = nextword;
+        *e_what = EWHAT_NONE;
+        acct_event_(vm, DCPU16_ACCT_EV_REG_WRITE, DCPU16_REG_PC);
+        *pc_adjust += 1;
+        *cycle_adjust += 1;
+        *work_v = value_data;
         *v = work_v;
-        TRACE(">>     nextword (0x%04x)", **v);
+
+#ifdef DEBUG_DECODE
+        MSG_(MSG_DEBUG_DECODE, vm, "%s>>     nextword (0x%04x)",
+              __func__,
+              **v);
+#endif /* DEBUG_DECODE */
         break;
 
-        default: /* 0x20-0x3f: literal values 0x00-0x1f */
-        *work_v = value & 0x1f;
+        default: /* 0x20-0x3f: literal values 0xffff-0x1e */
+        *e_what = EWHAT_NONE;
+        *work_v = (value & 0x1f) - 1;
         *v = work_v;
-        TRACE(">>     literal (0x%04x)", **v);
-    }
 
-    return retval;
+#ifdef DEBUG_DECODE
+        MSG_(MSG_DEBUG_DECODE, vm, "%s>>     literal (0x%04x)",
+              __func__,
+              **v);
+#endif /* DEBUG_DECODE */
+    }
 }
 
-#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, 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)
+/* opcode doesn't adjust its own PC, the step function which invoked it handles that */
+/* opcode does adjust stack and cycle count */
 
-#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;\
-    unsigned int lit_a;\
+#define OP_IMPL(x) static void op_##x(struct dcpu16 *vm, DCPU16_WORD val_b, DCPU16_WORD val_b_data, DCPU16_WORD val_a, DCPU16_WORD val_a_data)
+
+#define OP_TYPE(op_type) DCPU16_WORD *a, *b;\
+    DCPU16_WORD ev_a_addr = 0, ev_b_addr = 0;\
+    enum dcpu16_register_indexes ev_a_reg = DCPU16_REG__NUM, ev_b_reg = DCPU16_REG__NUM;\
+    unsigned int ev_a_what = 0, ev_b_what = 0;\
+    short pc_adjust = 0, sp_adjust = 0;\
+    unsigned int cycle_adjust = 0;\
     do {\
-        lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
         op_type;\
-        if (d->skip_) {\
-            TRACE("++ SKIPPED");\
-            d->skip_ = 0;\
-            return;\
-        }\
+        value_decode_(vm, val_a, 1, val_a_data,\
+                      &vm->reg_work_[1], &a, &ev_a_addr, &ev_a_reg, &ev_a_what,\
+                      &pc_adjust, &sp_adjust, &cycle_adjust);\
+        vm->reg[DCPU16_REG_SP] += sp_adjust;\
+        if (cycle_adjust) dcpu16_cycle_inc(vm, cycle_adjust);\
     } while (0)
+#define OP_NBI_ (void)val_b, (void)b, (void)ev_b_addr, (void)val_b_data, (void)ev_b_reg, (void)ev_b_what
+#define OP_BASIC_ value_decode_(vm, val_b, 0, val_b_data,\
+                                &vm->reg_work_[0], &b, &ev_b_addr, &ev_b_reg, &ev_b_what,\
+                                &pc_adjust, &sp_adjust, &cycle_adjust)
 #define OP_BASIC(x) OP_TYPE(OP_BASIC_)
 #define OP_NBI(x) OP_TYPE(OP_NBI_)
 
+/* after invoking one of these header macros, the instruction and operands will have been decoded, and the control registers have been adjusted to the next instruction et cetera */
+
+/*
+    accounting helpers, these fire off the related callbacks for memory reads,
+    memory writes, and execution of reserved instructions
+ */
+#define ACCT_ILL(addr) do { acct_event_(vm, DCPU16_ACCT_EV_NOP, addr); } while (0)
+#define ACCT_RAM_R(addr) do { acct_event_(vm, DCPU16_ACCT_EV_READ, addr); } while (0)
+#define ACCT_RAM_W(addr) do { acct_event_(vm, DCPU16_ACCT_EV_WRITE, addr); } while (0)
+#define ACCT_REG_R(reg) do { acct_event_(vm, DCPU16_ACCT_EV_REG_READ, reg); } while (0)
+#define ACCT_REG_W(reg) do { acct_event_(vm, DCPU16_ACCT_EV_REG_WRITE, reg); } while (0)
+
+#define ACCT_R(__x__) do {\
+    if (ev_##__x__##_what & EWHAT_REG) ACCT_REG_R(ev_##__x__##_reg);\
+    if (ev_##__x__##_what & EWHAT_RAM) ACCT_RAM_R(ev_##__x__##_addr);\
+} while (0)
+#define ACCT_W(__x__) do {\
+    if (ev_##__x__##_what & EWHAT_REG) ACCT_REG_W(ev_##__x__##_reg);\
+    if (ev_##__x__##_what & EWHAT_RAM) ACCT_RAM_W(ev_##__x__##_addr);\
+} while (0)
 
 /* extended opcodes */
 
@@ -242,32 +480,235 @@ 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);
+    /* fire an illegal instruction event for current instruction */
+    DCPU16_WORD future_opcode = (vm->ram[vm->reg[DCPU16_REG_PC] - pc_adjust] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_B_BITS));
+
+    MSG_INFO(vm, "reserved future opcode 0x%04x invoked", future_opcode);
+
+    ACCT_ILL(vm->reg[DCPU16_REG_PC] - pc_adjust);
+
+    dcpu16_cycle_inc(vm, 1);
 }
 
 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;
+    ACCT_R(a);
+
+    ACCT_REG_R(DCPU16_REG_PC);
+    ACCT_REG_R(DCPU16_REG_SP);
+    vm->ram[ --vm->reg[DCPU16_REG_SP] ] = vm->reg[DCPU16_REG_PC];
+    ACCT_REG_W(DCPU16_REG_SP);
+    ACCT_RAM_W(vm->reg[DCPU16_REG_SP] + 1);
+
+    vm->reg[DCPU16_REG_PC] = *a;
+    ACCT_REG_W(DCPU16_REG_PC);
 
-    d->cycle += 2;
+
+    dcpu16_cycle_inc(vm, 2);
 }
 
 OP_IMPL(nbi__reserved2_) {
     OP_NBI(nbi__reserved2_);
     /* reserved */
 
-    WARN("reserved nbi opcode invoked");
+    MSG_INFO(vm, "reserved nbi opcode invoked");
+
+    ACCT_ILL(vm->reg[DCPU16_REG_PC] - pc_adjust);
+
+    dcpu16_cycle_inc(vm, 1);
+}
+
+OP_IMPL(nbi_int) {
+    OP_NBI(nbi_int);
+
+    ACCT_R(a);
+
+    ACCT_REG_R(DCPU16_REG_IA);
+    if (vm->reg[DCPU16_REG_IA]) {
+        if ( interrupt_enqueue_(vm, *a) ) {
+            MSG_INFO(vm, "failed to queue interrupt");
+            return;
+        }
+
+        if (vm->interrupts_deferred_)
+            return;
+
+        vm->interrupts_deferred_ = 1;
+
+        ACCT_REG_R(DCPU16_REG_PC);
+        ACCT_REG_R(DCPU16_REG_SP);
+        vm->ram[--vm->reg[DCPU16_REG_SP]] = vm->reg[DCPU16_REG_PC];
+        ACCT_RAM_W(vm->reg[DCPU16_REG_SP] + 1);
+        ACCT_REG_W(DCPU16_REG_SP);
+
+        ACCT_REG_R(DCPU16_REG_A);
+        ACCT_REG_R(DCPU16_REG_SP);
+        vm->ram[--vm->reg[DCPU16_REG_SP]] = vm->reg[DCPU16_REG_A];
+        ACCT_RAM_W(vm->reg[DCPU16_REG_SP] + 1);
+        ACCT_REG_W(DCPU16_REG_SP);
+
+        ACCT_REG_R(DCPU16_REG_IA);
+        vm->reg[DCPU16_REG_PC] = vm->reg[DCPU16_REG_IA];
+        ACCT_REG_W(DCPU16_REG_PC);
+
+        vm->reg[DCPU16_REG_A] = *a;
+        ACCT_REG_W(DCPU16_REG_A);
+    }
+
+    dcpu16_cycle_inc(vm, 4);
+}
+
+OP_IMPL(nbi_iag) {
+    OP_NBI(nbi_iag);
+
+    ACCT_REG_R(DCPU16_REG_IA);
+    *a = vm->reg[DCPU16_REG_IA];
+
+    ACCT_W(a);
+
+    dcpu16_cycle_inc(vm, 1);
+}
+
+OP_IMPL(nbi_ias) {
+    OP_NBI(nbi_ias);
+
+    ACCT_R(a);
+
+    vm->reg[DCPU16_REG_IA] = *a;
+    ACCT_REG_W(DCPU16_REG_IA);
+
+    dcpu16_cycle_inc(vm, 1);
+}
+
+/* does this just ignore its operand? */
+OP_IMPL(nbi_rfi) {
+    OP_NBI(nbi_rfi);
+
+    /* well, it consumes the argument, currently, so I guess pretend like we care */
+    ACCT_R(a);
+
+    vm->interrupts_deferred_ = 0;
+
+    ACCT_REG_R(DCPU16_REG_SP);
+    ACCT_RAM_R(vm->reg[DCPU16_REG_SP]);
+    vm->reg[DCPU16_REG_A] = vm->ram[vm->reg[DCPU16_REG_SP]++];
+    ACCT_REG_W(DCPU16_REG_A);
+    ACCT_REG_W(DCPU16_REG_SP);
+
+    ACCT_REG_R(DCPU16_REG_SP);
+    ACCT_RAM_R(vm->reg[DCPU16_REG_SP]);
+    vm->reg[DCPU16_REG_PC] = vm->ram[vm->reg[DCPU16_REG_SP]++];
+    ACCT_REG_W(DCPU16_REG_PC);
+    ACCT_REG_W(DCPU16_REG_SP);
+
+    dcpu16_cycle_inc(vm, 3);
+}
+
+OP_IMPL(nbi_iaq) {
+    OP_NBI(nbi_iaq);
+
+    ACCT_R(a);
+
+    if (*a) {
+        vm->interrupts_deferred_ = 1;
+    } else {
+        vm->interrupts_deferred_ = 0;
+    }
+
+    dcpu16_cycle_inc(vm, 2);
+}
+
+OP_IMPL(nbi_hwn) {
+    OP_NBI(nbi_hwn);
+
+    *a = vm->hw_table_entries_;
+    ACCT_W(a);
+
+    dcpu16_cycle_inc(vm, 2);
+}
+
+OP_IMPL(nbi_hwq) {
+    OP_NBI(nbi_hwq);
+
+    ACCT_R(a);
+
+    if (*a >= vm->hw_table_entries_) {
+        MSG_INFO(vm, "hardware query for non-extant device 0x%04x", *a);
+        vm->reg[DCPU16_REG_A] = 0;
+        vm->reg[DCPU16_REG_B] = 0;
+        vm->reg[DCPU16_REG_C] = 0;
+        vm->reg[DCPU16_REG_X] = 0;
+        vm->reg[DCPU16_REG_Y] = 0;
+    } else {
+        vm->reg[DCPU16_REG_A] = vm->hw_table_[*a].mod->id_l;
+        vm->reg[DCPU16_REG_B] = vm->hw_table_[*a].mod->id_h;
+        vm->reg[DCPU16_REG_C] = vm->hw_table_[*a].mod->ver;
+        vm->reg[DCPU16_REG_X] = vm->hw_table_[*a].mod->mfg_l;
+        vm->reg[DCPU16_REG_Y] = vm->hw_table_[*a].mod->mfg_h;
+    }
+
+    ACCT_REG_W(DCPU16_REG_A);
+    ACCT_REG_W(DCPU16_REG_B);
+    ACCT_REG_W(DCPU16_REG_C);
+    ACCT_REG_W(DCPU16_REG_X);
+    ACCT_REG_W(DCPU16_REG_Y);
+
+    dcpu16_cycle_inc(vm, 4);
+}
+
+OP_IMPL(nbi_hwi) {
+    OP_NBI(nbi_hwi);
+
+    ACCT_R(a);
+
+    if (*a > vm->hw_table_entries_) {
+        MSG_INFO(vm, "interrupt for non-extant device 0x%04x", *a);
+        return;
+    }
+
+    if (vm->hw_table_[*a].mod->hwi)
+        vm->hw_table_[*a].mod->hwi(vm, &vm->hw_table_[*a]);
+    else
+        MSG_INFO(vm, "hardware 0x%04x has no interrupt handler", *a);
+
+    dcpu16_cycle_inc(vm, 4);
+}
+
+OP_IMPL(nbi_hcf) {
+    OP_NBI(nbi_hcf);
+
+    ACCT_R(a);
+
+    vm->on_fire_ = 1;
+    MSG_INFO(vm, "system on fire");
+
+    dcpu16_cycle_inc(vm, 9);
 }
 
 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}
+    {0x00, "(reserved)", op_nbi__reserved_},
+    {0x01, "JSR", op_nbi_jsr},
+    {0x02, "(reserved)", op_nbi__reserved2_},
+    {0x03, "(reserved)", op_nbi__reserved2_},
+    {0x04, "(reserved)", op_nbi__reserved2_},
+    {0x05, "(reserved)", op_nbi__reserved2_},
+    {0x06, "(reserved)", op_nbi__reserved2_},
+    {0x07, "HCF", op_nbi_hcf}, /* undocumented */
+    {0x08, "INT", op_nbi_int},
+    {0x09, "IAG", op_nbi_iag},
+    {0x0a, "IAS", op_nbi_ias},
+    {0x0b, "RFI", op_nbi_rfi},
+    {0x0c, "IAQ", op_nbi_iaq},
+    {0x0d, "(reserved)", op_nbi__reserved2_},
+    {0x0e, "(reserved)", op_nbi__reserved2_},
+    {0x0f, "(reserved)", op_nbi__reserved2_},
+    {0x10, "HWN", op_nbi_hwn},
+    {0x11, "HWQ", op_nbi_hwq},
+    {0x12, "HWI", op_nbi_hwi},
+    {0x13, "(reserved)", op_nbi__reserved2_},
+    {0x00, "", NULL}
 };
 #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1)
 
@@ -275,377 +716,992 @@ static const struct opcode_entry opcode_nbi_entries[] = {
 /* basic opcodes */
 
 /*
-    N.B. the following function does not decode values, as the nbi
-    instructions only have one operand.
+    N.B. the following function does not decode values.
+    Decoding is handled by the secondary opcode functions it calls.
 */
 OP_IMPL(_nbi_) {
     /* non-basic instruction */
 
     /* don't do normal value decoding here */
 
-    WORD nbi_opcode = val_a;
+    DCPU16_WORD nbi_opcode = val_b;
     const struct opcode_entry *e = opcode_nbi_entries;
 
+    (void)val_b_data;
+
     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);
+    MSG_DEBUG(vm, "%s>> %s 0x%04x", __func__, e->name, val_b);
+    e->impl(vm, 0, 0, val_a, val_a_data);
 }
 
 OP_IMPL(set) {
     OP_BASIC(set);
-    /* sets a to b */
+    /* sets b to a */
 
-    /* only set non-literal target */
-    if (val_a < 0x1f) {
-        *a = *b;
-    }
+    ACCT_R(a);
+
+    /*
+        if b is a literal, it's aimed at a scratch register,
+        so it's fine to update, as it won't have any effect.
+     */
+    *b = *a;
+
+    ACCT_W(b);
 
-    d->cycle += 1;
+    dcpu16_cycle_inc(vm, 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;
+    /* sets b to b+a, sets EX to 0x0001 if there's an overflow, 0x0 otherwise */
+    unsigned int acc = *b + *a;
 
-    if (val_a < 0x1f) {
-        *a = acc;
-    }
-    d->o = (acc > 0xffff);
+    ACCT_R(b);
+    ACCT_R(a);
+
+    *b = acc;
+    vm->reg[DCPU16_REG_EX] = (acc > 0xffff);
+
+    ACCT_REG_W(DCPU16_REG_EX);
 
-    d->cycle += 2;
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 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;
+    /* sets b to b-a, sets EX to 0xffff if there's an underflow, 0x0 otherwise */
+    unsigned int acc = *b - *a;
 
-    if (val_a < 0x1f) {
-        *a = acc;
-    d->o = (acc > 0xffff);
+    ACCT_R(b);
+    ACCT_R(a);
 
-    }
-    d->cycle += 2;
+    *b = acc;
+    vm->reg[DCPU16_REG_EX] = (acc > 0xffff);
+
+    ACCT_REG_W(DCPU16_REG_EX);
+
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 2);
 }
 
 OP_IMPL(mul) {
     OP_BASIC(mul);
-    /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
-    unsigned int acc = *a * *b;
+    /* sets b to b*a, unsigned, sets EX to ((b*a)>>16)&0xffff */
+    unsigned int acc = *b * *a;
 
-    if (val_a < 0x1f) {
-        *a = acc;
-    }
-    d->o = acc >> 16;
-    d->cycle += 2;
+    ACCT_R(b);
+    ACCT_R(a);
+
+    *b = acc;
+    vm->reg[DCPU16_REG_EX] = acc >> 16;
+
+    ACCT_REG_W(DCPU16_REG_EX);
+
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 2);
+}
+
+OP_IMPL(mli) {
+    OP_BASIC(mli);
+    /* sets b to b*a, signed */
+    int acc = (short)*b * (short)*a;
+
+    ACCT_R(b);
+    ACCT_R(a);
+
+    *b = acc;
+    vm->reg[DCPU16_REG_EX] = acc >> 16;
+
+    ACCT_REG_W(DCPU16_REG_EX);
+
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 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. */
+    /* sets b to b/a, sets EX to ((b<<16)/a)&0xffff. if a==0, sets a and EX to 0 instead. */
 
-    if (*b == 0) {
-        if (val_a < 0x1f) {
-            *a = 0;
-        }
-        d->o = 0;
+    ACCT_R(b);
+    ACCT_R(a);
+
+    if (*a == 0) {
+        *b = 0;
+        vm->reg[DCPU16_REG_EX] = 0;
     } else {
-        unsigned int acc = *a / *b;
+        *b = *b / *a;
+        vm->reg[DCPU16_REG_EX] = (*b << 16) / *a;
+    }
 
-        if (val_a < 0x1f) {
-            *a = acc;
-        }
+    ACCT_REG_W(DCPU16_REG_EX);
 
-        acc = (*a << 16) / *b;
-        d->o = acc;
-    }
+    ACCT_W(b);
 
-    d->cycle += 3;
+    dcpu16_cycle_inc(vm, 3);
 }
 
-OP_IMPL(mod) {
-    OP_BASIC(mod);
-    /* sets a to a%b. if b==0, sets a to 0 instead. */
+OP_IMPL(dvi) {
+    OP_BASIC(dvi);
+    /* sets b to b/a, signed, round towards 0 */
 
-    if (*b == 0) {
-        if (val_a < 0x1f) {
-            *a = 0;
-        }
+    ACCT_R(b);
+    ACCT_R(a);
+
+    if (*a == 0) {
+        *b = 0;
+        vm->reg[DCPU16_REG_EX] = 0;
     } else {
-        if (val_a < 0x1f) {
-            *a = *a % *b;
-        }
+        *b = (short)*b / (short)*a;
+        vm->reg[DCPU16_REG_EX] = (short)(*b << 16) / (short)*a;
     }
 
-    d->cycle += 3;
+    ACCT_REG_W(DCPU16_REG_EX);
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 3);
 }
 
-OP_IMPL(shl) {
-    OP_BASIC(shl);
-    /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
-    unsigned int acc = *a << *b;
+OP_IMPL(mod) {
+    OP_BASIC(mod);
+    /* sets b to b%a. if a==0, sets b to 0 instead. */
+
+    ACCT_R(b);
+    ACCT_R(a);
 
-    if (val_a < 0x1f) {
-        *a = acc;
+    if (*a == 0) {
+        *b = 0;
+    } else {
+        *b = *b % *a;
     }
-    d->o = acc >> 16;
 
-    d->cycle += 2;
+    ACCT_W(a);
+
+    dcpu16_cycle_inc(vm, 3);
 }
 
-OP_IMPL(shr) {
-    OP_BASIC(shr);
-    /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
-    unsigned int acc = *a >> *b;
+OP_IMPL(mdi) {
+    OP_BASIC(mdi);
+    /* sets b to b%a, signed */
+
+    ACCT_R(b);
+    ACCT_R(a);
 
-    if (val_a < 0x1f) {
-        *a = acc;
+    if (*a == 0) {
+        *b = 0;
+    } else {
+        *b = (short)*b % (short)*a;
     }
-    d->o = (*a << 16) >> *b;
 
-    d->cycle += 2;
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 3);
 }
 
 OP_IMPL(and) {
     OP_BASIC(and);
-    /* sets a to a&b */
+    /* sets b to b&a */
 
-    if (val_a < 0x1f) {
-        *a = *a & *b;
-    }
+    ACCT_R(b);
+    ACCT_R(a);
+
+    *b = *b & *a;
 
-    d->cycle += 1;
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 1);
 }
 
 OP_IMPL(bor) {
     OP_BASIC(bor);
-    /* sets a to a|b */
+    /* sets b to b|a */
 
-    if (val_a < 0x1f) {
-        *a = *a | *b;
-    }
+    ACCT_R(b);
+    ACCT_R(a);
+
+    *b = *b | *a;
 
-    d->cycle += 1;
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 1);
 }
 
 OP_IMPL(xor) {
     OP_BASIC(xor);
-    /* sets a to a^b */
+    /* sets b to b^a */
+
+    ACCT_R(b);
+    ACCT_R(a);
+
+    *b = *b ^ *a;
+
+    dcpu16_cycle_inc(vm, 1);
+
+    ACCT_W(b);
+}
+
+OP_IMPL(shr) {
+    OP_BASIC(shr);
+    /* sets b to b>>>a, sets EX to ((b<<16)>>a)&0xffff */
+    unsigned int acc = *b >> *a;
+
+    ACCT_R(b);
+    ACCT_R(a);
+
+    *b = acc & 0xffff;
+    vm->reg[DCPU16_REG_EX] = (*b << 16) >> *a;
+
+    MSG_ERROR(vm, "IMPLEMENT");
+
+    ACCT_REG_W(DCPU16_REG_EX);
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 2);
+}
+
+OP_IMPL(asr) {
+    OP_BASIC(asr);
+    /* sets b to b>>a, sets EX to ((b<<16)>>>a)&0xffff (arithmetic shift) (treats b as signed) */
+    unsigned int acc = *b << *a;
+
+    ACCT_R(b);
+    ACCT_R(a);
+
+    *b = acc & 0xffff;
+    vm->reg[DCPU16_REG_EX] = (*b << 16) >> *a;
+
+    MSG_ERROR(vm, "IMPLEMENT");
+
+    ACCT_REG_W(DCPU16_REG_EX);
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 2);
+}
+
+OP_IMPL(shl) {
+    OP_BASIC(shl);
+    /* sets b to b<<a, sets EX to ((b<<a)>>16)&0xffff */
+    unsigned int acc = *b << *a;
+
+    ACCT_R(b);
+    ACCT_R(a);
+
+    *b = acc;
+
+    vm->reg[DCPU16_REG_EX] = acc >> 16;
+
+    ACCT_REG_W(DCPU16_REG_EX);
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 2);
+}
+
+OP_IMPL(ifb) {
+    OP_BASIC(ifb);
+    /* performs next instruction only if (b&a)!=0 */
+
+    ACCT_R(b);
+    ACCT_R(a);
+
+    if ((*b & *a) != 0) {
+        /* */
+    } else {
+        vm->skip_ = 1;
+        dcpu16_cycle_inc(vm, 1);
+    }
 
-    if (val_a < 0x1f) {
-        *a = *a ^ *b;
+    dcpu16_cycle_inc(vm, 2);
+}
+
+OP_IMPL(ifc) {
+    OP_BASIC(ifc);
+    /* performs next instruction only if (b&a)==0 */
+
+    ACCT_R(b);
+    ACCT_R(a);
+
+    if ((*b & *a) == 0) {
+        /* */
+    } else {
+        vm->skip_ = 1;
+        dcpu16_cycle_inc(vm, 1);
     }
 
-    d->cycle += 1;
+    dcpu16_cycle_inc(vm, 2);
 }
 
 OP_IMPL(ife) {
     OP_BASIC(ife);
-    /* performs next instruction only if a==b */
+    /* performs next instruction only if b==a */
 
-    if (*a == *b) {
+    ACCT_R(b);
+    ACCT_R(a);
+
+    if (*b == *a) {
         /* */
     } else {
-        d->skip_ = 1;
-        d->cycle++;
+        vm->skip_ = 1;
+        dcpu16_cycle_inc(vm, 1);
     }
 
-    d->cycle += 2;
+    dcpu16_cycle_inc(vm, 2);
 }
 
 OP_IMPL(ifn) {
     OP_BASIC(ifn);
-    /* performs next instruction only if a!=b */
+    /* performs next instruction only if b!=a */
+
+    ACCT_R(b);
+    ACCT_R(a);
 
-    if (*a != *b) {
+    if (*b != *a) {
         /* */
     } else {
-        d->skip_ = 1;
-        d->cycle++;
+        vm->skip_ = 1;
+        dcpu16_cycle_inc(vm, 1);
     }
 
-    d->cycle += 2;
+    dcpu16_cycle_inc(vm, 2);
 }
 
 OP_IMPL(ifg) {
     OP_BASIC(ifg);
-    /* performs next instruction only if a>b */
+    /* performs next instruction only if b>a */
+
+    ACCT_R(b);
+    ACCT_R(a);
 
-    if (*a > *b) {
+    if (*b > *a) {
         /* */
     } else {
-        d->skip_ = 1;
-        d->cycle++;
+        vm->skip_ = 1;
+        dcpu16_cycle_inc(vm, 1);
     }
 
-    d->cycle += 2;
+    dcpu16_cycle_inc(vm, 2);
 }
 
-OP_IMPL(ifb) {
-    OP_BASIC(ifb);
-    /* performs next instruction only if (a&b)!=0 */
+OP_IMPL(ifa) {
+    OP_BASIC(ifa);
+    /* performs next instruction only if b>a (signed) */
+
+    ACCT_R(b);
+    ACCT_R(a);
+
+    if (*b > *a) {
+        /* */
+    } else {
+        vm->skip_ = 1;
+        dcpu16_cycle_inc(vm, 1);
+    }
+
+    dcpu16_cycle_inc(vm, 2);
+}
 
-    if ((*a & *b) != 0) {
+OP_IMPL(ifl) {
+    OP_BASIC(ifl);
+    /* performs next instruction only if b<a */
+
+    ACCT_R(b);
+    ACCT_R(a);
+
+    if (*b < *a) {
+        /* */
+    } else {
+        vm->skip_ = 1;
+        dcpu16_cycle_inc(vm, 1);
+    }
+
+    dcpu16_cycle_inc(vm, 2);
+}
+
+OP_IMPL(ifu) {
+    OP_BASIC(ifu);
+    /* performs next instruction only if b<a (signed) */
+
+    ACCT_R(b);
+    ACCT_R(a);
+
+    if (*b < *a) {
         /* */
     } else {
-        d->skip_ = 1;
-        d->cycle++;
+        vm->skip_ = 1;
+        dcpu16_cycle_inc(vm, 1);
     }
 
-    d->cycle += 2;
+    dcpu16_cycle_inc(vm, 2);
+}
+
+OP_IMPL(adx) {
+    OP_BASIC(adx);
+    /* sets b to b+a+EX, sets EX to 0x0001 if overflow, 0x0 otherwise */
+    unsigned int acc;
+
+    ACCT_R(b);
+    ACCT_R(a);
+
+    ACCT_REG_R(DCPU16_REG_EX);
+
+    acc = *b + *a + vm->reg[DCPU16_REG_EX];
+    *b = acc & 0xffff;
+    if (acc > 0xffff)
+        vm->reg[DCPU16_REG_EX] = 0x0001;
+    else
+        vm->reg[DCPU16_REG_EX] = 0x0000;
+
+    ACCT_REG_W(DCPU16_REG_EX);
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 3);
+}
+
+OP_IMPL(sbx) {
+    OP_BASIC(sbx);
+    /* sets b to b-a+EX, sets EX to 0xffff if underflow, 0x0 otherwise */
+    unsigned int acc;
+
+    ACCT_R(b);
+    ACCT_R(a);
+
+    ACCT_REG_R(DCPU16_REG_EX);
+
+    acc = *b - *a + vm->reg[DCPU16_REG_EX];
+    *b = acc & 0xffff;
+    if (acc > 0xffff)
+        vm->reg[DCPU16_REG_EX] = 0xffff;
+    else
+        vm->reg[DCPU16_REG_EX] = 0;
+
+    ACCT_REG_W(DCPU16_REG_EX);
+
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 3);
+}
+
+OP_IMPL(sti) {
+    OP_BASIC(sti);
+    /* sets b to a, then increases I and J by 1 */
+
+    ACCT_R(b);
+    ACCT_R(a);
+
+    *b = *a;
+    vm->reg[DCPU16_REG_I] += 1;
+    vm->reg[DCPU16_REG_J] += 1;
+
+    ACCT_REG_W(DCPU16_REG_I);
+    ACCT_REG_W(DCPU16_REG_J);
+
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 2);
+}
+
+OP_IMPL(std) {
+    OP_BASIC(std);
+    /* sets b to a, then decreases I and J by 1 */
+
+    ACCT_R(b);
+    ACCT_R(a);
+
+    *b = *a;
+    vm->reg[DCPU16_REG_I] -= 1;
+    vm->reg[DCPU16_REG_J] -= 1;
+
+    ACCT_REG_W(DCPU16_REG_I);
+    ACCT_REG_W(DCPU16_REG_J);
+
+    ACCT_W(b);
+
+    dcpu16_cycle_inc(vm, 2);
+}
+
+OP_IMPL(_reserved_) {
+    OP_BASIC(_reserved_);
+
+    MSG_INFO(vm, "reserved opcode invoked");
+
+    ACCT_ILL(vm->reg[DCPU16_REG_PC] - pc_adjust);
 }
 
 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 }
+    {0x00, "(nbi)", op__nbi_},
+    {0x01, "SET", op_set },
+    {0x02, "ADD", op_add },
+    {0x03, "SUB", op_sub },
+    {0x04, "MUL", op_mul },
+    {0x05, "MLI", op_mli },
+    {0x06, "DIV", op_div },
+    {0x07, "DVI", op_dvi },
+    {0x08, "MOD", op_mod },
+    {0x09, "MDI", op_mdi },
+    {0x0a, "AND", op_and },
+    {0x0b, "BOR", op_bor },
+    {0x0c, "XOR", op_xor },
+    {0x0d, "SHR", op_shr },
+    {0x0e, "ASR", op_asr },
+    {0x0f, "SHL", op_shl },
+    {0x10, "IFB", op_ifb },
+    {0x11, "IFC", op_ifc },
+    {0x12, "IFE", op_ife },
+    {0x13, "IFN", op_ifn },
+    {0x14, "IFG", op_ifg },
+    {0x15, "IFA", op_ifa },
+    {0x16, "IFL", op_ifl },
+    {0x17, "IFU", op_ifu },
+    {0x18, "(reserved)", op__reserved_ },
+    {0x19, "(reserved)", op__reserved_ },
+    {0x1a, "ADX", op_adx },
+    {0x1b, "SBX", op_sbx },
+    {0x1c, "(reserved)", op__reserved_ },
+    {0x1d, "(reserved)", op__reserved_ },
+    {0x1e, "STI", op_sti },
+    {0x1f, "STD", op_std },
+    {0x00, "", NULL }
 };
+#define OPCODE_BASIC_MAX (((sizeof(opcode_basic_entries)) / (sizeof(struct opcode_entry))) - 1)
 
-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]);
+static inline
+void dump_operand_value_(DCPU16_WORD value, DCPU16_WORD nextword, unsigned int value_position) {
+    printf(" ");
+    if (value <= 0x07) {
+        printf("%s", dcpu16_reg_names[value]);
+    } else if (value <= 0x0f) {
+        printf("[%s]", dcpu16_reg_names[value & 0x07]);
+    } else if (value <= 0x17) {
+        printf("[0x%04x + %s]", nextword, dcpu16_reg_names[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);
+        case 0x18:
+            if (value_position == 0) { /* b */
+                printf("PUSH");
+            } else {
+                printf("POP");
+            }
+            break;
+        case 0x19: printf("PEEK"); break;
+        case 0x1a: printf("PICK 0x%04x", nextword); break;
+        case 0x1b: printf("SP"); break;
+        case 0x1c: printf("PC"); break;
+        case 0x1d: printf("EX"); break;
+        case 0x1e: printf("[0x%04x]", nextword); break;
+        case 0x1f: printf("0x%04x", nextword); break;
+        default:   printf("0x%02x", value - 0x21);
     }
 }
 
-void dcpu16_disassemble_print(struct dcpu16 *d, WORD addr) {
-    WORD opcode, a, b;
-    unsigned int instr_len = 1;
-    const struct opcode_entry *e;
+static inline
+int operand_snprint_(char *buf, size_t buf_sz, DCPU16_WORD value, DCPU16_WORD nextword, unsigned int operand_is_a) {
+    int len;
+
+    len = snprintf(buf, buf_sz, " ");
+    if ((size_t)len >= buf_sz)
+        return -1;
 
-    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); 
+    buf += len, buf_sz -= len;
 
-    assert(opcode <= 0x0f);
-    assert(a <= 0x3f);
-    assert(b <= 0x3f);
+    if (value <= 0x07) {
+        len = snprintf(buf, buf_sz, "%s", dcpu16_reg_names[value]);
+    } else if (value <= 0x0f) {
+        len = snprintf(buf, buf_sz, "[%s]", dcpu16_reg_names[value & 0x07]);
+    } else if (value <= 0x17) {
+        len = snprintf(buf, buf_sz, "[0x%04x + %s]", nextword, dcpu16_reg_names[value & 0x07]);
+    } else switch (value) {
+        case 0x18:
+            if (operand_is_a == 0) { /* b */
+                len = snprintf(buf, buf_sz, "PUSH");
+            } else {
+                len = snprintf(buf, buf_sz, "POP");
+            }
+            break;
+        case 0x19: len = snprintf(buf, buf_sz, "PEEK"); break;
+        case 0x1a: len = snprintf(buf, buf_sz, "PICK 0x%04x", nextword); break;
+        case 0x1b: len = snprintf(buf, buf_sz, "SP"); break;
+        case 0x1c: len = snprintf(buf, buf_sz, "PC"); break;
+        case 0x1d: len = snprintf(buf, buf_sz, "EX"); break;
+        case 0x1e: len = snprintf(buf, buf_sz, "[0x%04x]", nextword); break;
+        case 0x1f: len = snprintf(buf, buf_sz, "0x%04x", nextword); break;
+        default:   len = snprintf(buf, buf_sz, "0x%02x", (short)(value - 0x21));
+    }
 
-    printf("%04x", d->ram[addr]);
+    if ((size_t)len >= buf_sz)
+        return -1;
 
-    if (opcode != 0)
-    {
-        if (a == 0x1e || a == 0x1f) {
-            printf(" %04x", d->ram[addr + instr_len]);
-            instr_len++;
-        }
+    return len;
+}
+
+
+/* split a sequence of (one to three) words into the components of an instruction */
+static inline
+void instruction_decode_(DCPU16_WORD *mem, DCPU16_WORD addr,
+                         DCPU16_WORD *opcode, DCPU16_WORD *b, DCPU16_WORD **b_data, DCPU16_WORD *a, DCPU16_WORD **a_data,
+                         DCPU16_WORD *instr_len) {
+    *opcode = *a = *b = mem[addr];
+    *opcode = mem[addr] & ((1 << OPCODE_BASIC_BITS) - 1);
+    *b = (mem[addr] >> OPCODE_BASIC_BITS) & ((1 << OPCODE_OPERAND_B_BITS) - 1);
+    *a = (mem[addr] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_B_BITS)) & ((1 << OPCODE_OPERAND_A_BITS) - 1);
+    *instr_len = 1;
+
+    if ((*opcode != 0x0000) &&
+        ( (*b >= 0x10 && *b <= 0x17) || *b == 0x1e || *b == 0x1f ) ) {
+        *b_data = mem + (DCPU16_WORD)(addr + *instr_len);
+        *instr_len += 1;
+    } else {
+        *b_data = NULL;
     }
-    if (b == 0x1e || b == 0x1f) {
-        printf(" %04x", d->ram[addr + instr_len]);
-        instr_len++;
+
+    if ( (*opcode != 0x0000 || (*opcode == 0 && *b != 0x0000) )
+    &&   ( (*a >= 0x10 && *a <= 0x17) || *a == 0x1e || *a == 0x1f) ) {
+        *a_data = mem + (DCPU16_WORD)(addr + *instr_len);
+        *instr_len += 1;
+    } else {
+        *a_data = NULL;
     }
 
-    if (opcode)
-        e = opcode_basic_entries + opcode;
+#ifdef DEBUG_DECODE
+    MSG_(MSG_DEBUG_DECODE, NULL, "\n%s: [0x%04x]:0x%04x op:0x%02x b:0x%02x (b_data:0x%04x) a:0x%02x (a_data:0x%04x) len:0x%02x\n",
+            __func__, 
+            addr,
+            mem[addr],
+            *opcode,
+            *b,
+            *b_data ? **b_data : 0,
+            *a,
+            *a_data ? **a_data : 0,
+            *instr_len);
+#endif /* DEBUG_DECODE */
+}
+
+/*  dcpu16_mnemonify_buf
+    print words as words
+ */
+DCPU16_WORD dcpu16_mnemonify_buf(DCPU16_WORD *buf) {
+    DCPU16_WORD opcode, b, a, instr_len, *b_data, *a_data;
+    const struct opcode_entry *e;
+    char operand[16];
+
+    instruction_decode_(buf, 0, &opcode, &b, &b_data, &a, &a_data, &instr_len);
+
+    if (opcode == 0x0000)
+        e = opcode_nbi_entries +
+            ( (b < OPCODE_NBI_MAX) ? b : (OPCODE_NBI_MAX - 1) );
     else
-        e = opcode_nbi_entries + ( (a < OPCODE_NBI_MAX) ? a : (OPCODE_NBI_MAX - 1) );
+        e = opcode_basic_entries + opcode;
+    printf("%s", e->name);
+
+    if (opcode) {
+        operand_snprint_(operand, sizeof operand, b, b_data ? *b_data : 0, 0);
+        printf("%s,", operand);
+    }
+
+    if (opcode || b) {
+        operand_snprint_(operand, sizeof operand, a, a_data ? *a_data : 0, 1);
+        printf("%s", operand);
+    }
+
+    return 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 *vm, DCPU16_WORD addr) {
+    DCPU16_WORD opcode, b, a, instr_len, i, *b_data, *a_data;
+    DCPU16_WORD buf[3] = { vm->ram[addr], vm->ram[(DCPU16_WORD)(addr + 1)], vm->ram[(DCPU16_WORD)(addr + 2)] };
+    unsigned int indent = 0;
+    unsigned int partial = 0;
+
+    if (!vm) return 0;
+
+#if 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_(vm->ram, (DCPU16_WORD)(addr - i), &opcode, &b, &b_data, &a, &a_data, &instr_len);
+        if (instr_len > i)
+            partial++;
+        if (instr_len == i
+        &&  (opcode >= 0x10 && opcode <= 0x17) ) {
+            indent++;
+            break;
+        }
+    }
+#endif
+
+    /* just need instr_len */
+    instruction_decode_(vm->ram, addr, &opcode, &b, &b_data, &a, &a_data, &instr_len);
+
+    /* show the raw words */
+    printf("%04x", vm->ram[addr]);
+    for (i = 1; i < instr_len; i++) {
+        printf(" %04x", vm->ram[addr + i]);
+    }
 
-    printf("%s%s ; %s",
+    /* align things neatly, show the instruction */
+    printf("%s%s ;%s%s",
            instr_len < 3 ? "     " : "",
            instr_len < 2 ? "     " : "",
-           e->name);
-    if (opcode != 0) {
-        dump_value(a, d->ram[addr + 1]);
-        if (a == 0x1e || a == 0x1f)
-            addr++;
-        printf(",");
-    }
+           partial ? "*" : " ",
+           indent ? "  " : "");
+
+    dcpu16_mnemonify_buf(buf);
+
+    return instr_len;
+}
 
-    dump_value(b, d->ram[addr + 1]);
+int dcpu16_interrupt(struct dcpu16 *vm, DCPU16_WORD message) {
+    MSG_DEBUG(vm, "%s>> message:0x%04x", __func__, message);
+    return interrupt_enqueue_(vm, message);
 }
 
-void dcpu16_step(struct dcpu16 *d) {
-    WORD opcode;
-    WORD val_a, val_b;
+/* execute the next instruction */
+void dcpu16_step(struct dcpu16 *vm) {
+    DCPU16_WORD opcode, b, a, instr_len, *b_data, *a_data;
     const struct opcode_entry *e;
 
-    /* decode opcode and invoke */
+    if (!vm)
+        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);
+    instruction_decode_(vm->ram, vm->reg[DCPU16_REG_PC], &opcode, &b, &b_data, &a, &a_data, &instr_len);
 
-    d->pc++;
+    /* consume what we decoded */
+    /* this happens immediately as PC might be re-set as an operation */
+    vm->reg[DCPU16_REG_PC] += instr_len;
 
-    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;
+    /* run the operation */
+    e = opcode_basic_entries + opcode;
+    MSG_DEBUG(vm, "%s", e->name ? e->name : "???");
+    e->impl(vm, b, b_data ? *b_data : 0, a, a_data ? *a_data : 0);
+
+
+    /* and jump over next instr(s) if needed */
+    while (vm->skip_) {
+        instruction_decode_(vm->ram, vm->reg[DCPU16_REG_PC], &opcode, &b, &b_data, &a, &a_data, &instr_len);
+        vm->reg[DCPU16_REG_PC] += instr_len;
+        MSG_DEBUG(vm, "%s>> ++ SKIPPED %x words", __func__, instr_len);
+        if (opcode >= 0x10 && opcode <= 0x17) {
+            /* skipping a branch instruction? skip branch's skippable instruction as well */
+            dcpu16_cycle_inc(vm, 1);
+        } else {
+            vm->skip_ = 0;
+        }
+    }
+
+    /* if we're currently servicing interrupts */
+    if (vm->interrupts_deferred_ == 0) {
+        /* and there are interrupts to be serviced */
+        if (vm->interrupts_head_ != vm->interrupts_tail_) {
+            DCPU16_WORD message;
+            message = interrupt_dequeue_(vm);
+
+            MSG_DEBUG(vm, "%s>> %s interrupt IA:0x%04x message:0x%04x",
+                      __func__,
+                      vm->reg[DCPU16_REG_IA] ? "servicing" : "ignoring",
+                      vm->reg[DCPU16_REG_IA],
+                      message);
+
+            if (vm->reg[DCPU16_REG_IA]) {
+                /* then service the next interrupt */
+                vm->interrupts_deferred_ = 1;
+                vm->ram[--vm->reg[DCPU16_REG_SP]] = vm->reg[DCPU16_REG_PC];
+                vm->ram[--vm->reg[DCPU16_REG_SP]] = vm->reg[DCPU16_REG_A];
+                vm->reg[DCPU16_REG_PC] = vm->reg[DCPU16_REG_IA];
+                vm->reg[DCPU16_REG_A] = message;
+            }
         }
     }
 }
 
-void dcpu16_state_print(struct dcpu16 *d) {
-    unsigned int i;
 
-    printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
-           d->cycle,
-           "O", d->o,
-           "SP", d->sp,
-           "PC", d->pc,
-           "PC");
+/* instantiate a new 'hardware' device */
+struct dcpu16_hw *dcpu16_hw_new(struct dcpu16 *vm, struct dcpu16_hw_module *mod, void *data) {
+    struct dcpu16_hw *hw;
+
+    MSG_DEBUG(vm, "%s>> mod:%p data:%p", __func__, mod, data);
+
+    hw = malloc(sizeof *hw);
+    if (hw == NULL) {
+        MSG_ERROR(vm, "%s():%s", "malloc", strerror(errno));
+        return NULL;
+    }
+    hw->vm = vm;
+    hw->mod = mod;
+
+    if (mod->data_init) {
+        if (mod->data_init(hw, data)) {
+            MSG_ERROR(vm, "failed to init hw module data");
+            free(hw);
+            return NULL;
+        }
+    } else {
+        hw->data = NULL;
+    }
+
+    return hw;
+}
 
-    dcpu16_disassemble_print(d, d->pc);
-    printf("\n  ");
+/* destroy a 'hardware' device */
+void dcpu16_hw_del(struct dcpu16_hw **hw) {
+    if (hw) {
+        if (*hw) {
+            MSG_DEBUG((*hw)->vm, "%s>> hw:%p",
+                                 __func__,
+                                 *hw);
+
+            if ((*hw)->mod->data_free) {
+                (*hw)->mod->data_free(*hw);
+            }
+            free(*hw);
+            *hw = NULL;
+        }
+    }
+}
+
+/*  dcpu16_hw_ctl
+ *  invokes per-module controls for hw device
+ */
+int dcpu16_hw_ctl(struct dcpu16_hw *hw, const char *cmd, void *data_in, void *data_out) {
+    if (!hw)
+        return -1;
+
+    MSG_DEBUG(hw->vm, "%s>> name:%s cmd:%s in:%p out:%p",
+                      __func__,
+                      hw->mod->name_,
+                      cmd,
+                      data_in,
+                      data_out);
+
+    if (hw->mod) {
+        if (hw->mod->ctl) {
+            if (cmd) {
+                return hw->mod->ctl(hw, cmd, data_in, data_out);
+            }
+        }
+    }
 
-    for (i = 0; i < 8; i++)
-        printf("  %c:0x%04x", regnames_[i], d->reg[i]);
-    printf("\n");
+    return 0;
 }
 
-/*  dcpu16_dump_ram
- *  print raw ram contents from start to stop
+
+/*  dcpu16_hw_attach
+ *  registers new 'hardware' device with system
  */
-void dcpu16_dump_ram(struct dcpu16 *d, WORD start, WORD end) {
-    unsigned int i, j;
-    const unsigned int n = 8; /* words per line */
+int dcpu16_hw_attach(struct dcpu16 *vm, struct dcpu16_hw *hw) {
+    if (!vm || !hw)
+        return -1;
+
+    MSG_DEBUG(vm, "%s>> name:%s ID:0x%04x%04x MFG:0x%04x%04x VER:0x%04x",
+                  __func__,
+                  hw->mod->name_,
+                  hw->mod->id_h, hw->mod->id_l,
+                  hw->mod->mfg_l, hw->mod->mfg_h,
+                  hw->mod->ver);
+
+    if (vm->hw_table_entries_ == 0xffff) {
+        MSG_ERROR(vm, "maximum hardware entries reached");
+        return -1;
+    }
+
+    if (vm->hw_table_entries_ == vm->hw_table_allocated_) {
+        size_t new_entries = vm->hw_table_allocated_ + 32;
+        void *tmp_ptr = realloc(vm->hw_table_, new_entries * sizeof * (vm->hw_table_));
+        if (tmp_ptr == NULL) {
+            MSG_ERROR(vm, "%s():%s", "realloc", strerror(errno));
+            return -1;
+        }
+        vm->hw_table_ = tmp_ptr;
+        vm->hw_table_allocated_ += 32;
+    }
 
-    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" : "");
+    memcpy(vm->hw_table_ + vm->hw_table_entries_, hw, sizeof *hw);
+    vm->hw_table_entries_++;
+
+    MSG_DEBUG(vm, "%s>> added hw entry %zu", __func__, vm->hw_table_entries_);
+
+    return 0;
+}
+
+/*  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, dcpu16_ev_cb_t *fn, DCPU16_WORD addr_l, DCPU16_WORD addr_h, void *data) {
+    struct dcpu16_acct_cb cb = {
+        .mask = mask,
+        .addr_l = addr_l,
+        .addr_h = addr_h,
+        .fn = fn,
+        .data = data,
+    };
+
+    if (!vm)
+        return -1;
+
+    cb.mask = mask;
+    cb.addr_l = addr_l;
+    cb.addr_h = addr_h;
+    cb.fn = fn;
+    cb.data = data;
+
+    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) {
+            MSG_ERROR(vm, "%s():%s", "realloc", strerror(errno));
+            return -1;
+        }
+        vm->cb_table_ = tmp_ptr;
+        vm->cb_table_allocated_ += 32;
     }
-    if ((j % n) != (n - 1))
-        printf("\n");
+
+    memcpy(vm->cb_table_ + vm->cb_table_entries_, &cb, sizeof cb);
+    vm->cb_table_entries_++;
+
+    MSG_DEBUG(vm, "%s>> attached event callback %zu", __func__, vm->cb_table_entries_);
+
+    return 0;
 }
 
 /*  dcpu16_reset
- *  resets a dcpu16 instance to initial state
+ *  signals cpu to reset, clearing runstate and ram, then reload any init callbacks
  */
-void dcpu16_reset(struct dcpu16 *d) {
-    memset(d, 0, sizeof *d);
+void dcpu16_reset(struct dcpu16 *vm) {
+    size_t i;
+
+    if (!vm)
+        return;
+
+    MSG_DEBUG(vm, "%s>> reset", __func__);
+
+    vm->skip_ = 0;
+    vm->interrupts_deferred_ = 0;
+    vm->on_fire_ = 0;
+    memset(vm->interrupts_, 0, sizeof vm->interrupts_);
+    vm->interrupts_head_ = 0;
+    vm->interrupts_tail_ = 0;
+
+    /* signal attached hardware */
+    for (i = 0; i < vm->hw_table_entries_; i++) {
+        if (vm->hw_table_[i].mod->reset) 
+            vm->hw_table_[i].mod->reset(vm, &vm->hw_table_[i]);
+    }
+
+    memset(vm->reg, 0, sizeof vm->reg);
+    memset(vm->ram, 0, sizeof vm->ram);
+    vm->cycle_ = 0;
+
+    acct_event_(vm, DCPU16_ACCT_EV_RESET, 0);
 }
 
 /*  dcpu16_new
@@ -656,7 +1712,9 @@ struct dcpu16 *dcpu16_new(void) {
 
     vm = calloc(1, sizeof *vm);
     if (vm == NULL)
-        WARN("%s: %s(%zu): %s", __func__, "calloc", strerror(errno));
+        MSG_ERROR(NULL, "%s: %s(%zu): %s", __func__, "calloc", strerror(errno));
+
+    vm->msg_cb_ = dcpu16_msg_;
 
     return vm;
 }
@@ -665,6 +1723,9 @@ struct dcpu16 *dcpu16_new(void) {
  *  release a dcpu16 instance
  */
 void dcpu16_delete(struct dcpu16 **vm) {
+    if (!vm || !*vm)
+        return;
+
     free(*vm);
     *vm = NULL;
 }