X-Git-Url: http://git.squeep.com/?p=dcpu16;a=blobdiff_plain;f=dcpu16.c;h=772aec84917cd407884f7a281a6e0ed3be547d29;hp=cfa6d8a2b02e2faa3f11e144db8528b0338a8d5b;hb=76e24789b10f9165fa53b569c9ae33504d237ca6;hpb=eb3e31c1bfba67227dfd02890cc84330b1580918 diff --git a/dcpu16.c b/dcpu16.c index cfa6d8a..772aec8 100644 --- a/dcpu16.c +++ b/dcpu16.c @@ -21,15 +21,18 @@ * 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 * * TODO + * change api to print into buffers rather than stdio * drop checks for assigning to literals -- it won't affect anything anyhow - * debug short literal decoding + * refactor opcode functiontables into switch statements */ static const char * const src_id_ = "$Id$"; -#define WORD DCPU16_WORD +#define OPCODE_BASIC_BITS 4 +#define OPCODE_OPERAND_BITS 6 static const char regnames_[] = "ABCXYZIJ"; @@ -80,13 +83,32 @@ void dcpu16_trace_cb_set(void (*fn)(char *fmt, ...)) { trace_cb_ = fn; } + +/* acct_event_ + * invokes callbacks for specified event + */ +static inline +void acct_event_(struct dcpu16 *vm, dcpu16_acct_event_ ev, DCPU16_WORD addr) { + struct dcpu16_acct_cb *cb = vm->cb_table_; + size_t i; + + for (i = 0; i < vm->cb_table_entries_; i++) { + if ( (cb[i].mask & ev) ) + cb[i].fn(ev, addr); + } +} + + /* value_decode_ * sets *v to be the destination of the value - * workv is buffer to use to accumulate literal value before use + * 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, WORD value, WORD *work_v, WORD **v) { - WORD nextword; +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; assert(value <= 0x3f); @@ -95,14 +117,6 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR if (value >= 0x1f) retval = 1; - /* if we're skipping this instruction, just advance the pc if needed */ - if (d->skip_) { - TRACE(">> SKIP decode"); - if (value == 0x1e || value == 0x1f) - d->pc++; - return retval; - } - if (value <= 0x07) { /* register */ *v = d->reg + value; TRACE(">> %c (0x%04x)", @@ -115,6 +129,7 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR 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++ ]; @@ -125,6 +140,7 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR nextword, d->reg[(value & 0x07)], **v); + *e_addr = nextword + d->reg[(value & 0x07)]; } else switch (value) { case 0x18: /* POP / [sp++] */ @@ -132,6 +148,7 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR TRACE(">> POP [0x%04x] (0x%04x)", d->sp - 1, **v); + *e_addr = d->sp - 1; break; case 0x19: /* PEEK / [sp] */ @@ -139,6 +156,7 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR TRACE(">> PEEK [0x%04x] (0x%04x)", d->sp, **v); + *e_addr = d->sp; break; case 0x1a: /* PUSH / [--sp] */ @@ -146,6 +164,7 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR TRACE(">> PUSH [0x%04x] (0x%04x)", d->sp + 1, **v); + *e_addr = d->sp + 1; break; case 0x1b: /* SP */ @@ -171,6 +190,7 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR TRACE(">> [nextword] [0x%04x] (0x%04x)", nextword, **v); + *e_addr = nextword; break; case 0x1f: /* next word (literal) / [pc++] */ @@ -190,42 +210,37 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR return retval; } -#define OPCODE_BASIC_BITS (4) -#define OPCODE_BASIC_SHIFT (0) - -#define OPCODE_NBI_BITS (6) -#define OPCODE_NBI_SHIFT (4) - -#define OPCODE_FUTURE_BITS (16) -#define OPCODE_FUTURE_SHIFT (10) #define OPCODE_NAME_LEN 16 struct opcode_entry { unsigned short value; char name[OPCODE_NAME_LEN]; - void (*impl)(struct dcpu16 *, WORD, WORD); + void (*impl)(struct dcpu16 *, DCPU16_WORD, DCPU16_WORD); }; /* messy boilerplate for opcode handlers */ -#define OP_IMPL(x) static void op_##x(struct dcpu16 *d, WORD val_a, WORD val_b) +#define OP_IMPL(x) static void op_##x(struct dcpu16 *d, DCPU16_WORD val_a, DCPU16_WORD val_b) -#define OP_NBI_ (void)val_b, (void)b -#define OP_BASIC_ (void)value_decode(d, val_b, &d->reg_work_[0], &b) -#define OP_TYPE(op_type) WORD *a, *b;\ +#define OP_TYPE(op_type) DCPU16_WORD *a, *b;\ unsigned int lit_a;\ + DCPU16_WORD ev_a_addr = 0, ev_b_addr = 0;\ do {\ - lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\ + lit_a = value_decode_(d, val_a, &d->reg_work_[0], &a, &ev_a_addr);\ op_type;\ - if (d->skip_) {\ - TRACE("++ SKIPPED");\ - d->skip_ = 0;\ - return;\ - }\ } 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_BASIC(x) OP_TYPE(OP_BASIC_) #define OP_NBI(x) OP_TYPE(OP_NBI_) +/* + 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) /* extended opcodes */ @@ -240,18 +255,24 @@ OP_IMPL(nbi__reserved_) { OP_NBI(nbi__reserved_); /* reserved for future expansion */ - WORD future_opcode = (d->ram[d->pc] >> OPCODE_FUTURE_SHIFT); + DCPU16_WORD future_opcode = (d->ram[d->pc] >> (OPCODE_BASIC_BITS + OPCODE_OPERAND_BITS)); WARN("reserved future opcode 0x%04x invoked", future_opcode); + + ACCT_ILL(d->pc); } 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); + d->ram[ --d->sp ] = d->pc; d->pc = *a; d->cycle += 2; + + ACCT_W(d->sp + 1); } OP_IMPL(nbi__reserved2_) { @@ -259,6 +280,8 @@ OP_IMPL(nbi__reserved2_) { /* reserved */ WARN("reserved nbi opcode invoked"); + + ACCT_ILL(d->pc); } static const struct opcode_entry opcode_nbi_entries[] = { @@ -281,7 +304,7 @@ OP_IMPL(_nbi_) { /* don't do normal value decoding here */ - WORD nbi_opcode = val_a; + DCPU16_WORD nbi_opcode = val_a; const struct opcode_entry *e = opcode_nbi_entries; e = opcode_nbi_entries + ( (nbi_opcode < OPCODE_NBI_MAX) ? nbi_opcode : (OPCODE_NBI_MAX - 1) ); @@ -296,8 +319,11 @@ OP_IMPL(set) { OP_BASIC(set); /* sets a to b */ + ACCT_R(ev_b_addr); + ACCT_W(ev_a_addr); + /* only set non-literal target */ - if (val_a < 0x1f) { + if (!lit_a) { *a = *b; } @@ -309,12 +335,17 @@ OP_IMPL(add) { /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */ unsigned int acc = *a + *b; - if (val_a < 0x1f) { + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + + if (!lit_a) { *a = acc; } - d->o = acc >> 16; + d->o = (acc > 0xffff); d->cycle += 2; + + ACCT_W(ev_a_addr); } OP_IMPL(sub) { @@ -322,12 +353,16 @@ OP_IMPL(sub) { /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */ unsigned int acc = *a - *b; - if (val_a < 0x1f) { + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + + if (!lit_a) { *a = acc; } - d->o = acc >> 16; - + d->o = (acc > 0xffff); d->cycle += 2; + + ACCT_W(ev_a_addr); } OP_IMPL(mul) { @@ -335,26 +370,34 @@ OP_IMPL(mul) { /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */ unsigned int acc = *a * *b; - if (val_a < 0x1f) { + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + + if (!lit_a) { *a = acc; } d->o = acc >> 16; d->cycle += 2; + + ACCT_W(ev_a_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. */ + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + if (*b == 0) { - if (val_a < 0x1f) { + if (!lit_a) { *a = 0; } d->o = 0; } else { unsigned int acc = *a / *b; - if (val_a < 0x1f) { + if (!lit_a) { *a = acc; } @@ -363,23 +406,30 @@ OP_IMPL(div) { } d->cycle += 3; + + ACCT_W(ev_a_addr); } OP_IMPL(mod) { OP_BASIC(mod); /* sets a to a%b. if b==0, sets a to 0 instead. */ + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + if (*b == 0) { - if (val_a < 0x1f) { + if (!lit_a) { *a = 0; } } else { - if (val_a < 0x1f) { + if (!lit_a) { *a = *a % *b; } } d->cycle += 3; + + ACCT_W(ev_a_addr); } OP_IMPL(shl) { @@ -387,12 +437,17 @@ OP_IMPL(shl) { /* sets a to a<>16)&0xffff */ unsigned int acc = *a << *b; - if (val_a < 0x1f) { + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + + if (!lit_a) { *a = acc; } d->o = acc >> 16; d->cycle += 2; + + ACCT_W(ev_a_addr); } OP_IMPL(shr) { @@ -400,44 +455,64 @@ OP_IMPL(shr) { /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */ unsigned int acc = *a >> *b; - if (val_a < 0x1f) { + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + + if (!lit_a) { *a = acc; } d->o = (*a << 16) >> *b; d->cycle += 2; + + ACCT_W(ev_a_addr); } OP_IMPL(and) { OP_BASIC(and); /* sets a to a&b */ - if (val_a < 0x1f) { + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + + if (!lit_a) { *a = *a & *b; } d->cycle += 1; + + ACCT_W(ev_a_addr); } OP_IMPL(bor) { OP_BASIC(bor); /* sets a to a|b */ - if (val_a < 0x1f) { + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + + if (!lit_a) { *a = *a | *b; } d->cycle += 1; + + ACCT_W(ev_a_addr); } OP_IMPL(xor) { OP_BASIC(xor); /* sets a to a^b */ - if (val_a < 0x1f) { + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + + if (!lit_a) { *a = *a ^ *b; } + ACCT_W(ev_a_addr); + d->cycle += 1; } @@ -445,6 +520,9 @@ OP_IMPL(ife) { OP_BASIC(ife); /* performs next instruction only if a==b */ + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + if (*a == *b) { /* */ } else { @@ -459,6 +537,9 @@ OP_IMPL(ifn) { OP_BASIC(ifn); /* performs next instruction only if a!=b */ + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + if (*a != *b) { /* */ } else { @@ -473,6 +554,9 @@ OP_IMPL(ifg) { OP_BASIC(ifg); /* performs next instruction only if a>b */ + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + if (*a > *b) { /* */ } else { @@ -487,6 +571,9 @@ OP_IMPL(ifb) { OP_BASIC(ifb); /* performs next instruction only if (a&b)!=0 */ + ACCT_R(ev_b_addr); + ACCT_R(ev_a_addr); + if ((*a & *b) != 0) { /* */ } else { @@ -517,12 +604,13 @@ static const struct opcode_entry opcode_basic_entries[] = { {0x0, "", NULL } }; -void dump_value(WORD value, WORD nextword) { - if (value < 0x07) { +static inline +void dump_operand_value_(DCPU16_WORD value, DCPU16_WORD nextword) { + if (value <= 0x07) { printf(" %c", regnames_[value]); - } else if (value < 0x0f) { + } else if (value <= 0x0f) { printf(" [%c]", regnames_[value & 0x07]); - } else if (value < 0x17) { + } else if (value <= 0x17) { printf(" [0x%04x + %c]", nextword, regnames_[value & 0x07]); } else switch (value) { case 0x18: printf(" POP"); break; @@ -537,77 +625,135 @@ void dump_value(WORD value, WORD nextword) { } } -void dcpu16_disassemble_print(struct dcpu16 *d, WORD addr) { - WORD opcode, a, b; - unsigned int instr_len = 1; - const struct opcode_entry *e; - - opcode = (d->ram[addr] >> OPCODE_BASIC_SHIFT) & ((1 << OPCODE_BASIC_BITS) - 1); - a = (d->ram[addr] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS)) & ((1 << 6) - 1); - b = (d->ram[addr] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS + 6)) & ((1 << 6) - 1); - assert(opcode <= 0x0f); - assert(a <= 0x3f); - assert(b <= 0x3f); - - printf("%04x", d->ram[addr]); +/* split a word into the parts of an instruction, and determine how many words it takes up in total */ +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; + } +} - if (opcode != 0) - { - if (a == 0x1e || a == 0x1f) { - printf(" %04x", d->ram[addr + instr_len]); - 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; + unsigned int indent = 0; + unsigned int partial = 0; + + if (!d) return 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); + if (instr_len > i) + partial++; + if (instr_len == i && opcode >= 0xc) { + indent++; + break; } } - if (b == 0x1e || b == 0x1f) { - printf(" %04x", d->ram[addr + instr_len]); - instr_len++; - } + + /* 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) ); - printf("%s%s ; %s", + /* show the raw words */ + printf("%04x", d->ram[addr]); + for (i = 1; i < instr_len; i++) { + printf(" %04x", d->ram[addr + i]); + } + + /* align things neatly, show the instruction */ + printf("%s%s ;%s%s%s", instr_len < 3 ? " " : "", instr_len < 2 ? " " : "", + partial ? "*" : " ", + indent ? " " : "", e->name); - if (opcode != 0) { - dump_value(a, d->ram[addr + 1]); - if (a == 0x1e || a == 0x1f) + + /* 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(","); } - dump_value(b, d->ram[addr + 1]); + if (opcode || a) + dump_operand_value_(b, d->ram[addr + 1]); + + return instr_len; } +/* execute the next instruction */ void dcpu16_step(struct dcpu16 *d) { - WORD opcode; - WORD val_a, val_b; + DCPU16_WORD opcode, a, b, instr_len; const struct opcode_entry *e; - /* decode opcode and invoke */ + if (!d) return; - opcode = (d->ram[ d->pc ] >> OPCODE_BASIC_SHIFT) & ((1 << OPCODE_BASIC_BITS) - 1); - val_a = (d->ram[d->pc] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS)) & ((1 << 6) - 1); - val_b = (d->ram[d->pc] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS + 6)) & ((1 << 6) - 1); + /* + 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); - d->pc++; + d->pc++; /* all instructions take at least one word */ for (e = opcode_basic_entries; e->impl; e++) { if (e->value == opcode) { - TRACE(">> %s 0x%04x, 0x%04x", e->name, val_a, val_b); - e->impl(d, val_a, val_b); + TRACE(">> %s 0x%04x, 0x%04x", e->name, a, b); + e->impl(d, a, b); break; } } + + /* 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); + } } +/* + 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; + + printf(" "); + for (i = 0; i < 8; i++) + printf(" %c:0x%04x", regnames_[i], d->reg[i]); + printf("\n"); + printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:", d->cycle, "O", d->o, @@ -616,20 +762,18 @@ void dcpu16_state_print(struct dcpu16 *d) { "PC"); dcpu16_disassemble_print(d, d->pc); - printf("\n "); - - for (i = 0; i < 8; i++) - printf(" %c:0x%04x", regnames_[i], d->reg[i]); printf("\n"); } /* dcpu16_dump_ram * print raw ram contents from start to stop */ -void dcpu16_dump_ram(struct dcpu16 *d, WORD start, WORD end) { +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 */ + if (!d) return; + for (i = start, j = 0; i <= end; i++, j++) { if (j % n == 0) printf("0x%04x:\t", i); @@ -639,10 +783,39 @@ void dcpu16_dump_ram(struct dcpu16 *d, WORD start, WORD end) { printf("\n"); } +/* 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, void (*fn)(dcpu16_acct_event_, DCPU16_WORD)) { + struct dcpu16_acct_cb cb; + + cb.mask = mask; + cb.fn = fn; + + if (vm->cb_table_entries_ == vm->cb_table_allocated_) { + size_t new_entries = vm->cb_table_allocated_ + 32; + void *tmp_ptr = realloc(vm->cb_table_, new_entries * sizeof *(vm->cb_table_)); + if (tmp_ptr == NULL) { + fprintf(stderr, "%s():%s", "realloc", strerror(errno)); + return -1; + } + vm->cb_table_ = tmp_ptr; + vm->cb_table_allocated_ += 32; + } + + memcpy(vm->cb_table_ + vm->cb_table_entries_, &cb, sizeof cb); + vm->cb_table_entries_++; + + return 0; +} + /* dcpu16_reset * resets a dcpu16 instance to initial state */ void dcpu16_reset(struct dcpu16 *d) { + if (!d) return; + memset(d, 0, sizeof *d); } @@ -663,6 +836,8 @@ struct dcpu16 *dcpu16_new(void) { * release a dcpu16 instance */ void dcpu16_delete(struct dcpu16 **vm) { + if (!vm || !*vm) return; + free(*vm); *vm = NULL; }