diff --git a/memory.py b/memory.py new file mode 100644 index 0000000..cddfc94 --- /dev/null +++ b/memory.py @@ -0,0 +1,40 @@ +class ObservableMemory: + READ = 0 + WRITE = 1 + RW = 2 + + def __init__(self, subject=None): + if subject is None: + subject = [] + for addr in range(0x0000, 0xFFFF+1): + subject.insert(addr, 0x00) + self._subject = subject + self._observers = [] + + def __setitem__(self, address, value): + self._notify(self.WRITE, address, value) + self._subject[address] = value + + def __getitem__(self, address): + self._notify(self.READ, address) + return self._subject[address] + + def __getattr__(self, address): + return getattr(self._subject, address) + + def _notify(self, operation, address, value=None): + for oper, addr_range, callback in self._observers: + if address in addr_range: + if (oper == self.RW) or (oper == operation): + callback(operation, address, value) + + def subscribe(self, operation, addr_range, callback): + if operation not in (self.READ, self.WRITE, self.RW): + raise ValueError("Unsupported operation") + self._observers.append([operation, addr_range, callback]) + + def dma_read(self, key): + return self._subject[key] + + def dma_write(self, key, value): + self._subject[key] = value diff --git a/monitor.py b/monitor.py index cb40c59..a22441c 100644 --- a/monitor.py +++ b/monitor.py @@ -4,15 +4,21 @@ import cmd import os import re import shlex +import asyncore +from cmd import Cmd from mpu import MPU +from util import itoa +from memory import ObservableMemory class Monitor(cmd.Cmd): - def __init__(self, options, completekey='tab', stdin=None, stdout=None): + def __init__(self, options={}, completekey='tab', stdin=None, stdout=None): self.options = options self._mpu = MPU() + self._install_mpu_observers() self._update_prompt() self._radix = 16 + self._labels = {} cmd.Cmd.__init__(self, completekey, stdin, stdout) def onecmd(self, line): @@ -21,10 +27,28 @@ class Monitor(cmd.Cmd): start, end = matches.span() line = "tilde %s" % line[end:] - result = cmd.Cmd.onecmd(self, line) + result = None + try: + result = cmd.Cmd.onecmd(self, line) + except KeyboardInterrupt: + self._output("Interrupt") + except Exception,e: + (file, fun, line), t, v, tbinfo = asyncore.compact_traceback() + error = 'Error: %s, %s: file: %s line: %s' % (t, v, file, line) + self._output(error) + self._update_prompt() return result + def _install_mpu_observers(self): + def printit(operation, address, value): + self.stdout.write(chr(value)) + + m = ObservableMemory() + m.subscribe(m.WRITE, [0xE001], printit) + + self._mpu.memory = m + def _update_prompt(self): self.prompt = "\n%s\n." % repr(self._mpu) @@ -45,9 +69,10 @@ class Monitor(cmd.Cmd): def help_reset(self): self._output("reset\t\tReset the microprocessor") - def do_reset(self): + def do_reset(self, args): self._mpu = MPU() - + self._install_mpu_observers() + def do_EOF(self, args): self._output('') return 1 @@ -72,8 +97,17 @@ class Monitor(cmd.Cmd): self._output("before the next RTS or RTI is executed.") def do_return(self, args): + returns = [0x60, 0x40] # RTS, RTI + self._run(stopcodes=returns) + + def do_goto(self, args): + self._mpu.pc = self._parsenum(args) + brks = [0x00] # BRK + self._run(stopcodes=brks) + + def _run(self, stopcodes=[]): last_instruct = None - while last_instruct != 0x60: # RTS + while last_instruct not in stopcodes: self._mpu.step() last_instruct = self._mpu.memory[self._mpu.pc] @@ -104,7 +138,7 @@ class Monitor(cmd.Cmd): for name, radix in radixes.iteritems(): if self._radix == radix: self._output("Default radix is %s" % name) - + def help_tilde(self): self._output("~ ") self._output("Display the specified number in decimal, hex, octal, and binary.") @@ -119,7 +153,7 @@ class Monitor(cmd.Cmd): self._output("+%u" % num) self._output("$%02x" % num) self._output("%04o" % num) - self._output(self._itoa(num, 2).zfill(8)) + self._output(itoa(num, 2).zfill(8)) def help_registers(self): self._output("registers[ = [, = ]*]") @@ -191,14 +225,7 @@ class Monitor(cmd.Cmd): self._output(msg) return - address = start - for byte in bytes: - address &= 0xFFFF - self._mpu.memory[address] = ord(byte) - address += 1 - - fmt = (address - start, start, address) - self._output("Loaded %d bytes from %d to %d" % fmt) + self._fill(start, start, map(ord, bytes)) def help_fill(self): self.output("fill ") @@ -208,25 +235,34 @@ class Monitor(cmd.Cmd): def do_fill(self, args): split = shlex.split(args) - if len(split) != 2: + if len(split) < 2: self._output("Syntax error: %s" % args) return - addresses, fillbyte = split - start, end = self._parserange(addresses) - fillbyte = self._parsenum(fillbyte) + start, end = self._parserange(split[0]) + filler = map(self._parsenum, split[1:]) - self._fill(start, end, fillbyte) + self._fill(start, end, filler) - def _fill(self, start, end, byte): - byte &= 0xFF + def _fill(self, start, end, filler): address = start + length, index = len(filler), 0 + + if start == end: + end = start + length - 1 + if (end > 0xFFFF): + end = 0xFFFF + while address <= end: address &= 0xFFFF - self._mpu.memory[address] = byte + self._mpu.memory[address] = (filler[index] & 0xFF) + index += 1 + if index == length: + index = 0 address += 1 + fmt = (end - start + 1, start, end) - self._output("Filled +%d bytes from %d to %d" % fmt) + self._output("Wrote +%d bytes from $%04x to $%04x" % fmt) def help_mem(self): self._output("mem ") @@ -235,46 +271,86 @@ class Monitor(cmd.Cmd): def do_mem(self, args): start, end = self._parserange(args) - out = self._itoa(start, 16) + ": " - for byte in self._mpu.memory[start:end]: + out = itoa(start, 16).zfill(4) + ": " + for byte in self._mpu.memory[start:end+1]: out += "%02x " % byte self._output(out) - - def _parsenum(self, num): - if num.startswith('%'): - return int(num[1:], 2) - elif num.startswith('$'): + def do_add_label(self, args): + split = shlex.split(args) + if len(split) != 2: + self._output("Syntax error: %s" % args) + return + + address = self._parsenum(split[0]) + label = split[1] + + self._labels[label] = address + + def do_show_labels(self, args): + byaddress = zip(self._labels.values(), self._labels.keys()) + byaddress.sort() + for address, label in byaddress: + self._output("%04x: %s" % (address, label)) + + def do_delete_label(self, args): + try: + del self._labels[args] + except KeyError: + pass + + def _parsenum(self, num): + if num.startswith('$'): return int(num[1:], 16) elif num.startswith('+'): return int(num[1:], 10) + elif num.startswith('%'): + return int(num[1:], 2) + + elif num in self._labels: + return self._labels[num] + else: - return int(num, self._radix) + matches = re.match('^([^\s+-]+)\s*([+\-])\s*([$+%]?\d+)$', num) + if matches: + label, sign, offset = matches.groups() + + if label not in self._labels: + raise KeyError("Label not found: %s" % label) + + base = self._labels[label] + offset = self._parsenum(offset) + + if sign == '+': + address = base + offset + else: + address = base - offset + + if address < 0: + address = 0 + if address > 0xFFFF: + address = 0xFFFF + return address + + else: + try: + return int(num, self._radix) + except ValueError: + raise KeyError("Label not found: %s" % num) def _parserange(self, addresses): - matches = re.match('^([^,]+)\s*[:,]+\s*([^,]+)$', addresses) - if not matches: - raise ValueError - start, end = map(self._parsenum, matches.groups(0)) + matches = re.match('^([^:,]+)\s*[:,]+\s*([^:,]+)$', addresses) + if matches: + start, end = map(self._parsenum, matches.groups(0)) + else: + start = end = self._parsenum(addresses) if start > end: start, end = end, start return (start, end) - def _itoa(self, num, base=10): - negative = num < 0 - if negative: - num = -num - digits = [] - while num > 0: - num, last_digit = divmod(num, base) - digits.append('0123456789abcdefghijklmnopqrstuvwxyz'[last_digit]) - if negative: - digits.append('-') - digits.reverse() - return ''.join(digits) def main(args=None, options=None): c = Monitor(options) diff --git a/monitor_test.py b/monitor_test.py new file mode 100644 index 0000000..7dfc6cd --- /dev/null +++ b/monitor_test.py @@ -0,0 +1,89 @@ +import unittest +import sys +import re +from monitor import Monitor + +class MonitorTests(unittest.TestCase): + def test__parsenum_hex_literal(self): + mon = Monitor() + self.assertEqual(49152, mon._parsenum('$c000')) + + def test__parsenum_dec_literal(self): + mon = Monitor() + self.assertEqual(49152, mon._parsenum('+49152')) + + def test__parsenum_bin_literal(self): + mon = Monitor() + self.assertEqual(129, mon._parsenum('%10000001')) + + def test__parsenum_default_radix(self): + mon = Monitor() + mon._radix = 10 + self.assertEqual(10, mon._parsenum('10')) + mon._radix = 16 + self.assertEqual(16, mon._parsenum('10')) + + def test__parsenum_label(self): + mon = Monitor() + mon._labels = {'foo': 0xC000} + self.assertEquals(0xC000, mon._parsenum('foo')) + + def test__parsenum_bad_label(self): + mon = Monitor() + try: + mon._parsenum('bad_label') + self.fail() + except KeyError, why: + self.assertEqual('Label not found: bad_label', why[0]) + + def test__parsenum_label_hex_offset(self): + mon = Monitor() + mon._labels = {'foo': 0xC000} + self.assertEquals(0xC003, mon._parsenum('foo+$3')) + self.assertEquals(0xBFFD, mon._parsenum('foo-$3')) + self.assertEquals(0xC003, mon._parsenum('foo + $3')) + self.assertEquals(0xBFFD, mon._parsenum('foo - $3')) + + def test__parsenum_label_dec_offset(self): + mon = Monitor() + mon._labels = {'foo': 0xC000} + self.assertEquals(0xC003, mon._parsenum('foo++3')) + self.assertEquals(0xBFFD, mon._parsenum('foo-+3')) + self.assertEquals(0xC003, mon._parsenum('foo + +3')) + self.assertEquals(0xBFFD, mon._parsenum('foo - +3')) + + def test__parsenum_label_bin_offset(self): + mon = Monitor() + mon._labels = {'foo': 0xC000} + self.assertEquals(0xC003, mon._parsenum('foo+%00000011')) + self.assertEquals(0xBFFD, mon._parsenum('foo-%00000011')) + self.assertEquals(0xC003, mon._parsenum('foo + %00000011')) + self.assertEquals(0xBFFD, mon._parsenum('foo - %00000011')) + + def test__parsenum_label_offset_default_radix(self): + mon = Monitor() + mon._labels = {'foo': 0xC000} + mon._radix = 16 + self.assertEquals(0xC010, mon._parsenum('foo+10')) + self.assertEquals(0xBFF0, mon._parsenum('foo-10')) + self.assertEquals(0xC010, mon._parsenum('foo + 10')) + self.assertEquals(0xBFF0, mon._parsenum('foo - 10')) + mon._radix = 10 + self.assertEquals(0xC00A, mon._parsenum('foo+10')) + self.assertEquals(0xBFF6, mon._parsenum('foo-10')) + self.assertEquals(0xC00A, mon._parsenum('foo + 10')) + self.assertEquals(0xBFF6, mon._parsenum('foo - 10')) + + def test__parsenum_bad_label_with_offset(self): + mon = Monitor() + try: + mon._parsenum('bad_label+3') + self.fail() + except KeyError, why: + self.assertEqual('Label not found: bad_label', why[0]) + +def test_suite(): + return unittest.findTestCases(sys.modules[__name__]) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite')