mirror of
https://github.com/rzzzwilson/pymlac.git
synced 2025-06-10 09:32:41 +00:00
885 lines
30 KiB
Python
Executable File
885 lines
30 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
"""
|
|
The Python Imlac emulator (pymlac).
|
|
|
|
Usage: pymlac (<options>)*
|
|
|
|
where options may be zero or more of:
|
|
-b PTR|TTY|NONE set boot ROM
|
|
-c clear memory (not ROM if write protected)
|
|
-cf <file> set core file (default is pymlac.core)
|
|
-d <value> set dataswitch value
|
|
-h print this help and stop
|
|
-ptp <file> set papertape punch output file
|
|
-ptr <file> set papertape reader input file
|
|
-r [<addr>] run machine (optionally starting at given address)
|
|
-s <file> set memory from data in file
|
|
-t <addr>[,<addr>]|OFF set trace OFF or start/within range
|
|
-ttyin <file> set TTY input file
|
|
-ttyout <file> set TTY output file
|
|
-v <file> set memory contents view file
|
|
-w ON|OFF set ROM write to ON or OFF
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import time
|
|
import traceback
|
|
from threading import *
|
|
import wx
|
|
|
|
import Display
|
|
import DisplayCPU
|
|
from Globals import *
|
|
#import Imlac
|
|
|
|
import Memory
|
|
import Ptr
|
|
import Ptp
|
|
import MainCPU
|
|
import Trace
|
|
|
|
|
|
# if we don't have log.py, don't crash
|
|
try:
|
|
import log
|
|
log = log.Log('test.log', log.Log.DEBUG)
|
|
except ImportError:
|
|
def log(*args, **kwargs):
|
|
pass
|
|
|
|
|
|
######
|
|
# Default things
|
|
######
|
|
|
|
# map ROM name to type
|
|
RomTypeMap = {'ptr': ROM_PTR,
|
|
'tty': ROM_TTY,
|
|
'none': ROM_NONE}
|
|
|
|
DefaultBootROM = 'ptr'
|
|
DefaultCoreFile = 'pymlac.core'
|
|
|
|
######
|
|
# Various demo constants
|
|
######
|
|
|
|
WindowTitleHeight = 22 # FIXME probably depends on OS/GUI framework
|
|
DefaultAppSize = (600, 600+WindowTitleHeight)
|
|
|
|
count = 1 # debug
|
|
|
|
|
|
def abort(msg):
|
|
"""Handle a fatal 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 get_options():
|
|
"""Routine to analyse command line options.
|
|
|
|
Returns a list of operation objects which are tuples:
|
|
(OPERATION, ARGS)
|
|
ARGS may be None or possibly another tuple.
|
|
"""
|
|
|
|
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'")
|
|
else:
|
|
usage()
|
|
else:
|
|
usage()
|
|
|
|
result.append(op)
|
|
return result
|
|
|
|
def usage():
|
|
"""Help for befuddled user."""
|
|
|
|
print(__doc__)
|
|
sys.exit()
|
|
|
|
|
|
def main():
|
|
"""Start of the emulator."""
|
|
|
|
# get operations list
|
|
ops = get_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':
|
|
self.memory.set_ROM(args)
|
|
Trace.comment('Bootstrap ROM set to %s' % args.upper())
|
|
elif operation == 'clear':
|
|
self.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('------ ------------- ------ -------------- '
|
|
'-----------------------')
|
|
self.run()
|
|
# 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
|
|
pass
|
|
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':
|
|
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())
|
|
else:
|
|
abort('Invalid internal operation: %s' % operation)
|
|
|
|
# Imlac.close(CORE_FILENAME)
|
|
|
|
class Led_1(object):
|
|
def __init__(self, parent, label, x, y, off, on):
|
|
wx.StaticText(parent, -1, label, pos=(x, y))
|
|
y += 15
|
|
wx.StaticBitmap(parent, -1, off, pos=(x-1, y))
|
|
self.led = wx.StaticBitmap(parent, -1, on, pos=(x-1, y))
|
|
self.set_value(0)
|
|
|
|
def set_value(self, value):
|
|
if value:
|
|
self.led.Enable()
|
|
else:
|
|
self.led.Disable()
|
|
|
|
|
|
class Led_16(object):
|
|
def __init__(self, parent, label, x, y, off, on):
|
|
led_width = off.GetWidth()
|
|
led_height = off.GetHeight()
|
|
wx.StaticText(parent, -1, label, pos=(x, y))
|
|
y += 15
|
|
self.leds = []
|
|
mark_count = 2
|
|
ticks = [(x-17+led_width,y+led_height/2+5)]
|
|
# dc = wx.PaintDC(parent)
|
|
for i in range(16):
|
|
wx.StaticBitmap(parent, -1, off, pos=(x-1+i*17, y))
|
|
led = wx.StaticBitmap(parent, -1, on, pos=(x-1+i*17, y))
|
|
self.leds.append(led)
|
|
mark_count += 1
|
|
if mark_count >= 3:
|
|
mark_count = 0
|
|
ticks.append((x+i*17 + led_width, y+led_height/2+5))
|
|
|
|
self.set_value(0)
|
|
self.ticks = ticks
|
|
|
|
def set_value(self, value):
|
|
mask = 0x8000
|
|
for l in self.leds:
|
|
if value & mask:
|
|
l.Enable()
|
|
else:
|
|
l.Disable()
|
|
mask = mask >> 1
|
|
|
|
|
|
#class PymlacFrame(wx.Frame):
|
|
# """a frame with two panels"""
|
|
#
|
|
# 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
|
|
#
|
|
# IMAGE_LED_OFF = 'images/led_off.png'
|
|
# IMAGE_LED_ON = 'images/led_on.png'
|
|
#
|
|
# INC = 0.01
|
|
#
|
|
# def __init__(self, parent=None, id=-1, title=None):
|
|
# wx.Frame.__init__(self, parent, id, title)
|
|
#
|
|
# self.make_gui()
|
|
# self.run()
|
|
#
|
|
# def make_gui(self):
|
|
# self.screen = wx.Panel(self,
|
|
# size=(self.WIDTH_SCREEN, self.HEIGHT_SCREEN),
|
|
# pos=(0,0))
|
|
# self.screen.SetBackgroundColour(self.SCREEN_COLOUR)
|
|
# self.console = wx.Panel(self, style=wx.SIMPLE_BORDER,
|
|
# size=(self.WIDTH_CONSOLE, self.HEIGHT_SCREEN),
|
|
# pos=(self.WIDTH_SCREEN,0))
|
|
# self.console.SetBackgroundColour(self.CONSOLE_COLOUR)
|
|
#
|
|
# python_png = wx.Image('images/PythonPowered.png',
|
|
# wx.BITMAP_TYPE_PNG).ConvertToBitmap()
|
|
# python_height = python_png.GetHeight()
|
|
# python_width = python_png.GetWidth()
|
|
#
|
|
# wxpython_png = wx.Image('images/wxPython2.png',
|
|
# wx.BITMAP_TYPE_PNG).ConvertToBitmap()
|
|
# wxpython_height = wxpython_png.GetHeight()
|
|
# wxpython_width = wxpython_png.GetWidth()
|
|
#
|
|
# h_margin = (self.WIDTH_CONSOLE - wxpython_width - python_width) / 3
|
|
#
|
|
# png_height = max(python_height, wxpython_height) + self.V_MARGIN
|
|
#
|
|
# v_margin = (png_height - python_height)/2
|
|
# python_ypos = self.HEIGHT_CONSOLE - png_height + v_margin
|
|
# v_margin = (png_height - wxpython_height)/2
|
|
# wxpython_ypos = self.HEIGHT_CONSOLE - png_height + v_margin
|
|
#
|
|
# wx.StaticBitmap(self.console, -1, python_png,
|
|
# pos=(h_margin, python_ypos))
|
|
# wx.StaticBitmap(self.console, -1, wxpython_png,
|
|
# pos=(python_width + 2*h_margin, wxpython_ypos))
|
|
#
|
|
# self.png_height = png_height
|
|
#
|
|
# led_off = wx.Image('images/led_off.png',
|
|
# wx.BITMAP_TYPE_PNG).ConvertToBitmap()
|
|
# led_on = wx.Image('images/led_on.png',
|
|
# wx.BITMAP_TYPE_PNG).ConvertToBitmap()
|
|
#
|
|
# y_pos = 8
|
|
# self.led_l = Led_1(self.console, 'l', self.CTL_MARGIN, y_pos,
|
|
# led_off, led_on)
|
|
# self.led_ac = Led_16(self.console, 'ac', 3*self.CTL_MARGIN, y_pos,
|
|
# led_off, led_on)
|
|
# y_pos += 35
|
|
# self.led_pc = Led_16(self.console, 'pc', 3*self.CTL_MARGIN, y_pos,
|
|
# led_off, led_on)
|
|
#
|
|
#
|
|
# y_pos = 305
|
|
# wx.StaticText(self.console, -1, 'ptr', pos=(self.CTL_MARGIN, y_pos))
|
|
# y_pos += 15
|
|
# self.txt_ptrFile = wx.TextCtrl(self.console, -1,
|
|
# pos=(self.CTL_MARGIN, y_pos),
|
|
# size=(self.WIDTH_CONSOLE-2*self.CTL_MARGIN, 25))
|
|
# y_pos += 30
|
|
#
|
|
# wx.StaticText(self.console, -1, 'ptp', pos=(self.CTL_MARGIN, y_pos))
|
|
# y_pos += 15
|
|
# self.txt_ptpFile = wx.TextCtrl(self.console, -1,
|
|
# pos=(self.CTL_MARGIN, y_pos),
|
|
# size=(self.WIDTH_CONSOLE-2*self.CTL_MARGIN, 25))
|
|
# y_pos += 15
|
|
#
|
|
# dc = wx.PaintDC(self.console)
|
|
# dc.SetPen(wx.Pen('black', 1))
|
|
# dc.DrawLine(0, self.HEIGHT_CONSOLE - self.png_height,
|
|
# self.WIDTH_CONSOLE-1, self.HEIGHT_CONSOLE - self.png_height)
|
|
#
|
|
# for (x, y) in self.led_ac.ticks:
|
|
# dc.DrawLine(x, y, x, y+5)
|
|
# first = self.led_ac.ticks[0]
|
|
# last = self.led_ac.ticks[-1]
|
|
# (x1, y1) = first
|
|
# (x2, y2) = last
|
|
# dc.DrawLine(x1, y2+5, x2, y2+5)
|
|
#
|
|
# for (x, y) in self.led_pc.ticks:
|
|
# dc.DrawLine(x, y, x, y+5)
|
|
# first = self.led_pc.ticks[0]
|
|
# last = self.led_pc.ticks[-1]
|
|
# (x1, y1) = first
|
|
# (x2, y2) = last
|
|
# dc.DrawLine(x1, y2+5, x2, y2+5)
|
|
#
|
|
# self.y_offset = 0
|
|
# self.y_sign = +1
|
|
#
|
|
# self.Fit()
|
|
#
|
|
# def run_machine(self, ops):
|
|
# """Run the console options."""
|
|
#
|
|
# for (operation, args) in ops:
|
|
# if operation == 'boot':
|
|
# Memory.set_ROM(args)
|
|
# Trace.comment('Bootstrap ROM set to %s' % args.upper())
|
|
# self.on_paint()
|
|
# elif operation == 'clear':
|
|
# Memory.clear_core()
|
|
# Trace.comment('Core cleared')
|
|
# self.on_paint()
|
|
# elif operation == 'load_ptp':
|
|
# Ptp.mount(args)
|
|
# Trace.comment("File '%s' mounted on PTP" % args)
|
|
# self.on_paint()
|
|
# elif operation == 'load_ptr':
|
|
# Ptr.mount(args)
|
|
# Trace.comment("File '%s' mounted on PTR" % args)
|
|
# self.on_paint()
|
|
# elif operation == 'data':
|
|
# MainCPU.DS = args
|
|
# Trace.comment('Dataswitch value set to %06o' % args)
|
|
# self.on_paint()
|
|
# 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('------ ------------- ------ -------------- '
|
|
# '-----------------------')
|
|
# self.on_paint()
|
|
# Imlac.run()
|
|
# self.on_paint()
|
|
# Trace.comment('Imlac halted')
|
|
# self.on_paint()
|
|
# elif operation == 'set':
|
|
# Trace.comment("Setting memory from file '%s'" % args)
|
|
# self.on_paint()
|
|
# 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))
|
|
# self.on_paint()
|
|
# elif operation == 'ttyin':
|
|
# TtyIn.mount(args)
|
|
# Trace.comment("File '%s' mounted on TTYIN" % args)
|
|
# self.on_paint()
|
|
# elif operation == 'ttyout':
|
|
# TtyOut.mount(args)
|
|
# Trace.comment("File '%s' mounted on TTYOUT" % args)
|
|
# self.on_paint()
|
|
# elif operation == 'view':
|
|
# Trace.comment("Viewing memory from file '%s'" % args)
|
|
# self.on_paint()
|
|
# 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())
|
|
# self.on_paint()
|
|
# else:
|
|
# abort('Invalid internal operation: %s' % operation)
|
|
#
|
|
# Imlac.close(CORE_FILENAME)
|
|
#
|
|
#
|
|
# def on_paint(self, event=None):
|
|
# global count
|
|
#
|
|
# # establish the painting surface
|
|
# dc = wx.PaintDC(self.screen)
|
|
# dc.SetBackground(wx.Brush(self.SCREEN_COLOUR, 1))
|
|
# dc.SetPen(wx.Pen(self.PHOSPHOR_COLOUR, 1))
|
|
# dc.Clear()
|
|
# if self.y_sign > 0:
|
|
# self.y_offset = self.y_offset + self.INC
|
|
# if self.y_offset > self.HEIGHT_SCREEN-1:
|
|
# self.y_sign = -1
|
|
# self.y_offset = self.HEIGHT_SCREEN-1
|
|
# else:
|
|
# self.y_offset = self.y_offset - self.INC
|
|
# if self.y_offset < 0:
|
|
# self.y_sign = +1
|
|
# self.y_offset = 0
|
|
# dc.DrawLine(0, int(self.y_offset), self.WIDTH_SCREEN-1, int(self.HEIGHT_SCREEN-self.y_offset-1))
|
|
# dc.DrawLine(0, int(self.HEIGHT_SCREEN-self.y_offset-1), self.WIDTH_SCREEN-1, int(self.y_offset))
|
|
#
|
|
# dc = wx.PaintDC(self.console)
|
|
# #dc.SetPen(wx.Pen('black', wx.DOT))
|
|
# dc.SetPen(wx.Pen('black', 1))
|
|
# dc.DrawLine(0, self.HEIGHT_CONSOLE - self.png_height,
|
|
# self.WIDTH_CONSOLE-1, self.HEIGHT_CONSOLE - self.png_height)
|
|
#
|
|
# for (x, y) in self.led_ac.ticks:
|
|
# dc.DrawLine(x, y, x, y+5)
|
|
# first = self.led_ac.ticks[0]
|
|
# last = self.led_ac.ticks[-1]
|
|
# (x1, y1) = first
|
|
# (x2, y2) = last
|
|
# dc.DrawLine(x1, y2+5, x2, y2+5)
|
|
#
|
|
# for (x, y) in self.led_pc.ticks:
|
|
# dc.DrawLine(x, y, x, y+5)
|
|
# first = self.led_pc.ticks[0]
|
|
# last = self.led_pc.ticks[-1]
|
|
# (x1, y1) = first
|
|
# (x2, y2) = last
|
|
# dc.DrawLine(x1, y2+5, x2, y2+5)
|
|
#
|
|
# count += 1
|
|
# self.led_ac.set_value(count)
|
|
#
|
|
#
|
|
# pass
|
|
|
|
|
|
|
|
################################################################################
|
|
# Stuff for the interpreter thead
|
|
################################################################################
|
|
|
|
# type of interpreter events
|
|
(InterpreterStop, InterpreterDraw) = range(2)
|
|
|
|
# interpreter stop event
|
|
_myEVT_INTERPRETER_STOP = wx.NewEventType()
|
|
EVT_INTERPRETER_STOP = wx.PyEventBinder(_myEVT_INTERPRETER_STOP, 1)
|
|
|
|
# interpreter draw event
|
|
_myEVT_INTERPRETER_DRAW = wx.NewEventType()
|
|
EVT_INTERPRETER_DRAW = wx.PyEventBinder(_myEVT_INTERPRETER_DRAW, 1)
|
|
|
|
|
|
class _InterpreterEvent(wx.PyCommandEvent):
|
|
"""Event sent from the interpreter."""
|
|
|
|
def __init__(self, type, id):
|
|
"""Construct an interpreter event.
|
|
|
|
type the type of event
|
|
id unique event number
|
|
|
|
Event will be adorned with attributes by raising code.
|
|
"""
|
|
|
|
wx.PyCommandEvent.__init__(self, type, id)
|
|
|
|
|
|
|
|
# Thread class that executes processing
|
|
class InterpreterThread(Thread):
|
|
"""Worker Thread Class."""
|
|
|
|
def __init__(self, parent, cpu, dcpu):
|
|
"""Init Worker Thread Class."""
|
|
|
|
Thread.__init__(self)
|
|
self.parent = parent
|
|
self._want_abort = 0
|
|
self.cpu = cpu
|
|
self.dcpu = dcpu
|
|
|
|
# This starts the thread running on creation, but you could
|
|
# also make the GUI thread responsible for calling this
|
|
self.start()
|
|
|
|
def RaiseInterpreterStop(self, data=None):
|
|
"""Raise an interpreter stop event."""
|
|
|
|
event = _InterpreterEvent(_myEVT_INTERPRETER_STOP, self.parent.GetId())
|
|
event.type = EVT_INTERPRETER_STOP
|
|
event.data = data
|
|
self.parent.GetEventHandler().ProcessEvent(event)
|
|
|
|
def RaiseInterpreterDraw(self, data=None):
|
|
"""Raise an interpreter draw event."""
|
|
|
|
event = _InterpreterEvent(_myEVT_INTERPRETER_DRAW, self.parent.GetId())
|
|
event.type = EVT_INTERPRETER_DRAW
|
|
event.data = data
|
|
self.parent.GetEventHandler().ProcessEvent(event)
|
|
|
|
def run(self):
|
|
"""Run the interpreter."""
|
|
|
|
self.cpu.running = True
|
|
while self.execute_once() > 0: pass
|
|
self.cpu.running = False
|
|
|
|
self.RaiseInterpreterStop()
|
|
|
|
def execute_once():
|
|
if traceend is None:
|
|
if self.cpu.PC == tracestart:
|
|
Trace.settrace(True)
|
|
else:
|
|
Trace.settrace(self.cpu.PC >= tracestart and self.cpu.PC <= traceend)
|
|
|
|
if self.dcpu.ison():
|
|
Trace.trace('%6.6o' % self.dcpu.DPC)
|
|
Trace.trace('\t')
|
|
|
|
instruction_cycles = self.dcpu.execute_one_instruction()
|
|
|
|
Trace.trace('%6.6o\t' % self.cpu.PC)
|
|
|
|
instruction_cycles += self.cpu.execute_one_instruction()
|
|
|
|
Trace.itraceend(self.dcpu.ison())
|
|
|
|
self.__tick_all(instruction_cycles)
|
|
|
|
if not self.dcpu.ison() and not self.cpu.running:
|
|
return 0
|
|
|
|
return instruction_cycles
|
|
|
|
def abort(self):
|
|
"""abort worker thread."""
|
|
# Method for use by main thread to signal an abort
|
|
self._want_abort = 1
|
|
|
|
################################################################################
|
|
# The main application frame
|
|
################################################################################
|
|
|
|
class DisplayFrame(wx.Frame):
|
|
def __init__(self):
|
|
wx.Frame.__init__(self, parent=None, size=DefaultAppSize)
|
|
self.SetMinSize(DefaultAppSize)
|
|
self.panel = wx.Panel(self, wx.ID_ANY)
|
|
self.panel.ClearBackground()
|
|
|
|
# build the GUI
|
|
box = wx.BoxSizer(wx.VERTICAL)
|
|
self.display = Display.Display(self.panel)
|
|
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.Refresh()
|
|
|
|
self.lock = False
|
|
self.running = False
|
|
|
|
# Set up handler for interpreter thread events
|
|
self.Bind(EVT_INTERPRETER_STOP, self.OnInterpreterStop)
|
|
|
|
# And indicate we don't have an interpreter thread yet
|
|
self.interpreter = None
|
|
|
|
self.run_machine()
|
|
|
|
def OnInterpreterStop(self, event):
|
|
"""A STOP event from the interpreter."""
|
|
|
|
# print('OnInterpreterStop: event=%s' % str(dir(event)))
|
|
self.running = False
|
|
Trace.comment('Imlac halted')
|
|
|
|
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):
|
|
"""Run the interpreter."""
|
|
|
|
# create device, construct machine
|
|
self.kbd = None # Kbd.Kbd()
|
|
self.ttyin = None # TtyIn.TtyIn()
|
|
self.ttyout = None # TtyOut.TtyOut()
|
|
self.memory = Memory.Memory(boot_rom=DefaultBootROM, core=DefaultCoreFile)
|
|
self.ptr = Ptr.Ptr()
|
|
self.ptp = Ptp.Ptp()
|
|
self.display = Display.Display(self)
|
|
self.dcpu = DisplayCPU.DisplayCPU(self.display, self.memory)
|
|
self.cpu = MainCPU.MainCPU(self.memory, self.display, self.dcpu,
|
|
self.kbd, self.ttyin, self.ttyout,
|
|
self.ptr, self.ptp)
|
|
|
|
Trace.init(TRACE_FILENAME, self.cpu, self.dcpu)
|
|
tracestart = traceend = None
|
|
|
|
# get and execute the operations list
|
|
ops = get_options()
|
|
for (operation, args) in ops:
|
|
while self.running:
|
|
time.sleep(1)
|
|
|
|
if operation == 'boot':
|
|
self.memory.set_ROM(args)
|
|
Trace.comment('Bootstrap ROM set to %s' % args.upper())
|
|
elif operation == 'clear':
|
|
self.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':
|
|
self.ptr.mount(args)
|
|
Trace.comment("File '%s' mounted on PTR" % args)
|
|
elif operation == 'data':
|
|
self.cpu.DS = args
|
|
Trace.comment('Dataswitch value set to %06o' % args)
|
|
elif operation == 'run':
|
|
self.cpu.PC = args
|
|
Trace.comment('Running from address %06o' % args)
|
|
if tracestart:
|
|
Trace.comment('DPC\tDisplay\t\tPC\tMain\t\tRegs')
|
|
Trace.comment('------ ------------- ------ -------------- '
|
|
'-----------------------')
|
|
self.interpreter = InterpreterThread(self, self.cpu, self.dcpu)
|
|
self.running = True
|
|
elif operation == 'set':
|
|
Trace.comment("Setting memory from file '%s'" % args)
|
|
elif operation == 'trace':
|
|
if args == 'off':
|
|
tracestart = None
|
|
traceend = None
|
|
else:
|
|
(start, end) = args
|
|
tracestart = start
|
|
traceend = end
|
|
tstart = tracestart
|
|
if tstart is not None:
|
|
tstart = '%06o' % tstart
|
|
tend = traceend
|
|
if tend is not None:
|
|
tend = '%06o' % tend
|
|
Trace.comment('Trace set to (%s, %s)' % (tstart, tend))
|
|
elif operation == 'ttyin':
|
|
self.ttyin.mount(args)
|
|
Trace.comment("File '%s' mounted on TTYIN" % args)
|
|
elif operation == 'ttyout':
|
|
self.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':
|
|
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())
|
|
else:
|
|
abort('Invalid internal operation: %s' % operation)
|
|
|
|
# Imlac.close(CORE_FILENAME)
|
|
|
|
def excepthook(type, value, tb):
|
|
"""Handler for uncaught exceptions."""
|
|
|
|
msg = '\n' + '=' * 80
|
|
msg += '\nUncaught exception:\n'
|
|
msg += ''.join(traceback.format_exception(type, value, tb))
|
|
msg += '=' * 80 + '\n'
|
|
print(msg)
|
|
log.critical(msg)
|
|
sys.exit(1)
|
|
|
|
# plug our handler into the python system
|
|
sys.excepthook = excepthook
|
|
|
|
# start wxPython app
|
|
app = wx.App()
|
|
DisplayFrame().Show()
|
|
app.MainLoop()
|