13 * emulates the DCPU16 system from http://0x10c.com/doc/dcpu-16.txt
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.
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
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
33 static const char * const src_id_
= "$Id$";
35 #define OPCODE_BASIC_BITS 4
36 #define OPCODE_OPERAND_BITS 6
38 static const char * const regnames_
= "ABCXYZIJ";
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)));
44 void warn_(char *fmt
, ...) {
47 fprintf(stderr
, "[warning] ");
49 vfprintf(stderr
, fmt
, ap
);
51 fprintf(stderr
, "\n");
54 static void (*warn_cb_
)(char *fmt
, ...) = warn_
;
55 void dcpu16_warn_cb_set(void (*fn
)(char *fmt
, ...)) {
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)));
63 void trace_(char *fmt
, ...) {
66 fprintf(stdout
, "[debug] ");
68 vfprintf(stdout
, fmt
, ap
);
70 fprintf(stdout
, "\n");
74 #define TRACE(...) do {} while(0)
76 static void (*trace_cb_
)(char *fmt
, ...) =
83 void dcpu16_trace_cb_set(void (*fn
)(char *fmt
, ...)) {
89 * invokes callbacks for specified event
92 void acct_event_(struct dcpu16
*vm
, dcpu16_acct_event ev
, DCPU16_WORD addr
) {
93 struct dcpu16_acct_cb
*cb
= vm
->cb_table_
;
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
);
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)
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;
115 assert(value
<= 0x3f);
117 /* does this value indicate a literal */
121 if (value
<= 0x07) { /* register */
123 TRACE(">> %c (0x%04x)",
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],
133 *e_addr
= d
->reg
[(value
& 0x07)];
135 } else if (value
<= 0x17) { /* [next word + register] */
136 nextword
= d
->ram
[ d
->pc
++ ];
138 *v
= &(d
->ram
[ nextword
+ d
->reg
[(value
& 0x07)] ]);
139 TRACE(">> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)",
140 regnames_
[(value
& 0x07)],
142 d
->reg
[(value
& 0x07)],
144 *e_addr
= nextword
+ d
->reg
[(value
& 0x07)];
146 } else switch (value
) {
147 case 0x18: /* POP / [sp++] */
148 *v
= &(d
->ram
[ d
->sp
++ ]);
149 TRACE(">> POP [0x%04x] (0x%04x)",
155 case 0x19: /* PEEK / [sp] */
156 *v
= &(d
->ram
[ d
->sp
]);
157 TRACE(">> PEEK [0x%04x] (0x%04x)",
163 case 0x1a: /* PUSH / [--sp] */
164 *v
= &(d
->ram
[ --d
->sp
]);
165 TRACE(">> PUSH [0x%04x] (0x%04x)",
173 TRACE(">> SP (0x%04x)",
179 TRACE(">> PC (0x%04x)", **v
);
184 TRACE(">> O (0x%04x)", **v
);
187 case 0x1e: /* [next word] / [[pc++]] */
188 nextword
= d
->ram
[ d
->pc
++ ];
190 *v
= &(d
->ram
[ nextword
]);
191 TRACE(">> [nextword] [0x%04x] (0x%04x)",
197 case 0x1f: /* next word (literal) / [pc++] */
198 nextword
= d
->ram
[ d
->pc
++ ];
202 TRACE(">> nextword (0x%04x)", **v
);
205 default: /* 0x20-0x3f: literal values 0x00-0x1f */
206 *work_v
= value
& 0x1f;
208 TRACE(">> literal (0x%04x)", **v
);
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
);
222 /* messy boilerplate for opcode handlers */
224 #define OP_IMPL(x) static void op_##x(struct dcpu16 *d, DCPU16_WORD val_a, DCPU16_WORD val_b)
226 #define OP_TYPE(op_type) DCPU16_WORD *a, *b;\
228 DCPU16_WORD ev_a_addr = 0, ev_b_addr = 0;\
230 lit_a = value_decode_(d, val_a, &d->reg_work_[0], &a, &ev_a_addr);\
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_)
239 accounting helpers, these fire off the related callbacks for memory reads,
240 memory writes, and execution of reserved instructions
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)
246 /* extended opcodes */
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.
255 OP_IMPL(nbi__reserved_
) {
256 OP_NBI(nbi__reserved_
);
257 /* reserved for future expansion */
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
);
267 /* pushes the address of the next instruction to the stack, then sets PC to a */
271 d
->ram
[ --d
->sp
] = d
->pc
;
279 OP_IMPL(nbi__reserved2_
) {
280 OP_NBI(nbi__reserved2_
);
283 WARN("reserved nbi opcode invoked");
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_
},
294 #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1)
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.
304 /* non-basic instruction */
306 /* don't do normal value decoding here */
308 DCPU16_WORD nbi_opcode
= val_a
;
309 const struct opcode_entry
*e
= opcode_nbi_entries
;
311 e
= opcode_nbi_entries
+ ( (nbi_opcode
< OPCODE_NBI_MAX
) ? nbi_opcode
: (OPCODE_NBI_MAX
- 1) );
313 assert(e
->impl
!= NULL
);
315 TRACE(">> %s 0x%04x", e
->name
, val_b
);
316 e
->impl(d
, val_b
, 0);
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.
338 /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */
339 unsigned int acc
= *a
+ *b
;
345 d
->o
= (acc
> 0xffff);
354 /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
355 unsigned int acc
= *a
- *b
;
361 d
->o
= (acc
> 0xffff);
370 /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
371 unsigned int acc
= *a
* *b
;
386 /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
396 d
->o
= (*a
<< 16) / *b
;
406 /* sets a to a%b. if b==0, sets a to 0 instead. */
424 /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
425 unsigned int acc
= *a
<< *b
;
441 /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
442 unsigned int acc
= *a
>> *b
;
448 d
->o
= (*a
<< 16) >> *b
;
499 /* performs next instruction only if a==b */
516 /* performs next instruction only if a!=b */
533 /* performs next instruction only if a>b */
550 /* performs next instruction only if (a&b)!=0 */
555 if ((*a
& *b
) != 0) {
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
},
586 void dump_operand_value_(DCPU16_WORD value
, DCPU16_WORD nextword
) {
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);
607 /* split a word into the parts of an instruction, and determine how many words it takes up in total */
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);
615 /* both basic and nbi opcodes use their b operand */
616 if ( (*b
>= 0x10 && *b
<= 0x17) || *b
== 0x1e || *b
== 0x1f )
618 /* but only basic uses a */
620 && ((*a
>= 0x10 && *a
<= 0x17) || *a
== 0x1e || *a
== 0x1f) )
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
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;
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..
642 for (i
= 3; i
; i
--) {
643 instruction_decode_(d
, addr
- i
, &opcode
, &a
, &b
, &instr_len
);
646 if (instr_len
== i
&& opcode
>= 0xc) {
652 /* now get what we're really interested in */
653 instruction_decode_(d
, addr
, &opcode
, &a
, &b
, &instr_len
);
656 e
= opcode_basic_entries
+ opcode
;
658 e
= opcode_nbi_entries
+ ( (a
< OPCODE_NBI_MAX
) ? a
: (OPCODE_NBI_MAX
- 1) );
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
]);
666 /* align things neatly, show the instruction */
667 printf("%s%s ;%s%s%s",
668 instr_len
< 3 ? " " : "",
669 instr_len
< 2 ? " " : "",
674 /* show the operands */
677 dump_operand_value_(a
, d
->ram
[addr
+ 1]);
678 if ((a
>= 0x10 && a
<= 0x17) || a
== 0x1e || a
== 0x1f)
684 dump_operand_value_(b
, d
->ram
[addr
+ 1]);
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
;
696 acct_event_(d
, DCPU16_ACCT_EV_CYCLE
, d
->pc
);
699 PC is advanced while decoding the operands by the opcode functions.
700 Things like this could be organized a little better..
702 instruction_decode_(d
, d
->pc
, &opcode
, &a
, &b
, NULL
);
704 d
->pc
++; /* all instructions take at least one word */
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
);
714 /* and jump over next instr if needed */
716 instruction_decode_(d
, d
->pc
, &opcode
, &a
, &b
, &instr_len
);
719 TRACE("++ SKIPPED %x words", instr_len
);
724 print the current state of the machine
725 shows current cycle count, registers, and next instruction
727 void dcpu16_state_print(struct dcpu16
*d
) {
733 for (i
= 0; i
< 8; i
++)
734 printf(" %c:0x%04x", regnames_
[i
], d
->reg
[i
]);
737 printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
744 dcpu16_disassemble_print(d
, d
->pc
);
749 * print raw ram contents from start to stop
751 void dcpu16_dump_ram(struct dcpu16
*d
, DCPU16_WORD start
, DCPU16_WORD end
) {
753 const unsigned int n
= 8; /* words per line */
757 for (i
= start
, j
= 0; i
<= end
; i
++, j
++) {
759 printf("0x%04x:\t", i
);
760 printf(" %04x%s", d
->ram
[i
], (j
% n
) == (n
- 1) ? "\n" : "");
762 if ((j
% n
) != (n
- 1))
767 * Register callback fn to be triggered whenever event matching any events
768 * in bitwise mask occur.
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
;
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
));
784 vm
->cb_table_
= tmp_ptr
;
785 vm
->cb_table_allocated_
+= 32;
788 memcpy(vm
->cb_table_
+ vm
->cb_table_entries_
, &cb
, sizeof cb
);
789 vm
->cb_table_entries_
++;
795 * signals cpu to reset, clearing runstate and ram, then reload any init callbacks
797 void dcpu16_reset(struct dcpu16
*d
) {
801 memset(d
->reg
, 0, sizeof d
->reg
);
806 memset(d
->ram
, 0, sizeof d
->ram
);
808 acct_event_(d
, DCPU16_ACCT_EV_RESET
, 0);
812 * allocate a new dcpu16 instance
814 struct dcpu16
*dcpu16_new(void) {
817 vm
= calloc(1, sizeof *vm
);
819 WARN("%s: %s(%zu): %s", __func__
, "calloc", strerror(errno
));
825 * release a dcpu16 instance
827 void dcpu16_delete(struct dcpu16
**vm
) {
828 if (!vm
|| !*vm
) return;