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