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
;
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
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
== 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
;
407 d
->o
= (acc
> 0xffff);
417 /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
418 unsigned int acc
= *a
* *b
;
434 /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
445 unsigned int acc
= *a
/ *b
;
451 acc
= (*a
<< 16) / *b
;
462 /* sets a to a%b. if b==0, sets a to 0 instead. */
484 /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
485 unsigned int acc
= *a
<< *b
;
502 /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
503 unsigned int acc
= *a
>> *b
;
511 d
->o
= (*a
<< 16) >> *b
;
568 /* performs next instruction only if a==b */
585 /* performs next instruction only if a!=b */
602 /* performs next instruction only if a>b */
619 /* performs next instruction only if (a&b)!=0 */
624 if ((*a
& *b
) != 0) {
634 static const struct opcode_entry opcode_basic_entries
[] = {
635 {0x0, "(nbi)", op__nbi_
},
636 {0x1, "SET", op_set
},
637 {0x2, "ADD", op_add
},
638 {0x3, "SUB", op_sub
},
639 {0x4, "MUL", op_mul
},
640 {0x5, "DIV", op_div
},
641 {0x6, "MOD", op_mod
},
642 {0x7, "SHL", op_shl
},
643 {0x8, "SHR", op_shr
},
644 {0x9, "AND", op_and
},
645 {0xa, "BOR", op_bor
},
646 {0xb, "XOR", op_xor
},
647 {0xc, "IFE", op_ife
},
648 {0xd, "IFN", op_ifn
},
649 {0xe, "IFG", op_ifg
},
650 {0xf, "IFB", op_ifb
},
654 void dump_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 void dcpu16_disassemble_print(struct dcpu16
*d
, WORD addr
) {
676 unsigned int instr_len
= 1;
677 const struct opcode_entry
*e
;
679 opcode
= (d
->ram
[addr
] >> OPCODE_BASIC_SHIFT
) & ((1 << OPCODE_BASIC_BITS
) - 1);
680 a
= (d
->ram
[addr
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
)) & ((1 << 6) - 1);
681 b
= (d
->ram
[addr
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
+ 6)) & ((1 << 6) - 1);
683 assert(opcode
<= 0x0f);
687 printf("%04x", d
->ram
[addr
]);
691 if (a
== 0x1e || a
== 0x1f) {
692 printf(" %04x", d
->ram
[addr
+ instr_len
]);
696 if (b
== 0x1e || b
== 0x1f) {
697 printf(" %04x", d
->ram
[addr
+ instr_len
]);
702 e
= opcode_basic_entries
+ opcode
;
704 e
= opcode_nbi_entries
+ ( (a
< OPCODE_NBI_MAX
) ? a
: (OPCODE_NBI_MAX
- 1) );
707 instr_len
< 3 ? " " : "",
708 instr_len
< 2 ? " " : "",
711 dump_value(a
, d
->ram
[addr
+ 1]);
712 if (a
== 0x1e || a
== 0x1f)
717 dump_value(b
, d
->ram
[addr
+ 1]);
720 void dcpu16_step(struct dcpu16
*d
) {
723 const struct opcode_entry
*e
;
725 /* decode opcode and invoke */
727 opcode
= (d
->ram
[ d
->pc
] >> OPCODE_BASIC_SHIFT
) & ((1 << OPCODE_BASIC_BITS
) - 1);
728 val_a
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
)) & ((1 << 6) - 1);
729 val_b
= (d
->ram
[d
->pc
] >> (OPCODE_BASIC_SHIFT
+ OPCODE_BASIC_BITS
+ 6)) & ((1 << 6) - 1);
733 for (e
= opcode_basic_entries
; e
->impl
; e
++) {
734 if (e
->value
== opcode
) {
735 TRACE(">> %s 0x%04x, 0x%04x", e
->name
, val_a
, val_b
);
736 e
->impl(d
, val_a
, val_b
);
742 void dcpu16_state_print(struct dcpu16
*d
) {
745 printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
752 dcpu16_disassemble_print(d
, d
->pc
);
755 for (i
= 0; i
< 8; i
++)
756 printf(" %c:0x%04x", regnames_
[i
], d
->reg
[i
]);
761 * print raw ram contents from start to stop
763 void dcpu16_dump_ram(struct dcpu16
*d
, WORD start
, WORD end
) {
765 const unsigned int n
= 8; /* words per line */
767 for (i
= start
, j
= 0; i
<= end
; i
++, j
++) {
769 printf("0x%04x:\t", i
);
770 printf(" %04x%s", d
->ram
[i
], (j
% n
) == (n
- 1) ? "\n" : "");
772 if ((j
% n
) != (n
- 1))
777 * resets a dcpu16 instance to initial state
779 void dcpu16_reset(struct dcpu16
*d
) {
780 memset(d
, 0, sizeof *d
);
784 * allocate a new dcpu16 instance
786 struct dcpu16
*dcpu16_new(void) {
789 vm
= calloc(1, sizeof *vm
);
791 WARN("%s: %s(%zu): %s", __func__
, "calloc", strerror(errno
));
797 * release a dcpu16 instance
799 void dcpu16_delete(struct dcpu16
**vm
) {