diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 7e99e36..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.pyc \ No newline at end of file diff --git a/README.mdown b/README.mdown index 9c59a9d..e59b781 100644 --- a/README.mdown +++ b/README.mdown @@ -1,20 +1,20 @@ # What is this? -A DCPU-16 implementation in Python. See [the spec][0]. +A DCPU-16 implementation in Python. See [the spec](http://0x10c.com/doc/dcpu-16.txt). -# Goal +# But what's the goal of this? There's another one already! -Many high-level implementations looked like C-in-other-language, so let's have a pythonic enough (whatever that means, but you should [read][1] [this][2]) implementation. The spirit of the thing is to be educative for everyone. +Well, what's wrong with taking another stab at it? Besides, I personnally felt it was too C-ish, and not pythonic enough (whatever that means). I wanted to revive whatever low-level (admittedly limited) ASM knowledge I had (from 6800/68000) and sharpen my Python-fu. The spirit of the thing is to be educative for everyone (including me). -# Usage +# How do I use this? -It's meant to be used interactively via the Python REPL as well as programmatically. A specific ASM REPL might be implemented at some point. +It's meant to be used interactively via the Python REPL as well as programmatically. I might implement a specific ASM REPL at some point. An example of a Python REPL session: - >>> from dcpu_16 import CPU, spec_demo + >>> from dcpu_16 import CPU >>> c = CPU(debug=True) - >>> c.load_m(spec_demo) # loads demo program + >>> c.load_m() # loads demo program >>> c.step() # step by one instruction << SET << c.r[0x0] @@ -31,21 +31,7 @@ An example of a Python REPL session: >>> c.dump_r() # get CPU register state as string 'A=0000 B=0000 C=0000 X=0000 Y=0000 Z=0000 I=0000 J=0000 PC=0000 SP=0000 O=0000' -# Status +# What is the status of this? -As of v1.0 the CPU implementation ought to be complete according to DCPU-16 spec v1.1. +It's not bug-free yet, the implementation itself is still a WIP and the whole of the spec example does not pass yet. But hey, that's what you get in a few late hours. Fixes coming, I promise. -# Features - -Opcodes and valcodes are as declarative as possible using decorators, leaving the dispatcher as a quasi-one-liner and leveraging `dict` power instead of `if`/`elif`. - -Using functions/methods mean there are doctrings everywhere, hence documentation is both very local and as exhaustive as possible. Try `help(dcpu_16)`. - -You can use `cpu[]` to dispatch valcodes and get/set directly without having to handle a pointer structure. - - -The CPU is a class, so you can instantiate a bunch of them. I might move memory outside the CPU so that it would be shared by CPU instances (SMP!) - -[0]: http://0x10c.com/doc/dcpu-16.txt -[1]: http://www.dabeaz.com/generators/ -[2]: http://www.dabeaz.com/coroutines/ diff --git a/dcpu_16.py b/dcpu_16.py index b0e60c1..c22727c 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -1,48 +1,32 @@ -"""DCPU-16 implementation. (c) 2012, Loic Nageleisen - -Spec: http://0x10c.com/doc/dcpu-16.txt - -See LICENSE for licensing information. -""" - - from __future__ import print_function, division - -# DCPU-16 spec version -spec = '1.1' - - -# log tool def log(string): print(" << %s" % string) -# width constants +#http://0x10c.com/doc/dcpu-16.txt +spec = '1.1' + w = 16 -wmask = 2**w - 1 +wmask = 2**w-1 + +literals = [l for l in xrange(0, 0x20)] + +opcode_map = {} +valcode_map = {} -_opcode_map = {} class opcode(object): - """Opcode decorator""" def __init__(self, *opcode): self.opcode = opcode def __call__(self, f): _opcode_f = f _opcode_f.opcode = self.opcode - if len(self.opcode) == 1: - _opcode_map[self.opcode[0]] = _opcode_f - else: - if not self.opcode[0] in _opcode_map.keys(): - _opcode_map[self.opcode[0]] = {} - _opcode_map[self.opcode[0]][self.opcode[1]] = _opcode_f + opcode_map[self.opcode] = _opcode_f return _opcode_f -_valcode_map = {} class valcode(object): - """Valcode decorator""" def __init__(self, valcode): self.valcode = valcode @@ -52,60 +36,18 @@ class valcode(object): 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) + 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 + valcode_map[self.valcode] = _valcode_f return _valcode_f -class Register(object): - """Register descriptor""" - def __init__(self, regcode=None): - self.regcode = regcode - - def __get__(self, c, type=None): - if self.regcode is not None: - return c.r[self.regcode] - else: - return self.value - - def __set__(self, c, value): - if self.regcode is not None: - c.r[self.regcode] = value & wmask - else: - self.value = value & wmask - -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 - - @opcode(0x0, 0x01) def JSR(c, a): """pushes the address of the next instruction to the stack, then sets PC to a""" - c.sp -= 1 - c.m[c.sp] = c.pc - c.pc = a() + #pushnext + c.pc = c[a] @opcode(0x1) def SET(c, a, b): @@ -141,8 +83,8 @@ def MUL(c, a, b): @opcode(0x5) def DIV(c, a, b): """sets a to a/b, sets O to ((a<<16)/b)&0xFFFF""" - res = a() // b() - c.o = ((a() << w) // b()) & wmask + res = a() / b() + c.o = ((a() << w) / b()) & wmask a.set(res) @opcode(0x6) @@ -192,40 +134,56 @@ def XOR(c, a, b): def IFE(c, a, b): """performs next instruction only if a==b""" if a() == b(): - c.skip = False - else: c.skip = True @opcode(0xD) def IFN(c, a, b): """performs next instruction only if a!=b""" if a() != b(): - c.skip = False - else: c.skip = True @opcode(0xE) def IFG(c, a, b): """performs next instruction only if a>b""" if a() > b(): - c.skip = False - else: c.skip = True @opcode(0xF) def IFB(c, a, b): """performs next instruction only if (a&b)!=0""" if (a() & b()) != 0: - c.skip = False - else: 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[0x%01X]" % code + log(v) return v @valcode(range(0x08, 0x10)) @@ -233,13 +191,15 @@ def register(c, code): def register_value(c, code): """[register]""" v = "c.m[c.r[0x%01X]]" % (code-0x07) + log(v) return v @valcode(range(0x10, 0x18)) @pointerize def next_word_plus_register_value(c, code): """[next word + register]""" - v = "c.m[0x%04X + c.r[0x%01X]]" % (c.m[c.pc], code-0x0F) + v = "c.m[0x%04X + 0x%01X]" % (c.m[c.pc], code-0x0F) + log(v) c.pc += 1 return v @@ -248,6 +208,7 @@ def next_word_plus_register_value(c, code): def pop(c): """POP / [SP++]""" v = "c.m[0x%04X]" % c.sp + log(v) c.sp += 1 return v @@ -256,6 +217,7 @@ def pop(c): def peek(c): """PEEK / [SP]""" v = "c.m[0x%04X]" % c.sp + log(v) return v @valcode(0x1A) @@ -264,6 +226,7 @@ def push(c): """PUSH / [--SP]""" c.sp -= 1 v = "c.m[0x%04X]" % c.sp + log(v) return v @valcode(0x1B) @@ -271,6 +234,7 @@ def push(c): def stack_pointer(c): """stack pointer""" v = "c.sp" + log(v) return v @valcode(0x1C) @@ -278,6 +242,7 @@ def stack_pointer(c): def program_counter(c): """program counter""" v = "c.pc" + log(v) return v @valcode(0x1D) @@ -285,6 +250,7 @@ def program_counter(c): def overflow(c): """overflow""" v = "c.o" + log(v) return v @valcode(0x1E) @@ -293,6 +259,7 @@ def next_word_value(c): """[next_word]""" v = "c.m[0x%04X]" % c.m[c.pc] c.pc += 1 + log(v) return v @valcode(0x1F) @@ -301,6 +268,7 @@ def next_word(c): """next_word (literal)""" v = "c.m[0x%04X]" % c.pc c.pc += 1 + log(v) return v @valcode(range(0x20, 0x40)) @@ -308,34 +276,13 @@ def next_word(c): def literal(c, code): """literal value 0x00-0x1F (literal)""" v = "0x%04X" % (code - 0x20) + log(v) return v -class Memory(object): - """array of 16-bit words""" - def __init__(m, debug=False): - m.clear() - - def clear(m): - """clear memory""" - # TODO: (addr, len) memory range to clear - m.w = [0 for _ in xrange(0, 2**w)] - - def __getitem__(m, addr): - """get word at addr""" - return m.w[addr] - - def __setitem__(m, addr, value): - """assignment truncates values to 16-bit words""" - # TODO: multi-word assignment starting at addr when value is list - m.w[addr] = value & wmask - class CPU(object): - """DCPU-16""" - def __init__(c, memory=Memory(), debug=False): - """If you don't specify memory, CPUs will share the same, default memory object""" - c.m = memory + def __init__(c, debug=False): c.clear() c.reset() c.debug = debug @@ -350,77 +297,41 @@ class CPU(object): def clear(c): """clear memory""" - c.m.clear() - - a = Register(0x0) - b = Register(0x1) - c = Register(0x2) - x = Register(0x3) - y = Register(0x4) - z = Register(0x5) - i = Register(0x6) - j = Register(0x7) - pc = Register() - sp = Register() - o = Register() - - def _op(c, word): - """dispatch word to op and args""" - opcode = word & 0xF - a_code = word >> 4 & 0x3F - b_code = word >> 10 & 0x3F - try: - op = _opcode_map[opcode] - try: - op = op[a_code] - except TypeError: - args = (c._pointer(a_code), - c._pointer(b_code)) - else: - args = (c._pointer(b_code),) - finally: - if c.debug: - log(' '.join([op.__name__] + [arg.codestr for arg in args])) - except KeyError: - raise Exception('Invalid opcode %s at PC=%04X' % (["%02X"%x for x in opcode], c.pc)) - return op, args + c.m = [0 for _ in xrange(0, 2**w)] def _pointer(c, code): - """get pointer to valcode""" + """get pointer to value code""" try: - return _valcode_map[code](c) + return valcode_map[code](c) except KeyError: - raise Exception("Invalid valcode") + raise Exception("Invalid value code") def __getitem__(c, code): - """get value of valcode""" + """get pointer to value""" return c._pointer(code)() def __setitem__(c, code, value): - """set value at valcode""" + """set value at pointer""" c._pointer(code).set(value) def step(c): """start handling [PC]""" word = c.m[c.pc] c.pc += 1 - op, args = c._op(word) + 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 = c._pointer(word >> 4 & 0x3F) + b = c._pointer(word >> 10 & 0x3F) if c.skip: c.skip = False - if c.debug: log("Skipped") else: - op(c, *args) - if c.debug: log(c.dump_r()) - - def run(c): - """step until PC is constant""" - last_pc = 0xFFFF - last_sp = 0xFFFF - while c.pc != last_pc or c.sp != last_sp: - last_pc = c.pc - last_sp = c.sp - c.step() - log("Infinite loop") + op(c, a, b) + if c.debug: + log(c.dump_r()) def dump_r(c): """human-readable register status""" @@ -433,9 +344,15 @@ class CPU(object): (c.r + [c.pc, c.sp, c.o])[i]) for i in range(11)) - def load_m(c, data=None, io=None): + 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] @@ -445,9 +362,3 @@ class CPU(object): pass -spec_demo = [ - 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, -] diff --git a/test.py b/test.py deleted file mode 100644 index 640601c..0000000 --- a/test.py +++ /dev/null @@ -1,327 +0,0 @@ -import unittest -import random -import dcpu_16 -from dcpu_16 import CPU - -class TestInstructions(unittest.TestCase): - """Instruction set""" - - def setUp(self): - pass - - def test_SET(self): - dcpu_16.SET.opcode = (0x1,) - c = CPU() - c.a = 0x0 - c.b = 0x42 - dcpu_16.SET(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.a, 0x42) - self.assertEqual(c.b, 0x42) - - def test_ADD(self): - dcpu_16.SET.opcode = (0x2,) - c = CPU() - c.a = 0x17 - c.b = 0x42 - dcpu_16.ADD(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.a, 0x17+0x42) - self.assertEqual(c.b, 0x42) - self.assertEqual(c.o, 0x0) - - def test_SUB(self): - dcpu_16.SET.opcode = (0x3,) - c = CPU() - c.a = 0x42 - c.b = 0x17 - dcpu_16.SUB(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.a, 0x42-0x17) - self.assertEqual(c.b, 0x17) - self.assertEqual(c.o, 0x0) - - def test_MUL(self): - dcpu_16.SET.opcode = (0x4,) - c = CPU() - c.a = 0x17 - c.b = 0x42 - dcpu_16.MUL(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.a, 0x17*0x42) - self.assertEqual(c.b, 0x42) - self.assertEqual(c.o, 0x0) - - def test_DIV(self): - dcpu_16.SET.opcode = (0x5,) - c = CPU() - c.a = 0x17 - c.b = 0x42 - dcpu_16.DIV(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.a, 0x17/0x42) - self.assertEqual(c.b, 0x42) - self.assertEqual(c.o, ((0x17<<16)/0x42)&0xFFFF) - - def test_MOD(self): - dcpu_16.SET.opcode = (0x6,) - c = CPU() - c.a = 0x17 - c.b = 0x42 - dcpu_16.MOD(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.a, 0x17%0x42) - self.assertEqual(c.b, 0x42) - self.assertEqual(c.o, 0x0) - - def test_SHL(self): - dcpu_16.SET.opcode = (0x7,) - c = CPU() - c.a = 0x17 - c.b = 0x4 - dcpu_16.SHL(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.a, 0x17<<0x4 & dcpu_16.wmask) - self.assertEqual(c.b, 0x4) - self.assertEqual(c.o, 0x0) - - def test_SHR(self): - dcpu_16.SET.opcode = (0x8,) - c = CPU() - c.a = 0x17 - c.b = 0x42 - dcpu_16.SHR(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.a, 0x17>>0x42) - self.assertEqual(c.b, 0x42) - self.assertEqual(c.o, 0x0) - - def test_AND(self): - dcpu_16.SET.opcode = (0x9,) - c = CPU() - c.a = 0x17 - c.b = 0x42 - dcpu_16.AND(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.a, 0x17&0x42) - self.assertEqual(c.b, 0x42) - self.assertEqual(c.o, 0x0) - - def test_BOR(self): - dcpu_16.SET.opcode = (0xA,) - c = CPU() - c.a = 0x17 - c.b = 0x42 - dcpu_16.BOR(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.a, 0x17|0x42) - self.assertEqual(c.b, 0x42) - self.assertEqual(c.o, 0x0) - - def test_XOR(self): - dcpu_16.SET.opcode = (0xB,) - c = CPU() - c.a = 0x17 - c.b = 0x42 - dcpu_16.XOR(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.a, 0x17^0x42) - self.assertEqual(c.b, 0x42) - self.assertEqual(c.o, 0x0) - - def test_IFE(self): - dcpu_16.SET.opcode = (0xC,) - c = CPU() - - c.a = 0x17 - c.b = 0x42 - dcpu_16.IFE(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.skip, True) - - c.a = 0x42 - c.b = 0x42 - dcpu_16.IFE(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.skip, False) - - def test_IFN(self): - dcpu_16.SET.opcode = (0xD,) - c = CPU() - - c.a = 0x17 - c.b = 0x42 - dcpu_16.IFN(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.skip, False) - - c.a = 0x42 - c.b = 0x42 - dcpu_16.IFN(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.skip, True) - - def test_IFG(self): - dcpu_16.SET.opcode = (0xE,) - c = CPU() - - c.a = 0x41 - c.b = 0x42 - dcpu_16.IFG(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.skip, True) - - c.a = 0x42 - c.b = 0x42 - dcpu_16.IFG(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.skip, True) - - c.a = 0x42 - c.b = 0x41 - dcpu_16.IFG(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.skip, False) - - def test_IFB(self): - dcpu_16.SET.opcode = (0xF,) - c = CPU() - - c.a = 0xF0F0 - c.b = 0x0F0F - dcpu_16.IFB(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.skip, True) - - c.a = 0xF1F0 - c.b = 0x0F0F - dcpu_16.IFB(c, c._pointer(0x0), c._pointer(0x1)) - self.assertEqual(c.skip, False) - - def test_JSR(self): - dcpu_16.JSR.opcode = (0x0, 0x1) - c = CPU() - - c.a = 0xDEAD - c.pc = 0xBEEF - dcpu_16.JSR(c, c._pointer(0x0)) - self.assertEqual(c.pc, 0xDEAD) - self.assertEqual(c.sp, 0xFFFF) - self.assertEqual(c.m[0xFFFF], 0xBEEF) - - -class TestCPU(unittest.TestCase): - """CPU behavior""" - - def setUp(self): - pass - - def test_initial_state(self): - """Initial state shall be all zero""" - c = CPU() - for r in c.r: - self.assertEqual(r, 0) - self.assertEqual(c.pc, 0) - self.assertEqual(c.o, 0) - self.assertEqual(c.sp, 0) - self.assertEqual(c.skip, False) - self.assertEqual(c.pc, 0) - for r in c.m: - self.assertEqual(r, 0) - - def test_reset(self): - """Reset shall bring CPU register state to initial""" - c = CPU() - for i in xrange(0x8): - c.r[i] = random.randrange(0x10000) - c.reset() - for r in c.r: - self.assertEqual(r, 0) - self.assertEqual(c.pc, 0) - self.assertEqual(c.o, 0) - self.assertEqual(c.sp, 0) - self.assertEqual(c.skip, False) - self.assertEqual(c.pc, 0) - - def test_clear(self): - """Clear shall zero memory""" - c = CPU() - for i in xrange(0x10000): - c.m[i] = random.randrange(0x10000) - c.clear() - for r in c.m: - self.assertEqual(r, 0) - -class TestCPUWithPrograms(unittest.TestCase): - def setUP(self): - pass - - def test_spec_demo(self): - c = CPU() - 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, - ] - c.load_m(data=data) - - self.assertEqual(c.pc, 0) - c.step() # SET A, 0x30 - self.assertEqual(c.a, 0x30) - - self.assertEqual(c.pc, 2) - c.step() # SET [0x1000], 0x20 - self.assertEqual(c.m[0x1000], 0x20) - - self.assertEqual(c.pc, 5) - c.step() # SUB A, [0x1000] - self.assertEqual(c.a, 0x10) - - self.assertEqual(c.pc, 7) - c.step() # IFN A, 0x10 - self.assertEqual(c.pc, 8) - self.assertEqual(c.skip, True) - - c.step() # skip SET PC, crash - self.assertEqual(c.skip, False) - - self.assertEqual(c.pc, 10) - c.step() # SET I, 10 - self.assertEqual(c.i, 0x0A) - - self.assertEqual(c.pc, 11) - c.step() # SET A, 0x2000 - self.assertEqual(c.a, 0x2000) - - for i in range(10, 0, -1): - self.assertEqual(c.pc, 13) - c.step() # SET [0x2000+I], [A] - self.assertEqual(c.m[0x2000+i], 0x0) - - self.assertEqual(c.pc, 15) - c.step() # SUB I, 1 - self.assertEqual(c.i, i-1) - - self.assertEqual(c.pc, 16) - c.step() # IFN I, 0 - self.assertEqual(c.skip, i-1==0) - - self.assertEqual(c.pc, 17) - c.step() # SET PC, loop (with skip if c.i==0) - - self.assertEqual(c.pc, 19) - c.step() # SET X, 0x4 - self.assertEqual(c.x, 0x4) - - self.assertEqual(c.pc, 20) - c.step() # JSR testsub - self.assertEqual(c.sp, 0xFFFF) - self.assertEqual(c.m[0xFFFF], 22) - - self.assertEqual(c.pc, 24) - c.step() # SHL X, 4 - self.assertEqual(c.x, 0x40) - - self.assertEqual(c.pc, 25) - c.step() # SET PC, POP - - self.assertEqual(c.pc, 22) - c.step() # SET PC, crash - - self.assertEqual(c.pc, 26) - c.step() # SET PC, crash - - self.assertEqual(c.pc, 26) - # endless loop - - -if __name__ == '__main__': - cases = [ - 'test.TestInstructions', - 'test.TestCPU', - 'test.TestCPUWithPrograms' - ] - suite = unittest.TestLoader().loadTestsFromNames(cases) - unittest.TextTestRunner(verbosity=2).run(suite)