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";
37 static unsigned int trace_mode_
= 0; /* spew overly verbose internals */
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
, ...)) {
57 #define TRACE(...) do { if (trace_cb_ && trace_mode_) trace_cb_(__VA_ARGS__); } while (0)
58 static inline void trace_(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
60 void trace_(char *fmt
, ...) {
63 fprintf(stdout
, "[debug] ");
65 vfprintf(stdout
, fmt
, ap
);
67 fprintf(stdout
, "\n");
70 static void (*trace_cb_
)(char *fmt
, ...) = trace_
;
71 void dcpu16_trace_cb_set(void (*fn
)(char *fmt
, ...)) {
77 * sets *v to be the destination of the value
78 * workv is buffer to use to accumulate literal value before use
79 * returns true if destination points to literal (id est *v should ignore writes)
81 static unsigned int value_decode(struct dcpu16
*d
, WORD value
, WORD
*work_v
, WORD
**v
) {
83 unsigned int retval
= 0;
85 assert(value
<= 0x3f);
87 /* does this value indicate a literal */
91 /* if we're skipping this instruction, just advance the pc if needed */
93 TRACE(">> SKIP decode");
94 if (value
== 0x1e || value
== 0x1f)
99 if (value
<= 0x07) { /* register */
101 TRACE(">> %c (0x%04x)",
105 } else if (value
<= 0x0f) { /* [register] */
106 *v
= &(d
->ram
[ d
->reg
[(value
& 0x07)] ]);
107 TRACE(">> [%c] [0x%04x] (0x%04x)",
108 regnames_
[value
&0x07],
112 } else if (value
<= 0x17) { /* [next word + register] */
113 nextword
= d
->ram
[ d
->pc
++ ];
115 *v
= &(d
->ram
[ nextword
+ d
->reg
[(value
& 0x07)] ]);
116 TRACE(">> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)",
117 regnames_
[(value
& 0x07)],
119 d
->reg
[(value
& 0x07)],
122 } else switch (value
) {
123 case 0x18: /* POP / [sp++] */
124 *v
= &(d
->ram
[ d
->sp
++ ]);
125 TRACE(">> POP [0x%04x] (0x%04x)",
130 case 0x19: /* PEEK / [sp] */
131 *v
= &(d
->ram
[ d
->sp
]);
132 TRACE(">> PEEK [0x%04x] (0x%04x)",
137 case 0x1a: /* PUSH / [--sp] */
138 *v
= &(d
->ram
[ --d
->sp
]);
139 TRACE(">> PUSH [0x%04x] (0x%04x)",
146 TRACE(">> SP (0x%04x)",
152 TRACE(">> PC (0x%04x)", **v
);
157 TRACE(">> O (0x%04x)", **v
);
160 case 0x1e: /* [next word] / [[pc++]] */
161 nextword
= d
->ram
[ d
->pc
++ ];
163 *v
= &(d
->ram
[ nextword
]);
164 TRACE(">> [nextword] [0x%04x] (0x%04x)",
169 case 0x1f: /* next word (literal) / [pc++] */
170 nextword
= d
->ram
[ d
->pc
++ ];
174 TRACE(">> nextword (0x%04x)", **v
);
177 default: /* 0x20-0x3f: literal values 0x00-0x1f */
178 *work_v
= value
& 0x1f;
180 TRACE(">> literal (0x%04x)", **v
);
186 #define OPCODE_BASIC_BITS (4)
187 #define OPCODE_BASIC_SHIFT (0)
189 #define OPCODE_NBI_BITS (6)
190 #define OPCODE_NBI_SHIFT (4)
192 #define OPCODE_FUTURE_BITS (16)
193 #define OPCODE_FUTURE_SHIFT (10)
195 #define OPCODE_NAME_LEN 16
196 struct opcode_entry
{
197 unsigned short value
;
198 char name
[OPCODE_NAME_LEN
];
199 void (*impl
)(struct dcpu16
*, WORD
, WORD
);
202 /* messy boilerplate for opcode handlers */
204 #define OP_IMPL(x) static void op_##x(struct dcpu16 *d, WORD val_a, WORD val_b)
206 #define OP_NBI_ (void)val_b, (void)b
207 #define OP_BASIC_ (void)value_decode(d, val_b, &d->reg_work_[0], &b)
208 #define OP_TYPE(op_type) WORD *a, *b;\
211 lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
214 TRACE("++ SKIPPED");\
219 #define OP_BASIC(x) OP_TYPE(OP_BASIC_)
220 #define OP_NBI(x) OP_TYPE(OP_NBI_)
223 /* extended opcodes */
226 N.B. this next function currently decodes values -- id est, it is
227 an opcode processing terminus; however, if 'future instruction set
228 expansion' happens, this will probably need to behave more like
229 the OP_IMPL(_nbi_) function which invoked it, if those instructions
230 have zero or differently styled operands.
232 OP_IMPL(nbi__reserved_
) {
233 OP_NBI(nbi__reserved_
);
234 /* reserved for future expansion */
236 WORD future_opcode
= (d
->ram
[d
->pc
] >> OPCODE_FUTURE_SHIFT
);
237 WARN("reserved future opcode 0x%04x invoked", future_opcode
);
242 /* pushes the address of the next instruction to the stack, then sets PC to a */
244 d
->ram
[ --d
->sp
] = d
->pc
;
250 OP_IMPL(nbi__reserved2_
) {
251 OP_NBI(nbi__reserved2_
);
254 WARN("reserved nbi opcode invoked");
257 static const struct opcode_entry opcode_nbi_entries
[] = {
258 {0x0, "(reserved)", op_nbi__reserved_
},
259 {0x1, "JSR", op_nbi_jsr
},
260 {0x2, "(reserved)", op_nbi__reserved2_
},
263 #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1)
269 N.B. the following function does not decode values, as the nbi
270 instructions only have one operand.
273 /* non-basic instruction */
275 /* don't do normal value decoding here */
277 WORD nbi_opcode
= val_a
;
278 const struct opcode_entry
*e
= opcode_nbi_entries
;
280 e
= opcode_nbi_entries
+ ( (nbi_opcode
< OPCODE_NBI_MAX
) ? nbi_opcode
: (OPCODE_NBI_MAX
- 1) );
282 assert(e
->impl
!= NULL
);
284 TRACE(">> %s 0x%04x", e
->name
, val_b
);
285 e
->impl(d
, val_b
, 0);
292 /* only set non-literal target */
302 /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */
303 unsigned int acc
= *a
+ *b
;
315 /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
316 unsigned int acc
= *a
- *b
;
328 /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
329 unsigned int acc
= *a
* *b
;
340 /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
348 unsigned int acc
= *a
/ *b
;
354 acc
= (*a
<< 16) / *b
;
363 /* sets a to a%b. if b==0, sets a to 0 instead. */
380 /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
381 unsigned int acc
= *a
<< *b
;
393 /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
394 unsigned int acc
= *a
>> *b
;
399 d
->o
= (*a
<< 16) >> *b
;
439 /* performs next instruction only if a==b */
453 /* performs next instruction only if a!=b */
467 /* performs next instruction only if a>b */
481 /* performs next instruction only if (a&b)!=0 */
483 if ((*a
& *b
) != 0) {
493 static const struct opcode_entry opcode_basic_entries
[] = {
494 {0x0, "(nbi)", op__nbi_
},
495 {0x1, "SET", op_set
},
496 {0x2, "ADD", op_add
},
497 {0x3, "SUB", op_sub
},
498 {0x4, "MUL", op_mul
},
499 {0x5, "DIV", op_div
},
500 {0x6, "MOD", op_mod
},
501 {0x7, "SHL", op_shl
},
502 {0x8, "SHR", op_shr
},
503 {0x9, "AND", op_and
},
504 {0xa, "BOR", op_bor
},
505 {0xb, "XOR", op_xor
},
506 {0xc, "IFE", op_ife
},
507 {0xd, "IFN", op_ifn
},
508 {0xe, "IFG", op_ifg
},
509 {0xf, "IFB", op_ifb
},
513 void dump_value(WORD value
, WORD nextword
) {
515 printf(" %c", regnames_
[value
]);
516 } else if (value
< 0x0f) {
517 printf(" [%c]", regnames_
[value
& 0x07]);
518 } else if (value
< 0x17) {
519 printf(" [0x%04x + %c]", nextword
, regnames_
[value
& 0x07]);
520 } else switch (value
) {
521 case 0x18: printf(" POP"); break;
522 case 0x19: printf(" PEEK"); break;
523 case 0x1a: printf(" PUSH"); break;
524 case 0x1b: printf(" SP"); break;
525 case 0x1c: printf(" PC"); break;
526 case 0x1d: printf(" O"); break;
527 case 0x1e: printf(" [0x%04x]", nextword
); break;
528 case 0x1f: printf(" 0x%04x", nextword
); break;
529 default: printf(" 0x%02x", value
- 0x20);
533 void dcpu16_disassemble_print(struct dcpu16
*d
, WORD addr
) {
535 unsigned int instr_len
= 1;
536 const struct opcode_entry
*e
;
538 opcode
= (d
->ram
[addr
] >> OPCODE_BASIC_SHIFT
) & ((1 << OPCODE_BASIC_BITS
) - 1);
539 a
= (d
->ram
[addr
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
)) & ((1 << 6) - 1);
540 b
= (d
->ram
[addr
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
+ 6)) & ((1 << 6) - 1);
542 assert(opcode
<= 0x0f);
546 printf(" next instr 0x%04x: %04x", addr
, d
->ram
[addr
]);
550 if (a
== 0x1e || a
== 0x1f) {
551 printf(" %04x", d
->ram
[addr
+ instr_len
]);
555 if (b
== 0x1e || b
== 0x1f) {
556 printf(" %04x", d
->ram
[addr
+ instr_len
]);
561 e
= opcode_basic_entries
+ opcode
;
563 e
= opcode_nbi_entries
+ ( (a
< OPCODE_NBI_MAX
) ? a
: (OPCODE_NBI_MAX
- 1) );
566 instr_len
< 3 ? " " : "",
567 instr_len
< 2 ? " " : "",
570 dump_value(a
, d
->ram
[addr
+ 1]);
571 if (a
== 0x1e || a
== 0x1f)
576 dump_value(b
, d
->ram
[addr
+ 1]);
581 void dcpu16_step(struct dcpu16
*d
) {
584 const struct opcode_entry
*e
;
586 /* decode opcode and invoke */
588 opcode
= (d
->ram
[ d
->pc
] >> OPCODE_BASIC_SHIFT
) & ((1 << OPCODE_BASIC_BITS
) - 1);
589 val_a
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
)) & ((1 << 6) - 1);
590 val_b
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
+ 6)) & ((1 << 6) - 1);
594 for (e
= opcode_basic_entries
; e
->impl
; e
++) {
595 if (e
->value
== opcode
) {
596 TRACE(">> %s 0x%04x, 0x%04x", e
->name
, val_a
, val_b
);
597 e
->impl(d
, val_a
, val_b
);
603 void dcpu16_state_print(struct dcpu16
*d
) {
606 printf("---- cycle:0x%08llx %2s:0x%04x %2s:0x%04x %2s:0x%04x\n",
612 for (i
= 0; i
< 8; i
++)
613 printf(" %c:0x%04x", regnames_
[i
], d
->reg
[i
]);
617 void dcpu16_dump_ram(struct dcpu16
*d
, WORD start
, WORD stop
) {
619 const unsigned int n
= 8; /* words per line */
621 for (i
= start
, j
= 0; i
<= stop
; i
++, j
++) {
623 printf("0x%04x:\t", i
);
624 printf(" %04x%s", d
->ram
[i
], (j
% n
) == (n
- 1) ? "\n" : "");
629 * allocate a new dcpu16 instance
631 struct dcpu16
*dcpu16_new(void) {
634 vm
= calloc(1, sizeof *vm
);
636 WARN("%s: %s(%zu): %s", __func__
, "calloc", strerror(errno
));
642 * release a dcpu16 instance
644 void dcpu16_delete(struct dcpu16
**vm
) {