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
28 * add callbacks queues for set/get, attach your own filters
32 static const char * const src_id_
= "$Id$";
34 #define WORD DCPU16_WORD
36 static const char regnames_
[] = "ABCXYZIJ";
38 /* some default warning and debug reporting functions, which can be overridden by clients */
39 #define WARN(...) do { if (warn_cb_) warn_cb_(__VA_ARGS__); } while (0)
40 static inline void warn_(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
42 void warn_(char *fmt
, ...) {
45 fprintf(stderr
, "[warning] ");
47 vfprintf(stderr
, fmt
, ap
);
49 fprintf(stderr
, "\n");
52 static void (*warn_cb_
)(char *fmt
, ...) = warn_
;
53 void dcpu16_warn_cb_set(void (*fn
)(char *fmt
, ...)) {
58 #define TRACE(...) do { if (trace_cb_) trace_cb_(__VA_ARGS__); } while (0)
59 static inline void trace_(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
61 void trace_(char *fmt
, ...) {
64 fprintf(stdout
, "[debug] ");
66 vfprintf(stdout
, fmt
, ap
);
68 fprintf(stdout
, "\n");
72 #define TRACE(...) do {} while(0)
74 static void (*trace_cb_
)(char *fmt
, ...) =
81 void dcpu16_trace_cb_set(void (*fn
)(char *fmt
, ...)) {
86 * sets *v to be the destination of the value
87 * workv is buffer to use to accumulate literal value before use
88 * 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
;
317 d
->o
= (acc
> 0xffff);
324 /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
325 unsigned int acc
= *a
- *b
;
329 d
->o
= (acc
> 0xffff);
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 dcpu16_disassemble_print(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("%04x", 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]);
588 void dcpu16_step(struct dcpu16
*d
) {
591 const struct opcode_entry
*e
;
593 /* decode opcode and invoke */
595 opcode
= (d
->ram
[ d
->pc
] >> OPCODE_BASIC_SHIFT
) & ((1 << OPCODE_BASIC_BITS
) - 1);
596 val_a
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
)) & ((1 << 6) - 1);
597 val_b
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
+ 6)) & ((1 << 6) - 1);
601 for (e
= opcode_basic_entries
; e
->impl
; e
++) {
602 if (e
->value
== opcode
) {
603 TRACE(">> %s 0x%04x, 0x%04x", e
->name
, val_a
, val_b
);
604 e
->impl(d
, val_a
, val_b
);
610 void dcpu16_state_print(struct dcpu16
*d
) {
613 printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
620 dcpu16_disassemble_print(d
, d
->pc
);
623 for (i
= 0; i
< 8; i
++)
624 printf(" %c:0x%04x", regnames_
[i
], d
->reg
[i
]);
629 * print raw ram contents from start to stop
631 void dcpu16_dump_ram(struct dcpu16
*d
, WORD start
, WORD end
) {
633 const unsigned int n
= 8; /* words per line */
635 for (i
= start
, j
= 0; i
<= end
; i
++, j
++) {
637 printf("0x%04x:\t", i
);
638 printf(" %04x%s", d
->ram
[i
], (j
% n
) == (n
- 1) ? "\n" : "");
640 if ((j
% n
) != (n
- 1))
645 * resets a dcpu16 instance to initial state
647 void dcpu16_reset(struct dcpu16
*d
) {
648 memset(d
, 0, sizeof *d
);
652 * allocate a new dcpu16 instance
654 struct dcpu16
*dcpu16_new(void) {
657 vm
= calloc(1, sizeof *vm
);
659 WARN("%s: %s(%zu): %s", __func__
, "calloc", strerror(errno
));
665 * release a dcpu16 instance
667 void dcpu16_delete(struct dcpu16
**vm
) {