mirror of
https://github.com/lloeki/python-dcpu_16.git
synced 2025-12-06 18:04:38 +01:00
Compare commits
No commits in common. "master" and "v0.7" have entirely different histories.
4 changed files with 87 additions and 518 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +0,0 @@
|
||||||
*.pyc
|
|
||||||
32
README.mdown
32
README.mdown
|
|
@ -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/
|
|
||||||
|
|
|
||||||
245
dcpu_16.py
245
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
|
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
327
test.py
|
|
@ -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)
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue