initial commit
[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 *
21 * TODO
22 * move cli driver to separate module
23 */
24
25 static const char * const src_id_ = "$Id$";
26
27 /* the target system's concept of a word */
28 #define WORD DCPU16_WORD
29 typedef unsigned short WORD;
30
31 #define RAM_SIZE 0x10000
32 static const char regnames_[] = "ABCXYZIJ";
33 struct dcpu16 {
34 unsigned long long cycle; /* number of cycles it took to get to current state */
35 WORD reg_work_[2]; /* holding bins for literal values when decoding instructions */
36 WORD reg[8]; /* system registers, a b c x y z i j */
37 WORD pc; /* program counter */
38 WORD sp; /* stack pointer */
39 WORD o; /* overflow */
40 unsigned int skip_ : 1; /* */
41 WORD ram[RAM_SIZE]; /* memory */
42 };
43
44
45 static unsigned int trace_mode_ = 0; /* turn on for overly verbose internals */
46
47 #define WARN(...) warn(__VA_ARGS__)
48 static inline void warn(char *fmt, ...) __attribute__((format(printf, 1, 2)));
49 static inline void warn(char *fmt, ...) {
50 va_list ap;
51
52 fprintf(stderr, "!!! ");
53 va_start(ap, fmt);
54 vfprintf(stderr, fmt, ap);
55 va_end(ap);
56 fprintf(stderr, "\n");
57 fflush(stderr);
58 }
59
60
61 #define TRACE(...) do { if (trace_mode_) trace(__VA_ARGS__); } while (0)
62 static inline void trace(char *fmt, ...) __attribute__((format(printf, 1, 2)));
63 static inline
64 void trace(char *fmt, ...) {
65 va_list ap;
66
67 va_start(ap, fmt);
68 vfprintf(stdout, fmt, ap);
69 va_end(ap);
70 fprintf(stdout, "\n");
71 fflush(stdout);
72 }
73
74
75 /* sets *v to be the destination of the value */
76 /* workv is buffer to use to accumulate literal value before use */
77 /* returns true if destination points to literal (id est *v should ignore writes) */
78 static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WORD **v) {
79 WORD nextword;
80 unsigned int retval = 0;
81
82 assert(value >= 0x00 && value <= 0x3f);
83
84 /* does this value indicate a literal */
85 if (value >= 0x1f)
86 retval = 1;
87
88 /* if we're skipping this instruction, just advance the pc if needed */
89 if (d->skip_) {
90 TRACE(">> SKIP decode");
91 if (value == 0x1e || value == 0x1f)
92 d->pc++;
93 return retval;
94 }
95
96 if (value <= 0x07) { /* register */
97 *v = d->reg + value;
98 TRACE(">> %c (0x%04x)",
99 regnames_[value],
100 **v);
101
102 } else if (value <= 0x0f) { /* [register] */
103 *v = &(d->ram[ d->reg[(value & 0x07)] ]);
104 TRACE(">> [%c] [0x%04x] (0x%04x)",
105 regnames_[value&0x07],
106 d->reg[value&0x07],
107 **v);
108
109 } else if (value <= 0x17) { /* [next word + register] */
110 nextword = d->ram[ d->pc++ ];
111 d->cycle++;
112 *v = &(d->ram[ nextword + d->reg[(value & 0x07)] ]);
113 TRACE(">> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)",
114 regnames_[(value & 0x07)],
115 nextword,
116 d->reg[(value & 0x07)],
117 **v);
118
119 } else switch (value) {
120 case 0x18: /* POP / [sp++] */
121 *v = &(d->ram[ d->sp++ ]);
122 TRACE(">> POP [0x%04x] (0x%04x)",
123 d->sp - 1,
124 **v);
125 break;
126
127 case 0x19: /* PEEK / [sp] */
128 *v = &(d->ram[ d->sp ]);
129 TRACE(">> PEEK [0x%04x] (0x%04x)",
130 d->sp,
131 **v);
132 break;
133
134 case 0x1a: /* PUSH / [--sp] */
135 *v = &(d->ram[ --d->sp ]);
136 TRACE(">> PUSH [0x%04x] (0x%04x)",
137 d->sp + 1,
138 **v);
139 break;
140
141 case 0x1b: /* SP */
142 *v = &(d->sp);
143 TRACE(">> SP (0x%04x)",
144 **v);
145 break;
146
147 case 0x1c: /* PC */
148 *v = &(d->pc);
149 TRACE(">> PC (0x%04x)", **v);
150 break;
151
152 case 0x1d: /* O */
153 *v = &(d->o);
154 TRACE(">> O (0x%04x)", **v);
155 break;
156
157 case 0x1e: /* [next word] / [[pc++]] */
158 nextword = d->ram[ d->pc++ ];
159 d->cycle++;
160 *v = &(d->ram[ nextword ]);
161 TRACE(">> [nextword] [0x%04x] (0x%04x)",
162 nextword,
163 **v);
164 break;
165
166 case 0x1f: /* next word (literal) / [pc++] */
167 nextword = d->ram[ d->pc++ ];
168 d->cycle++;
169 *work_v = nextword;
170 *v = work_v;
171 TRACE(">> nextword (0x%04x)", **v);
172 break;
173
174 default: /* 0x20-0x3f: literal values 0x00-0x1f */
175 *work_v = value & 0x1f;
176 *v = work_v;
177 TRACE(">> literal (0x%04x)", **v);
178 }
179
180 return retval;
181 }
182
183 #define OPCODE_BASIC_BITS (4)
184 #define OPCODE_BASIC_SHIFT (0)
185
186 #define OPCODE_NBI_BITS (6)
187 #define OPCODE_NBI_SHIFT (4)
188
189 #define OPCODE_FUTURE_BITS (16)
190 #define OPCODE_FUTURE_SHIFT (10)
191
192 #define OPCODE_NAME_LEN 16
193 struct opcode_entry {
194 unsigned short value;
195 char name[OPCODE_NAME_LEN];
196 void (*impl)(struct dcpu16 *, WORD, WORD);
197 };
198
199 /* messy boilerplate for opcode handlers */
200
201 #define OP_IMPL(x) static void op_##x(struct dcpu16 *d, WORD val_a, WORD val_b)
202
203 #define OP_NBI_ (void)val_b
204 #define OP_BASIC_ (void)value_decode(d, val_b, &d->reg_work_[0], &b)
205 #define OP_TYPE(op_type) WORD *a, *b;\
206 unsigned int lit_a;\
207 do {\
208 assert(d != NULL);\
209 lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
210 op_type;\
211 if (d->skip_) {\
212 TRACE("++ SKIPPED");\
213 d->skip_ = 0;\
214 return;\
215 }\
216 } while (0)
217
218
219 #define OP_BASIC(x) WORD *a, *b;\
220 unsigned int lit_a;\
221 do {\
222 assert(d != NULL);\
223 lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
224 value_decode(d, val_b, &d->reg_work_[1], &b);\
225 if (d->skip_) {\
226 TRACE("++ SKIPPED");\
227 d->skip_ = 0;\
228 return;\
229 }\
230 } while(0)
231
232 #define OP_NBI(x) WORD *a;\
233 unsigned int lit_a;\
234 do {\
235 assert(d != NULL);\
236 lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
237 (void)val_b;\
238 if (d->skip_) {\
239 TRACE("++ SKIPPED");\
240 d->skip_ = 0;\
241 return;\
242 }\
243 } while(0)
244
245
246 /* extended opcodes */
247
248 /*
249 N.B. this next function currently decodes values -- id est, it is
250 an opcode processing terminus; however, if 'future instruction set
251 expansion' happens, this will probably need to behave more like
252 the OP_IMPL(_nbi_) function which invoked it, if those instructions
253 have zero or differently styled operands.
254 */
255 OP_IMPL(nbi__reserved_) {
256 OP_NBI(nbi__reserved_);
257 /* reserved for future expansion */
258
259 WORD future_opcode = (d->ram[d->pc] >> OPCODE_FUTURE_SHIFT);
260 WARN("reserved future opcode 0x%04x invoked", future_opcode);
261 }
262
263 OP_IMPL(nbi_jsr) {
264 OP_NBI(nbi_jsr);
265 /* pushes the address of the next instruction to the stack, then sets PC to a */
266
267 d->ram[ --d->sp ] = d->pc;
268 d->pc = *a;
269
270 d->cycle += 2;
271 }
272
273 OP_IMPL(nbi__reserved2_) {
274 OP_NBI(nbi__reserved2_);
275 /* reserved */
276
277 WARN("reserved nbi opcode invoked");
278 }
279
280 static const struct opcode_entry opcode_nbi_entries[] = {
281 {0x0, "(reserved)", op_nbi__reserved_},
282 {0x1, "JSR", op_nbi_jsr},
283 {0x2, "(reserved)", op_nbi__reserved2_},
284 {0x0, "", NULL}
285 };
286 #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1)
287
288
289 /* basic opcodes */
290
291 /*
292 N.B. the following function does not decode values, as the nbi
293 instructions only have one operand.
294 */
295 OP_IMPL(_nbi_) {
296 assert(d != NULL);
297 /* non-basic instruction */
298
299 /* don't do normal value decoding here */
300
301 WORD nbi_opcode = val_a;
302 const struct opcode_entry *e = opcode_nbi_entries;
303
304 e = opcode_nbi_entries + ( (nbi_opcode < OPCODE_NBI_MAX) ? nbi_opcode : (OPCODE_NBI_MAX - 1) );
305
306 assert(e->impl != NULL);
307
308 TRACE(">> %s 0x%04x", e->name, val_b);
309 e->impl(d, val_b, 0);
310 }
311
312 OP_IMPL(set) {
313 OP_BASIC(set);
314 /* sets a to b */
315
316 /* only set non-literal target */
317 if (val_a < 0x1f) {
318 *a = *b;
319 }
320
321 d->cycle += 1;
322 }
323
324 OP_IMPL(add) {
325 OP_BASIC(add);
326 /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */
327 unsigned int acc = *a + *b;
328
329 if (val_a < 0x1f) {
330 *a = acc;
331 }
332 d->o = acc >> 16;
333
334 d->cycle += 2;
335 }
336
337 OP_IMPL(sub) {
338 OP_BASIC(sub);
339 /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
340 unsigned int acc = *a - *b;
341
342 if (val_a < 0x1f) {
343 *a = acc;
344 }
345 d->o = acc >> 16;
346
347 d->cycle += 2;
348 }
349
350 OP_IMPL(mul) {
351 OP_BASIC(mul);
352 /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
353 unsigned int acc = *a * *b;
354
355 if (val_a < 0x1f) {
356 *a = acc;
357 }
358 d->o = acc >> 16;
359 d->cycle += 2;
360 }
361
362 OP_IMPL(div) {
363 OP_BASIC(div);
364 /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
365
366 if (*b == 0) {
367 if (val_a < 0x1f) {
368 *a = 0;
369 }
370 d->o = 0;
371 } else {
372 unsigned int acc = *a / *b;
373
374 if (val_a < 0x1f) {
375 *a = acc;
376 }
377
378 acc = (*a << 16) / *b;
379 d->o = acc;
380 }
381
382 d->cycle += 3;
383 }
384
385 OP_IMPL(mod) {
386 OP_BASIC(mod);
387 /* sets a to a%b. if b==0, sets a to 0 instead. */
388
389 if (*b == 0) {
390 if (val_a < 0x1f) {
391 *a = 0;
392 }
393 } else {
394 if (val_a < 0x1f) {
395 *a = *a % *b;
396 }
397 }
398
399 d->cycle += 3;
400 }
401
402 OP_IMPL(shl) {
403 OP_BASIC(shl);
404 /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
405 unsigned int acc = *a << *b;
406
407 if (val_a < 0x1f) {
408 *a = acc;
409 }
410 d->o = acc >> 16;
411
412 d->cycle += 2;
413 }
414
415 OP_IMPL(shr) {
416 OP_BASIC(shr);
417 /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
418 unsigned int acc = *a >> *b;
419
420 if (val_a < 0x1f) {
421 *a = acc;
422 }
423 d->o = (*a << 16) >> *b;
424
425 d->cycle += 2;
426 }
427
428 OP_IMPL(and) {
429 OP_BASIC(and);
430 /* sets a to a&b */
431
432 if (val_a < 0x1f) {
433 *a = *a & *b;
434 }
435
436 d->cycle += 1;
437 }
438
439 OP_IMPL(bor) {
440 OP_BASIC(bor);
441 /* sets a to a|b */
442
443 if (val_a < 0x1f) {
444 *a = *a | *b;
445 }
446
447 d->cycle += 1;
448 }
449
450 OP_IMPL(xor) {
451 OP_BASIC(xor);
452 /* sets a to a^b */
453
454 if (val_a < 0x1f) {
455 *a = *a ^ *b;
456 }
457
458 d->cycle += 1;
459 }
460
461 OP_IMPL(ife) {
462 OP_BASIC(ife);
463 /* performs next instruction only if a==b */
464
465 if (*a == *b) {
466 /* */
467 } else {
468 d->skip_ = 1;
469 d->cycle++;
470 }
471
472 d->cycle += 2;
473 }
474
475 OP_IMPL(ifn) {
476 OP_BASIC(ifn);
477 /* performs next instruction only if a!=b */
478
479 if (*a != *b) {
480 /* */
481 } else {
482 d->skip_ = 1;
483 d->cycle++;
484 }
485
486 d->cycle += 2;
487 }
488
489 OP_IMPL(ifg) {
490 OP_BASIC(ifg);
491 /* performs next instruction only if a>b */
492
493 if (*a > *b) {
494 /* */
495 } else {
496 d->skip_ = 1;
497 d->cycle++;
498 }
499
500 d->cycle += 2;
501 }
502
503 OP_IMPL(ifb) {
504 OP_BASIC(ifb);
505 /* performs next instruction only if (a&b)!=0 */
506
507 if ((*a & *b) != 0) {
508 /* */
509 } else {
510 d->skip_ = 1;
511 d->cycle++;
512 }
513
514 d->cycle += 2;
515 }
516
517 static const struct opcode_entry opcode_basic_entries[] = {
518 {0x0, "(nbi)", op__nbi_},
519 {0x1, "SET", op_set },
520 {0x2, "ADD", op_add },
521 {0x3, "SUB", op_sub },
522 {0x4, "MUL", op_mul },
523 {0x5, "DIV", op_div },
524 {0x6, "MOD", op_mod },
525 {0x7, "SHL", op_shl },
526 {0x8, "SHR", op_shr },
527 {0x9, "AND", op_and },
528 {0xa, "BOR", op_bor },
529 {0xb, "XOR", op_xor },
530 {0xc, "IFE", op_ife },
531 {0xd, "IFN", op_ifn },
532 {0xe, "IFG", op_ifg },
533 {0xf, "IFB", op_ifb },
534 {0x0, "", NULL }
535 };
536
537 void dump_value(WORD value, WORD nextword) {
538 if (value < 0x07) {
539 printf(" %c", regnames_[value]);
540 } else if (value < 0x0f) {
541 printf(" [%c]", regnames_[value & 0x07]);
542 } else if (value < 0x17) {
543 printf(" [0x%04x + %c]", nextword, regnames_[value & 0x07]);
544 } else switch (value) {
545 case 0x18: printf(" POP"); break;
546 case 0x19: printf(" PEEK"); break;
547 case 0x1a: printf(" PUSH"); break;
548 case 0x1b: printf(" SP"); break;
549 case 0x1c: printf(" PC"); break;
550 case 0x1d: printf(" O"); break;
551 case 0x1e: printf(" [0x%04x]", nextword); break;
552 case 0x1f: printf(" 0x%04x", nextword); break;
553 default: printf(" 0x%02x", value - 0x20);
554 }
555 }
556
557 void dump_instruction(struct dcpu16 *d, WORD addr) {
558 WORD opcode, a, b;
559 unsigned int instr_len = 1;
560 const struct opcode_entry *e;
561
562 opcode = (d->ram[addr] >> OPCODE_BASIC_SHIFT) & ((1 << OPCODE_BASIC_BITS) - 1);
563 a = (d->ram[addr] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS)) & ((1 << 6) - 1);
564 b = (d->ram[addr] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS + 6)) & ((1 << 6) - 1);
565
566 assert(opcode < 0x0f);
567 assert(a < 0x3f);
568 assert(b < 0x3f);
569
570 printf("next instr 0x%04x: %04x", addr, d->ram[addr]);
571
572 if (opcode != 0)
573 {
574 if (a == 0x1e || a == 0x1f) {
575 printf(" %04x", d->ram[addr + instr_len]);
576 instr_len++;
577 }
578 }
579 if (b == 0x1e || b == 0x1f) {
580 printf(" %04x", d->ram[addr + instr_len]);
581 instr_len++;
582 }
583
584 if (opcode)
585 e = opcode_basic_entries + opcode;
586 else
587 e = opcode_nbi_entries + ( (a < OPCODE_NBI_MAX) ? a : (OPCODE_NBI_MAX - 1) );
588 printf("\n\t%s", e->name);
589 if (opcode != 0) {
590 dump_value(a, d->ram[addr + 1]);
591 if (a == 0x1e || a == 0x1f)
592 addr++;
593 printf(",");
594 }
595
596 dump_value(b, d->ram[addr + 1]);
597
598 printf("\n");
599 }
600
601 void dcpu16_execute_next_instruction(struct dcpu16 *d) {
602 WORD opcode;
603 WORD val_a, val_b;
604 const struct opcode_entry *e;
605
606 /* fetch next instruction */
607 if (d->pc > RAM_SIZE) { /* currently impossible */
608 WARN("%s beyond %u", "PC", RAM_SIZE);
609 /* d->pc %= RAM_SIZE; */
610 }
611
612 opcode = (d->ram[ d->pc ] >> OPCODE_BASIC_SHIFT) & ((1 << OPCODE_BASIC_BITS) - 1);
613 val_a = (d->ram[d->pc] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS)) & ((1 << 6) - 1);
614 val_b = (d->ram[d->pc] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS + 6)) & ((1 << 6) - 1);
615
616 d->pc++;
617
618 for (e = opcode_basic_entries; e->impl; e++) {
619 if (e->value == opcode) {
620 TRACE(">> %s 0x%04x, 0x%04x", e->name, val_a, val_b);
621 e->impl(d, val_a, val_b);
622 break;
623 }
624 }
625 }
626
627 static void usage(char *prog, unsigned int full) {
628 FILE *f = full ? stdout : stderr;
629 char *x = strrchr(prog, '/');
630
631 if (x && *(x + 1))
632 prog = x + 1;
633
634 if (full)
635 fprintf(f, "%s -- \n\n",
636 prog);
637
638 fprintf(f, "Usage: %s\n",
639 prog);
640
641 if (full) {
642 fprintf(f, "\nOptions:\n"
643 "\t-h -- this screen\n");
644
645 fprintf(f, "\n%78s\n", src_id_);
646 }
647 }
648
649 static int file_load(struct dcpu16 *d, char *filename) {
650 FILE *f;
651 size_t r;
652
653 f = fopen(filename, "rb");
654 if (f == NULL)
655 {
656 fprintf(stderr, "%s(%s):%s\n", "fopen", filename, strerror(errno));
657 return -1;
658 }
659
660 r = fread(d->ram, sizeof(WORD), RAM_SIZE, f);
661 TRACE("read %zu words", r);
662
663 if (ferror(f)) {
664 fprintf(stderr, "%s():%s\n", "fread", strerror(errno));
665 }
666
667 fclose(f);
668 return 0;
669 }
670
671 static void testprog_load(struct dcpu16 *d) {
672 static WORD bin[] = {
673 0x7c01, 0x0030, 0x7de1, 0x1000, 0x0020, 0x7803, 0x1000, 0xc00d,
674 0x7dc1, 0x001a, 0xa861, 0x7c01, 0x2000, 0x2161, 0x2000, 0x8463,
675 0x806d, 0x7dc1, 0x000d, 0x9031, 0x7c10, 0x0018, 0x7dc1, 0x001a,
676 0x9037, 0x61c1, 0x7dc1, 0x001a, 0x0000
677 };
678 size_t i;
679
680 printf("loading...\n");
681 for (i = 0; i < (sizeof(bin) / sizeof(WORD)); i++)
682 {
683 printf(" %04x", bin[i]);
684 d->ram[i] = bin[i];
685 }
686 printf("\nloaded 0x%04zx words\n", i - 1);
687 }
688
689 static
690 void dump_cpu_state(struct dcpu16 *d) {
691 unsigned int i;
692
693 printf("[--- cycle:0x%08llx %2s:0x%04x %2s:0x%04x %2s:0x%04x\n",
694 d->cycle,
695 "PC", d->pc,
696 "SP", d->sp,
697 "O", d->o);
698 printf(" ");
699 for (i = 0; i < 8; i++)
700 printf(" %c:0x%04x", regnames_[i], d->reg[i]);
701 printf("\n");
702 }
703
704 static
705 void dump_ram(struct dcpu16 *d, WORD start, WORD stop) {
706 unsigned int i, j;
707 const unsigned int n = 8; /* words per line */
708
709 for (i = start, j = 0; i <= stop; i++, j++) {
710 if (j % n == 0)
711 printf("0x%04x:\t", i);
712 printf(" %04x%s", d->ram[i], (j % n) == (n - 1) ? "\n" : "");
713 }
714 }
715
716
717 int main(int argc, char **argv) {
718 struct dcpu16 *m;
719 int c;
720 char buf[512];
721
722 m = calloc(1, sizeof *m);
723 if (m == NULL)
724 {
725 fprintf(stderr, "%s:%s\n", "calloc", strerror(errno));
726 exit(EX_OSERR);
727 }
728
729 while ( (c = getopt(argc, argv, "ht")) != EOF )
730 {
731 switch (c)
732 {
733 case 't':
734 trace_mode_ = 1;
735 dump_ram(m, 0, 0x001f);
736 testprog_load(m);
737 dump_ram(m, 0, 0x001f);
738 break;
739
740 case 'h':
741 usage(argv[0], 1);
742 exit(EX_OK);
743
744 default:
745 usage(argv[0], 0);
746 exit(EX_USAGE);
747 }
748 }
749
750 if (argc - optind)
751 {
752 /* read file */
753 file_load(m, argv[optind]);
754 }
755
756 dump_cpu_state(m);
757 while (fgets(buf, sizeof buf, stdin)) {
758 dcpu16_execute_next_instruction(m);
759 dump_cpu_state(m);
760 if (trace_mode_)
761 dump_instruction(m, m->pc);
762 }
763
764 free(m);
765
766 exit(EX_OK);
767 }