support png output, buffered file writing
[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 * 2012 04 12 - added basic callback support for address accesses
25 *
26 * TODO
27 * change api to print into buffers rather than stdio
28 * refactor opcode functiontables into switch statements
29 * let callbacks determine whether to override events, or just observe
30 * sort init callbacks by base addr, to call in-order
31 */
32
33 static const char * const src_id_ = "$Id$";
34
35 #define OPCODE_BASIC_BITS 4
36 #define OPCODE_OPERAND_BITS 6
37
38 static const char * const regnames_ = "ABCXYZIJ";
39
40 /* some default warning and debug reporting functions, which can be overridden by clients */
41 #define WARN(...) do { if (warn_cb_) warn_cb_(__VA_ARGS__); } while (0)
42 static inline void warn_(char *fmt, ...) __attribute__((format(printf, 1, 2)));
43 static inline
44 void warn_(char *fmt, ...) {
45 va_list ap;
46
47 fprintf(stderr, "[warning] ");
48 va_start(ap, fmt);
49 vfprintf(stderr, fmt, ap);
50 va_end(ap);
51 fprintf(stderr, "\n");
52 fflush(stderr);
53 }
54 static void (*warn_cb_)(char *fmt, ...) = warn_;
55 void dcpu16_warn_cb_set(void (*fn)(char *fmt, ...)) {
56 warn_cb_ = fn;
57 }
58
59 #ifdef DEBUG
60 #define TRACE(...) do { if (trace_cb_) trace_cb_(__VA_ARGS__); } while (0)
61 static inline void trace_(char *fmt, ...) __attribute__((format(printf, 1, 2)));
62 static inline
63 void trace_(char *fmt, ...) {
64 va_list ap;
65
66 fprintf(stdout, "[debug] ");
67 va_start(ap, fmt);
68 vfprintf(stdout, fmt, ap);
69 va_end(ap);
70 fprintf(stdout, "\n");
71 fflush(stdout);
72 }
73 #else /* DEBUG */
74 #define TRACE(...) do {} while(0)
75 #endif /* DEBUG */
76 static void (*trace_cb_)(char *fmt, ...) =
77 #ifdef DEBUG
78 trace_
79 #else /* DEBUG */
80 NULL
81 #endif
82 ;
83 void dcpu16_trace_cb_set(void (*fn)(char *fmt, ...)) {
84 trace_cb_ = fn;
85 }
86
87
88 /* acct_event_
89 * invokes callbacks for specified event
90 */
91 static inline
92 void acct_event_(struct dcpu16 *vm, dcpu16_acct_event ev, DCPU16_WORD addr) {
93 struct dcpu16_acct_cb *cb = vm->cb_table_;
94 size_t i;
95
96 for (i = 0; i < vm->cb_table_entries_; i++) {
97 if ( (cb[i].mask & ev) )
98 cb[i].fn(vm, ev, addr, cb[i].data);
99 }
100 }
101
102
103 /* value_decode_
104 * sets *v to be the destination of the value
105 * advances d->pc if necessary
106 * workv is buffer to use to accumulate literal value before use, one exists for either potential instruction operand
107 * e_addr is for accounting callback
108 * returns true if destination points to literal (id est *v should ignore writes)
109 */
110 static
111 unsigned int value_decode_(struct dcpu16 *d, DCPU16_WORD value, DCPU16_WORD *work_v, DCPU16_WORD **v, DCPU16_WORD *e_addr) {
112 DCPU16_WORD nextword;
113 unsigned int retval = 0;
114
115 assert(value <= 0x3f);
116
117 /* does this value indicate a literal */
118 if (value >= 0x1f)
119 retval = 1;
120
121 if (value <= 0x07) { /* register */
122 *v = d->reg + value;
123 TRACE(">> %c (0x%04x)",
124 regnames_[value],
125 **v);
126
127 } else if (value <= 0x0f) { /* [register] */
128 *v = &(d->ram[ d->reg[(value & 0x07)] ]);
129 TRACE(">> [%c] [0x%04x] (0x%04x)",
130 regnames_[value&0x07],
131 d->reg[value&0x07],
132 **v);
133 *e_addr = d->reg[(value & 0x07)];
134
135 } else if (value <= 0x17) { /* [next word + register] */
136 nextword = d->ram[ d->pc++ ];
137 d->cycle++;
138 *v = &(d->ram[ nextword + d->reg[(value & 0x07)] ]);
139 TRACE(">> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)",
140 regnames_[(value & 0x07)],
141 nextword,
142 d->reg[(value & 0x07)],
143 **v);
144 *e_addr = nextword + d->reg[(value & 0x07)];
145
146 } else switch (value) {
147 case 0x18: /* POP / [sp++] */
148 *v = &(d->ram[ d->sp++ ]);
149 TRACE(">> POP [0x%04x] (0x%04x)",
150 d->sp - 1,
151 **v);
152 *e_addr = d->sp - 1;
153 break;
154
155 case 0x19: /* PEEK / [sp] */
156 *v = &(d->ram[ d->sp ]);
157 TRACE(">> PEEK [0x%04x] (0x%04x)",
158 d->sp,
159 **v);
160 *e_addr = d->sp;
161 break;
162
163 case 0x1a: /* PUSH / [--sp] */
164 *v = &(d->ram[ --d->sp ]);
165 TRACE(">> PUSH [0x%04x] (0x%04x)",
166 d->sp + 1,
167 **v);
168 *e_addr = d->sp + 1;
169 break;
170
171 case 0x1b: /* SP */
172 *v = &(d->sp);
173 TRACE(">> SP (0x%04x)",
174 **v);
175 break;
176
177 case 0x1c: /* PC */
178 *v = &(d->pc);
179 TRACE(">> PC (0x%04x)", **v);
180 break;
181
182 case 0x1d: /* O */
183 *v = &(d->o);
184 TRACE(">> O (0x%04x)", **v);
185 break;
186
187 case 0x1e: /* [next word] / [[pc++]] */
188 nextword = d->ram[ d->pc++ ];
189 d->cycle++;
190 *v = &(d->ram[ nextword ]);
191 TRACE(">> [nextword] [0x%04x] (0x%04x)",
192 nextword,
193 **v);
194 *e_addr = nextword;
195 break;
196
197 case 0x1f: /* next word (literal) / [pc++] */
198 nextword = d->ram[ d->pc++ ];
199 d->cycle++;
200 *work_v = nextword;
201 *v = work_v;
202 TRACE(">> nextword (0x%04x)", **v);
203 break;
204
205 default: /* 0x20-0x3f: literal values 0x00-0x1f */
206 *work_v = value & 0x1f;
207 *v = work_v;
208 TRACE(">> literal (0x%04x)", **v);
209 }
210
211 return retval;
212 }
213
214
215 #define OPCODE_NAME_LEN 16
216 struct opcode_entry {
217 unsigned short value;
218 char name[OPCODE_NAME_LEN];
219 void (*impl)(struct dcpu16 *, DCPU16_WORD, DCPU16_WORD);
220 };
221
222 /* messy boilerplate for opcode handlers */
223
224 #define OP_IMPL(x) static void op_##x(struct dcpu16 *d, DCPU16_WORD val_a, DCPU16_WORD val_b)
225
226 #define OP_TYPE(op_type) DCPU16_WORD *a, *b;\
227 unsigned int lit_a;\
228 DCPU16_WORD ev_a_addr = 0, ev_b_addr = 0;\
229 do {\
230 lit_a = value_decode_(d, val_a, &d->reg_work_[0], &a, &ev_a_addr);\
231 op_type;\
232 } while (0)
233 #define OP_NBI_ (void)val_b, (void)b, (void)ev_b_addr
234 #define OP_BASIC_ (void)value_decode_(d, val_b, &d->reg_work_[0], &b, &ev_b_addr)
235 #define OP_BASIC(x) OP_TYPE(OP_BASIC_)
236 #define OP_NBI(x) OP_TYPE(OP_NBI_)
237
238 /*
239 accounting helpers, these fire off the related callbacks for memory reads,
240 memory writes, and execution of reserved instructions
241 */
242 #define ACCT_R(addr) do { acct_event_(d, DCPU16_ACCT_EV_READ, addr); } while (0)
243 #define ACCT_W(addr) do { acct_event_(d, DCPU16_ACCT_EV_WRITE, addr); } while (0)
244 #define ACCT_ILL(addr) do { acct_event_(d, DCPU16_ACCT_EV_NOP, addr); } while (0)
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 DCPU16_WORD future_opcode = (d->ram[d->pc] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_BITS));
260 WARN("reserved future opcode 0x%04x invoked", future_opcode);
261
262 ACCT_ILL(d->pc);
263 }
264
265 OP_IMPL(nbi_jsr) {
266 OP_NBI(nbi_jsr);
267 /* pushes the address of the next instruction to the stack, then sets PC to a */
268
269 ACCT_R(ev_a_addr);
270
271 d->ram[ --d->sp ] = d->pc;
272 d->pc = *a;
273
274 d->cycle += 2;
275
276 ACCT_W(d->sp + 1);
277 }
278
279 OP_IMPL(nbi__reserved2_) {
280 OP_NBI(nbi__reserved2_);
281 /* reserved */
282
283 WARN("reserved nbi opcode invoked");
284
285 ACCT_ILL(d->pc);
286 }
287
288 static const struct opcode_entry opcode_nbi_entries[] = {
289 {0x0, "(reserved)", op_nbi__reserved_},
290 {0x1, "JSR", op_nbi_jsr},
291 {0x2, "(reserved)", op_nbi__reserved2_},
292 {0x0, "", NULL}
293 };
294 #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1)
295
296
297 /* basic opcodes */
298
299 /*
300 N.B. the following function does not decode values, (thus does not advance pc &c)
301 Decoding is handled by the opcode functions it calls.
302 */
303 OP_IMPL(_nbi_) {
304 /* non-basic instruction */
305
306 /* don't do normal value decoding here */
307
308 DCPU16_WORD nbi_opcode = val_a;
309 const struct opcode_entry *e = opcode_nbi_entries;
310
311 e = opcode_nbi_entries + ( (nbi_opcode < OPCODE_NBI_MAX) ? nbi_opcode : (OPCODE_NBI_MAX - 1) );
312
313 assert(e->impl != NULL);
314
315 TRACE(">> %s 0x%04x", e->name, val_b);
316 e->impl(d, val_b, 0);
317 }
318
319 OP_IMPL(set) {
320 OP_BASIC(set);
321 /* sets a to b */
322
323 ACCT_R(ev_b_addr);
324
325 /*
326 if a is a literal, it's aimed at a scratch register,
327 so it's fine to update, as it won't have any effect.
328 */
329 *a = *b;
330
331 d->cycle += 1;
332
333 ACCT_W(ev_a_addr);
334 }
335
336 OP_IMPL(add) {
337 OP_BASIC(add);
338 /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */
339 unsigned int acc = *a + *b;
340
341 ACCT_R(ev_b_addr);
342 ACCT_R(ev_a_addr);
343
344 *a = acc;
345 d->o = (acc > 0xffff);
346
347 d->cycle += 2;
348
349 ACCT_W(ev_a_addr);
350 }
351
352 OP_IMPL(sub) {
353 OP_BASIC(sub);
354 /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
355 unsigned int acc = *a - *b;
356
357 ACCT_R(ev_b_addr);
358 ACCT_R(ev_a_addr);
359
360 *a = acc;
361 d->o = (acc > 0xffff);
362
363 d->cycle += 2;
364
365 ACCT_W(ev_a_addr);
366 }
367
368 OP_IMPL(mul) {
369 OP_BASIC(mul);
370 /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
371 unsigned int acc = *a * *b;
372
373 ACCT_R(ev_b_addr);
374 ACCT_R(ev_a_addr);
375
376 *a = acc;
377 d->o = acc >> 16;
378
379 d->cycle += 2;
380
381 ACCT_W(ev_a_addr);
382 }
383
384 OP_IMPL(div) {
385 OP_BASIC(div);
386 /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
387
388 ACCT_R(ev_b_addr);
389 ACCT_R(ev_a_addr);
390
391 if (*b == 0) {
392 *a = 0;
393 d->o = 0;
394 } else {
395 *a = *a / *b;
396 d->o = (*a << 16) / *b;
397 }
398
399 d->cycle += 3;
400
401 ACCT_W(ev_a_addr);
402 }
403
404 OP_IMPL(mod) {
405 OP_BASIC(mod);
406 /* sets a to a%b. if b==0, sets a to 0 instead. */
407
408 ACCT_R(ev_b_addr);
409 ACCT_R(ev_a_addr);
410
411 if (*b == 0) {
412 *a = 0;
413 } else {
414 *a = *a % *b;
415 }
416
417 d->cycle += 3;
418
419 ACCT_W(ev_a_addr);
420 }
421
422 OP_IMPL(shl) {
423 OP_BASIC(shl);
424 /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
425 unsigned int acc = *a << *b;
426
427 ACCT_R(ev_b_addr);
428 ACCT_R(ev_a_addr);
429
430 *a = acc;
431
432 d->o = acc >> 16;
433
434 d->cycle += 2;
435
436 ACCT_W(ev_a_addr);
437 }
438
439 OP_IMPL(shr) {
440 OP_BASIC(shr);
441 /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
442 unsigned int acc = *a >> *b;
443
444 ACCT_R(ev_b_addr);
445 ACCT_R(ev_a_addr);
446
447 *a = acc;
448 d->o = (*a << 16) >> *b;
449
450 d->cycle += 2;
451
452 ACCT_W(ev_a_addr);
453 }
454
455 OP_IMPL(and) {
456 OP_BASIC(and);
457 /* sets a to a&b */
458
459 ACCT_R(ev_b_addr);
460 ACCT_R(ev_a_addr);
461
462 *a = *a & *b;
463
464 d->cycle += 1;
465
466 ACCT_W(ev_a_addr);
467 }
468
469 OP_IMPL(bor) {
470 OP_BASIC(bor);
471 /* sets a to a|b */
472
473 ACCT_R(ev_b_addr);
474 ACCT_R(ev_a_addr);
475
476 *a = *a | *b;
477
478 d->cycle += 1;
479
480 ACCT_W(ev_a_addr);
481 }
482
483 OP_IMPL(xor) {
484 OP_BASIC(xor);
485 /* sets a to a^b */
486
487 ACCT_R(ev_b_addr);
488 ACCT_R(ev_a_addr);
489
490 *a = *a ^ *b;
491
492 d->cycle += 1;
493
494 ACCT_W(ev_a_addr);
495 }
496
497 OP_IMPL(ife) {
498 OP_BASIC(ife);
499 /* performs next instruction only if a==b */
500
501 ACCT_R(ev_b_addr);
502 ACCT_R(ev_a_addr);
503
504 if (*a == *b) {
505 /* */
506 } else {
507 d->skip_ = 1;
508 d->cycle++;
509 }
510
511 d->cycle += 2;
512 }
513
514 OP_IMPL(ifn) {
515 OP_BASIC(ifn);
516 /* performs next instruction only if a!=b */
517
518 ACCT_R(ev_b_addr);
519 ACCT_R(ev_a_addr);
520
521 if (*a != *b) {
522 /* */
523 } else {
524 d->skip_ = 1;
525 d->cycle++;
526 }
527
528 d->cycle += 2;
529 }
530
531 OP_IMPL(ifg) {
532 OP_BASIC(ifg);
533 /* performs next instruction only if a>b */
534
535 ACCT_R(ev_b_addr);
536 ACCT_R(ev_a_addr);
537
538 if (*a > *b) {
539 /* */
540 } else {
541 d->skip_ = 1;
542 d->cycle++;
543 }
544
545 d->cycle += 2;
546 }
547
548 OP_IMPL(ifb) {
549 OP_BASIC(ifb);
550 /* performs next instruction only if (a&b)!=0 */
551
552 ACCT_R(ev_b_addr);
553 ACCT_R(ev_a_addr);
554
555 if ((*a & *b) != 0) {
556 /* */
557 } else {
558 d->skip_ = 1;
559 d->cycle++;
560 }
561
562 d->cycle += 2;
563 }
564
565 static const struct opcode_entry opcode_basic_entries[] = {
566 {0x0, "(nbi)", op__nbi_},
567 {0x1, "SET", op_set },
568 {0x2, "ADD", op_add },
569 {0x3, "SUB", op_sub },
570 {0x4, "MUL", op_mul },
571 {0x5, "DIV", op_div },
572 {0x6, "MOD", op_mod },
573 {0x7, "SHL", op_shl },
574 {0x8, "SHR", op_shr },
575 {0x9, "AND", op_and },
576 {0xa, "BOR", op_bor },
577 {0xb, "XOR", op_xor },
578 {0xc, "IFE", op_ife },
579 {0xd, "IFN", op_ifn },
580 {0xe, "IFG", op_ifg },
581 {0xf, "IFB", op_ifb },
582 {0x0, "", NULL }
583 };
584
585 static inline
586 void dump_operand_value_(DCPU16_WORD value, DCPU16_WORD nextword) {
587 if (value <= 0x07) {
588 printf(" %c", regnames_[value]);
589 } else if (value <= 0x0f) {
590 printf(" [%c]", regnames_[value & 0x07]);
591 } else if (value <= 0x17) {
592 printf(" [0x%04x + %c]", nextword, regnames_[value & 0x07]);
593 } else switch (value) {
594 case 0x18: printf(" POP"); break;
595 case 0x19: printf(" PEEK"); break;
596 case 0x1a: printf(" PUSH"); break;
597 case 0x1b: printf(" SP"); break;
598 case 0x1c: printf(" PC"); break;
599 case 0x1d: printf(" O"); break;
600 case 0x1e: printf(" [0x%04x]", nextword); break;
601 case 0x1f: printf(" 0x%04x", nextword); break;
602 default: printf(" 0x%02x", value - 0x20);
603 }
604 }
605
606
607 /* split a word into the parts of an instruction, and determine how many words it takes up in total */
608 static inline
609 void instruction_decode_(struct dcpu16 *d, DCPU16_WORD addr, DCPU16_WORD *opcode, DCPU16_WORD *a, DCPU16_WORD *b, DCPU16_WORD *instr_len) {
610 *opcode = d->ram[addr] & ((1 << OPCODE_BASIC_BITS) - 1);
611 *a = (d->ram[addr] >> OPCODE_BASIC_BITS) & ((1 << OPCODE_OPERAND_BITS) - 1);
612 *b = (d->ram[addr] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_BITS)) & ((1 << OPCODE_OPERAND_BITS) - 1);
613 if (instr_len) {
614 *instr_len = 1;
615 /* both basic and nbi opcodes use their b operand */
616 if ( (*b >= 0x10 && *b <= 0x17) || *b == 0x1e || *b == 0x1f )
617 *instr_len += 1;
618 /* but only basic uses a */
619 if (*opcode
620 && ((*a >= 0x10 && *a <= 0x17) || *a == 0x1e || *a == 0x1f) )
621 *instr_len += 1;
622 }
623 }
624
625 /* dcpu16_disassemble_print
626 print the words of the instruction at addr, followed by its assembly representation
627 returns the length of the instruction in words
628 */
629 DCPU16_WORD dcpu16_disassemble_print(struct dcpu16 *d, DCPU16_WORD addr) {
630 DCPU16_WORD opcode, a, b, instr_len, i;
631 const struct opcode_entry *e;
632 unsigned int indent = 0;
633 unsigned int partial = 0;
634
635 if (!d) return 0;
636
637 /*
638 Check the previous instruction, to see if this one should be
639 indented. This check isn't foolproof, as preceeding addresses
640 could be data which happen to match instructions..
641 */
642 for (i = 3; i; i--) {
643 instruction_decode_(d, addr - i, &opcode, &a, &b, &instr_len);
644 if (instr_len > i)
645 partial++;
646 if (instr_len == i && opcode >= 0xc) {
647 indent++;
648 break;
649 }
650 }
651
652 /* now get what we're really interested in */
653 instruction_decode_(d, addr, &opcode, &a, &b, &instr_len);
654
655 if (opcode)
656 e = opcode_basic_entries + opcode;
657 else
658 e = opcode_nbi_entries + ( (a < OPCODE_NBI_MAX) ? a : (OPCODE_NBI_MAX - 1) );
659
660 /* show the raw words */
661 printf("%04x", d->ram[addr]);
662 for (i = 1; i < instr_len; i++) {
663 printf(" %04x", d->ram[addr + i]);
664 }
665
666 /* align things neatly, show the instruction */
667 printf("%s%s ;%s%s%s",
668 instr_len < 3 ? " " : "",
669 instr_len < 2 ? " " : "",
670 partial ? "*" : " ",
671 indent ? " " : "",
672 e->name);
673
674 /* show the operands */
675 i = 0;
676 if (opcode) {
677 dump_operand_value_(a, d->ram[addr + 1]);
678 if ((a >= 0x10 && a <= 0x17) || a == 0x1e || a == 0x1f)
679 addr++;
680 printf(",");
681 }
682
683 if (opcode || a)
684 dump_operand_value_(b, d->ram[addr + 1]);
685
686 return instr_len;
687 }
688
689 /* execute the next instruction */
690 void dcpu16_step(struct dcpu16 *d) {
691 DCPU16_WORD opcode, a, b, instr_len;
692 const struct opcode_entry *e;
693
694 if (!d) return;
695
696 acct_event_(d, DCPU16_ACCT_EV_CYCLE, d->pc);
697
698 /*
699 PC is advanced while decoding the operands by the opcode functions.
700 Things like this could be organized a little better..
701 */
702 instruction_decode_(d, d->pc, &opcode, &a, &b, NULL);
703
704 d->pc++; /* all instructions take at least one word */
705
706 for (e = opcode_basic_entries; e->impl; e++) {
707 if (e->value == opcode) {
708 TRACE(">> %s 0x%04x, 0x%04x", e->name, a, b);
709 e->impl(d, a, b);
710 break;
711 }
712 }
713
714 /* and jump over next instr if needed */
715 if (d->skip_) {
716 instruction_decode_(d, d->pc, &opcode, &a, &b, &instr_len);
717 d->pc += instr_len;
718 d->skip_ = 0;
719 TRACE("++ SKIPPED %x words", instr_len);
720 }
721 }
722
723 /*
724 print the current state of the machine
725 shows current cycle count, registers, and next instruction
726 */
727 void dcpu16_state_print(struct dcpu16 *d) {
728 unsigned int i;
729
730 if (!d) return;
731
732 printf(" ");
733 for (i = 0; i < 8; i++)
734 printf(" %c:0x%04x", regnames_[i], d->reg[i]);
735 printf("\n");
736
737 printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
738 d->cycle,
739 "O", d->o,
740 "SP", d->sp,
741 "PC", d->pc,
742 "PC");
743
744 dcpu16_disassemble_print(d, d->pc);
745 printf("\n");
746 }
747
748 /* dcpu16_dump_ram
749 * print raw ram contents from start to stop
750 */
751 void dcpu16_dump_ram(struct dcpu16 *d, DCPU16_WORD start, DCPU16_WORD end) {
752 unsigned int i, j;
753 const unsigned int n = 8; /* words per line */
754
755 if (!d) return;
756
757 for (i = start, j = 0; i <= end; i++, j++) {
758 if (j % n == 0)
759 printf("0x%04x:\t", i);
760 printf(" %04x%s", d->ram[i], (j % n) == (n - 1) ? "\n" : "");
761 }
762 if ((j % n) != (n - 1))
763 printf("\n");
764 }
765
766 /* dcpu16_acct_add
767 * Register callback fn to be triggered whenever event matching any events
768 * in bitwise mask occur.
769 */
770 int dcpu16_acct_add(struct dcpu16 *vm, dcpu16_acct_event mask, dcpu16_ev_cb_t *fn, void *data) {
771 struct dcpu16_acct_cb cb;
772
773 cb.mask = mask;
774 cb.fn = fn;
775 cb.data = data;
776
777 if (vm->cb_table_entries_ == vm->cb_table_allocated_) {
778 size_t new_entries = vm->cb_table_allocated_ + 32;
779 void *tmp_ptr = realloc(vm->cb_table_, new_entries * sizeof *(vm->cb_table_));
780 if (tmp_ptr == NULL) {
781 fprintf(stderr, "%s():%s", "realloc", strerror(errno));
782 return -1;
783 }
784 vm->cb_table_ = tmp_ptr;
785 vm->cb_table_allocated_ += 32;
786 }
787
788 memcpy(vm->cb_table_ + vm->cb_table_entries_, &cb, sizeof cb);
789 vm->cb_table_entries_++;
790
791 return 0;
792 }
793
794 /* dcpu16_reset
795 * signals cpu to reset, clearing runstate and ram, then reload any init callbacks
796 */
797 void dcpu16_reset(struct dcpu16 *d) {
798 if (!d) return;
799
800 d->cycle = 0;
801 memset(d->reg, 0, sizeof d->reg);
802 d->pc = 0;
803 d->sp = 0;
804 d->o = 0;
805 d->skip_ = 0;
806 memset(d->ram, 0, sizeof d->ram);
807
808 acct_event_(d, DCPU16_ACCT_EV_RESET, 0);
809 }
810
811 /* dcpu16_new
812 * allocate a new dcpu16 instance
813 */
814 struct dcpu16 *dcpu16_new(void) {
815 struct dcpu16 *vm;
816
817 vm = calloc(1, sizeof *vm);
818 if (vm == NULL)
819 WARN("%s: %s(%zu): %s", __func__, "calloc", strerror(errno));
820
821 return vm;
822 }
823
824 /* dcpu16_delete
825 * release a dcpu16 instance
826 */
827 void dcpu16_delete(struct dcpu16 **vm) {
828 if (!vm || !*vm) return;
829
830 free(*vm);
831 *vm = NULL;
832 }