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
31 static const char * const src_id_
= "$Id$";
33 #define OPCODE_BASIC_BITS 4
34 #define OPCODE_OPERAND_BITS 6
36 static const char * const regnames_
= "ABCXYZIJ";
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)));
42 void warn_(char *fmt
, ...) {
45 fprintf(stderr
, "[warning] ");
47 vfprintf(stderr
, fmt
, ap
);
49 fprintf(stderr
, "\n");
52 static void (*warn_cb_
)(char *fmt
, ...) = warn_
;
53 void dcpu16_warn_cb_set(void (*fn
)(char *fmt
, ...)) {
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)));
61 void trace_(char *fmt
, ...) {
64 fprintf(stdout
, "[debug] ");
66 vfprintf(stdout
, fmt
, ap
);
68 fprintf(stdout
, "\n");
72 #define TRACE(...) do {} while(0)
74 static void (*trace_cb_
)(char *fmt
, ...) =
81 void dcpu16_trace_cb_set(void (*fn
)(char *fmt
, ...)) {
87 * invokes callbacks for specified event
90 void acct_event_(struct dcpu16
*vm
, dcpu16_acct_event ev
, DCPU16_WORD addr
) {
91 struct dcpu16_acct_cb
*cb
= vm
->cb_table_
;
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
);
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)
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;
113 assert(value
<= 0x3f);
115 /* does this value indicate a literal */
119 if (value
<= 0x07) { /* register */
121 TRACE(">> %c (0x%04x)",
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],
131 *e_addr
= d
->reg
[(value
& 0x07)];
133 } else if (value
<= 0x17) { /* [next word + register] */
134 nextword
= d
->ram
[ d
->pc
++ ];
136 *v
= &(d
->ram
[ nextword
+ d
->reg
[(value
& 0x07)] ]);
137 TRACE(">> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)",
138 regnames_
[(value
& 0x07)],
140 d
->reg
[(value
& 0x07)],
142 *e_addr
= nextword
+ d
->reg
[(value
& 0x07)];
144 } else switch (value
) {
145 case 0x18: /* POP / [sp++] */
146 *v
= &(d
->ram
[ d
->sp
++ ]);
147 TRACE(">> POP [0x%04x] (0x%04x)",
153 case 0x19: /* PEEK / [sp] */
154 *v
= &(d
->ram
[ d
->sp
]);
155 TRACE(">> PEEK [0x%04x] (0x%04x)",
161 case 0x1a: /* PUSH / [--sp] */
162 *v
= &(d
->ram
[ --d
->sp
]);
163 TRACE(">> PUSH [0x%04x] (0x%04x)",
171 TRACE(">> SP (0x%04x)",
177 TRACE(">> PC (0x%04x)", **v
);
182 TRACE(">> O (0x%04x)", **v
);
185 case 0x1e: /* [next word] / [[pc++]] */
186 nextword
= d
->ram
[ d
->pc
++ ];
188 *v
= &(d
->ram
[ nextword
]);
189 TRACE(">> [nextword] [0x%04x] (0x%04x)",
195 case 0x1f: /* next word (literal) / [pc++] */
196 nextword
= d
->ram
[ d
->pc
++ ];
200 TRACE(">> nextword (0x%04x)", **v
);
203 default: /* 0x20-0x3f: literal values 0x00-0x1f */
204 *work_v
= value
& 0x1f;
206 TRACE(">> literal (0x%04x)", **v
);
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
);
220 /* messy boilerplate for opcode handlers */
222 #define OP_IMPL(x) static void op_##x(struct dcpu16 *d, DCPU16_WORD val_a, DCPU16_WORD val_b)
224 #define OP_TYPE(op_type) DCPU16_WORD *a, *b;\
226 DCPU16_WORD ev_a_addr = 0, ev_b_addr = 0;\
228 lit_a = value_decode_(d, val_a, &d->reg_work_[0], &a, &ev_a_addr);\
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_)
237 accounting helpers, these fire off the related callbacks for memory reads,
238 memory writes, and execution of reserved instructions
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)
244 /* extended opcodes */
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.
253 OP_IMPL(nbi__reserved_
) {
254 OP_NBI(nbi__reserved_
);
255 /* reserved for future expansion */
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
);
265 /* pushes the address of the next instruction to the stack, then sets PC to a */
269 d
->ram
[ --d
->sp
] = d
->pc
;
277 OP_IMPL(nbi__reserved2_
) {
278 OP_NBI(nbi__reserved2_
);
281 WARN("reserved nbi opcode invoked");
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_
},
292 #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1)
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.
302 /* non-basic instruction */
304 /* don't do normal value decoding here */
306 DCPU16_WORD nbi_opcode
= val_a
;
307 const struct opcode_entry
*e
= opcode_nbi_entries
;
309 e
= opcode_nbi_entries
+ ( (nbi_opcode
< OPCODE_NBI_MAX
) ? nbi_opcode
: (OPCODE_NBI_MAX
- 1) );
311 assert(e
->impl
!= NULL
);
313 TRACE(">> %s 0x%04x", e
->name
, val_b
);
314 e
->impl(d
, val_b
, 0);
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.
336 /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */
337 unsigned int acc
= *a
+ *b
;
343 d
->o
= (acc
> 0xffff);
352 /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
353 unsigned int acc
= *a
- *b
;
359 d
->o
= (acc
> 0xffff);
368 /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
369 unsigned int acc
= *a
* *b
;
384 /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
394 d
->o
= (*a
<< 16) / *b
;
404 /* sets a to a%b. if b==0, sets a to 0 instead. */
422 /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
423 unsigned int acc
= *a
<< *b
;
439 /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
440 unsigned int acc
= *a
>> *b
;
446 d
->o
= (*a
<< 16) >> *b
;
497 /* performs next instruction only if a==b */
514 /* performs next instruction only if a!=b */
531 /* performs next instruction only if a>b */
548 /* performs next instruction only if (a&b)!=0 */
553 if ((*a
& *b
) != 0) {
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
},
584 void dump_operand_value_(DCPU16_WORD value
, DCPU16_WORD nextword
) {
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);
605 /* split a word into the parts of an instruction, and determine how many words it takes up in total */
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);
613 /* both basic and nbi opcodes use their b operand */
614 if ( (*b
>= 0x10 && *b
<= 0x17) || *b
== 0x1e || *b
== 0x1f )
616 /* but only basic uses a */
618 && ((*a
>= 0x10 && *a
<= 0x17) || *a
== 0x1e || *a
== 0x1f) )
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
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;
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..
640 for (i
= 3; i
; i
--) {
641 instruction_decode_(d
, addr
- i
, &opcode
, &a
, &b
, &instr_len
);
644 if (instr_len
== i
&& opcode
>= 0xc) {
650 /* now get what we're really interested in */
651 instruction_decode_(d
, addr
, &opcode
, &a
, &b
, &instr_len
);
654 e
= opcode_basic_entries
+ opcode
;
656 e
= opcode_nbi_entries
+ ( (a
< OPCODE_NBI_MAX
) ? a
: (OPCODE_NBI_MAX
- 1) );
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
]);
664 /* align things neatly, show the instruction */
665 printf("%s%s ;%s%s%s",
666 instr_len
< 3 ? " " : "",
667 instr_len
< 2 ? " " : "",
672 /* show the operands */
675 dump_operand_value_(a
, d
->ram
[addr
+ 1]);
676 if ((a
>= 0x10 && a
<= 0x17) || a
== 0x1e || a
== 0x1f)
682 dump_operand_value_(b
, d
->ram
[addr
+ 1]);
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
;
695 PC is advanced while decoding the operands by the opcode functions.
696 Things like this could be organized a little better..
698 instruction_decode_(d
, d
->pc
, &opcode
, &a
, &b
, NULL
);
700 d
->pc
++; /* all instructions take at least one word */
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
);
710 /* and jump over next instr if needed */
712 instruction_decode_(d
, d
->pc
, &opcode
, &a
, &b
, &instr_len
);
715 TRACE("++ SKIPPED %x words", instr_len
);
720 print the current state of the machine
721 shows current cycle count, registers, and next instruction
723 void dcpu16_state_print(struct dcpu16
*d
) {
729 for (i
= 0; i
< 8; i
++)
730 printf(" %c:0x%04x", regnames_
[i
], d
->reg
[i
]);
733 printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
740 dcpu16_disassemble_print(d
, d
->pc
);
745 * print raw ram contents from start to stop
747 void dcpu16_dump_ram(struct dcpu16
*d
, DCPU16_WORD start
, DCPU16_WORD end
) {
749 const unsigned int n
= 8; /* words per line */
753 for (i
= start
, j
= 0; i
<= end
; i
++, j
++) {
755 printf("0x%04x:\t", i
);
756 printf(" %04x%s", d
->ram
[i
], (j
% n
) == (n
- 1) ? "\n" : "");
758 if ((j
% n
) != (n
- 1))
763 * Register callback fn to be triggered whenever event matching any events
764 * in bitwise mask occur.
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
;
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
));
780 vm
->cb_table_
= tmp_ptr
;
781 vm
->cb_table_allocated_
+= 32;
784 memcpy(vm
->cb_table_
+ vm
->cb_table_entries_
, &cb
, sizeof cb
);
785 vm
->cb_table_entries_
++;
791 * signals cpu to reset, clearing runstate and ram, then reload any init callbacks
793 void dcpu16_reset(struct dcpu16
*d
) {
797 memset(d
->reg
, 0, sizeof d
->reg
);
802 memset(d
->ram
, 0, sizeof d
->ram
);
804 acct_event_(d
, DCPU16_ACCT_EV_RESET
, 0);
808 * allocate a new dcpu16 instance
810 struct dcpu16
*dcpu16_new(void) {
813 vm
= calloc(1, sizeof *vm
);
815 WARN("%s: %s(%zu): %s", __func__
, "calloc", strerror(errno
));
821 * release a dcpu16 instance
823 void dcpu16_delete(struct dcpu16
**vm
) {
824 if (!vm
|| !*vm
) return;