#include #include #include #include #include #include #include #include #include "dcpu16.h" /* * emulates the DCPU16 system from http://0x10c.com/doc/dcpu-16.txt * currently emulates '1.7' spec from http://pastebin.com/Q4JvQvnM * * I couldn't remember ever implementing an emulator before, so this * happened. As such, consider this a toy in progress. * There are likely many improvable aspects. * * Justin Wind * 2012 04 05 - implementation started * 2012 05 05 - start of v1.7 revisions * 2012 05 08 - v1.7 revisions mostly complete * * TODO * !! v1.7 bit-shift and signed opcodes need to be reviewed/finished * !! v1.7 hardware interface needs to be finished * !! v1.7 interrupts need to be finished * change api to print into buffers rather than stdio * let callbacks determine whether to override events, or just observe * sort init callbacks by base addr, to call in-order * make all callbacks register addr range of interest */ static const char * const src_id_ = "$Id$"; #define OPCODE_BASIC_BITS 5 #define OPCODE_OPERAND_B_BITS 5 #define OPCODE_OPERAND_A_BITS 6 const char * const dcpu16_reg_names[] = { "A", "B", "C", "X", "Y", "Z", "I", "J", "PC", "SP", "EX", "IA", NULL }; #define MSG_(__level__, __vm__, ...) do { ((__vm__) ? ((struct dcpu16 *)(__vm__))->msg_cb_ : dcpu16_msg_)((__level__), __VA_ARGS__); } while (0) #define MSG_INFO(__vm__,...) MSG_(DCPU16_MSG_INFO, __vm__, __VA_ARGS__) #define MSG_ERROR(__vm__,...) MSG_(DCPU16_MSG_ERROR, __vm__, __VA_ARGS__) #ifdef DEBUG #define MSG_DEBUG(__vm__,...) MSG_(DCPU16_MSG_DEBUG, __vm__, __VA_ARGS__) #else /* DEBUG */ #define MSG_DEBUG(__vm__,...) do {} while (0) #endif /* DEBUG */ #ifdef DEBUG_DECODE #define MSG_DEBUG_DECODE (DCPU16_MSG_DEBUG + 2) #endif /* DEBUG_DECODE /* messages could be sent nowhere */ static void msg_null_(unsigned int l, char *fmt, ...) { (void)l, (void)fmt; } /* messages default to standard streams */ static void msg_default_(unsigned int, char *, ...) __attribute__((format(printf, 2, 3))); static inline void msg_default_(unsigned int l, char *fmt, ...) { static const char * const msg_tag_[] = { "info", "error", "debug" }; FILE *f = (l <= DCPU16_MSG_INFO) ? stderr : stdout; va_list ap; if (l < sizeof msg_tag_ / sizeof *msg_tag_) fprintf(f, "[%s] ", msg_tag_[l]); else fprintf(f, "[%u] ", l); va_start(ap, fmt); vfprintf(f, fmt, ap); va_end(ap); fprintf(f, "\n"); fflush(f); } /* dcpu16 message callback * This function pointer is copied into newly instantiated dcpu16 structures, * and is invoked directly for messages independant of a dcpu16 context. */ dcpu16_msg_cb_t *dcpu16_msg_ = msg_default_; /* set a new default message callback */ /* returns the previous setting */ dcpu16_msg_cb_t *dcpu16_msg_set_default(dcpu16_msg_cb_t *msg_cb) { dcpu16_msg_cb_t *r = dcpu16_msg_; dcpu16_msg_ = msg_cb ? msg_cb : msg_null_; return r; } /* set a new callback */ dcpu16_msg_cb_t *dcpu16_msg_set(struct dcpu16 *vm, dcpu16_msg_cb_t *msg_cb) { dcpu16_msg_cb_t *r = vm->msg_cb_; vm->msg_cb_ = msg_cb ? msg_cb : msg_null_; return r; } /* acct_event_ * invokes callbacks for specified event */ static inline void acct_event_(struct dcpu16 *vm, dcpu16_acct_event ev, DCPU16_WORD addr) { struct dcpu16_acct_cb *cb = vm->cb_table_; size_t i; for (i = 0; i < vm->cb_table_entries_; i++) { if ( (cb[i].mask & ev) ) if (addr >= cb[i].addr_l && addr <= cb[i].addr_h) cb[i].fn(vm, ev, addr, cb[i].data); } } /* add an entry to the interrupt queue */ static int interrupt_enqueue_(struct dcpu16 *vm, DCPU16_WORD message) { vm->interrupts_[vm->interrupts_tail_] = message; vm->interrupts_tail_ += 1; vm->interrupts_tail_ %= DCPU16_INTERRUPT_QUEUE_SIZE; if (vm->interrupts_tail_ == vm->interrupts_head_) { vm->on_fire_ = 1; MSG_INFO(vm, "interrupt queue overflow (system is now on fire)"); return -1; } return 0; } static DCPU16_WORD interrupt_dequeue_(struct dcpu16 *vm) { DCPU16_WORD message; if (vm->interrupts_tail_ == vm->interrupts_head_) { MSG_INFO(vm, "interrupt underflow"); return 0; } message = vm->interrupts_[vm->interrupts_head_]; vm->interrupts_head_ += 1; vm->interrupts_head_ %= DCPU16_INTERRUPT_QUEUE_SIZE; return message; } inline void dcpu16_cycle_inc(struct dcpu16 *vm, unsigned int n) { size_t i; while (n--) { /* new cycle */ vm->cycle_ += 1; MSG_DEBUG(vm, "%s>> starting cycle %llu", __func__, vm->cycle_); /* signal interested cycle hooks */ acct_event_(vm, DCPU16_ACCT_EV_CYCLE, vm->reg[DCPU16_REG_PC]); /* signal attached hardware */ for (i = 0; i < vm->hw_table_entries_; i++) { MSG_DEBUG(vm, "%s>> notifying %s", __func__, vm->hw_table_[i].mod->name_); if (vm->hw_table_[i].mod->cycle) vm->hw_table_[i].mod->cycle(vm, &vm->hw_table_[i]); } } } /* value_decode_ * sets *v to be the address of the represented value * value_is_a is 0 for b, 1 for a, alters behavior of some operands * value_data is 'nextword' for this operand, ignored if unused * workv is buffer to use to accumulate literal value into, before use. one exists for either potential instruction operand * e_addr is set to a referenced address, for accounting callback * pc_adjust is set to how to change the program counter * stack_adjust is set to how to change the stack pointer * cycle_adjust is set to number of cycles spent looking up operand * * zero all adjustables before decoding first operand, and pass in these values when * decoding next operand.. * */ #define EWHAT_NONE (0) #define EWHAT_REG (1<<1) #define EWHAT_RAM (1<<2) static inline void value_decode_(struct dcpu16 *vm, DCPU16_WORD value, unsigned int value_is_a, DCPU16_WORD value_data, DCPU16_WORD *work_v, DCPU16_WORD **v, DCPU16_WORD *e_addr, enum dcpu16_register_indexes *e_reg, unsigned int *e_what, short *pc_adjust, short *sp_adjust, unsigned int *cycle_adjust) { assert(value <= 0x3f); DCPU16_WORD pc = (DCPU16_WORD)(vm->reg[DCPU16_REG_PC] + *pc_adjust), sp = (DCPU16_WORD)(vm->reg[DCPU16_REG_SP] + *sp_adjust); (void)pc; #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, vm ,"%s>> is_a:%u pc:0x%04x sp:0x%04x value_data:0x%04x\n", __func__, value_is_a, pc, sp, value_data); #endif /* DEBUG_DECODE */ if (value <= 0x07) { /* register */ *e_what = EWHAT_REG; *e_reg = value & 0x07; *v = vm->reg + *e_reg; #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, vm, "%s>> %s (0x%04x)", __func__, dcpu16_reg_names[value], **v); #endif /* DEBUG_DECODE */ return; } if (value <= 0x0f) { /* [register] */ *e_what = EWHAT_RAM; *e_addr = vm->reg[value & 0x07]; *v = &(vm->ram[ *e_addr ]); acct_event_(vm, DCPU16_ACCT_EV_REG_READ, value & 0x07); #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, vm, "%s>> [%s] [0x%04x] (0x%04x)", __func__, dcpu16_reg_names[value & 0x07], vm->reg[value & 0x07], **v); #endif /* DEBUG_DECODE */ return; } if (value <= 0x17) { /* [next word + register] */ acct_event_(vm, DCPU16_ACCT_EV_REG_WRITE, DCPU16_REG_PC); *pc_adjust += 1; /* consume next word */ *cycle_adjust += 1; *e_what = EWHAT_RAM; *e_addr = value_data + vm->reg[value & 0x07]; acct_event_(vm, DCPU16_ACCT_EV_REG_READ, value & 0x07); *v = vm->ram + *e_addr; #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, vm, "%s>> [nextword + %s] [0x%04x + 0x%04x] (0x%04x)", __func__, dcpu16_reg_names[value & 0x07], value_data, vm->reg[value & 0x07], **v); #endif /* DEBUG_DECODE */ return; } switch (value) { case 0x18: /* PUSH/[--SP] or POP/[SP++] */ *e_what = EWHAT_RAM; acct_event_(vm, DCPU16_ACCT_EV_REG_READ, DCPU16_REG_SP); acct_event_(vm, DCPU16_ACCT_EV_REG_WRITE, DCPU16_REG_SP); if (value_is_a == 0) { /* b */ *v = &(vm->ram[sp - 1]); *sp_adjust -= 1; *e_addr = sp - 1; #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, vm, "%s>> PUSH [0x%04x] (0x%04x)", __func__, sp - 1, **v); #endif /* DEBUG_DECODE */ } else { /* a */ *v = &(vm->ram[sp]); *sp_adjust += 1; *e_addr = sp; #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, vm, "%s>> POP [0x%04x] (0x%04x)", __func__, sp, **v); #endif /* DEBUG_DECODE */ } break; case 0x19: /* PEEK/[SP] */ acct_event_(vm, DCPU16_ACCT_EV_REG_READ, DCPU16_REG_SP); *e_what = EWHAT_RAM; *v = &(vm->ram[sp]); *e_addr = sp; #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, vm, "%s>> PEEK [0x%04x] (0x%04x)", __func__, sp, **v); #endif /* DEBUG_DECODE */ break; case 0x1a: /* PICK n */ acct_event_(vm, DCPU16_ACCT_EV_REG_READ, DCPU16_REG_SP); *e_what = EWHAT_RAM; *pc_adjust += 1; *cycle_adjust += 1; *e_addr = sp + value_data; *v = vm->ram + *e_addr; #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, vm, "%s>> PICK 0x%04x [0x%04x] (0x%04x)", __func__, value_data, sp + value_data, **v); #endif /* DEBUG_DECODE */ break; case 0x1b: /* SP */ *e_reg = DCPU16_REG_SP; *e_what = EWHAT_REG; *v = &(vm->reg[DCPU16_REG_SP]); #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, vm, "%s>> %s (0x%04x)", __func__, dcpu16_reg_names[DCPU16_REG_SP], **v); #endif /* DEBUG_DECODE */ break; case 0x1c: /* PC */ *e_reg = DCPU16_REG_PC; *e_what = EWHAT_REG; *v = &(vm->reg[DCPU16_REG_PC]); #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, vm, "%s>> %s (0x%04x)", __func__, dcpu16_reg_names[DCPU16_REG_PC], **v); #endif /* DEBUG_DECODE */ break; case 0x1d: /* EX */ *e_reg = DCPU16_REG_EX; *e_what = EWHAT_REG; *v = &(vm->reg[DCPU16_REG_EX]); #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, vm, "%s>> %s (0x%04x)", __func__, dcpu16_reg_names[DCPU16_REG_EX], **v); #endif /* DEBUG_DECODE */ break; case 0x1e: /* [next word] / [[pc++]] */ *e_what = EWHAT_RAM; acct_event_(vm, DCPU16_ACCT_EV_REG_WRITE, DCPU16_REG_PC); *pc_adjust += 1; *cycle_adjust += 1; *e_addr = value_data; *v = vm->ram + *e_addr; #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, vm, "%s>> [nextword] [0x%04x] (0x%04x)", __func__, value_data, **v); #endif /* DEBUG_DECODE */ break; case 0x1f: /* next word (literal) / [pc++] */ *e_what = EWHAT_NONE; acct_event_(vm, DCPU16_ACCT_EV_REG_WRITE, DCPU16_REG_PC); *pc_adjust += 1; *cycle_adjust += 1; *work_v = value_data; *v = work_v; #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, vm, "%s>> nextword (0x%04x)", __func__, **v); #endif /* DEBUG_DECODE */ break; default: /* 0x20-0x3f: literal values 0xffff-0x1e */ *e_what = EWHAT_NONE; *work_v = (value & 0x1f) - 1; *v = work_v; #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, vm, "%s>> literal (0x%04x)", __func__, **v); #endif /* DEBUG_DECODE */ } } #define OPCODE_NAME_LEN 16 struct opcode_entry { unsigned short value; char name[OPCODE_NAME_LEN]; void (*impl)(struct dcpu16 *, DCPU16_WORD, DCPU16_WORD, DCPU16_WORD, DCPU16_WORD); }; /* messy boilerplate for opcode handlers */ /* opcode doesn't adjust its own PC, the step function which invoked it handles that */ /* opcode does adjust stack and cycle count */ #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) #define OP_TYPE(op_type) DCPU16_WORD *a, *b;\ DCPU16_WORD ev_a_addr = 0, ev_b_addr = 0;\ enum dcpu16_register_indexes ev_a_reg = DCPU16_REG__NUM, ev_b_reg = DCPU16_REG__NUM;\ unsigned int ev_a_what = 0, ev_b_what = 0;\ short pc_adjust = 0, sp_adjust = 0;\ unsigned int cycle_adjust = 0;\ do {\ op_type;\ value_decode_(vm, val_a, 1, val_a_data,\ &vm->reg_work_[1], &a, &ev_a_addr, &ev_a_reg, &ev_a_what,\ &pc_adjust, &sp_adjust, &cycle_adjust);\ vm->reg[DCPU16_REG_SP] += sp_adjust;\ if (cycle_adjust) dcpu16_cycle_inc(vm, cycle_adjust);\ } while (0) #define OP_NBI_ (void)val_b, (void)b, (void)ev_b_addr, (void)val_b_data, (void)ev_b_reg, (void)ev_b_what #define OP_BASIC_ value_decode_(vm, val_b, 0, val_b_data,\ &vm->reg_work_[0], &b, &ev_b_addr, &ev_b_reg, &ev_b_what,\ &pc_adjust, &sp_adjust, &cycle_adjust) #define OP_BASIC(x) OP_TYPE(OP_BASIC_) #define OP_NBI(x) OP_TYPE(OP_NBI_) /* 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 */ /* accounting helpers, these fire off the related callbacks for memory reads, memory writes, and execution of reserved instructions */ #define ACCT_ILL(addr) do { acct_event_(vm, DCPU16_ACCT_EV_NOP, addr); } while (0) #define ACCT_RAM_R(addr) do { acct_event_(vm, DCPU16_ACCT_EV_READ, addr); } while (0) #define ACCT_RAM_W(addr) do { acct_event_(vm, DCPU16_ACCT_EV_WRITE, addr); } while (0) #define ACCT_REG_R(reg) do { acct_event_(vm, DCPU16_ACCT_EV_REG_READ, reg); } while (0) #define ACCT_REG_W(reg) do { acct_event_(vm, DCPU16_ACCT_EV_REG_WRITE, reg); } while (0) #define ACCT_R(__x__) do {\ if (ev_##__x__##_what & EWHAT_REG) ACCT_REG_R(ev_##__x__##_reg);\ if (ev_##__x__##_what & EWHAT_RAM) ACCT_RAM_R(ev_##__x__##_addr);\ } while (0) #define ACCT_W(__x__) do {\ if (ev_##__x__##_what & EWHAT_REG) ACCT_REG_W(ev_##__x__##_reg);\ if (ev_##__x__##_what & EWHAT_RAM) ACCT_RAM_W(ev_##__x__##_addr);\ } while (0) /* extended opcodes */ /* N.B. this next function currently decodes values -- id est, it is an opcode processing terminus; however, if 'future instruction set expansion' happens, this will probably need to behave more like the OP_IMPL(_nbi_) function which invoked it, if those instructions have zero or differently styled operands. */ OP_IMPL(nbi__reserved_) { OP_NBI(nbi__reserved_); /* reserved for future expansion */ /* fire an illegal instruction event for current instruction */ DCPU16_WORD future_opcode = (vm->ram[vm->reg[DCPU16_REG_PC] - pc_adjust] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_B_BITS)); MSG_INFO(vm, "reserved future opcode 0x%04x invoked", future_opcode); ACCT_ILL(vm->reg[DCPU16_REG_PC] - pc_adjust); dcpu16_cycle_inc(vm, 1); } OP_IMPL(nbi_jsr) { OP_NBI(nbi_jsr); /* pushes the address of the next instruction to the stack, then sets PC to a */ ACCT_R(a); ACCT_REG_R(DCPU16_REG_PC); ACCT_REG_R(DCPU16_REG_SP); vm->ram[ --vm->reg[DCPU16_REG_SP] ] = vm->reg[DCPU16_REG_PC]; ACCT_REG_W(DCPU16_REG_SP); ACCT_RAM_W(vm->reg[DCPU16_REG_SP] + 1); vm->reg[DCPU16_REG_PC] = *a; ACCT_REG_W(DCPU16_REG_PC); dcpu16_cycle_inc(vm, 2); } OP_IMPL(nbi__reserved2_) { OP_NBI(nbi__reserved2_); /* reserved */ MSG_INFO(vm, "reserved nbi opcode invoked"); ACCT_ILL(vm->reg[DCPU16_REG_PC] - pc_adjust); dcpu16_cycle_inc(vm, 1); } OP_IMPL(nbi_int) { OP_NBI(nbi_int); ACCT_R(a); ACCT_REG_R(DCPU16_REG_IA); if (vm->reg[DCPU16_REG_IA]) { if ( interrupt_enqueue_(vm, *a) ) { MSG_INFO(vm, "failed to queue interrupt"); return; } if (vm->interrupts_deferred_) return; vm->interrupts_deferred_ = 1; ACCT_REG_R(DCPU16_REG_PC); ACCT_REG_R(DCPU16_REG_SP); vm->ram[--vm->reg[DCPU16_REG_SP]] = vm->reg[DCPU16_REG_PC]; ACCT_RAM_W(vm->reg[DCPU16_REG_SP] + 1); ACCT_REG_W(DCPU16_REG_SP); ACCT_REG_R(DCPU16_REG_A); ACCT_REG_R(DCPU16_REG_SP); vm->ram[--vm->reg[DCPU16_REG_SP]] = vm->reg[DCPU16_REG_A]; ACCT_RAM_W(vm->reg[DCPU16_REG_SP] + 1); ACCT_REG_W(DCPU16_REG_SP); ACCT_REG_R(DCPU16_REG_IA); vm->reg[DCPU16_REG_PC] = vm->reg[DCPU16_REG_IA]; ACCT_REG_W(DCPU16_REG_PC); vm->reg[DCPU16_REG_A] = *a; ACCT_REG_W(DCPU16_REG_A); } dcpu16_cycle_inc(vm, 4); } OP_IMPL(nbi_iag) { OP_NBI(nbi_iag); ACCT_REG_R(DCPU16_REG_IA); *a = vm->reg[DCPU16_REG_IA]; ACCT_W(a); dcpu16_cycle_inc(vm, 1); } OP_IMPL(nbi_ias) { OP_NBI(nbi_ias); ACCT_R(a); vm->reg[DCPU16_REG_IA] = *a; ACCT_REG_W(DCPU16_REG_IA); dcpu16_cycle_inc(vm, 1); } /* does this just ignore its operand? */ OP_IMPL(nbi_rfi) { OP_NBI(nbi_rfi); /* well, it consumes the argument, currently, so I guess pretend like we care */ ACCT_R(a); vm->interrupts_deferred_ = 0; ACCT_REG_R(DCPU16_REG_SP); ACCT_RAM_R(vm->reg[DCPU16_REG_SP]); vm->reg[DCPU16_REG_A] = vm->ram[vm->reg[DCPU16_REG_SP]++]; ACCT_REG_W(DCPU16_REG_A); ACCT_REG_W(DCPU16_REG_SP); ACCT_REG_R(DCPU16_REG_SP); ACCT_RAM_R(vm->reg[DCPU16_REG_SP]); vm->reg[DCPU16_REG_PC] = vm->ram[vm->reg[DCPU16_REG_SP]++]; ACCT_REG_W(DCPU16_REG_PC); ACCT_REG_W(DCPU16_REG_SP); dcpu16_cycle_inc(vm, 3); } OP_IMPL(nbi_iaq) { OP_NBI(nbi_iaq); ACCT_R(a); if (*a) { vm->interrupts_deferred_ = 1; } else { vm->interrupts_deferred_ = 0; } dcpu16_cycle_inc(vm, 2); } OP_IMPL(nbi_hwn) { OP_NBI(nbi_hwn); *a = vm->hw_table_entries_; ACCT_W(a); dcpu16_cycle_inc(vm, 2); } OP_IMPL(nbi_hwq) { OP_NBI(nbi_hwq); ACCT_R(a); if (*a >= vm->hw_table_entries_) { MSG_INFO(vm, "hardware query for non-extant device 0x%04x", *a); vm->reg[DCPU16_REG_A] = 0; vm->reg[DCPU16_REG_B] = 0; vm->reg[DCPU16_REG_C] = 0; vm->reg[DCPU16_REG_X] = 0; vm->reg[DCPU16_REG_Y] = 0; } else { vm->reg[DCPU16_REG_A] = vm->hw_table_[*a].mod->id_l; vm->reg[DCPU16_REG_B] = vm->hw_table_[*a].mod->id_h; vm->reg[DCPU16_REG_C] = vm->hw_table_[*a].mod->ver; vm->reg[DCPU16_REG_X] = vm->hw_table_[*a].mod->mfg_l; vm->reg[DCPU16_REG_Y] = vm->hw_table_[*a].mod->mfg_h; } ACCT_REG_W(DCPU16_REG_A); ACCT_REG_W(DCPU16_REG_B); ACCT_REG_W(DCPU16_REG_C); ACCT_REG_W(DCPU16_REG_X); ACCT_REG_W(DCPU16_REG_Y); dcpu16_cycle_inc(vm, 4); } OP_IMPL(nbi_hwi) { OP_NBI(nbi_hwi); ACCT_R(a); if (*a > vm->hw_table_entries_) { MSG_INFO(vm, "interrupt for non-extant device 0x%04x", *a); return; } if (vm->hw_table_[*a].mod->hwi) vm->hw_table_[*a].mod->hwi(vm, &vm->hw_table_[*a]); else MSG_INFO(vm, "hardware 0x%04x has no interrupt handler", *a); dcpu16_cycle_inc(vm, 4); } OP_IMPL(nbi_hcf) { OP_NBI(nbi_hcf); ACCT_R(a); vm->on_fire_ = 1; MSG_INFO(vm, "system on fire"); dcpu16_cycle_inc(vm, 9); } static const struct opcode_entry opcode_nbi_entries[] = { {0x00, "(reserved)", op_nbi__reserved_}, {0x01, "JSR", op_nbi_jsr}, {0x02, "(reserved)", op_nbi__reserved2_}, {0x03, "(reserved)", op_nbi__reserved2_}, {0x04, "(reserved)", op_nbi__reserved2_}, {0x05, "(reserved)", op_nbi__reserved2_}, {0x06, "(reserved)", op_nbi__reserved2_}, {0x07, "HCF", op_nbi_hcf}, /* undocumented */ {0x08, "INT", op_nbi_int}, {0x09, "IAG", op_nbi_iag}, {0x0a, "IAS", op_nbi_ias}, {0x0b, "RFI", op_nbi_rfi}, {0x0c, "IAQ", op_nbi_iaq}, {0x0d, "(reserved)", op_nbi__reserved2_}, {0x0e, "(reserved)", op_nbi__reserved2_}, {0x0f, "(reserved)", op_nbi__reserved2_}, {0x10, "HWN", op_nbi_hwn}, {0x11, "HWQ", op_nbi_hwq}, {0x12, "HWI", op_nbi_hwi}, {0x13, "(reserved)", op_nbi__reserved2_}, {0x00, "", NULL} }; #define OPCODE_NBI_MAX (((sizeof(opcode_nbi_entries)) / (sizeof(struct opcode_entry))) - 1) /* basic opcodes */ /* N.B. the following function does not decode values. Decoding is handled by the secondary opcode functions it calls. */ OP_IMPL(_nbi_) { /* non-basic instruction */ /* don't do normal value decoding here */ DCPU16_WORD nbi_opcode = val_b; const struct opcode_entry *e = opcode_nbi_entries; (void)val_b_data; e = opcode_nbi_entries + ( (nbi_opcode < OPCODE_NBI_MAX) ? nbi_opcode : (OPCODE_NBI_MAX - 1) ); assert(e->impl != NULL); MSG_DEBUG(vm, "%s>> %s 0x%04x", __func__, e->name, val_b); e->impl(vm, 0, 0, val_a, val_a_data); } OP_IMPL(set) { OP_BASIC(set); /* sets b to a */ ACCT_R(a); /* if b is a literal, it's aimed at a scratch register, so it's fine to update, as it won't have any effect. */ *b = *a; ACCT_W(b); dcpu16_cycle_inc(vm, 1); } OP_IMPL(add) { OP_BASIC(add); /* sets b to b+a, sets EX to 0x0001 if there's an overflow, 0x0 otherwise */ unsigned int acc = *b + *a; ACCT_R(b); ACCT_R(a); *b = acc; vm->reg[DCPU16_REG_EX] = (acc > 0xffff); ACCT_REG_W(DCPU16_REG_EX); ACCT_W(b); dcpu16_cycle_inc(vm, 2); } OP_IMPL(sub) { OP_BASIC(sub); /* sets b to b-a, sets EX to 0xffff if there's an underflow, 0x0 otherwise */ unsigned int acc = *b - *a; ACCT_R(b); ACCT_R(a); *b = acc; vm->reg[DCPU16_REG_EX] = (acc > 0xffff); ACCT_REG_W(DCPU16_REG_EX); ACCT_W(b); dcpu16_cycle_inc(vm, 2); } OP_IMPL(mul) { OP_BASIC(mul); /* sets b to b*a, unsigned, sets EX to ((b*a)>>16)&0xffff */ unsigned int acc = *b * *a; ACCT_R(b); ACCT_R(a); *b = acc; vm->reg[DCPU16_REG_EX] = acc >> 16; ACCT_REG_W(DCPU16_REG_EX); ACCT_W(b); dcpu16_cycle_inc(vm, 2); } OP_IMPL(mli) { OP_BASIC(mli); /* sets b to b*a, signed */ int acc = (short)*b * (short)*a; ACCT_R(b); ACCT_R(a); *b = acc; vm->reg[DCPU16_REG_EX] = acc >> 16; ACCT_REG_W(DCPU16_REG_EX); ACCT_W(b); dcpu16_cycle_inc(vm, 2); } OP_IMPL(div) { OP_BASIC(div); /* sets b to b/a, sets EX to ((b<<16)/a)&0xffff. if a==0, sets a and EX to 0 instead. */ ACCT_R(b); ACCT_R(a); if (*a == 0) { *b = 0; vm->reg[DCPU16_REG_EX] = 0; } else { *b = *b / *a; vm->reg[DCPU16_REG_EX] = (*b << 16) / *a; } ACCT_REG_W(DCPU16_REG_EX); ACCT_W(b); dcpu16_cycle_inc(vm, 3); } OP_IMPL(dvi) { OP_BASIC(dvi); /* sets b to b/a, signed, round towards 0 */ ACCT_R(b); ACCT_R(a); if (*a == 0) { *b = 0; vm->reg[DCPU16_REG_EX] = 0; } else { *b = (short)*b / (short)*a; vm->reg[DCPU16_REG_EX] = (short)(*b << 16) / (short)*a; } ACCT_REG_W(DCPU16_REG_EX); ACCT_W(b); dcpu16_cycle_inc(vm, 3); } OP_IMPL(mod) { OP_BASIC(mod); /* sets b to b%a. if a==0, sets b to 0 instead. */ ACCT_R(b); ACCT_R(a); if (*a == 0) { *b = 0; } else { *b = *b % *a; } ACCT_W(a); dcpu16_cycle_inc(vm, 3); } OP_IMPL(mdi) { OP_BASIC(mdi); /* sets b to b%a, signed */ ACCT_R(b); ACCT_R(a); if (*a == 0) { *b = 0; } else { *b = (short)*b % (short)*a; } ACCT_W(b); dcpu16_cycle_inc(vm, 3); } OP_IMPL(and) { OP_BASIC(and); /* sets b to b&a */ ACCT_R(b); ACCT_R(a); *b = *b & *a; ACCT_W(b); dcpu16_cycle_inc(vm, 1); } OP_IMPL(bor) { OP_BASIC(bor); /* sets b to b|a */ ACCT_R(b); ACCT_R(a); *b = *b | *a; ACCT_W(b); dcpu16_cycle_inc(vm, 1); } OP_IMPL(xor) { OP_BASIC(xor); /* sets b to b^a */ ACCT_R(b); ACCT_R(a); *b = *b ^ *a; dcpu16_cycle_inc(vm, 1); ACCT_W(b); } OP_IMPL(shr) { OP_BASIC(shr); /* sets b to b>>>a, sets EX to ((b<<16)>>a)&0xffff */ unsigned int acc = *b >> *a; ACCT_R(b); ACCT_R(a); *b = acc & 0xffff; vm->reg[DCPU16_REG_EX] = (*b << 16) >> *a; MSG_ERROR(vm, "IMPLEMENT"); ACCT_REG_W(DCPU16_REG_EX); ACCT_W(b); dcpu16_cycle_inc(vm, 2); } OP_IMPL(asr) { OP_BASIC(asr); /* sets b to b>>a, sets EX to ((b<<16)>>>a)&0xffff (arithmetic shift) (treats b as signed) */ unsigned int acc = *b << *a; ACCT_R(b); ACCT_R(a); *b = acc & 0xffff; vm->reg[DCPU16_REG_EX] = (*b << 16) >> *a; MSG_ERROR(vm, "IMPLEMENT"); ACCT_REG_W(DCPU16_REG_EX); ACCT_W(b); dcpu16_cycle_inc(vm, 2); } OP_IMPL(shl) { OP_BASIC(shl); /* sets b to b<>16)&0xffff */ unsigned int acc = *b << *a; ACCT_R(b); ACCT_R(a); *b = acc; vm->reg[DCPU16_REG_EX] = acc >> 16; ACCT_REG_W(DCPU16_REG_EX); ACCT_W(b); dcpu16_cycle_inc(vm, 2); } OP_IMPL(ifb) { OP_BASIC(ifb); /* performs next instruction only if (b&a)!=0 */ ACCT_R(b); ACCT_R(a); if ((*b & *a) != 0) { /* */ } else { vm->skip_ = 1; dcpu16_cycle_inc(vm, 1); } dcpu16_cycle_inc(vm, 2); } OP_IMPL(ifc) { OP_BASIC(ifc); /* performs next instruction only if (b&a)==0 */ ACCT_R(b); ACCT_R(a); if ((*b & *a) == 0) { /* */ } else { vm->skip_ = 1; dcpu16_cycle_inc(vm, 1); } dcpu16_cycle_inc(vm, 2); } OP_IMPL(ife) { OP_BASIC(ife); /* performs next instruction only if b==a */ ACCT_R(b); ACCT_R(a); if (*b == *a) { /* */ } else { vm->skip_ = 1; dcpu16_cycle_inc(vm, 1); } dcpu16_cycle_inc(vm, 2); } OP_IMPL(ifn) { OP_BASIC(ifn); /* performs next instruction only if b!=a */ ACCT_R(b); ACCT_R(a); if (*b != *a) { /* */ } else { vm->skip_ = 1; dcpu16_cycle_inc(vm, 1); } dcpu16_cycle_inc(vm, 2); } OP_IMPL(ifg) { OP_BASIC(ifg); /* performs next instruction only if b>a */ ACCT_R(b); ACCT_R(a); if (*b > *a) { /* */ } else { vm->skip_ = 1; dcpu16_cycle_inc(vm, 1); } dcpu16_cycle_inc(vm, 2); } OP_IMPL(ifa) { OP_BASIC(ifa); /* performs next instruction only if b>a (signed) */ ACCT_R(b); ACCT_R(a); if (*b > *a) { /* */ } else { vm->skip_ = 1; dcpu16_cycle_inc(vm, 1); } dcpu16_cycle_inc(vm, 2); } OP_IMPL(ifl) { OP_BASIC(ifl); /* performs next instruction only if bskip_ = 1; dcpu16_cycle_inc(vm, 1); } dcpu16_cycle_inc(vm, 2); } OP_IMPL(ifu) { OP_BASIC(ifu); /* performs next instruction only if bskip_ = 1; dcpu16_cycle_inc(vm, 1); } dcpu16_cycle_inc(vm, 2); } OP_IMPL(adx) { OP_BASIC(adx); /* sets b to b+a+EX, sets EX to 0x0001 if overflow, 0x0 otherwise */ unsigned int acc; ACCT_R(b); ACCT_R(a); ACCT_REG_R(DCPU16_REG_EX); acc = *b + *a + vm->reg[DCPU16_REG_EX]; *b = acc & 0xffff; if (acc > 0xffff) vm->reg[DCPU16_REG_EX] = 0x0001; else vm->reg[DCPU16_REG_EX] = 0x0000; ACCT_REG_W(DCPU16_REG_EX); ACCT_W(b); dcpu16_cycle_inc(vm, 3); } OP_IMPL(sbx) { OP_BASIC(sbx); /* sets b to b-a+EX, sets EX to 0xffff if underflow, 0x0 otherwise */ unsigned int acc; ACCT_R(b); ACCT_R(a); ACCT_REG_R(DCPU16_REG_EX); acc = *b - *a + vm->reg[DCPU16_REG_EX]; *b = acc & 0xffff; if (acc > 0xffff) vm->reg[DCPU16_REG_EX] = 0xffff; else vm->reg[DCPU16_REG_EX] = 0; ACCT_REG_W(DCPU16_REG_EX); ACCT_W(b); dcpu16_cycle_inc(vm, 3); } OP_IMPL(sti) { OP_BASIC(sti); /* sets b to a, then increases I and J by 1 */ ACCT_R(b); ACCT_R(a); *b = *a; vm->reg[DCPU16_REG_I] += 1; vm->reg[DCPU16_REG_J] += 1; ACCT_REG_W(DCPU16_REG_I); ACCT_REG_W(DCPU16_REG_J); ACCT_W(b); dcpu16_cycle_inc(vm, 2); } OP_IMPL(std) { OP_BASIC(std); /* sets b to a, then decreases I and J by 1 */ ACCT_R(b); ACCT_R(a); *b = *a; vm->reg[DCPU16_REG_I] -= 1; vm->reg[DCPU16_REG_J] -= 1; ACCT_REG_W(DCPU16_REG_I); ACCT_REG_W(DCPU16_REG_J); ACCT_W(b); dcpu16_cycle_inc(vm, 2); } OP_IMPL(_reserved_) { OP_BASIC(_reserved_); MSG_INFO(vm, "reserved opcode invoked"); ACCT_ILL(vm->reg[DCPU16_REG_PC] - pc_adjust); } static const struct opcode_entry opcode_basic_entries[] = { {0x00, "(nbi)", op__nbi_}, {0x01, "SET", op_set }, {0x02, "ADD", op_add }, {0x03, "SUB", op_sub }, {0x04, "MUL", op_mul }, {0x05, "MLI", op_mli }, {0x06, "DIV", op_div }, {0x07, "DVI", op_dvi }, {0x08, "MOD", op_mod }, {0x09, "MDI", op_mdi }, {0x0a, "AND", op_and }, {0x0b, "BOR", op_bor }, {0x0c, "XOR", op_xor }, {0x0d, "SHR", op_shr }, {0x0e, "ASR", op_asr }, {0x0f, "SHL", op_shl }, {0x10, "IFB", op_ifb }, {0x11, "IFC", op_ifc }, {0x12, "IFE", op_ife }, {0x13, "IFN", op_ifn }, {0x14, "IFG", op_ifg }, {0x15, "IFA", op_ifa }, {0x16, "IFL", op_ifl }, {0x17, "IFU", op_ifu }, {0x18, "(reserved)", op__reserved_ }, {0x19, "(reserved)", op__reserved_ }, {0x1a, "ADX", op_adx }, {0x1b, "SBX", op_sbx }, {0x1c, "(reserved)", op__reserved_ }, {0x1d, "(reserved)", op__reserved_ }, {0x1e, "STI", op_sti }, {0x1f, "STD", op_std }, {0x00, "", NULL } }; #define OPCODE_BASIC_MAX (((sizeof(opcode_basic_entries)) / (sizeof(struct opcode_entry))) - 1) static inline void dump_operand_value_(DCPU16_WORD value, DCPU16_WORD nextword, unsigned int value_position) { printf(" "); if (value <= 0x07) { printf("%s", dcpu16_reg_names[value]); } else if (value <= 0x0f) { printf("[%s]", dcpu16_reg_names[value & 0x07]); } else if (value <= 0x17) { printf("[0x%04x + %s]", nextword, dcpu16_reg_names[value & 0x07]); } else switch (value) { case 0x18: if (value_position == 0) { /* b */ printf("PUSH"); } else { printf("POP"); } break; case 0x19: printf("PEEK"); break; case 0x1a: printf("PICK 0x%04x", nextword); break; case 0x1b: printf("SP"); break; case 0x1c: printf("PC"); break; case 0x1d: printf("EX"); break; case 0x1e: printf("[0x%04x]", nextword); break; case 0x1f: printf("0x%04x", nextword); break; default: printf("0x%02x", value - 0x21); } } static inline int operand_snprint_(char *buf, size_t buf_sz, DCPU16_WORD value, DCPU16_WORD nextword, unsigned int operand_is_a) { int len; len = snprintf(buf, buf_sz, " "); if ((size_t)len >= buf_sz) return -1; buf += len, buf_sz -= len; if (value <= 0x07) { len = snprintf(buf, buf_sz, "%s", dcpu16_reg_names[value]); } else if (value <= 0x0f) { len = snprintf(buf, buf_sz, "[%s]", dcpu16_reg_names[value & 0x07]); } else if (value <= 0x17) { len = snprintf(buf, buf_sz, "[0x%04x + %s]", nextword, dcpu16_reg_names[value & 0x07]); } else switch (value) { case 0x18: if (operand_is_a == 0) { /* b */ len = snprintf(buf, buf_sz, "PUSH"); } else { len = snprintf(buf, buf_sz, "POP"); } break; case 0x19: len = snprintf(buf, buf_sz, "PEEK"); break; case 0x1a: len = snprintf(buf, buf_sz, "PICK 0x%04x", nextword); break; case 0x1b: len = snprintf(buf, buf_sz, "SP"); break; case 0x1c: len = snprintf(buf, buf_sz, "PC"); break; case 0x1d: len = snprintf(buf, buf_sz, "EX"); break; case 0x1e: len = snprintf(buf, buf_sz, "[0x%04x]", nextword); break; case 0x1f: len = snprintf(buf, buf_sz, "0x%04x", nextword); break; default: len = snprintf(buf, buf_sz, "0x%02x", (short)(value - 0x21)); } if ((size_t)len >= buf_sz) return -1; return len; } /* split a sequence of (one to three) words into the components of an instruction */ static inline void instruction_decode_(DCPU16_WORD *mem, DCPU16_WORD addr, DCPU16_WORD *opcode, DCPU16_WORD *b, DCPU16_WORD **b_data, DCPU16_WORD *a, DCPU16_WORD **a_data, DCPU16_WORD *instr_len) { *opcode = *a = *b = mem[addr]; *opcode = mem[addr] & ((1 << OPCODE_BASIC_BITS) - 1); *b = (mem[addr] >> OPCODE_BASIC_BITS) & ((1 << OPCODE_OPERAND_B_BITS) - 1); *a = (mem[addr] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_B_BITS)) & ((1 << OPCODE_OPERAND_A_BITS) - 1); *instr_len = 1; if ((*opcode != 0x0000) && ( (*b >= 0x10 && *b <= 0x17) || *b == 0x1e || *b == 0x1f ) ) { *b_data = mem + (DCPU16_WORD)(addr + *instr_len); *instr_len += 1; } else { *b_data = NULL; } if ( (*opcode != 0x0000 || (*opcode == 0 && *b != 0x0000) ) && ( (*a >= 0x10 && *a <= 0x17) || *a == 0x1e || *a == 0x1f) ) { *a_data = mem + (DCPU16_WORD)(addr + *instr_len); *instr_len += 1; } else { *a_data = NULL; } #ifdef DEBUG_DECODE MSG_(MSG_DEBUG_DECODE, NULL, "\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", __func__, addr, mem[addr], *opcode, *b, *b_data ? **b_data : 0, *a, *a_data ? **a_data : 0, *instr_len); #endif /* DEBUG_DECODE */ } /* dcpu16_mnemonify_buf print words as words */ DCPU16_WORD dcpu16_mnemonify_buf(DCPU16_WORD *buf) { DCPU16_WORD opcode, b, a, instr_len, *b_data, *a_data; const struct opcode_entry *e; char operand[16]; instruction_decode_(buf, 0, &opcode, &b, &b_data, &a, &a_data, &instr_len); if (opcode == 0x0000) e = opcode_nbi_entries + ( (b < OPCODE_NBI_MAX) ? b : (OPCODE_NBI_MAX - 1) ); else e = opcode_basic_entries + opcode; printf("%s", e->name); if (opcode) { operand_snprint_(operand, sizeof operand, b, b_data ? *b_data : 0, 0); printf("%s,", operand); } if (opcode || b) { operand_snprint_(operand, sizeof operand, a, a_data ? *a_data : 0, 1); printf("%s", operand); } return instr_len; } /* dcpu16_disassemble_print print the words of the instruction at addr, followed by its assembly representation returns the length of the instruction in words */ DCPU16_WORD dcpu16_disassemble_print(struct dcpu16 *vm, DCPU16_WORD addr) { DCPU16_WORD opcode, b, a, instr_len, i, *b_data, *a_data; DCPU16_WORD buf[3] = { vm->ram[addr], vm->ram[(DCPU16_WORD)(addr + 1)], vm->ram[(DCPU16_WORD)(addr + 2)] }; unsigned int indent = 0; unsigned int partial = 0; if (!vm) return 0; #if 0 /* Check the previous instruction, to see if this one should be indented. This check isn't foolproof, as preceeding addresses could be data which happen to match instructions.. */ for (i = 3; i; i--) { instruction_decode_(vm->ram, (DCPU16_WORD)(addr - i), &opcode, &b, &b_data, &a, &a_data, &instr_len); if (instr_len > i) partial++; if (instr_len == i && (opcode >= 0x10 && opcode <= 0x17) ) { indent++; break; } } #endif /* just need instr_len */ instruction_decode_(vm->ram, addr, &opcode, &b, &b_data, &a, &a_data, &instr_len); /* show the raw words */ printf("%04x", vm->ram[addr]); for (i = 1; i < instr_len; i++) { printf(" %04x", vm->ram[addr + i]); } /* align things neatly, show the instruction */ printf("%s%s ;%s%s", instr_len < 3 ? " " : "", instr_len < 2 ? " " : "", partial ? "*" : " ", indent ? " " : ""); dcpu16_mnemonify_buf(buf); return instr_len; } int dcpu16_interrupt(struct dcpu16 *vm, DCPU16_WORD message) { MSG_DEBUG(vm, "%s>> message:0x%04x", __func__, message); return interrupt_enqueue_(vm, message); } /* execute the next instruction */ void dcpu16_step(struct dcpu16 *vm) { DCPU16_WORD opcode, b, a, instr_len, *b_data, *a_data; const struct opcode_entry *e; if (!vm) return; instruction_decode_(vm->ram, vm->reg[DCPU16_REG_PC], &opcode, &b, &b_data, &a, &a_data, &instr_len); /* consume what we decoded */ /* this happens immediately as PC might be re-set as an operation */ vm->reg[DCPU16_REG_PC] += instr_len; /* run the operation */ e = opcode_basic_entries + opcode; MSG_DEBUG(vm, "%s", e->name ? e->name : "???"); e->impl(vm, b, b_data ? *b_data : 0, a, a_data ? *a_data : 0); /* and jump over next instr(s) if needed */ while (vm->skip_) { instruction_decode_(vm->ram, vm->reg[DCPU16_REG_PC], &opcode, &b, &b_data, &a, &a_data, &instr_len); vm->reg[DCPU16_REG_PC] += instr_len; MSG_DEBUG(vm, "%s>> ++ SKIPPED %x words", __func__, instr_len); if (opcode >= 0x10 && opcode <= 0x17) { /* skipping a branch instruction? skip branch's skippable instruction as well */ dcpu16_cycle_inc(vm, 1); } else { vm->skip_ = 0; } } /* if we're currently servicing interrupts */ if (vm->interrupts_deferred_ == 0) { /* and there are interrupts to be serviced */ if (vm->interrupts_head_ != vm->interrupts_tail_) { DCPU16_WORD message; message = interrupt_dequeue_(vm); MSG_DEBUG(vm, "%s>> %s interrupt IA:0x%04x message:0x%04x", __func__, vm->reg[DCPU16_REG_IA] ? "servicing" : "ignoring", vm->reg[DCPU16_REG_IA], message); if (vm->reg[DCPU16_REG_IA]) { /* then service the next interrupt */ vm->interrupts_deferred_ = 1; vm->ram[--vm->reg[DCPU16_REG_SP]] = vm->reg[DCPU16_REG_PC]; vm->ram[--vm->reg[DCPU16_REG_SP]] = vm->reg[DCPU16_REG_A]; vm->reg[DCPU16_REG_PC] = vm->reg[DCPU16_REG_IA]; vm->reg[DCPU16_REG_A] = message; } } } } /* instantiate a new 'hardware' device */ struct dcpu16_hw *dcpu16_hw_new(struct dcpu16 *vm, struct dcpu16_hw_module *mod, void *data) { struct dcpu16_hw *hw; MSG_DEBUG(vm, "%s>> mod:%p data:%p", __func__, mod, data); hw = malloc(sizeof *hw); if (hw == NULL) { MSG_ERROR(vm, "%s():%s", "malloc", strerror(errno)); return NULL; } hw->vm = vm; hw->mod = mod; if (mod->data_init) { if (mod->data_init(hw, data)) { MSG_ERROR(vm, "failed to init hw module data"); free(hw); return NULL; } } else { hw->data = NULL; } return hw; } /* destroy a 'hardware' device */ void dcpu16_hw_del(struct dcpu16_hw **hw) { if (hw) { if (*hw) { MSG_DEBUG((*hw)->vm, "%s>> hw:%p", __func__, *hw); if ((*hw)->mod->data_free) { (*hw)->mod->data_free(*hw); } free(*hw); *hw = NULL; } } } /* dcpu16_hw_ctl * invokes per-module controls for hw device */ int dcpu16_hw_ctl(struct dcpu16_hw *hw, const char *cmd, void *data_in, void *data_out) { if (!hw) return -1; MSG_DEBUG(hw->vm, "%s>> name:%s cmd:%s in:%p out:%p", __func__, hw->mod->name_, cmd, data_in, data_out); if (hw->mod) { if (hw->mod->ctl) { if (cmd) { return hw->mod->ctl(hw, cmd, data_in, data_out); } } } return 0; } /* dcpu16_hw_attach * registers new 'hardware' device with system */ int dcpu16_hw_attach(struct dcpu16 *vm, struct dcpu16_hw *hw) { if (!vm || !hw) return -1; MSG_DEBUG(vm, "%s>> name:%s ID:0x%04x%04x MFG:0x%04x%04x VER:0x%04x", __func__, hw->mod->name_, hw->mod->id_h, hw->mod->id_l, hw->mod->mfg_l, hw->mod->mfg_h, hw->mod->ver); if (vm->hw_table_entries_ == 0xffff) { MSG_ERROR(vm, "maximum hardware entries reached"); return -1; } if (vm->hw_table_entries_ == vm->hw_table_allocated_) { size_t new_entries = vm->hw_table_allocated_ + 32; void *tmp_ptr = realloc(vm->hw_table_, new_entries * sizeof * (vm->hw_table_)); if (tmp_ptr == NULL) { MSG_ERROR(vm, "%s():%s", "realloc", strerror(errno)); return -1; } vm->hw_table_ = tmp_ptr; vm->hw_table_allocated_ += 32; } memcpy(vm->hw_table_ + vm->hw_table_entries_, hw, sizeof *hw); vm->hw_table_entries_++; MSG_DEBUG(vm, "%s>> added hw entry %zu", __func__, vm->hw_table_entries_); return 0; } /* dcpu16_acct_add * Register callback fn to be triggered whenever event matching any events * in bitwise mask occur. */ int dcpu16_acct_add(struct dcpu16 *vm, dcpu16_acct_event mask, dcpu16_ev_cb_t *fn, DCPU16_WORD addr_l, DCPU16_WORD addr_h, void *data) { struct dcpu16_acct_cb cb = { .mask = mask, .addr_l = addr_l, .addr_h = addr_h, .fn = fn, .data = data, }; if (!vm) return -1; cb.mask = mask; cb.addr_l = addr_l; cb.addr_h = addr_h; cb.fn = fn; cb.data = data; if (vm->cb_table_entries_ == vm->cb_table_allocated_) { size_t new_entries = vm->cb_table_allocated_ + 32; void *tmp_ptr = realloc(vm->cb_table_, new_entries * sizeof *(vm->cb_table_)); if (tmp_ptr == NULL) { MSG_ERROR(vm, "%s():%s", "realloc", strerror(errno)); return -1; } vm->cb_table_ = tmp_ptr; vm->cb_table_allocated_ += 32; } memcpy(vm->cb_table_ + vm->cb_table_entries_, &cb, sizeof cb); vm->cb_table_entries_++; MSG_DEBUG(vm, "%s>> attached event callback %zu", __func__, vm->cb_table_entries_); return 0; } /* dcpu16_reset * signals cpu to reset, clearing runstate and ram, then reload any init callbacks */ void dcpu16_reset(struct dcpu16 *vm) { size_t i; if (!vm) return; MSG_DEBUG(vm, "%s>> reset", __func__); vm->skip_ = 0; vm->interrupts_deferred_ = 0; vm->on_fire_ = 0; memset(vm->interrupts_, 0, sizeof vm->interrupts_); vm->interrupts_head_ = 0; vm->interrupts_tail_ = 0; /* signal attached hardware */ for (i = 0; i < vm->hw_table_entries_; i++) { if (vm->hw_table_[i].mod->reset) vm->hw_table_[i].mod->reset(vm, &vm->hw_table_[i]); } memset(vm->reg, 0, sizeof vm->reg); memset(vm->ram, 0, sizeof vm->ram); vm->cycle_ = 0; acct_event_(vm, DCPU16_ACCT_EV_RESET, 0); } /* dcpu16_new * allocate a new dcpu16 instance */ struct dcpu16 *dcpu16_new(void) { struct dcpu16 *vm; vm = calloc(1, sizeof *vm); if (vm == NULL) MSG_ERROR(NULL, "%s: %s(%zu): %s", __func__, "calloc", strerror(errno)); vm->msg_cb_ = dcpu16_msg_; return vm; } /* dcpu16_delete * release a dcpu16 instance */ void dcpu16_delete(struct dcpu16 **vm) { if (!vm || !*vm) return; free(*vm); *vm = NULL; }