v1.7 spec mostly implemented, mostly untested
authorJustin Wind <justin.wind@gmail.com>
Wed, 9 May 2012 03:28:01 +0000 (20:28 -0700)
committerJustin Wind <justin.wind@gmail.com>
Wed, 9 May 2012 03:28:01 +0000 (20:28 -0700)
changed cpu to work as per v1.7 specification, minor additional work still needed
added interrupts, hardware interface, new opcodes/operands

Makefile
as-dcpu16.c
dcpu16.c
dcpu16.h

index fdd94d25be26003fc452bc190786d4c7328dce0b..f0f3b717c7a923d97f23943f323c954f0cb484f1 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -8,6 +8,8 @@ endif
 PROGRAMS = as-dcpu16 vm-dcpu16
 SOURCES = common.c dcpu16.c as-dcpu16.c vm-dcpu16.c display.c
 
+CPPFLAGS += -DDEBUG
+
 CPPFLAGS += -DHAVE_LIBPNG -I/usr/X11/include/
 CFLAGS = -g -Wall -Wextra -pedantic -std=c99
 LDFLAGS += -lreadline
@@ -37,7 +39,7 @@ include .depend
 
 vm-dcpu16:     vm-dcpu16.o dcpu16.o common.o display.o
 
-as-dcpu16:     as-dcpu16.o common.o
+as-dcpu16:     as-dcpu16.o dcpu16.o common.o
 
 clean: 
        @rm -rf $(PROGRAMS) *.o *.dSYM .depend
index 1475ddfa661303cdc9dd9461c6ce3ccde4d1b32c..73b2fa37a662dc4c4a2dbd9219dc01706a98ac6d 100644 (file)
@@ -17,6 +17,8 @@
  *    2012 04 07 - implementation started
  *    2012 04 10 - functional
  *    2012 04 16 - support dat statements
+ *    2012 05 05 - v1.7 revision started
+ *    2012 05 08 - v1.7 revision implemented
  *
  *  TODO
  *    needs ability to specify location for code or data
@@ -71,6 +73,12 @@ void usage_(char *prog, unsigned int full) {
     }
 }
 
+/* LSB-0  aaaaaabbbbbooooo */
+#define OPCODE_BITS     5
+#define OPERAND_B_BITS  5
+#define OPERAND_A_BITS  6
+#define N_BIT_MASK(__x__) ((1 << (__x__)) - 1)
+
 
 /* instructions have operands */
 struct operand_ {
@@ -207,22 +215,41 @@ int opcode_bits_(char *opcode) {
         char value;
     } opcodes_lower_nibble[] = {
         { "JSR", 0x00 },
-        /* { "future nbi instruction", 0x00 }, */
+        { "INT", 0x00 },
+        { "IAG", 0x00 },
+        { "IAS", 0x00 },
+        { "RFI", 0x00 },
+        { "IAQ", 0x00 },
+        { "HWN", 0x00 },
+        { "HWQ", 0x00 },
+        { "HWI", 0x00 },
         { "SET", 0x01 },
         { "ADD", 0x02 },
         { "SUB", 0x03 },
         { "MUL", 0x04 },
-        { "DIV", 0x05 },
-        { "MOD", 0x06 },
-        { "SHL", 0x07 },
-        { "SHR", 0x08 },
-        { "AND", 0x09 },
-        { "BOR", 0x0a },
-        { "XOR", 0x0b },
-        { "IFE", 0x0c },
-        { "IFN", 0x0d },
-        { "IFG", 0x0e },
-        { "IFB", 0x0f },
+        { "MLI", 0x05 },
+        { "DIV", 0x06 },
+        { "DVI", 0x07 },
+        { "MOD", 0x08 },
+        { "MDI", 0x09 },
+        { "AND", 0x0a },
+        { "BOR", 0x0b },
+        { "XOR", 0x0c },
+        { "SHR", 0x0d },
+        { "ASR", 0x0e },
+        { "SHL", 0x0f },
+        { "IFB", 0x10 },
+        { "IFC", 0x11 },
+        { "IFE", 0x12 },
+        { "IFN", 0x13 },
+        { "IFG", 0x14 },
+        { "IFA", 0x15 },
+        { "IFL", 0x16 },
+        { "IFU", 0x17 },
+        { "ADX", 0x1a },
+        { "SBX", 0x1b },
+        { "STI", 0x1e },
+        { "SDI", 0x1f },
         {    "", 0x00 }
     }, *o;
 
@@ -248,6 +275,14 @@ int nbi_opcode_bits_(char *nbi_opcode) {
     } nbi_opcodes_bits[] = {
         { "   ", 0x00 }, /* reserved for future */
         { "JSR", 0x01 },
+        { "INT", 0x08 },
+        { "IAG", 0x09 },
+        { "IAS", 0x0a },
+        { "RFI", 0x0b },
+        { "IAQ", 0x0c },
+        { "HWN", 0x10 },
+        { "HWQ", 0x11 },
+        { "HWI", 0x12 },
         { "", 0x00 }
     }, *o;
 
@@ -299,6 +334,7 @@ void buf_strip_chars_(char *buf, char *chars) {
  *  returns -1 if it could not parse the operand
  *  returns -2 if it could not parse the operand due to an unresolved label
  *  notes: nextword may be overwritten even if it's not used in final instruction
+ *
  */
 static
 int value_bits_(struct dynamic_array *labels, const char *operand_orig, DCPU16_WORD *nextword, unsigned int *nextwordused, unsigned int allow_short_labels) {
@@ -347,18 +383,49 @@ int value_bits_(struct dynamic_array *labels, const char *operand_orig, DCPU16_W
     }
 
     /* easy matches */
-    if (strcasecmp(operand, "POP") == 0) {
+
+    /* push and pop now share the same operand value */
+    if (strcasecmp(operand, "POP") == 0
+    ||  strcasecmp(operand, "[SP++]") == 0) {
         DEBUG_PRINTFQ("is POP\n");
         return 0x18;
     }
-    if (strcasecmp(operand, "PUSH") == 0) {
+    if (strcasecmp(operand, "PUSH") == 0
+    || strcasecmp(operand, "[--SP]") == 0) {
         DEBUG_PRINTFQ("is PUSH\n");
-        return 0x19;
+        return 0x18;
     }
-    if (strcasecmp(operand, "PEEK") == 0) {
+
+    if (strcasecmp(operand, "PEEK") == 0
+    ||  strcasecmp(operand, "[SP]") == 0) {
         DEBUG_PRINTFQ("is PEEK\n");
+        return 0x19;
+    }
+
+    /* this could be better, if we had a real token tree */
+    if (strncasecmp(operand, "PICK", 4) == 0) {
+        DEBUG_PRINTFQ("is PICK ");
+
+        errno = 0;
+        l = strtoul(operand + 4, &ep, 0);
+        if (errno == 0
+        && (*(operand + 4) && (*ep == '\0')) ) {
+            if (l > 0xffff) {
+                DEBUG_PRINTFQ("(out of range)\n");
+                fprintf(stderr, "constant invalid in operand '%s'\n", operand_orig);
+                return -1;
+            }
+        } else if (errno == ERANGE) {
+            DEBUG_PRINTFQ("(out of range)\n");
+            fprintf(stderr, "constant invalid in operand '%s'\n", operand_orig);
+            return -1;
+        }
+        *nextword = l & 0xffff;
+        *nextwordused += 1;
+        DEBUG_PRINTFQ("0x%04x\n", *nextword);
         return 0x1a;
     }
+
     if (strcasecmp(operand, "SP") == 0) {
         DEBUG_PRINTFQ("is register SP\n");
         return 0x1b;
@@ -367,8 +434,8 @@ int value_bits_(struct dynamic_array *labels, const char *operand_orig, DCPU16_W
         DEBUG_PRINTFQ("is register PC\n");
         return 0x1c;
     }
-    if (strcasecmp(operand, "O") == 0) {
-        DEBUG_PRINTFQ("is register O\n");
+    if (strcasecmp(operand, "EX") == 0) {
+        DEBUG_PRINTFQ("is register EX\n");
         return 0x1d;
     }
 
@@ -397,12 +464,12 @@ int value_bits_(struct dynamic_array *labels, const char *operand_orig, DCPU16_W
             ep++;
 
             /* figure out which one is which */
-            if (strlen(ep) == 1
-            &&  strchr("AaBbCcXxYyZzIiJj", *ep)) {
+            if ((strlen(ep) == 1 && strchr("AaBbCcXxYyZzIiJj", *ep))
+            ||  (strlen(ep) == 2 && strcasecmp(ep, "SP")) ) {
                 reg = ep;
                 constant = operand;
-            } else if (strlen(operand) == 1
-            &&         strchr("AaBbCcXxYyZzIiJj", *operand) ) {
+            } else if ((strlen(operand) == 1 && strchr("AaBbCcXxYyZzIiJj", *operand))
+            ||         (strlen(operand) == 2 && strcasecmp(operand, "SP")) ) {
                 reg = operand;
                 constant = ep;
             } else {
@@ -427,15 +494,17 @@ int value_bits_(struct dynamic_array *labels, const char *operand_orig, DCPU16_W
                 /* seems fine */
                 *nextword = l & 0xffff;
                 *nextwordused += 1;
+
+                /* special case [SP+n]/PICK n */
+                if (strlen(reg) == 2) {
+                    DEBUG_PRINTFQ("is PICK 0x%04x\n", *nextword);
+                    return 0x1a;
+                }
+
                 DEBUG_PRINTFQ("is a dereferenced register (%c) + constant (%hu)\n", *reg, *nextword);
                 return 0x10 | register_enumerate_(*reg);
             } else if (errno == ERANGE) {
-#if 0
-oh, right, labels fall through
-                DEBUG_PRINTFQ("is out of range\n");
-                fprintf(stderr, "trouble with operand '%s': %s\n", operand_orig, strerror(errno));
-                return -1;
-#endif
+                fprintf(stderr, "%s('%s'):%s\n", "strtoul", constant, strerror(errno));
             }
 
             /* what? still here? assume it's a label, I guess */
@@ -497,8 +566,11 @@ oh, right, labels fall through
         }
 
         DEBUG_PRINTFQ("is literal value (%lu)\n", l);
-        if (l < 0x20) {
-            return l + 0x20;
+        if (l < 0x1f) {
+            return l + 0x21;
+        }
+        if (l == 0xffff) {
+            return 0x20;
         }
 
         *nextword = l & 0xffff;
@@ -515,9 +587,15 @@ oh, right, labels fall through
     }
 
     DEBUG_PRINTFQ("is label '%s' (0x%02hx)\n", operand, *nextword);
-    if (*nextword < 0x20 && allow_short_labels) {
+    if (allow_short_labels
+    &&  (*nextword < 0x1f) ) {
         DEBUG_PRINTF("small value label win\n");
-        return (0x20 + *nextword) & 0x3f;
+        return (0x21 + *nextword) & N_BIT_MASK(OPERAND_A_BITS);
+    }
+    if (allow_short_labels
+    &&  (*nextword == 0xffff) ) {
+        DEBUG_PRINTF("small value label win\n");
+        return 0x20;
     }
 
     *nextwordused += 1;
@@ -538,6 +616,15 @@ int instruction_print_(struct instruction_ *i, unsigned int with_label) {
     for (o = i->operands; o; o = o->next)
         r += printf(" %s%s", o->operand, o->next ? "," : "");
 
+    if (i->ready) {
+        DCPU16_WORD l;
+        printf(" [");
+        l = dcpu16_mnemonify_buf(i->instr_words);
+        printf("]");
+
+        if (i->length != l)
+            DEBUG_PRINTF("!!internal inconsistency!! i->length:%u l:%hu should match\n", i->length, l);
+    }
     return r;
 }
 
@@ -715,14 +802,6 @@ int instr_assemble_(struct dynamic_array *labels, struct instruction_ *i, unsign
         printf("(line %zu)\n", i->src_line);
     }
 
-#if 0
-while debugging, always reassemble
-    if (i->ready) {
-        /* already assembled, nothing to do */
-        return 0;
-    }
-#endif
-
     if (i->opcode == NULL) {
         assert(i->label);
         assert(i->operands == NULL);
@@ -807,10 +886,10 @@ while debugging, always reassemble
         fprintf(stderr, "'\n");
         return -1;
     }
-    i->instr_words[0] |= 0x0f & bits;
+    i->instr_words[0] |= bits & N_BIT_MASK(OPCODE_BITS);
 
-    /* in rendered bytecode, all instructions have two operands; nbi instructions take 'first operand' bits. */
-    if ((bits & 0x0f) == 0) {
+    /* in rendered bytecode, all instructions have a and b operands; nbi instructions occupy 'b operand' bits. */
+    if ((bits & N_BIT_MASK(OPCODE_BITS)) == 0) {
         bits = nbi_opcode_bits_(i->opcode);
         if (bits < 0) {
             fprintf(stderr, "INTERNAL ERROR: missing instruction in nbi opcode table\n");
@@ -833,7 +912,11 @@ while debugging, always reassemble
         }
         o = o->next;
     }
-    i->instr_words[0] |= (bits & 0x3f) << 4;
+    if (bits > N_BIT_MASK(OPERAND_B_BITS)) {
+        fprintf(stderr, "%s: internal error: operand '%s' generated out of range\n", __func__, "b");
+        return -1;
+    }
+    i->instr_words[0] |= (bits & N_BIT_MASK(OPERAND_B_BITS)) << OPCODE_BITS;
 
     if (o == NULL) {
         fprintf(stderr, "'%s' requires more operands\n", i->opcode);
@@ -851,7 +934,10 @@ while debugging, always reassemble
         bits = 0;
     }
     o = o->next;
-    i->instr_words[0] |= (bits & 0x3f) << 10;
+    if (bits > N_BIT_MASK(OPERAND_A_BITS)) {
+        fprintf(stderr, "%s: internal error: operand '%s' generated out of range\n", __func__, "a");
+    }
+    i->instr_words[0] |= (bits & N_BIT_MASK(OPERAND_A_BITS)) << (OPCODE_BITS + OPERAND_B_BITS);
 
     if (o != NULL) {
         fprintf(stderr, "too many operands\n");
index 23ef53a41747da7aadb9a51baec085033d5e1bab..75c6cf0df39c3d0d33ce0bd06cf6bad984dd17b2 100644 (file)
--- a/dcpu16.c
+++ b/dcpu16.c
  *    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
  *
  *  TODO
+ *    !! 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
  *    refactor opcode functiontables into switch statements
  *    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 OPCODE_BASIC_BITS   4
-#define OPCODE_OPERAND_BITS 6
+#define OPCODE_BASIC_BITS   5
+#define OPCODE_OPERAND_B_BITS 5
+#define OPCODE_OPERAND_A_BITS 6
 
 static const char * const regnames_ = "ABCXYZIJ";
 
@@ -99,116 +106,195 @@ 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;
+
+    if (d->interrupts_tail_ == d->interrupts_head_) {
+        d->on_fire_ = 1;
+        WARN("interrupt queue overflow (system is now on fire)");
+        return -1;
+    }
+
+    return 0;
+}
 
-/*  value_decode_
- * sets *v to be the destination of the value
- * advances d->pc if necessary
- * workv is buffer to use to accumulate literal value before use, one exists for either potential instruction operand
- * e_addr is for accounting callback
- * returns true if destination points to literal (id est *v should ignore writes)
- */
 static
-unsigned int value_decode_(struct dcpu16 *d, DCPU16_WORD value, DCPU16_WORD *work_v, DCPU16_WORD **v, DCPU16_WORD *e_addr) {
-    DCPU16_WORD nextword;
-    unsigned int retval = 0;
+DCPU16_WORD interrupt_dequeue_(struct dcpu16 *d) {
+    DCPU16_WORD message;
 
+    if (d->interrupts_tail_ == d->interrupts_head_) {
+        WARN("interrupt underflow");
+        return 0;
+    }
+
+    message = d->interrupts_[d->interrupts_head_];
+    d->interrupts_head_ += 1;
+    d->interrupts_head_ %= DCPU16_INTERRUPT_QUEUE_SIZE;
+
+    return message;
+}
+
+/*  value_decode_
+ * 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
+ * cycles 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,
+                  DCPU16_WORD *work_v, DCPU16_WORD **v, DCPU16_WORD *e_addr,
+                  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)(d->pc + *pc_adjust),
+                sp = (DCPU16_WORD)(d->sp + *sp_adjust);
+
+    TRACE("%s: pc:0x%04x sp:0x%04x value_data:0x%04x\n", 
+          __func__,
+          pc,
+          sp,
+          value_data);
 
     if (value <= 0x07) { /* register */
         *v = d->reg + value;
-        TRACE(">>     %c (0x%04x)",
+        TRACE("%s>>     %c (0x%04x)",
+              __func__,
               regnames_[value],
               **v);
+        return;
+    }
 
-    } 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],
+    if (value <= 0x0f) { /* [register] */
+        *v = &(d->ram[ d->reg[value & 0x07] ]);
+        *e_addr = d->reg[value & 0x07];
+        TRACE("%s>>     [%c] [0x%04x] (0x%04x)",
+              __func__,
+              regnames_[value & 0x07],
+              d->reg[value & 0x07],
               **v);
-        *e_addr = d->reg[(value & 0x07)];
+        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 <= 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)",
+              __func__,
+              regnames_[value & 0x07],
+              value_data,
+              d->reg[value & 0x07],
               **v);
-        *e_addr = nextword + d->reg[(value & 0x07)];
+        return;
+    }
 
-    } else switch (value) {
-        case 0x18: /* POP / [sp++] */
-        *v = &(d->ram[ d->sp++ ]);
-        TRACE(">>     POP [0x%04x] (0x%04x)",
-              d->sp - 1,
-              **v);
-        *e_addr = d->sp - 1;
+    switch (value) {
+        case 0x18: /* PUSH/[--SP] or POP/[SP++] */
+        if (value_is_a == 0) { /* b */
+            *v = &(d->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]);
+            *sp_adjust += 1;
+            *e_addr = sp;
+            TRACE("%s>>     POP [0x%04x] (0x%04x)", __func__, sp, **v);
+        }
         break;
 
-        case 0x19: /* PEEK / [sp] */
-        *v = &(d->ram[ d->sp ]);
-        TRACE(">>     PEEK [0x%04x] (0x%04x)",
-              d->sp,
+        case 0x19: /* PEEK/[SP] */
+        *v = &(d->ram[sp]);
+        *e_addr = sp;
+        TRACE("%s>>     PEEK [0x%04x] (0x%04x)",
+              __func__,
+              sp,
               **v);
-        *e_addr = d->sp;
         break;
 
-        case 0x1a: /* PUSH / [--sp] */
-        *v = &(d->ram[ --d->sp ]);
-        TRACE(">>     PUSH [0x%04x] (0x%04x)",
-              d->sp + 1,
+        case 0x1a: /* PICK n */
+        *pc_adjust += 1;
+        *cycle_adjust += 1;
+        *e_addr = sp + value_data;
+        *v = d->ram + *e_addr;
+        TRACE("%s>>     PICK 0x%04x [0x%04x] (0x%04x)",
+              __func__,
+              value_data,
+              sp + value_data,
               **v);
-        *e_addr = d->sp + 1;
         break;
 
         case 0x1b: /* SP */
         *v = &(d->sp);
-        TRACE(">>     SP (0x%04x)",
+        TRACE("%s>>     %s (0x%04x)",
+              __func__,
+              "SP",
               **v);
         break;
 
         case 0x1c: /* PC */
         *v = &(d->pc);
-        TRACE(">>     PC (0x%04x)", **v);
+        TRACE("%s>>     %s (0x%04x)",
+              __func__,
+              "PC",
+              **v);
         break;
 
-        case 0x1d: /* O */
-        *v = &(d->o);
-        TRACE(">>     O (0x%04x)", **v);
+        case 0x1d: /* EX */
+        *v = &(d->ex);
+        TRACE("%s>>     %s (0x%04x)",
+              __func__,
+              "EX",
+              **v);
         break;
 
         case 0x1e: /* [next word] / [[pc++]] */
-        nextword = d->ram[ d->pc++ ];
-        d->cycle++;
-        *v = &(d->ram[ nextword ]);
-        TRACE(">>     [nextword] [0x%04x] (0x%04x)",
-              nextword,
+        *pc_adjust += 1;
+        *cycle_adjust += 1;
+        *e_addr = value_data;
+        *v = d->ram + *e_addr;
+        TRACE("%s>>     [nextword] [0x%04x] (0x%04x)",
+              __func__,
+              value_data,
               **v);
-        *e_addr = nextword;
         break;
 
         case 0x1f: /* next word (literal) / [pc++] */
-        nextword = d->ram[ d->pc++ ];
-        d->cycle++;
-        *work_v = nextword;
+        *pc_adjust += 1;
+        *cycle_adjust += 1;
+        *work_v = value_data;
         *v = work_v;
-        TRACE(">>     nextword (0x%04x)", **v);
+        TRACE("%s>>     nextword (0x%04x)",
+              __func__,
+              **v);
         break;
 
-        default: /* 0x20-0x3f: literal values 0x00-0x1f */
-        *work_v = value & 0x1f;
+        default: /* 0x20-0x3f: literal values 0xffff-0x1e */
+        *work_v = (value & 0x1f) - 1;
         *v = work_v;
-        TRACE(">>     literal (0x%04x)", **v);
+        TRACE("%s>>     literal (0x%04x)",
+              __func__,
+              **v);
     }
-
-    return retval;
 }
 
 
@@ -216,25 +302,37 @@ unsigned int value_decode_(struct dcpu16 *d, DCPU16_WORD value, DCPU16_WORD *wor
 struct opcode_entry {
     unsigned short value;
     char name[OPCODE_NAME_LEN];
-    void (*impl)(struct dcpu16 *, DCPU16_WORD, DCPU16_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, DCPU16_WORD val_a, DCPU16_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_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_TYPE(op_type) DCPU16_WORD *a, *b;\
-    unsigned int lit_a;\
     DCPU16_WORD ev_a_addr = 0, ev_b_addr = 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, &ev_a_addr);\
         op_type;\
+        value_decode_(d, val_a, 1, val_a_data,\
+                      &d->reg_work_[1], &a, &ev_a_addr,\
+                      &pc_adjust, &sp_adjust, &cycle_adjust);\
+        d->sp += sp_adjust;\
+        d->cycle += cycle_adjust;\
     } while (0)
-#define OP_NBI_ (void)val_b, (void)b, (void)ev_b_addr
-#define OP_BASIC_ (void)value_decode_(d, val_b, &d->reg_work_[0], &b, &ev_b_addr)
+#define OP_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,\
+                                &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
@@ -256,10 +354,11 @@ OP_IMPL(nbi__reserved_) {
     OP_NBI(nbi__reserved_);
     /* reserved for future expansion */
 
-    DCPU16_WORD future_opcode = (d->ram[d->pc] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_BITS));
+    /* 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));
     WARN("reserved future opcode 0x%04x invoked", future_opcode);
 
-    ACCT_ILL(d->pc);
+    ACCT_ILL(d->pc - pc_adjust);
 }
 
 OP_IMPL(nbi_jsr) {
@@ -282,14 +381,151 @@ OP_IMPL(nbi__reserved2_) {
 
     WARN("reserved nbi opcode invoked");
 
-    ACCT_ILL(d->pc);
+    ACCT_ILL(d->pc - pc_adjust);
+}
+
+OP_IMPL(nbi_int) {
+    OP_NBI(nbi_int);
+
+    ACCT_R(ev_a_addr);
+
+    if (d->ia) {
+        if ( interrupt_enqueue_(d, *a) ) {
+            WARN("failed to queue interrupt");
+            return;
+        }
+
+        if (d->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;
+    }
+
+    d->cycle += 4;
+}
+
+OP_IMPL(nbi_iag) {
+    OP_NBI(nbi_iag);
+
+    *a = d->ia;
+
+    ACCT_W(ev_a_addr);
+}
+
+OP_IMPL(nbi_ias) {
+    OP_NBI(nbi_ias);
+
+    d->ia = *a;
+
+    ACCT_R(ev_a_addr);
+}
+
+/* does this just ignore its operand? */
+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++];
+}
+
+OP_IMPL(nbi_iaq) {
+    OP_NBI(nbi_iaq);
+
+    if (*a) {
+        d->interrupts_deferred_ = 1;
+    } else {
+        d->interrupts_deferred_ = 0;
+    }
+
+    ACCT_R(ev_a_addr);
+}
+
+OP_IMPL(nbi_hwn) {
+    OP_NBI(nbi_hwn);
+
+    ACCT_W(ev_a_addr);
+
+    *a = d->hw_table_entries_;
+
+    d->cycle += 2;
+}
+
+OP_IMPL(nbi_hwq) {
+    OP_NBI(nbi_hwq);
+
+    ACCT_R(ev_a_addr);
+
+    if (*a >= d->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;
+        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;
+
+    d->cycle += 4;
+}
+
+OP_IMPL(nbi_hwi) {
+    OP_NBI(nbi_hwi);
+
+    ACCT_R(ev_a_addr);
+
+    if (*a > d->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);
+}
+
+OP_IMPL(nbi_hcf) {
+    OP_NBI(nbi_hcf);
+
+    ACCT_R(ev_a_addr);
+
+    d->on_fire_ = 1;
+    WARN("system on fire");
+
+    d->cycle += 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)
 
@@ -297,215 +533,325 @@ static const struct opcode_entry opcode_nbi_entries[] = {
 /* basic opcodes */
 
 /*
-    N.B. the following function does not decode values, (thus does not advance pc &c)
-    Decoding is handled by the opcode functions it calls.
+    N.B. the following function does not decode values, (thus does not advance sp)
+    Decoding is handled by the secondary opcode functions it calls.
 */
 OP_IMPL(_nbi_) {
     /* non-basic instruction */
 
     /* don't do normal value decoding here */
 
-    DCPU16_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);
+    e->impl(d, 0, 0, val_a, val_a_data);
 }
 
 OP_IMPL(set) {
     OP_BASIC(set);
-    /* sets a to b */
+    /* sets b to a */
 
-    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
 
     /*
-        if a is a literal, it's aimed at a scratch register,
+        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.
      */
-    *a = *b;
+    *b = *a;
 
     d->cycle += 1;
 
-    ACCT_W(ev_a_addr);
+    ACCT_W(ev_b_addr);
 }
 
 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;
 
     ACCT_R(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    *a = acc;
-    d->o = (acc > 0xffff);
+    *b = acc;
+    d->ex = (acc > 0xffff);
 
     d->cycle += 2;
 
-    ACCT_W(ev_a_addr);
+    ACCT_W(ev_b_addr);
 }
 
 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;
 
     ACCT_R(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    *a = acc;
-    d->o = (acc > 0xffff);
+    *b = acc;
+    d->ex = (acc > 0xffff);
 
     d->cycle += 2;
 
-    ACCT_W(ev_a_addr);
+    ACCT_W(ev_b_addr);
 }
 
 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;
 
     ACCT_R(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    *a = acc;
-    d->o = acc >> 16;
+    *b = acc;
+    d->ex = acc >> 16;
 
     d->cycle += 2;
 
-    ACCT_W(ev_a_addr);
+    ACCT_W(ev_b_addr);
+}
+
+OP_IMPL(mli) {
+    OP_BASIC(mli);
+    /* sets b to b*a, signed */
+    int acc = (short)*b * (short)*a;
+
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    *b = acc;
+    d->ex = acc >> 16;
+
+    d->cycle += 2;
+
+    ACCT_W(ev_b_addr);
 }
 
 OP_IMPL(div) {
     OP_BASIC(div);
-    /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
+    /* sets b to b/a, sets EX to ((b<<16)/a)&0xffff. if a==0, sets a and EX to 0 instead. */
 
     ACCT_R(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    if (*b == 0) {
-        *a = 0;
-        d->o = 0;
+    if (*a == 0) {
+        *b = 0;
+        d->ex = 0;
     } else {
-        *a = *a / *b;
-        d->o = (*a << 16) / *b;
+        *b = *b / *a;
+        d->ex = (*b << 16) / *a;
     }
 
     d->cycle += 3;
 
-    ACCT_W(ev_a_addr);
+    ACCT_W(ev_b_addr);
 }
 
-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 */
 
     ACCT_R(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    if (*b == 0) {
-        *a = 0;
+    if (*a == 0) {
+        *b = 0;
+        d->ex = 0;
     } else {
-        *a = *a % *b;
+        *b = (short)*b / (short)*a;
+        d->ex = (short)(*b << 16) / (short)*a;
     }
 
     d->cycle += 3;
 
-    ACCT_W(ev_a_addr);
+    ACCT_W(ev_b_addr);
 }
 
-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(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    *a = acc;
-
-    d->o = acc >> 16;
+    if (*a == 0) {
+        *b = 0;
+    } else {
+        *b = *b % *a;
+    }
 
-    d->cycle += 2;
+    d->cycle += 3;
 
     ACCT_W(ev_a_addr);
 }
 
-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(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    *a = acc;
-    d->o = (*a << 16) >> *b;
+    if (*a == 0) {
+        *b = 0;
+    } else {
+        *b = (short)*b % (short)*a;
+    }
 
-    d->cycle += 2;
+    d->cycle += 3;
 
-    ACCT_W(ev_a_addr);
+    ACCT_W(ev_b_addr);
 }
 
 OP_IMPL(and) {
     OP_BASIC(and);
-    /* sets a to a&b */
+    /* sets b to b&a */
 
     ACCT_R(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    *a = *a & *b;
+    *b = *b & *a;
 
     d->cycle += 1;
 
-    ACCT_W(ev_a_addr);
+    ACCT_W(ev_b_addr);
 }
 
 OP_IMPL(bor) {
     OP_BASIC(bor);
-    /* sets a to a|b */
+    /* sets b to b|a */
 
     ACCT_R(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    *a = *a | *b;
+    *b = *b | *a;
 
     d->cycle += 1;
 
-    ACCT_W(ev_a_addr);
+    ACCT_W(ev_b_addr);
 }
 
 OP_IMPL(xor) {
     OP_BASIC(xor);
-    /* sets a to a^b */
+    /* sets b to b^a */
 
     ACCT_R(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    *a = *a ^ *b;
+    *b = *b ^ *a;
 
     d->cycle += 1;
 
-    ACCT_W(ev_a_addr);
+    ACCT_W(ev_b_addr);
+}
+
+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(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    *b = acc & 0xffff;
+    d->ex = (*b << 16) >> *a;
+
+    d->cycle += 2;
+
+    WARN("IMPLEMENT");
+
+    ACCT_W(ev_b_addr);
+}
+
+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(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    *b = acc & 0xffff;
+    d->ex = (*b << 16) >> *a;
+
+    d->cycle += 2;
+
+    WARN("IMPLEMENT");
+
+    ACCT_W(ev_b_addr);
+}
+
+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(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    *b = acc;
+
+    d->ex = acc >> 16;
+
+    d->cycle += 2;
+
+    ACCT_W(ev_b_addr);
+}
+
+OP_IMPL(ifb) {
+    OP_BASIC(ifb);
+    /* performs next instruction only if (b&a)!=0 */
+
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    if ((*b & *a) != 0) {
+        /* */
+    } else {
+        d->skip_ = 1;
+        d->cycle += 1;
+    }
+
+    d->cycle += 2;
+}
+
+OP_IMPL(ifc) {
+    OP_BASIC(ifc);
+    /* performs next instruction only if (b&a)==0 */
+
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    if ((*b & *a) == 0) {
+        
+    } else {
+        d->skip_ = 1;
+        d->cycle += 1;
+    }
+
+    d->cycle += 2;
 }
 
 OP_IMPL(ife) {
     OP_BASIC(ife);
-    /* performs next instruction only if a==b */
+    /* performs next instruction only if b==a */
 
     ACCT_R(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    if (*a == *b) {
+    if (*b == *a) {
         /* */
     } else {
         d->skip_ = 1;
-        d->cycle++;
+        d->cycle += 1;
     }
 
     d->cycle += 2;
@@ -513,12 +859,12 @@ OP_IMPL(ife) {
 
 OP_IMPL(ifn) {
     OP_BASIC(ifn);
-    /* performs next instruction only if a!=b */
+    /* performs next instruction only if b!=a */
 
     ACCT_R(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    if (*a != *b) {
+    if (*b != *a) {
         /* */
     } else {
         d->skip_ = 1;
@@ -530,12 +876,12 @@ OP_IMPL(ifn) {
 
 OP_IMPL(ifg) {
     OP_BASIC(ifg);
-    /* performs next instruction only if a>b */
+    /* performs next instruction only if b>a */
 
     ACCT_R(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    if (*a > *b) {
+    if (*b > *a) {
         /* */
     } else {
         d->skip_ = 1;
@@ -545,14 +891,31 @@ OP_IMPL(ifg) {
     d->cycle += 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(ev_b_addr);
     ACCT_R(ev_a_addr);
 
-    if ((*a & *b) != 0) {
+    if (*b > *a) {
+        /* */
+    } else {
+        d->skip_ = 1;
+        d->cycle += 1;
+    }
+
+    d->cycle += 2;
+}
+
+OP_IMPL(ifl) {
+    OP_BASIC(ifl);
+    /* performs next instruction only if b<a */
+
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    if (*b < *a) {
         /* */
     } else {
         d->skip_ = 1;
@@ -562,28 +925,141 @@ OP_IMPL(ifb) {
     d->cycle += 2;
 }
 
+OP_IMPL(ifu) {
+    OP_BASIC(ifu);
+    /* performs next instruction only if b<a (signed) */
+
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    if (*b < *a) {
+        /* */
+    } else {
+        d->skip_ = 1;
+        d->cycle += 1;
+    }
+
+    d->cycle += 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(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    acc = *b + *a + d->ex;
+    *b = acc & 0xffff;
+    if (acc > 0xffff)
+        d->ex = 0x0001;
+    else
+        d->ex = 0x0000;
+
+    d->cycle += 3;
+
+    ACCT_W(ev_b_addr);
+}
+
+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(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    acc = *b - *a + d->ex;
+    *b = acc & 0xffff;
+    if (acc > 0xffff)
+        d->ex = 0xffff;
+    else
+        d->ex = 0;
+
+    d->cycle += 3;
+
+    ACCT_W(ev_b_addr);
+}
+
+OP_IMPL(sti) {
+    OP_BASIC(sti);
+    /* sets b to a, then increases I and J by 1 */
+
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    *b = *a;
+    d->reg[reg_index_('I')] += 1;
+    d->reg[reg_index_('J')] += 1;
+
+    d->cycle += 2;
+
+    ACCT_W(ev_b_addr);
+}
+
+OP_IMPL(std) {
+    OP_BASIC(std);
+    /* sets b to a, then decreases I and J by 1 */
+
+    ACCT_R(ev_b_addr);
+    ACCT_R(ev_a_addr);
+
+    *b = *a;
+    d->reg[reg_index_('I')] -= 1;
+    d->reg[reg_index_('J')] -= 1;
+
+    d->cycle += 2;
+
+    ACCT_W(ev_b_addr);
+}
+
+OP_IMPL(_reserved_) {
+    OP_BASIC(_reserved_);
+
+    WARN("reserved opcode invoked");
+
+    ACCT_ILL(d->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 }
 };
 
 static inline
-void dump_operand_value_(DCPU16_WORD value, DCPU16_WORD nextword) {
+void dump_operand_value_(DCPU16_WORD value, DCPU16_WORD nextword, unsigned int value_position) {
     if (value <= 0x07) {
         printf(" %c", regnames_[value]);
     } else if (value <= 0x0f) {
@@ -591,35 +1067,93 @@ void dump_operand_value_(DCPU16_WORD value, DCPU16_WORD nextword) {
     } else if (value <= 0x17) {
         printf(" [0x%04x + %c]", nextword, regnames_[value & 0x07]);
     } else switch (value) {
-        case 0x18: printf(" POP"); break;
+        case 0x18:
+            if (value_position == 0) { /* b */
+                printf(" PUSH");
+            } else {
+                printf(" POP");
+            }
+            break;
         case 0x19: printf(" PEEK"); break;
-        case 0x1a: printf(" PUSH"); break;
+        case 0x1a: printf(" PICK 0x%04x", nextword); break;
         case 0x1b: printf(" SP"); break;
         case 0x1c: printf(" PC"); break;
-        case 0x1d: printf(" O"); 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 - 0x20);
+        default:   printf(" 0x%02x", value - 0x21);
     }
 }
 
 
-/* split a word into the parts of an instruction, and determine how many words it takes up in total */
+/* split a sequence of (one to three) words into the components of an instruction */
 static inline
-void instruction_decode_(struct dcpu16 *d, DCPU16_WORD addr, DCPU16_WORD *opcode, DCPU16_WORD *a, DCPU16_WORD *b, DCPU16_WORD *instr_len) {
-    *opcode = d->ram[addr] & ((1 << OPCODE_BASIC_BITS) - 1);
-    *a = (d->ram[addr] >> OPCODE_BASIC_BITS) & ((1 << OPCODE_OPERAND_BITS) - 1);
-    *b = (d->ram[addr] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_BITS)) & ((1 << OPCODE_OPERAND_BITS) - 1);
-    if (instr_len) {
-        *instr_len = 1;
-        /* both basic and nbi opcodes use their b operand */
-        if ( (*b >= 0x10 && *b <= 0x17) || *b == 0x1e || *b == 0x1f )
-            *instr_len += 1;
-        /* but only basic uses a */
-        if (*opcode
-        &&  ((*a >= 0x10 && *a <= 0x17) || *a == 0x1e || *a == 0x1f) )
-                *instr_len += 1;
+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 ( (*b >= 0x10 && *b <= 0x17) || *b == 0x1e || *b == 0x1f ) {
+        *b_data = mem + (DCPU16_WORD)(addr + *instr_len);
+        TRACE("**b_data:%hu", **b_data);
+        *instr_len += 1;
+    } else {
+        *b_data = NULL;
+    }
+
+    if ( (*opcode != 0x0000 || (*opcode == 0 && *b != 0x0000) )
+    &&   ( (*a >= 0x10 && *a <= 0x17) || *a == 0x1e || *a == 0x1f) ) {
+        *a_data = mem + (DCPU16_WORD)(addr + *instr_len);
+        TRACE("**a_data:%hu", **a_data);
+        *instr_len += 1;
+    } else {
+        *a_data = NULL;
+    }
+
+#if 0
+    TRACE("\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
+}
+
+/*  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;
+
+    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_basic_entries + opcode;
+    printf("%s", e->name);
+
+    if (opcode) {
+        dump_operand_value_(b, b_data ? *b_data : 0, 0);
+        printf(",");
     }
+
+    if (opcode || b) {
+        dump_operand_value_(a, a_data ? *a_data : 0, 1);
+    }
+
+    return instr_len;
 }
 
 /*  dcpu16_disassemble_print
@@ -627,35 +1161,33 @@ void instruction_decode_(struct dcpu16 *d, DCPU16_WORD addr, DCPU16_WORD *opcode
     returns the length of the instruction in words
  */
 DCPU16_WORD dcpu16_disassemble_print(struct dcpu16 *d, DCPU16_WORD addr) {
-    DCPU16_WORD opcode, a, b, instr_len, i;
-    const struct opcode_entry *e;
+    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)] };
     unsigned int indent = 0;
     unsigned int partial = 0;
 
     if (!d) 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_(d, addr - i, &opcode, &a, &b, &instr_len);
+        instruction_decode_(d->ram, (DCPU16_WORD)(addr - i), &opcode, &b, &b_data, &a, &a_data, &instr_len);
         if (instr_len > i)
             partial++;
-        if (instr_len == i && opcode >= 0xc) {
+        if (instr_len == i
+        &&  (opcode >= 0x10 && opcode <= 0x17) ) {
             indent++;
             break;
         }
     }
+#endif
 
-    /* now get what we're really interested in */
-    instruction_decode_(d, addr, &opcode, &a, &b, &instr_len);
-
-    if (opcode)
-        e = opcode_basic_entries + opcode;
-    else
-        e = opcode_nbi_entries + ( (a < OPCODE_NBI_MAX) ? a : (OPCODE_NBI_MAX - 1) );
+    /* just need instr_len */
+    instruction_decode_(d->ram, addr, &opcode, &b, &b_data, &a, &a_data, &instr_len);
 
     /* show the raw words */
     printf("%04x", d->ram[addr]);
@@ -664,59 +1196,75 @@ DCPU16_WORD dcpu16_disassemble_print(struct dcpu16 *d, DCPU16_WORD addr) {
     }
 
     /* align things neatly, show the instruction */
-    printf("%s%s ;%s%s%s",
+    printf("%s%s ;%s%s",
            instr_len < 3 ? "     " : "",
            instr_len < 2 ? "     " : "",
            partial ? "*" : " ",
-           indent ? "  " : "",
-           e->name);
-
-    /* show the operands */
-    i = 0;
-    if (opcode) {
-        dump_operand_value_(a, d->ram[addr + 1]);
-        if ((a >= 0x10 && a <= 0x17) || a == 0x1e || a == 0x1f)
-            addr++;
-        printf(",");
-    }
+           indent ? "  " : "");
 
-    if (opcode || a)
-        dump_operand_value_(b, d->ram[addr + 1]);
+    dcpu16_mnemonify_buf(buf);
 
     return instr_len;
 }
 
 /* execute the next instruction */
 void dcpu16_step(struct dcpu16 *d) {
-    DCPU16_WORD opcode, a, b, instr_len;
+    DCPU16_WORD opcode, b, a, instr_len, *b_data, *a_data;
     const struct opcode_entry *e;
 
     if (!d) return;
 
     acct_event_(d, DCPU16_ACCT_EV_CYCLE, d->pc);
 
-    /*
-        PC is advanced while decoding the operands by the opcode functions.
-        Things like this could be organized a little better..
-    */
-    instruction_decode_(d, d->pc, &opcode, &a, &b, NULL);
+    /* 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);
+
+            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");
+            }
+        }
+    }
+
+    /* and make sure to execute an instruction after an interrupt */
 
-    d->pc++; /* all instructions take at least one word */
+    instruction_decode_(d->ram, d->pc, &opcode, &b, &b_data, &a, &a_data, &instr_len);
 
     for (e = opcode_basic_entries; e->impl; e++) {
         if (e->value == opcode) {
-            TRACE(">> %s 0x%04x, 0x%04x", e->name, a, b);
-            e->impl(d, a, b);
+            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);
             break;
         }
     }
 
-    /* and jump over next instr if needed */
+    /* get ready for the next one */
+    d->pc += instr_len;
+
+    /* and jump over next instr(s) if needed */
     if (d->skip_) {
-        instruction_decode_(d, d->pc, &opcode, &a, &b, &instr_len);
+        instruction_decode_(d->ram, d->pc, &opcode, &b, &b_data, &a, &a_data, &instr_len);
         d->pc += instr_len;
-        d->skip_ = 0;
         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);
+        }
+        d->skip_ = 0;
     }
 }
 
@@ -734,11 +1282,12 @@ void dcpu16_state_print(struct dcpu16 *d) {
         printf("  %c:0x%04x", regnames_[i], d->reg[i]);
     printf("\n");
 
-    printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
+    printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
            d->cycle,
-           "O", d->o,
+           "EX", d->ex,
            "SP", d->sp,
            "PC", d->pc,
+           "IA", d->ia,
            "PC");
 
     dcpu16_disassemble_print(d, d->pc);
@@ -763,6 +1312,30 @@ void dcpu16_dump_ram(struct dcpu16 *d, DCPU16_WORD start, DCPU16_WORD end) {
         printf("\n");
 }
 
+/*  dcpu16_hw_add
+ *  registers new 'hardware' device with system
+ */
+int dcpu16_hw_add(struct dcpu16 *vm, struct dcpu16_hw *hw) {
+    if (!vm || !hw)
+        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) {
+            fprintf(stderr, "%s():%s", "realloc", strerror(errno));
+            return -1;
+        }
+        vm->hw_table_ = tmp_ptr;
+        vm->hw_table_allocated_ += 32;
+    }
+
+    memcpy(vm->hw_table_ + vm->hw_table_entries_, hw, sizeof *hw);
+    vm->hw_table_entries_++;
+
+    return 0;
+}
+
 /*  dcpu16_acct_add
  *  Register callback fn to be triggered whenever event matching any events
  *  in bitwise mask occur.
@@ -801,7 +1374,8 @@ void dcpu16_reset(struct dcpu16 *d) {
     memset(d->reg, 0, sizeof d->reg);
     d->pc = 0;
     d->sp = 0;
-    d->o = 0;
+    d->ex = 0;
+    d->ia = 0;
     d->skip_ = 0;
     memset(d->ram, 0, sizeof d->ram);
 
index c893347ea3485e9b6aecb7bd0893b12c1a111ccf..35c1fe878877ba8ce9cf00c8f4e55a54caf60633 100644 (file)
--- a/dcpu16.h
+++ b/dcpu16.h
@@ -8,20 +8,32 @@ typedef unsigned short DCPU16_WORD;
 
 /* how much ram the system has */
 #define DCPU16_RAM 0x10000
+#define DCPU16_INTERRUPT_QUEUE_SIZE 256
 
 /* a self-contained dcpu16 core */
 struct dcpu16 {
     struct dcpu16_acct_cb *cb_table_;   /* list of callbacks to invoke for certain events */
     size_t cb_table_entries_;           /* callback list maintenance */
     size_t cb_table_allocated_;         /* callback list maintenance */
+
+    struct dcpu16_hw *hw_table_;        /* list of hardware attached to system */
+    size_t hw_table_entries_;           /* hardware list maintenance */
+    size_t hw_table_allocated_;         /* hardware list maintenance */
+
     unsigned int skip_ : 1;             /* skip execution of next instruction */
+    unsigned int interrupts_deferred_ : 1; /* queue software interrupts */
+    unsigned int on_fire_ : 1;          /* cpu is on fire */
     DCPU16_WORD reg_work_[2];           /* work registers for holding literal values while decoding instructions */
+    DCPU16_WORD interrupts_[DCPU16_INTERRUPT_QUEUE_SIZE];    /* fifo of pending interrupts */
+    size_t interrupts_head_;            /* interrupt queue maintenance */
+    size_t interrupts_tail_;            /* interrupt queue maintenance */
 
     unsigned long long cycle;           /* number of cycles this core has executed */
     DCPU16_WORD reg[8];                 /* system registers, a b c x y z i j */
     DCPU16_WORD pc;                     /* program counter */
     DCPU16_WORD sp;                     /* stack pointer */
-    DCPU16_WORD o;                      /* overflow */
+    DCPU16_WORD ex;                     /* overflow */
+    DCPU16_WORD ia;                     /* interrupt address */
     DCPU16_WORD ram[DCPU16_RAM];        /* memory */
 };
 
@@ -39,6 +51,18 @@ struct dcpu16_acct_cb {
     dcpu16_acct_event mask;
 };
 
+/* these are used to define hardware attached to the system */
+struct dcpu16_hw {
+    char *name_;
+    DCPU16_WORD id_l;
+    DCPU16_WORD id_h;
+    DCPU16_WORD ver;
+    DCPU16_WORD mfg_l;
+    DCPU16_WORD mfg_h;
+    void (*int_fn)(struct dcpu16 *, void *);
+    void *data;
+};
+
 /* instantiate a new core */
 struct dcpu16 *dcpu16_new(void);
 
@@ -51,11 +75,17 @@ void dcpu16_state_print(struct dcpu16 *);
 /* print the contents of ram from second to third argument */
 void dcpu16_dump_ram(struct dcpu16 *, DCPU16_WORD, DCPU16_WORD);
 
+/* print words in buf as asm */
+DCPU16_WORD dcpu16_mnemonify_buf(DCPU16_WORD *);
+
 /* print the instruction at the specified address, returns number of words consumed in decoding */
 DCPU16_WORD dcpu16_disassemble_print(struct dcpu16 *, DCPU16_WORD);
 
+/* register new 'hardware' device with system */
+int dcpu16_hw_add(struct dcpu16 *, struct dcpu16_hw *);
+
 /* register a callback for an accounting event */
-int dcpu16_acct_add(struct dcpu16 *, dcpu16_acct_event mask, dcpu16_ev_cb_t *fn, void *data);
+int dcpu16_acct_add(struct dcpu16 *, dcpu16_acct_event, dcpu16_ev_cb_t *, void *);
 
 /* execute the next instruction */
 void dcpu16_step(struct dcpu16 *);