Compare commits

..

No commits in common. "master" and "v0.7" have entirely different histories.
master ... v0.7

4 changed files with 87 additions and 518 deletions

1
.gitignore vendored
View file

@ -1 +0,0 @@
*.pyc

View file

@ -1,20 +1,20 @@
# What is this? # 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: An example of a Python REPL session:
>>> from dcpu_16 import CPU, spec_demo >>> from dcpu_16 import CPU
>>> c = CPU(debug=True) >>> c = CPU(debug=True)
>>> c.load_m(spec_demo) # loads demo program >>> c.load_m() # loads demo program
>>> c.step() # step by one instruction >>> c.step() # step by one instruction
<< SET << SET
<< c.r[0x0] << c.r[0x0]
@ -31,21 +31,7 @@ An example of a Python REPL session:
>>> c.dump_r() # get CPU register state as string >>> 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' '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/

View file

@ -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 from __future__ import print_function, division
# DCPU-16 spec version
spec = '1.1'
# log tool
def log(string): def log(string):
print(" << %s" % string) print(" << %s" % string)
# width constants #http://0x10c.com/doc/dcpu-16.txt
spec = '1.1'
w = 16 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): class opcode(object):
"""Opcode decorator"""
def __init__(self, *opcode): def __init__(self, *opcode):
self.opcode = opcode self.opcode = opcode
def __call__(self, f): def __call__(self, f):
_opcode_f = f _opcode_f = f
_opcode_f.opcode = self.opcode _opcode_f.opcode = self.opcode
if len(self.opcode) == 1: opcode_map[self.opcode] = _opcode_f
_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
return _opcode_f return _opcode_f
_valcode_map = {}
class valcode(object): class valcode(object):
"""Valcode decorator"""
def __init__(self, valcode): def __init__(self, valcode):
self.valcode = valcode self.valcode = valcode
@ -52,60 +36,18 @@ class valcode(object):
try: try:
for code in self.valcode: for code in self.valcode:
# currify with (bound) code # currify with (bound) code
_valcode_map[code] = lambda c, code=code: _valcode_f(c, 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].__name__ = '%s(code=0x%02X)' % (_valcode_f.__name__, code)
except TypeError: except TypeError:
_valcode_map[self.valcode] = _valcode_f valcode_map[self.valcode] = _valcode_f
return _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) @opcode(0x0, 0x01)
def JSR(c, a): def JSR(c, a):
"""pushes the address of the next instruction to the stack, then sets PC to a""" """pushes the address of the next instruction to the stack, then sets PC to a"""
c.sp -= 1 #pushnext
c.m[c.sp] = c.pc c.pc = c[a]
c.pc = a()
@opcode(0x1) @opcode(0x1)
def SET(c, a, b): def SET(c, a, b):
@ -141,8 +83,8 @@ def MUL(c, a, b):
@opcode(0x5) @opcode(0x5)
def DIV(c, a, b): def DIV(c, a, b):
"""sets a to a/b, sets O to ((a<<16)/b)&0xFFFF""" """sets a to a/b, sets O to ((a<<16)/b)&0xFFFF"""
res = a() // b() res = a() / b()
c.o = ((a() << w) // b()) & wmask c.o = ((a() << w) / b()) & wmask
a.set(res) a.set(res)
@opcode(0x6) @opcode(0x6)
@ -192,40 +134,56 @@ def XOR(c, a, b):
def IFE(c, a, b): def IFE(c, a, b):
"""performs next instruction only if a==b""" """performs next instruction only if a==b"""
if a() == b(): if a() == b():
c.skip = False
else:
c.skip = True c.skip = True
@opcode(0xD) @opcode(0xD)
def IFN(c, a, b): def IFN(c, a, b):
"""performs next instruction only if a!=b""" """performs next instruction only if a!=b"""
if a() != b(): if a() != b():
c.skip = False
else:
c.skip = True c.skip = True
@opcode(0xE) @opcode(0xE)
def IFG(c, a, b): def IFG(c, a, b):
"""performs next instruction only if a>b""" """performs next instruction only if a>b"""
if a() > b(): if a() > b():
c.skip = False
else:
c.skip = True c.skip = True
@opcode(0xF) @opcode(0xF)
def IFB(c, a, b): def IFB(c, a, b):
"""performs next instruction only if (a&b)!=0""" """performs next instruction only if (a&b)!=0"""
if (a() & b()) != 0: if (a() & b()) != 0:
c.skip = False
else:
c.skip = True 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)) @valcode(range(0x00, 0x08))
@pointerize @pointerize
def register(c, code): def register(c, code):
"""register""" """register"""
v = "c.r[0x%01X]" % code v = "c.r[0x%01X]" % code
log(v)
return v return v
@valcode(range(0x08, 0x10)) @valcode(range(0x08, 0x10))
@ -233,13 +191,15 @@ def register(c, code):
def register_value(c, code): def register_value(c, code):
"""[register]""" """[register]"""
v = "c.m[c.r[0x%01X]]" % (code-0x07) v = "c.m[c.r[0x%01X]]" % (code-0x07)
log(v)
return v return v
@valcode(range(0x10, 0x18)) @valcode(range(0x10, 0x18))
@pointerize @pointerize
def next_word_plus_register_value(c, code): def next_word_plus_register_value(c, code):
"""[next word + register]""" """[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 c.pc += 1
return v return v
@ -248,6 +208,7 @@ def next_word_plus_register_value(c, code):
def pop(c): def pop(c):
"""POP / [SP++]""" """POP / [SP++]"""
v = "c.m[0x%04X]" % c.sp v = "c.m[0x%04X]" % c.sp
log(v)
c.sp += 1 c.sp += 1
return v return v
@ -256,6 +217,7 @@ def pop(c):
def peek(c): def peek(c):
"""PEEK / [SP]""" """PEEK / [SP]"""
v = "c.m[0x%04X]" % c.sp v = "c.m[0x%04X]" % c.sp
log(v)
return v return v
@valcode(0x1A) @valcode(0x1A)
@ -264,6 +226,7 @@ def push(c):
"""PUSH / [--SP]""" """PUSH / [--SP]"""
c.sp -= 1 c.sp -= 1
v = "c.m[0x%04X]" % c.sp v = "c.m[0x%04X]" % c.sp
log(v)
return v return v
@valcode(0x1B) @valcode(0x1B)
@ -271,6 +234,7 @@ def push(c):
def stack_pointer(c): def stack_pointer(c):
"""stack pointer""" """stack pointer"""
v = "c.sp" v = "c.sp"
log(v)
return v return v
@valcode(0x1C) @valcode(0x1C)
@ -278,6 +242,7 @@ def stack_pointer(c):
def program_counter(c): def program_counter(c):
"""program counter""" """program counter"""
v = "c.pc" v = "c.pc"
log(v)
return v return v
@valcode(0x1D) @valcode(0x1D)
@ -285,6 +250,7 @@ def program_counter(c):
def overflow(c): def overflow(c):
"""overflow""" """overflow"""
v = "c.o" v = "c.o"
log(v)
return v return v
@valcode(0x1E) @valcode(0x1E)
@ -293,6 +259,7 @@ def next_word_value(c):
"""[next_word]""" """[next_word]"""
v = "c.m[0x%04X]" % c.m[c.pc] v = "c.m[0x%04X]" % c.m[c.pc]
c.pc += 1 c.pc += 1
log(v)
return v return v
@valcode(0x1F) @valcode(0x1F)
@ -301,6 +268,7 @@ def next_word(c):
"""next_word (literal)""" """next_word (literal)"""
v = "c.m[0x%04X]" % c.pc v = "c.m[0x%04X]" % c.pc
c.pc += 1 c.pc += 1
log(v)
return v return v
@valcode(range(0x20, 0x40)) @valcode(range(0x20, 0x40))
@ -308,34 +276,13 @@ def next_word(c):
def literal(c, code): def literal(c, code):
"""literal value 0x00-0x1F (literal)""" """literal value 0x00-0x1F (literal)"""
v = "0x%04X" % (code - 0x20) v = "0x%04X" % (code - 0x20)
log(v)
return 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): class CPU(object):
"""DCPU-16""" def __init__(c, debug=False):
def __init__(c, memory=Memory(), debug=False):
"""If you don't specify memory, CPUs will share the same, default memory object"""
c.m = memory
c.clear() c.clear()
c.reset() c.reset()
c.debug = debug c.debug = debug
@ -350,77 +297,41 @@ class CPU(object):
def clear(c): def clear(c):
"""clear memory""" """clear memory"""
c.m.clear() c.m = [0 for _ in xrange(0, 2**w)]
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
def _pointer(c, code): def _pointer(c, code):
"""get pointer to valcode""" """get pointer to value code"""
try: try:
return _valcode_map[code](c) return valcode_map[code](c)
except KeyError: except KeyError:
raise Exception("Invalid valcode") raise Exception("Invalid value code")
def __getitem__(c, code): def __getitem__(c, code):
"""get value of valcode""" """get pointer to value"""
return c._pointer(code)() return c._pointer(code)()
def __setitem__(c, code, value): def __setitem__(c, code, value):
"""set value at valcode""" """set value at pointer"""
c._pointer(code).set(value) c._pointer(code).set(value)
def step(c): def step(c):
"""start handling [PC]""" """start handling [PC]"""
word = c.m[c.pc] word = c.m[c.pc]
c.pc += 1 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: if c.skip:
c.skip = False c.skip = False
if c.debug: log("Skipped")
else: else:
op(c, *args) op(c, a, b)
if c.debug: log(c.dump_r()) 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")
def dump_r(c): def dump_r(c):
"""human-readable register status""" """human-readable register status"""
@ -433,9 +344,15 @@ class CPU(object):
(c.r + [c.pc, c.sp, c.o])[i]) (c.r + [c.pc, c.sp, c.o])[i])
for i in range(11)) for i in range(11))
def load_m(c, data=None, io=None): def load_m(c, io=None):
"""load data in memory""" """load data in memory"""
# TODO: load from io object # 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)): for i in xrange(len(data)):
c.m[i] = data[i] c.m[i] = data[i]
@ -445,9 +362,3 @@ class CPU(object):
pass 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,
]

327
test.py
View file

@ -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)