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 * drop checks for assigning to literals -- it won't affect anything anyhow
28 * debug short literal decoding
31 static const char * const src_id_
= "$Id$";
33 #define WORD DCPU16_WORD
35 static const char regnames_
[] = "ABCXYZIJ";
37 /* some default warning and debug reporting functions, which can be overridden by clients */
38 #define WARN(...) do { if (warn_cb_) warn_cb_(__VA_ARGS__); } while (0)
39 static inline void warn_(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
41 void warn_(char *fmt
, ...) {
44 fprintf(stderr
, "[warning] ");
46 vfprintf(stderr
, fmt
, ap
);
48 fprintf(stderr
, "\n");
51 static void (*warn_cb_
)(char *fmt
, ...) = warn_
;
52 void dcpu16_warn_cb_set(void (*fn
)(char *fmt
, ...)) {
57 #define TRACE(...) do { if (trace_cb_) trace_cb_(__VA_ARGS__); } while (0)
58 static inline void trace_(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
60 void trace_(char *fmt
, ...) {
63 fprintf(stdout
, "[debug] ");
65 vfprintf(stdout
, fmt
, ap
);
67 fprintf(stdout
, "\n");
71 #define TRACE(...) do {} while(0)
73 static void (*trace_cb_
)(char *fmt
, ...) =
80 void dcpu16_trace_cb_set(void (*fn
)(char *fmt
, ...)) {
86 * Register callback fn to be triggered whenever event matching exactly mask_ev
87 * and additionally matching any of mask events occur.
89 int dcpu16_acct_add(struct dcpu16
*vm
, dcpu16_acct_event_ match_all
, dcpu16_acct_event_ match_any
, void (*fn
)(dcpu16_acct_event_
, DCPU16_WORD
)) {
90 struct dcpu16_acct_cb cb
;
92 cb
.match_all
= match_all
;
93 cb
.match_any
= match_any
;
96 /* add to vm->cb_table_, vm->cb_table_entries_, vm->cb_table_allocated_ */
97 if (vm
->cb_table_entries_
== vm
->cb_table_allocated_
) {
98 size_t new_entries
= vm
->cb_table_allocated_
+ 32;
99 void *tmp_ptr
= realloc(vm
->cb_table_
, new_entries
* sizeof *(vm
->cb_table_
));
100 if (tmp_ptr
== NULL
) {
101 fprintf(stderr
, "%s():%s", "realloc", strerror(errno
));
104 vm
->cb_table_
= tmp_ptr
;
105 vm
->cb_table_allocated_
+= 32;
108 memcpy(vm
->cb_table_
+ vm
->cb_table_entries_
, &cb
, sizeof cb
);
109 vm
->cb_table_entries_
++;
115 * invokes callbacks for specified event
118 void acct_event_(struct dcpu16
*vm
, dcpu16_acct_event_ ev
, DCPU16_WORD addr
) {
119 struct dcpu16_acct_cb
*cb
= vm
->cb_table_
;
122 for (i
= 0; i
< vm
->cb_table_entries_
; i
++) {
123 if ( (cb
[i
].match_all
& ev
) == cb
[i
].match_all
/* exact match on event flags */
124 && (cb
[i
].match_any
& ev
) ) { /* any match on rest */
131 * sets *v to be the destination of the value
132 * workv is buffer to use to accumulate literal value before use, one exists for either potential instruction operand
133 * returns true if destination points to literal (id est *v should ignore writes)
136 unsigned int value_decode(struct dcpu16
*d
, WORD value
, WORD
*work_v
, WORD
**v
, dcpu16_acct_event_
*e
, WORD
*e_addr
) {
138 unsigned int retval
= 0;
140 assert(value
<= 0x3f);
144 /* does this value indicate a literal */
148 /* if we're skipping this instruction, just advance the pc if needed */
150 TRACE(">> SKIP decode");
151 if ((value
>= 0x10 && value
<= 0x17) || value
== 0x1e || value
== 0x1f)
156 if (value
<= 0x07) { /* register */
158 TRACE(">> %c (0x%04x)",
162 } else if (value
<= 0x0f) { /* [register] */
163 *v
= &(d
->ram
[ d
->reg
[(value
& 0x07)] ]);
164 TRACE(">> [%c] [0x%04x] (0x%04x)",
165 regnames_
[value
&0x07],
168 *e
|= DCPU16_ACCT_RAM
;
169 *e_addr
= d
->reg
[(value
& 0x07)];
171 } else if (value
<= 0x17) { /* [next word + register] */
172 nextword
= d
->ram
[ d
->pc
++ ];
174 *v
= &(d
->ram
[ nextword
+ d
->reg
[(value
& 0x07)] ]);
175 TRACE(">> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)",
176 regnames_
[(value
& 0x07)],
178 d
->reg
[(value
& 0x07)],
180 *e
|= DCPU16_ACCT_RAM
;
181 *e_addr
= nextword
+ d
->reg
[(value
& 0x07)];
183 } else switch (value
) {
184 case 0x18: /* POP / [sp++] */
185 *v
= &(d
->ram
[ d
->sp
++ ]);
186 TRACE(">> POP [0x%04x] (0x%04x)",
189 *e
|= DCPU16_ACCT_RAM
;
193 case 0x19: /* PEEK / [sp] */
194 *v
= &(d
->ram
[ d
->sp
]);
195 TRACE(">> PEEK [0x%04x] (0x%04x)",
198 *e
|= DCPU16_ACCT_RAM
;
202 case 0x1a: /* PUSH / [--sp] */
203 *v
= &(d
->ram
[ --d
->sp
]);
204 TRACE(">> PUSH [0x%04x] (0x%04x)",
207 *e
|= DCPU16_ACCT_RAM
;
213 TRACE(">> SP (0x%04x)",
219 TRACE(">> PC (0x%04x)", **v
);
224 TRACE(">> O (0x%04x)", **v
);
227 case 0x1e: /* [next word] / [[pc++]] */
228 nextword
= d
->ram
[ d
->pc
++ ];
230 *v
= &(d
->ram
[ nextword
]);
231 TRACE(">> [nextword] [0x%04x] (0x%04x)",
234 *e
|= DCPU16_ACCT_RAM
;
238 case 0x1f: /* next word (literal) / [pc++] */
239 nextword
= d
->ram
[ d
->pc
++ ];
243 TRACE(">> nextword (0x%04x)", **v
);
246 default: /* 0x20-0x3f: literal values 0x00-0x1f */
247 *work_v
= value
& 0x1f;
249 TRACE(">> literal (0x%04x)", **v
);
255 #define OPCODE_BASIC_BITS (4)
256 #define OPCODE_BASIC_SHIFT (0)
258 #define OPCODE_NBI_BITS (6)
259 #define OPCODE_NBI_SHIFT (4)
261 #define OPCODE_FUTURE_BITS (16)
262 #define OPCODE_FUTURE_SHIFT (10)
264 #define OPCODE_NAME_LEN 16
265 struct opcode_entry
{
266 unsigned short value
;
267 char name
[OPCODE_NAME_LEN
];
268 void (*impl
)(struct dcpu16
*, WORD
, WORD
);
271 /* messy boilerplate for opcode handlers */
273 #define OP_IMPL(x) static void op_##x(struct dcpu16 *d, WORD val_a, WORD val_b)
275 #define OP_NBI_ (void)val_b, (void)b, (void)ev_b, (void)ev_b_addr
276 #define OP_BASIC_ (void)value_decode(d, val_b, &d->reg_work_[0], &b, &ev_b, &ev_b_addr)
277 #define OP_TYPE(op_type) WORD *a, *b;\
279 dcpu16_acct_event_ ev_a = 0, ev_b = 0;\
280 WORD ev_a_addr = 0, ev_b_addr = 0;\
282 lit_a = value_decode(d, val_a, &d->reg_work_[0], &a, &ev_a, &ev_a_addr);\
285 TRACE("++ SKIPPED");\
290 #define OP_BASIC(x) OP_TYPE(OP_BASIC_)
291 #define OP_NBI(x) OP_TYPE(OP_NBI_)
293 /* accounting helpers */
294 #define ACCT_R(ev) do { if (ev) { acct_event_(d, ev | DCPU16_ACCT_EV_READ, ev##_addr); } } while (0)
295 #define ACCT_W(ev) do { if (ev) { acct_event_(d, ev | DCPU16_ACCT_EV_WRITE, ev##_addr); } } while (0)
297 /* extended opcodes */
300 N.B. this next function currently decodes values -- id est, it is
301 an opcode processing terminus; however, if 'future instruction set
302 expansion' happens, this will probably need to behave more like
303 the OP_IMPL(_nbi_) function which invoked it, if those instructions
304 have zero or differently styled operands.
306 OP_IMPL(nbi__reserved_
) {
307 OP_NBI(nbi__reserved_
);
308 /* reserved for future expansion */
310 WORD future_opcode
= (d
->ram
[d
->pc
] >> OPCODE_FUTURE_SHIFT
);
311 WARN("reserved future opcode 0x%04x invoked", future_opcode
);
316 /* pushes the address of the next instruction to the stack, then sets PC to a */
320 d
->ram
[ --d
->sp
] = d
->pc
;
326 OP_IMPL(nbi__reserved2_
) {
327 OP_NBI(nbi__reserved2_
);
330 WARN("reserved nbi opcode invoked");
333 static const struct opcode_entry opcode_nbi_entries
[] = {
334 {0x0, "(reserved)", op_nbi__reserved_
},
335 {0x1, "JSR", op_nbi_jsr
},
336 {0x2, "(reserved)", op_nbi__reserved2_
},
339 #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1)
345 N.B. the following function does not decode values, as the nbi
346 instructions only have one operand.
349 /* non-basic instruction */
351 /* don't do normal value decoding here */
353 WORD nbi_opcode
= val_a
;
354 const struct opcode_entry
*e
= opcode_nbi_entries
;
356 e
= opcode_nbi_entries
+ ( (nbi_opcode
< OPCODE_NBI_MAX
) ? nbi_opcode
: (OPCODE_NBI_MAX
- 1) );
358 assert(e
->impl
!= NULL
);
360 TRACE(">> %s 0x%04x", e
->name
, val_b
);
361 e
->impl(d
, val_b
, 0);
371 /* only set non-literal target */
381 /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */
382 unsigned int acc
= *a
+ *b
;
390 d
->o
= (acc
> 0xffff);
399 /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
400 unsigned int acc
= *a
- *b
;
408 d
->o
= (acc
> 0xffff);
416 /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
417 unsigned int acc
= *a
* *b
;
433 /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
444 unsigned int acc
= *a
/ *b
;
450 acc
= (*a
<< 16) / *b
;
461 /* sets a to a%b. if b==0, sets a to 0 instead. */
483 /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
484 unsigned int acc
= *a
<< *b
;
501 /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
502 unsigned int acc
= *a
>> *b
;
510 d
->o
= (*a
<< 16) >> *b
;
567 /* performs next instruction only if a==b */
584 /* performs next instruction only if a!=b */
601 /* performs next instruction only if a>b */
618 /* performs next instruction only if (a&b)!=0 */
623 if ((*a
& *b
) != 0) {
633 static const struct opcode_entry opcode_basic_entries
[] = {
634 {0x0, "(nbi)", op__nbi_
},
635 {0x1, "SET", op_set
},
636 {0x2, "ADD", op_add
},
637 {0x3, "SUB", op_sub
},
638 {0x4, "MUL", op_mul
},
639 {0x5, "DIV", op_div
},
640 {0x6, "MOD", op_mod
},
641 {0x7, "SHL", op_shl
},
642 {0x8, "SHR", op_shr
},
643 {0x9, "AND", op_and
},
644 {0xa, "BOR", op_bor
},
645 {0xb, "XOR", op_xor
},
646 {0xc, "IFE", op_ife
},
647 {0xd, "IFN", op_ifn
},
648 {0xe, "IFG", op_ifg
},
649 {0xf, "IFB", op_ifb
},
654 void dump_operand_value_(WORD value
, WORD nextword
) {
656 printf(" %c", regnames_
[value
]);
657 } else if (value
<= 0x0f) {
658 printf(" [%c]", regnames_
[value
& 0x07]);
659 } else if (value
<= 0x17) {
660 printf(" [0x%04x + %c]", nextword
, regnames_
[value
& 0x07]);
661 } else switch (value
) {
662 case 0x18: printf(" POP"); break;
663 case 0x19: printf(" PEEK"); break;
664 case 0x1a: printf(" PUSH"); break;
665 case 0x1b: printf(" SP"); break;
666 case 0x1c: printf(" PC"); break;
667 case 0x1d: printf(" O"); break;
668 case 0x1e: printf(" [0x%04x]", nextword
); break;
669 case 0x1f: printf(" 0x%04x", nextword
); break;
670 default: printf(" 0x%02x", value
- 0x20);
674 /* split a word into the parts of an instruction, and determine how many words it takes up in total */
676 void instruction_decode_(struct dcpu16
*d
, WORD addr
, WORD
*opcode
, WORD
*a
, WORD
*b
, unsigned int *instr_len
) {
677 *opcode
= d
->ram
[addr
] & ((1 << OPCODE_BASIC_BITS
) - 1);
678 *a
= (d
->ram
[addr
] >> OPCODE_BASIC_BITS
) & ((1 << 6) - 1);
679 *b
= (d
->ram
[addr
] >> (OPCODE_BASIC_BITS
+ 6)) & ((1 << 6) - 1);
681 /* both basic and nbi opcodes use their b operand */
682 if ( (*b
>= 0x10 && *b
<= 0x17) || *b
== 0x1e || *b
== 0x1f )
684 /* but only basic uses a */
686 && ((*a
>= 0x10 && *a
<= 0x17) || *a
== 0x1e || *a
== 0x1f) )
690 /* dcpu16_disassemble_print
691 print the words of the instruction at addr, followed by its assembly
693 7de1 1000 0020 ; SET [0x1000], 0x0020
697 WORD
dcpu16_disassemble_print(struct dcpu16
*d
, WORD addr
) {
699 unsigned int instr_len
, i
;
700 const struct opcode_entry
*e
;
701 unsigned int indent
= 0;
702 unsigned int partial
= 0;
705 Check the previous instruction, to see if this one should be
706 indented. This check isn't foolproof, as preceeding addresses
707 could be data which happen to match instructions..
709 for (i
= 3; i
; i
--) {
710 instruction_decode_(d
, addr
- i
, &opcode
, &a
, &b
, &instr_len
);
713 if (instr_len
== i
&& opcode
>= 0xc) {
719 /* now get what we're really interested in */
720 instruction_decode_(d
, addr
, &opcode
, &a
, &b
, &instr_len
);
723 e
= opcode_basic_entries
+ opcode
;
725 e
= opcode_nbi_entries
+ ( (a
< OPCODE_NBI_MAX
) ? a
: (OPCODE_NBI_MAX
- 1) );
727 /* show the raw words */
728 printf("%04x", d
->ram
[addr
]);
729 for (i
= 1; i
< instr_len
; i
++) {
730 printf(" %04x", d
->ram
[addr
+ i
]);
733 /* align things neatly, show the instruction */
734 printf("%s%s ;%s%s%s",
735 instr_len
< 3 ? " " : "",
736 instr_len
< 2 ? " " : "",
741 /* show the operands */
744 dump_operand_value_(a
, d
->ram
[addr
+ 1]);
745 if ((a
>= 0x10 && a
<= 0x17) || a
== 0x1e || a
== 0x1f)
751 dump_operand_value_(b
, d
->ram
[addr
+ 1]);
756 void dcpu16_step(struct dcpu16
*d
) {
759 const struct opcode_entry
*e
;
761 /* decode opcode and invoke */
763 opcode
= (d
->ram
[ d
->pc
] >> OPCODE_BASIC_SHIFT
) & ((1 << OPCODE_BASIC_BITS
) - 1);
764 val_a
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
)) & ((1 << 6) - 1);
765 val_b
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
+ 6)) & ((1 << 6) - 1);
769 for (e
= opcode_basic_entries
; e
->impl
; e
++) {
770 if (e
->value
== opcode
) {
771 TRACE(">> %s 0x%04x, 0x%04x", e
->name
, val_a
, val_b
);
772 e
->impl(d
, val_a
, val_b
);
778 void dcpu16_state_print(struct dcpu16
*d
) {
781 printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
788 dcpu16_disassemble_print(d
, d
->pc
);
791 for (i
= 0; i
< 8; i
++)
792 printf(" %c:0x%04x", regnames_
[i
], d
->reg
[i
]);
797 * print raw ram contents from start to stop
799 void dcpu16_dump_ram(struct dcpu16
*d
, WORD start
, WORD end
) {
801 const unsigned int n
= 8; /* words per line */
803 for (i
= start
, j
= 0; i
<= end
; i
++, j
++) {
805 printf("0x%04x:\t", i
);
806 printf(" %04x%s", d
->ram
[i
], (j
% n
) == (n
- 1) ? "\n" : "");
808 if ((j
% n
) != (n
- 1))
813 * resets a dcpu16 instance to initial state
815 void dcpu16_reset(struct dcpu16
*d
) {
816 memset(d
, 0, sizeof *d
);
820 * allocate a new dcpu16 instance
822 struct dcpu16
*dcpu16_new(void) {
825 vm
= calloc(1, sizeof *vm
);
827 WARN("%s: %s(%zu): %s", __func__
, "calloc", strerror(errno
));
833 * release a dcpu16 instance
835 void dcpu16_delete(struct dcpu16
**vm
) {