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 * drop checks for assigning to literals -- it won't affect anything anyhow
29 * refactor opcode functiontables into switch statements
32 static const char * const src_id_
= "$Id$";
34 #define OPCODE_BASIC_BITS 4
35 #define OPCODE_OPERAND_BITS 6
37 static const char regnames_
[] = "ABCXYZIJ";
39 /* some default warning and debug reporting functions, which can be overridden by clients */
40 #define WARN(...) do { if (warn_cb_) warn_cb_(__VA_ARGS__); } while (0)
41 static inline void warn_(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
43 void warn_(char *fmt
, ...) {
46 fprintf(stderr
, "[warning] ");
48 vfprintf(stderr
, fmt
, ap
);
50 fprintf(stderr
, "\n");
53 static void (*warn_cb_
)(char *fmt
, ...) = warn_
;
54 void dcpu16_warn_cb_set(void (*fn
)(char *fmt
, ...)) {
59 #define TRACE(...) do { if (trace_cb_) trace_cb_(__VA_ARGS__); } while (0)
60 static inline void trace_(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
62 void trace_(char *fmt
, ...) {
65 fprintf(stdout
, "[debug] ");
67 vfprintf(stdout
, fmt
, ap
);
69 fprintf(stdout
, "\n");
73 #define TRACE(...) do {} while(0)
75 static void (*trace_cb_
)(char *fmt
, ...) =
82 void dcpu16_trace_cb_set(void (*fn
)(char *fmt
, ...)) {
88 * invokes callbacks for specified event
91 void acct_event_(struct dcpu16
*vm
, dcpu16_acct_event_ ev
, DCPU16_WORD addr
) {
92 struct dcpu16_acct_cb
*cb
= vm
->cb_table_
;
95 for (i
= 0; i
< vm
->cb_table_entries_
; i
++) {
96 if ( (cb
[i
].mask
& ev
) )
103 * sets *v to be the destination of the value
104 * advances d->pc if necessary
105 * workv is buffer to use to accumulate literal value before use, one exists for either potential instruction operand
106 * e_addr is for accounting callback
107 * returns true if destination points to literal (id est *v should ignore writes)
110 unsigned int value_decode_(struct dcpu16
*d
, DCPU16_WORD value
, DCPU16_WORD
*work_v
, DCPU16_WORD
**v
, DCPU16_WORD
*e_addr
) {
111 DCPU16_WORD nextword
;
112 unsigned int retval
= 0;
114 assert(value
<= 0x3f);
116 /* does this value indicate a literal */
120 if (value
<= 0x07) { /* register */
122 TRACE(">> %c (0x%04x)",
126 } else if (value
<= 0x0f) { /* [register] */
127 *v
= &(d
->ram
[ d
->reg
[(value
& 0x07)] ]);
128 TRACE(">> [%c] [0x%04x] (0x%04x)",
129 regnames_
[value
&0x07],
132 *e_addr
= d
->reg
[(value
& 0x07)];
134 } else if (value
<= 0x17) { /* [next word + register] */
135 nextword
= d
->ram
[ d
->pc
++ ];
137 *v
= &(d
->ram
[ nextword
+ d
->reg
[(value
& 0x07)] ]);
138 TRACE(">> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)",
139 regnames_
[(value
& 0x07)],
141 d
->reg
[(value
& 0x07)],
143 *e_addr
= nextword
+ d
->reg
[(value
& 0x07)];
145 } else switch (value
) {
146 case 0x18: /* POP / [sp++] */
147 *v
= &(d
->ram
[ d
->sp
++ ]);
148 TRACE(">> POP [0x%04x] (0x%04x)",
154 case 0x19: /* PEEK / [sp] */
155 *v
= &(d
->ram
[ d
->sp
]);
156 TRACE(">> PEEK [0x%04x] (0x%04x)",
162 case 0x1a: /* PUSH / [--sp] */
163 *v
= &(d
->ram
[ --d
->sp
]);
164 TRACE(">> PUSH [0x%04x] (0x%04x)",
172 TRACE(">> SP (0x%04x)",
178 TRACE(">> PC (0x%04x)", **v
);
183 TRACE(">> O (0x%04x)", **v
);
186 case 0x1e: /* [next word] / [[pc++]] */
187 nextword
= d
->ram
[ d
->pc
++ ];
189 *v
= &(d
->ram
[ nextword
]);
190 TRACE(">> [nextword] [0x%04x] (0x%04x)",
196 case 0x1f: /* next word (literal) / [pc++] */
197 nextword
= d
->ram
[ d
->pc
++ ];
201 TRACE(">> nextword (0x%04x)", **v
);
204 default: /* 0x20-0x3f: literal values 0x00-0x1f */
205 *work_v
= value
& 0x1f;
207 TRACE(">> literal (0x%04x)", **v
);
214 #define OPCODE_NAME_LEN 16
215 struct opcode_entry
{
216 unsigned short value
;
217 char name
[OPCODE_NAME_LEN
];
218 void (*impl
)(struct dcpu16
*, DCPU16_WORD
, DCPU16_WORD
);
221 /* messy boilerplate for opcode handlers */
223 #define OP_IMPL(x) static void op_##x(struct dcpu16 *d, DCPU16_WORD val_a, DCPU16_WORD val_b)
225 #define OP_TYPE(op_type) DCPU16_WORD *a, *b;\
227 DCPU16_WORD ev_a_addr = 0, ev_b_addr = 0;\
229 lit_a = value_decode_(d, val_a, &d->reg_work_[0], &a, &ev_a_addr);\
232 #define OP_NBI_ (void)val_b, (void)b, (void)ev_b_addr
233 #define OP_BASIC_ (void)value_decode_(d, val_b, &d->reg_work_[0], &b, &ev_b_addr)
234 #define OP_BASIC(x) OP_TYPE(OP_BASIC_)
235 #define OP_NBI(x) OP_TYPE(OP_NBI_)
238 accounting helpers, these fire off the related callbacks for memory reads,
239 memory writes, and execution of reserved instructions
241 #define ACCT_R(addr) do { acct_event_(d, DCPU16_ACCT_EV_READ, addr); } while (0)
242 #define ACCT_W(addr) do { acct_event_(d, DCPU16_ACCT_EV_WRITE, addr); } while (0)
243 #define ACCT_ILL(addr) do { acct_event_(d, DCPU16_ACCT_EV_NOP, addr); } while (0)
245 /* extended opcodes */
248 N.B. this next function currently decodes values -- id est, it is
249 an opcode processing terminus; however, if 'future instruction set
250 expansion' happens, this will probably need to behave more like
251 the OP_IMPL(_nbi_) function which invoked it, if those instructions
252 have zero or differently styled operands.
254 OP_IMPL(nbi__reserved_
) {
255 OP_NBI(nbi__reserved_
);
256 /* reserved for future expansion */
258 DCPU16_WORD future_opcode
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_BITS
+ OPCODE_OPERAND_BITS
));
259 WARN("reserved future opcode 0x%04x invoked", future_opcode
);
266 /* pushes the address of the next instruction to the stack, then sets PC to a */
270 d
->ram
[ --d
->sp
] = d
->pc
;
278 OP_IMPL(nbi__reserved2_
) {
279 OP_NBI(nbi__reserved2_
);
282 WARN("reserved nbi opcode invoked");
287 static const struct opcode_entry opcode_nbi_entries
[] = {
288 {0x0, "(reserved)", op_nbi__reserved_
},
289 {0x1, "JSR", op_nbi_jsr
},
290 {0x2, "(reserved)", op_nbi__reserved2_
},
293 #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1)
299 N.B. the following function does not decode values, as the nbi
300 instructions only have one operand.
303 /* non-basic instruction */
305 /* don't do normal value decoding here */
307 DCPU16_WORD nbi_opcode
= val_a
;
308 const struct opcode_entry
*e
= opcode_nbi_entries
;
310 e
= opcode_nbi_entries
+ ( (nbi_opcode
< OPCODE_NBI_MAX
) ? nbi_opcode
: (OPCODE_NBI_MAX
- 1) );
312 assert(e
->impl
!= NULL
);
314 TRACE(">> %s 0x%04x", e
->name
, val_b
);
315 e
->impl(d
, val_b
, 0);
325 /* only set non-literal target */
335 /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */
336 unsigned int acc
= *a
+ *b
;
344 d
->o
= (acc
> 0xffff);
353 /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
354 unsigned int acc
= *a
- *b
;
362 d
->o
= (acc
> 0xffff);
370 /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
371 unsigned int acc
= *a
* *b
;
387 /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
398 unsigned int acc
= *a
/ *b
;
404 acc
= (*a
<< 16) / *b
;
415 /* sets a to a%b. if b==0, sets a to 0 instead. */
437 /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
438 unsigned int acc
= *a
<< *b
;
455 /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
456 unsigned int acc
= *a
>> *b
;
464 d
->o
= (*a
<< 16) >> *b
;
521 /* performs next instruction only if a==b */
538 /* performs next instruction only if a!=b */
555 /* performs next instruction only if a>b */
572 /* performs next instruction only if (a&b)!=0 */
577 if ((*a
& *b
) != 0) {
587 static const struct opcode_entry opcode_basic_entries
[] = {
588 {0x0, "(nbi)", op__nbi_
},
589 {0x1, "SET", op_set
},
590 {0x2, "ADD", op_add
},
591 {0x3, "SUB", op_sub
},
592 {0x4, "MUL", op_mul
},
593 {0x5, "DIV", op_div
},
594 {0x6, "MOD", op_mod
},
595 {0x7, "SHL", op_shl
},
596 {0x8, "SHR", op_shr
},
597 {0x9, "AND", op_and
},
598 {0xa, "BOR", op_bor
},
599 {0xb, "XOR", op_xor
},
600 {0xc, "IFE", op_ife
},
601 {0xd, "IFN", op_ifn
},
602 {0xe, "IFG", op_ifg
},
603 {0xf, "IFB", op_ifb
},
608 void dump_operand_value_(DCPU16_WORD value
, DCPU16_WORD nextword
) {
610 printf(" %c", regnames_
[value
]);
611 } else if (value
<= 0x0f) {
612 printf(" [%c]", regnames_
[value
& 0x07]);
613 } else if (value
<= 0x17) {
614 printf(" [0x%04x + %c]", nextword
, regnames_
[value
& 0x07]);
615 } else switch (value
) {
616 case 0x18: printf(" POP"); break;
617 case 0x19: printf(" PEEK"); break;
618 case 0x1a: printf(" PUSH"); break;
619 case 0x1b: printf(" SP"); break;
620 case 0x1c: printf(" PC"); break;
621 case 0x1d: printf(" O"); break;
622 case 0x1e: printf(" [0x%04x]", nextword
); break;
623 case 0x1f: printf(" 0x%04x", nextword
); break;
624 default: printf(" 0x%02x", value
- 0x20);
629 /* split a word into the parts of an instruction, and determine how many words it takes up in total */
631 void instruction_decode_(struct dcpu16
*d
, DCPU16_WORD addr
, DCPU16_WORD
*opcode
, DCPU16_WORD
*a
, DCPU16_WORD
*b
, DCPU16_WORD
*instr_len
) {
632 *opcode
= d
->ram
[addr
] & ((1 << OPCODE_BASIC_BITS
) - 1);
633 *a
= (d
->ram
[addr
] >> OPCODE_BASIC_BITS
) & ((1 << OPCODE_OPERAND_BITS
) - 1);
634 *b
= (d
->ram
[addr
] >> (OPCODE_BASIC_BITS
+ OPCODE_OPERAND_BITS
)) & ((1 << OPCODE_OPERAND_BITS
) - 1);
637 /* both basic and nbi opcodes use their b operand */
638 if ( (*b
>= 0x10 && *b
<= 0x17) || *b
== 0x1e || *b
== 0x1f )
640 /* but only basic uses a */
642 && ((*a
>= 0x10 && *a
<= 0x17) || *a
== 0x1e || *a
== 0x1f) )
647 /* dcpu16_disassemble_print
648 print the words of the instruction at addr, followed by its assembly representation
649 returns the length of the instruction in words
651 DCPU16_WORD
dcpu16_disassemble_print(struct dcpu16
*d
, DCPU16_WORD addr
) {
652 DCPU16_WORD opcode
, a
, b
, instr_len
, i
;
653 const struct opcode_entry
*e
;
654 unsigned int indent
= 0;
655 unsigned int partial
= 0;
660 Check the previous instruction, to see if this one should be
661 indented. This check isn't foolproof, as preceeding addresses
662 could be data which happen to match instructions..
664 for (i
= 3; i
; i
--) {
665 instruction_decode_(d
, addr
- i
, &opcode
, &a
, &b
, &instr_len
);
668 if (instr_len
== i
&& opcode
>= 0xc) {
674 /* now get what we're really interested in */
675 instruction_decode_(d
, addr
, &opcode
, &a
, &b
, &instr_len
);
678 e
= opcode_basic_entries
+ opcode
;
680 e
= opcode_nbi_entries
+ ( (a
< OPCODE_NBI_MAX
) ? a
: (OPCODE_NBI_MAX
- 1) );
682 /* show the raw words */
683 printf("%04x", d
->ram
[addr
]);
684 for (i
= 1; i
< instr_len
; i
++) {
685 printf(" %04x", d
->ram
[addr
+ i
]);
688 /* align things neatly, show the instruction */
689 printf("%s%s ;%s%s%s",
690 instr_len
< 3 ? " " : "",
691 instr_len
< 2 ? " " : "",
696 /* show the operands */
699 dump_operand_value_(a
, d
->ram
[addr
+ 1]);
700 if ((a
>= 0x10 && a
<= 0x17) || a
== 0x1e || a
== 0x1f)
706 dump_operand_value_(b
, d
->ram
[addr
+ 1]);
711 /* execute the next instruction */
712 void dcpu16_step(struct dcpu16
*d
) {
713 DCPU16_WORD opcode
, a
, b
, instr_len
;
714 const struct opcode_entry
*e
;
719 PC is advanced while decoding the operands by the opcode functions.
720 Things like this could be organized a little better..
722 instruction_decode_(d
, d
->pc
, &opcode
, &a
, &b
, NULL
);
724 d
->pc
++; /* all instructions take at least one word */
726 for (e
= opcode_basic_entries
; e
->impl
; e
++) {
727 if (e
->value
== opcode
) {
728 TRACE(">> %s 0x%04x, 0x%04x", e
->name
, a
, b
);
734 /* and jump over next instr if needed */
736 instruction_decode_(d
, d
->pc
, &opcode
, &a
, &b
, &instr_len
);
739 TRACE("++ SKIPPED %x words", instr_len
);
744 print the current state of the machine
745 shows current cycle count, registers, and next instruction
747 void dcpu16_state_print(struct dcpu16
*d
) {
753 for (i
= 0; i
< 8; i
++)
754 printf(" %c:0x%04x", regnames_
[i
], d
->reg
[i
]);
757 printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
764 dcpu16_disassemble_print(d
, d
->pc
);
769 * print raw ram contents from start to stop
771 void dcpu16_dump_ram(struct dcpu16
*d
, DCPU16_WORD start
, DCPU16_WORD end
) {
773 const unsigned int n
= 8; /* words per line */
777 for (i
= start
, j
= 0; i
<= end
; i
++, j
++) {
779 printf("0x%04x:\t", i
);
780 printf(" %04x%s", d
->ram
[i
], (j
% n
) == (n
- 1) ? "\n" : "");
782 if ((j
% n
) != (n
- 1))
787 * Register callback fn to be triggered whenever event matching any events
788 * in bitwise mask occur.
790 int dcpu16_acct_add(struct dcpu16
*vm
, dcpu16_acct_event_ mask
, void (*fn
)(dcpu16_acct_event_
, DCPU16_WORD
)) {
791 struct dcpu16_acct_cb cb
;
796 if (vm
->cb_table_entries_
== vm
->cb_table_allocated_
) {
797 size_t new_entries
= vm
->cb_table_allocated_
+ 32;
798 void *tmp_ptr
= realloc(vm
->cb_table_
, new_entries
* sizeof *(vm
->cb_table_
));
799 if (tmp_ptr
== NULL
) {
800 fprintf(stderr
, "%s():%s", "realloc", strerror(errno
));
803 vm
->cb_table_
= tmp_ptr
;
804 vm
->cb_table_allocated_
+= 32;
807 memcpy(vm
->cb_table_
+ vm
->cb_table_entries_
, &cb
, sizeof cb
);
808 vm
->cb_table_entries_
++;
814 * resets a dcpu16 instance to initial state
816 void dcpu16_reset(struct dcpu16
*d
) {
819 memset(d
, 0, sizeof *d
);
823 * allocate a new dcpu16 instance
825 struct dcpu16
*dcpu16_new(void) {
828 vm
= calloc(1, sizeof *vm
);
830 WARN("%s: %s(%zu): %s", __func__
, "calloc", strerror(errno
));
836 * release a dcpu16 instance
838 void dcpu16_delete(struct dcpu16
**vm
) {
839 if (!vm
|| !*vm
) return;