further changes for v1.7: cpu fixes, support for 'hardware' devices, display to vnc
[dcpu16] / dcpu16.c
index 75c6cf0df39c3d0d33ce0bd06cf6bad984dd17b2..d7b3d4822eb25cf61edf0cd2c4b9fd1f5f6dbd48 100644 (file)
--- a/dcpu16.c
+++ b/dcpu16.c
  *  
  *  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 04 12 - added basic callback support for address accesses
  *    2012 05 05 - start of v1.7 revisions
  *    2012 05 08 - v1.7 revisions mostly complete
  *
@@ -42,7 +38,23 @@ static const char * const src_id_ = "$Id$";
 #define OPCODE_OPERAND_B_BITS 5
 #define OPCODE_OPERAND_A_BITS 6
 
-static const char * const regnames_ = "ABCXYZIJ";
+const char * const dcpu16_reg_names[] = {
+    "A",
+    "B",
+    "C",
+    "X",
+    "Y",
+    "Z",
+    "I",
+    "J",
+    "PC",
+    "SP",
+    "EX",
+    "IA",
+    NULL
+};
+
+static void printf_null_(char *fmt, ...) { (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)
@@ -60,7 +72,10 @@ void warn_(char *fmt, ...) {
 }
 static void (*warn_cb_)(char *fmt, ...) = warn_;
 void dcpu16_warn_cb_set(void (*fn)(char *fmt, ...)) {
-    warn_cb_ = fn;
+    if (fn)
+        warn_cb_ = fn;
+    else
+        warn_cb_ = printf_null_;
 }
 
 #ifdef DEBUG
@@ -88,7 +103,10 @@ static void (*trace_cb_)(char *fmt, ...) =
 #endif
     ;
 void dcpu16_trace_cb_set(void (*fn)(char *fmt, ...)) {
-    trace_cb_ = fn;
+    if (fn)
+        trace_cb_ = fn;
+    else
+        trace_cb_ = printf_null_;
 }
 
 
@@ -106,21 +124,16 @@ void acct_event_(struct dcpu16 *vm, dcpu16_acct_event ev, DCPU16_WORD addr) {
     }
 }
 
-/* convert register name to index into register array */
-static inline
-off_t reg_index_(int reg) {
-    return strchr(regnames_, reg) - regnames_;
-}
 
 /* add an entry to the interrupt queue */
 static
-int interrupt_enqueue_(struct dcpu16 *d, DCPU16_WORD message) {
-    d->interrupts_[d->interrupts_tail_] = message;
-    d->interrupts_tail_ += 1;
-    d->interrupts_tail_ %= DCPU16_INTERRUPT_QUEUE_SIZE;
+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 (d->interrupts_tail_ == d->interrupts_head_) {
-        d->on_fire_ = 1;
+    if (vm->interrupts_tail_ == vm->interrupts_head_) {
+        vm->on_fire_ = 1;
         WARN("interrupt queue overflow (system is now on fire)");
         return -1;
     }
@@ -129,17 +142,17 @@ int interrupt_enqueue_(struct dcpu16 *d, DCPU16_WORD message) {
 }
 
 static
-DCPU16_WORD interrupt_dequeue_(struct dcpu16 *d) {
+DCPU16_WORD interrupt_dequeue_(struct dcpu16 *vm) {
     DCPU16_WORD message;
 
-    if (d->interrupts_tail_ == d->interrupts_head_) {
+    if (vm->interrupts_tail_ == vm->interrupts_head_) {
         WARN("interrupt underflow");
         return 0;
     }
 
-    message = d->interrupts_[d->interrupts_head_];
-    d->interrupts_head_ += 1;
-    d->interrupts_head_ %= DCPU16_INTERRUPT_QUEUE_SIZE;
+    message = vm->interrupts_[vm->interrupts_head_];
+    vm->interrupts_head_ += 1;
+    vm->interrupts_head_ %= DCPU16_INTERRUPT_QUEUE_SIZE;
 
     return message;
 }
@@ -152,20 +165,20 @@ DCPU16_WORD interrupt_dequeue_(struct dcpu16 *d) {
  * 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
- * cycles is set to number of cycles spent looking up operand
+ * 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 inline
-void value_decode_(struct dcpu16 *d, DCPU16_WORD value, unsigned int value_is_a, DCPU16_WORD value_data,
+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,
                   short *pc_adjust, short *sp_adjust, unsigned int *cycle_adjust) {
     assert(value <= 0x3f);
 
-    DCPU16_WORD pc = (DCPU16_WORD)(d->pc + *pc_adjust),
-                sp = (DCPU16_WORD)(d->sp + *sp_adjust);
+    DCPU16_WORD pc = (DCPU16_WORD)(vm->reg[DCPU16_REG_PC] + *pc_adjust),
+                sp = (DCPU16_WORD)(vm->reg[DCPU16_REG_PC] + *sp_adjust);
 
     TRACE("%s: pc:0x%04x sp:0x%04x value_data:0x%04x\n", 
           __func__,
@@ -174,21 +187,21 @@ void value_decode_(struct dcpu16 *d, DCPU16_WORD value, unsigned int value_is_a,
           value_data);
 
     if (value <= 0x07) { /* register */
-        *v = d->reg + value;
-        TRACE("%s>>     %c (0x%04x)",
+        *v = vm->reg + value;
+        TRACE("%s>>     %s (0x%04x)",
               __func__,
-              regnames_[value],
+              dcpu16_reg_names[value],
               **v);
         return;
     }
 
     if (value <= 0x0f) { /* [register] */
-        *v = &(d->ram[ d->reg[value & 0x07] ]);
-        *e_addr = d->reg[value & 0x07];
-        TRACE("%s>>     [%c] [0x%04x] (0x%04x)",
+        *v = &(vm->ram[ vm->reg[value & 0x07] ]);
+        *e_addr = vm->reg[value & 0x07];
+        TRACE("%s>>     [%s] [0x%04x] (0x%04x)",
               __func__,
-              regnames_[value & 0x07],
-              d->reg[value & 0x07],
+              dcpu16_reg_names[value & 0x07],
+              vm->reg[value & 0x07],
               **v);
         return;
     }
@@ -196,13 +209,13 @@ void value_decode_(struct dcpu16 *d, DCPU16_WORD value, unsigned int value_is_a,
     if (value <= 0x17) { /* [next word + register] */
         *pc_adjust += 1; /* consume next word */
         *cycle_adjust += 1;
-        *e_addr = value_data + d->reg[value & 0x07];
-        *v = d->ram + *e_addr;
-        TRACE("%s>>     [nextword + %c] [0x%04x + 0x%04x] (0x%04x)",
+        *e_addr = value_data + vm->reg[value & 0x07];
+        *v = vm->ram + *e_addr;
+        TRACE("%s>>     [nextword + %s] [0x%04x + 0x%04x] (0x%04x)",
               __func__,
-              regnames_[value & 0x07],
+              dcpu16_reg_names[value & 0x07],
               value_data,
-              d->reg[value & 0x07],
+              vm->reg[value & 0x07],
               **v);
         return;
     }
@@ -210,12 +223,12 @@ void value_decode_(struct dcpu16 *d, DCPU16_WORD value, unsigned int value_is_a,
     switch (value) {
         case 0x18: /* PUSH/[--SP] or POP/[SP++] */
         if (value_is_a == 0) { /* b */
-            *v = &(d->ram[sp - 1]);
+            *v = &(vm->ram[sp - 1]);
             *sp_adjust -= 1;
             *e_addr = sp - 1;
             TRACE("%s>>     PUSH [0x%04x] (0x%04x)", __func__, sp - 1, **v);
         } else { /* a */
-            *v = &(d->ram[sp]);
+            *v = &(vm->ram[sp]);
             *sp_adjust += 1;
             *e_addr = sp;
             TRACE("%s>>     POP [0x%04x] (0x%04x)", __func__, sp, **v);
@@ -223,7 +236,7 @@ void value_decode_(struct dcpu16 *d, DCPU16_WORD value, unsigned int value_is_a,
         break;
 
         case 0x19: /* PEEK/[SP] */
-        *v = &(d->ram[sp]);
+        *v = &(vm->ram[sp]);
         *e_addr = sp;
         TRACE("%s>>     PEEK [0x%04x] (0x%04x)",
               __func__,
@@ -235,7 +248,7 @@ void value_decode_(struct dcpu16 *d, DCPU16_WORD value, unsigned int value_is_a,
         *pc_adjust += 1;
         *cycle_adjust += 1;
         *e_addr = sp + value_data;
-        *v = d->ram + *e_addr;
+        *v = vm->ram + *e_addr;
         TRACE("%s>>     PICK 0x%04x [0x%04x] (0x%04x)",
               __func__,
               value_data,
@@ -244,26 +257,26 @@ void value_decode_(struct dcpu16 *d, DCPU16_WORD value, unsigned int value_is_a,
         break;
 
         case 0x1b: /* SP */
-        *v = &(d->sp);
+        *v = &(vm->reg[DCPU16_REG_SP]);
         TRACE("%s>>     %s (0x%04x)",
               __func__,
-              "SP",
+              dcpu16_reg_names[DCPU16_REG_SP],
               **v);
         break;
 
         case 0x1c: /* PC */
-        *v = &(d->pc);
+        *v = &(vm->reg[DCPU16_REG_PC]);
         TRACE("%s>>     %s (0x%04x)",
               __func__,
-              "PC",
+              dcpu16_reg_names[DCPU16_REG_PC],
               **v);
         break;
 
         case 0x1d: /* EX */
-        *v = &(d->ex);
+        *v = &(vm->reg[DCPU16_REG_EX]);
         TRACE("%s>>     %s (0x%04x)",
               __func__,
-              "EX",
+              dcpu16_reg_names[DCPU16_REG_EX],
               **v);
         break;
 
@@ -271,7 +284,7 @@ void value_decode_(struct dcpu16 *d, DCPU16_WORD value, unsigned int value_is_a,
         *pc_adjust += 1;
         *cycle_adjust += 1;
         *e_addr = value_data;
-        *v = d->ram + *e_addr;
+        *v = vm->ram + *e_addr;
         TRACE("%s>>     [nextword] [0x%04x] (0x%04x)",
               __func__,
               value_data,
@@ -310,7 +323,7 @@ struct opcode_entry {
 /* opcode doesn't adjust its own PC, the step function which invoked it handles that */
 /* opcode does adjust stack and cycle count */
 
-#define OP_IMPL(x) static void op_##x(struct dcpu16 *d, DCPU16_WORD val_b, DCPU16_WORD val_b_data, DCPU16_WORD val_a, DCPU16_WORD val_a_data)
+#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;\
@@ -318,15 +331,15 @@ struct opcode_entry {
     unsigned int cycle_adjust = 0;\
     do {\
         op_type;\
-        value_decode_(d, val_a, 1, val_a_data,\
-                      &d->reg_work_[1], &a, &ev_a_addr,\
+        value_decode_(vm, val_a, 1, val_a_data,\
+                      &vm->reg_work_[1], &a, &ev_a_addr,\
                       &pc_adjust, &sp_adjust, &cycle_adjust);\
-        d->sp += sp_adjust;\
-        d->cycle += cycle_adjust;\
+        vm->reg[DCPU16_REG_SP] += sp_adjust;\
+        vm->cycle += cycle_adjust;\
     } while (0)
 #define OP_NBI_ (void)val_b, (void)b, (void)ev_b_addr, (void)val_b_data
-#define OP_BASIC_ value_decode_(d, val_b, 0, val_b_data,\
-                                &d->reg_work_[0], &b, &ev_b_addr,\
+#define OP_BASIC_ value_decode_(vm, val_b, 0, val_b_data,\
+                                &vm->reg_work_[0], &b, &ev_b_addr,\
                                 &pc_adjust, &sp_adjust, &cycle_adjust)
 #define OP_BASIC(x) OP_TYPE(OP_BASIC_)
 #define OP_NBI(x) OP_TYPE(OP_NBI_)
@@ -337,9 +350,9 @@ struct opcode_entry {
     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)
+#define ACCT_R(addr) do { acct_event_(vm, DCPU16_ACCT_EV_READ, addr); } while (0)
+#define ACCT_W(addr) do { acct_event_(vm, DCPU16_ACCT_EV_WRITE, addr); } while (0)
+#define ACCT_ILL(addr) do { acct_event_(vm, DCPU16_ACCT_EV_NOP, addr); } while (0)
 
 /* extended opcodes */
 
@@ -355,10 +368,10 @@ OP_IMPL(nbi__reserved_) {
     /* reserved for future expansion */
 
     /* fire an illegal instruction event for current instruction */
-    DCPU16_WORD future_opcode = (d->ram[d->pc - pc_adjust] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_B_BITS));
+    DCPU16_WORD future_opcode = (vm->ram[vm->reg[DCPU16_REG_PC] - pc_adjust] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_B_BITS));
     WARN("reserved future opcode 0x%04x invoked", future_opcode);
 
-    ACCT_ILL(d->pc - pc_adjust);
+    ACCT_ILL(vm->reg[DCPU16_REG_PC] - pc_adjust);
 }
 
 OP_IMPL(nbi_jsr) {
@@ -367,12 +380,12 @@ OP_IMPL(nbi_jsr) {
 
     ACCT_R(ev_a_addr);
 
-    d->ram[ --d->sp ] = d->pc;
-    d->pc = *a;
+    vm->ram[ --vm->reg[DCPU16_REG_SP] ] = vm->reg[DCPU16_REG_PC];
+    vm->reg[DCPU16_REG_PC] = *a;
 
-    d->cycle += 2;
+    vm->cycle += 2;
 
-    ACCT_W(d->sp + 1);
+    ACCT_W(vm->reg[DCPU16_REG_SP] + 1);
 }
 
 OP_IMPL(nbi__reserved2_) {
@@ -381,7 +394,7 @@ OP_IMPL(nbi__reserved2_) {
 
     WARN("reserved nbi opcode invoked");
 
-    ACCT_ILL(d->pc - pc_adjust);
+    ACCT_ILL(vm->reg[DCPU16_REG_PC] - pc_adjust);
 }
 
 OP_IMPL(nbi_int) {
@@ -389,29 +402,29 @@ OP_IMPL(nbi_int) {
 
     ACCT_R(ev_a_addr);
 
-    if (d->ia) {
-        if ( interrupt_enqueue_(d, *a) ) {
+    if (vm->reg[DCPU16_REG_IA]) {
+        if ( interrupt_enqueue_(vm, *a) ) {
             WARN("failed to queue interrupt");
             return;
         }
 
-        if (d->interrupts_deferred_)
+        if (vm->interrupts_deferred_)
             return;
 
-        d->interrupts_deferred_ = 1;
-        d->ram[--d->sp] = d->pc;
-        d->ram[--d->sp] = d->reg[reg_index_('A')];
-        d->pc = d->ia;
-        d->reg[0] = *a;
+        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[0] = *a;
     }
 
-    d->cycle += 4;
+    vm->cycle += 4;
 }
 
 OP_IMPL(nbi_iag) {
     OP_NBI(nbi_iag);
 
-    *a = d->ia;
+    *a = vm->reg[DCPU16_REG_IA];
 
     ACCT_W(ev_a_addr);
 }
@@ -419,7 +432,7 @@ OP_IMPL(nbi_iag) {
 OP_IMPL(nbi_ias) {
     OP_NBI(nbi_ias);
 
-    d->ia = *a;
+    vm->reg[DCPU16_REG_IA] = *a;
 
     ACCT_R(ev_a_addr);
 }
@@ -428,18 +441,18 @@ OP_IMPL(nbi_ias) {
 OP_IMPL(nbi_rfi) {
     OP_NBI(nbi_rfi);
 
-    d->interrupts_deferred_ = 0;
-    d->reg[reg_index_('A')] = d->ram[d->sp++];
-    d->pc = d->ram[d->sp++];
+    vm->interrupts_deferred_ = 0;
+    vm->reg[DCPU16_REG_A] = vm->ram[vm->reg[DCPU16_REG_SP]++];
+    vm->reg[DCPU16_REG_PC] = vm->ram[vm->reg[DCPU16_REG_SP]++];
 }
 
 OP_IMPL(nbi_iaq) {
     OP_NBI(nbi_iaq);
 
     if (*a) {
-        d->interrupts_deferred_ = 1;
+        vm->interrupts_deferred_ = 1;
     } else {
-        d->interrupts_deferred_ = 0;
+        vm->interrupts_deferred_ = 0;
     }
 
     ACCT_R(ev_a_addr);
@@ -450,9 +463,9 @@ OP_IMPL(nbi_hwn) {
 
     ACCT_W(ev_a_addr);
 
-    *a = d->hw_table_entries_;
+    *a = vm->hw_table_entries_;
 
-    d->cycle += 2;
+    vm->cycle += 2;
 }
 
 OP_IMPL(nbi_hwq) {
@@ -460,23 +473,23 @@ OP_IMPL(nbi_hwq) {
 
     ACCT_R(ev_a_addr);
 
-    if (*a >= d->hw_table_entries_) {
+    if (*a >= vm->hw_table_entries_) {
         WARN("hardware query for non-extant device 0x%04x", *a);
-        d->reg[reg_index_('A')] = 0;
-        d->reg[reg_index_('B')] = 0;
-        d->reg[reg_index_('C')] = 0;
-        d->reg[reg_index_('X')] = 0;
-        d->reg[reg_index_('Y')] = 0;
+        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;
         return;
     }
 
-    d->reg[reg_index_('A')] = d->hw_table_[*a].id_l;
-    d->reg[reg_index_('B')] = d->hw_table_[*a].id_h;
-    d->reg[reg_index_('C')] = d->hw_table_[*a].ver;
-    d->reg[reg_index_('X')] = d->hw_table_[*a].mfg_l;
-    d->reg[reg_index_('Y')] = d->hw_table_[*a].mfg_h;
+    vm->reg[DCPU16_REG_A] = vm->hw_table_[*a].id_l;
+    vm->reg[DCPU16_REG_B] = vm->hw_table_[*a].id_h;
+    vm->reg[DCPU16_REG_C] = vm->hw_table_[*a].ver;
+    vm->reg[DCPU16_REG_X] = vm->hw_table_[*a].mfg_l;
+    vm->reg[DCPU16_REG_Y] = vm->hw_table_[*a].mfg_h;
 
-    d->cycle += 4;
+    vm->cycle += 4;
 }
 
 OP_IMPL(nbi_hwi) {
@@ -484,13 +497,16 @@ OP_IMPL(nbi_hwi) {
 
     ACCT_R(ev_a_addr);
 
-    if (*a > d->hw_table_entries_) {
+    if (*a > vm->hw_table_entries_) {
         WARN("interrupt for non-extant device 0x%04x", *a);
         return;
     }
 
-    d->cycle += 4;
-    d->hw_table_[*a].int_fn(d, d->hw_table_[*a].data);
+    vm->cycle += 4;
+    if (vm->hw_table_[*a].hwi)
+        vm->hw_table_[*a].hwi(vm, vm->hw_table_[*a].data);
+    else
+        WARN("hardware 0x%04x has no interrupt handler", *a);
 }
 
 OP_IMPL(nbi_hcf) {
@@ -498,10 +514,10 @@ OP_IMPL(nbi_hcf) {
 
     ACCT_R(ev_a_addr);
 
-    d->on_fire_ = 1;
+    vm->on_fire_ = 1;
     WARN("system on fire");
 
-    d->cycle += 9;
+    vm->cycle += 9;
 }
 
 static const struct opcode_entry opcode_nbi_entries[] = {
@@ -533,7 +549,7 @@ static const struct opcode_entry opcode_nbi_entries[] = {
 /* basic opcodes */
 
 /*
-    N.B. the following function does not decode values, (thus does not advance sp)
+    N.B. the following function does not decode values.
     Decoding is handled by the secondary opcode functions it calls.
 */
 OP_IMPL(_nbi_) {
@@ -551,7 +567,7 @@ OP_IMPL(_nbi_) {
     assert(e->impl != NULL);
 
     TRACE(">> %s 0x%04x", e->name, val_b);
-    e->impl(d, 0, 0, val_a, val_a_data);
+    e->impl(vm, 0, 0, val_a, val_a_data);
 }
 
 OP_IMPL(set) {
@@ -566,7 +582,7 @@ OP_IMPL(set) {
      */
     *b = *a;
 
-    d->cycle += 1;
+    vm->cycle += 1;
 
     ACCT_W(ev_b_addr);
 }
@@ -580,9 +596,9 @@ OP_IMPL(add) {
     ACCT_R(ev_a_addr);
 
     *b = acc;
-    d->ex = (acc > 0xffff);
+    vm->reg[DCPU16_REG_EX] = (acc > 0xffff);
 
-    d->cycle += 2;
+    vm->cycle += 2;
 
     ACCT_W(ev_b_addr);
 }
@@ -596,9 +612,9 @@ OP_IMPL(sub) {
     ACCT_R(ev_a_addr);
 
     *b = acc;
-    d->ex = (acc > 0xffff);
+    vm->reg[DCPU16_REG_EX] = (acc > 0xffff);
 
-    d->cycle += 2;
+    vm->cycle += 2;
 
     ACCT_W(ev_b_addr);
 }
@@ -612,9 +628,9 @@ OP_IMPL(mul) {
     ACCT_R(ev_a_addr);
 
     *b = acc;
-    d->ex = acc >> 16;
+    vm->reg[DCPU16_REG_EX] = acc >> 16;
 
-    d->cycle += 2;
+    vm->cycle += 2;
 
     ACCT_W(ev_b_addr);
 }
@@ -628,9 +644,9 @@ OP_IMPL(mli) {
     ACCT_R(ev_a_addr);
 
     *b = acc;
-    d->ex = acc >> 16;
+    vm->reg[DCPU16_REG_EX] = acc >> 16;
 
-    d->cycle += 2;
+    vm->cycle += 2;
 
     ACCT_W(ev_b_addr);
 }
@@ -644,13 +660,13 @@ OP_IMPL(div) {
 
     if (*a == 0) {
         *b = 0;
-        d->ex = 0;
+        vm->reg[DCPU16_REG_EX] = 0;
     } else {
         *b = *b / *a;
-        d->ex = (*b << 16) / *a;
+        vm->reg[DCPU16_REG_EX] = (*b << 16) / *a;
     }
 
-    d->cycle += 3;
+    vm->cycle += 3;
 
     ACCT_W(ev_b_addr);
 }
@@ -664,13 +680,13 @@ OP_IMPL(dvi) {
 
     if (*a == 0) {
         *b = 0;
-        d->ex = 0;
+        vm->reg[DCPU16_REG_EX] = 0;
     } else {
         *b = (short)*b / (short)*a;
-        d->ex = (short)(*b << 16) / (short)*a;
+        vm->reg[DCPU16_REG_EX] = (short)(*b << 16) / (short)*a;
     }
 
-    d->cycle += 3;
+    vm->cycle += 3;
 
     ACCT_W(ev_b_addr);
 }
@@ -688,7 +704,7 @@ OP_IMPL(mod) {
         *b = *b % *a;
     }
 
-    d->cycle += 3;
+    vm->cycle += 3;
 
     ACCT_W(ev_a_addr);
 }
@@ -706,7 +722,7 @@ OP_IMPL(mdi) {
         *b = (short)*b % (short)*a;
     }
 
-    d->cycle += 3;
+    vm->cycle += 3;
 
     ACCT_W(ev_b_addr);
 }
@@ -720,7 +736,7 @@ OP_IMPL(and) {
 
     *b = *b & *a;
 
-    d->cycle += 1;
+    vm->cycle += 1;
 
     ACCT_W(ev_b_addr);
 }
@@ -734,7 +750,7 @@ OP_IMPL(bor) {
 
     *b = *b | *a;
 
-    d->cycle += 1;
+    vm->cycle += 1;
 
     ACCT_W(ev_b_addr);
 }
@@ -748,7 +764,7 @@ OP_IMPL(xor) {
 
     *b = *b ^ *a;
 
-    d->cycle += 1;
+    vm->cycle += 1;
 
     ACCT_W(ev_b_addr);
 }
@@ -762,9 +778,9 @@ OP_IMPL(shr) {
     ACCT_R(ev_a_addr);
 
     *b = acc & 0xffff;
-    d->ex = (*b << 16) >> *a;
+    vm->reg[DCPU16_REG_EX] = (*b << 16) >> *a;
 
-    d->cycle += 2;
+    vm->cycle += 2;
 
     WARN("IMPLEMENT");
 
@@ -780,9 +796,9 @@ OP_IMPL(asr) {
     ACCT_R(ev_a_addr);
 
     *b = acc & 0xffff;
-    d->ex = (*b << 16) >> *a;
+    vm->reg[DCPU16_REG_EX] = (*b << 16) >> *a;
 
-    d->cycle += 2;
+    vm->cycle += 2;
 
     WARN("IMPLEMENT");
 
@@ -799,9 +815,9 @@ OP_IMPL(shl) {
 
     *b = acc;
 
-    d->ex = acc >> 16;
+    vm->reg[DCPU16_REG_EX] = acc >> 16;
 
-    d->cycle += 2;
+    vm->cycle += 2;
 
     ACCT_W(ev_b_addr);
 }
@@ -816,11 +832,11 @@ OP_IMPL(ifb) {
     if ((*b & *a) != 0) {
         /* */
     } else {
-        d->skip_ = 1;
-        d->cycle += 1;
+        vm->skip_ = 1;
+        vm->cycle += 1;
     }
 
-    d->cycle += 2;
+    vm->cycle += 2;
 }
 
 OP_IMPL(ifc) {
@@ -831,13 +847,13 @@ OP_IMPL(ifc) {
     ACCT_R(ev_a_addr);
 
     if ((*b & *a) == 0) {
-        
+        /* */
     } else {
-        d->skip_ = 1;
-        d->cycle += 1;
+        vm->skip_ = 1;
+        vm->cycle += 1;
     }
 
-    d->cycle += 2;
+    vm->cycle += 2;
 }
 
 OP_IMPL(ife) {
@@ -850,11 +866,11 @@ OP_IMPL(ife) {
     if (*b == *a) {
         /* */
     } else {
-        d->skip_ = 1;
-        d->cycle += 1;
+        vm->skip_ = 1;
+        vm->cycle += 1;
     }
 
-    d->cycle += 2;
+    vm->cycle += 2;
 }
 
 OP_IMPL(ifn) {
@@ -867,11 +883,11 @@ OP_IMPL(ifn) {
     if (*b != *a) {
         /* */
     } else {
-        d->skip_ = 1;
-        d->cycle++;
+        vm->skip_ = 1;
+        vm->cycle++;
     }
 
-    d->cycle += 2;
+    vm->cycle += 2;
 }
 
 OP_IMPL(ifg) {
@@ -884,11 +900,11 @@ OP_IMPL(ifg) {
     if (*b > *a) {
         /* */
     } else {
-        d->skip_ = 1;
-        d->cycle++;
+        vm->skip_ = 1;
+        vm->cycle++;
     }
 
-    d->cycle += 2;
+    vm->cycle += 2;
 }
 
 OP_IMPL(ifa) {
@@ -901,11 +917,11 @@ OP_IMPL(ifa) {
     if (*b > *a) {
         /* */
     } else {
-        d->skip_ = 1;
-        d->cycle += 1;
+        vm->skip_ = 1;
+        vm->cycle += 1;
     }
 
-    d->cycle += 2;
+    vm->cycle += 2;
 }
 
 OP_IMPL(ifl) {
@@ -918,11 +934,11 @@ OP_IMPL(ifl) {
     if (*b < *a) {
         /* */
     } else {
-        d->skip_ = 1;
-        d->cycle++;
+        vm->skip_ = 1;
+        vm->cycle++;
     }
 
-    d->cycle += 2;
+    vm->cycle += 2;
 }
 
 OP_IMPL(ifu) {
@@ -935,11 +951,11 @@ OP_IMPL(ifu) {
     if (*b < *a) {
         /* */
     } else {
-        d->skip_ = 1;
-        d->cycle += 1;
+        vm->skip_ = 1;
+        vm->cycle += 1;
     }
 
-    d->cycle += 2;
+    vm->cycle += 2;
 }
 
 OP_IMPL(adx) {
@@ -950,14 +966,14 @@ OP_IMPL(adx) {
     ACCT_R(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    acc = *b + *a + d->ex;
+    acc = *b + *a + vm->reg[DCPU16_REG_EX];
     *b = acc & 0xffff;
     if (acc > 0xffff)
-        d->ex = 0x0001;
+        vm->reg[DCPU16_REG_EX] = 0x0001;
     else
-        d->ex = 0x0000;
+        vm->reg[DCPU16_REG_EX] = 0x0000;
 
-    d->cycle += 3;
+    vm->cycle += 3;
 
     ACCT_W(ev_b_addr);
 }
@@ -970,14 +986,14 @@ OP_IMPL(sbx) {
     ACCT_R(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    acc = *b - *a + d->ex;
+    acc = *b - *a + vm->reg[DCPU16_REG_EX];
     *b = acc & 0xffff;
     if (acc > 0xffff)
-        d->ex = 0xffff;
+        vm->reg[DCPU16_REG_EX] = 0xffff;
     else
-        d->ex = 0;
+        vm->reg[DCPU16_REG_EX] = 0;
 
-    d->cycle += 3;
+    vm->cycle += 3;
 
     ACCT_W(ev_b_addr);
 }
@@ -990,10 +1006,10 @@ OP_IMPL(sti) {
     ACCT_R(ev_a_addr);
 
     *b = *a;
-    d->reg[reg_index_('I')] += 1;
-    d->reg[reg_index_('J')] += 1;
+    vm->reg[DCPU16_REG_I] += 1;
+    vm->reg[DCPU16_REG_J] += 1;
 
-    d->cycle += 2;
+    vm->cycle += 2;
 
     ACCT_W(ev_b_addr);
 }
@@ -1006,10 +1022,10 @@ OP_IMPL(std) {
     ACCT_R(ev_a_addr);
 
     *b = *a;
-    d->reg[reg_index_('I')] -= 1;
-    d->reg[reg_index_('J')] -= 1;
+    vm->reg[DCPU16_REG_I] -= 1;
+    vm->reg[DCPU16_REG_J] -= 1;
 
-    d->cycle += 2;
+    vm->cycle += 2;
 
     ACCT_W(ev_b_addr);
 }
@@ -1019,7 +1035,7 @@ OP_IMPL(_reserved_) {
 
     WARN("reserved opcode invoked");
 
-    ACCT_ILL(d->pc - pc_adjust);
+    ACCT_ILL(vm->reg[DCPU16_REG_PC] - pc_adjust);
 }
 
 static const struct opcode_entry opcode_basic_entries[] = {
@@ -1061,11 +1077,11 @@ static const struct opcode_entry opcode_basic_entries[] = {
 static inline
 void dump_operand_value_(DCPU16_WORD value, DCPU16_WORD nextword, unsigned int value_position) {
     if (value <= 0x07) {
-        printf(" %c", regnames_[value]);
+        printf(" %s", dcpu16_reg_names[value]);
     } else if (value <= 0x0f) {
-        printf(" [%c]", regnames_[value & 0x07]);
+        printf(" [%s]", dcpu16_reg_names[value & 0x07]);
     } else if (value <= 0x17) {
-        printf(" [0x%04x + %c]", nextword, regnames_[value & 0x07]);
+        printf(" [0x%04x + %s]", nextword, dcpu16_reg_names[value & 0x07]);
     } else switch (value) {
         case 0x18:
             if (value_position == 0) { /* b */
@@ -1160,13 +1176,13 @@ DCPU16_WORD dcpu16_mnemonify_buf(DCPU16_WORD *buf) {
     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 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] = { d->ram[addr], d->ram[(DCPU16_WORD)(addr + 1)], d->ram[(DCPU16_WORD)(addr + 2)] };
+    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 (!d) return 0;
+    if (!vm) return 0;
 
 #if 0
     /*
@@ -1175,7 +1191,7 @@ DCPU16_WORD dcpu16_disassemble_print(struct dcpu16 *d, DCPU16_WORD addr) {
         could be data which happen to match instructions..
     */
     for (i = 3; i; i--) {
-        instruction_decode_(d->ram, (DCPU16_WORD)(addr - i), &opcode, &b, &b_data, &a, &a_data, &instr_len);
+        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
@@ -1187,12 +1203,12 @@ DCPU16_WORD dcpu16_disassemble_print(struct dcpu16 *d, DCPU16_WORD addr) {
 #endif
 
     /* just need instr_len */
-    instruction_decode_(d->ram, addr, &opcode, &b, &b_data, &a, &a_data, &instr_len);
+    instruction_decode_(vm->ram, addr, &opcode, &b, &b_data, &a, &a_data, &instr_len);
 
     /* show the raw words */
-    printf("%04x", d->ram[addr]);
+    printf("%04x", vm->ram[addr]);
     for (i = 1; i < instr_len; i++) {
-        printf(" %04x", d->ram[addr + i]);
+        printf(" %04x", vm->ram[addr + i]);
     }
 
     /* align things neatly, show the instruction */
@@ -1207,64 +1223,78 @@ DCPU16_WORD dcpu16_disassemble_print(struct dcpu16 *d, DCPU16_WORD addr) {
     return instr_len;
 }
 
+int dcpu16_interrupt(struct dcpu16 *vm, DCPU16_WORD message) {
+    TRACE("%s>> message:0x%04x", __func__, message);
+    return interrupt_enqueue_(vm, message);
+}
+
 /* execute the next instruction */
-void dcpu16_step(struct dcpu16 *d) {
+void dcpu16_step(struct dcpu16 *vm) {
     DCPU16_WORD opcode, b, a, instr_len, *b_data, *a_data;
+    size_t i;
     const struct opcode_entry *e;
 
-    if (!d) return;
-
-    acct_event_(d, DCPU16_ACCT_EV_CYCLE, d->pc);
+    if (!vm) return;
 
-    /* if we're currently servicing interrupts */
-    if (d->interrupts_deferred_ == 0) {
-        /* and there are interrupts to be serviced */
-        if (d->interrupts_head_ != d->interrupts_tail_) {
-            DCPU16_WORD message;
-            message = interrupt_dequeue_(d);
+    /* signal interested parties that a new cycle has ticked */
+    TRACE("%s: sending global cycle event", __func__);
+    acct_event_(vm, DCPU16_ACCT_EV_CYCLE, vm->reg[DCPU16_REG_PC]);
 
-            if (d->ia) {
-                TRACE("servicing interrupt IA:0x%04x message:0x%04x \n", d->ia, message);
-                /* then service the next interrupt */
-                d->interrupts_deferred_ = 1;
-                d->ram[--d->sp] = d->pc;
-                d->ram[--d->sp] = d->reg[reg_index_('A')];
-                d->pc = d->ia;
-                d->reg[0] = message;
-            } else {
-                TRACE("ignoring interrupt IA:0");
-            }
+    /* signal attached hardware */
+    for (i = 0; i < vm->hw_table_entries_; i++) {
+        if (vm->hw_table_[i].cycle) {
+            TRACE("%s: sending cycle to %s", __func__, vm->hw_table_[i].name_);
+            vm->hw_table_[i].cycle(vm, vm->hw_table_[i].data);
         }
     }
 
-    /* and make sure to execute an instruction after an interrupt */
+    instruction_decode_(vm->ram, vm->reg[DCPU16_REG_PC], &opcode, &b, &b_data, &a, &a_data, &instr_len);
 
-    instruction_decode_(d->ram, d->pc, &opcode, &b, &b_data, &a, &a_data, &instr_len);
+    /* consume what we decoded */
+    /* this happens immediately as PC might be re-set as an operation */
+    vm->reg[DCPU16_REG_PC] += instr_len;
 
+    /* run the operation */
     for (e = opcode_basic_entries; e->impl; e++) {
         if (e->value == opcode) {
             TRACE("%s>> %s 0x%04x, 0x%04x", __func__, e->name, b, a);
-            e->impl(d, b, b_data ? *b_data : 0, a, a_data ? *a_data : 0);
+            e->impl(vm, b, b_data ? *b_data : 0, a, a_data ? *a_data : 0);
             break;
         }
     }
 
-    /* get ready for the next one */
-    d->pc += instr_len;
-
     /* and jump over next instr(s) if needed */
-    if (d->skip_) {
-        instruction_decode_(d->ram, d->pc, &opcode, &b, &b_data, &a, &a_data, &instr_len);
-        d->pc += instr_len;
+    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;
         TRACE("++ SKIPPED %x words", instr_len);
         if (opcode >= 0x10 && opcode <= 0x17) {
             /* skipping a branch instruction? skip branch's skippable instruction as well */
-            d->cycle += 1;
-            instruction_decode_(d->ram, d->pc, &opcode, &b, &b_data, &a, &a_data, &instr_len);
-            d->pc += instr_len;
-            TRACE("++ SKIPPED %x words", instr_len);
+            vm->cycle += 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);
+
+            if (vm->reg[DCPU16_REG_IA]) {
+                TRACE("servicing interrupt IA:0x%04x message:0x%04x \n", vm->reg[DCPU16_REG_IA], message);
+                /* 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;
+            } else {
+                TRACE("ignoring interrupt IA:0");
+            }
         }
-        d->skip_ = 0;
     }
 }
 
@@ -1272,41 +1302,41 @@ void dcpu16_step(struct dcpu16 *d) {
     print the current state of the machine
     shows current cycle count, registers, and next instruction
 */
-void dcpu16_state_print(struct dcpu16 *d) {
+void dcpu16_state_print(struct dcpu16 *vm) {
     unsigned int i;
 
-    if (!d) return;
+    if (!vm) return;
 
     printf("  ");
     for (i = 0; i < 8; i++)
-        printf("  %c:0x%04x", regnames_[i], d->reg[i]);
+        printf("  %s:0x%04x", dcpu16_reg_names[i], vm->reg[i]);
     printf("\n");
 
     printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
-           d->cycle,
-           "EX", d->ex,
-           "SP", d->sp,
-           "PC", d->pc,
-           "IA", d->ia,
+           vm->cycle,
+           dcpu16_reg_names[DCPU16_REG_EX], vm->reg[DCPU16_REG_EX],
+           dcpu16_reg_names[DCPU16_REG_SP], vm->reg[DCPU16_REG_SP],
+           dcpu16_reg_names[DCPU16_REG_PC], vm->reg[DCPU16_REG_PC],
+           dcpu16_reg_names[DCPU16_REG_IA], vm->reg[DCPU16_REG_IA],
            "PC");
 
-    dcpu16_disassemble_print(d, d->pc);
+    dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]);
     printf("\n");
 }
 
 /*  dcpu16_dump_ram
  *  print raw ram contents from start to stop
  */
-void dcpu16_dump_ram(struct dcpu16 *d, DCPU16_WORD start, DCPU16_WORD end) {
+void dcpu16_dump_ram(struct dcpu16 *vm, DCPU16_WORD start, DCPU16_WORD end) {
     unsigned int i, j;
     const unsigned int n = 8; /* words per line */
 
-    if (!d) return;
+    if (!vm) 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" : "");
+        printf(" %04x%s", vm->ram[i], (j % n) == (n - 1) ? "\n" : "");
     }
     if ((j % n) != (n - 1))
         printf("\n");
@@ -1319,6 +1349,11 @@ int dcpu16_hw_add(struct dcpu16 *vm, struct dcpu16_hw *hw) {
     if (!vm || !hw)
         return -1;
 
+    if (vm->hw_table_entries_ == 0xffff) {
+        WARN("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_));
@@ -1343,6 +1378,9 @@ int dcpu16_hw_add(struct dcpu16 *vm, struct dcpu16_hw *hw) {
 int dcpu16_acct_add(struct dcpu16 *vm, dcpu16_acct_event mask, dcpu16_ev_cb_t *fn, void *data) {
     struct dcpu16_acct_cb cb;
 
+    if (!vm)
+        return -1;
+
     cb.mask = mask;
     cb.fn = fn;
     cb.data = data;
@@ -1367,19 +1405,30 @@ int dcpu16_acct_add(struct dcpu16 *vm, dcpu16_acct_event mask, dcpu16_ev_cb_t *f
 /*  dcpu16_reset
  *  signals cpu to reset, clearing runstate and ram, then reload any init callbacks
  */
-void dcpu16_reset(struct dcpu16 *d) {
-    if (!d) return;
-
-    d->cycle = 0;
-    memset(d->reg, 0, sizeof d->reg);
-    d->pc = 0;
-    d->sp = 0;
-    d->ex = 0;
-    d->ia = 0;
-    d->skip_ = 0;
-    memset(d->ram, 0, sizeof d->ram);
-
-    acct_event_(d, DCPU16_ACCT_EV_RESET, 0);
+void dcpu16_reset(struct dcpu16 *vm) {
+    size_t i;
+
+    if (!vm)
+        return;
+
+    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].reset) 
+            vm->hw_table_[i].reset(vm, vm->hw_table_[i].data);
+    }
+
+    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
@@ -1392,6 +1441,9 @@ struct dcpu16 *dcpu16_new(void) {
     if (vm == NULL)
         WARN("%s: %s(%zu): %s", __func__, "calloc", strerror(errno));
 
+    vm->warn_cb_ = warn_cb_;
+    vm->trace_cb_ = trace_cb_;
+
     return vm;
 }
 
@@ -1399,7 +1451,8 @@ struct dcpu16 *dcpu16_new(void) {
  *  release a dcpu16 instance
  */
 void dcpu16_delete(struct dcpu16 **vm) {
-    if (!vm || !*vm) return;
+    if (!vm || !*vm)
+        return;
 
     free(*vm);
     *vm = NULL;