13 * emulates the DCPU16 system from http://0x10c.com/doc/dcpu-16.txt
15 * I couldn't remember ever implementing an emulator before, so this
16 * happened. As such, consider this a toy in progress.
17 * There are likely many improvable aspects.
19 * Justin Wind <justin.wind@gmail.com>
20 * 2012 04 05 - implementation started
21 * 2012 04 06 - first functionality achieved
22 * 2012 04 09 - minor cleanups
23 * 2012 04 10 - moved cli to separate module
26 * drop checks for assigning to literals -- it won't affect anything anyhow
27 * debug short literal decoding
30 static const char * const src_id_
= "$Id$";
32 #define WORD DCPU16_WORD
34 static const char regnames_
[] = "ABCXYZIJ";
36 /* some default warning and debug reporting functions, which can be overridden by clients */
37 #define WARN(...) do { if (warn_cb_) warn_cb_(__VA_ARGS__); } while (0)
38 static inline void warn_(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
40 void warn_(char *fmt
, ...) {
43 fprintf(stderr
, "[warning] ");
45 vfprintf(stderr
, fmt
, ap
);
47 fprintf(stderr
, "\n");
50 static void (*warn_cb_
)(char *fmt
, ...) = warn_
;
51 void dcpu16_warn_cb_set(void (*fn
)(char *fmt
, ...)) {
56 #define TRACE(...) do { if (trace_cb_) trace_cb_(__VA_ARGS__); } while (0)
57 static inline void trace_(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
59 void trace_(char *fmt
, ...) {
62 fprintf(stdout
, "[debug] ");
64 vfprintf(stdout
, fmt
, ap
);
66 fprintf(stdout
, "\n");
70 #define TRACE(...) do {} while(0)
72 static void (*trace_cb_
)(char *fmt
, ...) =
79 void dcpu16_trace_cb_set(void (*fn
)(char *fmt
, ...)) {
84 * sets *v to be the destination of the value
85 * workv is buffer to use to accumulate literal value before use
86 * returns true if destination points to literal (id est *v should ignore writes)
88 static unsigned int value_decode(struct dcpu16
*d
, WORD value
, WORD
*work_v
, WORD
**v
) {
90 unsigned int retval
= 0;
92 assert(value
<= 0x3f);
94 /* does this value indicate a literal */
98 /* if we're skipping this instruction, just advance the pc if needed */
100 TRACE(">> SKIP decode");
101 if (value
== 0x1e || value
== 0x1f)
106 if (value
<= 0x07) { /* register */
108 TRACE(">> %c (0x%04x)",
112 } else if (value
<= 0x0f) { /* [register] */
113 *v
= &(d
->ram
[ d
->reg
[(value
& 0x07)] ]);
114 TRACE(">> [%c] [0x%04x] (0x%04x)",
115 regnames_
[value
&0x07],
119 } else if (value
<= 0x17) { /* [next word + register] */
120 nextword
= d
->ram
[ d
->pc
++ ];
122 *v
= &(d
->ram
[ nextword
+ d
->reg
[(value
& 0x07)] ]);
123 TRACE(">> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)",
124 regnames_
[(value
& 0x07)],
126 d
->reg
[(value
& 0x07)],
129 } else switch (value
) {
130 case 0x18: /* POP / [sp++] */
131 *v
= &(d
->ram
[ d
->sp
++ ]);
132 TRACE(">> POP [0x%04x] (0x%04x)",
137 case 0x19: /* PEEK / [sp] */
138 *v
= &(d
->ram
[ d
->sp
]);
139 TRACE(">> PEEK [0x%04x] (0x%04x)",
144 case 0x1a: /* PUSH / [--sp] */
145 *v
= &(d
->ram
[ --d
->sp
]);
146 TRACE(">> PUSH [0x%04x] (0x%04x)",
153 TRACE(">> SP (0x%04x)",
159 TRACE(">> PC (0x%04x)", **v
);
164 TRACE(">> O (0x%04x)", **v
);
167 case 0x1e: /* [next word] / [[pc++]] */
168 nextword
= d
->ram
[ d
->pc
++ ];
170 *v
= &(d
->ram
[ nextword
]);
171 TRACE(">> [nextword] [0x%04x] (0x%04x)",
176 case 0x1f: /* next word (literal) / [pc++] */
177 nextword
= d
->ram
[ d
->pc
++ ];
181 TRACE(">> nextword (0x%04x)", **v
);
184 default: /* 0x20-0x3f: literal values 0x00-0x1f */
185 *work_v
= value
& 0x1f;
187 TRACE(">> literal (0x%04x)", **v
);
193 #define OPCODE_BASIC_BITS (4)
194 #define OPCODE_BASIC_SHIFT (0)
196 #define OPCODE_NBI_BITS (6)
197 #define OPCODE_NBI_SHIFT (4)
199 #define OPCODE_FUTURE_BITS (16)
200 #define OPCODE_FUTURE_SHIFT (10)
202 #define OPCODE_NAME_LEN 16
203 struct opcode_entry
{
204 unsigned short value
;
205 char name
[OPCODE_NAME_LEN
];
206 void (*impl
)(struct dcpu16
*, WORD
, WORD
);
209 /* messy boilerplate for opcode handlers */
211 #define OP_IMPL(x) static void op_##x(struct dcpu16 *d, WORD val_a, WORD val_b)
213 #define OP_NBI_ (void)val_b, (void)b
214 #define OP_BASIC_ (void)value_decode(d, val_b, &d->reg_work_[0], &b)
215 #define OP_TYPE(op_type) WORD *a, *b;\
218 lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
221 TRACE("++ SKIPPED");\
226 #define OP_BASIC(x) OP_TYPE(OP_BASIC_)
227 #define OP_NBI(x) OP_TYPE(OP_NBI_)
230 /* extended opcodes */
233 N.B. this next function currently decodes values -- id est, it is
234 an opcode processing terminus; however, if 'future instruction set
235 expansion' happens, this will probably need to behave more like
236 the OP_IMPL(_nbi_) function which invoked it, if those instructions
237 have zero or differently styled operands.
239 OP_IMPL(nbi__reserved_
) {
240 OP_NBI(nbi__reserved_
);
241 /* reserved for future expansion */
243 WORD future_opcode
= (d
->ram
[d
->pc
] >> OPCODE_FUTURE_SHIFT
);
244 WARN("reserved future opcode 0x%04x invoked", future_opcode
);
249 /* pushes the address of the next instruction to the stack, then sets PC to a */
251 d
->ram
[ --d
->sp
] = d
->pc
;
257 OP_IMPL(nbi__reserved2_
) {
258 OP_NBI(nbi__reserved2_
);
261 WARN("reserved nbi opcode invoked");
264 static const struct opcode_entry opcode_nbi_entries
[] = {
265 {0x0, "(reserved)", op_nbi__reserved_
},
266 {0x1, "JSR", op_nbi_jsr
},
267 {0x2, "(reserved)", op_nbi__reserved2_
},
270 #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1)
276 N.B. the following function does not decode values, as the nbi
277 instructions only have one operand.
280 /* non-basic instruction */
282 /* don't do normal value decoding here */
284 WORD nbi_opcode
= val_a
;
285 const struct opcode_entry
*e
= opcode_nbi_entries
;
287 e
= opcode_nbi_entries
+ ( (nbi_opcode
< OPCODE_NBI_MAX
) ? nbi_opcode
: (OPCODE_NBI_MAX
- 1) );
289 assert(e
->impl
!= NULL
);
291 TRACE(">> %s 0x%04x", e
->name
, val_b
);
292 e
->impl(d
, val_b
, 0);
299 /* only set non-literal target */
309 /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */
310 unsigned int acc
= *a
+ *b
;
322 /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
323 unsigned int acc
= *a
- *b
;
335 /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
336 unsigned int acc
= *a
* *b
;
347 /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
355 unsigned int acc
= *a
/ *b
;
361 acc
= (*a
<< 16) / *b
;
370 /* sets a to a%b. if b==0, sets a to 0 instead. */
387 /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
388 unsigned int acc
= *a
<< *b
;
400 /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
401 unsigned int acc
= *a
>> *b
;
406 d
->o
= (*a
<< 16) >> *b
;
446 /* performs next instruction only if a==b */
460 /* performs next instruction only if a!=b */
474 /* performs next instruction only if a>b */
488 /* performs next instruction only if (a&b)!=0 */
490 if ((*a
& *b
) != 0) {
500 static const struct opcode_entry opcode_basic_entries
[] = {
501 {0x0, "(nbi)", op__nbi_
},
502 {0x1, "SET", op_set
},
503 {0x2, "ADD", op_add
},
504 {0x3, "SUB", op_sub
},
505 {0x4, "MUL", op_mul
},
506 {0x5, "DIV", op_div
},
507 {0x6, "MOD", op_mod
},
508 {0x7, "SHL", op_shl
},
509 {0x8, "SHR", op_shr
},
510 {0x9, "AND", op_and
},
511 {0xa, "BOR", op_bor
},
512 {0xb, "XOR", op_xor
},
513 {0xc, "IFE", op_ife
},
514 {0xd, "IFN", op_ifn
},
515 {0xe, "IFG", op_ifg
},
516 {0xf, "IFB", op_ifb
},
520 void dump_value(WORD value
, WORD nextword
) {
522 printf(" %c", regnames_
[value
]);
523 } else if (value
< 0x0f) {
524 printf(" [%c]", regnames_
[value
& 0x07]);
525 } else if (value
< 0x17) {
526 printf(" [0x%04x + %c]", nextword
, regnames_
[value
& 0x07]);
527 } else switch (value
) {
528 case 0x18: printf(" POP"); break;
529 case 0x19: printf(" PEEK"); break;
530 case 0x1a: printf(" PUSH"); break;
531 case 0x1b: printf(" SP"); break;
532 case 0x1c: printf(" PC"); break;
533 case 0x1d: printf(" O"); break;
534 case 0x1e: printf(" [0x%04x]", nextword
); break;
535 case 0x1f: printf(" 0x%04x", nextword
); break;
536 default: printf(" 0x%02x", value
- 0x20);
540 void dcpu16_disassemble_print(struct dcpu16
*d
, WORD addr
) {
542 unsigned int instr_len
= 1;
543 const struct opcode_entry
*e
;
545 opcode
= (d
->ram
[addr
] >> OPCODE_BASIC_SHIFT
) & ((1 << OPCODE_BASIC_BITS
) - 1);
546 a
= (d
->ram
[addr
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
)) & ((1 << 6) - 1);
547 b
= (d
->ram
[addr
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
+ 6)) & ((1 << 6) - 1);
549 assert(opcode
<= 0x0f);
553 printf("%04x", d
->ram
[addr
]);
557 if (a
== 0x1e || a
== 0x1f) {
558 printf(" %04x", d
->ram
[addr
+ instr_len
]);
562 if (b
== 0x1e || b
== 0x1f) {
563 printf(" %04x", d
->ram
[addr
+ instr_len
]);
568 e
= opcode_basic_entries
+ opcode
;
570 e
= opcode_nbi_entries
+ ( (a
< OPCODE_NBI_MAX
) ? a
: (OPCODE_NBI_MAX
- 1) );
573 instr_len
< 3 ? " " : "",
574 instr_len
< 2 ? " " : "",
577 dump_value(a
, d
->ram
[addr
+ 1]);
578 if (a
== 0x1e || a
== 0x1f)
583 dump_value(b
, d
->ram
[addr
+ 1]);
586 void dcpu16_step(struct dcpu16
*d
) {
589 const struct opcode_entry
*e
;
591 /* decode opcode and invoke */
593 opcode
= (d
->ram
[ d
->pc
] >> OPCODE_BASIC_SHIFT
) & ((1 << OPCODE_BASIC_BITS
) - 1);
594 val_a
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
)) & ((1 << 6) - 1);
595 val_b
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
+ 6)) & ((1 << 6) - 1);
599 for (e
= opcode_basic_entries
; e
->impl
; e
++) {
600 if (e
->value
== opcode
) {
601 TRACE(">> %s 0x%04x, 0x%04x", e
->name
, val_a
, val_b
);
602 e
->impl(d
, val_a
, val_b
);
608 void dcpu16_state_print(struct dcpu16
*d
) {
611 printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
618 dcpu16_disassemble_print(d
, d
->pc
);
621 for (i
= 0; i
< 8; i
++)
622 printf(" %c:0x%04x", regnames_
[i
], d
->reg
[i
]);
627 * print raw ram contents from start to stop
629 void dcpu16_dump_ram(struct dcpu16
*d
, WORD start
, WORD end
) {
631 const unsigned int n
= 8; /* words per line */
633 for (i
= start
, j
= 0; i
<= end
; i
++, j
++) {
635 printf("0x%04x:\t", i
);
636 printf(" %04x%s", d
->ram
[i
], (j
% n
) == (n
- 1) ? "\n" : "");
638 if ((j
% n
) != (n
- 1))
643 * resets a dcpu16 instance to initial state
645 void dcpu16_reset(struct dcpu16
*d
) {
646 memset(d
, 0, sizeof *d
);
650 * allocate a new dcpu16 instance
652 struct dcpu16
*dcpu16_new(void) {
655 vm
= calloc(1, sizeof *vm
);
657 WARN("%s: %s(%zu): %s", __func__
, "calloc", strerror(errno
));
663 * release a dcpu16 instance
665 void dcpu16_delete(struct dcpu16
**vm
) {