From: Justin Wind Date: Wed, 9 May 2012 03:28:01 +0000 (-0700) Subject: v1.7 spec mostly implemented, mostly untested X-Git-Url: https://git.squeep.com/?a=commitdiff_plain;h=b819e4d8f696703ad42c97b357672fd9325bdac6;p=dcpu16 v1.7 spec mostly implemented, mostly untested changed cpu to work as per v1.7 specification, minor additional work still needed added interrupts, hardware interface, new opcodes/operands --- diff --git a/Makefile b/Makefile index fdd94d2..f0f3b71 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,8 @@ endif PROGRAMS = as-dcpu16 vm-dcpu16 SOURCES = common.c dcpu16.c as-dcpu16.c vm-dcpu16.c display.c +CPPFLAGS += -DDEBUG + CPPFLAGS += -DHAVE_LIBPNG -I/usr/X11/include/ CFLAGS = -g -Wall -Wextra -pedantic -std=c99 LDFLAGS += -lreadline @@ -37,7 +39,7 @@ include .depend vm-dcpu16: vm-dcpu16.o dcpu16.o common.o display.o -as-dcpu16: as-dcpu16.o common.o +as-dcpu16: as-dcpu16.o dcpu16.o common.o clean: @rm -rf $(PROGRAMS) *.o *.dSYM .depend diff --git a/as-dcpu16.c b/as-dcpu16.c index 1475ddf..73b2fa3 100644 --- a/as-dcpu16.c +++ b/as-dcpu16.c @@ -17,6 +17,8 @@ * 2012 04 07 - implementation started * 2012 04 10 - functional * 2012 04 16 - support dat statements + * 2012 05 05 - v1.7 revision started + * 2012 05 08 - v1.7 revision implemented * * TODO * needs ability to specify location for code or data @@ -71,6 +73,12 @@ void usage_(char *prog, unsigned int full) { } } +/* LSB-0 aaaaaabbbbbooooo */ +#define OPCODE_BITS 5 +#define OPERAND_B_BITS 5 +#define OPERAND_A_BITS 6 +#define N_BIT_MASK(__x__) ((1 << (__x__)) - 1) + /* instructions have operands */ struct operand_ { @@ -207,22 +215,41 @@ int opcode_bits_(char *opcode) { char value; } opcodes_lower_nibble[] = { { "JSR", 0x00 }, - /* { "future nbi instruction", 0x00 }, */ + { "INT", 0x00 }, + { "IAG", 0x00 }, + { "IAS", 0x00 }, + { "RFI", 0x00 }, + { "IAQ", 0x00 }, + { "HWN", 0x00 }, + { "HWQ", 0x00 }, + { "HWI", 0x00 }, { "SET", 0x01 }, { "ADD", 0x02 }, { "SUB", 0x03 }, { "MUL", 0x04 }, - { "DIV", 0x05 }, - { "MOD", 0x06 }, - { "SHL", 0x07 }, - { "SHR", 0x08 }, - { "AND", 0x09 }, - { "BOR", 0x0a }, - { "XOR", 0x0b }, - { "IFE", 0x0c }, - { "IFN", 0x0d }, - { "IFG", 0x0e }, - { "IFB", 0x0f }, + { "MLI", 0x05 }, + { "DIV", 0x06 }, + { "DVI", 0x07 }, + { "MOD", 0x08 }, + { "MDI", 0x09 }, + { "AND", 0x0a }, + { "BOR", 0x0b }, + { "XOR", 0x0c }, + { "SHR", 0x0d }, + { "ASR", 0x0e }, + { "SHL", 0x0f }, + { "IFB", 0x10 }, + { "IFC", 0x11 }, + { "IFE", 0x12 }, + { "IFN", 0x13 }, + { "IFG", 0x14 }, + { "IFA", 0x15 }, + { "IFL", 0x16 }, + { "IFU", 0x17 }, + { "ADX", 0x1a }, + { "SBX", 0x1b }, + { "STI", 0x1e }, + { "SDI", 0x1f }, { "", 0x00 } }, *o; @@ -248,6 +275,14 @@ int nbi_opcode_bits_(char *nbi_opcode) { } nbi_opcodes_bits[] = { { " ", 0x00 }, /* reserved for future */ { "JSR", 0x01 }, + { "INT", 0x08 }, + { "IAG", 0x09 }, + { "IAS", 0x0a }, + { "RFI", 0x0b }, + { "IAQ", 0x0c }, + { "HWN", 0x10 }, + { "HWQ", 0x11 }, + { "HWI", 0x12 }, { "", 0x00 } }, *o; @@ -299,6 +334,7 @@ void buf_strip_chars_(char *buf, char *chars) { * returns -1 if it could not parse the operand * returns -2 if it could not parse the operand due to an unresolved label * notes: nextword may be overwritten even if it's not used in final instruction + * */ static int value_bits_(struct dynamic_array *labels, const char *operand_orig, DCPU16_WORD *nextword, unsigned int *nextwordused, unsigned int allow_short_labels) { @@ -347,18 +383,49 @@ int value_bits_(struct dynamic_array *labels, const char *operand_orig, DCPU16_W } /* easy matches */ - if (strcasecmp(operand, "POP") == 0) { + + /* push and pop now share the same operand value */ + if (strcasecmp(operand, "POP") == 0 + || strcasecmp(operand, "[SP++]") == 0) { DEBUG_PRINTFQ("is POP\n"); return 0x18; } - if (strcasecmp(operand, "PUSH") == 0) { + if (strcasecmp(operand, "PUSH") == 0 + || strcasecmp(operand, "[--SP]") == 0) { DEBUG_PRINTFQ("is PUSH\n"); - return 0x19; + return 0x18; } - if (strcasecmp(operand, "PEEK") == 0) { + + if (strcasecmp(operand, "PEEK") == 0 + || strcasecmp(operand, "[SP]") == 0) { DEBUG_PRINTFQ("is PEEK\n"); + return 0x19; + } + + /* this could be better, if we had a real token tree */ + if (strncasecmp(operand, "PICK", 4) == 0) { + DEBUG_PRINTFQ("is PICK "); + + errno = 0; + l = strtoul(operand + 4, &ep, 0); + if (errno == 0 + && (*(operand + 4) && (*ep == '\0')) ) { + if (l > 0xffff) { + DEBUG_PRINTFQ("(out of range)\n"); + fprintf(stderr, "constant invalid in operand '%s'\n", operand_orig); + return -1; + } + } else if (errno == ERANGE) { + DEBUG_PRINTFQ("(out of range)\n"); + fprintf(stderr, "constant invalid in operand '%s'\n", operand_orig); + return -1; + } + *nextword = l & 0xffff; + *nextwordused += 1; + DEBUG_PRINTFQ("0x%04x\n", *nextword); return 0x1a; } + if (strcasecmp(operand, "SP") == 0) { DEBUG_PRINTFQ("is register SP\n"); return 0x1b; @@ -367,8 +434,8 @@ int value_bits_(struct dynamic_array *labels, const char *operand_orig, DCPU16_W DEBUG_PRINTFQ("is register PC\n"); return 0x1c; } - if (strcasecmp(operand, "O") == 0) { - DEBUG_PRINTFQ("is register O\n"); + if (strcasecmp(operand, "EX") == 0) { + DEBUG_PRINTFQ("is register EX\n"); return 0x1d; } @@ -397,12 +464,12 @@ int value_bits_(struct dynamic_array *labels, const char *operand_orig, DCPU16_W ep++; /* figure out which one is which */ - if (strlen(ep) == 1 - && strchr("AaBbCcXxYyZzIiJj", *ep)) { + if ((strlen(ep) == 1 && strchr("AaBbCcXxYyZzIiJj", *ep)) + || (strlen(ep) == 2 && strcasecmp(ep, "SP")) ) { reg = ep; constant = operand; - } else if (strlen(operand) == 1 - && strchr("AaBbCcXxYyZzIiJj", *operand) ) { + } else if ((strlen(operand) == 1 && strchr("AaBbCcXxYyZzIiJj", *operand)) + || (strlen(operand) == 2 && strcasecmp(operand, "SP")) ) { reg = operand; constant = ep; } else { @@ -427,15 +494,17 @@ int value_bits_(struct dynamic_array *labels, const char *operand_orig, DCPU16_W /* seems fine */ *nextword = l & 0xffff; *nextwordused += 1; + + /* special case [SP+n]/PICK n */ + if (strlen(reg) == 2) { + DEBUG_PRINTFQ("is PICK 0x%04x\n", *nextword); + return 0x1a; + } + DEBUG_PRINTFQ("is a dereferenced register (%c) + constant (%hu)\n", *reg, *nextword); return 0x10 | register_enumerate_(*reg); } else if (errno == ERANGE) { -#if 0 -oh, right, labels fall through - DEBUG_PRINTFQ("is out of range\n"); - fprintf(stderr, "trouble with operand '%s': %s\n", operand_orig, strerror(errno)); - return -1; -#endif + fprintf(stderr, "%s('%s'):%s\n", "strtoul", constant, strerror(errno)); } /* what? still here? assume it's a label, I guess */ @@ -497,8 +566,11 @@ oh, right, labels fall through } DEBUG_PRINTFQ("is literal value (%lu)\n", l); - if (l < 0x20) { - return l + 0x20; + if (l < 0x1f) { + return l + 0x21; + } + if (l == 0xffff) { + return 0x20; } *nextword = l & 0xffff; @@ -515,9 +587,15 @@ oh, right, labels fall through } DEBUG_PRINTFQ("is label '%s' (0x%02hx)\n", operand, *nextword); - if (*nextword < 0x20 && allow_short_labels) { + if (allow_short_labels + && (*nextword < 0x1f) ) { DEBUG_PRINTF("small value label win\n"); - return (0x20 + *nextword) & 0x3f; + return (0x21 + *nextword) & N_BIT_MASK(OPERAND_A_BITS); + } + if (allow_short_labels + && (*nextword == 0xffff) ) { + DEBUG_PRINTF("small value label win\n"); + return 0x20; } *nextwordused += 1; @@ -538,6 +616,15 @@ int instruction_print_(struct instruction_ *i, unsigned int with_label) { for (o = i->operands; o; o = o->next) r += printf(" %s%s", o->operand, o->next ? "," : ""); + if (i->ready) { + DCPU16_WORD l; + printf(" ["); + l = dcpu16_mnemonify_buf(i->instr_words); + printf("]"); + + if (i->length != l) + DEBUG_PRINTF("!!internal inconsistency!! i->length:%u l:%hu should match\n", i->length, l); + } return r; } @@ -715,14 +802,6 @@ int instr_assemble_(struct dynamic_array *labels, struct instruction_ *i, unsign printf("(line %zu)\n", i->src_line); } -#if 0 -while debugging, always reassemble - if (i->ready) { - /* already assembled, nothing to do */ - return 0; - } -#endif - if (i->opcode == NULL) { assert(i->label); assert(i->operands == NULL); @@ -807,10 +886,10 @@ while debugging, always reassemble fprintf(stderr, "'\n"); return -1; } - i->instr_words[0] |= 0x0f & bits; + i->instr_words[0] |= bits & N_BIT_MASK(OPCODE_BITS); - /* in rendered bytecode, all instructions have two operands; nbi instructions take 'first operand' bits. */ - if ((bits & 0x0f) == 0) { + /* in rendered bytecode, all instructions have a and b operands; nbi instructions occupy 'b operand' bits. */ + if ((bits & N_BIT_MASK(OPCODE_BITS)) == 0) { bits = nbi_opcode_bits_(i->opcode); if (bits < 0) { fprintf(stderr, "INTERNAL ERROR: missing instruction in nbi opcode table\n"); @@ -833,7 +912,11 @@ while debugging, always reassemble } o = o->next; } - i->instr_words[0] |= (bits & 0x3f) << 4; + if (bits > N_BIT_MASK(OPERAND_B_BITS)) { + fprintf(stderr, "%s: internal error: operand '%s' generated out of range\n", __func__, "b"); + return -1; + } + i->instr_words[0] |= (bits & N_BIT_MASK(OPERAND_B_BITS)) << OPCODE_BITS; if (o == NULL) { fprintf(stderr, "'%s' requires more operands\n", i->opcode); @@ -851,7 +934,10 @@ while debugging, always reassemble bits = 0; } o = o->next; - i->instr_words[0] |= (bits & 0x3f) << 10; + if (bits > N_BIT_MASK(OPERAND_A_BITS)) { + fprintf(stderr, "%s: internal error: operand '%s' generated out of range\n", __func__, "a"); + } + i->instr_words[0] |= (bits & N_BIT_MASK(OPERAND_A_BITS)) << (OPCODE_BITS + OPERAND_B_BITS); if (o != NULL) { fprintf(stderr, "too many operands\n"); diff --git a/dcpu16.c b/dcpu16.c index 23ef53a..75c6cf0 100644 --- a/dcpu16.c +++ b/dcpu16.c @@ -22,18 +22,25 @@ * 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 static const char * const regnames_ = "ABCXYZIJ"; @@ -99,116 +106,195 @@ void acct_event_(struct dcpu16 *vm, dcpu16_acct_event ev, DCPU16_WORD addr) { } } +/* convert register name to index into register array */ +static inline +off_t reg_index_(int reg) { + return strchr(regnames_, reg) - regnames_; +} + +/* add an entry to the interrupt queue */ +static +int interrupt_enqueue_(struct dcpu16 *d, DCPU16_WORD message) { + d->interrupts_[d->interrupts_tail_] = message; + d->interrupts_tail_ += 1; + d->interrupts_tail_ %= DCPU16_INTERRUPT_QUEUE_SIZE; + + if (d->interrupts_tail_ == d->interrupts_head_) { + d->on_fire_ = 1; + WARN("interrupt queue overflow (system is now on fire)"); + return -1; + } + + return 0; +} -/* 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) - */ 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 *d) { + DCPU16_WORD message; + if (d->interrupts_tail_ == d->interrupts_head_) { + WARN("interrupt underflow"); + return 0; + } + + message = d->interrupts_[d->interrupts_head_]; + d->interrupts_head_ += 1; + d->interrupts_head_ %= DCPU16_INTERRUPT_QUEUE_SIZE; + + return message; +} + +/* 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 + * cycles 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.. + * + */ +static inline +void value_decode_(struct dcpu16 *d, DCPU16_WORD value, unsigned int value_is_a, DCPU16_WORD value_data, + DCPU16_WORD *work_v, DCPU16_WORD **v, DCPU16_WORD *e_addr, + 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)(d->pc + *pc_adjust), + sp = (DCPU16_WORD)(d->sp + *sp_adjust); + + TRACE("%s: pc:0x%04x sp:0x%04x value_data:0x%04x\n", + __func__, + pc, + sp, + value_data); if (value <= 0x07) { /* register */ *v = d->reg + value; - TRACE(">> %c (0x%04x)", + TRACE("%s>> %c (0x%04x)", + __func__, regnames_[value], **v); + return; + } - } 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], + if (value <= 0x0f) { /* [register] */ + *v = &(d->ram[ d->reg[value & 0x07] ]); + *e_addr = d->reg[value & 0x07]; + TRACE("%s>> [%c] [0x%04x] (0x%04x)", + __func__, + regnames_[value & 0x07], + d->reg[value & 0x07], **v); - *e_addr = d->reg[(value & 0x07)]; + return; + } - } 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)], + if (value <= 0x17) { /* [next word + register] */ + *pc_adjust += 1; /* consume next word */ + *cycle_adjust += 1; + *e_addr = value_data + d->reg[value & 0x07]; + *v = d->ram + *e_addr; + TRACE("%s>> [nextword + %c] [0x%04x + 0x%04x] (0x%04x)", + __func__, + regnames_[value & 0x07], + value_data, + d->reg[value & 0x07], **v); - *e_addr = nextword + d->reg[(value & 0x07)]; + return; + } - } else switch (value) { - case 0x18: /* POP / [sp++] */ - *v = &(d->ram[ d->sp++ ]); - TRACE(">> POP [0x%04x] (0x%04x)", - d->sp - 1, - **v); - *e_addr = d->sp - 1; + switch (value) { + case 0x18: /* PUSH/[--SP] or POP/[SP++] */ + if (value_is_a == 0) { /* b */ + *v = &(d->ram[sp - 1]); + *sp_adjust -= 1; + *e_addr = sp - 1; + TRACE("%s>> PUSH [0x%04x] (0x%04x)", __func__, sp - 1, **v); + } else { /* a */ + *v = &(d->ram[sp]); + *sp_adjust += 1; + *e_addr = sp; + TRACE("%s>> POP [0x%04x] (0x%04x)", __func__, sp, **v); + } break; - case 0x19: /* PEEK / [sp] */ - *v = &(d->ram[ d->sp ]); - TRACE(">> PEEK [0x%04x] (0x%04x)", - d->sp, + case 0x19: /* PEEK/[SP] */ + *v = &(d->ram[sp]); + *e_addr = sp; + TRACE("%s>> PEEK [0x%04x] (0x%04x)", + __func__, + sp, **v); - *e_addr = d->sp; break; - case 0x1a: /* PUSH / [--sp] */ - *v = &(d->ram[ --d->sp ]); - TRACE(">> PUSH [0x%04x] (0x%04x)", - d->sp + 1, + case 0x1a: /* PICK n */ + *pc_adjust += 1; + *cycle_adjust += 1; + *e_addr = sp + value_data; + *v = d->ram + *e_addr; + TRACE("%s>> PICK 0x%04x [0x%04x] (0x%04x)", + __func__, + value_data, + sp + value_data, **v); - *e_addr = d->sp + 1; break; case 0x1b: /* SP */ *v = &(d->sp); - TRACE(">> SP (0x%04x)", + TRACE("%s>> %s (0x%04x)", + __func__, + "SP", **v); break; case 0x1c: /* PC */ *v = &(d->pc); - TRACE(">> PC (0x%04x)", **v); + TRACE("%s>> %s (0x%04x)", + __func__, + "PC", + **v); break; - case 0x1d: /* O */ - *v = &(d->o); - TRACE(">> O (0x%04x)", **v); + case 0x1d: /* EX */ + *v = &(d->ex); + TRACE("%s>> %s (0x%04x)", + __func__, + "EX", + **v); break; case 0x1e: /* [next word] / [[pc++]] */ - nextword = d->ram[ d->pc++ ]; - d->cycle++; - *v = &(d->ram[ nextword ]); - TRACE(">> [nextword] [0x%04x] (0x%04x)", - nextword, + *pc_adjust += 1; + *cycle_adjust += 1; + *e_addr = value_data; + *v = d->ram + *e_addr; + TRACE("%s>> [nextword] [0x%04x] (0x%04x)", + __func__, + value_data, **v); - *e_addr = nextword; break; case 0x1f: /* next word (literal) / [pc++] */ - nextword = d->ram[ d->pc++ ]; - d->cycle++; - *work_v = nextword; + *pc_adjust += 1; + *cycle_adjust += 1; + *work_v = value_data; *v = work_v; - TRACE(">> nextword (0x%04x)", **v); + TRACE("%s>> nextword (0x%04x)", + __func__, + **v); break; - default: /* 0x20-0x3f: literal values 0x00-0x1f */ - *work_v = value & 0x1f; + default: /* 0x20-0x3f: literal values 0xffff-0x1e */ + *work_v = (value & 0x1f) - 1; *v = work_v; - TRACE(">> literal (0x%04x)", **v); + TRACE("%s>> literal (0x%04x)", + __func__, + **v); } - - return retval; } @@ -216,25 +302,37 @@ 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 *d, 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;\ + 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_(d, val_a, 1, val_a_data,\ + &d->reg_work_[1], &a, &ev_a_addr,\ + &pc_adjust, &sp_adjust, &cycle_adjust);\ + d->sp += sp_adjust;\ + d->cycle += 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 +#define OP_BASIC_ value_decode_(d, val_b, 0, val_b_data,\ + &d->reg_work_[0], &b, &ev_b_addr,\ + &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 @@ -256,10 +354,11 @@ 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)); + /* fire an illegal instruction event for current instruction */ + DCPU16_WORD future_opcode = (d->ram[d->pc - pc_adjust] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_B_BITS)); WARN("reserved future opcode 0x%04x invoked", future_opcode); - ACCT_ILL(d->pc); + ACCT_ILL(d->pc - pc_adjust); } OP_IMPL(nbi_jsr) { @@ -282,14 +381,151 @@ OP_IMPL(nbi__reserved2_) { WARN("reserved nbi opcode invoked"); - ACCT_ILL(d->pc); + ACCT_ILL(d->pc - pc_adjust); +} + +OP_IMPL(nbi_int) { + OP_NBI(nbi_int); + + ACCT_R(ev_a_addr); + + if (d->ia) { + if ( interrupt_enqueue_(d, *a) ) { + WARN("failed to queue interrupt"); + return; + } + + if (d->interrupts_deferred_) + return; + + d->interrupts_deferred_ = 1; + d->ram[--d->sp] = d->pc; + d->ram[--d->sp] = d->reg[reg_index_('A')]; + d->pc = d->ia; + d->reg[0] = *a; + } + + d->cycle += 4; +} + +OP_IMPL(nbi_iag) { + OP_NBI(nbi_iag); + + *a = d->ia; + + ACCT_W(ev_a_addr); +} + +OP_IMPL(nbi_ias) { + OP_NBI(nbi_ias); + + d->ia = *a; + + ACCT_R(ev_a_addr); +} + +/* does this just ignore its operand? */ +OP_IMPL(nbi_rfi) { + OP_NBI(nbi_rfi); + + d->interrupts_deferred_ = 0; + d->reg[reg_index_('A')] = d->ram[d->sp++]; + d->pc = d->ram[d->sp++]; +} + +OP_IMPL(nbi_iaq) { + OP_NBI(nbi_iaq); + + if (*a) { + d->interrupts_deferred_ = 1; + } else { + d->interrupts_deferred_ = 0; + } + + ACCT_R(ev_a_addr); +} + +OP_IMPL(nbi_hwn) { + OP_NBI(nbi_hwn); + + ACCT_W(ev_a_addr); + + *a = d->hw_table_entries_; + + d->cycle += 2; +} + +OP_IMPL(nbi_hwq) { + OP_NBI(nbi_hwq); + + ACCT_R(ev_a_addr); + + if (*a >= d->hw_table_entries_) { + WARN("hardware query for non-extant device 0x%04x", *a); + d->reg[reg_index_('A')] = 0; + d->reg[reg_index_('B')] = 0; + d->reg[reg_index_('C')] = 0; + d->reg[reg_index_('X')] = 0; + d->reg[reg_index_('Y')] = 0; + return; + } + + d->reg[reg_index_('A')] = d->hw_table_[*a].id_l; + d->reg[reg_index_('B')] = d->hw_table_[*a].id_h; + d->reg[reg_index_('C')] = d->hw_table_[*a].ver; + d->reg[reg_index_('X')] = d->hw_table_[*a].mfg_l; + d->reg[reg_index_('Y')] = d->hw_table_[*a].mfg_h; + + d->cycle += 4; +} + +OP_IMPL(nbi_hwi) { + OP_NBI(nbi_hwi); + + ACCT_R(ev_a_addr); + + if (*a > d->hw_table_entries_) { + WARN("interrupt for non-extant device 0x%04x", *a); + return; + } + + d->cycle += 4; + d->hw_table_[*a].int_fn(d, d->hw_table_[*a].data); +} + +OP_IMPL(nbi_hcf) { + OP_NBI(nbi_hcf); + + ACCT_R(ev_a_addr); + + d->on_fire_ = 1; + WARN("system on fire"); + + d->cycle += 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,215 +533,325 @@ 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, (thus does not advance sp) + 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); + e->impl(d, 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(ev_a_addr); /* - 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(ev_a_addr); + ACCT_W(ev_b_addr); } 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(ev_b_addr); ACCT_R(ev_a_addr); - *a = acc; - d->o = (acc > 0xffff); + *b = acc; + d->ex = (acc > 0xffff); d->cycle += 2; - ACCT_W(ev_a_addr); + ACCT_W(ev_b_addr); } 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(ev_b_addr); ACCT_R(ev_a_addr); - *a = acc; - d->o = (acc > 0xffff); + *b = acc; + d->ex = (acc > 0xffff); d->cycle += 2; - ACCT_W(ev_a_addr); + ACCT_W(ev_b_addr); } 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(ev_b_addr); ACCT_R(ev_a_addr); - *a = acc; - d->o = acc >> 16; + *b = acc; + d->ex = acc >> 16; d->cycle += 2; - ACCT_W(ev_a_addr); + ACCT_W(ev_b_addr); +} + +OP_IMPL(mli) { + OP_BASIC(mli); + /* sets b to b*a, signed */ + int acc = (short)*b * (short)*a; + + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + + *b = acc; + d->ex = acc >> 16; + + d->cycle += 2; + + ACCT_W(ev_b_addr); } 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); - if (*b == 0) { - *a = 0; - d->o = 0; + if (*a == 0) { + *b = 0; + d->ex = 0; } else { - *a = *a / *b; - d->o = (*a << 16) / *b; + *b = *b / *a; + d->ex = (*b << 16) / *a; } d->cycle += 3; - ACCT_W(ev_a_addr); + ACCT_W(ev_b_addr); } -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); - if (*b == 0) { - *a = 0; + if (*a == 0) { + *b = 0; + d->ex = 0; } else { - *a = *a % *b; + *b = (short)*b / (short)*a; + d->ex = (short)(*b << 16) / (short)*a; } d->cycle += 3; - ACCT_W(ev_a_addr); + ACCT_W(ev_b_addr); } -OP_IMPL(shl) { - OP_BASIC(shl); - /* sets a to a<>16)&0xffff */ - unsigned int acc = *a << *b; +OP_IMPL(mod) { + OP_BASIC(mod); + /* sets b to b%a. if a==0, sets b to 0 instead. */ ACCT_R(ev_b_addr); ACCT_R(ev_a_addr); - *a = acc; - - d->o = acc >> 16; + if (*a == 0) { + *b = 0; + } else { + *b = *b % *a; + } - d->cycle += 2; + d->cycle += 3; ACCT_W(ev_a_addr); } -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); - *a = acc; - d->o = (*a << 16) >> *b; + if (*a == 0) { + *b = 0; + } else { + *b = (short)*b % (short)*a; + } - d->cycle += 2; + d->cycle += 3; - ACCT_W(ev_a_addr); + ACCT_W(ev_b_addr); } 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); - *a = *a & *b; + *b = *b & *a; d->cycle += 1; - ACCT_W(ev_a_addr); + ACCT_W(ev_b_addr); } 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); - *a = *a | *b; + *b = *b | *a; d->cycle += 1; - ACCT_W(ev_a_addr); + ACCT_W(ev_b_addr); } OP_IMPL(xor) { OP_BASIC(xor); - /* sets a to a^b */ + /* sets b to b^a */ ACCT_R(ev_b_addr); ACCT_R(ev_a_addr); - *a = *a ^ *b; + *b = *b ^ *a; d->cycle += 1; - ACCT_W(ev_a_addr); + ACCT_W(ev_b_addr); +} + +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(ev_b_addr); + ACCT_R(ev_a_addr); + + *b = acc & 0xffff; + d->ex = (*b << 16) >> *a; + + d->cycle += 2; + + WARN("IMPLEMENT"); + + ACCT_W(ev_b_addr); +} + +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(ev_b_addr); + ACCT_R(ev_a_addr); + + *b = acc & 0xffff; + d->ex = (*b << 16) >> *a; + + d->cycle += 2; + + WARN("IMPLEMENT"); + + ACCT_W(ev_b_addr); +} + +OP_IMPL(shl) { + OP_BASIC(shl); + /* sets b to b<>16)&0xffff */ + unsigned int acc = *b << *a; + + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + + *b = acc; + + d->ex = acc >> 16; + + d->cycle += 2; + + ACCT_W(ev_b_addr); +} + +OP_IMPL(ifb) { + OP_BASIC(ifb); + /* performs next instruction only if (b&a)!=0 */ + + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + + if ((*b & *a) != 0) { + /* */ + } else { + d->skip_ = 1; + d->cycle += 1; + } + + d->cycle += 2; +} + +OP_IMPL(ifc) { + OP_BASIC(ifc); + /* performs next instruction only if (b&a)==0 */ + + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + + if ((*b & *a) == 0) { + + } else { + d->skip_ = 1; + d->cycle += 1; + } + + d->cycle += 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); - if (*a == *b) { + if (*b == *a) { /* */ } else { d->skip_ = 1; - d->cycle++; + d->cycle += 1; } d->cycle += 2; @@ -513,12 +859,12 @@ OP_IMPL(ife) { 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); - if (*a != *b) { + if (*b != *a) { /* */ } else { d->skip_ = 1; @@ -530,12 +876,12 @@ OP_IMPL(ifn) { 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); - if (*a > *b) { + if (*b > *a) { /* */ } else { d->skip_ = 1; @@ -545,14 +891,31 @@ OP_IMPL(ifg) { d->cycle += 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); - if ((*a & *b) != 0) { + if (*b > *a) { + /* */ + } else { + d->skip_ = 1; + d->cycle += 1; + } + + d->cycle += 2; +} + +OP_IMPL(ifl) { + OP_BASIC(ifl); + /* performs next instruction only if bskip_ = 1; @@ -562,28 +925,141 @@ OP_IMPL(ifb) { d->cycle += 2; } +OP_IMPL(ifu) { + OP_BASIC(ifu); + /* performs next instruction only if bskip_ = 1; + d->cycle += 1; + } + + d->cycle += 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(ev_b_addr); + ACCT_R(ev_a_addr); + + acc = *b + *a + d->ex; + *b = acc & 0xffff; + if (acc > 0xffff) + d->ex = 0x0001; + else + d->ex = 0x0000; + + d->cycle += 3; + + ACCT_W(ev_b_addr); +} + +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(ev_b_addr); + ACCT_R(ev_a_addr); + + acc = *b - *a + d->ex; + *b = acc & 0xffff; + if (acc > 0xffff) + d->ex = 0xffff; + else + d->ex = 0; + + d->cycle += 3; + + ACCT_W(ev_b_addr); +} + +OP_IMPL(sti) { + OP_BASIC(sti); + /* sets b to a, then increases I and J by 1 */ + + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + + *b = *a; + d->reg[reg_index_('I')] += 1; + d->reg[reg_index_('J')] += 1; + + d->cycle += 2; + + ACCT_W(ev_b_addr); +} + +OP_IMPL(std) { + OP_BASIC(std); + /* sets b to a, then decreases I and J by 1 */ + + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + + *b = *a; + d->reg[reg_index_('I')] -= 1; + d->reg[reg_index_('J')] -= 1; + + d->cycle += 2; + + ACCT_W(ev_b_addr); +} + +OP_IMPL(_reserved_) { + OP_BASIC(_reserved_); + + WARN("reserved opcode invoked"); + + ACCT_ILL(d->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 } }; static inline -void dump_operand_value_(DCPU16_WORD value, DCPU16_WORD nextword) { +void dump_operand_value_(DCPU16_WORD value, DCPU16_WORD nextword, unsigned int value_position) { if (value <= 0x07) { printf(" %c", regnames_[value]); } else if (value <= 0x0f) { @@ -591,35 +1067,93 @@ void dump_operand_value_(DCPU16_WORD value, DCPU16_WORD nextword) { } else if (value <= 0x17) { printf(" [0x%04x + %c]", nextword, regnames_[value & 0x07]); } else switch (value) { - case 0x18: printf(" POP"); break; + case 0x18: + if (value_position == 0) { /* b */ + printf(" PUSH"); + } else { + printf(" POP"); + } + break; case 0x19: printf(" PEEK"); break; - case 0x1a: printf(" PUSH"); break; + case 0x1a: printf(" PICK 0x%04x", nextword); break; case 0x1b: printf(" SP"); break; case 0x1c: printf(" PC"); break; - case 0x1d: printf(" O"); 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 - 0x20); + default: printf(" 0x%02x", value - 0x21); } } -/* 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 ( (*b >= 0x10 && *b <= 0x17) || *b == 0x1e || *b == 0x1f ) { + *b_data = mem + (DCPU16_WORD)(addr + *instr_len); + TRACE("**b_data:%hu", **b_data); + *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); + TRACE("**a_data:%hu", **a_data); + *instr_len += 1; + } else { + *a_data = NULL; + } + +#if 0 + 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", + __func__, + addr, + mem[addr], + *opcode, + *b, + *b_data ? **b_data : 0, + *a, + *a_data ? **a_data : 0, + *instr_len); +#endif +} + +/* 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; + + 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) { + dump_operand_value_(b, b_data ? *b_data : 0, 0); + printf(","); } + + if (opcode || b) { + dump_operand_value_(a, a_data ? *a_data : 0, 1); + } + + return instr_len; } /* dcpu16_disassemble_print @@ -627,35 +1161,33 @@ void instruction_decode_(struct dcpu16 *d, DCPU16_WORD addr, DCPU16_WORD *opcode 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 opcode, b, a, instr_len, i, *b_data, *a_data; + DCPU16_WORD buf[3] = { d->ram[addr], d->ram[(DCPU16_WORD)(addr + 1)], d->ram[(DCPU16_WORD)(addr + 2)] }; unsigned int indent = 0; unsigned int partial = 0; if (!d) 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_(d->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_(d->ram, addr, &opcode, &b, &b_data, &a, &a_data, &instr_len); /* show the raw words */ printf("%04x", d->ram[addr]); @@ -664,59 +1196,75 @@ DCPU16_WORD dcpu16_disassemble_print(struct dcpu16 *d, DCPU16_WORD addr) { } /* 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; } /* execute the next instruction */ void dcpu16_step(struct dcpu16 *d) { - DCPU16_WORD opcode, a, b, instr_len; + DCPU16_WORD opcode, b, a, instr_len, *b_data, *a_data; const struct opcode_entry *e; if (!d) return; acct_event_(d, DCPU16_ACCT_EV_CYCLE, d->pc); - /* - 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); + /* if we're currently servicing interrupts */ + if (d->interrupts_deferred_ == 0) { + /* and there are interrupts to be serviced */ + if (d->interrupts_head_ != d->interrupts_tail_) { + DCPU16_WORD message; + message = interrupt_dequeue_(d); + + if (d->ia) { + TRACE("servicing interrupt IA:0x%04x message:0x%04x \n", d->ia, message); + /* then service the next interrupt */ + d->interrupts_deferred_ = 1; + d->ram[--d->sp] = d->pc; + d->ram[--d->sp] = d->reg[reg_index_('A')]; + d->pc = d->ia; + d->reg[0] = message; + } else { + TRACE("ignoring interrupt IA:0"); + } + } + } + + /* and make sure to execute an instruction after an interrupt */ - d->pc++; /* all instructions take at least one word */ + instruction_decode_(d->ram, d->pc, &opcode, &b, &b_data, &a, &a_data, &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); + TRACE("%s>> %s 0x%04x, 0x%04x", __func__, e->name, b, a); + e->impl(d, b, b_data ? *b_data : 0, a, a_data ? *a_data : 0); break; } } - /* and jump over next instr if needed */ + /* get ready for the next one */ + d->pc += instr_len; + + /* and jump over next instr(s) if needed */ if (d->skip_) { - instruction_decode_(d, d->pc, &opcode, &a, &b, &instr_len); + instruction_decode_(d->ram, d->pc, &opcode, &b, &b_data, &a, &a_data, &instr_len); d->pc += instr_len; - d->skip_ = 0; TRACE("++ SKIPPED %x words", instr_len); + if (opcode >= 0x10 && opcode <= 0x17) { + /* skipping a branch instruction? skip branch's skippable instruction as well */ + d->cycle += 1; + instruction_decode_(d->ram, d->pc, &opcode, &b, &b_data, &a, &a_data, &instr_len); + d->pc += instr_len; + TRACE("++ SKIPPED %x words", instr_len); + } + d->skip_ = 0; } } @@ -734,11 +1282,12 @@ void dcpu16_state_print(struct dcpu16 *d) { printf(" %c:0x%04x", regnames_[i], d->reg[i]); printf("\n"); - printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:", + printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:", d->cycle, - "O", d->o, + "EX", d->ex, "SP", d->sp, "PC", d->pc, + "IA", d->ia, "PC"); dcpu16_disassemble_print(d, d->pc); @@ -763,6 +1312,30 @@ void dcpu16_dump_ram(struct dcpu16 *d, DCPU16_WORD start, DCPU16_WORD end) { printf("\n"); } +/* dcpu16_hw_add + * registers new 'hardware' device with system + */ +int dcpu16_hw_add(struct dcpu16 *vm, struct dcpu16_hw *hw) { + if (!vm || !hw) + 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) { + fprintf(stderr, "%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_++; + + return 0; +} + /* dcpu16_acct_add * Register callback fn to be triggered whenever event matching any events * in bitwise mask occur. @@ -801,7 +1374,8 @@ void dcpu16_reset(struct dcpu16 *d) { memset(d->reg, 0, sizeof d->reg); d->pc = 0; d->sp = 0; - d->o = 0; + d->ex = 0; + d->ia = 0; d->skip_ = 0; memset(d->ram, 0, sizeof d->ram); diff --git a/dcpu16.h b/dcpu16.h index c893347..35c1fe8 100644 --- a/dcpu16.h +++ b/dcpu16.h @@ -8,20 +8,32 @@ typedef unsigned short DCPU16_WORD; /* how much ram the system has */ #define DCPU16_RAM 0x10000 +#define DCPU16_INTERRUPT_QUEUE_SIZE 256 /* a self-contained dcpu16 core */ struct dcpu16 { struct dcpu16_acct_cb *cb_table_; /* list of callbacks to invoke for certain events */ size_t cb_table_entries_; /* callback list maintenance */ size_t cb_table_allocated_; /* callback list maintenance */ + + struct dcpu16_hw *hw_table_; /* list of hardware attached to system */ + size_t hw_table_entries_; /* hardware list maintenance */ + size_t hw_table_allocated_; /* hardware list maintenance */ + unsigned int skip_ : 1; /* skip execution of next instruction */ + unsigned int interrupts_deferred_ : 1; /* queue software interrupts */ + unsigned int on_fire_ : 1; /* cpu is on fire */ DCPU16_WORD reg_work_[2]; /* work registers for holding literal values while decoding instructions */ + DCPU16_WORD interrupts_[DCPU16_INTERRUPT_QUEUE_SIZE]; /* fifo of pending interrupts */ + size_t interrupts_head_; /* interrupt queue maintenance */ + size_t interrupts_tail_; /* interrupt queue maintenance */ unsigned long long cycle; /* number of cycles this core has executed */ DCPU16_WORD reg[8]; /* system registers, a b c x y z i j */ DCPU16_WORD pc; /* program counter */ DCPU16_WORD sp; /* stack pointer */ - DCPU16_WORD o; /* overflow */ + DCPU16_WORD ex; /* overflow */ + DCPU16_WORD ia; /* interrupt address */ DCPU16_WORD ram[DCPU16_RAM]; /* memory */ }; @@ -39,6 +51,18 @@ struct dcpu16_acct_cb { dcpu16_acct_event mask; }; +/* these are used to define hardware attached to the system */ +struct dcpu16_hw { + char *name_; + DCPU16_WORD id_l; + DCPU16_WORD id_h; + DCPU16_WORD ver; + DCPU16_WORD mfg_l; + DCPU16_WORD mfg_h; + void (*int_fn)(struct dcpu16 *, void *); + void *data; +}; + /* instantiate a new core */ struct dcpu16 *dcpu16_new(void); @@ -51,11 +75,17 @@ void dcpu16_state_print(struct dcpu16 *); /* print the contents of ram from second to third argument */ void dcpu16_dump_ram(struct dcpu16 *, DCPU16_WORD, DCPU16_WORD); +/* print words in buf as asm */ +DCPU16_WORD dcpu16_mnemonify_buf(DCPU16_WORD *); + /* print the instruction at the specified address, returns number of words consumed in decoding */ DCPU16_WORD dcpu16_disassemble_print(struct dcpu16 *, DCPU16_WORD); +/* register new 'hardware' device with system */ +int dcpu16_hw_add(struct dcpu16 *, struct dcpu16_hw *); + /* register a callback for an accounting event */ -int dcpu16_acct_add(struct dcpu16 *, dcpu16_acct_event mask, dcpu16_ev_cb_t *fn, void *data); +int dcpu16_acct_add(struct dcpu16 *, dcpu16_acct_event, dcpu16_ev_cb_t *, void *); /* execute the next instruction */ void dcpu16_step(struct dcpu16 *);