2a59d24e1cca31f5e161163fc8578d14fdcdc968
[dcpu16] / dcpu16.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <stdarg.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <errno.h>
7 #include <assert.h>
8 #include <sysexits.h>
9
10 #include "dcpu16.h"
11
12 /*
13 * emulates the DCPU16 system from http://0x10c.com/doc/dcpu-16.txt
14 *
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.
18 *
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
24 *
25 * TODO
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
29 * such as a display
30 */
31
32 static const char * const src_id_ = "$Id$";
33
34 #define WORD DCPU16_WORD
35
36 static const char regnames_[] = "ABCXYZIJ";
37
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)));
41 static inline
42 void warn_(char *fmt, ...) {
43 va_list ap;
44
45 fprintf(stderr, "[warning] ");
46 va_start(ap, fmt);
47 vfprintf(stderr, fmt, ap);
48 va_end(ap);
49 fprintf(stderr, "\n");
50 fflush(stderr);
51 }
52 static void (*warn_cb_)(char *fmt, ...) = warn_;
53 void dcpu16_warn_cb_set(void (*fn)(char *fmt, ...)) {
54 warn_cb_ = fn;
55 }
56
57 #ifdef DEBUG
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)));
60 static inline
61 void trace_(char *fmt, ...) {
62 va_list ap;
63
64 fprintf(stdout, "[debug] ");
65 va_start(ap, fmt);
66 vfprintf(stdout, fmt, ap);
67 va_end(ap);
68 fprintf(stdout, "\n");
69 fflush(stdout);
70 }
71 #else /* DEBUG */
72 #define TRACE(...) do {} while(0)
73 #endif /* DEBUG */
74 static void (*trace_cb_)(char *fmt, ...) =
75 #ifdef DEBUG
76 trace_
77 #else /* DEBUG */
78 NULL
79 #endif
80 ;
81 void dcpu16_trace_cb_set(void (*fn)(char *fmt, ...)) {
82 trace_cb_ = fn;
83 }
84
85 /* value_decode_
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)
89 */
90 static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WORD **v) {
91 WORD nextword;
92 unsigned int retval = 0;
93
94 assert(value <= 0x3f);
95
96 /* does this value indicate a literal */
97 if (value >= 0x1f)
98 retval = 1;
99
100 /* if we're skipping this instruction, just advance the pc if needed */
101 if (d->skip_) {
102 TRACE(">> SKIP decode");
103 if (value == 0x1e || value == 0x1f)
104 d->pc++;
105 return retval;
106 }
107
108 if (value <= 0x07) { /* register */
109 *v = d->reg + value;
110 TRACE(">> %c (0x%04x)",
111 regnames_[value],
112 **v);
113
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],
118 d->reg[value&0x07],
119 **v);
120
121 } else if (value <= 0x17) { /* [next word + register] */
122 nextword = d->ram[ d->pc++ ];
123 d->cycle++;
124 *v = &(d->ram[ nextword + d->reg[(value & 0x07)] ]);
125 TRACE(">> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)",
126 regnames_[(value & 0x07)],
127 nextword,
128 d->reg[(value & 0x07)],
129 **v);
130
131 } else switch (value) {
132 case 0x18: /* POP / [sp++] */
133 *v = &(d->ram[ d->sp++ ]);
134 TRACE(">> POP [0x%04x] (0x%04x)",
135 d->sp - 1,
136 **v);
137 break;
138
139 case 0x19: /* PEEK / [sp] */
140 *v = &(d->ram[ d->sp ]);
141 TRACE(">> PEEK [0x%04x] (0x%04x)",
142 d->sp,
143 **v);
144 break;
145
146 case 0x1a: /* PUSH / [--sp] */
147 *v = &(d->ram[ --d->sp ]);
148 TRACE(">> PUSH [0x%04x] (0x%04x)",
149 d->sp + 1,
150 **v);
151 break;
152
153 case 0x1b: /* SP */
154 *v = &(d->sp);
155 TRACE(">> SP (0x%04x)",
156 **v);
157 break;
158
159 case 0x1c: /* PC */
160 *v = &(d->pc);
161 TRACE(">> PC (0x%04x)", **v);
162 break;
163
164 case 0x1d: /* O */
165 *v = &(d->o);
166 TRACE(">> O (0x%04x)", **v);
167 break;
168
169 case 0x1e: /* [next word] / [[pc++]] */
170 nextword = d->ram[ d->pc++ ];
171 d->cycle++;
172 *v = &(d->ram[ nextword ]);
173 TRACE(">> [nextword] [0x%04x] (0x%04x)",
174 nextword,
175 **v);
176 break;
177
178 case 0x1f: /* next word (literal) / [pc++] */
179 nextword = d->ram[ d->pc++ ];
180 d->cycle++;
181 *work_v = nextword;
182 *v = work_v;
183 TRACE(">> nextword (0x%04x)", **v);
184 break;
185
186 default: /* 0x20-0x3f: literal values 0x00-0x1f */
187 *work_v = value & 0x1f;
188 *v = work_v;
189 TRACE(">> literal (0x%04x)", **v);
190 }
191
192 return retval;
193 }
194
195 #define OPCODE_BASIC_BITS (4)
196 #define OPCODE_BASIC_SHIFT (0)
197
198 #define OPCODE_NBI_BITS (6)
199 #define OPCODE_NBI_SHIFT (4)
200
201 #define OPCODE_FUTURE_BITS (16)
202 #define OPCODE_FUTURE_SHIFT (10)
203
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);
209 };
210
211 /* messy boilerplate for opcode handlers */
212
213 #define OP_IMPL(x) static void op_##x(struct dcpu16 *d, WORD val_a, WORD val_b)
214
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;\
218 unsigned int lit_a;\
219 do {\
220 lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
221 op_type;\
222 if (d->skip_) {\
223 TRACE("++ SKIPPED");\
224 d->skip_ = 0;\
225 return;\
226 }\
227 } while (0)
228 #define OP_BASIC(x) OP_TYPE(OP_BASIC_)
229 #define OP_NBI(x) OP_TYPE(OP_NBI_)
230
231
232 /* extended opcodes */
233
234 /*
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.
240 */
241 OP_IMPL(nbi__reserved_) {
242 OP_NBI(nbi__reserved_);
243 /* reserved for future expansion */
244
245 WORD future_opcode = (d->ram[d->pc] >> OPCODE_FUTURE_SHIFT);
246 WARN("reserved future opcode 0x%04x invoked", future_opcode);
247 }
248
249 OP_IMPL(nbi_jsr) {
250 OP_NBI(nbi_jsr);
251 /* pushes the address of the next instruction to the stack, then sets PC to a */
252
253 d->ram[ --d->sp ] = d->pc;
254 d->pc = *a;
255
256 d->cycle += 2;
257 }
258
259 OP_IMPL(nbi__reserved2_) {
260 OP_NBI(nbi__reserved2_);
261 /* reserved */
262
263 WARN("reserved nbi opcode invoked");
264 }
265
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_},
270 {0x0, "", NULL}
271 };
272 #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1)
273
274
275 /* basic opcodes */
276
277 /*
278 N.B. the following function does not decode values, as the nbi
279 instructions only have one operand.
280 */
281 OP_IMPL(_nbi_) {
282 /* non-basic instruction */
283
284 /* don't do normal value decoding here */
285
286 WORD nbi_opcode = val_a;
287 const struct opcode_entry *e = opcode_nbi_entries;
288
289 e = opcode_nbi_entries + ( (nbi_opcode < OPCODE_NBI_MAX) ? nbi_opcode : (OPCODE_NBI_MAX - 1) );
290
291 assert(e->impl != NULL);
292
293 TRACE(">> %s 0x%04x", e->name, val_b);
294 e->impl(d, val_b, 0);
295 }
296
297 OP_IMPL(set) {
298 OP_BASIC(set);
299 /* sets a to b */
300
301 /* only set non-literal target */
302 if (val_a < 0x1f) {
303 *a = *b;
304 }
305
306 d->cycle += 1;
307 }
308
309 OP_IMPL(add) {
310 OP_BASIC(add);
311 /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */
312 unsigned int acc = *a + *b;
313
314 if (val_a < 0x1f) {
315 *a = acc;
316 }
317 d->o = (acc > 0xffff);
318
319 d->cycle += 2;
320 }
321
322 OP_IMPL(sub) {
323 OP_BASIC(sub);
324 /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
325 unsigned int acc = *a - *b;
326
327 if (val_a < 0x1f) {
328 *a = acc;
329 d->o = (acc > 0xffff);
330
331 }
332 d->cycle += 2;
333 }
334
335 OP_IMPL(mul) {
336 OP_BASIC(mul);
337 /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
338 unsigned int acc = *a * *b;
339
340 if (val_a < 0x1f) {
341 *a = acc;
342 }
343 d->o = acc >> 16;
344 d->cycle += 2;
345 }
346
347 OP_IMPL(div) {
348 OP_BASIC(div);
349 /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
350
351 if (*b == 0) {
352 if (val_a < 0x1f) {
353 *a = 0;
354 }
355 d->o = 0;
356 } else {
357 unsigned int acc = *a / *b;
358
359 if (val_a < 0x1f) {
360 *a = acc;
361 }
362
363 acc = (*a << 16) / *b;
364 d->o = acc;
365 }
366
367 d->cycle += 3;
368 }
369
370 OP_IMPL(mod) {
371 OP_BASIC(mod);
372 /* sets a to a%b. if b==0, sets a to 0 instead. */
373
374 if (*b == 0) {
375 if (val_a < 0x1f) {
376 *a = 0;
377 }
378 } else {
379 if (val_a < 0x1f) {
380 *a = *a % *b;
381 }
382 }
383
384 d->cycle += 3;
385 }
386
387 OP_IMPL(shl) {
388 OP_BASIC(shl);
389 /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
390 unsigned int acc = *a << *b;
391
392 if (val_a < 0x1f) {
393 *a = acc;
394 }
395 d->o = acc >> 16;
396
397 d->cycle += 2;
398 }
399
400 OP_IMPL(shr) {
401 OP_BASIC(shr);
402 /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
403 unsigned int acc = *a >> *b;
404
405 if (val_a < 0x1f) {
406 *a = acc;
407 }
408 d->o = (*a << 16) >> *b;
409
410 d->cycle += 2;
411 }
412
413 OP_IMPL(and) {
414 OP_BASIC(and);
415 /* sets a to a&b */
416
417 if (val_a < 0x1f) {
418 *a = *a & *b;
419 }
420
421 d->cycle += 1;
422 }
423
424 OP_IMPL(bor) {
425 OP_BASIC(bor);
426 /* sets a to a|b */
427
428 if (val_a < 0x1f) {
429 *a = *a | *b;
430 }
431
432 d->cycle += 1;
433 }
434
435 OP_IMPL(xor) {
436 OP_BASIC(xor);
437 /* sets a to a^b */
438
439 if (val_a < 0x1f) {
440 *a = *a ^ *b;
441 }
442
443 d->cycle += 1;
444 }
445
446 OP_IMPL(ife) {
447 OP_BASIC(ife);
448 /* performs next instruction only if a==b */
449
450 if (*a == *b) {
451 /* */
452 } else {
453 d->skip_ = 1;
454 d->cycle++;
455 }
456
457 d->cycle += 2;
458 }
459
460 OP_IMPL(ifn) {
461 OP_BASIC(ifn);
462 /* performs next instruction only if a!=b */
463
464 if (*a != *b) {
465 /* */
466 } else {
467 d->skip_ = 1;
468 d->cycle++;
469 }
470
471 d->cycle += 2;
472 }
473
474 OP_IMPL(ifg) {
475 OP_BASIC(ifg);
476 /* performs next instruction only if a>b */
477
478 if (*a > *b) {
479 /* */
480 } else {
481 d->skip_ = 1;
482 d->cycle++;
483 }
484
485 d->cycle += 2;
486 }
487
488 OP_IMPL(ifb) {
489 OP_BASIC(ifb);
490 /* performs next instruction only if (a&b)!=0 */
491
492 if ((*a & *b) != 0) {
493 /* */
494 } else {
495 d->skip_ = 1;
496 d->cycle++;
497 }
498
499 d->cycle += 2;
500 }
501
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 },
519 {0x0, "", NULL }
520 };
521
522 void dump_value(WORD value, WORD nextword) {
523 if (value < 0x07) {
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);
539 }
540 }
541
542 void dcpu16_disassemble_print(struct dcpu16 *d, WORD addr) {
543 WORD opcode, a, b;
544 unsigned int instr_len = 1;
545 const struct opcode_entry *e;
546
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);
550
551 assert(opcode <= 0x0f);
552 assert(a <= 0x3f);
553 assert(b <= 0x3f);
554
555 printf("%04x", d->ram[addr]);
556
557 if (opcode != 0)
558 {
559 if (a == 0x1e || a == 0x1f) {
560 printf(" %04x", d->ram[addr + instr_len]);
561 instr_len++;
562 }
563 }
564 if (b == 0x1e || b == 0x1f) {
565 printf(" %04x", d->ram[addr + instr_len]);
566 instr_len++;
567 }
568
569 if (opcode)
570 e = opcode_basic_entries + opcode;
571 else
572 e = opcode_nbi_entries + ( (a < OPCODE_NBI_MAX) ? a : (OPCODE_NBI_MAX - 1) );
573
574 printf("%s%s ; %s",
575 instr_len < 3 ? " " : "",
576 instr_len < 2 ? " " : "",
577 e->name);
578 if (opcode != 0) {
579 dump_value(a, d->ram[addr + 1]);
580 if (a == 0x1e || a == 0x1f)
581 addr++;
582 printf(",");
583 }
584
585 dump_value(b, d->ram[addr + 1]);
586 }
587
588 void dcpu16_step(struct dcpu16 *d) {
589 WORD opcode;
590 WORD val_a, val_b;
591 const struct opcode_entry *e;
592
593 /* decode opcode and invoke */
594
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);
598
599 d->pc++;
600
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);
605 break;
606 }
607 }
608 }
609
610 void dcpu16_state_print(struct dcpu16 *d) {
611 unsigned int i;
612
613 printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
614 d->cycle,
615 "O", d->o,
616 "SP", d->sp,
617 "PC", d->pc,
618 "PC");
619
620 dcpu16_disassemble_print(d, d->pc);
621 printf("\n ");
622
623 for (i = 0; i < 8; i++)
624 printf(" %c:0x%04x", regnames_[i], d->reg[i]);
625 printf("\n");
626 }
627
628 /* dcpu16_dump_ram
629 * print raw ram contents from start to stop
630 */
631 void dcpu16_dump_ram(struct dcpu16 *d, WORD start, WORD end) {
632 unsigned int i, j;
633 const unsigned int n = 8; /* words per line */
634
635 for (i = start, j = 0; i <= end; i++, j++) {
636 if (j % n == 0)
637 printf("0x%04x:\t", i);
638 printf(" %04x%s", d->ram[i], (j % n) == (n - 1) ? "\n" : "");
639 }
640 if ((j % n) != (n - 1))
641 printf("\n");
642 }
643
644 /* dcpu16_reset
645 * resets a dcpu16 instance to initial state
646 */
647 void dcpu16_reset(struct dcpu16 *d) {
648 memset(d, 0, sizeof *d);
649 }
650
651 /* dcpu16_new
652 * allocate a new dcpu16 instance
653 */
654 struct dcpu16 *dcpu16_new(void) {
655 struct dcpu16 *vm;
656
657 vm = calloc(1, sizeof *vm);
658 if (vm == NULL)
659 WARN("%s: %s(%zu): %s", __func__, "calloc", strerror(errno));
660
661 return vm;
662 }
663
664 /* dcpu16_delete
665 * release a dcpu16 instance
666 */
667 void dcpu16_delete(struct dcpu16 **vm) {
668 free(*vm);
669 *vm = NULL;
670 }