X-Git-Url: http://git.squeep.com/?p=dcpu16;a=blobdiff_plain;f=dcpu16.c;h=0b1d391e58261f46332fa914aeda4673abe889df;hp=51d755dfa7731e4479e136bf85ac5d8bf2f3616a;hb=HEAD;hpb=a4e2c238ea47badea198132fc5d9311d1f40ca13 diff --git a/dcpu16.c b/dcpu16.c index 51d755d..0b1d391 100644 --- a/dcpu16.c +++ b/dcpu16.c @@ -11,6 +11,7 @@ /* * 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. @@ -18,72 +19,97 @@ * * Justin Wind * 2012 04 05 - implementation started - * 2012 04 06 - first functionality achieved - * 2012 04 09 - minor cleanups - * 2012 04 10 - moved cli to separate module - * 2012 04 12 - added basic callback support for address accesses + * 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 - * refactor opcode functiontables into switch statements * 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 4 -#define OPCODE_OPERAND_BITS 6 +#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 -static const char * const regnames_ = "ABCXYZIJ"; +/* messages could be sent nowhere */ +static void msg_null_(unsigned int l, char *fmt, ...) { (void)l, (void)fmt; } -/* some default warning and debug reporting functions, which can be overridden by clients */ -#define WARN(...) do { if (warn_cb_) warn_cb_(__VA_ARGS__); } while (0) -static inline void warn_(char *fmt, ...) __attribute__((format(printf, 1, 2))); +/* messages default to standard streams */ +static void msg_default_(unsigned int, char *, ...) __attribute__((format(printf, 2, 3))); static inline -void warn_(char *fmt, ...) { +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; - fprintf(stderr, "[warning] "); + if (l < sizeof msg_tag_ / sizeof *msg_tag_) + fprintf(f, "[%s] ", msg_tag_[l]); + else + fprintf(f, "[%u] ", l); + va_start(ap, fmt); - vfprintf(stderr, fmt, ap); + vfprintf(f, fmt, ap); va_end(ap); - fprintf(stderr, "\n"); - fflush(stderr); -} -static void (*warn_cb_)(char *fmt, ...) = warn_; -void dcpu16_warn_cb_set(void (*fn)(char *fmt, ...)) { - warn_cb_ = fn; -} -#ifdef DEBUG -#define TRACE(...) do { if (trace_cb_) trace_cb_(__VA_ARGS__); } while (0) -static inline void trace_(char *fmt, ...) __attribute__((format(printf, 1, 2))); -static inline -void trace_(char *fmt, ...) { - va_list ap; + fprintf(f, "\n"); - fprintf(stdout, "[debug] "); - va_start(ap, fmt); - vfprintf(stdout, fmt, ap); - va_end(ap); - fprintf(stdout, "\n"); - fflush(stdout); -} -#else /* DEBUG */ -#define TRACE(...) do {} while(0) -#endif /* DEBUG */ -static void (*trace_cb_)(char *fmt, ...) = -#ifdef DEBUG - trace_ -#else /* DEBUG */ - NULL -#endif - ; -void dcpu16_trace_cb_set(void (*fn)(char *fmt, ...)) { - trace_cb_ = fn; + 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 @@ -95,120 +121,293 @@ void acct_event_(struct dcpu16 *vm, dcpu16_acct_event ev, DCPU16_WORD addr) { for (i = 0; i < vm->cb_table_entries_; i++) { if ( (cb[i].mask & ev) ) - cb[i].fn(vm, ev, addr, cb[i].data); + if (addr >= cb[i].addr_l && addr <= cb[i].addr_h) + cb[i].fn(vm, ev, addr, cb[i].data); } } -/* value_decode_ - * sets *v to be the destination of the value - * advances d->pc if necessary - * workv is buffer to use to accumulate literal value before use, one exists for either potential instruction operand - * e_addr is for accounting callback - * returns true if destination points to literal (id est *v should ignore writes) - */ +/* 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 -unsigned int value_decode_(struct dcpu16 *d, DCPU16_WORD value, DCPU16_WORD *work_v, DCPU16_WORD **v, DCPU16_WORD *e_addr) { - DCPU16_WORD nextword; - unsigned int retval = 0; +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); - /* does this value indicate a literal */ - if (value >= 0x1f) - retval = 1; + 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 */ - *v = d->reg + value; - TRACE(">> %c (0x%04x)", - regnames_[value], + *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 */ - } else if (value <= 0x0f) { /* [register] */ - *v = &(d->ram[ d->reg[(value & 0x07)] ]); - TRACE(">> [%c] [0x%04x] (0x%04x)", - regnames_[value&0x07], - d->reg[value&0x07], - **v); - *e_addr = d->reg[(value & 0x07)]; - - } else if (value <= 0x17) { /* [next word + register] */ - nextword = d->ram[ d->pc++ ]; - d->cycle++; - *v = &(d->ram[ nextword + d->reg[(value & 0x07)] ]); - TRACE(">> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)", - regnames_[(value & 0x07)], - nextword, - d->reg[(value & 0x07)], + 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); - *e_addr = nextword + d->reg[(value & 0x07)]; +#endif /* DEBUG_DECODE */ - } else switch (value) { - case 0x18: /* POP / [sp++] */ - *v = &(d->ram[ d->sp++ ]); - TRACE(">> POP [0x%04x] (0x%04x)", - d->sp - 1, + 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); - *e_addr = d->sp - 1; +#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] */ - *v = &(d->ram[ d->sp ]); - TRACE(">> PEEK [0x%04x] (0x%04x)", - d->sp, + 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); - *e_addr = d->sp; +#endif /* DEBUG_DECODE */ break; - case 0x1a: /* PUSH / [--sp] */ - *v = &(d->ram[ --d->sp ]); - TRACE(">> PUSH [0x%04x] (0x%04x)", - d->sp + 1, + 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); - *e_addr = d->sp + 1; +#endif /* DEBUG_DECODE */ break; case 0x1b: /* SP */ - *v = &(d->sp); - TRACE(">> SP (0x%04x)", + *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 */ - *v = &(d->pc); - TRACE(">> PC (0x%04x)", **v); + *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: /* O */ - *v = &(d->o); - TRACE(">> O (0x%04x)", **v); + 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++]] */ - nextword = d->ram[ d->pc++ ]; - d->cycle++; - *v = &(d->ram[ nextword ]); - TRACE(">> [nextword] [0x%04x] (0x%04x)", - nextword, + *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); - *e_addr = nextword; +#endif /* DEBUG_DECODE */ break; case 0x1f: /* next word (literal) / [pc++] */ - nextword = d->ram[ d->pc++ ]; - d->cycle++; - *work_v = nextword; + *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; - TRACE(">> nextword (0x%04x)", **v); + +#ifdef DEBUG_DECODE + MSG_(MSG_DEBUG_DECODE, vm, "%s>> nextword (0x%04x)", + __func__, + **v); +#endif /* DEBUG_DECODE */ break; - default: /* 0x20-0x3f: literal values 0x00-0x1f */ - *work_v = value & 0x1f; + default: /* 0x20-0x3f: literal values 0xffff-0x1e */ + *e_what = EWHAT_NONE; + *work_v = (value & 0x1f) - 1; *v = work_v; - TRACE(">> literal (0x%04x)", **v); - } - return retval; +#ifdef DEBUG_DECODE + MSG_(MSG_DEBUG_DECODE, vm, "%s>> literal (0x%04x)", + __func__, + **v); +#endif /* DEBUG_DECODE */ + } } @@ -216,32 +415,57 @@ unsigned int value_decode_(struct dcpu16 *d, DCPU16_WORD value, DCPU16_WORD *wor struct opcode_entry { unsigned short value; char name[OPCODE_NAME_LEN]; - void (*impl)(struct dcpu16 *, DCPU16_WORD, DCPU16_WORD); + void (*impl)(struct dcpu16 *, DCPU16_WORD, DCPU16_WORD, DCPU16_WORD, DCPU16_WORD); }; /* messy boilerplate for opcode handlers */ -#define OP_IMPL(x) static void op_##x(struct dcpu16 *d, DCPU16_WORD val_a, DCPU16_WORD val_b) +/* 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;\ - unsigned int lit_a;\ 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 {\ - lit_a = value_decode_(d, val_a, &d->reg_work_[0], &a, &ev_a_addr);\ 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 -#define OP_BASIC_ (void)value_decode_(d, val_b, &d->reg_work_[0], &b, &ev_b_addr) +#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_R(addr) do { acct_event_(d, DCPU16_ACCT_EV_READ, addr); } while (0) -#define ACCT_W(addr) do { acct_event_(d, DCPU16_ACCT_EV_WRITE, addr); } while (0) -#define ACCT_ILL(addr) do { acct_event_(d, DCPU16_ACCT_EV_NOP, addr); } while (0) +#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 */ @@ -256,40 +480,235 @@ OP_IMPL(nbi__reserved_) { OP_NBI(nbi__reserved_); /* reserved for future expansion */ - DCPU16_WORD future_opcode = (d->ram[d->pc] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_BITS)); - WARN("reserved future opcode 0x%04x invoked", future_opcode); + /* 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); - ACCT_ILL(d->pc); + 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(ev_a_addr); + ACCT_R(a); - d->ram[ --d->sp ] = d->pc; - d->pc = *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); - d->cycle += 2; + vm->reg[DCPU16_REG_PC] = *a; + ACCT_REG_W(DCPU16_REG_PC); - ACCT_W(d->sp + 1); + + dcpu16_cycle_inc(vm, 2); } OP_IMPL(nbi__reserved2_) { OP_NBI(nbi__reserved2_); /* reserved */ - WARN("reserved nbi opcode invoked"); + MSG_INFO(vm, "reserved nbi opcode invoked"); + + ACCT_ILL(vm->reg[DCPU16_REG_PC] - pc_adjust); - ACCT_ILL(d->pc); + 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[] = { - {0x0, "(reserved)", op_nbi__reserved_}, - {0x1, "JSR", op_nbi_jsr}, - {0x2, "(reserved)", op_nbi__reserved2_}, - {0x0, "", NULL} + {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) @@ -297,478 +716,941 @@ static const struct opcode_entry opcode_nbi_entries[] = { /* basic opcodes */ /* - N.B. the following function does not decode values, (thus does not advance pc &c) - Decoding is handled by the opcode functions it calls. + 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_a; + 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); - TRACE(">> %s 0x%04x", e->name, val_b); - e->impl(d, val_b, 0); + 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 a to b */ + /* sets b to a */ - ACCT_R(ev_b_addr); + ACCT_R(a); /* - if a is a literal, it's aimed at a scratch register, + 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. */ - *a = *b; + *b = *a; - d->cycle += 1; + ACCT_W(b); - ACCT_W(ev_a_addr); + dcpu16_cycle_inc(vm, 1); } OP_IMPL(add) { OP_BASIC(add); - /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */ - unsigned int acc = *a + *b; + /* 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); - ACCT_R(ev_b_addr); - ACCT_R(ev_a_addr); + *b = acc; + vm->reg[DCPU16_REG_EX] = (acc > 0xffff); - *a = acc; - d->o = (acc > 0xffff); + ACCT_REG_W(DCPU16_REG_EX); - d->cycle += 2; + ACCT_W(b); - ACCT_W(ev_a_addr); + dcpu16_cycle_inc(vm, 2); } OP_IMPL(sub) { OP_BASIC(sub); - /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */ - unsigned int acc = *a - *b; + /* 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); - ACCT_R(ev_b_addr); - ACCT_R(ev_a_addr); + *b = acc; + vm->reg[DCPU16_REG_EX] = (acc > 0xffff); - *a = acc; - d->o = (acc > 0xffff); + ACCT_REG_W(DCPU16_REG_EX); - d->cycle += 2; + ACCT_W(b); - ACCT_W(ev_a_addr); + dcpu16_cycle_inc(vm, 2); } OP_IMPL(mul) { OP_BASIC(mul); - /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */ - unsigned int acc = *a * *b; + /* 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); - ACCT_R(ev_b_addr); - ACCT_R(ev_a_addr); + *b = acc; + vm->reg[DCPU16_REG_EX] = acc >> 16; - *a = acc; - d->o = acc >> 16; + ACCT_REG_W(DCPU16_REG_EX); - d->cycle += 2; + ACCT_W(b); - ACCT_W(ev_a_addr); + dcpu16_cycle_inc(vm, 2); } OP_IMPL(div) { OP_BASIC(div); - /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */ + /* sets b to b/a, sets EX to ((b<<16)/a)&0xffff. if a==0, sets a and EX to 0 instead. */ - ACCT_R(ev_b_addr); - ACCT_R(ev_a_addr); + ACCT_R(b); + ACCT_R(a); - if (*b == 0) { - *a = 0; - d->o = 0; + if (*a == 0) { + *b = 0; + vm->reg[DCPU16_REG_EX] = 0; } else { - *a = *a / *b; - d->o = (*a << 16) / *b; + *b = *b / *a; + vm->reg[DCPU16_REG_EX] = (*b << 16) / *a; } - d->cycle += 3; + ACCT_REG_W(DCPU16_REG_EX); - ACCT_W(ev_a_addr); + ACCT_W(b); + + dcpu16_cycle_inc(vm, 3); } -OP_IMPL(mod) { - OP_BASIC(mod); - /* sets a to a%b. if b==0, sets a to 0 instead. */ +OP_IMPL(dvi) { + OP_BASIC(dvi); + /* sets b to b/a, signed, round towards 0 */ - ACCT_R(ev_b_addr); - ACCT_R(ev_a_addr); + ACCT_R(b); + ACCT_R(a); - if (*b == 0) { - *a = 0; + if (*a == 0) { + *b = 0; + vm->reg[DCPU16_REG_EX] = 0; } else { - *a = *a % *b; + *b = (short)*b / (short)*a; + vm->reg[DCPU16_REG_EX] = (short)(*b << 16) / (short)*a; } - d->cycle += 3; + ACCT_REG_W(DCPU16_REG_EX); + ACCT_W(b); - ACCT_W(ev_a_addr); + dcpu16_cycle_inc(vm, 3); } -OP_IMPL(shl) { - OP_BASIC(shl); - /* sets a to a<>16)&0xffff */ - unsigned int acc = *a << *b; - - ACCT_R(ev_b_addr); - ACCT_R(ev_a_addr); +OP_IMPL(mod) { + OP_BASIC(mod); + /* sets b to b%a. if a==0, sets b to 0 instead. */ - *a = acc; + ACCT_R(b); + ACCT_R(a); - d->o = acc >> 16; + if (*a == 0) { + *b = 0; + } else { + *b = *b % *a; + } - d->cycle += 2; + ACCT_W(a); - ACCT_W(ev_a_addr); + dcpu16_cycle_inc(vm, 3); } -OP_IMPL(shr) { - OP_BASIC(shr); - /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */ - unsigned int acc = *a >> *b; +OP_IMPL(mdi) { + OP_BASIC(mdi); + /* sets b to b%a, signed */ - ACCT_R(ev_b_addr); - ACCT_R(ev_a_addr); + ACCT_R(b); + ACCT_R(a); - *a = acc; - d->o = (*a << 16) >> *b; + if (*a == 0) { + *b = 0; + } else { + *b = (short)*b % (short)*a; + } - d->cycle += 2; + ACCT_W(b); - ACCT_W(ev_a_addr); + dcpu16_cycle_inc(vm, 3); } OP_IMPL(and) { OP_BASIC(and); - /* sets a to a&b */ + /* sets b to b&a */ - ACCT_R(ev_b_addr); - ACCT_R(ev_a_addr); + ACCT_R(b); + ACCT_R(a); - *a = *a & *b; + *b = *b & *a; - d->cycle += 1; + ACCT_W(b); - ACCT_W(ev_a_addr); + dcpu16_cycle_inc(vm, 1); } OP_IMPL(bor) { OP_BASIC(bor); - /* sets a to a|b */ + /* sets b to b|a */ - ACCT_R(ev_b_addr); - ACCT_R(ev_a_addr); + ACCT_R(b); + ACCT_R(a); - *a = *a | *b; + *b = *b | *a; - d->cycle += 1; + ACCT_W(b); - ACCT_W(ev_a_addr); + dcpu16_cycle_inc(vm, 1); } OP_IMPL(xor) { OP_BASIC(xor); - /* sets a to a^b */ + /* 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); +} - ACCT_R(ev_b_addr); - ACCT_R(ev_a_addr); +OP_IMPL(ifc) { + OP_BASIC(ifc); + /* performs next instruction only if (b&a)==0 */ - *a = *a ^ *b; + ACCT_R(b); + ACCT_R(a); - d->cycle += 1; + if ((*b & *a) == 0) { + /* */ + } else { + vm->skip_ = 1; + dcpu16_cycle_inc(vm, 1); + } - ACCT_W(ev_a_addr); + dcpu16_cycle_inc(vm, 2); } OP_IMPL(ife) { OP_BASIC(ife); - /* performs next instruction only if a==b */ + /* performs next instruction only if b==a */ - ACCT_R(ev_b_addr); - ACCT_R(ev_a_addr); + ACCT_R(b); + ACCT_R(a); - if (*a == *b) { + if (*b == *a) { /* */ } else { - d->skip_ = 1; - d->cycle++; + vm->skip_ = 1; + dcpu16_cycle_inc(vm, 1); } - d->cycle += 2; + dcpu16_cycle_inc(vm, 2); } OP_IMPL(ifn) { OP_BASIC(ifn); - /* performs next instruction only if a!=b */ + /* performs next instruction only if b!=a */ - ACCT_R(ev_b_addr); - ACCT_R(ev_a_addr); + ACCT_R(b); + ACCT_R(a); - if (*a != *b) { + if (*b != *a) { /* */ } else { - d->skip_ = 1; - d->cycle++; + vm->skip_ = 1; + dcpu16_cycle_inc(vm, 1); } - d->cycle += 2; + dcpu16_cycle_inc(vm, 2); } OP_IMPL(ifg) { OP_BASIC(ifg); - /* performs next instruction only if a>b */ + /* performs next instruction only if b>a */ - ACCT_R(ev_b_addr); - ACCT_R(ev_a_addr); + ACCT_R(b); + ACCT_R(a); - if (*a > *b) { + if (*b > *a) { /* */ } else { - d->skip_ = 1; - d->cycle++; + vm->skip_ = 1; + dcpu16_cycle_inc(vm, 1); } - d->cycle += 2; + dcpu16_cycle_inc(vm, 2); } -OP_IMPL(ifb) { - OP_BASIC(ifb); - /* performs next instruction only if (a&b)!=0 */ +OP_IMPL(ifa) { + OP_BASIC(ifa); + /* performs next instruction only if b>a (signed) */ - ACCT_R(ev_b_addr); - ACCT_R(ev_a_addr); + ACCT_R(b); + ACCT_R(a); - if ((*a & *b) != 0) { + if (*b > *a) { /* */ } else { - d->skip_ = 1; - d->cycle++; + vm->skip_ = 1; + dcpu16_cycle_inc(vm, 1); } - d->cycle += 2; + 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[] = { - {0x0, "(nbi)", op__nbi_}, - {0x1, "SET", op_set }, - {0x2, "ADD", op_add }, - {0x3, "SUB", op_sub }, - {0x4, "MUL", op_mul }, - {0x5, "DIV", op_div }, - {0x6, "MOD", op_mod }, - {0x7, "SHL", op_shl }, - {0x8, "SHR", op_shr }, - {0x9, "AND", op_and }, - {0xa, "BOR", op_bor }, - {0xb, "XOR", op_xor }, - {0xc, "IFE", op_ife }, - {0xd, "IFN", op_ifn }, - {0xe, "IFG", op_ifg }, - {0xf, "IFB", op_ifb }, - {0x0, "", NULL } + {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 -void dump_operand_value_(DCPU16_WORD value, DCPU16_WORD nextword) { +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) { - printf(" %c", regnames_[value]); + len = snprintf(buf, buf_sz, "%s", dcpu16_reg_names[value]); } else if (value <= 0x0f) { - printf(" [%c]", regnames_[value & 0x07]); + len = snprintf(buf, buf_sz, "[%s]", dcpu16_reg_names[value & 0x07]); } else if (value <= 0x17) { - printf(" [0x%04x + %c]", nextword, regnames_[value & 0x07]); + len = snprintf(buf, buf_sz, "[0x%04x + %s]", nextword, dcpu16_reg_names[value & 0x07]); } else switch (value) { - case 0x18: printf(" POP"); break; - case 0x19: printf(" PEEK"); break; - case 0x1a: printf(" PUSH"); break; - case 0x1b: printf(" SP"); break; - case 0x1c: printf(" PC"); break; - case 0x1d: printf(" O"); break; - case 0x1e: printf(" [0x%04x]", nextword); break; - case 0x1f: printf(" 0x%04x", nextword); break; - default: printf(" 0x%02x", value - 0x20); + 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 word into the parts of an instruction, and determine how many words it takes up in total */ +/* split a sequence of (one to three) words into the components of an instruction */ static inline -void instruction_decode_(struct dcpu16 *d, DCPU16_WORD addr, DCPU16_WORD *opcode, DCPU16_WORD *a, DCPU16_WORD *b, DCPU16_WORD *instr_len) { - *opcode = d->ram[addr] & ((1 << OPCODE_BASIC_BITS) - 1); - *a = (d->ram[addr] >> OPCODE_BASIC_BITS) & ((1 << OPCODE_OPERAND_BITS) - 1); - *b = (d->ram[addr] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_BITS)) & ((1 << OPCODE_OPERAND_BITS) - 1); - if (instr_len) { - *instr_len = 1; - /* both basic and nbi opcodes use their b operand */ - if ( (*b >= 0x10 && *b <= 0x17) || *b == 0x1e || *b == 0x1f ) - *instr_len += 1; - /* but only basic uses a */ - if (*opcode - && ((*a >= 0x10 && *a <= 0x17) || *a == 0x1e || *a == 0x1f) ) - *instr_len += 1; +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 *d, DCPU16_WORD addr) { - DCPU16_WORD opcode, a, b, instr_len, i; - const struct opcode_entry *e; +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 (!d) return 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_(d, addr - i, &opcode, &a, &b, &instr_len); + 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 >= 0xc) { + if (instr_len == i + && (opcode >= 0x10 && opcode <= 0x17) ) { indent++; break; } } +#endif - /* now get what we're really interested in */ - instruction_decode_(d, addr, &opcode, &a, &b, &instr_len); - - if (opcode) - e = opcode_basic_entries + opcode; - else - e = opcode_nbi_entries + ( (a < OPCODE_NBI_MAX) ? a : (OPCODE_NBI_MAX - 1) ); + /* just need instr_len */ + instruction_decode_(vm->ram, addr, &opcode, &b, &b_data, &a, &a_data, &instr_len); /* show the raw words */ - printf("%04x", d->ram[addr]); + printf("%04x", vm->ram[addr]); for (i = 1; i < instr_len; i++) { - printf(" %04x", d->ram[addr + i]); + printf(" %04x", vm->ram[addr + i]); } /* align things neatly, show the instruction */ - printf("%s%s ;%s%s%s", + printf("%s%s ;%s%s", instr_len < 3 ? " " : "", instr_len < 2 ? " " : "", partial ? "*" : " ", - indent ? " " : "", - e->name); - - /* show the operands */ - i = 0; - if (opcode) { - dump_operand_value_(a, d->ram[addr + 1]); - if ((a >= 0x10 && a <= 0x17) || a == 0x1e || a == 0x1f) - addr++; - printf(","); - } + indent ? " " : ""); - if (opcode || a) - dump_operand_value_(b, d->ram[addr + 1]); + 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 *d) { - DCPU16_WORD opcode, a, b, instr_len; +void dcpu16_step(struct dcpu16 *vm) { + DCPU16_WORD opcode, b, a, instr_len, *b_data, *a_data; const struct opcode_entry *e; - if (!d) return; + if (!vm) + return; - /* - PC is advanced while decoding the operands by the opcode functions. - Things like this could be organized a little better.. - */ - instruction_decode_(d, d->pc, &opcode, &a, &b, NULL); + instruction_decode_(vm->ram, vm->reg[DCPU16_REG_PC], &opcode, &b, &b_data, &a, &a_data, &instr_len); - d->pc++; /* all instructions take at least one word */ + /* consume what we decoded */ + /* this happens immediately as PC might be re-set as an operation */ + vm->reg[DCPU16_REG_PC] += instr_len; - for (e = opcode_basic_entries; e->impl; e++) { - if (e->value == opcode) { - TRACE(">> %s 0x%04x, 0x%04x", e->name, a, b); - e->impl(d, a, b); - break; + /* 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; } } - /* and jump over next instr if needed */ - if (d->skip_) { - instruction_decode_(d, d->pc, &opcode, &a, &b, &instr_len); - d->pc += instr_len; - d->skip_ = 0; - TRACE("++ SKIPPED %x words", instr_len); + /* 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; + } + } } } -/* - print the current state of the machine - shows current cycle count, registers, and next instruction -*/ -void dcpu16_state_print(struct dcpu16 *d) { - unsigned int i; - if (!d) return; +/* 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; - printf(" "); - for (i = 0; i < 8; i++) - printf(" %c:0x%04x", regnames_[i], d->reg[i]); - printf("\n"); + MSG_DEBUG(vm, "%s>> mod:%p data:%p", __func__, mod, data); - printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:", - d->cycle, - "O", d->o, - "SP", d->sp, - "PC", d->pc, - "PC"); + 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; + } - dcpu16_disassemble_print(d, d->pc); - printf("\n"); + return hw; } -/* dcpu16_dump_ram - * print raw ram contents from start to stop +/* 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 */ -void dcpu16_dump_ram(struct dcpu16 *d, DCPU16_WORD start, DCPU16_WORD end) { - unsigned int i, j; - const unsigned int n = 8; /* words per line */ +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; +} - if (!d) return; - for (i = start, j = 0; i <= end; i++, j++) { - if (j % n == 0) - printf("0x%04x:\t", i); - printf(" %04x%s", d->ram[i], (j % n) == (n - 1) ? "\n" : ""); +/* 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 ((j % n) != (n - 1)) - printf("\n"); + + 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, void *data) { - struct dcpu16_acct_cb cb; +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; @@ -776,7 +1658,7 @@ int dcpu16_acct_add(struct dcpu16 *vm, dcpu16_acct_event mask, dcpu16_ev_cb_t *f 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) { - fprintf(stderr, "%s():%s", "realloc", strerror(errno)); + MSG_ERROR(vm, "%s():%s", "realloc", strerror(errno)); return -1; } vm->cb_table_ = tmp_ptr; @@ -786,24 +1668,40 @@ int dcpu16_acct_add(struct dcpu16 *vm, dcpu16_acct_event mask, dcpu16_ev_cb_t *f 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 *d) { - if (!d) return; +void dcpu16_reset(struct dcpu16 *vm) { + size_t i; + + if (!vm) + return; - d->cycle = 0; - memset(d->reg, 0, sizeof d->reg); - d->pc = 0; - d->sp = 0; - d->o = 0; - d->skip_ = 0; - memset(d->ram, 0, sizeof d->ram); + MSG_DEBUG(vm, "%s>> reset", __func__); - acct_event_(d, DCPU16_ACCT_EV_RESET, 0); + 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 @@ -814,7 +1712,9 @@ struct dcpu16 *dcpu16_new(void) { vm = calloc(1, sizeof *vm); if (vm == NULL) - WARN("%s: %s(%zu): %s", __func__, "calloc", strerror(errno)); + MSG_ERROR(NULL, "%s: %s(%zu): %s", __func__, "calloc", strerror(errno)); + + vm->msg_cb_ = dcpu16_msg_; return vm; } @@ -823,7 +1723,8 @@ struct dcpu16 *dcpu16_new(void) { * release a dcpu16 instance */ void dcpu16_delete(struct dcpu16 **vm) { - if (!vm || !*vm) return; + if (!vm || !*vm) + return; free(*vm); *vm = NULL;