11 * emulates the DCPU16 system from http://0x10c.com/doc/dcpu-16.txt
13 * I couldn't remember ever implementing an emulator before, so this
14 * happened. As such, consider this a toy in progress.
15 * There are likely many improvable aspects.
17 * Justin Wind <justin.wind@gmail.com>
18 * 2012 04 05 - implementation started
19 * 2012 04 06 - first functionality achieved
20 * 2012 04 09 - minor cleanups
23 * move cli driver to separate module
24 * drop checks for assigning to literals -- it won't affect anything anyhow
25 * debug short literal decoding
28 static const char * const src_id_
= "$Id$";
30 /* the target system's concept of a word */
31 #define WORD DCPU16_WORD
32 typedef unsigned short WORD
;
34 #define RAM_SIZE 0x10000
35 static const char regnames_
[] = "ABCXYZIJ";
37 unsigned long long cycle
; /* number of cycles it took to get to current state */
38 WORD reg_work_
[2]; /* holding bins for literal values when decoding instructions */
39 WORD reg
[8]; /* system registers, a b c x y z i j */
40 WORD pc
; /* program counter */
41 WORD sp
; /* stack pointer */
42 WORD o
; /* overflow */
43 unsigned int skip_
: 1; /* skip execution of next instruction */
44 WORD ram
[RAM_SIZE
]; /* memory */
48 static unsigned int trace_mode_
= 0; /* spew overly verbose internals */
50 #define WARN(...) do { if (warn_cb_) warn_cb_(__VA_ARGS__); } while (0)
51 static inline void warn_(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
53 void warn_(char *fmt
, ...) {
56 fprintf(stderr
, "!!! ");
58 vfprintf(stderr
, fmt
, ap
);
60 fprintf(stderr
, "\n");
63 static void (*warn_cb_
)(char *fmt
, ...) = warn_
;
64 void dcpu16_warn_cb_set(void (*fn
)(char *fmt
, ...)) {
68 #define TRACE(...) do { if (trace_cb_ && trace_mode_) trace_cb_(__VA_ARGS__); } while (0)
69 static inline void trace_(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
71 void trace_(char *fmt
, ...) {
75 vfprintf(stdout
, fmt
, ap
);
77 fprintf(stdout
, "\n");
80 static void (*trace_cb_
)(char *fmt
, ...) = trace_
;
81 void dcpu16_trace_cb_set(void (*fn
)(char *fmt
, ...)) {
87 /* sets *v to be the destination of the value */
88 /* workv is buffer to use to accumulate literal value before use */
89 /* returns true if destination points to literal (id est *v should ignore writes) */
90 static unsigned int value_decode(struct dcpu16
*d
, WORD value
, WORD
*work_v
, WORD
**v
) {
92 unsigned int retval
= 0;
94 assert(value
<= 0x3f);
96 /* does this value indicate a literal */
100 /* if we're skipping this instruction, just advance the pc if needed */
102 TRACE(">> SKIP decode");
103 if (value
== 0x1e || value
== 0x1f)
108 if (value
<= 0x07) { /* register */
110 TRACE(">> %c (0x%04x)",
114 } else if (value
<= 0x0f) { /* [register] */
115 *v
= &(d
->ram
[ d
->reg
[(value
& 0x07)] ]);
116 TRACE(">> [%c] [0x%04x] (0x%04x)",
117 regnames_
[value
&0x07],
121 } else if (value
<= 0x17) { /* [next word + register] */
122 nextword
= d
->ram
[ d
->pc
++ ];
124 *v
= &(d
->ram
[ nextword
+ d
->reg
[(value
& 0x07)] ]);
125 TRACE(">> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)",
126 regnames_
[(value
& 0x07)],
128 d
->reg
[(value
& 0x07)],
131 } else switch (value
) {
132 case 0x18: /* POP / [sp++] */
133 *v
= &(d
->ram
[ d
->sp
++ ]);
134 TRACE(">> POP [0x%04x] (0x%04x)",
139 case 0x19: /* PEEK / [sp] */
140 *v
= &(d
->ram
[ d
->sp
]);
141 TRACE(">> PEEK [0x%04x] (0x%04x)",
146 case 0x1a: /* PUSH / [--sp] */
147 *v
= &(d
->ram
[ --d
->sp
]);
148 TRACE(">> PUSH [0x%04x] (0x%04x)",
155 TRACE(">> SP (0x%04x)",
161 TRACE(">> PC (0x%04x)", **v
);
166 TRACE(">> O (0x%04x)", **v
);
169 case 0x1e: /* [next word] / [[pc++]] */
170 nextword
= d
->ram
[ d
->pc
++ ];
172 *v
= &(d
->ram
[ nextword
]);
173 TRACE(">> [nextword] [0x%04x] (0x%04x)",
178 case 0x1f: /* next word (literal) / [pc++] */
179 nextword
= d
->ram
[ d
->pc
++ ];
183 TRACE(">> nextword (0x%04x)", **v
);
186 default: /* 0x20-0x3f: literal values 0x00-0x1f */
187 *work_v
= value
& 0x1f;
189 TRACE(">> literal (0x%04x)", **v
);
195 #define OPCODE_BASIC_BITS (4)
196 #define OPCODE_BASIC_SHIFT (0)
198 #define OPCODE_NBI_BITS (6)
199 #define OPCODE_NBI_SHIFT (4)
201 #define OPCODE_FUTURE_BITS (16)
202 #define OPCODE_FUTURE_SHIFT (10)
204 #define OPCODE_NAME_LEN 16
205 struct opcode_entry
{
206 unsigned short value
;
207 char name
[OPCODE_NAME_LEN
];
208 void (*impl
)(struct dcpu16
*, WORD
, WORD
);
211 /* messy boilerplate for opcode handlers */
213 #define OP_IMPL(x) static void op_##x(struct dcpu16 *d, WORD val_a, WORD val_b)
215 #define OP_NBI_ (void)val_b, (void)b
216 #define OP_BASIC_ (void)value_decode(d, val_b, &d->reg_work_[0], &b)
217 #define OP_TYPE(op_type) WORD *a, *b;\
220 lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
223 TRACE("++ SKIPPED");\
228 #define OP_BASIC(x) OP_TYPE(OP_BASIC_)
229 #define OP_NBI(x) OP_TYPE(OP_NBI_)
232 /* extended opcodes */
235 N.B. this next function currently decodes values -- id est, it is
236 an opcode processing terminus; however, if 'future instruction set
237 expansion' happens, this will probably need to behave more like
238 the OP_IMPL(_nbi_) function which invoked it, if those instructions
239 have zero or differently styled operands.
241 OP_IMPL(nbi__reserved_
) {
242 OP_NBI(nbi__reserved_
);
243 /* reserved for future expansion */
245 WORD future_opcode
= (d
->ram
[d
->pc
] >> OPCODE_FUTURE_SHIFT
);
246 WARN("reserved future opcode 0x%04x invoked", future_opcode
);
251 /* pushes the address of the next instruction to the stack, then sets PC to a */
253 d
->ram
[ --d
->sp
] = d
->pc
;
259 OP_IMPL(nbi__reserved2_
) {
260 OP_NBI(nbi__reserved2_
);
263 WARN("reserved nbi opcode invoked");
266 static const struct opcode_entry opcode_nbi_entries
[] = {
267 {0x0, "(reserved)", op_nbi__reserved_
},
268 {0x1, "JSR", op_nbi_jsr
},
269 {0x2, "(reserved)", op_nbi__reserved2_
},
272 #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1)
278 N.B. the following function does not decode values, as the nbi
279 instructions only have one operand.
282 /* non-basic instruction */
284 /* don't do normal value decoding here */
286 WORD nbi_opcode
= val_a
;
287 const struct opcode_entry
*e
= opcode_nbi_entries
;
289 e
= opcode_nbi_entries
+ ( (nbi_opcode
< OPCODE_NBI_MAX
) ? nbi_opcode
: (OPCODE_NBI_MAX
- 1) );
291 assert(e
->impl
!= NULL
);
293 TRACE(">> %s 0x%04x", e
->name
, val_b
);
294 e
->impl(d
, val_b
, 0);
301 /* only set non-literal target */
311 /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */
312 unsigned int acc
= *a
+ *b
;
324 /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
325 unsigned int acc
= *a
- *b
;
337 /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
338 unsigned int acc
= *a
* *b
;
349 /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
357 unsigned int acc
= *a
/ *b
;
363 acc
= (*a
<< 16) / *b
;
372 /* sets a to a%b. if b==0, sets a to 0 instead. */
389 /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
390 unsigned int acc
= *a
<< *b
;
402 /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
403 unsigned int acc
= *a
>> *b
;
408 d
->o
= (*a
<< 16) >> *b
;
448 /* performs next instruction only if a==b */
462 /* performs next instruction only if a!=b */
476 /* performs next instruction only if a>b */
490 /* performs next instruction only if (a&b)!=0 */
492 if ((*a
& *b
) != 0) {
502 static const struct opcode_entry opcode_basic_entries
[] = {
503 {0x0, "(nbi)", op__nbi_
},
504 {0x1, "SET", op_set
},
505 {0x2, "ADD", op_add
},
506 {0x3, "SUB", op_sub
},
507 {0x4, "MUL", op_mul
},
508 {0x5, "DIV", op_div
},
509 {0x6, "MOD", op_mod
},
510 {0x7, "SHL", op_shl
},
511 {0x8, "SHR", op_shr
},
512 {0x9, "AND", op_and
},
513 {0xa, "BOR", op_bor
},
514 {0xb, "XOR", op_xor
},
515 {0xc, "IFE", op_ife
},
516 {0xd, "IFN", op_ifn
},
517 {0xe, "IFG", op_ifg
},
518 {0xf, "IFB", op_ifb
},
522 void dump_value(WORD value
, WORD nextword
) {
524 printf(" %c", regnames_
[value
]);
525 } else if (value
< 0x0f) {
526 printf(" [%c]", regnames_
[value
& 0x07]);
527 } else if (value
< 0x17) {
528 printf(" [0x%04x + %c]", nextword
, regnames_
[value
& 0x07]);
529 } else switch (value
) {
530 case 0x18: printf(" POP"); break;
531 case 0x19: printf(" PEEK"); break;
532 case 0x1a: printf(" PUSH"); break;
533 case 0x1b: printf(" SP"); break;
534 case 0x1c: printf(" PC"); break;
535 case 0x1d: printf(" O"); break;
536 case 0x1e: printf(" [0x%04x]", nextword
); break;
537 case 0x1f: printf(" 0x%04x", nextword
); break;
538 default: printf(" 0x%02x", value
- 0x20);
542 void dump_instruction(struct dcpu16
*d
, WORD addr
) {
544 unsigned int instr_len
= 1;
545 const struct opcode_entry
*e
;
547 opcode
= (d
->ram
[addr
] >> OPCODE_BASIC_SHIFT
) & ((1 << OPCODE_BASIC_BITS
) - 1);
548 a
= (d
->ram
[addr
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
)) & ((1 << 6) - 1);
549 b
= (d
->ram
[addr
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
+ 6)) & ((1 << 6) - 1);
551 assert(opcode
<= 0x0f);
555 printf(" next instr 0x%04x: %04x", addr
, d
->ram
[addr
]);
559 if (a
== 0x1e || a
== 0x1f) {
560 printf(" %04x", d
->ram
[addr
+ instr_len
]);
564 if (b
== 0x1e || b
== 0x1f) {
565 printf(" %04x", d
->ram
[addr
+ instr_len
]);
570 e
= opcode_basic_entries
+ opcode
;
572 e
= opcode_nbi_entries
+ ( (a
< OPCODE_NBI_MAX
) ? a
: (OPCODE_NBI_MAX
- 1) );
575 instr_len
< 3 ? " " : "",
576 instr_len
< 2 ? " " : "",
579 dump_value(a
, d
->ram
[addr
+ 1]);
580 if (a
== 0x1e || a
== 0x1f)
585 dump_value(b
, d
->ram
[addr
+ 1]);
590 void dcpu16_execute_next_instruction(struct dcpu16
*d
) {
593 const struct opcode_entry
*e
;
595 /* decode opcode and invoke */
597 opcode
= (d
->ram
[ d
->pc
] >> OPCODE_BASIC_SHIFT
) & ((1 << OPCODE_BASIC_BITS
) - 1);
598 val_a
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
)) & ((1 << 6) - 1);
599 val_b
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
+ 6)) & ((1 << 6) - 1);
603 for (e
= opcode_basic_entries
; e
->impl
; e
++) {
604 if (e
->value
== opcode
) {
605 TRACE(">> %s 0x%04x, 0x%04x", e
->name
, val_a
, val_b
);
606 e
->impl(d
, val_a
, val_b
);
612 static void usage(char *prog
, unsigned int full
) {
613 FILE *f
= full
? stdout
: stderr
;
614 char *x
= strrchr(prog
, '/');
620 fprintf(f
, "%s -- \n\n",
623 fprintf(f
, "Usage: %s\n",
627 fprintf(f
, "\nOptions:\n"
628 "\t-h -- this screen\n"
629 "\t-t -- test mode, load demo program\n"
630 "\t-v -- verbose execution tracing\n");
632 fprintf(f
, "\n%78s\n", src_id_
);
636 static int file_load(struct dcpu16
*d
, char *filename
) {
640 f
= fopen(filename
, "rb");
643 fprintf(stderr
, "%s(%s):%s\n", "fopen", filename
, strerror(errno
));
647 r
= fread(d
->ram
, sizeof(WORD
), RAM_SIZE
, f
);
648 TRACE("read %zu words", r
);
651 fprintf(stderr
, "%s():%s\n", "fread", strerror(errno
));
658 static void testprog_load(struct dcpu16
*d
) {
659 static WORD bin
[] = {
660 0x7c01, 0x0030, 0x7de1, 0x1000, 0x0020, 0x7803, 0x1000, 0xc00d,
661 0x7dc1, 0x001a, 0xa861, 0x7c01, 0x2000, 0x2161, 0x2000, 0x8463,
662 0x806d, 0x7dc1, 0x000d, 0x9031, 0x7c10, 0x0018, 0x7dc1, 0x001a,
663 0x9037, 0x61c1, 0x7dc1, 0x001a, 0x0000
667 printf("loading...\n");
668 for (i
= 0; i
< (sizeof(bin
) / sizeof(WORD
)); i
++)
670 printf(" %04x", bin
[i
]);
673 printf("\nloaded 0x%04zx words\n", i
- 1);
677 void dump_cpu_state(struct dcpu16
*d
) {
680 printf("---- cycle:0x%08llx %2s:0x%04x %2s:0x%04x %2s:0x%04x\n",
686 for (i
= 0; i
< 8; i
++)
687 printf(" %c:0x%04x", regnames_
[i
], d
->reg
[i
]);
692 void dump_ram(struct dcpu16
*d
, WORD start
, WORD stop
) {
694 const unsigned int n
= 8; /* words per line */
696 for (i
= start
, j
= 0; i
<= stop
; i
++, j
++) {
698 printf("0x%04x:\t", i
);
699 printf(" %04x%s", d
->ram
[i
], (j
% n
) == (n
- 1) ? "\n" : "");
704 int main(int argc
, char **argv
) {
709 m
= calloc(1, sizeof *m
);
712 fprintf(stderr
, "%s:%s\n", "calloc", strerror(errno
));
716 while ( (c
= getopt(argc
, argv
, "htv")) != EOF
)
725 dump_ram(m
, 0, 0x001f);
727 dump_ram(m
, 0, 0x001f);
743 file_load(m
, argv
[optind
]);
747 dump_instruction(m
, m
->pc
);
748 while (fgets(buf
, sizeof buf
, stdin
)) {
749 dcpu16_execute_next_instruction(m
);
751 dump_instruction(m
, m
->pc
);