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
22 * move cli driver to separate module
25 static const char * const src_id_
= "$Id$";
27 /* the target system's concept of a word */
28 #define WORD DCPU16_WORD
29 typedef unsigned short WORD
;
31 #define RAM_SIZE 0x10000
32 static const char regnames_
[] = "ABCXYZIJ";
34 unsigned long long cycle
; /* number of cycles it took to get to current state */
35 WORD reg_work_
[2]; /* holding bins for literal values when decoding instructions */
36 WORD reg
[8]; /* system registers, a b c x y z i j */
37 WORD pc
; /* program counter */
38 WORD sp
; /* stack pointer */
39 WORD o
; /* overflow */
40 unsigned int skip_
: 1; /* */
41 WORD ram
[RAM_SIZE
]; /* memory */
45 static unsigned int trace_mode_
= 0; /* turn on for overly verbose internals */
47 #define WARN(...) warn(__VA_ARGS__)
48 static inline void warn(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
49 static inline void warn(char *fmt
, ...) {
52 fprintf(stderr
, "!!! ");
54 vfprintf(stderr
, fmt
, ap
);
56 fprintf(stderr
, "\n");
61 #define TRACE(...) do { if (trace_mode_) trace(__VA_ARGS__); } while (0)
62 static inline void trace(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
64 void trace(char *fmt
, ...) {
68 vfprintf(stdout
, fmt
, ap
);
70 fprintf(stdout
, "\n");
75 /* sets *v to be the destination of the value */
76 /* workv is buffer to use to accumulate literal value before use */
77 /* returns true if destination points to literal (id est *v should ignore writes) */
78 static unsigned int value_decode(struct dcpu16
*d
, WORD value
, WORD
*work_v
, WORD
**v
) {
80 unsigned int retval
= 0;
82 assert(value
>= 0x00 && value
<= 0x3f);
84 /* does this value indicate a literal */
88 /* if we're skipping this instruction, just advance the pc if needed */
90 TRACE(">> SKIP decode");
91 if (value
== 0x1e || value
== 0x1f)
96 if (value
<= 0x07) { /* register */
98 TRACE(">> %c (0x%04x)",
102 } else if (value
<= 0x0f) { /* [register] */
103 *v
= &(d
->ram
[ d
->reg
[(value
& 0x07)] ]);
104 TRACE(">> [%c] [0x%04x] (0x%04x)",
105 regnames_
[value
&0x07],
109 } else if (value
<= 0x17) { /* [next word + register] */
110 nextword
= d
->ram
[ d
->pc
++ ];
112 *v
= &(d
->ram
[ nextword
+ d
->reg
[(value
& 0x07)] ]);
113 TRACE(">> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)",
114 regnames_
[(value
& 0x07)],
116 d
->reg
[(value
& 0x07)],
119 } else switch (value
) {
120 case 0x18: /* POP / [sp++] */
121 *v
= &(d
->ram
[ d
->sp
++ ]);
122 TRACE(">> POP [0x%04x] (0x%04x)",
127 case 0x19: /* PEEK / [sp] */
128 *v
= &(d
->ram
[ d
->sp
]);
129 TRACE(">> PEEK [0x%04x] (0x%04x)",
134 case 0x1a: /* PUSH / [--sp] */
135 *v
= &(d
->ram
[ --d
->sp
]);
136 TRACE(">> PUSH [0x%04x] (0x%04x)",
143 TRACE(">> SP (0x%04x)",
149 TRACE(">> PC (0x%04x)", **v
);
154 TRACE(">> O (0x%04x)", **v
);
157 case 0x1e: /* [next word] / [[pc++]] */
158 nextword
= d
->ram
[ d
->pc
++ ];
160 *v
= &(d
->ram
[ nextword
]);
161 TRACE(">> [nextword] [0x%04x] (0x%04x)",
166 case 0x1f: /* next word (literal) / [pc++] */
167 nextword
= d
->ram
[ d
->pc
++ ];
171 TRACE(">> nextword (0x%04x)", **v
);
174 default: /* 0x20-0x3f: literal values 0x00-0x1f */
175 *work_v
= value
& 0x1f;
177 TRACE(">> literal (0x%04x)", **v
);
183 #define OPCODE_BASIC_BITS (4)
184 #define OPCODE_BASIC_SHIFT (0)
186 #define OPCODE_NBI_BITS (6)
187 #define OPCODE_NBI_SHIFT (4)
189 #define OPCODE_FUTURE_BITS (16)
190 #define OPCODE_FUTURE_SHIFT (10)
192 #define OPCODE_NAME_LEN 16
193 struct opcode_entry
{
194 unsigned short value
;
195 char name
[OPCODE_NAME_LEN
];
196 void (*impl
)(struct dcpu16
*, WORD
, WORD
);
199 /* messy boilerplate for opcode handlers */
201 #define OP_IMPL(x) static void op_##x(struct dcpu16 *d, WORD val_a, WORD val_b)
203 #define OP_NBI_ (void)val_b
204 #define OP_BASIC_ (void)value_decode(d, val_b, &d->reg_work_[0], &b)
205 #define OP_TYPE(op_type) WORD *a, *b;\
209 lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
212 TRACE("++ SKIPPED");\
219 #define OP_BASIC(x) WORD *a, *b;\
223 lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
224 value_decode(d, val_b, &d->reg_work_[1], &b);\
226 TRACE("++ SKIPPED");\
232 #define OP_NBI(x) WORD *a;\
236 lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
239 TRACE("++ SKIPPED");\
246 /* extended opcodes */
249 N.B. this next function currently decodes values -- id est, it is
250 an opcode processing terminus; however, if 'future instruction set
251 expansion' happens, this will probably need to behave more like
252 the OP_IMPL(_nbi_) function which invoked it, if those instructions
253 have zero or differently styled operands.
255 OP_IMPL(nbi__reserved_
) {
256 OP_NBI(nbi__reserved_
);
257 /* reserved for future expansion */
259 WORD future_opcode
= (d
->ram
[d
->pc
] >> OPCODE_FUTURE_SHIFT
);
260 WARN("reserved future opcode 0x%04x invoked", future_opcode
);
265 /* pushes the address of the next instruction to the stack, then sets PC to a */
267 d
->ram
[ --d
->sp
] = d
->pc
;
273 OP_IMPL(nbi__reserved2_
) {
274 OP_NBI(nbi__reserved2_
);
277 WARN("reserved nbi opcode invoked");
280 static const struct opcode_entry opcode_nbi_entries
[] = {
281 {0x0, "(reserved)", op_nbi__reserved_
},
282 {0x1, "JSR", op_nbi_jsr
},
283 {0x2, "(reserved)", op_nbi__reserved2_
},
286 #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1)
292 N.B. the following function does not decode values, as the nbi
293 instructions only have one operand.
297 /* non-basic instruction */
299 /* don't do normal value decoding here */
301 WORD nbi_opcode
= val_a
;
302 const struct opcode_entry
*e
= opcode_nbi_entries
;
304 e
= opcode_nbi_entries
+ ( (nbi_opcode
< OPCODE_NBI_MAX
) ? nbi_opcode
: (OPCODE_NBI_MAX
- 1) );
306 assert(e
->impl
!= NULL
);
308 TRACE(">> %s 0x%04x", e
->name
, val_b
);
309 e
->impl(d
, val_b
, 0);
316 /* only set non-literal target */
326 /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */
327 unsigned int acc
= *a
+ *b
;
339 /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
340 unsigned int acc
= *a
- *b
;
352 /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
353 unsigned int acc
= *a
* *b
;
364 /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
372 unsigned int acc
= *a
/ *b
;
378 acc
= (*a
<< 16) / *b
;
387 /* sets a to a%b. if b==0, sets a to 0 instead. */
404 /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
405 unsigned int acc
= *a
<< *b
;
417 /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
418 unsigned int acc
= *a
>> *b
;
423 d
->o
= (*a
<< 16) >> *b
;
463 /* performs next instruction only if a==b */
477 /* performs next instruction only if a!=b */
491 /* performs next instruction only if a>b */
505 /* performs next instruction only if (a&b)!=0 */
507 if ((*a
& *b
) != 0) {
517 static const struct opcode_entry opcode_basic_entries
[] = {
518 {0x0, "(nbi)", op__nbi_
},
519 {0x1, "SET", op_set
},
520 {0x2, "ADD", op_add
},
521 {0x3, "SUB", op_sub
},
522 {0x4, "MUL", op_mul
},
523 {0x5, "DIV", op_div
},
524 {0x6, "MOD", op_mod
},
525 {0x7, "SHL", op_shl
},
526 {0x8, "SHR", op_shr
},
527 {0x9, "AND", op_and
},
528 {0xa, "BOR", op_bor
},
529 {0xb, "XOR", op_xor
},
530 {0xc, "IFE", op_ife
},
531 {0xd, "IFN", op_ifn
},
532 {0xe, "IFG", op_ifg
},
533 {0xf, "IFB", op_ifb
},
537 void dump_value(WORD value
, WORD nextword
) {
539 printf(" %c", regnames_
[value
]);
540 } else if (value
< 0x0f) {
541 printf(" [%c]", regnames_
[value
& 0x07]);
542 } else if (value
< 0x17) {
543 printf(" [0x%04x + %c]", nextword
, regnames_
[value
& 0x07]);
544 } else switch (value
) {
545 case 0x18: printf(" POP"); break;
546 case 0x19: printf(" PEEK"); break;
547 case 0x1a: printf(" PUSH"); break;
548 case 0x1b: printf(" SP"); break;
549 case 0x1c: printf(" PC"); break;
550 case 0x1d: printf(" O"); break;
551 case 0x1e: printf(" [0x%04x]", nextword
); break;
552 case 0x1f: printf(" 0x%04x", nextword
); break;
553 default: printf(" 0x%02x", value
- 0x20);
557 void dump_instruction(struct dcpu16
*d
, WORD addr
) {
559 unsigned int instr_len
= 1;
560 const struct opcode_entry
*e
;
562 opcode
= (d
->ram
[addr
] >> OPCODE_BASIC_SHIFT
) & ((1 << OPCODE_BASIC_BITS
) - 1);
563 a
= (d
->ram
[addr
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
)) & ((1 << 6) - 1);
564 b
= (d
->ram
[addr
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
+ 6)) & ((1 << 6) - 1);
566 assert(opcode
< 0x0f);
570 printf("next instr 0x%04x: %04x", addr
, d
->ram
[addr
]);
574 if (a
== 0x1e || a
== 0x1f) {
575 printf(" %04x", d
->ram
[addr
+ instr_len
]);
579 if (b
== 0x1e || b
== 0x1f) {
580 printf(" %04x", d
->ram
[addr
+ instr_len
]);
585 e
= opcode_basic_entries
+ opcode
;
587 e
= opcode_nbi_entries
+ ( (a
< OPCODE_NBI_MAX
) ? a
: (OPCODE_NBI_MAX
- 1) );
588 printf("\n\t%s", e
->name
);
590 dump_value(a
, d
->ram
[addr
+ 1]);
591 if (a
== 0x1e || a
== 0x1f)
596 dump_value(b
, d
->ram
[addr
+ 1]);
601 void dcpu16_execute_next_instruction(struct dcpu16
*d
) {
604 const struct opcode_entry
*e
;
606 /* fetch next instruction */
607 if (d
->pc
> RAM_SIZE
) { /* currently impossible */
608 WARN("%s beyond %u", "PC", RAM_SIZE
);
609 /* d->pc %= RAM_SIZE; */
612 opcode
= (d
->ram
[ d
->pc
] >> OPCODE_BASIC_SHIFT
) & ((1 << OPCODE_BASIC_BITS
) - 1);
613 val_a
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
)) & ((1 << 6) - 1);
614 val_b
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
+ 6)) & ((1 << 6) - 1);
618 for (e
= opcode_basic_entries
; e
->impl
; e
++) {
619 if (e
->value
== opcode
) {
620 TRACE(">> %s 0x%04x, 0x%04x", e
->name
, val_a
, val_b
);
621 e
->impl(d
, val_a
, val_b
);
627 static void usage(char *prog
, unsigned int full
) {
628 FILE *f
= full
? stdout
: stderr
;
629 char *x
= strrchr(prog
, '/');
635 fprintf(f
, "%s -- \n\n",
638 fprintf(f
, "Usage: %s\n",
642 fprintf(f
, "\nOptions:\n"
643 "\t-h -- this screen\n");
645 fprintf(f
, "\n%78s\n", src_id_
);
649 static int file_load(struct dcpu16
*d
, char *filename
) {
653 f
= fopen(filename
, "rb");
656 fprintf(stderr
, "%s(%s):%s\n", "fopen", filename
, strerror(errno
));
660 r
= fread(d
->ram
, sizeof(WORD
), RAM_SIZE
, f
);
661 TRACE("read %zu words", r
);
664 fprintf(stderr
, "%s():%s\n", "fread", strerror(errno
));
671 static void testprog_load(struct dcpu16
*d
) {
672 static WORD bin
[] = {
673 0x7c01, 0x0030, 0x7de1, 0x1000, 0x0020, 0x7803, 0x1000, 0xc00d,
674 0x7dc1, 0x001a, 0xa861, 0x7c01, 0x2000, 0x2161, 0x2000, 0x8463,
675 0x806d, 0x7dc1, 0x000d, 0x9031, 0x7c10, 0x0018, 0x7dc1, 0x001a,
676 0x9037, 0x61c1, 0x7dc1, 0x001a, 0x0000
680 printf("loading...\n");
681 for (i
= 0; i
< (sizeof(bin
) / sizeof(WORD
)); i
++)
683 printf(" %04x", bin
[i
]);
686 printf("\nloaded 0x%04zx words\n", i
- 1);
690 void dump_cpu_state(struct dcpu16
*d
) {
693 printf("[--- cycle:0x%08llx %2s:0x%04x %2s:0x%04x %2s:0x%04x\n",
699 for (i
= 0; i
< 8; i
++)
700 printf(" %c:0x%04x", regnames_
[i
], d
->reg
[i
]);
705 void dump_ram(struct dcpu16
*d
, WORD start
, WORD stop
) {
707 const unsigned int n
= 8; /* words per line */
709 for (i
= start
, j
= 0; i
<= stop
; i
++, j
++) {
711 printf("0x%04x:\t", i
);
712 printf(" %04x%s", d
->ram
[i
], (j
% n
) == (n
- 1) ? "\n" : "");
717 int main(int argc
, char **argv
) {
722 m
= calloc(1, sizeof *m
);
725 fprintf(stderr
, "%s:%s\n", "calloc", strerror(errno
));
729 while ( (c
= getopt(argc
, argv
, "ht")) != EOF
)
735 dump_ram(m
, 0, 0x001f);
737 dump_ram(m
, 0, 0x001f);
753 file_load(m
, argv
[optind
]);
757 while (fgets(buf
, sizeof buf
, stdin
)) {
758 dcpu16_execute_next_instruction(m
);
761 dump_instruction(m
, m
->pc
);