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