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 we're skipping this instruction, just advance the pc if needed */
122 TRACE(">> SKIP decode");
123 if ((value
>= 0x10 && value
<= 0x17) || value
== 0x1e || value
== 0x1f)
128 if (value
<= 0x07) { /* register */
130 TRACE(">> %c (0x%04x)",
134 } else if (value
<= 0x0f) { /* [register] */
135 *v
= &(d
->ram
[ d
->reg
[(value
& 0x07)] ]);
136 TRACE(">> [%c] [0x%04x] (0x%04x)",
137 regnames_
[value
&0x07],
140 *e_addr
= d
->reg
[(value
& 0x07)];
142 } else if (value
<= 0x17) { /* [next word + register] */
143 nextword
= d
->ram
[ d
->pc
++ ];
145 *v
= &(d
->ram
[ nextword
+ d
->reg
[(value
& 0x07)] ]);
146 TRACE(">> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)",
147 regnames_
[(value
& 0x07)],
149 d
->reg
[(value
& 0x07)],
151 *e_addr
= nextword
+ d
->reg
[(value
& 0x07)];
153 } else switch (value
) {
154 case 0x18: /* POP / [sp++] */
155 *v
= &(d
->ram
[ d
->sp
++ ]);
156 TRACE(">> POP [0x%04x] (0x%04x)",
162 case 0x19: /* PEEK / [sp] */
163 *v
= &(d
->ram
[ d
->sp
]);
164 TRACE(">> PEEK [0x%04x] (0x%04x)",
170 case 0x1a: /* PUSH / [--sp] */
171 *v
= &(d
->ram
[ --d
->sp
]);
172 TRACE(">> PUSH [0x%04x] (0x%04x)",
180 TRACE(">> SP (0x%04x)",
186 TRACE(">> PC (0x%04x)", **v
);
191 TRACE(">> O (0x%04x)", **v
);
194 case 0x1e: /* [next word] / [[pc++]] */
195 nextword
= d
->ram
[ d
->pc
++ ];
197 *v
= &(d
->ram
[ nextword
]);
198 TRACE(">> [nextword] [0x%04x] (0x%04x)",
204 case 0x1f: /* next word (literal) / [pc++] */
205 nextword
= d
->ram
[ d
->pc
++ ];
209 TRACE(">> nextword (0x%04x)", **v
);
212 default: /* 0x20-0x3f: literal values 0x00-0x1f */
213 *work_v
= value
& 0x1f;
215 TRACE(">> literal (0x%04x)", **v
);
222 #define OPCODE_NAME_LEN 16
223 struct opcode_entry
{
224 unsigned short value
;
225 char name
[OPCODE_NAME_LEN
];
226 void (*impl
)(struct dcpu16
*, DCPU16_WORD
, DCPU16_WORD
);
229 /* messy boilerplate for opcode handlers */
231 #define OP_IMPL(x) static void op_##x(struct dcpu16 *d, DCPU16_WORD val_a, DCPU16_WORD val_b)
233 #define OP_TYPE(op_type) DCPU16_WORD *a, *b;\
235 DCPU16_WORD ev_a_addr = 0, ev_b_addr = 0;\
237 lit_a = value_decode_(d, val_a, &d->reg_work_[0], &a, &ev_a_addr);\
240 #define OP_NBI_ (void)val_b, (void)b, (void)ev_b_addr
241 #define OP_BASIC_ (void)value_decode_(d, val_b, &d->reg_work_[0], &b, &ev_b_addr)
242 #define OP_BASIC(x) OP_TYPE(OP_BASIC_)
243 #define OP_NBI(x) OP_TYPE(OP_NBI_)
246 accounting helpers, these fire off the related callbacks for memory reads,
247 memory writes, and execution of reserved instructions
249 #define ACCT_R(addr) do { acct_event_(d, DCPU16_ACCT_EV_READ, addr); } while (0)
250 #define ACCT_W(addr) do { acct_event_(d, DCPU16_ACCT_EV_WRITE, addr); } while (0)
251 #define ACCT_ILL(addr) do { acct_event_(d, DCPU16_ACCT_EV_NOP, addr); } while (0)
253 /* extended opcodes */
256 N.B. this next function currently decodes values -- id est, it is
257 an opcode processing terminus; however, if 'future instruction set
258 expansion' happens, this will probably need to behave more like
259 the OP_IMPL(_nbi_) function which invoked it, if those instructions
260 have zero or differently styled operands.
262 OP_IMPL(nbi__reserved_
) {
263 OP_NBI(nbi__reserved_
);
264 /* reserved for future expansion */
266 DCPU16_WORD future_opcode
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_BITS
+ OPCODE_OPERAND_BITS
));
267 WARN("reserved future opcode 0x%04x invoked", future_opcode
);
274 /* pushes the address of the next instruction to the stack, then sets PC to a */
278 d
->ram
[ --d
->sp
] = d
->pc
;
286 OP_IMPL(nbi__reserved2_
) {
287 OP_NBI(nbi__reserved2_
);
290 WARN("reserved nbi opcode invoked");
295 static const struct opcode_entry opcode_nbi_entries
[] = {
296 {0x0, "(reserved)", op_nbi__reserved_
},
297 {0x1, "JSR", op_nbi_jsr
},
298 {0x2, "(reserved)", op_nbi__reserved2_
},
301 #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1)
307 N.B. the following function does not decode values, as the nbi
308 instructions only have one operand.
311 /* non-basic instruction */
313 /* don't do normal value decoding here */
315 DCPU16_WORD nbi_opcode
= val_a
;
316 const struct opcode_entry
*e
= opcode_nbi_entries
;
318 e
= opcode_nbi_entries
+ ( (nbi_opcode
< OPCODE_NBI_MAX
) ? nbi_opcode
: (OPCODE_NBI_MAX
- 1) );
320 assert(e
->impl
!= NULL
);
322 TRACE(">> %s 0x%04x", e
->name
, val_b
);
323 e
->impl(d
, val_b
, 0);
333 /* only set non-literal target */
343 /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */
344 unsigned int acc
= *a
+ *b
;
352 d
->o
= (acc
> 0xffff);
361 /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
362 unsigned int acc
= *a
- *b
;
370 d
->o
= (acc
> 0xffff);
378 /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
379 unsigned int acc
= *a
* *b
;
395 /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
406 unsigned int acc
= *a
/ *b
;
412 acc
= (*a
<< 16) / *b
;
423 /* sets a to a%b. if b==0, sets a to 0 instead. */
445 /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
446 unsigned int acc
= *a
<< *b
;
463 /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
464 unsigned int acc
= *a
>> *b
;
472 d
->o
= (*a
<< 16) >> *b
;
529 /* performs next instruction only if a==b */
546 /* performs next instruction only if a!=b */
563 /* performs next instruction only if a>b */
580 /* performs next instruction only if (a&b)!=0 */
585 if ((*a
& *b
) != 0) {
595 static const struct opcode_entry opcode_basic_entries
[] = {
596 {0x0, "(nbi)", op__nbi_
},
597 {0x1, "SET", op_set
},
598 {0x2, "ADD", op_add
},
599 {0x3, "SUB", op_sub
},
600 {0x4, "MUL", op_mul
},
601 {0x5, "DIV", op_div
},
602 {0x6, "MOD", op_mod
},
603 {0x7, "SHL", op_shl
},
604 {0x8, "SHR", op_shr
},
605 {0x9, "AND", op_and
},
606 {0xa, "BOR", op_bor
},
607 {0xb, "XOR", op_xor
},
608 {0xc, "IFE", op_ife
},
609 {0xd, "IFN", op_ifn
},
610 {0xe, "IFG", op_ifg
},
611 {0xf, "IFB", op_ifb
},
616 void dump_operand_value_(DCPU16_WORD value
, DCPU16_WORD nextword
) {
618 printf(" %c", regnames_
[value
]);
619 } else if (value
<= 0x0f) {
620 printf(" [%c]", regnames_
[value
& 0x07]);
621 } else if (value
<= 0x17) {
622 printf(" [0x%04x + %c]", nextword
, regnames_
[value
& 0x07]);
623 } else switch (value
) {
624 case 0x18: printf(" POP"); break;
625 case 0x19: printf(" PEEK"); break;
626 case 0x1a: printf(" PUSH"); break;
627 case 0x1b: printf(" SP"); break;
628 case 0x1c: printf(" PC"); break;
629 case 0x1d: printf(" O"); break;
630 case 0x1e: printf(" [0x%04x]", nextword
); break;
631 case 0x1f: printf(" 0x%04x", nextword
); break;
632 default: printf(" 0x%02x", value
- 0x20);
637 /* split a word into the parts of an instruction, and determine how many words it takes up in total */
639 void instruction_decode_(struct dcpu16
*d
, DCPU16_WORD addr
, DCPU16_WORD
*opcode
, DCPU16_WORD
*a
, DCPU16_WORD
*b
, DCPU16_WORD
*instr_len
) {
640 *opcode
= d
->ram
[addr
] & ((1 << OPCODE_BASIC_BITS
) - 1);
641 *a
= (d
->ram
[addr
] >> OPCODE_BASIC_BITS
) & ((1 << OPCODE_OPERAND_BITS
) - 1);
642 *b
= (d
->ram
[addr
] >> (OPCODE_BASIC_BITS
+ OPCODE_OPERAND_BITS
)) & ((1 << OPCODE_OPERAND_BITS
) - 1);
645 /* both basic and nbi opcodes use their b operand */
646 if ( (*b
>= 0x10 && *b
<= 0x17) || *b
== 0x1e || *b
== 0x1f )
648 /* but only basic uses a */
650 && ((*a
>= 0x10 && *a
<= 0x17) || *a
== 0x1e || *a
== 0x1f) )
655 /* dcpu16_disassemble_print
656 print the words of the instruction at addr, followed by its assembly representation
657 returns the length of the instruction in words
659 DCPU16_WORD
dcpu16_disassemble_print(struct dcpu16
*d
, DCPU16_WORD addr
) {
660 DCPU16_WORD opcode
, a
, b
, instr_len
, i
;
661 const struct opcode_entry
*e
;
662 unsigned int indent
= 0;
663 unsigned int partial
= 0;
668 Check the previous instruction, to see if this one should be
669 indented. This check isn't foolproof, as preceeding addresses
670 could be data which happen to match instructions..
672 for (i
= 3; i
; i
--) {
673 instruction_decode_(d
, addr
- i
, &opcode
, &a
, &b
, &instr_len
);
676 if (instr_len
== i
&& opcode
>= 0xc) {
682 /* now get what we're really interested in */
683 instruction_decode_(d
, addr
, &opcode
, &a
, &b
, &instr_len
);
686 e
= opcode_basic_entries
+ opcode
;
688 e
= opcode_nbi_entries
+ ( (a
< OPCODE_NBI_MAX
) ? a
: (OPCODE_NBI_MAX
- 1) );
690 /* show the raw words */
691 printf("%04x", d
->ram
[addr
]);
692 for (i
= 1; i
< instr_len
; i
++) {
693 printf(" %04x", d
->ram
[addr
+ i
]);
696 /* align things neatly, show the instruction */
697 printf("%s%s ;%s%s%s",
698 instr_len
< 3 ? " " : "",
699 instr_len
< 2 ? " " : "",
704 /* show the operands */
707 dump_operand_value_(a
, d
->ram
[addr
+ 1]);
708 if ((a
>= 0x10 && a
<= 0x17) || a
== 0x1e || a
== 0x1f)
714 dump_operand_value_(b
, d
->ram
[addr
+ 1]);
719 /* execute the next instruction */
720 void dcpu16_step(struct dcpu16
*d
) {
721 DCPU16_WORD opcode
, a
, b
, instr_len
;
722 const struct opcode_entry
*e
;
727 PC is advanced while decoding the operands by the opcode functions.
728 Things like this could be organized a little better..
730 instruction_decode_(d
, d
->pc
, &opcode
, &a
, &b
, NULL
);
732 d
->pc
++; /* all instructions take at least one word */
734 for (e
= opcode_basic_entries
; e
->impl
; e
++) {
735 if (e
->value
== opcode
) {
736 TRACE(">> %s 0x%04x, 0x%04x", e
->name
, a
, b
);
742 /* and jump over next instr if needed */
744 instruction_decode_(d
, d
->pc
, &opcode
, &a
, &b
, &instr_len
);
747 TRACE("++ SKIPPED %x words", instr_len
);
752 print the current state of the machine
753 shows current cycle count, registers, and next instruction
755 void dcpu16_state_print(struct dcpu16
*d
) {
761 for (i
= 0; i
< 8; i
++)
762 printf(" %c:0x%04x", regnames_
[i
], d
->reg
[i
]);
765 printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
772 dcpu16_disassemble_print(d
, d
->pc
);
777 * print raw ram contents from start to stop
779 void dcpu16_dump_ram(struct dcpu16
*d
, DCPU16_WORD start
, DCPU16_WORD end
) {
781 const unsigned int n
= 8; /* words per line */
785 for (i
= start
, j
= 0; i
<= end
; i
++, j
++) {
787 printf("0x%04x:\t", i
);
788 printf(" %04x%s", d
->ram
[i
], (j
% n
) == (n
- 1) ? "\n" : "");
790 if ((j
% n
) != (n
- 1))
795 * Register callback fn to be triggered whenever event matching any events
796 * in bitwise mask occur.
798 int dcpu16_acct_add(struct dcpu16
*vm
, dcpu16_acct_event_ mask
, void (*fn
)(dcpu16_acct_event_
, DCPU16_WORD
)) {
799 struct dcpu16_acct_cb cb
;
804 if (vm
->cb_table_entries_
== vm
->cb_table_allocated_
) {
805 size_t new_entries
= vm
->cb_table_allocated_
+ 32;
806 void *tmp_ptr
= realloc(vm
->cb_table_
, new_entries
* sizeof *(vm
->cb_table_
));
807 if (tmp_ptr
== NULL
) {
808 fprintf(stderr
, "%s():%s", "realloc", strerror(errno
));
811 vm
->cb_table_
= tmp_ptr
;
812 vm
->cb_table_allocated_
+= 32;
815 memcpy(vm
->cb_table_
+ vm
->cb_table_entries_
, &cb
, sizeof cb
);
816 vm
->cb_table_entries_
++;
822 * resets a dcpu16 instance to initial state
824 void dcpu16_reset(struct dcpu16
*d
) {
827 memset(d
, 0, sizeof *d
);
831 * allocate a new dcpu16 instance
833 struct dcpu16
*dcpu16_new(void) {
836 vm
= calloc(1, sizeof *vm
);
838 WARN("%s: %s(%zu): %s", __func__
, "calloc", strerror(errno
));
844 * release a dcpu16 instance
846 void dcpu16_delete(struct dcpu16
**vm
) {
847 if (!vm
|| !*vm
) return;