commit ff63a59cac215de480e2a751db11c78bef4d2c7c Author: Loic Nageleisen Date: Thu Apr 5 23:45:16 2012 +0200 almost there, for an initial impl. ironically test.py ended up way more advanced than dcpu_16.py diff --git a/dcpu_16.py b/dcpu_16.py new file mode 100644 index 0000000..ad94ac7 --- /dev/null +++ b/dcpu_16.py @@ -0,0 +1,136 @@ +# System width +w = 16 +wmask = 2**16-1 + +# Literal values +literal = xrange(0x00, 0x20) + +opcodes = [ + 'nbi', + 'SET', + 'ADD', + 'SUB', + 'MUL', + 'DIV', + 'MOD', + 'SHL', + 'SHR', + 'AND', + 'BOR', + 'XOR', + 'IFE', + 'IFN', + 'IFG', + 'IFB', +] + +class CPU(object): + + class Ops(object): + def SET(c, a, b): + return None + def ADD(c, a, b): + return None + def SUB(c, a, b): + return None + def MUL(c, a, b): + return None + def DIV(c, a, b): + return None + def MOD(c, a, b): + return None + def SHL(c, a, b): + return (a << b) & wmask + def SHR(c, a, b): + return (a >> b) + def AND(c, a, b): + return a & b + def BOR(c, a, b): + return a | b + def XOR(c, a, b): + return a ^ b + def IFE(c, a, b): + return a == b + def IFN(c, a, b): + return a != b + def IFG(c, a, b): + return a > b + def IFB(c, a, b): + return (a & b) != 0 + + def __init__(c): + c.reset() + c.clear() + + def reset(c): + """Reset CPU""" + c.r = [0 for _ in xrange(0, w)] + c.pc = 0 + c.sp = 0 + c.o = False + c.skip = False + + def clear(c): + """Clear memory""" + c.m = [0 for _ in xrange(0, 2**w)] + + def __getitem__(c, addr): + """Read value at memory address""" + return c.m[addr] + + def __setitem__(c, addr, value): + """Write value at memory address""" + c.m[addr] = value + + def next_word(c): + val = c[c.pc] + c.pc += 1 + return val + + def dispatch_op(c): + + def get_opcode(word): + return word & 0xF + def get_a(word): + return (word >> 4) & 0x3F + def get_b(word): + return (word >> 10) & 0x3F + + def get_op(word): + getattr(CPU.Ops, opcodes[get_opcode(word)]) + + def is_nbi(opcode): + return opcode == 0x0 + def is_set(opcode): + return 0x0 < opcode <= 0xb + def is_skip(opcode): + return 0xb < opcode <= 0xf + def set_a(value): + pass + + word = c.next_word() + if c.skip: + c.skip = False + return + + opcode = get_opcode(word) + if is_nbi(opcode): + pass + elif is_set(opcode): + set_a(get_op(opcode)()) + elif is_skip(opcode): + if not get_op(opcode)(): + c.skip = True + else: + raise OpcodeError(c.pc, opcode) + + + +class OpcodeError(Exception): + def __init__(self, addr, data): + self.addr = addr + self.data = data + + def __str__(self): + return "Invalid opcode at %s: '%s'" % (self.addr, self.data) + diff --git a/test.py b/test.py new file mode 100644 index 0000000..e0ebcd8 --- /dev/null +++ b/test.py @@ -0,0 +1,364 @@ +from __future__ import print_function, division + +def log(string): + print(" << %s" % string) + +#http://0x10c.com/doc/dcpu-16.txt +spec = '1.1' + +w = 16 +wmask = 2**w-1 + +literals = [l for l in xrange(0, 0x20)] + +opcode_map = {} +valcode_map = {} + + +class opcode(object): + def __init__(self, *opcode): + self.opcode = opcode + + def __call__(self, f): + _opcode_f = f + _opcode_f.opcode = self.opcode + opcode_map[self.opcode] = _opcode_f + return _opcode_f + + +class valcode(object): + def __init__(self, valcode): + self.valcode = valcode + + def __call__(self, f): + _valcode_f = f + _valcode_f.valcode = self.valcode + try: + for code in self.valcode: + # currify with (bound) code + valcode_map[code] = lambda c, code=code: _valcode_f(c, code) + valcode_map[code].__name__ = '%s(code=0x%02X)' % (_valcode_f.__name__, code) + except TypeError: + valcode_map[self.valcode] = _valcode_f + return _valcode_f + + +@opcode(0x0, 0x01) +def JSR(c, a): + """pushes the address of the next instruction to the stack, then sets PC to a""" + #pushnext + c.pc = c[a] + +@opcode(0x1) +def SET(c, a, b): + """sets a to b""" + c[a] = c[b] + +@opcode(0x2) +def ADD(c, a, b): + """sets a to a+b, sets O to 0x0001 if there's an overflow""" + res = (c[a] + c[b]) + if res > wmask: + c.o = 0x0001 + res = res & wmask + c[a] = res + +@opcode(0x3) +def SUB(c, a, b): + """sets a to a-b, sets O to 0xFFFF if there's an underflow""" + res = (c[a] - c[b]) + if res & (wmask+1): + c.o = 0xFFFF + res = res & wmask + c[a] = res + +@opcode(0x4) +def MUL(c, a, b): + """sets a to a*b, sets O to ((a*b)>>16)&0xFFFF""" + res = (c[a] * c[b]) + c.o = ((c[a] * c[b]) >> w) & wmask + res = res & wmask + c[a] = res + +@opcode(0x5) +def DIV(c, a, b): + """sets a to a/b, sets O to ((a<<16)/b)&0xFFFF""" + res = c[a] / c[b] + c.o = ((c[a] << w) / c[b]) & wmask + c[a] = res + +@opcode(0x6) +def MOD(c, a, b): + """sets a to a%b. if b==0, sets a to 0 instead""" + if c[b]==0: + res = 0 + else: + res = c[a] % c[b] + c[a] = res + +@opcode(0x7) +def SHL(c, a, b): + """sets a to a<>16)&0xFFFF""" + res = (c[a] << c[b]) + c.o = ((c[a] << c[b]) >> w) & wmask + res = res & wmask + c[a] = res + +@opcode(0x8) +def SHR(c, a, b): + """sets a to a>>b. sets O to ((a>>16)>>b)&0xFFFF""" + res = (c[a] >> c[b]) + c.o = ((c[a] >> w) >> c[b]) & wmask + res = res & wmask + c[a] = res + +@opcode(0x9) +def AND(c, a, b): + """sets a to a&b""" + res = c[a] & c[b] + c[a] = res + +@opcode(0xA) +def BOR(c, a, b): + """sets a to a|b""" + res = c[a] | c[b] + c[a] = res + +@opcode(0xB) +def XOR(c, a, b): + """sets a to a^b""" + res = c[a] ^ c[b] + c[a] = res + +@opcode(0xC) +def IFE(c, a, b): + """performs next instruction only if a==b""" + if c[a] == c[b]: + c.skip = True + +@opcode(0xD) +def IFN(c, a, b): + """performs next instruction only if a!=b""" + if c[a] != c[b]: + c.skip = True + +@opcode(0xE) +def IFG(c, a, b): + """performs next instruction only if a>b""" + if c[a] > c[b]: + c.skip = True + +@opcode(0xF) +def IFB(c, a, b): + """performs next instruction only if (a&b)!=0""" + if (c[a] & c[b]) != 0: + c.skip = True + + +def make_pointer(c, codestr): + """creates a pointer func that evaluates codestr""" + def getter(c=c): + return eval("%s" % codestr) + def setter(data, c=c): + exec("%s = %r" % (codestr, data)) in {}, {'c': c, 'data': data} + pointer = getter + pointer.codestr = codestr + pointer.set = setter + return pointer + +def pointerize(f): + """wraps a function that generates a codestr to create a pointer""" + if f.func_code.co_argcount == 1: + ptrz = lambda c: make_pointer(c, f(c)) + elif f.func_code.co_argcount == 2: + ptrz = lambda c, code: make_pointer(c, f(c, code)) + else: + raise Exception('%s has too many arguments' % f.__name__) + ptrz.__name__ = 'ptr_to_%s' % f.__name__ + ptrz.__doc__ = f.__doc__ + return ptrz + +@valcode(range(0x00, 0x08)) +@pointerize +def register(c, code): + """register""" + v = "c.r[%r]" % code + log(v) + return v + +@valcode(range(0x08, 0x10)) +@pointerize +def register_value(c, code): + """[register]""" + v = "c.m[c.r[%r-0x07]]" % code + log(v) + return v + +@valcode(range(0x10, 0x18)) +@pointerize +def next_word_plus_register_value(c, code): + """[next word + register]""" + v = "c.m[%r + %r-0x0f]" % (c.m[c.pc], code) + log(v) + c.pc += 1 + return v + +@valcode(0x18) +@pointerize +def pop(c): + """POP / [SP++]""" + v = "c.m[%r]" % c.sp + log(v) + c.sp += 1 + return v + +@valcode(0x19) +@pointerize +def peek(c): + """PEEK / [SP]""" + v = "c.m[%r]" % c.sp + log(v) + return v + +@valcode(0x1A) +@pointerize +def push(c): + """PUSH / [--SP]""" + c.sp -= 1 + v = "c.m[%r]" % c.sp + log(v) + return v + +@valcode(0x1B) +@pointerize +def stack_pointer(c): + """stack pointer""" + v = "c.sp" + log(v) + return v + +@valcode(0x1C) +@pointerize +def program_counter(c): + """program counter""" + v = "c.pc" + log(v) + return v + +@valcode(0x1D) +@pointerize +def overflow(c): + """overflow""" + v = "c.o" + log(v) + return v + +@valcode(0x1E) +@pointerize +def next_word_value(c): + """[next_word]""" + v = "c.m[%r]" % c.m[c.pc] + c.pc += 1 + log(v) + return v + +@valcode(0x1F) +@pointerize +def next_word(c): + """next_word (literal)""" + v = "c.m[%r]" % c.pc + c.pc += 1 + log(v) + return v + +@valcode(range(0x20, 0x40)) +@pointerize +def literal(c, code): + """literal value 0x00-0x1F (literal)""" + v = "%r" % (code - 0x20) + log(v) + return v + + + +class CPU(object): + def __init__(c, debug=False): + c.clear() + c.reset() + c.debug = debug + + def reset(c): + """reset CPU""" + c.r = [0 for _ in xrange(0, 8)] + c.pc = 0x0000 + c.sp = 0x0000 + c.o = 0x0000 + c.skip = False + + def clear(c): + """clear memory""" + c.m = [0 for _ in xrange(0, 2**w)] + + def _pointer(c, code): + """get pointer to value code""" + try: + return valcode_map[code](c) + except KeyError: + raise Exception("Invalid value code") + + def __getitem__(c, code): + """get pointer to value""" + return c._pointer(code)() + + def __setitem__(c, code, value): + """set value at pointer""" + c._pointer(code).set(value) + + def step(c): + """start handling [PC]""" + word = c.m[c.pc] + c.pc += 1 + opcode = word & 0xF + try: + op = opcode_map[(opcode,)] + log(op.__name__) + except KeyError: + raise Exception('Invalid opcode %01X at PC=%04X' % (opcode, c.pc)) + a = word >> 4 & 0x3F + b = word >> 10 & 0x3F + if c.skip: + c.skip = False + else: + op(c, a, b) + if c.debug: + log(c.dump_r()) + + def dump_r(c): + """human-readable register status""" + return " ".join( "%s=%04X" % + (["A", "B", "C", + "X", "Y", "Z", + "I", "J", + "PC", "SP", "O", + ][i], + (c.r + [c.pc, c.sp, c.o])[i]) + for i in range(11)) + + def load_m(c, io=None): + """load data in memory""" + # TODO: load from io object + data = [ + 0x7c01, 0x0030, 0x7de1, 0x1000, 0x0020, 0x7803, 0x1000, 0xc00d, + 0x7dc1, 0x001a, 0xa861, 0x7c01, 0x2000, 0x2161, 0x2000, 0x8463, + 0x806d, 0x7dc1, 0x000d, 0x9031, 0x7c10, 0x0018, 0x7dc1, 0x001a, + 0x9037, 0x61c1, 0x7dc1, 0x001a, 0x0000, 0x0000, 0x0000, 0x0000, + ] + for i in xrange(len(data)): + c.m[i] = data[i] + + def dump_m(c, io): + """dump memory data""" + # TODO: save to io object + pass + +