From 5fa919e35b8eb612fcbe5a4aa71a6d07e6135489 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Sun, 8 Apr 2012 19:57:02 +0200 Subject: [PATCH 01/22] uniform naming --- dcpu_16.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dcpu_16.py b/dcpu_16.py index 3b25456..347f4ab 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -367,18 +367,18 @@ class CPU(object): def _pointer(c, code): - """get pointer to value code""" + """get pointer to valcode""" try: return valcode_map[code](c) except KeyError: - raise Exception("Invalid value code") + raise Exception("Invalid valcode") def __getitem__(c, code): - """get pointer to value""" + """get value of valcode""" return c._pointer(code)() def __setitem__(c, code, value): - """set value at pointer""" + """set value at valcode""" c._pointer(code).set(value) def step(c): From 32a827b728f0bd5f5bd8f5fc89d413a984f4b957 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Sun, 8 Apr 2012 19:57:50 +0200 Subject: [PATCH 02/22] cosmetic --- dcpu_16.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dcpu_16.py b/dcpu_16.py index 347f4ab..8588ae4 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -7,7 +7,7 @@ def log(string): spec = '1.1' w = 16 -wmask = 2**w-1 +wmask = 2**w - 1 literals = [l for l in xrange(0, 0x20)] From a2c029f9cb81e650f5332ae68f682cd0c21a21b1 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Sun, 8 Apr 2012 19:58:24 +0200 Subject: [PATCH 03/22] spec demo program as a test --- dcpu_16.py | 8 +---- test.py | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/dcpu_16.py b/dcpu_16.py index 8588ae4..98acddd 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -418,15 +418,9 @@ class CPU(object): (c.r + [c.pc, c.sp, c.o])[i]) for i in range(11)) - def load_m(c, io=None): + def load_m(c, io=None, data=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] diff --git a/test.py b/test.py index 5232f5e..fe24b95 100644 --- a/test.py +++ b/test.py @@ -232,7 +232,95 @@ class TestCPU(unittest.TestCase): 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__': - suite = unittest.TestLoader().loadTestsFromNames(['test.TestInstructions', 'test.TestCPU']) + cases = [ + 'test.TestInstructions', + 'test.TestCPU', + 'test.TestCPUWithPrograms' + ] + suite = unittest.TestLoader().loadTestsFromNames(cases) unittest.TextTestRunner(verbosity=2).run(suite) From 726698b4ea95128958bc1360475c482e59565342 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Sun, 8 Apr 2012 19:58:35 +0200 Subject: [PATCH 04/22] cosmetic --- test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test.py b/test.py index fe24b95..640601c 100644 --- a/test.py +++ b/test.py @@ -190,6 +190,7 @@ class TestInstructions(unittest.TestCase): self.assertEqual(c.sp, 0xFFFF) self.assertEqual(c.m[0xFFFF], 0xBEEF) + class TestCPU(unittest.TestCase): """CPU behavior""" From ec4711d2af188a8d912efa1a2104f80d6f240fd0 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Sun, 8 Apr 2012 19:58:53 +0200 Subject: [PATCH 05/22] ignore pyc --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e99e36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc \ No newline at end of file From 54e56b82db44149ec182dfc5648fb96b2b57bf9c Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Sun, 8 Apr 2012 20:01:41 +0200 Subject: [PATCH 06/22] updated doc and brought back spec demo --- README.mdown | 4 ++-- dcpu_16.py | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/README.mdown b/README.mdown index 0076430..75bf769 100644 --- a/README.mdown +++ b/README.mdown @@ -12,9 +12,9 @@ It's meant to be used interactively via the Python REPL as well as programmatica An example of a Python REPL session: - >>> from dcpu_16 import CPU + >>> from dcpu_16 import CPU, spec_demo >>> c = CPU(debug=True) - >>> c.load_m() # loads demo program + >>> c.load_m(spec_demo) # loads demo program >>> c.step() # step by one instruction << SET << c.r[0x0] diff --git a/dcpu_16.py b/dcpu_16.py index 98acddd..89a2189 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -364,7 +364,8 @@ class CPU(object): def j(c, val): c.r[7] = val - + def _op(c, word): + """dispatch to op""" def _pointer(c, code): """get pointer to valcode""" @@ -430,3 +431,9 @@ 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, +] From c3e7012f2d4cf752c54ad01d649d281bb4634ad1 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Sun, 8 Apr 2012 20:16:57 +0200 Subject: [PATCH 07/22] Updated doc --- README.mdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.mdown b/README.mdown index 75bf769..75ceda5 100644 --- a/README.mdown +++ b/README.mdown @@ -2,13 +2,13 @@ A DCPU-16 implementation in Python. See [the spec][0]. -# But what's the goal of this? There's another one already! +# What's the goal of this? -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, but you should [read][1] [this][2]). 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). +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. # How do I use this? -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. +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. An example of a Python REPL session: @@ -33,7 +33,7 @@ An example of a Python REPL session: # What is the status of this? -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. +As of v1.0 the CPU implementation ought to be complete according to DCPU-16 spec v1.1. # Features From 6fcac3cf2e21f8b38cd9beb0c517887e2b996c37 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Sun, 8 Apr 2012 20:19:55 +0200 Subject: [PATCH 08/22] title changes --- README.mdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.mdown b/README.mdown index 75ceda5..9c59a9d 100644 --- a/README.mdown +++ b/README.mdown @@ -2,11 +2,11 @@ A DCPU-16 implementation in Python. See [the spec][0]. -# What's the goal of this? +# Goal 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. -# How do I use this? +# Usage 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. @@ -31,7 +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' -# What is the status of this? +# Status As of v1.0 the CPU implementation ought to be complete according to DCPU-16 spec v1.1. From f3acea29fdc9e780b83d06db184caed88bb2e08b Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Mon, 9 Apr 2012 11:27:06 +0200 Subject: [PATCH 09/22] fix param order to match doc --- dcpu_16.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dcpu_16.py b/dcpu_16.py index 89a2189..5f45b26 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -419,7 +419,7 @@ class CPU(object): (c.r + [c.pc, c.sp, c.o])[i]) for i in range(11)) - def load_m(c, io=None, data=None): + def load_m(c, data=None, io=None): """load data in memory""" # TODO: load from io object for i in xrange(len(data)): From 50ab21f3e54d8f8fd1e23ea5785217e3d67ef01c Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Mon, 9 Apr 2012 11:30:21 +0200 Subject: [PATCH 10/22] skip feedback --- dcpu_16.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dcpu_16.py b/dcpu_16.py index 5f45b26..7fbfb18 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -404,6 +404,7 @@ class CPU(object): raise Exception('Invalid opcode %s at PC=%04X' % (["%02X"%x for x in opcode], c.pc)) if c.skip: c.skip = False + if c.debug: log("Skipped") else: op(c, *args) if c.debug: log(c.dump_r()) From 2fff97dd6407f0d88cd96d24041647fbe9f63b2f Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Mon, 9 Apr 2012 11:47:14 +0200 Subject: [PATCH 11/22] run(), and HLT on infinite loop --- dcpu_16.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dcpu_16.py b/dcpu_16.py index 7fbfb18..98722ee 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -409,6 +409,13 @@ class CPU(object): op(c, *args) if c.debug: log(c.dump_r()) + def run(c): + last_pc = 0xFFFF + while c.pc != last_pc: + last_pc = c.pc + c.step() + log("Infinite loop") + def dump_r(c): """human-readable register status""" return " ".join( "%s=%04X" % From 037559160febc589c45b499ca5afa9e6a7648b61 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Mon, 9 Apr 2012 11:48:36 +0200 Subject: [PATCH 12/22] a little doc --- dcpu_16.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dcpu_16.py b/dcpu_16.py index 98722ee..42ffcbd 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -410,6 +410,7 @@ class CPU(object): if c.debug: log(c.dump_r()) def run(c): + """step until PC is constant""" last_pc = 0xFFFF while c.pc != last_pc: last_pc = c.pc From 605790257ff11740fa7ffc8cddda05ebcba9c8b0 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Mon, 9 Apr 2012 15:13:15 +0200 Subject: [PATCH 13/22] refactored opcode dispatching --- dcpu_16.py | 52 ++++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/dcpu_16.py b/dcpu_16.py index 42ffcbd..4798838 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -11,8 +11,8 @@ wmask = 2**w - 1 literals = [l for l in xrange(0, 0x20)] -opcode_map = {} -valcode_map = {} +_opcode_map = {} +_valcode_map = {} class opcode(object): @@ -22,7 +22,12 @@ class opcode(object): def __call__(self, f): _opcode_f = f _opcode_f.opcode = self.opcode - opcode_map[self.opcode] = _opcode_f + 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 return _opcode_f @@ -36,10 +41,10 @@ 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 @@ -365,12 +370,26 @@ class CPU(object): c.r[7] = val def _op(c, word): - """dispatch to op""" + """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] + args = (c._pointer(b_code),) + except TypeError: + args = (c._pointer(a_code), + c._pointer(b_code)) + 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): """get pointer to valcode""" try: - return valcode_map[code](c) + return _valcode_map[code](c) except KeyError: raise Exception("Invalid valcode") @@ -386,22 +405,7 @@ class CPU(object): """start handling [PC]""" word = c.m[c.pc] c.pc = (c.pc + 1) & wmask - if (word & 0xF) > 0: - opcode = (word & 0xF,) - a = c._pointer(word >> 4 & 0x3F) - b = c._pointer(word >> 10 & 0x3F) - args = (a, b) - elif (word >> 4 & 0x3F) > 0: - opcode = (0x0, word >> 4 & 0x3F) - a = c._pointer(word >> 10 & 0x3F) - args = (a, ) - else: - raise Exception('Invalid opcode %s at PC=%04X' % (["%02X"%x for x in opcode], c.pc)) - try: - op = opcode_map[opcode] - if c.debug: log(op.__name__) - except KeyError: - raise Exception('Invalid opcode %s at PC=%04X' % (["%02X"%x for x in opcode], c.pc)) + op, args = c._op(word) if c.skip: c.skip = False if c.debug: log("Skipped") From 6f493aab4e0a7b5fb2a3750868fe0670b541ffac Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Mon, 9 Apr 2012 15:13:56 +0200 Subject: [PATCH 14/22] cosmetic --- dcpu_16.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dcpu_16.py b/dcpu_16.py index 4798838..92f6102 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -371,7 +371,7 @@ class CPU(object): def _op(c, word): """dispatch word to op and args""" - opcode = word & 0xF + opcode = word & 0xF a_code = word >> 4 & 0x3F b_code = word >> 10 & 0x3F try: From 39ac76aa4fe5299fb898a8c1266f9a7a03200049 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Mon, 9 Apr 2012 15:32:14 +0200 Subject: [PATCH 15/22] exception isolation --- dcpu_16.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dcpu_16.py b/dcpu_16.py index 92f6102..48402d7 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -378,10 +378,11 @@ class CPU(object): op = _opcode_map[opcode] try: op = op[a_code] - args = (c._pointer(b_code),) except TypeError: args = (c._pointer(a_code), c._pointer(b_code)) + else: + args = (c._pointer(b_code),) except KeyError: raise Exception('Invalid opcode %s at PC=%04X' % (["%02X"%x for x in opcode], c.pc)) return op, args From 4d2d9147125480d12e06fd21df484a55247bc1a3 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Mon, 9 Apr 2012 15:32:51 +0200 Subject: [PATCH 16/22] log outside of valcodes --- dcpu_16.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/dcpu_16.py b/dcpu_16.py index 48402d7..95855c9 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -197,7 +197,6 @@ def pointerize(f): def register(c, code): """register""" v = "c.r[0x%01X]" % code - if c.debug: log(v) return v @valcode(range(0x08, 0x10)) @@ -205,7 +204,6 @@ def register(c, code): def register_value(c, code): """[register]""" v = "c.m[c.r[0x%01X]]" % (code-0x07) - if c.debug: log(v) return v @valcode(range(0x10, 0x18)) @@ -213,7 +211,6 @@ def register_value(c, code): 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) - if c.debug: log(v) c.pc = (c.pc + 1) & wmask return v @@ -222,7 +219,6 @@ def next_word_plus_register_value(c, code): def pop(c): """POP / [SP++]""" v = "c.m[0x%04X]" % c.sp - if c.debug: log(v) c.sp = (c.sp + 1) & wmask return v @@ -231,7 +227,6 @@ def pop(c): def peek(c): """PEEK / [SP]""" v = "c.m[0x%04X]" % c.sp - if c.debug: log(v) return v @valcode(0x1A) @@ -240,7 +235,6 @@ def push(c): """PUSH / [--SP]""" c.sp = (c.sp - 1) & wmask v = "c.m[0x%04X]" % c.sp - if c.debug: log(v) return v @valcode(0x1B) @@ -248,7 +242,6 @@ def push(c): def stack_pointer(c): """stack pointer""" v = "c.sp" - if c.debug: log(v) return v @valcode(0x1C) @@ -256,7 +249,6 @@ def stack_pointer(c): def program_counter(c): """program counter""" v = "c.pc" - if c.debug: log(v) return v @valcode(0x1D) @@ -264,7 +256,6 @@ def program_counter(c): def overflow(c): """overflow""" v = "c.o" - if c.debug: log(v) return v @valcode(0x1E) @@ -273,7 +264,6 @@ def next_word_value(c): """[next_word]""" v = "c.m[0x%04X]" % c.m[c.pc] c.pc = (c.pc + 1) & wmask - if c.debug: log(v) return v @valcode(0x1F) @@ -282,7 +272,6 @@ def next_word(c): """next_word (literal)""" v = "c.m[0x%04X]" % c.pc c.pc = (c.pc + 1) & wmask - if c.debug: log(v) return v @valcode(range(0x20, 0x40)) @@ -290,7 +279,6 @@ def next_word(c): def literal(c, code): """literal value 0x00-0x1F (literal)""" v = "0x%04X" % (code - 0x20) - if c.debug: log(v) return v @@ -383,6 +371,9 @@ class CPU(object): 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 From 9a11985cb6a6fcb56ea63e7d7e8c78dc9f8ab410 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Mon, 9 Apr 2012 15:40:40 +0200 Subject: [PATCH 17/22] simplified register declaration --- dcpu_16.py | 72 +++++++++++++----------------------------------------- 1 file changed, 17 insertions(+), 55 deletions(-) diff --git a/dcpu_16.py b/dcpu_16.py index 95855c9..83a2d41 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -48,6 +48,15 @@ class valcode(object): return _valcode_f +class Register(object): + def __init__(self, regcode): + self.regcode = regcode + def __get__(self, c, type=None): + return c.r[self.regcode] + def __set__(self, c, value): + c.r[self.regcode] = value & wmask + + @opcode(0x0, 0x01) def JSR(c, a): """pushes the address of the next instruction to the stack, then sets PC to a""" @@ -301,61 +310,14 @@ class CPU(object): """clear memory""" c.m = [0 for _ in xrange(0, 2**w)] - @property - def a(c): - return c.r[0] - @a.setter - def a(c, val): - c.r[0] = val - - @property - def b(c): - return c.r[1] - @b.setter - def b(c, val): - c.r[1] = val - - @property - def c(c): - return c.r[2] - @c.setter - def c(c, val): - c.r[2] = val - - @property - def x(c): - return c.r[3] - @x.setter - def x(c, val): - c.r[3] = val - - @property - def y(c): - return c.r[4] - @y.setter - def y(c, val): - c.r[4] = val - - @property - def z(c): - return c.r[5] - @z.setter - def z(c, val): - c.r[5] = val - - @property - def i(c): - return c.r[6] - @i.setter - def i(c, val): - c.r[6] = val - - @property - def j(c): - return c.r[7] - @j.setter - def j(c, val): - c.r[7] = val + a = Register(0x0) + b = Register(0x1) + c = Register(0x2) + x = Register(0x3) + y = Register(0x4) + z = Register(0x5) + i = Register(0x6) + j = Register(0x7) def _op(c, word): """dispatch word to op and args""" From 7d6205cac9f346a59ec5df796885413ccbd21775 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Mon, 9 Apr 2012 15:58:44 +0200 Subject: [PATCH 18/22] all registers wrapped as 16 bits --- dcpu_16.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/dcpu_16.py b/dcpu_16.py index 83a2d41..54e0a21 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -49,18 +49,24 @@ class valcode(object): class Register(object): - def __init__(self, regcode): + def __init__(self, regcode=None): self.regcode = regcode def __get__(self, c, type=None): - return c.r[self.regcode] + if self.regcode is not None: + return c.r[self.regcode] + else: + return self.value def __set__(self, c, value): - c.r[self.regcode] = value & wmask + if self.regcode is not None: + c.r[self.regcode] = value & wmask + else: + self.value = value & wmask @opcode(0x0, 0x01) def JSR(c, a): """pushes the address of the next instruction to the stack, then sets PC to a""" - c.sp = (c.sp - 1) & wmask + c.sp -= 1 c.m[c.sp] = c.pc c.pc = a() @@ -220,7 +226,7 @@ def register_value(c, code): 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) - c.pc = (c.pc + 1) & wmask + c.pc += 1 return v @valcode(0x18) @@ -228,7 +234,7 @@ def next_word_plus_register_value(c, code): def pop(c): """POP / [SP++]""" v = "c.m[0x%04X]" % c.sp - c.sp = (c.sp + 1) & wmask + c.sp += 1 return v @valcode(0x19) @@ -242,7 +248,7 @@ def peek(c): @pointerize def push(c): """PUSH / [--SP]""" - c.sp = (c.sp - 1) & wmask + c.sp -= 1 v = "c.m[0x%04X]" % c.sp return v @@ -272,7 +278,7 @@ def overflow(c): def next_word_value(c): """[next_word]""" v = "c.m[0x%04X]" % c.m[c.pc] - c.pc = (c.pc + 1) & wmask + c.pc += 1 return v @valcode(0x1F) @@ -280,7 +286,7 @@ def next_word_value(c): def next_word(c): """next_word (literal)""" v = "c.m[0x%04X]" % c.pc - c.pc = (c.pc + 1) & wmask + c.pc += 1 return v @valcode(range(0x20, 0x40)) @@ -318,6 +324,9 @@ class CPU(object): 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""" @@ -358,7 +367,7 @@ class CPU(object): def step(c): """start handling [PC]""" word = c.m[c.pc] - c.pc = (c.pc + 1) & wmask + c.pc += 1 op, args = c._op(word) if c.skip: c.skip = False From bf055e998e92d8f9e9dd31d15f5c94d6050b6958 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Mon, 9 Apr 2012 16:16:22 +0200 Subject: [PATCH 19/22] Isolate memory from CPU --- dcpu_16.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/dcpu_16.py b/dcpu_16.py index 54e0a21..05d09a9 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -297,9 +297,29 @@ def literal(c, code): 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): - def __init__(c, debug=False): + def __init__(c, memory=Memory(), debug=False): + c.m = memory c.clear() c.reset() c.debug = debug @@ -314,7 +334,7 @@ class CPU(object): def clear(c): """clear memory""" - c.m = [0 for _ in xrange(0, 2**w)] + c.m.clear() a = Register(0x0) b = Register(0x1) From d95072ceb47ccd703f11b91619e9337e6ffca7ca Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Mon, 9 Apr 2012 16:17:30 +0200 Subject: [PATCH 20/22] doc + cosmetic --- dcpu_16.py | 75 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/dcpu_16.py b/dcpu_16.py index 05d09a9..5d2a9e3 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -1,21 +1,30 @@ +"""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) -#http://0x10c.com/doc/dcpu-16.txt -spec = '1.1' - +# width constants w = 16 wmask = 2**w - 1 -literals = [l for l in xrange(0, 0x20)] _opcode_map = {} -_valcode_map = {} - - class opcode(object): + """Opcode decorator""" def __init__(self, *opcode): self.opcode = opcode @@ -31,7 +40,9 @@ class opcode(object): return _opcode_f +_valcode_map = {} class valcode(object): + """Valcode decorator""" def __init__(self, valcode): self.valcode = valcode @@ -49,19 +60,45 @@ class valcode(object): 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): @@ -184,29 +221,6 @@ def IFB(c, a, b): 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): @@ -318,6 +332,7 @@ class Memory(object): class CPU(object): + """DCPU-16""" def __init__(c, memory=Memory(), debug=False): c.m = memory c.clear() From d638ebe7ead4649618c530c3bac6dfa9de465f24 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Sat, 14 Apr 2012 15:39:42 +0200 Subject: [PATCH 21/22] docstring --- dcpu_16.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dcpu_16.py b/dcpu_16.py index 5d2a9e3..131d6f3 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -334,6 +334,7 @@ class Memory(object): 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 c.clear() c.reset() From a714a9d71a6a472908df8eb6f0434f53b378195a Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Sat, 14 Apr 2012 15:41:20 +0200 Subject: [PATCH 22/22] guard against SET PC, [SP++], and SET PC, [--SP] --- dcpu_16.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dcpu_16.py b/dcpu_16.py index 131d6f3..b0e60c1 100644 --- a/dcpu_16.py +++ b/dcpu_16.py @@ -415,8 +415,10 @@ class CPU(object): def run(c): """step until PC is constant""" last_pc = 0xFFFF - while c.pc != last_pc: + 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")