1
0
mirror of https://github.com/rzzzwilson/pymlac.git synced 2025-06-10 09:32:41 +00:00
Files
rzzzwilson.pymlac/pymlac/pymlac
2015-07-26 18:23:41 +07:00

571 lines
20 KiB
Python
Executable File

#!/usr/bin/python
"""
The Python Imlac emulator (pymlac).
Console-run version
"""
import sys
import os
import wx
from Globals import *
import Memory
import Ptr
import Ptp
import TtyIn
import TtyOut
import Kbd
import MainCPU
import Display
import DisplayCPU
import Trace
# map ROM name to type
RomTypeMap = {'ptr': ROM_PTR,
'tty': ROM_TTY,
'none': ROM_NONE}
count = 1
# display window
WindowTitleHeight = 22
#DefaultAppSize = (600, 600+WindowTitleHeight)
DefaultAppSize = (1024, 1024+WindowTitleHeight)
def abort(msg):
"""Handle a terminal problem."""
print(msg)
sys.exit(1)
def check_int(value, msg):
"""Check that a value is a valid 16 bit positive value.
We allow decimal and octal number representations.
"""
try:
if str(value)[0] == '0':
data = int(value, 8)
else:
data = int(value)
except:
abort(msg)
if data < 0 or data > 0177777:
abort(msg)
return data
def get_next(options, index, msg):
"""If possible, get next arg in list."""
if index < len(options):
next_arg = options[index]
else:
abort(msg)
return next_arg
def handle_options():
"""Routine to analyse command line options.
Returns a list of operation objects which are tuples:
(OPERATION, ARGS)
"""
options = sys.argv[1:]
index = 0
result = []
boot_rom = 'ptr'
while index < len(options):
arg = options[index]
index += 1
if arg[0] == '-':
if arg == '-b':
next_arg = get_next(options, index,
"-b option must be 'ptr' or 'tty' or 'none'")
index += 1
next_arg = next_arg.lower()
#next_arg = RomTypeMap.get(next_arg, False)
if next_arg:
op = ('boot', next_arg)
boot_rom = next_arg
else:
abort("-b option must be 'ptr', 'tty' or 'none'")
elif arg == '-c':
op = ('clear', boot_rom)
elif arg == '-d':
next_arg = get_next(options, index,
"-d option must be data switch value, positive & max 16 bits")
index += 1
data = check_int(next_arg,
"-d option must be data switch value, positive & max 16 bits")
op = ('data', data)
elif arg == '-h':
usage()
elif arg == '-ptp':
next_arg = get_next(options, index,
"-ptp option must be papertape punch file")
index += 1
if not os.path.isfile(next_arg):
abort("File %s isn't a papertape file" % next_arg)
op = ('load_ptp', next_arg)
elif arg == '-ptr':
next_arg = get_next(options, index,
"-ptr option must be papertape reader file")
index += 1
if not os.path.isfile(next_arg):
abort("File %s isn't a papertape file" % next_arg)
op = ('load_ptr', next_arg)
elif arg == '-r':
next_arg = get_next(options, index,
"-r option must be 'PC' or run address, positive & max 16 bits")
index += 1
next_arg = next_arg.lower()
if next_arg == 'pc':
op = ('run', None)
else:
address = check_int(next_arg,
"-r option must be 'PC' or run address, positive & max 16 bits")
op = ('run', address)
elif arg == '-s':
next_arg = get_next(options, index,
"-s option must be set file")
index += 1
if not os.path.isfile(next_arg):
abort("File %s isn't a 'set' file" % next_arg)
op = ('set', next_arg)
elif arg == '-t':
next_arg = get_next(options, index,
"-t option must be trace range")
index += 1
addr = next_arg.split(',')
if len(addr) == 1:
trace1 = addr[0]
if trace1.lower() == 'off':
trace1 = None
else:
trace1 = check_int(trace1,
"-t option must be trace range")
trace2 = None
elif len(addr) == 2:
trace1 = check_int(addr[0], "-t option must be trace range")
trace2 = check_int(addr[1], "-t option must be trace range")
else:
abort("-t option must be trace range")
op = ('trace', (trace1, trace2))
elif arg == '-ttyin':
next_arg = get_next(options, index,
"-ttyin option must be teletype input file")
index += 1
if not os.path.isfile(next_arg):
abort("File %s isn't a teletype file" % next_arg)
op = ('load_ttyin', next_arg)
elif arg == '-ttyout':
next_arg = get_next(options, index,
"-ttyout option must be teletype output file")
index += 1
if not os.path.isfile(next_arg):
abort("File %s isn't a teletype file" % next_arg)
op = ('load_ttyout', next_arg)
elif arg == '-v':
next_arg = get_next(options, index,
"-v option must be view file")
index += 1
if not os.path.isfile(next_arg):
abort("File %s isn't a 'view' file" % next_arg)
op = ('view', next_arg)
elif arg == '-w':
next_arg = get_next(options, index,
"-W option must be ON|OFF")
index += 1
next_arg = next_arg.lower()
if next_arg in ['on', 'off']:
op = ('write', next_arg)
else:
abort("-w option must be 'ON' or 'OFF'")
elif arg == '-x':
next_arg = get_next(options, index,
"-x option must be dump range")
index += 1
addr = next_arg.split(',')
if len(addr) == 1:
abort("-x option must be dump range: lower,higher")
elif len(addr) == 2:
lower = check_int(addr[0], "-x option must be dump range: lower,higher")
higher = check_int(addr[1], "-x option must be dump range: lower,higher")
else:
abort("-t option must be dump range: lower,higher")
op = ('x', (lower, higher))
else:
usage()
else:
usage()
result.append(op)
return result
def usage():
"""Help for befuddled user."""
print('Usage:\n')
print('pymlac [-b (ptr|tty|none)] [-c] [-t [<a>[,<b>]]] [-r <address>] [<imlacfile>]\n')
print('where -b (ptr|tty|none) sets the bootstrap ROM code, if no device disable ROM')
print(' -c clear core, except bootstrap ROM')
print(' -d <value> set data switches')
print(' -h prints this help')
print(' -ptp <file> mount <file> on the papertape punch')
print(' -ptr <file> mount <file> on the papertape reader')
print(' -r (<address>|PC) execute from the <address>, no address runs from current PC')
print(' -s <setfile> sets memory values from file')
print(' -t (<addr1>[,<addr2>]|off) controls trace:')
print(' -t 100 trace from address 0100')
print(' -t 100,200 traces between addresses 100 and 200')
print(' -t off trace OFF')
print(' -ttyin <file> mount <file> on the teletype reader')
print(' -ttyout <file> mount <file> on the teletype writer')
print(' -v <viewfile> prints memory locations from file')
print(' -w (on|off) turn ROM writability ON or OFF')
print(' -x <low>,<high> dumps memory to file:')
print(" -x 100,200 dumps memory between 100 and 200 to 'x.dump'")
sys.exit()
def main():
"""Start of the emulator."""
# get operations list
ops = handle_options()
# Initialize the emulator.
boot_rom = 'ptr' # default ROM loader
Imlac.init(0, TRACE_FILENAME, None, None, boot_rom, CORE_FILENAME)
# now perform operations
for (operation, args) in ops:
if operation == 'boot':
Memory.set_ROM(args)
Trace.comment('Bootstrap ROM set to %s' % args.upper())
elif operation == 'clear':
Memory.clear_core()
Trace.comment('Core cleared')
elif operation == 'load_ptp':
Ptp.mount(args)
Trace.comment("File '%s' mounted on PTP" % args)
elif operation == 'load_ptr':
Ptr.mount(args)
Trace.comment("File '%s' mounted on PTR" % args)
elif operation == 'data':
MainCPU.DS = args
Trace.comment('Dataswitch value set to %06o' % args)
elif operation == 'run':
MainCPU.PC = args
Trace.comment('Running from address %06o' % args)
if Imlac.tracestart:
Trace.comment('DPC\tDisplay\t\tPC\tMain\t\tRegs')
Trace.comment('------ ------------- ------ -------------- '
'-----------------------')
Imlac.run()
Trace.comment('Imlac halted')
elif operation == 'set':
Trace.comment("Setting memory from file '%s'" % args)
elif operation == 'trace':
if args == 'off':
Imlac.tracestart = None
Imlac.traceend = None
else:
(start, end) = args
Imlac.tracestart = start
Imlac.traceend = end
tstart = Imlac.tracestart
if tstart is not None:
tstart = '%06o' % tstart
tend = Imlac.traceend
if tend is not None:
tend = '%06o' % tend
Trace.comment('Trace set to (%s, %s)' % (tstart, tend))
elif operation == 'ttyin':
TtyIn.mount(args)
Trace.comment("File '%s' mounted on TTYIN" % args)
elif operation == 'ttyout':
TtyOut.mount(args)
Trace.comment("File '%s' mounted on TTYOUT" % args)
elif operation == 'view':
Trace.comment("Viewing memory from file '%s'" % args)
elif operation == 'write':
if args == 'on':
Memory.using_rom = True
elif args == 'off':
Memory.using_rom = False
else:
abort("Invalid view arg: %s" % args)
Trace.comment('ROM write protect set %s' % args.upper())
elif operation == 'x':
(low, high) = args
Memory.dump(args)
Trace.comment('Memory dumped: %07o to %07o' % (low, high))
else:
abort('Invalid internal operation: %s' % operation)
Imlac.close(CORE_FILENAME)
class PymlacFrame(wx.Frame):
"""A frame containing the pymlac display."""
WIDTH_SCREEN = 1024
HEIGHT_SCREEN = 1024
WIDTH_CONSOLE = 330 # 400 # 256
HEIGHT_CONSOLE = HEIGHT_SCREEN
SCREEN_COLOUR = (0, 0, 0)
CONSOLE_COLOUR = (255, 223, 169)
PHOSPHOR_COLOUR = '#F0F000' # yellow
#PHOSPHOR_COLOUR = '#40FF40' # green
V_MARGIN = 20
CTL_MARGIN = 15
LED_MARGIN = 5
INC = 0.01
def __init__(self, title):
wx.Frame.__init__(self, None, size=DefaultAppSize,
title=('pymlac %s' % '0.1'))
self.SetMinSize(DefaultAppSize)
self.panel = wx.Panel(self, wx.ID_ANY)
self.panel.ClearBackground()
# consruct the Imlac machine
self.ptr = Ptr.Ptr()
self.ptp = Ptp.Ptp()
self.ttyin = TtyIn.TtyIn()
self.ttyout = TtyOut.TtyOut()
self.kbd = Kbd.Kbd()
self.memory = Memory.Memory()
self.display = Display.Display(self.panel)
self.display_cpu = DisplayCPU.DisplayCPU(self.display, self.memory)
self.cpu = MainCPU.MainCPU(self.memory, self.display, self.display_cpu,
self.kbd, self.ttyin, self.ttyout,
self.ptp, self.ptr)
# build the GUI
box = wx.BoxSizer(wx.VERTICAL)
box.Add(self.display, proportion=1, border=1, flag=wx.EXPAND)
self.panel.SetSizer(box)
self.panel.Layout()
self.Centre()
self.Show(True)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.lock = False
self.Refresh()
# start running
self.run_machine()
def OnSize(self, event):
"""Maintain square window."""
if not self.lock:
self.lock = True
(w, h) = event.GetSize()
size = min(w, h)
self.SetSize((size-WindowTitleHeight, size))
self.lock = False
event.Skip()
def run_machine(self):
# get operations list
ops = handle_options()
# Initialize the emulator.
boot_rom = 'ptr' # default ROM loader
self.main_running = False
self.display_running = False
self.tracestart = None
self.traceend = None
self.DS = 0100000 # dataswitches
self.trace_filename = TRACE_FILENAME
self.core_filename = CORE_FILENAME
Trace.init(TRACE_FILENAME, self.cpu, self.display_cpu)
# now perform operations
for (operation, args) in ops:
if operation == 'boot':
self.memory.set_ROM(args)
Trace.comment('Bootstrap ROM set to %s' % args.upper())
self.Refresh()
elif operation == 'clear':
self.memory.clear_core()
Trace.comment('Core cleared')
self.Refresh()
elif operation == 'load_ptp':
self.ptp.mount(args)
Trace.comment("File '%s' mounted on PTP" % args)
self.Refresh()
elif operation == 'load_ptr':
self.ptr.mount(args)
Trace.comment("File '%s' mounted on PTR" % args)
self.Refresh()
elif operation == 'data':
self.cpu.DS = args
Trace.comment('Dataswitch value set to %06o' % args)
self.Refresh()
elif operation == 'run':
self.cpu.PC = args
Trace.comment('Running from address %06o' % args)
if self.tracestart:
Trace.comment('DPC\tDisplay\t\tPC\tMain\t\tRegs')
Trace.comment('------ ------------- ------ -------------- '
'-----------------------')
self.Refresh()
self.run()
self.Refresh()
Trace.comment('Imlac halted')
self.Refresh()
elif operation == 'set':
Trace.comment("Setting memory from file '%s'" % args)
self.Refresh()
elif operation == 'trace':
if args == 'off':
self.tracestart = None
self.traceend = None
else:
(start, end) = args
self.tracestart = start
self.traceend = end
tstart = self.tracestart
if tstart is not None:
tstart = '%06o' % tstart
tend = self.traceend
if tend is not None:
tend = '%06o' % tend
Trace.comment('Trace set to (%s, %s)' % (tstart, tend))
self.Refresh()
elif operation == 'ttyin':
self.ttyin.mount(args)
Trace.comment("File '%s' mounted on TTYIN" % args)
self.Refresh()
elif operation == 'ttyout':
self.ttyout.mount(args)
Trace.comment("File '%s' mounted on TTYOUT" % args)
self.Refresh()
elif operation == 'view':
Trace.comment("Viewing memory from file '%s'" % args)
self.Refresh()
elif operation == 'write':
if args == 'on':
self.memory.using_rom = True
elif args == 'off':
self.memory.using_rom = False
else:
abort("Invalid view arg: %s" % args)
Trace.comment('ROM write protect set %s' % args.upper())
self.Refresh()
elif operation == 'x':
(low, high) = args
self.memory.dump(low, high)
Trace.comment('Memory dumped: %07o to %07o' % (low, high))
else:
abort('Invalid internal operation: %s' % operation)
self.memory.savecore('after_%s.core' % operation)
# Imlac.close(CORE_FILENAME)
def close(self, corefile=None):
if corefile:
Memory.savecore(corefile)
sys.exit()
# def set_ROM(self, type):
# self.memory.set_ROM(type)
# def set_boot(self, romtype):
# pass
def __tick_all(self, cycles):
self.ptr.tick(cycles)
self.ptp.tick(cycles)
# self.ttyin.tick(cycles)
# self.ttyout.tick(cycles)
def set_trace(self, tstart, tend=None):
"""Set trace to required range of values."""
global tracestart, traceend
if tstart:
tracestart = tstart
traceend = tend
Trace.tracing = True
else:
Trace.tracing = False
def execute_once(self):
# decide if trace should be on/off
if self.traceend is None:
if self.cpu.PC == self.tracestart:
Trace.settrace(True)
else:
Trace.settrace(self.cpu.PC >= self.tracestart and
self.cpu.PC <= self.traceend)
# execute and trace the Display CPU
if self.display_cpu.ison():
Trace.trace('%6.6o' % self.display_cpu.DPC)
Trace.trace('\t')
instruction_cycles = self.display_cpu.execute_one_instruction()
# execute and trace the Main CPU
if self.cpu.running:
Trace.trace('%6.6o\t' % self.cpu.PC)
else:
Trace.trace('\t')
cpu_cycles = self.cpu.execute_one_instruction()
instruction_cycles += cpu_cycles
if cpu_cycles < 1:
Trace.trace('\t\t')
Trace.itraceend(self.display_cpu.ison())
self.__tick_all(instruction_cycles)
if not self.display_cpu.ison() and not self.cpu.running:
return 0
return instruction_cycles
def run(self):
"""Start the machine and run until it halts."""
self.cpu.running = True
while self.execute_once() > 0:
pass
self.cpu.running = False
# run it ...
app = wx.App()
PymlacFrame(title='pymlac 0.1').Show()
app.MainLoop()