actually commit minor cleanups
[dcpu16] / dcpu16.c
1 #include <stdio.h>
2 #include <stdlib.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 /*
11 * emulates the DCPU16 system from http://0x10c.com/doc/dcpu-16.txt
12 *
13 * I couldn't remember ever implementing an emulator before, so this
14 * happened. As such, consider this a toy in progress.
15 * There are likely many improvable aspects.
16 *
17 * Justin Wind <justin.wind@gmail.com>
18 * 2012 04 05 - implementation started
19 * 2012 04 06 - first functionality achieved
20 * 2012 04 09 - minor cleanups
21 *
22 * TODO
23 * move cli driver to separate module
24 * drop checks for assigning to literals -- it won't affect anything anyhow
25 * debug short literal decoding
26 */
27
28 static const char * const src_id_ = "$Id$";
29
30 /* the target system's concept of a word */
31 #define WORD DCPU16_WORD
32 typedef unsigned short WORD;
33
34 #define RAM_SIZE 0x10000
35 static const char regnames_[] = "ABCXYZIJ";
36 struct dcpu16 {
37 unsigned long long cycle; /* number of cycles it took to get to current state */
38 WORD reg_work_[2]; /* holding bins for literal values when decoding instructions */
39 WORD reg[8]; /* system registers, a b c x y z i j */
40 WORD pc; /* program counter */
41 WORD sp; /* stack pointer */
42 WORD o; /* overflow */
43 unsigned int skip_ : 1; /* skip execution of next instruction */
44 WORD ram[RAM_SIZE]; /* memory */
45 };
46
47
48 static unsigned int trace_mode_ = 0; /* spew overly verbose internals */
49
50 #define WARN(...) do { if (warn_cb_) warn_cb_(__VA_ARGS__); } while (0)
51 static inline void warn_(char *fmt, ...) __attribute__((format(printf, 1, 2)));
52 static inline
53 void warn_(char *fmt, ...) {
54 va_list ap;
55
56 fprintf(stderr, "!!! ");
57 va_start(ap, fmt);
58 vfprintf(stderr, fmt, ap);
59 va_end(ap);
60 fprintf(stderr, "\n");
61 fflush(stderr);
62 }
63 static void (*warn_cb_)(char *fmt, ...) = warn_;
64 void dcpu16_warn_cb_set(void (*fn)(char *fmt, ...)) {
65 warn_cb_ = fn;
66 }
67
68 #define TRACE(...) do { if (trace_cb_ && trace_mode_) trace_cb_(__VA_ARGS__); } while (0)
69 static inline void trace_(char *fmt, ...) __attribute__((format(printf, 1, 2)));
70 static inline
71 void trace_(char *fmt, ...) {
72 va_list ap;
73
74 va_start(ap, fmt);
75 vfprintf(stdout, fmt, ap);
76 va_end(ap);
77 fprintf(stdout, "\n");
78 fflush(stdout);
79 }
80 static void (*trace_cb_)(char *fmt, ...) = trace_;
81 void dcpu16_trace_cb_set(void (*fn)(char *fmt, ...)) {
82 trace_cb_ = fn;
83 }
84
85
86
87 /* sets *v to be the destination of the value */
88 /* workv is buffer to use to accumulate literal value before use */
89 /* 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) {
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 >> 16;
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 }
330 d->o = acc >> 16;
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 dump_instruction(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(" next instr 0x%04x: %04x", addr, 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 printf("\n");
588 }
589
590 void dcpu16_execute_next_instruction(struct dcpu16 *d) {
591 WORD opcode;
592 WORD val_a, val_b;
593 const struct opcode_entry *e;
594
595 /* decode opcode and invoke */
596
597 opcode = (d->ram[ d->pc ] >> OPCODE_BASIC_SHIFT) & ((1 << OPCODE_BASIC_BITS) - 1);
598 val_a = (d->ram[d->pc] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS)) & ((1 << 6) - 1);
599 val_b = (d->ram[d->pc] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS + 6)) & ((1 << 6) - 1);
600
601 d->pc++;
602
603 for (e = opcode_basic_entries; e->impl; e++) {
604 if (e->value == opcode) {
605 TRACE(">> %s 0x%04x, 0x%04x", e->name, val_a, val_b);
606 e->impl(d, val_a, val_b);
607 break;
608 }
609 }
610 }
611
612 static void usage(char *prog, unsigned int full) {
613 FILE *f = full ? stdout : stderr;
614 char *x = strrchr(prog, '/');
615
616 if (x && *(x + 1))
617 prog = x + 1;
618
619 if (full)
620 fprintf(f, "%s -- \n\n",
621 prog);
622
623 fprintf(f, "Usage: %s\n",
624 prog);
625
626 if (full) {
627 fprintf(f, "\nOptions:\n"
628 "\t-h -- this screen\n"
629 "\t-t -- test mode, load demo program\n"
630 "\t-v -- verbose execution tracing\n");
631
632 fprintf(f, "\n%78s\n", src_id_);
633 }
634 }
635
636 static int file_load(struct dcpu16 *d, char *filename) {
637 FILE *f;
638 size_t r;
639
640 f = fopen(filename, "rb");
641 if (f == NULL)
642 {
643 fprintf(stderr, "%s(%s):%s\n", "fopen", filename, strerror(errno));
644 return -1;
645 }
646
647 r = fread(d->ram, sizeof(WORD), RAM_SIZE, f);
648 TRACE("read %zu words", r);
649
650 if (ferror(f)) {
651 fprintf(stderr, "%s():%s\n", "fread", strerror(errno));
652 }
653
654 fclose(f);
655 return 0;
656 }
657
658 static void testprog_load(struct dcpu16 *d) {
659 static WORD bin[] = {
660 0x7c01, 0x0030, 0x7de1, 0x1000, 0x0020, 0x7803, 0x1000, 0xc00d,
661 0x7dc1, 0x001a, 0xa861, 0x7c01, 0x2000, 0x2161, 0x2000, 0x8463,
662 0x806d, 0x7dc1, 0x000d, 0x9031, 0x7c10, 0x0018, 0x7dc1, 0x001a,
663 0x9037, 0x61c1, 0x7dc1, 0x001a, 0x0000
664 };
665 size_t i;
666
667 printf("loading...\n");
668 for (i = 0; i < (sizeof(bin) / sizeof(WORD)); i++)
669 {
670 printf(" %04x", bin[i]);
671 d->ram[i] = bin[i];
672 }
673 printf("\nloaded 0x%04zx words\n", i - 1);
674 }
675
676 static
677 void dump_cpu_state(struct dcpu16 *d) {
678 unsigned int i;
679
680 printf("---- cycle:0x%08llx %2s:0x%04x %2s:0x%04x %2s:0x%04x\n",
681 d->cycle,
682 "PC", d->pc,
683 "SP", d->sp,
684 "O", d->o);
685 printf(" ");
686 for (i = 0; i < 8; i++)
687 printf(" %c:0x%04x", regnames_[i], d->reg[i]);
688 printf("\n");
689 }
690
691 static
692 void dump_ram(struct dcpu16 *d, WORD start, WORD stop) {
693 unsigned int i, j;
694 const unsigned int n = 8; /* words per line */
695
696 for (i = start, j = 0; i <= stop; i++, j++) {
697 if (j % n == 0)
698 printf("0x%04x:\t", i);
699 printf(" %04x%s", d->ram[i], (j % n) == (n - 1) ? "\n" : "");
700 }
701 }
702
703
704 int main(int argc, char **argv) {
705 struct dcpu16 *m;
706 int c;
707 char buf[512];
708
709 m = calloc(1, sizeof *m);
710 if (m == NULL)
711 {
712 fprintf(stderr, "%s:%s\n", "calloc", strerror(errno));
713 exit(EX_OSERR);
714 }
715
716 while ( (c = getopt(argc, argv, "htv")) != EOF )
717 {
718 switch (c)
719 {
720 case 'v':
721 trace_mode_ = 1;
722 break;
723
724 case 't':
725 dump_ram(m, 0, 0x001f);
726 testprog_load(m);
727 dump_ram(m, 0, 0x001f);
728 break;
729
730 case 'h':
731 usage(argv[0], 1);
732 exit(EX_OK);
733
734 default:
735 usage(argv[0], 0);
736 exit(EX_USAGE);
737 }
738 }
739
740 if (argc - optind)
741 {
742 /* read file */
743 file_load(m, argv[optind]);
744 }
745
746 dump_cpu_state(m);
747 dump_instruction(m, m->pc);
748 while (fgets(buf, sizeof buf, stdin)) {
749 dcpu16_execute_next_instruction(m);
750 dump_cpu_state(m);
751 dump_instruction(m, m->pc);
752 }
753
754 free(m);
755
756 exit(EX_OK);
757 }