From 4963eeca9d4e0b891c57b28fc09e6b2fee222346 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Tue, 16 Aug 2011 15:49:48 +1200 Subject: [PATCH 01/22] initial cassette input --- applepy.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/applepy.py b/applepy.py index 479de65..592cc64 100644 --- a/applepy.py +++ b/applepy.py @@ -9,6 +9,7 @@ import struct import subprocess import sys import time +import wave class Display: @@ -288,12 +289,24 @@ class Speaker: self.play() +class Cassette: + + def __init__(self, fn): + wav = wave.open(fn, "r") + self.raw = wav.readframes(wav.getnframes()) + + def read_byte(self, cycle): + sys.stdout.write(str(cycle * 22000 / 1000000) + "\r") + return ord(self.raw[cycle * 22000 / 1000000]) + + class SoftSwitches: - def __init__(self, display, speaker): + def __init__(self, display, speaker, cassette): self.kbd = 0x00 self.display = display self.speaker = speaker + self.cassette = cassette def read_byte(self, cycle, address): assert 0xC000 <= address <= 0xCFFF @@ -320,6 +333,8 @@ class SoftSwitches: self.display.lores() elif address == 0xC057: self.display.hires() + elif address == 0xC060: + return self.cassette.read_byte(cycle) else: pass # print "%04X" % address return 0x00 @@ -327,10 +342,10 @@ class SoftSwitches: class Apple2: - def __init__(self, options, display, speaker): + def __init__(self, options, display, speaker, cassette): self.display = display self.speaker = speaker - self.softswitches = SoftSwitches(display, speaker) + self.softswitches = SoftSwitches(display, speaker, cassette) args = [ sys.executable, @@ -429,6 +444,7 @@ if __name__ == "__main__": options = get_options() display = Display() speaker = None if options.quiet else Speaker() + cassette = Cassette("k7_apple_600202300_littlebrickout.wav") - apple = Apple2(options, display, speaker) + apple = Apple2(options, display, speaker, cassette) apple.run() From 6951db69adf9f42297a1cb3739610f62d92ae294 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Tue, 16 Aug 2011 16:41:15 +1200 Subject: [PATCH 02/22] finish cassette support --- applepy.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/applepy.py b/applepy.py index 592cc64..4fbfa37 100644 --- a/applepy.py +++ b/applepy.py @@ -296,8 +296,8 @@ class Cassette: self.raw = wav.readframes(wav.getnframes()) def read_byte(self, cycle): - sys.stdout.write(str(cycle * 22000 / 1000000) + "\r") - return ord(self.raw[cycle * 22000 / 1000000]) + offset = cycle * 22000 / 1000000 + return ord(self.raw[offset]) if offset < len(self.raw) else 0x80 class SoftSwitches: @@ -334,7 +334,8 @@ class SoftSwitches: elif address == 0xC057: self.display.hires() elif address == 0xC060: - return self.cassette.read_byte(cycle) + if self.cassette: + return self.cassette.read_byte(cycle) else: pass # print "%04X" % address return 0x00 @@ -406,6 +407,7 @@ def usage(): print >>sys.stderr print >>sys.stderr, "Usage: applepy.py [options]" print >>sys.stderr + print >>sys.stderr, " -c, --cassette Cassette wav file to load" print >>sys.stderr, " -R, --rom ROM file to use (default A2ROM.BIN)" print >>sys.stderr, " -r, --ram RAM file to load (default none)" print >>sys.stderr, " -q, --quiet Quiet mode, no sounds (default sounds)" @@ -415,6 +417,7 @@ def usage(): def get_options(): class Options: def __init__(self): + self.cassette = None self.rom = "A2ROM.BIN" self.ram = None self.quiet = False @@ -423,7 +426,10 @@ def get_options(): a = 1 while a < len(sys.argv): if sys.argv[a].startswith("-"): - if sys.argv[a] in ("-R", "--rom"): + if sys.argv[a] in ("-c", "--cassette"): + a += 1 + options.cassette = sys.argv[a] + elif sys.argv[a] in ("-R", "--rom"): a += 1 options.rom = sys.argv[a] elif sys.argv[a] in ("-r", "--ram"): @@ -444,7 +450,7 @@ if __name__ == "__main__": options = get_options() display = Display() speaker = None if options.quiet else Speaker() - cassette = Cassette("k7_apple_600202300_littlebrickout.wav") + cassette = Cassette(options.cassette) if options.cassette else None apple = Apple2(options, display, speaker, cassette) apple.run() From b8c7949d8e1a27f521bbb898468e2fa0b6036ca8 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Tue, 16 Aug 2011 18:22:41 +1200 Subject: [PATCH 03/22] attempt to skip to data part of tape --- applepy.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/applepy.py b/applepy.py index 4fbfa37..5052474 100644 --- a/applepy.py +++ b/applepy.py @@ -294,9 +294,18 @@ class Cassette: def __init__(self, fn): wav = wave.open(fn, "r") self.raw = wav.readframes(wav.getnframes()) + self.start_cycle = 0 + self.start_offset = 0 + + for i, b in enumerate(self.raw): + if ord(b) > 0xA0: + self.start_offset = i + break def read_byte(self, cycle): - offset = cycle * 22000 / 1000000 + if self.start_cycle == 0: + self.start_cycle = cycle + offset = self.start_offset + (cycle - self.start_cycle) * 22000 / 1000000 return ord(self.raw[offset]) if offset < len(self.raw) else 0x80 From 338f8962fde2038edf61e545e319b395a65c6802 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Fri, 19 Aug 2011 11:37:00 +1200 Subject: [PATCH 04/22] Mention the minimal applepy_curses.py in README --- README | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README b/README index 15e688b..1c81b4e 100644 --- a/README +++ b/README @@ -31,4 +31,5 @@ runs all the programs I've tried so far. The only I/O supported is the keyboard and screen but 40-column text, LORES and HIRES graphics are all supported. -ApplePy currently requires Pygame. \ No newline at end of file +ApplePy currently requires Pygame (although there is a minimal applepy_curses.py +that uses the curses to display text mode only). \ No newline at end of file From a73ab29be239b31396f4fba7bbd1c22c14778e95 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Fri, 19 Aug 2011 11:40:19 +1200 Subject: [PATCH 05/22] Edited README via GitHub --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index 1c81b4e..9bdd5d1 100644 --- a/README +++ b/README @@ -32,4 +32,4 @@ The only I/O supported is the keyboard and screen but 40-column text, LORES and HIRES graphics are all supported. ApplePy currently requires Pygame (although there is a minimal applepy_curses.py -that uses the curses to display text mode only). \ No newline at end of file +that uses curses to display text mode only). \ No newline at end of file From cd692af6f33d24a117bd0ad5db0011db63b13366 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Thu, 18 Aug 2011 20:39:28 +1200 Subject: [PATCH 06/22] use sockets for comms instead of stdio --- applepy.py | 19 +++++++++++-------- cpu6502.py | 36 +++++++++++++++++++++++------------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/applepy.py b/applepy.py index 5052474..e7feeb1 100644 --- a/applepy.py +++ b/applepy.py @@ -5,6 +5,7 @@ import numpy import pygame +import socket import struct import subprocess import sys @@ -357,30 +358,32 @@ class Apple2: self.speaker = speaker self.softswitches = SoftSwitches(display, speaker, cassette) + listener = socket.socket() + listener.bind(("127.0.0.1", 0)) + listener.listen(0) + args = [ sys.executable, "cpu6502.py", + "--ui", str(listener.getsockname()[1]), "--rom", options.rom, ] if options.ram: args.extend([ "--ram", options.ram, ]) - self.core = subprocess.Popen( - args=args, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - ) + self.core = subprocess.Popen(args) + + self.cpu, _ = listener.accept() def run(self): update_cycle = 0 quit = False while not quit: - op = self.core.stdout.read(8) + op = self.cpu.recv(8) cycle, rw, addr, val = struct.unpack(" 0x7F: x = x - 0x100 @@ -86,22 +90,20 @@ class Memory: return 0 op = struct.pack(">sys.stderr print >>sys.stderr, " -R, --rom ROM file to use (default A2ROM.BIN)" print >>sys.stderr, " -r, --ram RAM file to load (default none)" + print >>sys.stderr, " -u, --ui UI socket" sys.exit(1) @@ -983,6 +989,7 @@ def get_options(): def __init__(self): self.rom = "A2ROM.BIN" self.ram = None + self.ui = None options = Options() a = 1 @@ -994,6 +1001,9 @@ def get_options(): elif sys.argv[a] in ("-r", "--ram"): a += 1 options.ram = sys.argv[a] + elif sys.argv[a] in ("-u", "--ui"): + a += 1 + options.ui = int(sys.argv[a]) else: usage() else: @@ -1004,13 +1014,13 @@ def get_options(): if __name__ == "__main__": - if sys.stdout.isatty(): + options = get_options() + if options.ui is None: print "ApplePy cpu core" print "Run applepy.py instead" sys.exit(0) - options = get_options() mem = Memory(options) cpu = CPU(mem) - cpu.run() + cpu.run(options.ui) From dcc8e9d8ce6c0cfe5be6a007c1dcdc0e56dce056 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Thu, 18 Aug 2011 21:14:49 +1200 Subject: [PATCH 07/22] update curses UI for socket comms --- applepy_curses.py | 58 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/applepy_curses.py b/applepy_curses.py index 8b234e6..b85b779 100644 --- a/applepy_curses.py +++ b/applepy_curses.py @@ -4,6 +4,7 @@ import curses +import socket import struct import subprocess import sys @@ -55,20 +56,29 @@ def write(win, addr, val): def run(win): global kbd - p = subprocess.Popen( - args=[sys.executable, "cpu6502.py"], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - ) + + listener = socket.socket() + listener.bind(("127.0.0.1", 0)) + listener.listen(0) + + args = [ + sys.executable, + "cpu6502.py", + "--ui", str(listener.getsockname()[1]), + "--rom", options.rom, + ] + + p = subprocess.Popen(args) + cpu, _ = listener.accept() + win.clear() curses.noecho() win.nodelay(True) while True: - op = p.stdout.read(8) + op = cpu.recv(8) cycle, rw, addr, val = struct.unpack(">sys.stderr, "ApplePy - an Apple ][ emulator in Python" + print >>sys.stderr, "James Tauber / http://jtauber.com/" + print >>sys.stderr + print >>sys.stderr, "Usage: applepy_curses.py [options]" + print >>sys.stderr + print >>sys.stderr, " -R, --rom ROM file to use (default A2ROM.BIN)" + sys.exit(1) + + +def get_options(): + class Options: + def __init__(self): + self.rom = "A2ROM.BIN" + + options = Options() + a = 1 + while a < len(sys.argv): + if sys.argv[a].startswith("-"): + if sys.argv[a] in ("-R", "--rom"): + a += 1 + options.rom = sys.argv[a] + else: + usage() + else: + usage() + a += 1 + + return options if __name__ == "__main__": + options = get_options() curses.wrapper(run) From 9ffbf63716b69829d79df30c8319919cc5226c4a Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Thu, 18 Aug 2011 21:21:42 +1200 Subject: [PATCH 08/22] open memory files in binary mode --- cpu6502.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpu6502.py b/cpu6502.py index 0a3064d..86e4cc6 100644 --- a/cpu6502.py +++ b/cpu6502.py @@ -25,7 +25,7 @@ class ROM: self._mem[address - self.start + offset] = datum def load_file(self, address, filename): - with open(filename) as f: + with open(filename, "rb") as f: for offset, datum in enumerate(f.read()): self._mem[address - self.start + offset] = ord(datum) From 15e174c02a2037b8434d06cf102ecd9060b8bab6 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Fri, 19 Aug 2011 20:30:55 +1200 Subject: [PATCH 09/22] rename --ui switch to --bus --- applepy.py | 2 +- applepy_curses.py | 2 +- cpu6502.py | 40 ++++++++++++++++++++-------------------- tests.py | 26 +++++++++++++------------- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/applepy.py b/applepy.py index e7feeb1..cebe0c0 100644 --- a/applepy.py +++ b/applepy.py @@ -365,7 +365,7 @@ class Apple2: args = [ sys.executable, "cpu6502.py", - "--ui", str(listener.getsockname()[1]), + "--bus", str(listener.getsockname()[1]), "--rom", options.rom, ] if options.ram: diff --git a/applepy_curses.py b/applepy_curses.py index b85b779..a232c40 100644 --- a/applepy_curses.py +++ b/applepy_curses.py @@ -64,7 +64,7 @@ def run(win): args = [ sys.executable, "cpu6502.py", - "--ui", str(listener.getsockname()[1]), + "--bus", str(listener.getsockname()[1]), "--rom", options.rom, ] diff --git a/cpu6502.py b/cpu6502.py index f397aa6..e48b0c7 100644 --- a/cpu6502.py +++ b/cpu6502.py @@ -8,7 +8,7 @@ import struct import sys -ui = None +bus = None # socket for bus I/O def signed(x): @@ -46,8 +46,8 @@ class RAM(ROM): class Memory: - def __init__(self, options=None, use_stdio=True): - self.use_stdio = use_stdio + def __init__(self, options=None, use_bus=True): + self.use_bus = use_bus self.rom = ROM(0xD000, 0x3000) if options: @@ -86,12 +86,12 @@ class Memory: self.bus_write(cycle, address, value) def bus_read(self, cycle, address): - if not self.use_stdio: + if not self.use_bus: return 0 op = struct.pack(">sys.stderr print >>sys.stderr, "Usage: cpu6502.py [options]" print >>sys.stderr + print >>sys.stderr, " -b, --bus Bus port number" print >>sys.stderr, " -R, --rom ROM file to use (default A2ROM.BIN)" print >>sys.stderr, " -r, --ram RAM file to load (default none)" - print >>sys.stderr, " -u, --ui UI socket" sys.exit(1) @@ -989,21 +989,21 @@ def get_options(): def __init__(self): self.rom = "A2ROM.BIN" self.ram = None - self.ui = None + self.bus = None options = Options() a = 1 while a < len(sys.argv): if sys.argv[a].startswith("-"): - if sys.argv[a] in ("-R", "--rom"): + if sys.argv[a] in ("-b", "--bus"): + a += 1 + options.bus = int(sys.argv[a]) + elif sys.argv[a] in ("-R", "--rom"): a += 1 options.rom = sys.argv[a] elif sys.argv[a] in ("-r", "--ram"): a += 1 options.ram = sys.argv[a] - elif sys.argv[a] in ("-u", "--ui"): - a += 1 - options.ui = int(sys.argv[a]) else: usage() else: @@ -1015,7 +1015,7 @@ def get_options(): if __name__ == "__main__": options = get_options() - if options.ui is None: + if options.bus is None: print "ApplePy cpu core" print "Run applepy.py instead" sys.exit(0) @@ -1023,4 +1023,4 @@ if __name__ == "__main__": mem = Memory(options) cpu = CPU(mem) - cpu.run(options.ui) + cpu.run(options.bus) diff --git a/tests.py b/tests.py index 3d8dedd..6a5d606 100644 --- a/tests.py +++ b/tests.py @@ -5,7 +5,7 @@ from cpu6502 import Memory, CPU class TestMemory(unittest.TestCase): def setUp(self): - self.memory = Memory(use_stdio=False) + self.memory = Memory(use_bus=False) def test_load(self): self.memory.load(0x1000, [0x01, 0x02, 0x03]) @@ -25,7 +25,7 @@ class TestMemory(unittest.TestCase): class TestLoadStoreOperations(unittest.TestCase): def setUp(self): - self.memory = Memory(use_stdio=False) + self.memory = Memory(use_bus=False) self.cpu = CPU(self.memory) self.memory.load(0x1000, [0x00, 0x01, 0x7F, 0x80, 0xFF]) @@ -114,7 +114,7 @@ class TestLoadStoreOperations(unittest.TestCase): class TestRegisterTransferOperations(unittest.TestCase): def setUp(self): - self.memory = Memory(use_stdio=False) + self.memory = Memory(use_bus=False) self.cpu = CPU(self.memory) def test_TAX(self): @@ -189,7 +189,7 @@ class TestRegisterTransferOperations(unittest.TestCase): class TestStackOperations(unittest.TestCase): def setUp(self): - self.memory = Memory(use_stdio=False) + self.memory = Memory(use_bus=False) self.cpu = CPU(self.memory) def test_TSX(self): @@ -237,7 +237,7 @@ class TestStackOperations(unittest.TestCase): class TestLogicalOperations(unittest.TestCase): def setUp(self): - self.memory = Memory(use_stdio=False) + self.memory = Memory(use_bus=False) self.cpu = CPU(self.memory) def test_AND(self): @@ -325,7 +325,7 @@ class TestLogicalOperations(unittest.TestCase): class TestArithmeticOperations(unittest.TestCase): def setUp(self): - self.memory = Memory(use_stdio=False) + self.memory = Memory(use_bus=False) self.cpu = CPU(self.memory) def test_ADC_without_BCD(self): @@ -544,7 +544,7 @@ class TestArithmeticOperations(unittest.TestCase): class TestIncrementDecrementOperations(unittest.TestCase): def setUp(self): - self.memory = Memory(use_stdio=False) + self.memory = Memory(use_bus=False) self.cpu = CPU(self.memory) def test_INC(self): @@ -653,7 +653,7 @@ class TestIncrementDecrementOperations(unittest.TestCase): class TestShiftOperations(unittest.TestCase): def setUp(self): - self.memory = Memory(use_stdio=False) + self.memory = Memory(use_bus=False) self.cpu = CPU(self.memory) def test_ASL(self): @@ -760,7 +760,7 @@ class TestShiftOperations(unittest.TestCase): class TestJumpCallOperations(unittest.TestCase): def setUp(self): - self.memory = Memory(use_stdio=False) + self.memory = Memory(use_bus=False) self.cpu = CPU(self.memory) def test_JMP(self): @@ -792,7 +792,7 @@ class TestJumpCallOperations(unittest.TestCase): class TestBranchOperations(unittest.TestCase): def setUp(self): - self.memory = Memory(use_stdio=False) + self.memory = Memory(use_bus=False) self.cpu = CPU(self.memory) def test_BCC(self): @@ -879,7 +879,7 @@ class TestBranchOperations(unittest.TestCase): class TestStatusFlagOperations(unittest.TestCase): def setUp(self): - self.memory = Memory(use_stdio=False) + self.memory = Memory(use_bus=False) self.cpu = CPU(self.memory) def test_CLC(self): @@ -921,7 +921,7 @@ class TestStatusFlagOperations(unittest.TestCase): class TestSystemFunctionOperations(unittest.TestCase): def setUp(self): - self.memory = Memory(use_stdio=False) + self.memory = Memory(use_bus=False) self.cpu = CPU(self.memory) def test_BRK(self): @@ -951,7 +951,7 @@ class TestSystemFunctionOperations(unittest.TestCase): class Test6502Bugs(unittest.TestCase): def setUp(self): - self.memory = Memory(use_stdio=False) + self.memory = Memory(use_bus=False) self.cpu = CPU(self.memory) def test_zero_page_x(self): From 9f09818aa03362431add8770593236892cd02a65 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Fri, 19 Aug 2011 21:07:19 +1200 Subject: [PATCH 10/22] abandon startup if cpu module does not start --- applepy.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/applepy.py b/applepy.py index cebe0c0..2c35ca5 100644 --- a/applepy.py +++ b/applepy.py @@ -5,6 +5,7 @@ import numpy import pygame +import select import socket import struct import subprocess @@ -374,6 +375,10 @@ class Apple2: ]) self.core = subprocess.Popen(args) + rs, _, _ = select.select([listener], [], [], 2) + if not rs: + print >>sys.stderr, "CPU module did not start" + sys.exit(1) self.cpu, _ = listener.accept() def run(self): From a673f8a4d4b80933a2bc0694cc10b95a8c52a680 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Fri, 19 Aug 2011 21:43:12 +1200 Subject: [PATCH 11/22] graceful shutdown if cpu core exits --- applepy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applepy.py b/applepy.py index 2c35ca5..71e0aef 100644 --- a/applepy.py +++ b/applepy.py @@ -386,6 +386,8 @@ class Apple2: quit = False while not quit: op = self.cpu.recv(8) + if len(op) == 0: + break cycle, rw, addr, val = struct.unpack(" Date: Fri, 19 Aug 2011 21:48:46 +1200 Subject: [PATCH 12/22] start of cpu core control channel Currently this listens on TCP port 6502 on localhost. The protocol is a simple text protocol, type "help" for a list of commands. --- cpu6502.py | 106 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 11 deletions(-) diff --git a/cpu6502.py b/cpu6502.py index e48b0c7..61a0f76 100644 --- a/cpu6502.py +++ b/cpu6502.py @@ -3,6 +3,7 @@ # originally written 2001, updated 2011 +import select import socket import struct import sys @@ -326,6 +327,63 @@ class Disassemble: return s +class ControlHandler: + + def __init__(self, cpu, sock): + self.cpu = cpu + self.sock = sock + self.sock.send("ApplePy 6502 core\n") + self.buffer = "" + + def handle_read(self): + buf = self.sock.recv(1024) + if not buf: + self.cpu.control.remove(self) + return + self.buffer += buf + while True: + i = self.buffer.find("\n") + if i < 0: + break + s = self.buffer[:i].strip() + self.buffer = self.buffer[i+1:] + a = s.split() + if a[0] == "help": + self.sock.send("commands: help, peek, poke, status, quit, reset\n") + elif a[0] == "peek": + addr = int(a[1]) + self.sock.send("%02X\n" % self.cpu.memory.read_byte(self.cpu.cycles, addr)) + elif a[0] == "poke": + addr = int(a[1]) + val = int(a[2]) + self.cpu.memory.write_byte(self.cpu.cycles, addr, val) + self.sock.send("poked\n") + elif a[0] == "status": + self.sock.send("A=%02X X=%02X Y=%02X S=%02X PC=%04X F=%c%c0%c%c%c%c%c\n" % ( + self.cpu.accumulator, + self.cpu.x_index, + self.cpu.y_index, + self.cpu.stack_pointer, + self.cpu.program_counter, + "N" if self.cpu.sign_flag else "n", + "V" if self.cpu.overflow_flag else "v", + "B" if self.cpu.break_flag else "b", + "D" if self.cpu.decimal_mode_flag else "d", + "I" if self.cpu.interrupt_disable_flag else "i", + "Z" if self.cpu.zero_flag else "z", + "C" if self.cpu.carry_flag else "c", + )) + elif a[0] == "quit": + self.cpu.quit = True + self.sock.send("quitting\n") + elif a[0] == "reset": + self.cpu.reset() + self.cpu.running = True + self.sock.send("resetting\n") + else: + self.sock.send("unknown command\n") + + class CPU: STACK_PAGE = 0x100 @@ -334,6 +392,12 @@ class CPU: def __init__(self, memory): self.memory = memory self.disassemble = Disassemble(self, memory) + + self.control_listener = socket.socket() + self.control_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) + self.control_listener.bind(("127.0.0.1", 6502)) + self.control_listener.listen(1) + self.control = [] self.accumulator = 0x00 self.x_index = 0x00 @@ -353,6 +417,8 @@ class CPU: self.setup_ops() self.reset() + self.running = True + self.quit = False def setup_ops(self): self.ops = [None] * 0x100 @@ -515,17 +581,35 @@ class CPU: global bus bus = socket.socket() bus.connect(("127.0.0.1", bus_port)) - while True: - self.cycles += 2 # all instructions take this as a minimum - op = self.read_pc_byte() - func = self.ops[op] - if func is None: - print "UNKNOWN OP" - print hex(self.program_counter - 1) - print hex(op) - break - else: - self.ops[op]() + + while not self.quit: + + timeout = 0 + if not self.running: + timeout = 1 + sockets = [self.control_listener] + [x.sock for x in self.control] + rs, _, _ = select.select(sockets, [], [], timeout) + for s in rs: + if s is self.control_listener: + cs, _ = self.control_listener.accept() + self.control.append(ControlHandler(self, cs)) + else: + c = [x for x in self.control if x.sock is s][0] + c.handle_read() + + count = 1000 + while count > 0 and self.running: + self.cycles += 2 # all instructions take this as a minimum + op = self.read_pc_byte() + func = self.ops[op] + if func is None: + print "UNKNOWN OP" + print hex(self.program_counter - 1) + print hex(op) + break + else: + self.ops[op]() + count -= 1 def test_run(self, start, end): self.program_counter = start From 8b628601524f2183566d2f890739409cc0897994 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Sat, 20 Aug 2011 13:59:22 +1200 Subject: [PATCH 13/22] refactor control command processing --- cpu6502.py | 74 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/cpu6502.py b/cpu6502.py index 61a0f76..52bc32a 100644 --- a/cpu6502.py +++ b/cpu6502.py @@ -335,6 +335,44 @@ class ControlHandler: self.sock.send("ApplePy 6502 core\n") self.buffer = "" + def cmd_help(self, args): + self.sock.send("commands: %s\n" % ", ".join(sorted(x[4:] for x in dir(self) if x.startswith("cmd_")))) + + def cmd_peek(self, args): + addr = int(args[1]) + self.sock.send("%02X\n" % self.cpu.memory.read_byte(self.cpu.cycles, addr)) + + def cmd_poke(self, args): + addr = int(args[1]) + val = int(args[2]) + self.cpu.memory.write_byte(self.cpu.cycles, addr, val) + self.sock.send("poked\n") + + def cmd_status(self, args): + self.sock.send("A=%02X X=%02X Y=%02X S=%02X PC=%04X F=%c%c0%c%c%c%c%c\n" % ( + self.cpu.accumulator, + self.cpu.x_index, + self.cpu.y_index, + self.cpu.stack_pointer, + self.cpu.program_counter, + "N" if self.cpu.sign_flag else "n", + "V" if self.cpu.overflow_flag else "v", + "B" if self.cpu.break_flag else "b", + "D" if self.cpu.decimal_mode_flag else "d", + "I" if self.cpu.interrupt_disable_flag else "i", + "Z" if self.cpu.zero_flag else "z", + "C" if self.cpu.carry_flag else "c", + )) + + def cmd_quit(self, args): + self.cpu.quit = True + self.sock.send("quitting\n") + + def cmd_reset(self, args): + self.cpu.reset() + self.cpu.running = True + self.sock.send("resetting\n") + def handle_read(self): buf = self.sock.recv(1024) if not buf: @@ -348,39 +386,9 @@ class ControlHandler: s = self.buffer[:i].strip() self.buffer = self.buffer[i+1:] a = s.split() - if a[0] == "help": - self.sock.send("commands: help, peek, poke, status, quit, reset\n") - elif a[0] == "peek": - addr = int(a[1]) - self.sock.send("%02X\n" % self.cpu.memory.read_byte(self.cpu.cycles, addr)) - elif a[0] == "poke": - addr = int(a[1]) - val = int(a[2]) - self.cpu.memory.write_byte(self.cpu.cycles, addr, val) - self.sock.send("poked\n") - elif a[0] == "status": - self.sock.send("A=%02X X=%02X Y=%02X S=%02X PC=%04X F=%c%c0%c%c%c%c%c\n" % ( - self.cpu.accumulator, - self.cpu.x_index, - self.cpu.y_index, - self.cpu.stack_pointer, - self.cpu.program_counter, - "N" if self.cpu.sign_flag else "n", - "V" if self.cpu.overflow_flag else "v", - "B" if self.cpu.break_flag else "b", - "D" if self.cpu.decimal_mode_flag else "d", - "I" if self.cpu.interrupt_disable_flag else "i", - "Z" if self.cpu.zero_flag else "z", - "C" if self.cpu.carry_flag else "c", - )) - elif a[0] == "quit": - self.cpu.quit = True - self.sock.send("quitting\n") - elif a[0] == "reset": - self.cpu.reset() - self.cpu.running = True - self.sock.send("resetting\n") - else: + try: + getattr(self, "cmd_" + a[0])(a) + except AttributeError: self.sock.send("unknown command\n") From aee0bba7aa978bf49cde9b0c63931bb2875e5b33 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Sat, 20 Aug 2011 13:59:36 +1200 Subject: [PATCH 14/22] add dump memory command --- cpu6502.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cpu6502.py b/cpu6502.py index 52bc32a..3380b5f 100644 --- a/cpu6502.py +++ b/cpu6502.py @@ -335,6 +335,11 @@ class ControlHandler: self.sock.send("ApplePy 6502 core\n") self.buffer = "" + def cmd_dump(self, args): + addr = int(args[1]) + length = int(args[2]) + self.sock.send(" ".join("%02X" % self.cpu.memory.read_byte(self.cpu.cycles, x) for x in range(addr, addr + length)) + "\n") + def cmd_help(self, args): self.sock.send("commands: %s\n" % ", ".join(sorted(x[4:] for x in dir(self) if x.startswith("cmd_")))) From 29b1342a4743d0cc92847c8f4f34f1f5f5565ea1 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Sat, 20 Aug 2011 14:17:18 +1200 Subject: [PATCH 15/22] reincarnate disassembler on control channel --- cpu6502.py | 60 +++++++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/cpu6502.py b/cpu6502.py index 3380b5f..e18ec05 100644 --- a/cpu6502.py +++ b/cpu6502.py @@ -271,57 +271,57 @@ class Disassemble: self.ops[0xFE] = ("INC", self.absolute_x_mode) def absolute_mode(self, pc): - a = self.memory.read_word(pc + 1) - return "$%04X [%04X] = %02X" % (a, a, self.memory.read_word(a)) + a = self.cpu.read_word(pc + 1) + return "$%04X [%04X] = %02X" % (a, a, self.cpu.read_word(a)) def absolute_x_mode(self, pc): - a = self.memory.read_word(pc + 1) + a = self.cpu.read_word(pc + 1) e = a + self.cpu.x_index - return "$%04X,X [%04X] = %02X" % (a, e, self.memory.read_byte(e)) + return "$%04X,X [%04X] = %02X" % (a, e, self.cpu.read_byte(e)) def absolute_y_mode(self, pc): - a = self.memory.read_word(pc + 1) + a = self.cpu.read_word(pc + 1) e = a + self.cpu.y_index - return "$%04X,Y [%04X] = %02X" % (a, e, self.memory.read_byte(e)) + return "$%04X,Y [%04X] = %02X" % (a, e, self.cpu.read_byte(e)) def immediate_mode(self, pc): - return "#$%02X" % (self.memory.read_byte(pc + 1)) + return "#$%02X" % (self.cpu.read_byte(pc + 1)) def indirect_mode(self, pc): - a = self.memory.read_word(pc + 1) - return "($%04X) [%04X] = %02X" % (a, a, self.memory.read_word(a)) + a = self.cpu.read_word(pc + 1) + return "($%04X) [%04X] = %02X" % (a, a, self.cpu.read_word(a)) def indirect_x_mode(self, pc): - z = self.memory.read_byte(pc + 1) - a = self.memory.read_word((z + self.cpu.x_index) % 0x100) - return "($%02X,X) [%04X] = %02X" % (z, a, self.memory.read_byte(a)) + z = self.cpu.read_byte(pc + 1) + a = self.cpu.read_word((z + self.cpu.x_index) % 0x100) + return "($%02X,X) [%04X] = %02X" % (z, a, self.cpu.read_byte(a)) def indirect_y_mode(self, pc): - z = self.memory.read_byte(pc + 1) - a = self.memory.read_word(z) + self.cpu.y_index - return "($%02X),Y [%04X] = %02X" % (z, a, self.memory.read_byte(a)) + z = self.cpu.read_byte(pc + 1) + a = self.cpu.read_word(z) + self.cpu.y_index + return "($%02X),Y [%04X] = %02X" % (z, a, self.cpu.read_byte(a)) def relative_mode(self, pc): - return "$%04X" % (pc + signed(self.memory.read_byte(pc + 1) + 2)) + return "$%04X" % (pc + signed(self.cpu.read_byte(pc + 1) + 2)) def zero_page_mode(self, pc): - a = self.memory.read_byte(pc + 1) - return "$%02X [%04X] = %02X" % (a, a, self.memory.read_byte(a)) + a = self.cpu.read_byte(pc + 1) + return "$%02X [%04X] = %02X" % (a, a, self.cpu.read_byte(a)) def zero_page_x_mode(self, pc): - z = self.memory.read_byte(pc + 1) + z = self.cpu.read_byte(pc + 1) a = (z + self.cpu.x_index) % 0x100 - return "$%02X,X [%04X] = %02X" % (z, a, self.memory.read_byte(a)) + return "$%02X,X [%04X] = %02X" % (z, a, self.cpu.read_byte(a)) def zero_page_y_mode(self, pc): - z = self.memory.read_byte(pc + 1) + z = self.cpu.read_byte(pc + 1) a = (z + self.cpu.y_index) % 0x100 - return "$%02X,Y [%04X] = %02X" % (z, a, self.memory.read_byte(a)) + return "$%02X,Y [%04X] = %02X" % (z, a, self.cpu.read_byte(a)) def disasm(self, pc): - op = self.memory.read_byte(pc) + op = self.cpu.read_byte(pc) info = self.ops[op] - s = "%02X %s" % (pc, info[0]) + s = "%04X %s" % (pc, info[0]) if len(info) > 1: s += " " + info[1](pc) return s @@ -334,23 +334,28 @@ class ControlHandler: self.sock = sock self.sock.send("ApplePy 6502 core\n") self.buffer = "" + self.disassemble = Disassemble(self.cpu, self.cpu.memory) + + def cmd_disassemble(self, args): + addr = int(args[1]) + self.sock.send(self.disassemble.disasm(addr) + "\n") def cmd_dump(self, args): addr = int(args[1]) length = int(args[2]) - self.sock.send(" ".join("%02X" % self.cpu.memory.read_byte(self.cpu.cycles, x) for x in range(addr, addr + length)) + "\n") + self.sock.send(" ".join("%02X" % self.cpu.read_byte(x) for x in range(addr, addr + length)) + "\n") def cmd_help(self, args): self.sock.send("commands: %s\n" % ", ".join(sorted(x[4:] for x in dir(self) if x.startswith("cmd_")))) def cmd_peek(self, args): addr = int(args[1]) - self.sock.send("%02X\n" % self.cpu.memory.read_byte(self.cpu.cycles, addr)) + self.sock.send("%02X\n" % self.cpu.read_byte(addr)) def cmd_poke(self, args): addr = int(args[1]) val = int(args[2]) - self.cpu.memory.write_byte(self.cpu.cycles, addr, val) + self.cpu.write_byte(addr, val) self.sock.send("poked\n") def cmd_status(self, args): @@ -404,7 +409,6 @@ class CPU: def __init__(self, memory): self.memory = memory - self.disassemble = Disassemble(self, memory) self.control_listener = socket.socket() self.control_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) From 0b86a8693f5e4344078321ccf83179ddb70a0c1b Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Sat, 20 Aug 2011 15:37:55 +1200 Subject: [PATCH 16/22] disassemble show instruction bytes --- cpu6502.py | 314 +++++++++++++++++++++++++++-------------------------- 1 file changed, 160 insertions(+), 154 deletions(-) diff --git a/cpu6502.py b/cpu6502.py index e18ec05..54854ef 100644 --- a/cpu6502.py +++ b/cpu6502.py @@ -118,157 +118,157 @@ class Disassemble: def setup_ops(self): self.ops = [None] * 0x100 - self.ops[0x00] = ("BRK", ) - self.ops[0x01] = ("ORA", self.indirect_x_mode) - self.ops[0x05] = ("ORA", self.zero_page_mode) - self.ops[0x06] = ("ASL", self.zero_page_mode) - self.ops[0x08] = ("PHP", ) - self.ops[0x09] = ("ORA", self.immediate_mode) - self.ops[0x0A] = ("ASL", ) - self.ops[0x0D] = ("ORA", self.absolute_mode) - self.ops[0x0E] = ("ASL", self.absolute_mode) - self.ops[0x10] = ("BPL", self.relative_mode) - self.ops[0x11] = ("ORA", self.indirect_y_mode) - self.ops[0x15] = ("ORA", self.zero_page_x_mode) - self.ops[0x16] = ("ASL", self.zero_page_x_mode) - self.ops[0x18] = ("CLC", ) - self.ops[0x19] = ("ORA", self.absolute_y_mode) - self.ops[0x1D] = ("ORA", self.absolute_x_mode) - self.ops[0x1E] = ("ASL", self.absolute_x_mode) - self.ops[0x20] = ("JSR", self.absolute_mode) - self.ops[0x21] = ("AND", self.indirect_x_mode) - self.ops[0x24] = ("BIT", self.zero_page_mode) - self.ops[0x25] = ("AND", self.zero_page_mode) - self.ops[0x26] = ("ROL", self.zero_page_mode) - self.ops[0x28] = ("PLP", ) - self.ops[0x29] = ("AND", self.immediate_mode) - self.ops[0x2A] = ("ROL", ) - self.ops[0x2C] = ("BIT", self.absolute_mode) - self.ops[0x2D] = ("AND", self.absolute_mode) - self.ops[0x2E] = ("ROL", self.absolute_mode) - self.ops[0x30] = ("BMI", self.relative_mode) - self.ops[0x31] = ("AND", self.indirect_y_mode) - self.ops[0x35] = ("AND", self.zero_page_x_mode) - self.ops[0x36] = ("ROL", self.zero_page_x_mode) - self.ops[0x38] = ("SEC", ) - self.ops[0x39] = ("AND", self.absolute_y_mode) - self.ops[0x3D] = ("AND", self.absolute_x_mode) - self.ops[0x3E] = ("ROL", self.absolute_x_mode) - self.ops[0x40] = ("RTI", ) - self.ops[0x41] = ("EOR", self.indirect_x_mode) - self.ops[0x45] = ("EOR", self.zero_page_mode) - self.ops[0x46] = ("LSR", self.zero_page_mode) - self.ops[0x48] = ("PHA", ) - self.ops[0x49] = ("EOR", self.immediate_mode) - self.ops[0x4A] = ("LSR", ) - self.ops[0x4C] = ("JMP", self.absolute_mode) - self.ops[0x4D] = ("EOR", self.absolute_mode) - self.ops[0x4E] = ("LSR", self.absolute_mode) - self.ops[0x50] = ("BVC", self.relative_mode) - self.ops[0x51] = ("EOR", self.indirect_y_mode) - self.ops[0x55] = ("EOR", self.zero_page_x_mode) - self.ops[0x56] = ("LSR", self.zero_page_x_mode) - self.ops[0x58] = ("CLI", ) - self.ops[0x59] = ("EOR", self.absolute_y_mode) - self.ops[0x5D] = ("EOR", self.absolute_x_mode) - self.ops[0x5E] = ("LSR", self.absolute_x_mode) - self.ops[0x60] = ("RTS", ) - self.ops[0x61] = ("ADC", self.indirect_x_mode) - self.ops[0x65] = ("ADC", self.zero_page_mode) - self.ops[0x66] = ("ROR", self.zero_page_mode) - self.ops[0x68] = ("PLA", ) - self.ops[0x69] = ("ADC", self.immediate_mode) - self.ops[0x6A] = ("ROR", ) - self.ops[0x6C] = ("JMP", self.indirect_mode) - self.ops[0x6D] = ("ADC", self.absolute_mode) - self.ops[0x6E] = ("ROR", self.absolute_mode) - self.ops[0x70] = ("BVS", self.relative_mode) - self.ops[0x71] = ("ADC", self.indirect_y_mode) - self.ops[0x75] = ("ADC", self.zero_page_x_mode) - self.ops[0x76] = ("ROR", self.zero_page_x_mode) - self.ops[0x78] = ("SEI", ) - self.ops[0x79] = ("ADC", self.absolute_y_mode) - self.ops[0x7D] = ("ADC", self.absolute_x_mode) - self.ops[0x7E] = ("ROR", self.absolute_x_mode) - self.ops[0x81] = ("STA", self.indirect_x_mode) - self.ops[0x84] = ("STY", self.zero_page_mode) - self.ops[0x85] = ("STA", self.zero_page_mode) - self.ops[0x86] = ("STX", self.zero_page_mode) - self.ops[0x88] = ("DEY", ) - self.ops[0x8A] = ("TXA", ) - self.ops[0x8C] = ("STY", self.absolute_mode) - self.ops[0x8D] = ("STA", self.absolute_mode) - self.ops[0x8E] = ("STX", self.absolute_mode) - self.ops[0x90] = ("BCC", self.relative_mode) - self.ops[0x91] = ("STA", self.indirect_y_mode) - self.ops[0x94] = ("STY", self.zero_page_x_mode) - self.ops[0x95] = ("STA", self.zero_page_x_mode) - self.ops[0x96] = ("STX", self.zero_page_y_mode) - self.ops[0x98] = ("TYA", ) - self.ops[0x99] = ("STA", self.absolute_y_mode) - self.ops[0x9A] = ("TXS", ) - self.ops[0x9D] = ("STA", self.absolute_x_mode) - self.ops[0xA0] = ("LDY", self.immediate_mode) - self.ops[0xA1] = ("LDA", self.indirect_x_mode) - self.ops[0xA2] = ("LDX", self.immediate_mode) - self.ops[0xA4] = ("LDY", self.zero_page_mode) - self.ops[0xA5] = ("LDA", self.zero_page_mode) - self.ops[0xA6] = ("LDX", self.zero_page_mode) - self.ops[0xA8] = ("TAY", ) - self.ops[0xA9] = ("LDA", self.immediate_mode) - self.ops[0xAA] = ("TAX", ) - self.ops[0xAC] = ("LDY", self.absolute_mode) - self.ops[0xAD] = ("LDA", self.absolute_mode) - self.ops[0xAE] = ("LDX", self.absolute_mode) - self.ops[0xB0] = ("BCS", self.relative_mode) - self.ops[0xB1] = ("LDA", self.indirect_y_mode) - self.ops[0xB4] = ("LDY", self.zero_page_x_mode) - self.ops[0xB5] = ("LDA", self.zero_page_x_mode) - self.ops[0xB6] = ("LDX", self.zero_page_y_mode) - self.ops[0xB8] = ("CLV", ) - self.ops[0xB9] = ("LDA", self.absolute_y_mode) - self.ops[0xBA] = ("TSX", ) - self.ops[0xBC] = ("LDY", self.absolute_x_mode) - self.ops[0xBD] = ("LDA", self.absolute_x_mode) - self.ops[0xBE] = ("LDX", self.absolute_y_mode) - self.ops[0xC0] = ("CPY", self.immediate_mode) - self.ops[0xC1] = ("CMP", self.indirect_x_mode) - self.ops[0xC4] = ("CPY", self.zero_page_mode) - self.ops[0xC5] = ("CMP", self.zero_page_mode) - self.ops[0xC6] = ("DEC", self.zero_page_mode) - self.ops[0xC8] = ("INY", ) - self.ops[0xC9] = ("CMP", self.immediate_mode) - self.ops[0xCA] = ("DEX", ) - self.ops[0xCC] = ("CPY", self.absolute_mode) - self.ops[0xCD] = ("CMP", self.absolute_mode) - self.ops[0xCE] = ("DEC", self.absolute_mode) - self.ops[0xD0] = ("BNE", self.relative_mode) - self.ops[0xD1] = ("CMP", self.indirect_y_mode) - self.ops[0xD5] = ("CMP", self.zero_page_x_mode) - self.ops[0xD6] = ("DEC", self.zero_page_x_mode) - self.ops[0xD8] = ("CLD", ) - self.ops[0xD9] = ("CMP", self.absolute_y_mode) - self.ops[0xDD] = ("CMP", self.absolute_x_mode) - self.ops[0xDE] = ("DEC", self.absolute_x_mode) - self.ops[0xE0] = ("CPX", self.immediate_mode) - self.ops[0xE1] = ("SBC", self.indirect_x_mode) - self.ops[0xE4] = ("CPX", self.zero_page_mode) - self.ops[0xE5] = ("SBC", self.zero_page_mode) - self.ops[0xE6] = ("INC", self.zero_page_mode) - self.ops[0xE8] = ("INX", ) - self.ops[0xE9] = ("SBC", self.immediate_mode) - self.ops[0xEA] = ("NOP", ) - self.ops[0xEC] = ("CPX", self.absolute_mode) - self.ops[0xED] = ("SBC", self.absolute_mode) - self.ops[0xEE] = ("INC", self.absolute_mode) - self.ops[0xF0] = ("BEQ", self.relative_mode) - self.ops[0xF1] = ("SBC", self.indirect_y_mode) - self.ops[0xF5] = ("SBC", self.zero_page_x_mode) - self.ops[0xF6] = ("INC", self.zero_page_x_mode) - self.ops[0xF8] = ("SED", ) - self.ops[0xF9] = ("SBC", self.absolute_y_mode) - self.ops[0xFD] = ("SBC", self.absolute_x_mode) - self.ops[0xFE] = ("INC", self.absolute_x_mode) + self.ops[0x00] = (1, "BRK", ) + self.ops[0x01] = (2, "ORA", self.indirect_x_mode) + self.ops[0x05] = (2, "ORA", self.zero_page_mode) + self.ops[0x06] = (2, "ASL", self.zero_page_mode) + self.ops[0x08] = (1, "PHP", ) + self.ops[0x09] = (2, "ORA", self.immediate_mode) + self.ops[0x0A] = (1, "ASL", ) + self.ops[0x0D] = (3, "ORA", self.absolute_mode) + self.ops[0x0E] = (3, "ASL", self.absolute_mode) + self.ops[0x10] = (2, "BPL", self.relative_mode) + self.ops[0x11] = (2, "ORA", self.indirect_y_mode) + self.ops[0x15] = (2, "ORA", self.zero_page_x_mode) + self.ops[0x16] = (2, "ASL", self.zero_page_x_mode) + self.ops[0x18] = (1, "CLC", ) + self.ops[0x19] = (3, "ORA", self.absolute_y_mode) + self.ops[0x1D] = (3, "ORA", self.absolute_x_mode) + self.ops[0x1E] = (3, "ASL", self.absolute_x_mode) + self.ops[0x20] = (3, "JSR", self.absolute_mode) + self.ops[0x21] = (2, "AND", self.indirect_x_mode) + self.ops[0x24] = (2, "BIT", self.zero_page_mode) + self.ops[0x25] = (2, "AND", self.zero_page_mode) + self.ops[0x26] = (2, "ROL", self.zero_page_mode) + self.ops[0x28] = (1, "PLP", ) + self.ops[0x29] = (2, "AND", self.immediate_mode) + self.ops[0x2A] = (1, "ROL", ) + self.ops[0x2C] = (3, "BIT", self.absolute_mode) + self.ops[0x2D] = (3, "AND", self.absolute_mode) + self.ops[0x2E] = (3, "ROL", self.absolute_mode) + self.ops[0x30] = (2, "BMI", self.relative_mode) + self.ops[0x31] = (2, "AND", self.indirect_y_mode) + self.ops[0x35] = (2, "AND", self.zero_page_x_mode) + self.ops[0x36] = (2, "ROL", self.zero_page_x_mode) + self.ops[0x38] = (1, "SEC", ) + self.ops[0x39] = (3, "AND", self.absolute_y_mode) + self.ops[0x3D] = (3, "AND", self.absolute_x_mode) + self.ops[0x3E] = (3, "ROL", self.absolute_x_mode) + self.ops[0x40] = (1, "RTI", ) + self.ops[0x41] = (2, "EOR", self.indirect_x_mode) + self.ops[0x45] = (2, "EOR", self.zero_page_mode) + self.ops[0x46] = (2, "LSR", self.zero_page_mode) + self.ops[0x48] = (1, "PHA", ) + self.ops[0x49] = (2, "EOR", self.immediate_mode) + self.ops[0x4A] = (1, "LSR", ) + self.ops[0x4C] = (3, "JMP", self.absolute_mode) + self.ops[0x4D] = (3, "EOR", self.absolute_mode) + self.ops[0x4E] = (3, "LSR", self.absolute_mode) + self.ops[0x50] = (2, "BVC", self.relative_mode) + self.ops[0x51] = (2, "EOR", self.indirect_y_mode) + self.ops[0x55] = (2, "EOR", self.zero_page_x_mode) + self.ops[0x56] = (2, "LSR", self.zero_page_x_mode) + self.ops[0x58] = (1, "CLI", ) + self.ops[0x59] = (3, "EOR", self.absolute_y_mode) + self.ops[0x5D] = (3, "EOR", self.absolute_x_mode) + self.ops[0x5E] = (3, "LSR", self.absolute_x_mode) + self.ops[0x60] = (1, "RTS", ) + self.ops[0x61] = (2, "ADC", self.indirect_x_mode) + self.ops[0x65] = (2, "ADC", self.zero_page_mode) + self.ops[0x66] = (2, "ROR", self.zero_page_mode) + self.ops[0x68] = (1, "PLA", ) + self.ops[0x69] = (2, "ADC", self.immediate_mode) + self.ops[0x6A] = (1, "ROR", ) + self.ops[0x6C] = (3, "JMP", self.indirect_mode) + self.ops[0x6D] = (3, "ADC", self.absolute_mode) + self.ops[0x6E] = (3, "ROR", self.absolute_mode) + self.ops[0x70] = (2, "BVS", self.relative_mode) + self.ops[0x71] = (2, "ADC", self.indirect_y_mode) + self.ops[0x75] = (2, "ADC", self.zero_page_x_mode) + self.ops[0x76] = (2, "ROR", self.zero_page_x_mode) + self.ops[0x78] = (1, "SEI", ) + self.ops[0x79] = (3, "ADC", self.absolute_y_mode) + self.ops[0x7D] = (3, "ADC", self.absolute_x_mode) + self.ops[0x7E] = (3, "ROR", self.absolute_x_mode) + self.ops[0x81] = (2, "STA", self.indirect_x_mode) + self.ops[0x84] = (2, "STY", self.zero_page_mode) + self.ops[0x85] = (2, "STA", self.zero_page_mode) + self.ops[0x86] = (2, "STX", self.zero_page_mode) + self.ops[0x88] = (1, "DEY", ) + self.ops[0x8A] = (1, "TXA", ) + self.ops[0x8C] = (3, "STY", self.absolute_mode) + self.ops[0x8D] = (3, "STA", self.absolute_mode) + self.ops[0x8E] = (3, "STX", self.absolute_mode) + self.ops[0x90] = (2, "BCC", self.relative_mode) + self.ops[0x91] = (2, "STA", self.indirect_y_mode) + self.ops[0x94] = (2, "STY", self.zero_page_x_mode) + self.ops[0x95] = (2, "STA", self.zero_page_x_mode) + self.ops[0x96] = (2, "STX", self.zero_page_y_mode) + self.ops[0x98] = (1, "TYA", ) + self.ops[0x99] = (3, "STA", self.absolute_y_mode) + self.ops[0x9A] = (1, "TXS", ) + self.ops[0x9D] = (3, "STA", self.absolute_x_mode) + self.ops[0xA0] = (2, "LDY", self.immediate_mode) + self.ops[0xA1] = (2, "LDA", self.indirect_x_mode) + self.ops[0xA2] = (2, "LDX", self.immediate_mode) + self.ops[0xA4] = (2, "LDY", self.zero_page_mode) + self.ops[0xA5] = (2, "LDA", self.zero_page_mode) + self.ops[0xA6] = (2, "LDX", self.zero_page_mode) + self.ops[0xA8] = (1, "TAY", ) + self.ops[0xA9] = (2, "LDA", self.immediate_mode) + self.ops[0xAA] = (1, "TAX", ) + self.ops[0xAC] = (3, "LDY", self.absolute_mode) + self.ops[0xAD] = (3, "LDA", self.absolute_mode) + self.ops[0xAE] = (3, "LDX", self.absolute_mode) + self.ops[0xB0] = (2, "BCS", self.relative_mode) + self.ops[0xB1] = (2, "LDA", self.indirect_y_mode) + self.ops[0xB4] = (2, "LDY", self.zero_page_x_mode) + self.ops[0xB5] = (2, "LDA", self.zero_page_x_mode) + self.ops[0xB6] = (2, "LDX", self.zero_page_y_mode) + self.ops[0xB8] = (1, "CLV", ) + self.ops[0xB9] = (3, "LDA", self.absolute_y_mode) + self.ops[0xBA] = (1, "TSX", ) + self.ops[0xBC] = (3, "LDY", self.absolute_x_mode) + self.ops[0xBD] = (3, "LDA", self.absolute_x_mode) + self.ops[0xBE] = (3, "LDX", self.absolute_y_mode) + self.ops[0xC0] = (2, "CPY", self.immediate_mode) + self.ops[0xC1] = (2, "CMP", self.indirect_x_mode) + self.ops[0xC4] = (2, "CPY", self.zero_page_mode) + self.ops[0xC5] = (2, "CMP", self.zero_page_mode) + self.ops[0xC6] = (2, "DEC", self.zero_page_mode) + self.ops[0xC8] = (1, "INY", ) + self.ops[0xC9] = (2, "CMP", self.immediate_mode) + self.ops[0xCA] = (1, "DEX", ) + self.ops[0xCC] = (3, "CPY", self.absolute_mode) + self.ops[0xCD] = (3, "CMP", self.absolute_mode) + self.ops[0xCE] = (3, "DEC", self.absolute_mode) + self.ops[0xD0] = (2, "BNE", self.relative_mode) + self.ops[0xD1] = (2, "CMP", self.indirect_y_mode) + self.ops[0xD5] = (2, "CMP", self.zero_page_x_mode) + self.ops[0xD6] = (2, "DEC", self.zero_page_x_mode) + self.ops[0xD8] = (1, "CLD", ) + self.ops[0xD9] = (3, "CMP", self.absolute_y_mode) + self.ops[0xDD] = (3, "CMP", self.absolute_x_mode) + self.ops[0xDE] = (3, "DEC", self.absolute_x_mode) + self.ops[0xE0] = (2, "CPX", self.immediate_mode) + self.ops[0xE1] = (2, "SBC", self.indirect_x_mode) + self.ops[0xE4] = (2, "CPX", self.zero_page_mode) + self.ops[0xE5] = (2, "SBC", self.zero_page_mode) + self.ops[0xE6] = (2, "INC", self.zero_page_mode) + self.ops[0xE8] = (1, "INX", ) + self.ops[0xE9] = (2, "SBC", self.immediate_mode) + self.ops[0xEA] = (1, "NOP", ) + self.ops[0xEC] = (3, "CPX", self.absolute_mode) + self.ops[0xED] = (3, "SBC", self.absolute_mode) + self.ops[0xEE] = (3, "INC", self.absolute_mode) + self.ops[0xF0] = (2, "BEQ", self.relative_mode) + self.ops[0xF1] = (2, "SBC", self.indirect_y_mode) + self.ops[0xF5] = (2, "SBC", self.zero_page_x_mode) + self.ops[0xF6] = (2, "INC", self.zero_page_x_mode) + self.ops[0xF8] = (1, "SED", ) + self.ops[0xF9] = (3, "SBC", self.absolute_y_mode) + self.ops[0xFD] = (3, "SBC", self.absolute_x_mode) + self.ops[0xFE] = (3, "INC", self.absolute_x_mode) def absolute_mode(self, pc): a = self.cpu.read_word(pc + 1) @@ -321,9 +321,15 @@ class Disassemble: def disasm(self, pc): op = self.cpu.read_byte(pc) info = self.ops[op] - s = "%04X %s" % (pc, info[0]) - if len(info) > 1: - s += " " + info[1](pc) + s = "%04X " % (pc) + for i in range(3): + if i < info[0]: + s += "%02X " % self.cpu.read_byte(pc + i) + else: + s += " " + s += " %s" % (info[1]) + if len(info) > 2: + s += " " + info[2](pc) return s From 0604bd1515fee615a66eae1734b8492320067a45 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Sat, 20 Aug 2011 18:22:33 +1200 Subject: [PATCH 17/22] add fileno() method to ControlHandler for better compatiblity with select() --- cpu6502.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cpu6502.py b/cpu6502.py index 54854ef..89e41fc 100644 --- a/cpu6502.py +++ b/cpu6502.py @@ -389,6 +389,9 @@ class ControlHandler: self.cpu.running = True self.sock.send("resetting\n") + def fileno(self): + return self.sock.fileno() + def handle_read(self): buf = self.sock.recv(1024) if not buf: @@ -610,15 +613,14 @@ class CPU: timeout = 0 if not self.running: timeout = 1 - sockets = [self.control_listener] + [x.sock for x in self.control] + sockets = [self.control_listener] + self.control rs, _, _ = select.select(sockets, [], [], timeout) for s in rs: if s is self.control_listener: cs, _ = self.control_listener.accept() self.control.append(ControlHandler(self, cs)) else: - c = [x for x in self.control if x.sock is s][0] - c.handle_read() + s.handle_read() count = 1000 while count > 0 and self.running: From 80e95d114b32b6e79649468ceb1d0d09e07d0424 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Sat, 20 Aug 2011 21:46:14 +1200 Subject: [PATCH 18/22] control channel is now HTTP/REST/JSON --- cpu6502.py | 246 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 155 insertions(+), 91 deletions(-) diff --git a/cpu6502.py b/cpu6502.py index 89e41fc..74d1929 100644 --- a/cpu6502.py +++ b/cpu6502.py @@ -3,6 +3,9 @@ # originally written 2001, updated 2011 +import BaseHTTPServer +import json +import re import select import socket import struct @@ -117,7 +120,7 @@ class Disassemble: self.setup_ops() def setup_ops(self): - self.ops = [None] * 0x100 + self.ops = [(1, "???")] * 0x100 self.ops[0x00] = (1, "BRK", ) self.ops[0x01] = (2, "ORA", self.indirect_x_mode) self.ops[0x05] = (2, "ORA", self.zero_page_mode) @@ -272,143 +275,205 @@ class Disassemble: def absolute_mode(self, pc): a = self.cpu.read_word(pc + 1) - return "$%04X [%04X] = %02X" % (a, a, self.cpu.read_word(a)) + return { + "operand": "$%04X" % a, + "memory": [a, 2, self.cpu.read_word(a)], + } def absolute_x_mode(self, pc): a = self.cpu.read_word(pc + 1) e = a + self.cpu.x_index - return "$%04X,X [%04X] = %02X" % (a, e, self.cpu.read_byte(e)) + return { + "operand": "$%04X,X" % a, + "memory": [e, 1, self.cpu.read_byte(e)], + } def absolute_y_mode(self, pc): a = self.cpu.read_word(pc + 1) e = a + self.cpu.y_index - return "$%04X,Y [%04X] = %02X" % (a, e, self.cpu.read_byte(e)) + return { + "operand": "$%04X,Y" % a, + "memory": [e, 1, self.cpu.read_byte(e)], + } def immediate_mode(self, pc): - return "#$%02X" % (self.cpu.read_byte(pc + 1)) + return { + "operand": "#$%02X" % (self.cpu.read_byte(pc + 1)), + } def indirect_mode(self, pc): a = self.cpu.read_word(pc + 1) - return "($%04X) [%04X] = %02X" % (a, a, self.cpu.read_word(a)) + return { + "operand": "($%04X)" % a, + "memory": [a, 2, self.cpu.read_word(a)], + } def indirect_x_mode(self, pc): z = self.cpu.read_byte(pc + 1) a = self.cpu.read_word((z + self.cpu.x_index) % 0x100) - return "($%02X,X) [%04X] = %02X" % (z, a, self.cpu.read_byte(a)) + return { + "operand": "($%02X,X)" % z, + "memory": [a, 1, self.cpu.read_byte(a)], + } def indirect_y_mode(self, pc): z = self.cpu.read_byte(pc + 1) a = self.cpu.read_word(z) + self.cpu.y_index - return "($%02X),Y [%04X] = %02X" % (z, a, self.cpu.read_byte(a)) + return { + "operand": "($%02X),Y" % z, + "memory": [a, 1, self.cpu.read_byte(a)], + } def relative_mode(self, pc): - return "$%04X" % (pc + signed(self.cpu.read_byte(pc + 1) + 2)) + return { + "operand": "$%04X" % (pc + signed(self.cpu.read_byte(pc + 1) + 2)), + } def zero_page_mode(self, pc): a = self.cpu.read_byte(pc + 1) - return "$%02X [%04X] = %02X" % (a, a, self.cpu.read_byte(a)) + return { + "operand": "$%02X" % a, + "memory": [a, 1, self.cpu.read_byte(a)], + } def zero_page_x_mode(self, pc): z = self.cpu.read_byte(pc + 1) a = (z + self.cpu.x_index) % 0x100 - return "$%02X,X [%04X] = %02X" % (z, a, self.cpu.read_byte(a)) + return { + "operand": "$%02X,X" % z, + "memory": [a, 1, self.cpu.read_byte(a)], + } def zero_page_y_mode(self, pc): z = self.cpu.read_byte(pc + 1) a = (z + self.cpu.y_index) % 0x100 - return "$%02X,Y [%04X] = %02X" % (z, a, self.cpu.read_byte(a)) + return { + "operand": "$%02X,Y" % z, + "memory": [a, 1, self.cpu.read_byte(a)], + } def disasm(self, pc): op = self.cpu.read_byte(pc) info = self.ops[op] - s = "%04X " % (pc) - for i in range(3): - if i < info[0]: - s += "%02X " % self.cpu.read_byte(pc + i) - else: - s += " " - s += " %s" % (info[1]) + r = { + "address": pc, + "bytes": [self.cpu.read_byte(pc + i) for i in range(info[0])], + "mnemonic": info[1], + } if len(info) > 2: - s += " " + info[2](pc) - return s + r.update(info[2](pc)) + return r, info[0] -class ControlHandler: +class ControlHandler(BaseHTTPServer.BaseHTTPRequestHandler): - def __init__(self, cpu, sock): + def __init__(self, request, client_address, server, cpu): self.cpu = cpu - self.sock = sock - self.sock.send("ApplePy 6502 core\n") - self.buffer = "" self.disassemble = Disassemble(self.cpu, self.cpu.memory) - def cmd_disassemble(self, args): - addr = int(args[1]) - self.sock.send(self.disassemble.disasm(addr) + "\n") + self.get_urls = { + r"/disassemble/(\d+)$": self.get_disassemble, + r"/memory/(\d+)(-(\d+))?$": self.get_memory, + r"/memory/(\d+)(-(\d+))?/raw$": self.get_memory_raw, + r"/status$": self.get_status, + } - def cmd_dump(self, args): - addr = int(args[1]) - length = int(args[2]) - self.sock.send(" ".join("%02X" % self.cpu.read_byte(x) for x in range(addr, addr + length)) + "\n") + self.post_urls = { + #r"/memory/(\d+)(-(\d+))?$": self.post_memory, + #r"/memory/(\d+)(-(\d+))?/raw$": self.post_memory_raw, + r"/quit$": self.post_quit, + r"/reset$": self.post_reset, + } - def cmd_help(self, args): - self.sock.send("commands: %s\n" % ", ".join(sorted(x[4:] for x in dir(self) if x.startswith("cmd_")))) + BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request, client_address, server) - def cmd_peek(self, args): - addr = int(args[1]) - self.sock.send("%02X\n" % self.cpu.read_byte(addr)) + def log_request(self, code, size=0): + pass - def cmd_poke(self, args): - addr = int(args[1]) - val = int(args[2]) - self.cpu.write_byte(addr, val) - self.sock.send("poked\n") + def dispatch(self, urls): + for r, f in urls.items(): + m = re.match(r, self.path) + if m is not None: + f(m) + break + else: + self.send_response(404) + self.end_headers() - def cmd_status(self, args): - self.sock.send("A=%02X X=%02X Y=%02X S=%02X PC=%04X F=%c%c0%c%c%c%c%c\n" % ( - self.cpu.accumulator, - self.cpu.x_index, - self.cpu.y_index, - self.cpu.stack_pointer, - self.cpu.program_counter, - "N" if self.cpu.sign_flag else "n", - "V" if self.cpu.overflow_flag else "v", - "B" if self.cpu.break_flag else "b", - "D" if self.cpu.decimal_mode_flag else "d", - "I" if self.cpu.interrupt_disable_flag else "i", - "Z" if self.cpu.zero_flag else "z", - "C" if self.cpu.carry_flag else "c", - )) + def response(self, s): + self.send_response(200) + self.send_header("Content-Length", str(len(s))) + self.end_headers() + self.wfile.write(s) - def cmd_quit(self, args): + def do_GET(self): + self.dispatch(self.get_urls) + + def do_POST(self): + self.dispatch(self.post_urls) + + def get_disassemble(self, m): + addr = int(m.group(1)) + r = [] + n = 20 + while n > 0: + dis, length = self.disassemble.disasm(addr) + r.append(dis) + addr += length + n -= 1 + self.response(json.dumps(r)) + + def get_memory_raw(self, m): + addr = int(m.group(1)) + e = m.group(3) + if e is not None: + end = int(e) + else: + end = addr + self.response("".join([chr(self.cpu.read_byte(x)) for x in range(addr, end + 1)])) + + def get_memory(self, m): + addr = int(m.group(1)) + e = m.group(3) + if e is not None: + end = int(e) + else: + end = addr + self.response(json.dumps(list(map(self.cpu.read_byte, range(addr, end + 1))))) + + def get_status(self, m): + self.response(json.dumps(dict((x, getattr(self.cpu, x)) for x in ( + "accumulator", + "x_index", + "y_index", + "stack_pointer", + "program_counter", + "sign_flag", + "overflow_flag", + "break_flag", + "decimal_mode_flag", + "interrupt_disable_flag", + "zero_flag", + "carry_flag", + )))) + + def post_quit(self, m): self.cpu.quit = True - self.sock.send("quitting\n") + self.response("") - def cmd_reset(self, args): + def post_reset(self, m): self.cpu.reset() self.cpu.running = True - self.sock.send("resetting\n") + self.response("") - def fileno(self): - return self.sock.fileno() - def handle_read(self): - buf = self.sock.recv(1024) - if not buf: - self.cpu.control.remove(self) - return - self.buffer += buf - while True: - i = self.buffer.find("\n") - if i < 0: - break - s = self.buffer[:i].strip() - self.buffer = self.buffer[i+1:] - a = s.split() - try: - getattr(self, "cmd_" + a[0])(a) - except AttributeError: - self.sock.send("unknown command\n") +class ControlHandlerFactory: + + def __init__(self, cpu): + self.cpu = cpu + + def __call__(self, request, client_address, server): + return ControlHandler(request, client_address, server, self.cpu) class CPU: @@ -419,11 +484,7 @@ class CPU: def __init__(self, memory): self.memory = memory - self.control_listener = socket.socket() - self.control_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) - self.control_listener.bind(("127.0.0.1", 6502)) - self.control_listener.listen(1) - self.control = [] + self.control_server = BaseHTTPServer.HTTPServer(("127.0.0.1", 6502), ControlHandlerFactory(self)) self.accumulator = 0x00 self.x_index = 0x00 @@ -613,14 +674,17 @@ class CPU: timeout = 0 if not self.running: timeout = 1 - sockets = [self.control_listener] + self.control + # Currently this handler blocks from the moment + # a connection is accepted until the response + # is sent. TODO: use an async HTTP server that + # handles input data asynchronously. + sockets = [self.control_server] rs, _, _ = select.select(sockets, [], [], timeout) for s in rs: - if s is self.control_listener: - cs, _ = self.control_listener.accept() - self.control.append(ControlHandler(self, cs)) + if s is self.control_server: + self.control_server._handle_request_noblock() else: - s.handle_read() + pass count = 1000 while count > 0 and self.running: From 6782a1d268f3f640be305b20a8140f37973fb8e3 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Sun, 21 Aug 2011 18:29:33 +1200 Subject: [PATCH 19/22] implement post to /memory in control requests --- cpu6502.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/cpu6502.py b/cpu6502.py index 74d1929..0f611b9 100644 --- a/cpu6502.py +++ b/cpu6502.py @@ -379,8 +379,8 @@ class ControlHandler(BaseHTTPServer.BaseHTTPRequestHandler): } self.post_urls = { - #r"/memory/(\d+)(-(\d+))?$": self.post_memory, - #r"/memory/(\d+)(-(\d+))?/raw$": self.post_memory_raw, + r"/memory/(\d+)(-(\d+))?$": self.post_memory, + r"/memory/(\d+)(-(\d+))?/raw$": self.post_memory_raw, r"/quit$": self.post_quit, r"/reset$": self.post_reset, } @@ -457,6 +457,30 @@ class ControlHandler(BaseHTTPServer.BaseHTTPRequestHandler): "carry_flag", )))) + def post_memory(self, m): + addr = int(m.group(1)) + e = m.group(3) + if e is not None: + end = int(e) + else: + end = addr + data = json.loads(self.rfile.read(int(self.headers["Content-Length"]))) + for i, a in enumerate(range(addr, end + 1)): + self.cpu.write_byte(a, data[i]) + self.response("") + + def post_memory_raw(self, m): + addr = int(m.group(1)) + e = m.group(3) + if e is not None: + end = int(e) + else: + end = addr + data = self.rfile.read(int(self.headers["Content-Length"])) + for i, a in enumerate(range(addr, end + 1)): + self.cpu.write_byte(a, data[i]) + self.response("") + def post_quit(self, m): self.cpu.quit = True self.response("") From 60acf3715031f06f61adf4994fa20cf02f7f1443 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Sun, 21 Aug 2011 18:30:49 +1200 Subject: [PATCH 20/22] add console control utility --- control.py | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 control.py diff --git a/control.py b/control.py new file mode 100644 index 0000000..115f44c --- /dev/null +++ b/control.py @@ -0,0 +1,157 @@ +import json +import readline +import sys +import urllib + +URL_PREFIX = "http://localhost:6502" + +def get(url): + return json.loads(urllib.urlopen(URL_PREFIX + url).read()) + +def post(url, data=None): + return urllib.urlopen(URL_PREFIX + url, json.dumps(data) if data is not None else "") + +def value(s): + if s.startswith("$"): + return int(s[1:], 16) + if s.startswith("0x"): + return int(s[2:], 16) + return int(s) + +def format_disassemble(dis): + r = "%04X- " % dis["address"] + for i in range(3): + if i < len(dis["bytes"]): + r += "%02X " % dis["bytes"][i] + else: + r += " " + r += " %s" % dis["mnemonic"] + if "operand" in dis: + r += " %-10s" % dis["operand"] + if "memory" in dis: + r += "[%04X] = %0*X" % tuple(dis["memory"]) + return r + +def cmd_disassemble(a): + """Disassemble""" + if len(a) > 1: + addr = value(a[1]) + else: + status = get("/status") + addr = status["program_counter"] + disasm = get("/disassemble/%d" % addr) + for d in disasm: + print format_disassemble(d) + +def cmd_dump(a): + """Dump memory""" + start = value(a[1]) + if len(a) > 2: + end = value(a[2]) + else: + end = start + 15 + data = get("/memory/%d-%d" % (start, end)) + addr = start & ~0xF + while addr <= end: + s = "%04X-" % addr + for i in range(16): + if start <= addr + i <= end: + s += " %02X" % data[addr + i - start] + else: + s += " " + s += " " + for i in range(16): + if start <= addr + i <= end: + c = data[addr + i - start] + + # adjust for apple character set + c &= 0x3f + if c < 0x20: + c += 0x40 + + if 0x20 <= c < 0x7f: + s += chr(c) + else: + s += "." + else: + s += " " + print s + addr += 16 + +def cmd_help(a): + """Help commands""" + if len(a) > 1: + f = Commands.get(a[1]) + if f is not None: + print f.__doc__ + else: + print "Unknown command:", a[1] + else: + print "Commands:" + for c in sorted(Commands): + print " ", c + +def cmd_peek(a): + """Peek memory location""" + addr = value(a[1]) + dump = get("/memory/%d" % addr) + print "%04X: %02X" % (addr, dump[0]) + +def cmd_poke(a): + """Poke memory location""" + addr = value(a[1]) + val = value(a[2]) + post("/memory/%d" % addr, [val]) + +def cmd_status(a): + """CPU status""" + status = get("/status") + print "A=%02X X=%02X Y=%02X S=%02X PC=%04X F=%c%c0%c%c%c%c%c" % ( + status["accumulator"], + status["x_index"], + status["y_index"], + status["stack_pointer"], + status["program_counter"], + "N" if status["sign_flag"] else "n", + "V" if status["overflow_flag"] else "v", + "B" if status["break_flag"] else "b", + "D" if status["decimal_mode_flag"] else "d", + "I" if status["interrupt_disable_flag"] else "i", + "Z" if status["zero_flag"] else "z", + "C" if status["carry_flag"] else "c", + ) + disasm = get("/disassemble/%d" % status["program_counter"]) + print format_disassemble(disasm[0]) + +def cmd_quit(a): + """Quit""" + sys.exit(0) + +def cmd_reset(a): + """Reset""" + post("/reset") + +Commands = { + "disassemble": cmd_disassemble, + "dump": cmd_dump, + "help": cmd_help, + "peek": cmd_peek, + "poke": cmd_poke, + "status": cmd_status, + "quit": cmd_quit, + "reset": cmd_reset, +} + +def main(): + print "ApplePy control console" + while True: + s = raw_input("6502> ") + a = s.strip().split() + f = Commands.get(a[0]) + if f is not None: + f(a) + else: + print "Unknown command:", s + +if __name__ == "__main__": + main() From 4ee9b846f9aed785b1392ad282cd780fa08edce9 Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Sat, 1 Oct 2011 08:17:38 +1300 Subject: [PATCH 21/22] initialise display state variables in constructor --- applepy.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/applepy.py b/applepy.py index 71e0aef..5bb98bb 100644 --- a/applepy.py +++ b/applepy.py @@ -109,6 +109,10 @@ class Display: self.flash_time = time.time() self.flash_on = False self.flash_chars = [[0] * 0x400] * 2 + + self.page = 1 + self.text = True + self.colour = False self.chargen = [] for c in self.characters: From 9ff2dbb70ec858f71e4f80cf28a0a9826fc3d6da Mon Sep 17 00:00:00 2001 From: Greg Hewgill Date: Sat, 1 Oct 2011 08:18:16 +1300 Subject: [PATCH 22/22] add --pc switch for starting run at specific program counter --- applepy.py | 9 +++++++++ cpu6502.py | 11 +++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/applepy.py b/applepy.py index 5bb98bb..0ed7736 100644 --- a/applepy.py +++ b/applepy.py @@ -377,6 +377,10 @@ class Apple2: args.extend([ "--ram", options.ram, ]) + if options.pc is not None: + args.extend([ + "--pc", str(options.pc), + ]) self.core = subprocess.Popen(args) rs, _, _ = select.select([listener], [], [], 2) @@ -433,6 +437,7 @@ def usage(): print >>sys.stderr, " -c, --cassette Cassette wav file to load" print >>sys.stderr, " -R, --rom ROM file to use (default A2ROM.BIN)" print >>sys.stderr, " -r, --ram RAM file to load (default none)" + print >>sys.stderr, " -p, --pc Initial PC value" print >>sys.stderr, " -q, --quiet Quiet mode, no sounds (default sounds)" sys.exit(1) @@ -443,6 +448,7 @@ def get_options(): self.cassette = None self.rom = "A2ROM.BIN" self.ram = None + self.pc = None self.quiet = False options = Options() @@ -458,6 +464,9 @@ def get_options(): elif sys.argv[a] in ("-r", "--ram"): a += 1 options.ram = sys.argv[a] + elif sys.argv[a] in ("-p", "--pc"): + a += 1 + options.pc = int(sys.argv[a]) elif sys.argv[a] in ("-q", "--quiet"): options.quiet = True else: diff --git a/cpu6502.py b/cpu6502.py index 5fea0f4..c29c4cc 100644 --- a/cpu6502.py +++ b/cpu6502.py @@ -505,7 +505,7 @@ class CPU: STACK_PAGE = 0x100 RESET_VECTOR = 0xFFFC - def __init__(self, memory): + def __init__(self, options, memory): self.memory = memory self.control_server = BaseHTTPServer.HTTPServer(("127.0.0.1", 6502), ControlHandlerFactory(self)) @@ -528,6 +528,8 @@ class CPU: self.setup_ops() self.reset() + if options.pc is not None: + self.program_counter = options.pc self.running = True self.quit = False @@ -1176,6 +1178,7 @@ def usage(): print >>sys.stderr, "Usage: cpu6502.py [options]" print >>sys.stderr print >>sys.stderr, " -b, --bus Bus port number" + print >>sys.stderr, " -p, --pc Initial PC value" print >>sys.stderr, " -R, --rom ROM file to use (default A2ROM.BIN)" print >>sys.stderr, " -r, --ram RAM file to load (default none)" sys.exit(1) @@ -1187,6 +1190,7 @@ def get_options(): self.rom = "A2ROM.BIN" self.ram = None self.bus = None + self.pc = None options = Options() a = 1 @@ -1195,6 +1199,9 @@ def get_options(): if sys.argv[a] in ("-b", "--bus"): a += 1 options.bus = int(sys.argv[a]) + elif sys.argv[a] in ("-p", "--pc"): + a += 1 + options.pc = int(sys.argv[a]) elif sys.argv[a] in ("-R", "--rom"): a += 1 options.rom = sys.argv[a] @@ -1219,5 +1226,5 @@ if __name__ == "__main__": mem = Memory(options) - cpu = CPU(mem) + cpu = CPU(options, mem) cpu.run(options.bus)