77b03ecdc660f846ad528d5b624fb3908c0114ab — Charles Daniels 8 months ago 2e87d45
check in pickle8
M README.md => README.md +9 -0
@@ 76,3 76,12 @@ frequency to be useful.
 
 Consequentially, the project has been abandoned, as it would not seem to be a
 fruitful line of research.
+
+### pickle8
+
+[go to project](./pickle8)
+
+My attempt as a freshmen to write a
+[CHIP-8](https://en.wikipedia.org/wiki/CHIP-8) emulator in Python. It was never
+finished, as I got bored with the project. The name "pickle8" was a reference
+to an inside joke.

A pickle8/ByteUtil.py => pickle8/ByteUtil.py +16 -0
@@ 0,0 1,16 @@
+
+def fromInt(val, length):
+  # creates length-bit-long binary string which evaluates to val
+  val = int(val)
+  bits = bin(val)[2:]
+  while len(bits) < length:
+    bits = '0' + bits
+
+  assert int(bits, 2) == val
+  assert len(bits) == length
+  return bits
+
+
+def fromHex(val, length):
+  val = int(val, 16)
+  return fromInt(val, length)

A pickle8/CPU.py => pickle8/CPU.py +147 -0
@@ 0,0 1,147 @@
+import logging
+import Memory
+import ByteUtil
+
+
+class Processor:
+  __initial_PC = 513
+
+  def __init__(this):
+    this.__registers = {}
+    for c in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']:
+      this.__registers[c] = Memory.Byte()
+    this.__registers['I'] = 0
+    this.__registers['delay'] = Memory.Byte()
+    this.__registers['sound'] = Memory.Byte()
+    this.__registers['PC'] = this.__initial_PC
+    this.__registers['SP'] = Memory.Byte()
+    this.__registers['VF'] = Memory.Byte()
+
+    this.memory = Memory.MainMemory()
+
+  def getreg(this, reg):
+    reg = this.__registers[reg]
+    if type(reg) is Memory.Byte:
+      return reg.get(int)
+    else:
+      return reg
+
+  def setreg(this, reg, val):
+    if reg in ['PC', 'I']:
+      if val < 0 or val > 65535:
+        raise ValueError
+      this.__registers[reg] = val
+    else:
+      this.__registers[reg].set(val)
+
+  def dump(this, target):
+    with open(target + '.memory', 'w') as f:
+      f.write(this.memory.dump())
+    with open(target + '.registers', 'w') as f:
+      for key in this.__registers:
+        f.write("{}:{}\n".format(key, this.getreg(key)))
+
+  def load(this, target):
+    with open(target + '.memory', 'r') as f:
+      lines = ''
+      for line in f:
+        lines += line + '\n'
+      this.memory.load(lines)
+    with open(target + '.registers', 'r') as f:
+      for line in f:
+        if len(line) > 0 and ':' in line:
+          this.setreg(line.split(':')[0], int(line.split(':')[1]))
+
+  def tick(this):
+    PC = this.getreg('PC')
+    nextaddr = PC + 2
+    opcode = [this.memory.get(PC).getl(int),
+              this.memory.get(PC).geth(int),
+              this.memory.get(PC + 1).getl(int),
+              this.memory.get(PC + 1).geth(int)]
+    opcode_raw = opcode
+
+    if PC >= this.memory.size() - 2:
+      logging.info("PC exceeded address space, halting")
+      return False
+
+    # http://devernay.free.fr/hacks/chip8/C8TECH10.HTM
+
+    opcode = ''.join([ByteUtil.fromInt(x, 4) for x in opcode])
+    assert len(opcode) == 16
+
+    logging.debug("PC={}; opcode = {} = {} {} {} {}"
+                  .format(PC, opcode_raw, opcode[0:4], opcode[4:8],
+                          opcode[8:12], opcode[12:16]))
+
+    instruction = int(opcode[0:4], 2)
+    register = hex(int(opcode[4:8], 2))[2:]  # assuming register is specified
+    # at [4:8] (nibble 2)
+    # 2nd register at [8:12] (nibble 3)
+    register2 = hex(int(opcode[8:12], 2))[2:]
+    nibble2 = int(opcode[8:16], 2)  # int value of the last 2 nibbles
+    nibble3 = int(opcode[4:16], 2)  # int value of the last 3 nibbles
+
+    if instruction == 6:
+      # set
+      logging.debug("set {} {}".format(register, nibble2))
+      this.setreg(register, nibble2)
+
+    elif opcode[8:16] == '11100000':  # 14 00
+      # cls
+      logging.debug("cls")
+      addr = 3840
+      while addr < this.memory.size():
+        this.memory.set(addr, 0)
+        addr += 1
+
+    elif opcode[8:16] == '11101110':  # 14 14
+      # ret
+      logging.debug('ret')
+      logging.warning("call to ret (0xe), which is not implemented")
+
+    elif instruction == 1:
+      # JP
+      logging.debug("jp {}".format(nibble3))
+      nextaddr = nibble3
+
+    elif instruction == 2:
+      # call
+      logging.debug("call {}".format(nibble3))
+      logging.warning("call to call (0x02), which is not implemented")
+
+    elif instruction == 3:
+      # se
+      logging.debug("se {} {}".format(register, nibble2))
+      if this.getreg(register) == nibble2:
+        nextaddr += 2
+        logging.debug("because register {} is equal to {}, jumping ahead 2"
+                      .format(register, nibble2))
+
+    elif instruction == 4:
+      # sne
+      logging.debug("sne {} {}".format(register, nibble2))
+      if this.getreg(register) != nibble2:
+        nextaddr += 2
+        logging.debug("because register {} != {}, jumping ahead 2"
+                      .format(register, nibble2))
+
+    elif instruction == 5:
+      # se (register-register)
+      logging.debug("se {} {}".format(register, register2))
+      if this.getreg(register) == this.getreg(register2):
+        nextaddr += 2
+        logging.debug("because register {} == register {}, jumping ahead 2"
+                      .format(register, register2))
+
+    elif instruction == 7:
+      # add
+      logging.debug("add {} {}".format(register, nibble2))
+      existing = this.getreg(register)
+      this.setreg(register, existing + nibble2)
+
+    else:
+      nextaddr -= 1
+
+    this.setreg('PC', nextaddr)
+    return True

A pickle8/CPU_test.py => pickle8/CPU_test.py +30 -0
@@ 0,0 1,30 @@
+import unittest
+import Memory
+import CPU
+import random
+
+
+class ProcessorTest(unittest.TestCase):
+
+  def test_registers(this):
+    for run_n in range(10000):
+      p = CPU.Processor()
+      # test 16 bit registers
+      test_val = random.randint(-1000, 655350)
+      if test_val < 0 or test_val > 65535:
+        with this.assertRaises(ValueError):
+          p.setreg('I', test_val)
+        with this.assertRaises(ValueError):
+          p.setreg('PC', test_val)
+      else:
+        p.setreg('I', test_val)
+        p.setreg('PC', test_val)
+        this.assertTrue(p.getreg('I') == test_val)
+        this.assertTrue(p.getreg('PC') == test_val)
+
+      # test 8 bit registers
+      for reg in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',
+                  'c', 'd', 'e', 'f', 'delay', 'sound', 'SP', 'VF']:
+        test_val = random.randint(0, 255)
+        p.setreg(reg, test_val)
+        this.assertTrue(p.getreg(reg) == test_val)

A pickle8/Memory.py => pickle8/Memory.py +246 -0
@@ 0,0 1,246 @@
+import binascii
+import logging
+import sys
+import pickle
+
+
+class Byte:
+  __default_format = int
+  __bin_format = ["bin", "binary", "2", 2]
+  __hex_format = ["hex", "hexadecimal", "16", 16]
+  __int_format = ["int", "integer", int]
+  __oct_format = ["oct", "octal", "8", 8]
+  __list_format = ["list", list]
+
+  def __repr__(this):
+    return this.get(2)
+
+  def __init__(this, val=0):
+    this.__contents = [False for x in range(8)]
+    this.set(val)
+
+  def __format_bits(this, fmt, bits):
+    # bits is a list like __contents
+    assert type(bits) is list
+
+    if fmt in this.__bin_format:
+      return ''.join(['1' if x else '0' for x in bits])
+
+    if fmt in this.__hex_format:
+      s = hex(this.__format_bits(int, bits))
+      if len(s) < 4:
+        s = "0x0" + s[2:]
+      return s
+
+    if fmt in this.__oct_format:
+      return oct(this.__format_bits(int, bits))
+
+    if fmt in this.__int_format:
+      return int(this.__format_bits(2, bits), 2)
+
+    if fmt in this.__list_format:
+      return bits
+
+  def __to_bits(this, val, minlength=8, maxlength=8):
+    # generate a valid list-of-bools from the value
+    ret = []
+    if type(val) is int:
+      if val < 0:
+        raise ValueError
+      for c in bin(val)[2:]:
+        if c == '1':
+          ret.append(True)
+        else:
+          ret.append(False)
+
+    elif type(val) is str:
+      if val[0:2] == "0b":
+        ret = this.__to_bits(int(val[2:], 2), minlength, maxlength)
+      elif val[0:2] == "0o":
+        ret = this.__to_bits(int(val[2:], 8), minlength, maxlength)
+      elif val[0:2] == "0x":
+        ret = this.__to_bits(int(val[2:], 16), minlength, maxlength)
+      else:
+        # assume we just have 1s and 0s
+        ret = this.__to_bits(int(val, 2), minlength, maxlength)
+
+    else:
+      # give up, if it's not valid it will fail validation
+      ret = val
+
+    while len(ret) < minlength:
+      ret = [False] + ret
+
+    if len(ret) > maxlength:
+      raise ValueError
+
+    for elem in ret:
+      if elem not in [True, False]:
+        raise ValueError
+
+    return ret
+
+  def get(this, fmt=__default_format):
+    return this.__format_bits(fmt, this.__contents)
+
+  # get bit by index
+  def getb(this, idx, fmt=__default_format):
+    try:
+      assert idx >= 0
+      assert idx < 8
+    except Exception:
+      logging.warning("cannot get index '{}' from 1-byte block".format(idx))
+      raise IndexError
+
+    bit = int(this.get(2)[idx])
+    return this.__format_bits(fmt, [bit])
+
+  # get upper nibble
+  def getl(this, fmt=__default_format):
+    return this.__format_bits(fmt, this.__contents[4:])
+
+  def geth(this, fmt=__default_format):
+    return this.__format_bits(fmt, this.__contents[:4])
+
+  def setb(this, val, idx):
+    try:
+      val = int(val)
+      assert val in [0, 1]
+      assert idx >= 0
+      assert idx < 8
+    except Exception:
+      logging.warning("cannot set bit at index '{}' to '{}' in 1-byte block"
+                      .format(idx, val))
+      raise ValueError
+
+    this.__contents[idx] = val
+
+  def setl(this, val):
+    this.__to_bits(val, 4, 4)
+    low = this.getl(list)
+    new_contents = low + this.__to_bits(val, 4, 4)
+    assert len(new_contents) == 8
+    this.__contents = new_contents
+
+  def seth(this, val):
+    val = this.__to_bits(val, 4, 4)
+    high = this.geth(list)
+    new_contents = this.__to_bits(val, 4, 4) + high
+    assert len(new_contents) == 8
+    this.__contents = new_contents
+
+  def set(this, val):
+    try:
+      this.__contents = this.__to_bits(val, 8, 8)
+    except Exception as e:
+      logging.warning("failed to blit byte '{}'".format(val))
+      raise ValueError
+
+
+class MainMemory:
+  __size = 4096
+
+  def __init__(this):
+    this.__contents = []
+    for i in range(this.__size):
+      this.__contents.append(Byte(0))
+
+    this.clear()
+
+  def clear(this):
+    for i in range(this.__size):
+      this.get(i).set(0)
+
+  def size(this):
+    return this.__size
+
+  def get(this, addr):
+    if addr < 0 or addr >= this.__size:
+      logging.warning("attempt to access out of bounds address '{}'"
+                      .format(addr))
+      raise IndexError
+
+    return this.__contents[addr]
+
+  def set(this, addr, val):
+    if addr < 0 or addr >= this.__size:
+      logging.warning("attempt to access out of bounds address '{}'"
+                      .format(addr))
+      raise IndexError
+
+    this.__contents[addr].set(val)
+
+  def getRange(this, lower, upper):
+    upper += 1  # make the upper end of the range inclusive
+    if lower < 0 or lower > upper:
+      logging.warning("memory range lower bound {} is out of bounds"
+                      .format(lower))
+      raise IndexError
+    if upper < lower or upper >= this.size():
+      logging.warning("memory range upper bound {} is out of bounds"
+                      .format(upper))
+      raise IndexError
+
+    ret = []
+    for addr in range(lower, upper):
+      ret.append(this.get(addr))
+
+    return ret
+
+  def prettyPrint(this, lower=0, upper=__size-1):
+    print(this.prettyFormat(lower, upper))
+
+  def prettyFormat(this, lower=0, upper=__size-1):
+    s = ''
+    if lower < 0 or lower > upper:
+      logging.warning("range lower bound {} is out of bounds"
+                      .format(lower))
+      raise IndexError
+    if upper < lower or upper >= this.size():
+      logging.warning("range upper bound {} is out of bounds"
+                      .format(upper))
+      raise IndexError
+
+    addr = lower
+
+    while addr < upper:
+      left = hex(addr)
+      if len(left) < 4:
+        left = "0x0" + left[2:]
+      s += (left + " | ")
+      for col in range(4):
+        if addr >= this.size():
+          sys.stdout.write("\n")
+          return
+        s += (this.get(addr).get("hex") + " ")
+        addr += 1
+      s += " "
+      for col in range(4):
+        if addr >= this.size():
+          s += "\n"
+          return
+        s += (this.get(addr).get("hex") + " ")
+        addr += 1
+      s += "\n"
+
+    return s
+
+  def dump(this):
+    s = ''
+    for addr in range(0, this.size()):
+      if this.get(addr).get(int) != 0:
+        s += '{}:{}'.format(addr, this.get(addr).get(int))
+        s += "\n"
+
+    return s
+
+  def load(this, source):
+    for line in source.split('\n'):
+      if len(line.split(':')) != 2:
+        continue
+      try:
+        addr = int(line.split(':')[0])
+        val = int(line.split(':')[1])
+        this.set(addr, val)
+      except Exception:
+        logging.warning("failed to load line '{}'".format(line))

A pickle8/Memory_test.py => pickle8/Memory_test.py +128 -0
@@ 0,0 1,128 @@
+import unittest
+import Memory
+import random
+
+
+class ByteTest(unittest.TestCase):
+
+  def test_initilization(this):
+    for run_n in range(10000):
+      test_val = random.randint(0, 255)
+      b = Memory.Byte(test_val)
+      this.assertTrue(b.get(int) == test_val)
+      b = Memory.Byte()
+      this.assertTrue(b.get(int) == 0)
+
+  def test_getb(this):
+    for run_n in range(10000):
+      test_val = random.randint(136, 255)
+      expected = bin(test_val)[2:]
+      b = Memory.Byte(test_val)
+      this.assertTrue(b.getb(0, int) == int(expected[0]))
+      this.assertTrue(b.getb(1, int) == int(expected[1]))
+      this.assertTrue(b.getb(2, int) == int(expected[2]))
+      this.assertTrue(b.getb(3, int) == int(expected[3]))
+      this.assertTrue(b.getb(4, int) == int(expected[4]))
+      this.assertTrue(b.getb(5, int) == int(expected[5]))
+      this.assertTrue(b.getb(6, int) == int(expected[6]))
+      this.assertTrue(b.getb(7, int) == int(expected[7]))
+
+  def test_nibble_access(this):
+    for run_n in range(10000):
+      test_h = bin(random.randint(8, 15))[2:]
+      test_l = bin(random.randint(8, 15))[2:]
+      b = Memory.Byte(test_h + test_l)
+      high = b.geth(int)
+      this.assertTrue(high == int(test_h, 2))
+
+  def test_seth(this):
+    for run_n in range(0, 10000):
+      test_val = random.randint(-10, 20)
+      b = Memory.Byte()
+
+      if (test_val) < 0 or (test_val > 15):
+        with this.assertRaises(ValueError):
+          b.seth(test_val)
+      else:
+        b.seth(test_val)
+        this.assertTrue(b.geth(int) == test_val)
+
+  def test_setl(this):
+    for run_n in range(0, 10000):
+      test_val = random.randint(-10, 20)
+      b = Memory.Byte()
+
+      if (test_val) < 0 or (test_val > 15):
+        with this.assertRaises(ValueError):
+          b.setl(test_val)
+      else:
+        b.setl(test_val)
+        this.assertTrue(b.getl(int) == test_val)
+
+  def test_setb(this):
+    for run_n in range(0, 10000):
+      b = Memory.Byte()
+      test_addr = random.randint(0, 7)
+      test_val = random.randint(0, 1)
+      this.assertTrue(b.get() == 0)
+      b.setb(test_val, test_addr)
+      this.assertTrue(b.getb(test_addr, int) == test_val)
+
+  def test_edges(this):
+    b = Memory.Byte()
+    b.set(0)
+    this.assertTrue(b.get(int) == 0)
+
+    b.set(255)
+    this.assertTrue(b.get(int) == 255)
+    del(b)
+
+
+class MainMemory_test(unittest.TestCase):
+
+  def test_initilization(this):
+    m = Memory.MainMemory()
+    for addr in range(m.size()):
+      this.assertTrue(m.get(addr).get(int) == 0)
+    del(m)
+
+  def test_get_set(this):
+    for run_n in range(0, 10):
+      m = Memory.MainMemory()
+      success = True
+      for i in range(m.size()):
+        test_val = random.randint(0, 255)
+        m.set(i, test_val)
+        if test_val != m.get(i).get(int):
+          success = False
+
+      this.assertTrue(success)
+      m.clear()
+
+  def test_dump_load(this):
+    for run_n in range(0, 10):
+      m1 = Memory.MainMemory()
+      for i in range(m1.size()):
+        test_val = random.randint(0, 255)
+        m1.set(i, test_val)
+      s = m1.dump()
+      m2 = Memory.MainMemory()
+      m2.load(s)
+      for i in range(m1.size()):
+        this.assertTrue(m1.get(i).get(int) == m2.get(i).get(int))
+      m1.clear()
+      m2.clear()
+
+  def test_get_range(this):
+    for run_n in range(0, 10):
+      m = Memory.MainMemory()
+      val1 = random.randint(0, 255)
+      val2 = random.randint(0, 255)
+      addr = random.randint(0, 4094)
+      m.set(addr, val1)
+      m.set(addr+1, val2)
+      r = m.getRange(addr, addr+1)
+      this.assertTrue(len(r) == 2)
+      this.assertTrue(r[0].get(int) == val1)
+      this.assertTrue(r[1].get(int) == val2)
+      m.clear()

A pickle8/assemble.py => pickle8/assemble.py +240 -0
@@ 0,0 1,240 @@
+#!/usr/bin/env python3
+########10########20########30## DOCUMENTATION #50########60########70########80
+#
+#  OVERVIEW
+#  ========
+#  This applications in an assembler which given a valid .asm file, will omit
+#  pickle-8 opcodes. Specifically, when a program is assembled successfully, a
+#  pickle-8 .memory and .register file representing the desired initial CPU
+#  state will be written out.
+#
+#  SYNTAX
+#  ======
+#  The assembly files read by this application utilize the following syntax
+#
+#    * all text on a line after a literal `//` is ignored (including the //)
+#    * all constants are specified as integer values (eg. ``25``)
+#    * all registers begin with ``r_`` (eg. ``r_a``)
+#    * opcodes and their arguments are space delimited; the opcode always comes
+#      first. Usually, instructions follow the format `
+#      `opcode [arg1] [arg2] ...``. 
+#
+#  USAGE
+#  =====
+#
+#   See ./assemble.py -h
+#
+########10########20########30##### LICENSE ####50########60########70########80
+#  Copyright (c) 2016, Charles Daniels
+#  All rights reserved.
+#
+#  Redistribution and use in source and binary forms, with or without
+#  modification, are permitted provided that the following conditions are met:
+#
+#  1. Redistributions of source code must retain the above copyright notice,
+#     this list of conditions and the following disclaimer.
+#
+#  2. Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+#  3. Neither the name of the copyright holder nor the names of its
+#     contributors may be used to endorse or promote products derived from
+#     this software without specific prior written permission.
+#
+#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+#  POSSIBILITY OF SUCH DAMAGE.
+#
+########10########20########30########40########50########60########70########80
+
+import logging
+import sys
+import os
+import re
+import ByteUtil
+import argparse
+logging.basicConfig(level=logging.DEBUG)
+
+parser = argparse.ArgumentParser(description='pickle-8 assembler')
+parser.add_argument('--start', '-s',
+                    help='Starting address for code in main memory',
+                    default=512, type=int)
+parser.add_argument("--input", "-i",
+                    help="Absolute or relative path to input file. Use `-` for stdin.",
+                    required=True)
+parser.add_argument("--output", "-o", required=True,
+                    help="Absolute or relative path to output basename")
+args = vars(parser.parse_args())
+
+logging.debug("starting memory address is {}".format(args['start']))
+logging.debug("input is: {}".format(args['input']))
+logging.debug("output basepath is {}".format(args['output']))
+
+general_registers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',
+                     'c', 'd', 'e', 'f']
+
+# note that we match all integers as constants - this means the assembler will
+# allow you to try to compile with constants that are too large for their
+# underlying register/data structure.
+constant_match = re.compile("[0-9]+")
+register_match = re.compile("r_[1-9a-f]")
+opcode_match = ["set", "cls", "ret", "jp", "call", "se", "sne", "add"]
+
+input_file = None
+if args['input'] == '-':
+  input_file = sys.stdin
+elif os.path.isfile(args['input']):
+  input_file = open(args['input'], 'r')
+else:
+  logging.error("input file does not exist or is not a file")
+  exit(1)
+
+
+
+addr = args["start"]
+
+for line in input_file:
+  line = re.sub('//.*', '', line)
+  sl = line.split()
+  constants = []
+  registers = []
+
+  # match tokens
+  for token in sl:
+    # match constants
+    if constant_match.match(token):
+      logging.debug("constant token: {}".format(token))
+      constants.append(int(token))
+
+    # match registers
+    elif register_match.match(token):
+      logging.debug("register token: {}".format(token))
+      registers.append(token.replace("r_", ""))
+
+    # match opcodes
+    elif token in opcode_match:
+      logging.debug("opcode token: {}".format(token))
+
+    # show a warning if we don't know whats happening
+    else:
+      logging.warning("unmatched token: {}".format(token))
+
+  instruction = sl[0]
+
+  # we should have as many tokens as elements in the line (less the opcode)
+  if len(sl) != len(registers) + len(constants) + 1:
+    logging.error("failed to process line '{}', one or more unmatched tokens"
+                  .format(line))
+    exit(1)
+
+  # for convenience, we split things into 4 nibbles and keep them as strings,
+  # mostly so we can use python's nice subscription syntax  
+  opcode = ['0000', '0000', '0000', '0000']  # 2 bytes
+  if instruction == 'set':
+    opcode[0] = '0110'  # 6
+    opcode[1] = ByteUtil.fromHex(registers[0], 4)
+
+    val = ByteUtil.fromInt(sl[2], 8)
+    right = val[:4]
+    left = val[4:]
+    opcode[3] = left
+    opcode[2] = right
+
+  elif instruction == 'cls':
+    # CLS
+    # 00E0 = 0 0 14 0 
+    # clear the display
+    opcode = ['0000', '0000', '1110', '0000']
+
+  elif instruction== 'ret':
+    # RET
+    # 00EE = 0 0 14 14 
+    # Return from a subroutine.
+    # 
+    # PC is set to the address at the top of the stack, then SP is reduced by 1
+
+    opcode = ['0000', '0000', '1110', '1110']
+
+  elif instruction == 'jp':
+    # JP
+    # 1nnn = 1 n1 n2 n3
+    # Jump unconditionally to the address nnn (e nibbles)
+
+    # target address
+    tgt = ByteUtil.fromInt(sl[1], 12)  
+    opcode[0] = '0001'
+
+    opcode[1] = tgt[0:4]  # n1
+    opcode[2] = tgt[4:8]  # n2
+    opcode[3] = tgt[8:]   # n3
+
+  elif instruction == 'call':
+    # CALL
+    # 2nnn = 2 n1 n2 n3
+    # Call subroutine at nnn
+    # 
+    # SP incremented, current PC put on top of the stack, PC is set to nnn
+    tgt = ByteUtil.fromInt(sl[1], 12)
+    opcode[0] = '0010'
+
+    opcode[1] = tgt[0:4] # n1
+    opcode[2] = tgt[4:8] # n2
+    opcode[3] = tgt[8:]  # n3
+
+  elif instruction == 'se':
+    # SE
+    # 3xkk = 3 x k1 k2
+    # Skips the next instruction if register x equals the byte kk
+    opcode[0] = '0011'  # 3
+    register = registers[0]
+
+    if len(registers) > 1:
+      # case where we are comparing registers
+      opcode[0] = '0101'  # 5
+      opcode[1] = ByteUtil.fromHex(registers[0], 4)
+      opcode[2] = ByteUtil.fromHex(registers[1], 4)
+    else:
+      opcode[1] = ByteUtil.fromHex(registers[0], 4)
+      val = ByteUtil.fromInt(sl[2], 8)
+      opcode[2] = val[:4]
+      opcode[3] = val[4:]
+
+  elif instruction == 'sne':
+    opcode[0] = '0100'  # 4
+    register = registers[0]
+
+    opcode[1] = ByteUtil.fromHex(registers[0], 4)
+    val = ByteUtil.fromInt(sl[2], 8)
+    opcode[2] = val[:4]
+    opcode[3] = val[4:]
+
+  elif instruction == 'add':
+    opcode[0] = ByteUtil.fromInt(7, 4)
+    opcode[1] = ByteUtil.fromHex(registers[0], 4)
+    val = ByteUtil.fromInt(constants[0], 8)
+    opcode[2] = val[:4]
+    opcode[3] = val[4:]
+
+  logging.debug("{} -> i: {} c:{} r: {} -> {}"
+                .format(line.replace('\n', ''), instruction, constants,
+                        registers, opcode))
+  with open(args["output"] + ".memory", 'a') as o:
+    lower = int(opcode[1] + opcode[0], 2)
+    upper = int(opcode[3] + opcode[2], 2)
+    line1 = "{}:{}\n".format(addr, lower)
+    line2 = "{}:{}\n".format(addr + 1, upper)
+    o.write(line1)
+    o.write(line2)
+  addr += 2
+
+input_file.close()
+logging.info("assembly finished")

A pickle8/pickle8.py => pickle8/pickle8.py +22 -0
@@ 0,0 1,22 @@
+import Memory
+import logging
+import random
+import CPU
+
+logging.basicConfig(filename='pickle8.log', level=logging.DEBUG)
+console = logging.StreamHandler()
+console.setLevel(logging.INFO)
+formatter = logging.Formatter('%(levelname)-s:%(name)-s:%(message)s')
+console.setFormatter(formatter)
+logging.getLogger('').addHandler(console)
+
+logging.info("loading CPU state from file...")
+p = CPU.Processor()
+p.load('cpustate')
+logging.debug("memory state: \n{}".format(p.memory.prettyFormat()))
+logging.info("executing program code...")
+while p.tick():
+  pass
+p.dump('final')
+logging.info("simulation complete.")
+logging.debug("memory state: \n{}".format(p.memory.prettyFormat()))