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