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 05 05 - start of v1.7 revisions
22 * 2012 05 08 - v1.7 revisions mostly complete
25 * !! v1.7 bit-shift and signed opcodes need to be reviewed/finished
26 * !! v1.7 hardware interface needs to be finished
27 * !! v1.7 interrupts need to be finished
28 * change api to print into buffers rather than stdio
29 * refactor opcode functiontables into switch statements
30 * let callbacks determine whether to override events, or just observe
31 * sort init callbacks by base addr, to call in-order
32 * make all callbacks register addr range of interest
35 static const char * const src_id_
= "$Id$";
37 #define OPCODE_BASIC_BITS 5
38 #define OPCODE_OPERAND_B_BITS 5
39 #define OPCODE_OPERAND_A_BITS 6
41 const char * const dcpu16_reg_names
[] = {
57 static void printf_null_(char *fmt
, ...) { (void)fmt
; }
59 /* some default warning and debug reporting functions, which can be overridden by clients */
60 #define WARN(...) do { if (warn_cb_) warn_cb_(__VA_ARGS__); } while (0)
61 static inline void warn_(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
63 void warn_(char *fmt
, ...) {
66 fprintf(stderr
, "[warning] ");
68 vfprintf(stderr
, fmt
, ap
);
70 fprintf(stderr
, "\n");
73 static void (*warn_cb_
)(char *fmt
, ...) = warn_
;
74 void dcpu16_warn_cb_set(void (*fn
)(char *fmt
, ...)) {
78 warn_cb_
= printf_null_
;
82 #define TRACE(...) do { if (trace_cb_) trace_cb_(__VA_ARGS__); } while (0)
83 static inline void trace_(char *fmt
, ...) __attribute__((format(printf
, 1, 2)));
85 void trace_(char *fmt
, ...) {
88 fprintf(stdout
, "[debug] ");
90 vfprintf(stdout
, fmt
, ap
);
92 fprintf(stdout
, "\n");
96 #define TRACE(...) do {} while(0)
98 static void (*trace_cb_
)(char *fmt
, ...) =
105 void dcpu16_trace_cb_set(void (*fn
)(char *fmt
, ...)) {
109 trace_cb_
= printf_null_
;
114 * invokes callbacks for specified event
117 void acct_event_(struct dcpu16
*vm
, dcpu16_acct_event ev
, DCPU16_WORD addr
) {
118 struct dcpu16_acct_cb
*cb
= vm
->cb_table_
;
121 for (i
= 0; i
< vm
->cb_table_entries_
; i
++) {
122 if ( (cb
[i
].mask
& ev
) )
123 cb
[i
].fn(vm
, ev
, addr
, cb
[i
].data
);
128 /* add an entry to the interrupt queue */
130 int interrupt_enqueue_(struct dcpu16
*vm
, DCPU16_WORD message
) {
131 vm
->interrupts_
[vm
->interrupts_tail_
] = message
;
132 vm
->interrupts_tail_
+= 1;
133 vm
->interrupts_tail_
%= DCPU16_INTERRUPT_QUEUE_SIZE
;
135 if (vm
->interrupts_tail_
== vm
->interrupts_head_
) {
137 WARN("interrupt queue overflow (system is now on fire)");
145 DCPU16_WORD
interrupt_dequeue_(struct dcpu16
*vm
) {
148 if (vm
->interrupts_tail_
== vm
->interrupts_head_
) {
149 WARN("interrupt underflow");
153 message
= vm
->interrupts_
[vm
->interrupts_head_
];
154 vm
->interrupts_head_
+= 1;
155 vm
->interrupts_head_
%= DCPU16_INTERRUPT_QUEUE_SIZE
;
161 * sets *v to be the address of the represented value
162 * value_is_a is 0 for b, 1 for a, alters behavior of some operands
163 * value_data is 'nextword' for this operand, ignored if unused
164 * workv is buffer to use to accumulate literal value into, before use. one exists for either potential instruction operand
165 * e_addr is set to a referenced address, for accounting callback
166 * pc_adjust is set to how to change the program counter
167 * stack_adjust is set to how to change the stack pointer
168 * cycle_adjust is set to number of cycles spent looking up operand
170 * zero all adjustables before decoding first operand, and pass in these values when
171 * decoding next operand..
175 void value_decode_(struct dcpu16
*vm
, DCPU16_WORD value
, unsigned int value_is_a
, DCPU16_WORD value_data
,
176 DCPU16_WORD
*work_v
, DCPU16_WORD
**v
, DCPU16_WORD
*e_addr
,
177 short *pc_adjust
, short *sp_adjust
, unsigned int *cycle_adjust
) {
178 assert(value
<= 0x3f);
180 DCPU16_WORD pc
= (DCPU16_WORD
)(vm
->reg
[DCPU16_REG_PC
] + *pc_adjust
),
181 sp
= (DCPU16_WORD
)(vm
->reg
[DCPU16_REG_PC
] + *sp_adjust
);
183 TRACE("%s>> is_a:%u pc:0x%04x sp:0x%04x value_data:0x%04x\n",
190 if (value
<= 0x07) { /* register */
191 *v
= vm
->reg
+ value
;
192 TRACE("%s>> %s (0x%04x)",
194 dcpu16_reg_names
[value
],
199 if (value
<= 0x0f) { /* [register] */
200 *v
= &(vm
->ram
[ vm
->reg
[value
& 0x07] ]);
201 *e_addr
= vm
->reg
[value
& 0x07];
202 TRACE("%s>> [%s] [0x%04x] (0x%04x)",
204 dcpu16_reg_names
[value
& 0x07],
205 vm
->reg
[value
& 0x07],
210 if (value
<= 0x17) { /* [next word + register] */
211 *pc_adjust
+= 1; /* consume next word */
213 *e_addr
= value_data
+ vm
->reg
[value
& 0x07];
214 *v
= vm
->ram
+ *e_addr
;
215 TRACE("%s>> [nextword + %s] [0x%04x + 0x%04x] (0x%04x)",
217 dcpu16_reg_names
[value
& 0x07],
219 vm
->reg
[value
& 0x07],
225 case 0x18: /* PUSH/[--SP] or POP/[SP++] */
226 if (value_is_a
== 0) { /* b */
227 *v
= &(vm
->ram
[sp
- 1]);
230 TRACE("%s>> PUSH [0x%04x] (0x%04x)", __func__
, sp
- 1, **v
);
235 TRACE("%s>> POP [0x%04x] (0x%04x)", __func__
, sp
, **v
);
239 case 0x19: /* PEEK/[SP] */
242 TRACE("%s>> PEEK [0x%04x] (0x%04x)",
248 case 0x1a: /* PICK n */
251 *e_addr
= sp
+ value_data
;
252 *v
= vm
->ram
+ *e_addr
;
253 TRACE("%s>> PICK 0x%04x [0x%04x] (0x%04x)",
261 *v
= &(vm
->reg
[DCPU16_REG_SP
]);
262 TRACE("%s>> %s (0x%04x)",
264 dcpu16_reg_names
[DCPU16_REG_SP
],
269 *v
= &(vm
->reg
[DCPU16_REG_PC
]);
270 TRACE("%s>> %s (0x%04x)",
272 dcpu16_reg_names
[DCPU16_REG_PC
],
277 *v
= &(vm
->reg
[DCPU16_REG_EX
]);
278 TRACE("%s>> %s (0x%04x)",
280 dcpu16_reg_names
[DCPU16_REG_EX
],
284 case 0x1e: /* [next word] / [[pc++]] */
287 *e_addr
= value_data
;
288 *v
= vm
->ram
+ *e_addr
;
289 TRACE("%s>> [nextword] [0x%04x] (0x%04x)",
295 case 0x1f: /* next word (literal) / [pc++] */
298 *work_v
= value_data
;
300 TRACE("%s>> nextword (0x%04x)",
305 default: /* 0x20-0x3f: literal values 0xffff-0x1e */
306 *work_v
= (value
& 0x1f) - 1;
308 TRACE("%s>> literal (0x%04x)",
315 #define OPCODE_NAME_LEN 16
316 struct opcode_entry
{
317 unsigned short value
;
318 char name
[OPCODE_NAME_LEN
];
319 void (*impl
)(struct dcpu16
*, DCPU16_WORD
, DCPU16_WORD
, DCPU16_WORD
, DCPU16_WORD
);
322 /* messy boilerplate for opcode handlers */
324 /* opcode doesn't adjust its own PC, the step function which invoked it handles that */
325 /* opcode does adjust stack and cycle count */
327 #define OP_IMPL(x) static void op_##x(struct dcpu16 *vm, DCPU16_WORD val_b, DCPU16_WORD val_b_data, DCPU16_WORD val_a, DCPU16_WORD val_a_data)
329 #define OP_TYPE(op_type) DCPU16_WORD *a, *b;\
330 DCPU16_WORD ev_a_addr = 0, ev_b_addr = 0;\
331 short pc_adjust = 0, sp_adjust = 0;\
332 unsigned int cycle_adjust = 0;\
335 value_decode_(vm, val_a, 1, val_a_data,\
336 &vm->reg_work_[1], &a, &ev_a_addr,\
337 &pc_adjust, &sp_adjust, &cycle_adjust);\
338 vm->reg[DCPU16_REG_SP] += sp_adjust;\
339 vm->cycle += cycle_adjust;\
341 #define OP_NBI_ (void)val_b, (void)b, (void)ev_b_addr, (void)val_b_data
342 #define OP_BASIC_ value_decode_(vm, val_b, 0, val_b_data,\
343 &vm->reg_work_[0], &b, &ev_b_addr,\
344 &pc_adjust, &sp_adjust, &cycle_adjust)
345 #define OP_BASIC(x) OP_TYPE(OP_BASIC_)
346 #define OP_NBI(x) OP_TYPE(OP_NBI_)
348 /* after invoking one of these header macros, the instruction and operands will have been decoded, and the control registers have been adjusted to the next instruction et cetera */
351 accounting helpers, these fire off the related callbacks for memory reads,
352 memory writes, and execution of reserved instructions
354 #define ACCT_R(addr) do { acct_event_(vm, DCPU16_ACCT_EV_READ, addr); } while (0)
355 #define ACCT_W(addr) do { acct_event_(vm, DCPU16_ACCT_EV_WRITE, addr); } while (0)
356 #define ACCT_ILL(addr) do { acct_event_(vm, DCPU16_ACCT_EV_NOP, addr); } while (0)
358 /* extended opcodes */
361 N.B. this next function currently decodes values -- id est, it is
362 an opcode processing terminus; however, if 'future instruction set
363 expansion' happens, this will probably need to behave more like
364 the OP_IMPL(_nbi_) function which invoked it, if those instructions
365 have zero or differently styled operands.
367 OP_IMPL(nbi__reserved_
) {
368 OP_NBI(nbi__reserved_
);
369 /* reserved for future expansion */
371 /* fire an illegal instruction event for current instruction */
372 DCPU16_WORD future_opcode
= (vm
->ram
[vm
->reg
[DCPU16_REG_PC
] - pc_adjust
] >> (OPCODE_BASIC_BITS
+ OPCODE_OPERAND_B_BITS
));
373 WARN("reserved future opcode 0x%04x invoked", future_opcode
);
375 ACCT_ILL(vm
->reg
[DCPU16_REG_PC
] - pc_adjust
);
380 /* pushes the address of the next instruction to the stack, then sets PC to a */
384 vm
->ram
[ --vm
->reg
[DCPU16_REG_SP
] ] = vm
->reg
[DCPU16_REG_PC
];
385 vm
->reg
[DCPU16_REG_PC
] = *a
;
389 ACCT_W(vm
->reg
[DCPU16_REG_SP
] + 1);
392 OP_IMPL(nbi__reserved2_
) {
393 OP_NBI(nbi__reserved2_
);
396 WARN("reserved nbi opcode invoked");
398 ACCT_ILL(vm
->reg
[DCPU16_REG_PC
] - pc_adjust
);
406 if (vm
->reg
[DCPU16_REG_IA
]) {
407 if ( interrupt_enqueue_(vm
, *a
) ) {
408 WARN("failed to queue interrupt");
412 if (vm
->interrupts_deferred_
)
415 vm
->interrupts_deferred_
= 1;
416 vm
->ram
[--vm
->reg
[DCPU16_REG_SP
]] = vm
->reg
[DCPU16_REG_PC
];
417 vm
->ram
[--vm
->reg
[DCPU16_REG_SP
]] = vm
->reg
[DCPU16_REG_A
];
418 vm
->reg
[DCPU16_REG_PC
] = vm
->reg
[DCPU16_REG_IA
];
428 *a
= vm
->reg
[DCPU16_REG_IA
];
436 vm
->reg
[DCPU16_REG_IA
] = *a
;
441 /* does this just ignore its operand? */
445 vm
->interrupts_deferred_
= 0;
446 vm
->reg
[DCPU16_REG_A
] = vm
->ram
[vm
->reg
[DCPU16_REG_SP
]++];
447 vm
->reg
[DCPU16_REG_PC
] = vm
->ram
[vm
->reg
[DCPU16_REG_SP
]++];
454 vm
->interrupts_deferred_
= 1;
456 vm
->interrupts_deferred_
= 0;
467 *a
= vm
->hw_table_entries_
;
477 if (*a
>= vm
->hw_table_entries_
) {
478 WARN("hardware query for non-extant device 0x%04x", *a
);
479 vm
->reg
[DCPU16_REG_A
] = 0;
480 vm
->reg
[DCPU16_REG_B
] = 0;
481 vm
->reg
[DCPU16_REG_C
] = 0;
482 vm
->reg
[DCPU16_REG_X
] = 0;
483 vm
->reg
[DCPU16_REG_Y
] = 0;
487 vm
->reg
[DCPU16_REG_A
] = vm
->hw_table_
[*a
].id_l
;
488 vm
->reg
[DCPU16_REG_B
] = vm
->hw_table_
[*a
].id_h
;
489 vm
->reg
[DCPU16_REG_C
] = vm
->hw_table_
[*a
].ver
;
490 vm
->reg
[DCPU16_REG_X
] = vm
->hw_table_
[*a
].mfg_l
;
491 vm
->reg
[DCPU16_REG_Y
] = vm
->hw_table_
[*a
].mfg_h
;
501 if (*a
> vm
->hw_table_entries_
) {
502 WARN("interrupt for non-extant device 0x%04x", *a
);
507 if (vm
->hw_table_
[*a
].hwi
)
508 vm
->hw_table_
[*a
].hwi(vm
, vm
->hw_table_
[*a
].data
);
510 WARN("hardware 0x%04x has no interrupt handler", *a
);
519 WARN("system on fire");
524 static const struct opcode_entry opcode_nbi_entries
[] = {
525 {0x00, "(reserved)", op_nbi__reserved_
},
526 {0x01, "JSR", op_nbi_jsr
},
527 {0x02, "(reserved)", op_nbi__reserved2_
},
528 {0x03, "(reserved)", op_nbi__reserved2_
},
529 {0x04, "(reserved)", op_nbi__reserved2_
},
530 {0x05, "(reserved)", op_nbi__reserved2_
},
531 {0x06, "(reserved)", op_nbi__reserved2_
},
532 {0x07, "HCF", op_nbi_hcf
}, /* undocumented */
533 {0x08, "INT", op_nbi_int
},
534 {0x09, "IAG", op_nbi_iag
},
535 {0x0a, "IAS", op_nbi_ias
},
536 {0x0b, "RFI", op_nbi_rfi
},
537 {0x0c, "IAQ", op_nbi_iaq
},
538 {0x0d, "(reserved)", op_nbi__reserved2_
},
539 {0x0e, "(reserved)", op_nbi__reserved2_
},
540 {0x0f, "(reserved)", op_nbi__reserved2_
},
541 {0x10, "HWN", op_nbi_hwn
},
542 {0x11, "HWQ", op_nbi_hwq
},
543 {0x12, "HWI", op_nbi_hwi
},
544 {0x13, "(reserved)", op_nbi__reserved2_
},
547 #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1)
553 N.B. the following function does not decode values.
554 Decoding is handled by the secondary opcode functions it calls.
557 /* non-basic instruction */
559 /* don't do normal value decoding here */
561 DCPU16_WORD nbi_opcode
= val_b
;
562 const struct opcode_entry
*e
= opcode_nbi_entries
;
566 e
= opcode_nbi_entries
+ ( (nbi_opcode
< OPCODE_NBI_MAX
) ? nbi_opcode
: (OPCODE_NBI_MAX
- 1) );
568 assert(e
->impl
!= NULL
);
570 TRACE(">> %s 0x%04x", e
->name
, val_b
);
571 e
->impl(vm
, 0, 0, val_a
, val_a_data
);
581 if b is a literal, it's aimed at a scratch register,
582 so it's fine to update, as it won't have any effect.
593 /* sets b to b+a, sets EX to 0x0001 if there's an overflow, 0x0 otherwise */
594 unsigned int acc
= *b
+ *a
;
600 vm
->reg
[DCPU16_REG_EX
] = (acc
> 0xffff);
609 /* sets b to b-a, sets EX to 0xffff if there's an underflow, 0x0 otherwise */
610 unsigned int acc
= *b
- *a
;
616 vm
->reg
[DCPU16_REG_EX
] = (acc
> 0xffff);
625 /* sets b to b*a, unsigned, sets EX to ((b*a)>>16)&0xffff */
626 unsigned int acc
= *b
* *a
;
632 vm
->reg
[DCPU16_REG_EX
] = acc
>> 16;
641 /* sets b to b*a, signed */
642 int acc
= (short)*b
* (short)*a
;
648 vm
->reg
[DCPU16_REG_EX
] = acc
>> 16;
657 /* sets b to b/a, sets EX to ((b<<16)/a)&0xffff. if a==0, sets a and EX to 0 instead. */
664 vm
->reg
[DCPU16_REG_EX
] = 0;
667 vm
->reg
[DCPU16_REG_EX
] = (*b
<< 16) / *a
;
677 /* sets b to b/a, signed, round towards 0 */
684 vm
->reg
[DCPU16_REG_EX
] = 0;
686 *b
= (short)*b
/ (short)*a
;
687 vm
->reg
[DCPU16_REG_EX
] = (short)(*b
<< 16) / (short)*a
;
697 /* sets b to b%a. if a==0, sets b to 0 instead. */
715 /* sets b to b%a, signed */
723 *b
= (short)*b
% (short)*a
;
775 /* sets b to b>>>a, sets EX to ((b<<16)>>a)&0xffff */
776 unsigned int acc
= *b
>> *a
;
782 vm
->reg
[DCPU16_REG_EX
] = (*b
<< 16) >> *a
;
793 /* sets b to b>>a, sets EX to ((b<<16)>>>a)&0xffff (arithmetic shift) (treats b as signed) */
794 unsigned int acc
= *b
<< *a
;
800 vm
->reg
[DCPU16_REG_EX
] = (*b
<< 16) >> *a
;
811 /* sets b to b<<a, sets EX to ((b<<a)>>16)&0xffff */
812 unsigned int acc
= *b
<< *a
;
819 vm
->reg
[DCPU16_REG_EX
] = acc
>> 16;
828 /* performs next instruction only if (b&a)!=0 */
833 if ((*b
& *a
) != 0) {
845 /* performs next instruction only if (b&a)==0 */
850 if ((*b
& *a
) == 0) {
862 /* performs next instruction only if b==a */
879 /* performs next instruction only if b!=a */
896 /* performs next instruction only if b>a */
913 /* performs next instruction only if b>a (signed) */
930 /* performs next instruction only if b<a */
947 /* performs next instruction only if b<a (signed) */
964 /* sets b to b+a+EX, sets EX to 0x0001 if overflow, 0x0 otherwise */
970 acc
= *b
+ *a
+ vm
->reg
[DCPU16_REG_EX
];
973 vm
->reg
[DCPU16_REG_EX
] = 0x0001;
975 vm
->reg
[DCPU16_REG_EX
] = 0x0000;
984 /* sets b to b-a+EX, sets EX to 0xffff if underflow, 0x0 otherwise */
990 acc
= *b
- *a
+ vm
->reg
[DCPU16_REG_EX
];
993 vm
->reg
[DCPU16_REG_EX
] = 0xffff;
995 vm
->reg
[DCPU16_REG_EX
] = 0;
1004 /* sets b to a, then increases I and J by 1 */
1010 vm
->reg
[DCPU16_REG_I
] += 1;
1011 vm
->reg
[DCPU16_REG_J
] += 1;
1020 /* sets b to a, then decreases I and J by 1 */
1026 vm
->reg
[DCPU16_REG_I
] -= 1;
1027 vm
->reg
[DCPU16_REG_J
] -= 1;
1034 OP_IMPL(_reserved_
) {
1035 OP_BASIC(_reserved_
);
1037 WARN("reserved opcode invoked");
1039 ACCT_ILL(vm
->reg
[DCPU16_REG_PC
] - pc_adjust
);
1042 static const struct opcode_entry opcode_basic_entries
[] = {
1043 {0x00, "(nbi)", op__nbi_
},
1044 {0x01, "SET", op_set
},
1045 {0x02, "ADD", op_add
},
1046 {0x03, "SUB", op_sub
},
1047 {0x04, "MUL", op_mul
},
1048 {0x05, "MLI", op_mli
},
1049 {0x06, "DIV", op_div
},
1050 {0x07, "DVI", op_dvi
},
1051 {0x08, "MOD", op_mod
},
1052 {0x09, "MDI", op_mdi
},
1053 {0x0a, "AND", op_and
},
1054 {0x0b, "BOR", op_bor
},
1055 {0x0c, "XOR", op_xor
},
1056 {0x0d, "SHR", op_shr
},
1057 {0x0e, "ASR", op_asr
},
1058 {0x0f, "SHL", op_shl
},
1059 {0x10, "IFB", op_ifb
},
1060 {0x11, "IFC", op_ifc
},
1061 {0x12, "IFE", op_ife
},
1062 {0x13, "IFN", op_ifn
},
1063 {0x14, "IFG", op_ifg
},
1064 {0x15, "IFA", op_ifa
},
1065 {0x16, "IFL", op_ifl
},
1066 {0x17, "IFU", op_ifu
},
1067 {0x18, "(reserved)", op__reserved_
},
1068 {0x19, "(reserved)", op__reserved_
},
1069 {0x1a, "ADX", op_adx
},
1070 {0x1b, "SBX", op_sbx
},
1071 {0x1c, "(reserved)", op__reserved_
},
1072 {0x1d, "(reserved)", op__reserved_
},
1073 {0x1e, "STI", op_sti
},
1074 {0x1f, "STD", op_std
},
1079 void dump_operand_value_(DCPU16_WORD value
, DCPU16_WORD nextword
, unsigned int value_position
) {
1080 if (value
<= 0x07) {
1081 printf(" %s", dcpu16_reg_names
[value
]);
1082 } else if (value
<= 0x0f) {
1083 printf(" [%s]", dcpu16_reg_names
[value
& 0x07]);
1084 } else if (value
<= 0x17) {
1085 printf(" [0x%04x + %s]", nextword
, dcpu16_reg_names
[value
& 0x07]);
1086 } else switch (value
) {
1088 if (value_position
== 0) { /* b */
1094 case 0x19: printf(" PEEK"); break;
1095 case 0x1a: printf(" PICK 0x%04x", nextword
); break;
1096 case 0x1b: printf(" SP"); break;
1097 case 0x1c: printf(" PC"); break;
1098 case 0x1d: printf(" EX"); break;
1099 case 0x1e: printf(" [0x%04x]", nextword
); break;
1100 case 0x1f: printf(" 0x%04x", nextword
); break;
1101 default: printf(" 0x%02x", value
- 0x21);
1106 /* split a sequence of (one to three) words into the components of an instruction */
1108 void instruction_decode_(DCPU16_WORD
*mem
, DCPU16_WORD addr
,
1109 DCPU16_WORD
*opcode
, DCPU16_WORD
*b
, DCPU16_WORD
**b_data
, DCPU16_WORD
*a
, DCPU16_WORD
**a_data
,
1110 DCPU16_WORD
*instr_len
) {
1111 *opcode
= *a
= *b
= mem
[addr
];
1112 *opcode
= mem
[addr
] & ((1 << OPCODE_BASIC_BITS
) - 1);
1113 *b
= (mem
[addr
] >> OPCODE_BASIC_BITS
) & ((1 << OPCODE_OPERAND_B_BITS
) - 1);
1114 *a
= (mem
[addr
] >> (OPCODE_BASIC_BITS
+ OPCODE_OPERAND_B_BITS
)) & ((1 << OPCODE_OPERAND_A_BITS
) - 1);
1117 if ((*opcode
!= 0x0000) &&
1118 ( (*b
>= 0x10 && *b
<= 0x17) || *b
== 0x1e || *b
== 0x1f ) ) {
1119 *b_data
= mem
+ (DCPU16_WORD
)(addr
+ *instr_len
);
1125 if ( (*opcode
!= 0x0000 || (*opcode
== 0 && *b
!= 0x0000) )
1126 && ( (*a
>= 0x10 && *a
<= 0x17) || *a
== 0x1e || *a
== 0x1f) ) {
1127 *a_data
= mem
+ (DCPU16_WORD
)(addr
+ *instr_len
);
1133 TRACE("\n%s: [0x%04x]:0x%04x op:0x%02x b:0x%02x (b_data:0x%04x) a:0x%02x (a_data:0x%04x) len:0x%02x\n",
1139 *b_data
? **b_data
: 0,
1141 *a_data
? **a_data
: 0,
1145 /* dcpu16_mnemonify_buf
1146 print words as words
1148 DCPU16_WORD
dcpu16_mnemonify_buf(DCPU16_WORD
*buf
) {
1149 DCPU16_WORD opcode
, b
, a
, instr_len
, *b_data
, *a_data
;
1150 const struct opcode_entry
*e
;
1152 instruction_decode_(buf
, 0, &opcode
, &b
, &b_data
, &a
, &a_data
, &instr_len
);
1154 if (opcode
== 0x0000)
1155 e
= opcode_nbi_entries
+
1156 ( (b
< OPCODE_NBI_MAX
) ? b
: (OPCODE_NBI_MAX
- 1) );
1158 e
= opcode_basic_entries
+ opcode
;
1159 printf("%s", e
->name
);
1162 dump_operand_value_(b
, b_data
? *b_data
: 0, 0);
1167 dump_operand_value_(a
, a_data
? *a_data
: 0, 1);
1173 /* dcpu16_disassemble_print
1174 print the words of the instruction at addr, followed by its assembly representation
1175 returns the length of the instruction in words
1177 DCPU16_WORD
dcpu16_disassemble_print(struct dcpu16
*vm
, DCPU16_WORD addr
) {
1178 DCPU16_WORD opcode
, b
, a
, instr_len
, i
, *b_data
, *a_data
;
1179 DCPU16_WORD buf
[3] = { vm
->ram
[addr
], vm
->ram
[(DCPU16_WORD
)(addr
+ 1)], vm
->ram
[(DCPU16_WORD
)(addr
+ 2)] };
1180 unsigned int indent
= 0;
1181 unsigned int partial
= 0;
1187 Check the previous instruction, to see if this one should be
1188 indented. This check isn't foolproof, as preceeding addresses
1189 could be data which happen to match instructions..
1191 for (i
= 3; i
; i
--) {
1192 instruction_decode_(vm
->ram
, (DCPU16_WORD
)(addr
- i
), &opcode
, &b
, &b_data
, &a
, &a_data
, &instr_len
);
1196 && (opcode
>= 0x10 && opcode
<= 0x17) ) {
1203 /* just need instr_len */
1204 instruction_decode_(vm
->ram
, addr
, &opcode
, &b
, &b_data
, &a
, &a_data
, &instr_len
);
1206 /* show the raw words */
1207 printf("%04x", vm
->ram
[addr
]);
1208 for (i
= 1; i
< instr_len
; i
++) {
1209 printf(" %04x", vm
->ram
[addr
+ i
]);
1212 /* align things neatly, show the instruction */
1213 printf("%s%s ;%s%s",
1214 instr_len
< 3 ? " " : "",
1215 instr_len
< 2 ? " " : "",
1216 partial
? "*" : " ",
1219 dcpu16_mnemonify_buf(buf
);
1224 int dcpu16_interrupt(struct dcpu16
*vm
, DCPU16_WORD message
) {
1225 TRACE("%s>> message:0x%04x", __func__
, message
);
1226 return interrupt_enqueue_(vm
, message
);
1229 /* execute the next instruction */
1230 void dcpu16_step(struct dcpu16
*vm
) {
1231 DCPU16_WORD opcode
, b
, a
, instr_len
, *b_data
, *a_data
;
1233 const struct opcode_entry
*e
;
1238 /* signal interested parties that a new cycle has ticked */
1239 TRACE("%s>> sending global cycle event", __func__
);
1240 acct_event_(vm
, DCPU16_ACCT_EV_CYCLE
, vm
->reg
[DCPU16_REG_PC
]);
1242 /* signal attached hardware */
1243 for (i
= 0; i
< vm
->hw_table_entries_
; i
++) {
1244 if (vm
->hw_table_
[i
].cycle
) {
1245 TRACE("%s>> sending cycle to %s", __func__
, vm
->hw_table_
[i
].name_
);
1246 vm
->hw_table_
[i
].cycle(vm
, vm
->hw_table_
[i
].data
);
1250 instruction_decode_(vm
->ram
, vm
->reg
[DCPU16_REG_PC
], &opcode
, &b
, &b_data
, &a
, &a_data
, &instr_len
);
1252 /* consume what we decoded */
1253 /* this happens immediately as PC might be re-set as an operation */
1254 vm
->reg
[DCPU16_REG_PC
] += instr_len
;
1256 /* run the operation */
1257 for (e
= opcode_basic_entries
; e
->impl
; e
++) {
1258 if (e
->value
== opcode
) {
1259 TRACE("%s>> %s 0x%04x, 0x%04x", __func__
, e
->name
, b
, a
);
1260 e
->impl(vm
, b
, b_data
? *b_data
: 0, a
, a_data
? *a_data
: 0);
1265 /* and jump over next instr(s) if needed */
1267 instruction_decode_(vm
->ram
, vm
->reg
[DCPU16_REG_PC
], &opcode
, &b
, &b_data
, &a
, &a_data
, &instr_len
);
1268 vm
->reg
[DCPU16_REG_PC
] += instr_len
;
1269 TRACE("++ SKIPPED %x words", instr_len
);
1270 if (opcode
>= 0x10 && opcode
<= 0x17) {
1271 /* skipping a branch instruction? skip branch's skippable instruction as well */
1278 /* if we're currently servicing interrupts */
1279 if (vm
->interrupts_deferred_
== 0) {
1280 /* and there are interrupts to be serviced */
1281 if (vm
->interrupts_head_
!= vm
->interrupts_tail_
) {
1282 DCPU16_WORD message
;
1283 message
= interrupt_dequeue_(vm
);
1285 if (vm
->reg
[DCPU16_REG_IA
]) {
1286 TRACE("%s>> servicing interrupt IA:0x%04x message:0x%04x \n", __func__
, vm
->reg
[DCPU16_REG_IA
], message
);
1287 /* then service the next interrupt */
1288 vm
->interrupts_deferred_
= 1;
1289 vm
->ram
[--vm
->reg
[DCPU16_REG_SP
]] = vm
->reg
[DCPU16_REG_PC
];
1290 vm
->ram
[--vm
->reg
[DCPU16_REG_SP
]] = vm
->reg
[DCPU16_REG_A
];
1291 vm
->reg
[DCPU16_REG_PC
] = vm
->reg
[DCPU16_REG_IA
];
1292 vm
->reg
[DCPU16_REG_A
] = message
;
1294 TRACE("%s>> ignoring interrupt IA:0", __func__
);
1301 print the current state of the machine
1302 shows current cycle count, registers, and next instruction
1304 void dcpu16_state_print(struct dcpu16
*vm
) {
1310 for (i
= 0; i
< 8; i
++)
1311 printf(" %s:0x%04x", dcpu16_reg_names
[i
], vm
->reg
[i
]);
1314 printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
1316 dcpu16_reg_names
[DCPU16_REG_EX
], vm
->reg
[DCPU16_REG_EX
],
1317 dcpu16_reg_names
[DCPU16_REG_SP
], vm
->reg
[DCPU16_REG_SP
],
1318 dcpu16_reg_names
[DCPU16_REG_PC
], vm
->reg
[DCPU16_REG_PC
],
1319 dcpu16_reg_names
[DCPU16_REG_IA
], vm
->reg
[DCPU16_REG_IA
],
1322 dcpu16_disassemble_print(vm
, vm
->reg
[DCPU16_REG_PC
]);
1327 * print raw ram contents from start to stop
1329 void dcpu16_dump_ram(struct dcpu16
*vm
, DCPU16_WORD start
, DCPU16_WORD end
) {
1331 const unsigned int n
= 8; /* words per line */
1335 for (i
= start
, j
= 0; i
<= end
; i
++, j
++) {
1337 printf("0x%04x:\t", i
);
1338 printf(" %04x%s", vm
->ram
[i
], (j
% n
) == (n
- 1) ? "\n" : "");
1340 if ((j
% n
) != (n
- 1))
1345 * registers new 'hardware' device with system
1347 int dcpu16_hw_add(struct dcpu16
*vm
, struct dcpu16_hw
*hw
) {
1351 TRACE("%s>> name:%s ID:0x%04x%04x MFG:0x%04x%04x VER:0x%04x",
1355 hw
->mfg_l
, hw
->mfg_h
,
1358 if (vm
->hw_table_entries_
== 0xffff) {
1359 WARN("maximum hardware entries reached");
1363 if (vm
->hw_table_entries_
== vm
->hw_table_allocated_
) {
1364 size_t new_entries
= vm
->hw_table_allocated_
+ 32;
1365 void *tmp_ptr
= realloc(vm
->hw_table_
, new_entries
* sizeof * (vm
->hw_table_
));
1366 if (tmp_ptr
== NULL
) {
1367 fprintf(stderr
, "%s():%s", "realloc", strerror(errno
));
1370 vm
->hw_table_
= tmp_ptr
;
1371 vm
->hw_table_allocated_
+= 32;
1374 memcpy(vm
->hw_table_
+ vm
->hw_table_entries_
, hw
, sizeof *hw
);
1375 vm
->hw_table_entries_
++;
1377 TRACE("%s>> added hw entry %zu", __func__
, vm
->hw_table_entries_
);
1383 * Register callback fn to be triggered whenever event matching any events
1384 * in bitwise mask occur.
1386 int dcpu16_acct_add(struct dcpu16
*vm
, dcpu16_acct_event mask
, dcpu16_ev_cb_t
*fn
, void *data
) {
1387 struct dcpu16_acct_cb cb
;
1396 if (vm
->cb_table_entries_
== vm
->cb_table_allocated_
) {
1397 size_t new_entries
= vm
->cb_table_allocated_
+ 32;
1398 void *tmp_ptr
= realloc(vm
->cb_table_
, new_entries
* sizeof *(vm
->cb_table_
));
1399 if (tmp_ptr
== NULL
) {
1400 fprintf(stderr
, "%s():%s", "realloc", strerror(errno
));
1403 vm
->cb_table_
= tmp_ptr
;
1404 vm
->cb_table_allocated_
+= 32;
1407 memcpy(vm
->cb_table_
+ vm
->cb_table_entries_
, &cb
, sizeof cb
);
1408 vm
->cb_table_entries_
++;
1410 TRACE("%s>> attached event callback %zu", __func__
, vm
->cb_table_entries_
);
1416 * signals cpu to reset, clearing runstate and ram, then reload any init callbacks
1418 void dcpu16_reset(struct dcpu16
*vm
) {
1424 TRACE("%s>> reset", __func__
);
1427 vm
->interrupts_deferred_
= 0;
1429 memset(vm
->interrupts_
, 0, sizeof vm
->interrupts_
);
1430 vm
->interrupts_head_
= 0;
1431 vm
->interrupts_tail_
= 0;
1433 /* signal attached hardware */
1434 for (i
= 0; i
< vm
->hw_table_entries_
; i
++) {
1435 if (vm
->hw_table_
[i
].reset
)
1436 vm
->hw_table_
[i
].reset(vm
, vm
->hw_table_
[i
].data
);
1439 memset(vm
->reg
, 0, sizeof vm
->reg
);
1440 memset(vm
->ram
, 0, sizeof vm
->ram
);
1443 acct_event_(vm
, DCPU16_ACCT_EV_RESET
, 0);
1447 * allocate a new dcpu16 instance
1449 struct dcpu16
*dcpu16_new(void) {
1452 vm
= calloc(1, sizeof *vm
);
1454 WARN("%s: %s(%zu): %s", __func__
, "calloc", strerror(errno
));
1456 vm
->warn_cb_
= warn_cb_
;
1457 vm
->trace_cb_
= trace_cb_
;
1463 * release a dcpu16 instance
1465 void dcpu16_delete(struct dcpu16
**vm
) {