added callback support for memory access
[dcpu16] / dcpu16.c
index d0d3b40dbff499c2265750c26b710016bc445416..7d67cb178f88e15aea357c54ee221bb77ae2ff31 100644 (file)
--- a/dcpu16.c
+++ b/dcpu16.c
@@ -1,5 +1,5 @@
-#include <stdio.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
 #include <string.h>
@@ -7,6 +7,8 @@
 #include <assert.h>
 #include <sysexits.h>
 
+#include "dcpu16.h"
+
 /*
  *  emulates the DCPU16 system from http://0x10c.com/doc/dcpu-16.txt
  *  
  *  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
  *
  *  TODO
- *    move cli driver to separate module
+ *    drop checks for assigning to literals -- it won't affect anything anyhow
+ *    debug short literal decoding
  */
 
 static const char * const src_id_ = "$Id$";
 
-/* the target system's concept of a word */
 #define WORD DCPU16_WORD
-typedef unsigned short WORD;
 
-#define RAM_SIZE 0x10000
 static const char regnames_[] = "ABCXYZIJ";
-struct dcpu16 {
-    unsigned long long cycle;   /* number of cycles it took to get to current state */
-    WORD reg_work_[2];          /* holding bins for literal values when decoding instructions */
-    WORD reg[8];                /* system registers, a b c x y z i j */
-    WORD pc;                    /* program counter */
-    WORD sp;                    /* stack pointer */
-    WORD o;                     /* overflow */
-    unsigned int skip_ : 1;     /* */
-    WORD ram[RAM_SIZE];         /* memory */
-};
-
 
-static unsigned int trace_mode_ = 0; /* turn on for overly verbose internals */
-
-#define WARN(...) warn(__VA_ARGS__)
-static inline void warn(char *fmt, ...) __attribute__((format(printf, 1, 2)));
-static inline void warn(char *fmt, ...) {
+/* some default warning and debug reporting functions, which can be overridden by clients */
+#define WARN(...) do { if (warn_cb_) warn_cb_(__VA_ARGS__); } while (0)
+static inline void warn_(char *fmt, ...) __attribute__((format(printf, 1, 2)));
+static inline
+void warn_(char *fmt, ...) {
     va_list ap;
 
-    fprintf(stderr, "!!! ");
+    fprintf(stderr, "[warning] ");
     va_start(ap, fmt);
     vfprintf(stderr, fmt, ap);
     va_end(ap);
     fprintf(stderr, "\n");
     fflush(stderr);
 }
+static void (*warn_cb_)(char *fmt, ...) = warn_;
+void dcpu16_warn_cb_set(void (*fn)(char *fmt, ...)) {
+    warn_cb_ = fn;
+}
 
-
-#define TRACE(...) do { if (trace_mode_) trace(__VA_ARGS__); } while (0)
-static inline void trace(char *fmt, ...) __attribute__((format(printf, 1, 2)));
+#ifdef DEBUG
+#define TRACE(...) do { if (trace_cb_) trace_cb_(__VA_ARGS__); } while (0)
+static inline void trace_(char *fmt, ...) __attribute__((format(printf, 1, 2)));
 static inline
-void trace(char *fmt, ...) {
+void trace_(char *fmt, ...) {
     va_list ap;
 
+    fprintf(stdout, "[debug] ");
     va_start(ap, fmt);
     vfprintf(stdout, fmt, ap);
     va_end(ap);
     fprintf(stdout, "\n");
     fflush(stdout);
 }
+#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;
+}
+
+
+/*  dcpu16_acct_add
+ *  Register callback fn to be triggered whenever event matching exactly mask_ev
+ *  and additionally matching any of mask events occur.
+ */
+int dcpu16_acct_add(struct dcpu16 *vm, dcpu16_acct_event_ match_all, dcpu16_acct_event_ match_any, void (*fn)(dcpu16_acct_event_, DCPU16_WORD)) {
+    struct dcpu16_acct_cb cb;
+
+    cb.match_all = match_all;
+    cb.match_any = match_any;
+    cb.fn = fn;
+
+    /* add to vm->cb_table_, vm->cb_table_entries_, vm->cb_table_allocated_ */
+    if (vm->cb_table_entries_ == vm->cb_table_allocated_) {
+        size_t new_entries = vm->cb_table_allocated_ + 32;
+        void *tmp_ptr = realloc(vm->cb_table_, new_entries * sizeof *(vm->cb_table_));
+        if (tmp_ptr == NULL) {
+            fprintf(stderr, "%s():%s", "realloc", strerror(errno));
+            return -1;
+        }
+        vm->cb_table_ = tmp_ptr;
+        vm->cb_table_allocated_ += 32;
+    }
 
+    memcpy(vm->cb_table_ + vm->cb_table_entries_, &cb, sizeof cb);
+    vm->cb_table_entries_++;
 
-/* sets *v to be the destination of the value */
-/* workv is buffer to use to accumulate literal value before use */
-/* returns true if destination points to literal (id est *v should ignore writes) */
-static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WORD **v) {
+    return 0;
+}
+
+/*  acct_event_
+ *  invokes callbacks for specified event
+ */
+static inline
+void acct_event_(struct dcpu16 *vm, dcpu16_acct_event_ ev, DCPU16_WORD addr) {
+    struct dcpu16_acct_cb *cb;
+    size_t i;
+
+    for (i = 0; i < vm->cb_table_entries_; i++) {
+        if ( (cb[i].match_all & ev) == cb[i].match_all    /* exact match on event flags */
+        &&   (cb[i].match_any & ev) ) {                /* any match on rest */
+            cb[i].fn(ev, addr);
+        }
+    }
+}
+
+/*  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)
+ */
+static
+unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WORD **v, dcpu16_acct_event_ *e, WORD *e_addr) {
     WORD nextword;
     unsigned int retval = 0;
 
-    assert(value >= 0x00 && value <= 0x3f);
+    assert(value <= 0x3f);
+
+    *e = 0;
 
     /* does this value indicate a literal */
     if (value >= 0x1f)
@@ -105,6 +165,8 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
               regnames_[value&0x07],
               d->reg[value&0x07],
               **v);
+        *e |= DCPU16_ACCT_RAM;
+        *e_addr = d->reg[(value & 0x07)];
 
     } else if (value <= 0x17) { /* [next word + register] */
         nextword = d->ram[ d->pc++ ];
@@ -115,6 +177,8 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
               nextword,
               d->reg[(value & 0x07)],
               **v);
+        *e |= DCPU16_ACCT_RAM;
+        *e_addr = nextword + d->reg[(value & 0x07)];
 
     } else switch (value) {
         case 0x18: /* POP / [sp++] */
@@ -122,6 +186,8 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
         TRACE(">>     POP [0x%04x] (0x%04x)",
               d->sp - 1,
               **v);
+        *e |= DCPU16_ACCT_RAM;
+        *e_addr = d->sp - 1;
         break;
 
         case 0x19: /* PEEK / [sp] */
@@ -129,6 +195,8 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
         TRACE(">>     PEEK [0x%04x] (0x%04x)",
               d->sp,
               **v);
+        *e |= DCPU16_ACCT_RAM;
+        *e_addr = d->sp;
         break;
 
         case 0x1a: /* PUSH / [--sp] */
@@ -136,6 +204,8 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
         TRACE(">>     PUSH [0x%04x] (0x%04x)",
               d->sp + 1,
               **v);
+        *e |= DCPU16_ACCT_RAM;
+        *e_addr = d->sp + 1;
         break;
 
         case 0x1b: /* SP */
@@ -161,6 +231,8 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
         TRACE(">>     [nextword] [0x%04x] (0x%04x)",
               nextword,
               **v);
+        *e |= DCPU16_ACCT_RAM;
+        *e_addr = nextword;
         break;
 
         case 0x1f: /* next word (literal) / [pc++] */
@@ -200,13 +272,14 @@ struct opcode_entry {
 
 #define OP_IMPL(x) static void op_##x(struct dcpu16 *d, WORD val_a, WORD val_b)
 
-#define OP_NBI_ (void)val_b
-#define OP_BASIC_ (void)value_decode(d, val_b, &d->reg_work_[0], &b)
+#define OP_NBI_ (void)val_b, (void)b, (void)ev_b, (void)ev_b_addr
+#define OP_BASIC_ (void)value_decode(d, val_b, &d->reg_work_[0], &b, &ev_b, &ev_b_addr)
 #define OP_TYPE(op_type) WORD *a, *b;\
     unsigned int lit_a;\
+    dcpu16_acct_event_ ev_a = 0, ev_b = 0;\
+    WORD ev_a_addr = 0, ev_b_addr = 0;\
     do {\
-        assert(d != NULL);\
-        lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
+        lit_a = value_decode(d, val_a, &d->reg_work_[0], &a, &ev_a, &ev_a_addr);\
         op_type;\
         if (d->skip_) {\
             TRACE("++ SKIPPED");\
@@ -214,34 +287,12 @@ struct opcode_entry {
             return;\
         }\
     } while (0)
+#define OP_BASIC(x) OP_TYPE(OP_BASIC_)
+#define OP_NBI(x) OP_TYPE(OP_NBI_)
 
-
-#define OP_BASIC(x) WORD *a, *b;\
-    unsigned int lit_a;\
-    do {\
-        assert(d != NULL);\
-        lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
-        value_decode(d, val_b, &d->reg_work_[1], &b);\
-        if (d->skip_) {\
-            TRACE("++ SKIPPED");\
-            d->skip_ = 0;\
-            return;\
-        }\
-    } while(0)
-
-#define OP_NBI(x) WORD *a;\
-    unsigned int lit_a;\
-    do {\
-        assert(d != NULL);\
-        lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
-        (void)val_b;\
-        if (d->skip_) {\
-            TRACE("++ SKIPPED");\
-            d->skip_ = 0;\
-            return;\
-        }\
-    } while(0)
-
+/* accounting helpers */
+#define ACCT_R(ev) do { if (ev) { acct_event_(d, ev | DCPU16_ACCT_EV_READ, ev##_addr); } } while (0)
+#define ACCT_W(ev) do { if (ev) { acct_event_(d, ev | DCPU16_ACCT_EV_WRITE, ev##_addr); } } while (0)
 
 /* extended opcodes */
 
@@ -264,6 +315,8 @@ OP_IMPL(nbi_jsr) {
     OP_NBI(nbi_jsr);
     /* pushes the address of the next instruction to the stack, then sets PC to a */
 
+    ACCT_R(ev_a);
+
     d->ram[ --d->sp ] = d->pc;
     d->pc = *a;
 
@@ -293,7 +346,6 @@ static const struct opcode_entry opcode_nbi_entries[] = {
     instructions only have one operand.
 */
 OP_IMPL(_nbi_) {
-    assert(d != NULL);
     /* non-basic instruction */
 
     /* don't do normal value decoding here */
@@ -313,6 +365,9 @@ OP_IMPL(set) {
     OP_BASIC(set);
     /* sets a to b */
 
+    ACCT_R(ev_b);
+    ACCT_W(ev_a);
+
     /* only set non-literal target */
     if (val_a < 0x1f) {
         *a = *b;
@@ -326,12 +381,17 @@ OP_IMPL(add) {
     /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */
     unsigned int acc = *a + *b;
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (val_a < 0x1f) {
         *a = acc;
     }
-    d->o = acc >> 16;
+    d->o = (acc > 0xffff);
 
     d->cycle += 2;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(sub) {
@@ -339,12 +399,17 @@ OP_IMPL(sub) {
     /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
     unsigned int acc = *a - *b;
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (val_a < 0x1f) {
         *a = acc;
-    }
-    d->o = acc >> 16;
+    d->o = (acc > 0xffff);
 
+    }
     d->cycle += 2;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(mul) {
@@ -352,17 +417,25 @@ OP_IMPL(mul) {
     /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
     unsigned int acc = *a * *b;
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (val_a < 0x1f) {
         *a = acc;
     }
     d->o = acc >> 16;
     d->cycle += 2;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(div) {
     OP_BASIC(div);
     /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (*b == 0) {
         if (val_a < 0x1f) {
             *a = 0;
@@ -380,12 +453,17 @@ OP_IMPL(div) {
     }
 
     d->cycle += 3;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(mod) {
     OP_BASIC(mod);
     /* sets a to a%b. if b==0, sets a to 0 instead. */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (*b == 0) {
         if (val_a < 0x1f) {
             *a = 0;
@@ -397,6 +475,8 @@ OP_IMPL(mod) {
     }
 
     d->cycle += 3;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(shl) {
@@ -404,12 +484,17 @@ OP_IMPL(shl) {
     /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
     unsigned int acc = *a << *b;
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (val_a < 0x1f) {
         *a = acc;
     }
     d->o = acc >> 16;
 
     d->cycle += 2;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(shr) {
@@ -417,44 +502,64 @@ OP_IMPL(shr) {
     /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
     unsigned int acc = *a >> *b;
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (val_a < 0x1f) {
         *a = acc;
     }
     d->o = (*a << 16) >> *b;
 
     d->cycle += 2;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(and) {
     OP_BASIC(and);
     /* sets a to a&b */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (val_a < 0x1f) {
         *a = *a & *b;
     }
 
     d->cycle += 1;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(bor) {
     OP_BASIC(bor);
     /* sets a to a|b */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (val_a < 0x1f) {
         *a = *a | *b;
     }
 
     d->cycle += 1;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(xor) {
     OP_BASIC(xor);
     /* sets a to a^b */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (val_a < 0x1f) {
         *a = *a ^ *b;
     }
 
+    ACCT_W(ev_a);
+
     d->cycle += 1;
 }
 
@@ -462,6 +567,9 @@ OP_IMPL(ife) {
     OP_BASIC(ife);
     /* performs next instruction only if a==b */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (*a == *b) {
         /* */
     } else {
@@ -476,6 +584,9 @@ OP_IMPL(ifn) {
     OP_BASIC(ifn);
     /* performs next instruction only if a!=b */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (*a != *b) {
         /* */
     } else {
@@ -490,6 +601,9 @@ OP_IMPL(ifg) {
     OP_BASIC(ifg);
     /* performs next instruction only if a>b */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (*a > *b) {
         /* */
     } else {
@@ -504,6 +618,9 @@ OP_IMPL(ifb) {
     OP_BASIC(ifb);
     /* performs next instruction only if (a&b)!=0 */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if ((*a & *b) != 0) {
         /* */
     } else {
@@ -554,7 +671,7 @@ void dump_value(WORD value, WORD nextword) {
     }
 }
 
-void dump_instruction(struct dcpu16 *d, WORD addr) {
+void dcpu16_disassemble_print(struct dcpu16 *d, WORD addr) {
     WORD opcode, a, b;
     unsigned int instr_len = 1;
     const struct opcode_entry *e;
@@ -563,11 +680,11 @@ void dump_instruction(struct dcpu16 *d, WORD addr) {
     a = (d->ram[addr] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS)) & ((1 << 6) - 1);
     b = (d->ram[addr] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS + 6)) & ((1 << 6) - 1); 
 
-    assert(opcode < 0x0f);
-    assert(a < 0x3f);
-    assert(b < 0x3f);
+    assert(opcode <= 0x0f);
+    assert(a <= 0x3f);
+    assert(b <= 0x3f);
 
-    printf("next instr 0x%04x: %04x", addr, d->ram[addr]);
+    printf("%04x", d->ram[addr]);
 
     if (opcode != 0)
     {
@@ -585,7 +702,11 @@ void dump_instruction(struct dcpu16 *d, WORD addr) {
         e = opcode_basic_entries + opcode;
     else
         e = opcode_nbi_entries + ( (a < OPCODE_NBI_MAX) ? a : (OPCODE_NBI_MAX - 1) );
-    printf("\n\t%s", e->name);
+
+    printf("%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)
@@ -594,20 +715,14 @@ void dump_instruction(struct dcpu16 *d, WORD addr) {
     }
 
     dump_value(b, d->ram[addr + 1]);
-
-    printf("\n");
 }
 
-void dcpu16_execute_next_instruction(struct dcpu16 *d) {
+void dcpu16_step(struct dcpu16 *d) {
     WORD opcode;
     WORD val_a, val_b;
     const struct opcode_entry *e;
 
-    /* fetch next instruction */
-    if (d->pc > RAM_SIZE) { /* currently impossible */
-        WARN("%s beyond %u", "PC", RAM_SIZE);
-        /* d->pc %= RAM_SIZE; */
-    }
+    /* decode opcode and invoke */
 
     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);
@@ -624,144 +739,64 @@ void dcpu16_execute_next_instruction(struct dcpu16 *d) {
     }
 }
 
-static void usage(char *prog, unsigned int full) {
-    FILE *f = full ? stdout : stderr;
-    char *x = strrchr(prog, '/');
-
-    if (x && *(x + 1))
-        prog = x + 1;
-
-    if (full)
-        fprintf(f, "%s -- \n\n",
-                prog);
-
-    fprintf(f, "Usage: %s\n",
-            prog);
-
-    if (full) {
-        fprintf(f, "\nOptions:\n"
-                   "\t-h -- this screen\n");
-
-        fprintf(f, "\n%78s\n", src_id_);
-    }
-}
-
-static int file_load(struct dcpu16 *d, char *filename) {
-    FILE *f;
-    size_t r;
-
-    f = fopen(filename, "rb");
-    if (f == NULL)
-    {
-        fprintf(stderr, "%s(%s):%s\n", "fopen", filename, strerror(errno));
-        return -1;
-    }
-
-    r = fread(d->ram, sizeof(WORD), RAM_SIZE, f);
-    TRACE("read %zu words", r);
-
-    if (ferror(f)) {
-        fprintf(stderr, "%s():%s\n", "fread", strerror(errno));
-    }
-
-    fclose(f);
-    return 0;
-}
-
-static void testprog_load(struct dcpu16 *d) {
-    static WORD bin[] = {
-        0x7c01, 0x0030, 0x7de1, 0x1000, 0x0020, 0x7803, 0x1000, 0xc00d,
-        0x7dc1, 0x001a, 0xa861, 0x7c01, 0x2000, 0x2161, 0x2000, 0x8463,
-        0x806d, 0x7dc1, 0x000d, 0x9031, 0x7c10, 0x0018, 0x7dc1, 0x001a,
-        0x9037, 0x61c1, 0x7dc1, 0x001a, 0x0000
-    };
-    size_t i;
-
-    printf("loading...\n");
-    for (i = 0; i < (sizeof(bin) / sizeof(WORD)); i++)
-    {
-        printf(" %04x", bin[i]);
-        d->ram[i] = bin[i];
-    }
-    printf("\nloaded 0x%04zx words\n", i - 1);
-}
-
-static
-void dump_cpu_state(struct dcpu16 *d) {
+void dcpu16_state_print(struct dcpu16 *d) {
     unsigned int i;
 
-    printf("[--- cycle:0x%08llx %2s:0x%04x %2s:0x%04x %2s:0x%04x\n",
+    printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
            d->cycle,
-           "PC", d->pc,
+           "O", d->o,
            "SP", d->sp,
-           "O", d->o);
-    printf(" ");
+           "PC", d->pc,
+           "PC");
+
+    dcpu16_disassemble_print(d, d->pc);
+    printf("\n  ");
+
     for (i = 0; i < 8; i++)
         printf("  %c:0x%04x", regnames_[i], d->reg[i]);
     printf("\n");
 }
 
-static
-void dump_ram(struct dcpu16 *d, WORD start, WORD stop) {
+/*  dcpu16_dump_ram
+ *  print raw ram contents from start to stop
+ */
+void dcpu16_dump_ram(struct dcpu16 *d, WORD start, WORD end) {
     unsigned int i, j;
     const unsigned int n = 8; /* words per line */
 
-    for (i = start, j = 0; i <= stop; i++, j++) {
+    for (i = start, j = 0; i <= end; i++, j++) {
         if (j % n == 0)
             printf("0x%04x:\t", i);
         printf(" %04x%s", d->ram[i], (j % n) == (n - 1) ? "\n" : "");
     }
+    if ((j % n) != (n - 1))
+        printf("\n");
 }
 
+/*  dcpu16_reset
+ *  resets a dcpu16 instance to initial state
+ */
+void dcpu16_reset(struct dcpu16 *d) {
+    memset(d, 0, sizeof *d);
+}
 
-int main(int argc, char **argv) {
-    struct dcpu16 *m;
-    int c;
-    char buf[512];
-
-    m = calloc(1, sizeof *m);
-    if (m == NULL)
-    {
-        fprintf(stderr, "%s:%s\n", "calloc", strerror(errno));
-        exit(EX_OSERR);
-    }
-
-    while ( (c = getopt(argc, argv, "ht")) != EOF )
-    {
-        switch (c)
-        {
-            case 't':
-            trace_mode_ = 1;
-            dump_ram(m, 0, 0x001f);
-            testprog_load(m);
-            dump_ram(m, 0, 0x001f);
-            break;
-
-            case 'h':
-            usage(argv[0], 1);
-            exit(EX_OK);
-
-            default:
-            usage(argv[0], 0);
-            exit(EX_USAGE);
-        }
-    }
-
-    if (argc - optind)
-    {
-        /* read file */
-        file_load(m, argv[optind]);
-    }
+/*  dcpu16_new
+ *  allocate a new dcpu16 instance
+ */
+struct dcpu16 *dcpu16_new(void) {
+    struct dcpu16 *vm;
 
-    dump_cpu_state(m);
-    while (fgets(buf, sizeof buf, stdin)) {
-        dcpu16_execute_next_instruction(m);
-        dump_cpu_state(m);
-        if (trace_mode_)
-            dump_instruction(m, m->pc);
-    }
+    vm = calloc(1, sizeof *vm);
+    if (vm == NULL)
+        WARN("%s: %s(%zu): %s", __func__, "calloc", strerror(errno));
 
-    free(m);
+    return vm;
+}
 
-    exit(EX_OK);
+/*  dcpu16_delete
+ *  release a dcpu16 instance
+ */
+void dcpu16_delete(struct dcpu16 **vm) {
+    free(*vm);
+    *vm = NULL;
 }