mirror of
https://github.com/rzzzwilson/pymlac.git
synced 2025-06-10 09:32:41 +00:00
867 lines
26 KiB
Python
Executable File
867 lines
26 KiB
Python
Executable File
"""
|
|
Test pymlac CPU opcodes DIRECTLY.
|
|
|
|
Usage: test_CPU.py [<options>] <filename>
|
|
|
|
where <filename> is a file of test instructions and
|
|
<options> is one or more of
|
|
-h prints this help and stops
|
|
"""
|
|
|
|
import time
|
|
import collections
|
|
import filecmp
|
|
|
|
# We implement a small interpreter to test the CPU.
|
|
# The DSL is documented here: [github.com/rzzzwilson/pymlac].
|
|
|
|
|
|
import sys
|
|
import os
|
|
import json
|
|
import collections
|
|
|
|
from Globals import *
|
|
import MainCPU
|
|
import Memory
|
|
import PtrPtp
|
|
import TtyIn
|
|
import TtyOut
|
|
import Trace
|
|
|
|
trace = Trace.Trace(TRACE_FILENAME)
|
|
|
|
import log
|
|
log = log.Log('test.log', log.Log.DEBUG)
|
|
|
|
|
|
def octword_line(s, offset):
|
|
"""Generate one line of fdump output.
|
|
|
|
s integer list to convert (8 words or less)
|
|
offset offset from start of dump
|
|
"""
|
|
|
|
HEXLEN = 55
|
|
CHARLEN = 16
|
|
|
|
hex = ''
|
|
char = ''
|
|
for v in s:
|
|
hex += '%06o ' % v
|
|
hi = v >> 8
|
|
lo = v & 0xff
|
|
if ord(' ') <= hi <= ord('~'):
|
|
char += chr(hi)
|
|
else:
|
|
char += '.'
|
|
if ord(' ') <= lo <= ord('~'):
|
|
char += chr(lo)
|
|
else:
|
|
char += '.'
|
|
hex = (hex + ' '*HEXLEN)[:HEXLEN]
|
|
char = (char + '|' + ' '*CHARLEN)[:CHARLEN+1]
|
|
return '%06o %s |%s' % (offset, hex, char)
|
|
|
|
|
|
class TestCPU(object):
|
|
|
|
# temporary assembler file and listfile prefix
|
|
AsmFilename = '_#ASM#_'
|
|
ProgressChar = '|\\-/'
|
|
ProgressCount = 0
|
|
|
|
|
|
def __init__(self):
|
|
"""Initialize the test."""
|
|
|
|
pass
|
|
|
|
def show_progress(self):
|
|
"""Show progress to stdout. A spinning line."""
|
|
|
|
print('%s\r' % self.ProgressChar[self.ProgressCount], end='')
|
|
self.ProgressCount += 1
|
|
if self.ProgressCount >= len(self.ProgressChar):
|
|
self.ProgressCount = 0
|
|
sys.stdout.flush()
|
|
|
|
def list2int(self, values):
|
|
"""Convert a string of multiple values to a list of ints.
|
|
|
|
values a string like: '123;4;-2'
|
|
(could be just '5')
|
|
|
|
Returns a list of integers, for example: [123, 4, -2]
|
|
"""
|
|
|
|
values = values.split(';')
|
|
|
|
result = []
|
|
for value in values:
|
|
result.append(self.str2int(value))
|
|
|
|
return result
|
|
|
|
def str2int(self, s):
|
|
"""Convert string to numeric value.
|
|
|
|
s numeric string (decimal or octal)
|
|
(could be None)
|
|
|
|
Returns the numeric value.
|
|
Returns None if value incorrect.
|
|
"""
|
|
|
|
if s is None:
|
|
return None
|
|
|
|
base = 10
|
|
if s[0] == '0':
|
|
base = 8
|
|
|
|
try:
|
|
value = int(s, base=base)
|
|
except:
|
|
return None
|
|
|
|
return value
|
|
|
|
def assemble(self, addr, opcodes):
|
|
"""Assemble a set of instructions, return opcode values.
|
|
|
|
addr address to assemble at
|
|
opcodes list of ASM lines to assemble
|
|
|
|
Returns a list of integer opcodes.
|
|
"""
|
|
|
|
# split possible multiple instructions
|
|
opcodes = opcodes.split('|')
|
|
|
|
# create ASM file with instruction
|
|
with open(self.AsmFilename+'.asm', 'w') as fd:
|
|
fd.write('\torg\t%07o\n' % addr)
|
|
for line in opcodes:
|
|
fd.write('\t%s\n' % line)
|
|
fd.write('\tend\n')
|
|
|
|
# now assemble file
|
|
cmd = ('../iasm/iasm -l %s.lst %s.asm'
|
|
% (self.AsmFilename, self.AsmFilename))
|
|
res = os.system(cmd)
|
|
|
|
# read the listing file to get assembled opcode (second line)
|
|
with open(self.AsmFilename+'.lst', 'r') as fd:
|
|
lines = fd.readlines()
|
|
|
|
result = []
|
|
for line in lines[1:-1]:
|
|
(opcode, _) = line.split(None, 1)
|
|
result.append(int(opcode, base=8))
|
|
|
|
return result
|
|
|
|
# Here we implement the DSL primitives. They all take two parameters which are
|
|
# the DSL field1 and field2 values (lowercase strings). If one or both are
|
|
# missing the parameters are None.
|
|
#
|
|
# setreg <name> <value>
|
|
# setmem <address> <value>
|
|
# allreg <value>
|
|
# allmem <value>
|
|
# bootrom <type>
|
|
# romwrite <bool>
|
|
# run [<number>]
|
|
# rununtil <address>
|
|
# checkcycles <number>
|
|
# checkreg <name> <value>
|
|
# checkmem <addr> <value>
|
|
# checkcpu <state>
|
|
# checkdcpu <state>
|
|
# mount <device> <filename>
|
|
# dismount <device>
|
|
# checkfile <file1> <file2>
|
|
# dumpmem file begin,end
|
|
# cmpmem file
|
|
# trace <range>[:<range>:...]
|
|
# where <range> ::= <addr>,<addr>
|
|
# onerror ('ignore'|'abort')
|
|
# initially, "onerror abort" assumed
|
|
|
|
def setreg(self, name, value):
|
|
"""Set register to a value.
|
|
|
|
Remember value to check later.
|
|
"""
|
|
|
|
value = self.str2int(value)
|
|
|
|
self.reg_values[name] = value
|
|
|
|
if name == 'ac':
|
|
self.cpu.AC = value
|
|
elif name == 'l':
|
|
self.cpu.L = value & 1
|
|
elif name == 'pc':
|
|
self.cpu.PC = value
|
|
elif name == 'ds':
|
|
self.cpu.DS = value
|
|
else:
|
|
raise Exception('setreg: bad register name: %s' % name)
|
|
|
|
def setmem(self, addr, values):
|
|
"""Set memory location to a value.
|
|
|
|
addr address of memory location to set
|
|
values value to store at 'addr'
|
|
"""
|
|
|
|
addr = self.str2int(addr)
|
|
|
|
# check if we must assemble values
|
|
if values[0] == '[':
|
|
# assemble one or more instructions
|
|
values = self.assemble(addr, values[1:-1])
|
|
else:
|
|
values = self.list2int(values)
|
|
|
|
for v in values:
|
|
self.mem_values[addr] = v
|
|
self.memory.put(v, addr, False)
|
|
addr += 1
|
|
|
|
def allreg(self, value, ignore):
|
|
"""Set all registers to a value."""
|
|
|
|
new_value = self.str2int(value)
|
|
if new_value is None:
|
|
return 'allreg: bad value: %s' % str(value)
|
|
|
|
self.reg_all_value = new_value
|
|
|
|
self.cpu.AC = new_value
|
|
self.cpu.L = new_value & 1
|
|
self.cpu.PC = new_value
|
|
self.cpu.DS = new_value
|
|
|
|
def allmem(self, value, ignore):
|
|
"""Set all of memory to a value.
|
|
|
|
Remember value to check later.
|
|
"""
|
|
|
|
new_value = self.str2int(value)
|
|
if new_value is None:
|
|
return 'allmem: bad value: %s' % str(value)
|
|
|
|
self.mem_all_value = new_value
|
|
|
|
for mem in range(MEMORY_SIZE):
|
|
self.memory.put(new_value, mem, False)
|
|
|
|
def bootrom(self, loader_type, ignore):
|
|
"""Set bootloader memory range to appropriate bootloader code.
|
|
|
|
loader_type either 'ptr' or 'tty'
|
|
"""
|
|
|
|
if loader_type not in ['ptr', 'tty']:
|
|
return 'bootrom: invalid bootloader type: %s' % loader_type
|
|
self.memory.set_ROM(loader_type)
|
|
|
|
def romwrite(self, writable, ignore):
|
|
"""Set ROM to be writable or not."""
|
|
|
|
self.memory.rom_protected = writable
|
|
|
|
def run(self, num_instructions, ignore):
|
|
"""Execute one or more instructions.
|
|
|
|
num_instructions number of instructions to execute
|
|
|
|
Also sets self.abort to False.
|
|
"""
|
|
|
|
if num_instructions is None:
|
|
# assume number of instructions is 1
|
|
number = 1
|
|
else:
|
|
number = self.str2int(num_instructions)
|
|
if number is None:
|
|
return 'Invalid number of instructions: %s' % num_instructions
|
|
|
|
self.used_cycles= 0
|
|
while number > 0:
|
|
# for _ in range(number):
|
|
(cycles, _) = self.cpu.execute_one_instruction()
|
|
trace.itraceend(False)
|
|
self.ptrptp.ptr_tick(cycles)
|
|
self.ptrptp.ptp_tick(cycles)
|
|
self.used_cycles += cycles
|
|
number -= 1
|
|
self.abort = False
|
|
|
|
def rununtil(self, address, ignore):
|
|
"""Execute instructions until PC == address.
|
|
|
|
address address at which to stop
|
|
|
|
We must handle the case where PC initial address is the actual stop
|
|
address, that is, execute one instruction before checking for the
|
|
stop address.
|
|
|
|
We also stop if the CPU executes the HLT instruction.
|
|
|
|
Also sets self.abort to False.
|
|
"""
|
|
|
|
new_address = self.str2int(address)
|
|
if new_address is None:
|
|
return 'rununtil: invalid stop address: %s' % address
|
|
|
|
self.used_cycles = 0
|
|
self.cpu.running = True
|
|
while self.cpu.running:
|
|
(cycles, tracestr) = self.cpu.execute_one_instruction()
|
|
if tracestr:
|
|
endstr = trace.itraceend(False)
|
|
trace.comment('%s\t%s' % (tracestr, endstr))
|
|
trace.flush()
|
|
self.ptrptp.ptr_tick(cycles)
|
|
self.ptrptp.ptp_tick(cycles)
|
|
self.used_cycles += cycles
|
|
if self.cpu.PC == new_address:
|
|
break
|
|
self.abort = False
|
|
|
|
def checkcycles(self, cycles, ignore):
|
|
"""Check that opcode cycles used is correct.
|
|
|
|
cycles expected number of cycles used
|
|
"""
|
|
|
|
num_cycles = self.str2int(cycles)
|
|
if num_cycles is None:
|
|
return 'checkcycles: invalid number of cycles: %s' % cycles
|
|
|
|
if num_cycles != self.used_cycles:
|
|
return ('Run used %d cycles, expected %d'
|
|
% (self.used_cycles, num_cycles))
|
|
|
|
def checkreg(self, reg, value):
|
|
"""Check register is as it should be."""
|
|
|
|
new_value = self.str2int(value)
|
|
if new_value is None:
|
|
return 'checkreg: bad value: %s' % str(value)
|
|
|
|
if reg == 'ac':
|
|
self.reg_values[reg] = self.cpu.AC
|
|
if self.cpu.AC != new_value:
|
|
return ('AC wrong, is %07o, should be %07o'
|
|
% (self.cpu.AC, new_value))
|
|
elif reg == 'l':
|
|
self.reg_values[reg] = self.cpu.L
|
|
if self.cpu.L != new_value:
|
|
return ('L wrong, is %02o, should be %02o'
|
|
% (self.cpu.L, new_value))
|
|
elif reg == 'pc':
|
|
self.reg_values[reg] = self.cpu.PC
|
|
if self.cpu.PC != new_value:
|
|
return ('PC wrong, is %07o, should be %07o'
|
|
% (self.cpu.PC, new_value))
|
|
elif reg == 'ds':
|
|
self.reg_values[reg] = self.cpu.DS
|
|
if self.cpu.DS != new_value:
|
|
return ('DS wrong, is %07o, should be %07o'
|
|
% (self.cpu.DS, new_value))
|
|
else:
|
|
return 'checkreg: bad register name: %s' % str(name)
|
|
|
|
def checkmem(self, addr, value):
|
|
"""Check a memory location is as it should be."""
|
|
|
|
new_addr = self.str2int(addr)
|
|
if new_addr is None:
|
|
return 'checkmem: bad address: %s' % str(addr)
|
|
new_value = self.str2int(value)
|
|
if new_value is None:
|
|
return 'checkmem: bad value: %s' % str(value)
|
|
|
|
self.mem_values[new_addr] = new_value
|
|
|
|
memvalue = self.memory.fetch(new_addr, False)
|
|
if memvalue != new_value:
|
|
return ('Memory wrong at address %07o, is %07o, should be %07o'
|
|
% (new_addr, memvalue, new_value))
|
|
|
|
def checkcpu(self, state, ignore):
|
|
"""Check main CPU run state is as desired."""
|
|
|
|
if state not in ('on', 'off'):
|
|
return 'checkcpu: bad state: %s' % str(state)
|
|
|
|
cpu_state = str(self.cpu.running).lower()
|
|
|
|
if ((state == "on" and cpu_state != "true") or
|
|
(state == "off" and cpu_state != "false")):
|
|
return ('Main CPU run state is %s, should be %s'
|
|
% (str(self.cpu.running), str(state)))
|
|
|
|
def checkdcpu(self, state, ignore):
|
|
"""Check display CPU run state is as desired."""
|
|
|
|
if state not in ('on', 'off'):
|
|
return 'checkdcpu: bad state: %s' % str(state)
|
|
|
|
dcpu_state = str(self.display.running).lower()
|
|
|
|
if ((state == "on" and dcpu_state != "true") or
|
|
(state == "off" and dcpu_state != "false")):
|
|
return ('Display CPU run state is %s, should be %s'
|
|
% (str(self.cpu.running), str(state)))
|
|
|
|
def mount(self, device, filename):
|
|
"""Mount a file on a device.
|
|
|
|
device name of device
|
|
filename path to file to mount
|
|
|
|
If the device is an input device, the file must exist.
|
|
"""
|
|
|
|
if device == 'ptr':
|
|
if not os.path.exists(filename) or not os.path.isfile(filename):
|
|
return "mount: '%s' doesn't exist or isn't a file" % filename
|
|
self.ptrptp.ptr_mount(filename)
|
|
elif device == 'ptp':
|
|
self.ptrptp.ptp_mount(filename)
|
|
elif device == 'ttyin':
|
|
if not os.path.exists(filename) or not os.path.isfile(filename):
|
|
return "mount: '%s' doesn't exist or isn't a file" % filename
|
|
self.ttyin.mount(filename)
|
|
elif device == 'ttyout':
|
|
self.ttyout.mount(filename)
|
|
else:
|
|
print('mount: bad device: %s' % device)
|
|
return 'mount: bad device: %s' % device
|
|
|
|
def dismount(self, device, ignore):
|
|
"""Dismount a file from a device.
|
|
|
|
device name of device
|
|
"""
|
|
|
|
if device == 'ptr':
|
|
self.ptrptp.ptr_dismount()
|
|
elif device == 'ptp':
|
|
self.ptrptp.ptp_dismount()
|
|
else:
|
|
return 'dismount: bad device: %s' % device
|
|
|
|
def checkfile(self, file1, file2):
|
|
"""Compare two files, error if different."""
|
|
|
|
cmd = 'cmp -s %s %s' % (file1, file2)
|
|
if filecmp.cmp(file1, file2, shallow=False):
|
|
return 'Files %s and %s are different' % (file1, file2)
|
|
|
|
def dumpmem(self, filename, addresses):
|
|
"""Dump memory to a file.
|
|
|
|
filename path to the file to create
|
|
addresses string containing "begin,end" addresses
|
|
"""
|
|
|
|
# get address limits
|
|
if addresses is None:
|
|
# dump everything
|
|
begin = 0
|
|
end = 0x3fff
|
|
else:
|
|
s = addresses.split(',')
|
|
if len(s) != 2:
|
|
return "dumpmem: dump limits are bad: %s" % addresses
|
|
(begin, end) = s
|
|
begin = self.str2int(begin)
|
|
end = self.str2int(end)
|
|
if begin is None or end is None:
|
|
return "dumpmem: dump limits are bad: %s" % addresses
|
|
|
|
# create octdump-like text file with required memory locations
|
|
with open(filename, 'w') as handle:
|
|
addr = begin
|
|
offset = addr
|
|
chunk = []
|
|
while addr <= end:
|
|
word = self.memory.fetch(addr, False)
|
|
addr += 1
|
|
chunk.append(word)
|
|
if len(chunk) == 8:
|
|
line = octword_line(chunk, offset)
|
|
handle.write(line + '\n')
|
|
chunk = []
|
|
offset = addr
|
|
if len(chunk):
|
|
line = octword_line(chunk, offset)
|
|
handle.write(line + '\n')
|
|
|
|
def cmpmem(self, filename, ignore):
|
|
"""Compare a 'dumpmem' file with memory.
|
|
|
|
filename path of the 'dumpmem' file
|
|
|
|
A dumpmem file has the following format:
|
|
000100 100000 010104 000000 000000 004111 000000 000000 000000 |...D.....I......|
|
|
"""
|
|
|
|
# get file contents into memory
|
|
try:
|
|
with open(filename, 'r') as handle:
|
|
lines = handle.readlines()
|
|
except IOError as e:
|
|
return "Error opening file '%s': %s" % (filename, e.strerror)
|
|
|
|
# read dumpmem file, get address and expected contents
|
|
for line in lines:
|
|
line = line.split('|', 1)[0]
|
|
|
|
values = line.split()
|
|
address = self.str2int(values[0])
|
|
for value in values[1:]:
|
|
expected = self.str2int('0'+value) # force octal evaluation
|
|
actual = self.memory.fetch(address, False)
|
|
if actual != expected:
|
|
return ('Address %06o is wrong, expected %06o, is %06o'
|
|
% (address, expected, actual))
|
|
address += 1
|
|
|
|
def trace(self, ranges, ignore):
|
|
"""Set the trace range(s).
|
|
|
|
ranges trace ranges of the form <range>[:<range>:...]
|
|
where <range> ::= <addr>,<addr>
|
|
|
|
Puts the trace range(s) into the Trace object.
|
|
"""
|
|
|
|
trace_map = collections.defaultdict(bool)
|
|
for rng in ranges.split(':'):
|
|
be = rng.split(',')
|
|
if len(be) != 2:
|
|
return("Trace ranges must have the form 'begin,end'")
|
|
(begin, end) = be
|
|
begin = self.str2int(begin)
|
|
end = self.str2int(end)
|
|
if begin > end:
|
|
return("Trace begin address must be <= end address. Got: %s" % rng)
|
|
for addr in range(begin, end+1):
|
|
trace_map[addr] = True
|
|
|
|
trace.set_trace_map(trace_map)
|
|
|
|
def onerror(self, state, ignore):
|
|
"""Set the action upon an error.
|
|
|
|
state either 'ignore' or 'error'
|
|
"""
|
|
|
|
if state == 'ignore':
|
|
self.abort = False
|
|
elif state == 'error':
|
|
self.abort = True
|
|
else:
|
|
return 'onerror: bad action name: %s' % state
|
|
|
|
# end of DSL primitives
|
|
|
|
def check_all_mem(self):
|
|
"""Check memory for unwanted changes."""
|
|
|
|
result = []
|
|
|
|
for mem in range(MEMORY_SIZE):
|
|
value = self.memory.fetch(mem, False)
|
|
if mem in self.mem_values:
|
|
if value != self.mem_values[mem]:
|
|
result.append('Memory at %07o changed, is %07o, should be %07o'
|
|
% (mem, value, self.mem_values[mem]))
|
|
else:
|
|
if value != self.mem_all_value:
|
|
result.append('Memory at %07o changed, is %07o, should be %07o'
|
|
% (mem, value, self.mem_all_value))
|
|
|
|
def check_all_regs(self):
|
|
"""Check registers for unwanted changes."""
|
|
|
|
result = []
|
|
|
|
if 'ac' in self.reg_values:
|
|
if self.cpu.AC != self.reg_values['ac']:
|
|
result.append('AC changed, is %07o, should be %07o'
|
|
% (self.cpu.AC, self.reg_values['ac']))
|
|
else:
|
|
if self.cpu.AC != self.reg_all_value:
|
|
result.append('AC changed, is %07o, should be %07o'
|
|
% (self.cpu.AC, self.reg_all_value))
|
|
|
|
if 'l' in self.reg_values:
|
|
if self.cpu.L != self.reg_values['l']:
|
|
result.append('L changed, is %02o, should be %02o'
|
|
% (self.cpu.L, self.reg_values['l']))
|
|
else:
|
|
if self.cpu.L != self.reg_all_value & 1:
|
|
result.append('L changed, is %02o, should be %02o'
|
|
% (self.cpu.L, self.reg_all_value & 1))
|
|
|
|
if 'pc' in self.reg_values:
|
|
if self.cpu.PC != self.reg_values['pc']:
|
|
result.append('PC changed, is %07o, should be %07o'
|
|
% (self.cpu.PC, self.reg_values['pc']))
|
|
else:
|
|
if self.cpu.PC != self.reg_all_value:
|
|
result.append('PC changed, is %07o, should be %07o'
|
|
% (self.cpu.PC, self.reg_all_value))
|
|
|
|
if 'ds' in self.reg_values:
|
|
if self.cpu.DS != self.reg_values['ds']:
|
|
result.append('DS changed, is %07o, should be %07o'
|
|
% (self.cpu.DS, self.reg_values['ds']))
|
|
else:
|
|
if self.cpu.DS != self.reg_all_value:
|
|
result.append('DS changed, is %07o, should be %07o'
|
|
% (self.cpu.DS, self.reg_all_value))
|
|
|
|
return result
|
|
|
|
def setd(self, state, var2):
|
|
"""Set display state."""
|
|
|
|
if state == 'on':
|
|
self.display_state = True
|
|
elif state == 'off':
|
|
self.display_state = False
|
|
else:
|
|
raise Exception('setd: bad state: %s' % str(state))
|
|
|
|
def execute(self, test, filename):
|
|
"""Execute test string in 'test'."""
|
|
|
|
# set globals
|
|
self.reg_values = {}
|
|
self.mem_values = {}
|
|
self.reg_all_value = '0'
|
|
self.mem_all_value = '0'
|
|
|
|
result = []
|
|
|
|
self.memory = Memory.Memory()
|
|
self.ptrptp = PtrPtp.PtrPtp()
|
|
self.ttyin = TtyIn.TtyIn()
|
|
self.ttyout = TtyOut.TtyOut()
|
|
self.cpu = MainCPU.MainCPU(self.memory, None, None,
|
|
None, self.ttyin, self.ttyout, self.ptrptp)
|
|
# turn trace OFF, initially
|
|
trace_map = collections.defaultdict(bool)
|
|
trace.set_trace_map(trace_map)
|
|
|
|
self.cpu.running = True
|
|
self.display_state = False
|
|
|
|
self.abort = True # abort on errors if True
|
|
|
|
# prepare the trace
|
|
trace.add_maincpu(self.cpu)
|
|
|
|
# clear registers and memory to 0 first
|
|
self.allreg(self.reg_all_value, None)
|
|
self.allmem(self.mem_all_value, None)
|
|
|
|
# show the DSL we are about execute
|
|
trace.comment('')
|
|
trace.comment(test)
|
|
trace.comment('-'*80)
|
|
|
|
# interpret the test instructions
|
|
suite = test.split(';')
|
|
for instruction in suite:
|
|
fields = instruction.split(None, 2)
|
|
if not fields:
|
|
continue;
|
|
|
|
opcode = fields[0].strip().lower()
|
|
if len(fields) == 1:
|
|
fld1 = fld2 = None
|
|
elif len(fields) == 2:
|
|
fld1 = fields[1].strip().lower()
|
|
fld2 = None
|
|
elif len(fields) == 3:
|
|
fld1 = fields[1].strip().lower()
|
|
fld2 = fields[2].strip().lower()
|
|
|
|
# cll the correct DSL primitive
|
|
if opcode == 'setreg':
|
|
r = self.setreg(fld1, fld2)
|
|
elif opcode == 'setmem':
|
|
r = self.setmem(fld1, fld2)
|
|
elif opcode == 'run':
|
|
r = self.run(fld1, fld2)
|
|
elif opcode == 'rununtil':
|
|
r = self.rununtil(fld1, fld2)
|
|
elif opcode == 'checkcycles':
|
|
r = self.checkcycles(fld1, fld2)
|
|
elif opcode == 'checkreg':
|
|
r = self.checkreg(fld1, fld2)
|
|
elif opcode == 'checkmem':
|
|
r = self.checkmem(fld1, fld2)
|
|
elif opcode == 'allreg':
|
|
r = self.allreg(fld1, fld2)
|
|
elif opcode == 'allmem':
|
|
r = self.allmem(fld1, fld2)
|
|
elif opcode == 'checkcpu':
|
|
r = self.checkcpu(fld1, fld2)
|
|
elif opcode == 'checkdcpu':
|
|
r = self.checkdcpu(fld1, fld2)
|
|
elif opcode == 'setd':
|
|
r = self.setd(fld1, fld2)
|
|
elif opcode == 'bootrom':
|
|
r = self.bootrom(fld1, fld2)
|
|
elif opcode == 'romwrite':
|
|
r = self.romwrite(fld1, fld2)
|
|
elif opcode == 'mount':
|
|
r = self.mount(fld1, fld2)
|
|
elif opcode == 'dismount':
|
|
r = self.dismount(fld1, fld2)
|
|
elif opcode == 'checkfile':
|
|
r = self.checkfile(fld1, fld2)
|
|
elif opcode == 'dumpmem':
|
|
r = self.dumpmem(fld1, fld2)
|
|
elif opcode == 'cmpmem':
|
|
r = self.cmpmem(fld1, fld2)
|
|
elif opcode == 'trace':
|
|
r = self.trace(fld1, fld2)
|
|
elif opcode == 'onerror':
|
|
r = self.onerror(fld1, fld2)
|
|
else:
|
|
print("Unrecognized opcode '%s' in: %s" % (opcode, test))
|
|
raise Exception("Unrecognized opcode '%s' in: %s" % (opcode, test))
|
|
|
|
if r is not None:
|
|
result.append(r)
|
|
if self.abort:
|
|
break
|
|
|
|
# now check all memory and regs for changes
|
|
r = self.check_all_mem()
|
|
if r is not None:
|
|
result.append(r)
|
|
|
|
r = self.check_all_regs()
|
|
if r is not None:
|
|
result.extend(r)
|
|
|
|
if result:
|
|
print(test)
|
|
print('\t' + '\n\t'.join(result))
|
|
|
|
|
|
def memdump(self, filename, start, number):
|
|
"""Dump memory from 'start' into 'filename', 'number' words dumped."""
|
|
|
|
with open(filename, 'w') as fd:
|
|
for addr in range(start, start+number, 8):
|
|
a = addr
|
|
llen = min(8, start+number - addr)
|
|
line = '%04o ' % addr
|
|
for _ in range(llen):
|
|
line += '%06o ' % self.memory.fetch(a, False)
|
|
a += 1
|
|
fd.write('%s\n' % line)
|
|
|
|
def main(self, filename):
|
|
"""Execute CPU tests from 'filename'."""
|
|
|
|
# get all tests from file
|
|
with open(filename, 'r') as fd:
|
|
lines = fd.readlines()
|
|
|
|
# read lines, join continued, get complete tests
|
|
tests = []
|
|
test = ''
|
|
for line in lines:
|
|
self.show_progress()
|
|
|
|
line = line[:-1] # strip newline
|
|
|
|
if not line:
|
|
continue # skip blank lines
|
|
|
|
if '#' in line:
|
|
index = line.find('#')
|
|
line = line[:index]
|
|
|
|
# remove trailing spaces
|
|
line = line.rstrip()
|
|
|
|
if not line:
|
|
continue # skip blank lines
|
|
|
|
if line[0] in ('\t', ' '): # continuation
|
|
if test:
|
|
if not test.endswith(';'):
|
|
test += ';'
|
|
test += ' ' + line[1:].strip()
|
|
else: # beginning of new test
|
|
if test:
|
|
tests.append(test)
|
|
test = line
|
|
|
|
# flush last test
|
|
if test:
|
|
tests.append(test)
|
|
|
|
# now do each test
|
|
for test in tests:
|
|
self.show_progress()
|
|
self.execute(test, filename)
|
|
|
|
################################################################################
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
import getopt
|
|
|
|
def usage(msg=None):
|
|
if msg:
|
|
print('*'*60)
|
|
print(msg)
|
|
print('*'*60)
|
|
print(__doc__)
|
|
|
|
try:
|
|
(opts, args) = getopt.gnu_getopt(sys.argv, "h", ["help"])
|
|
except getopt.GetoptError:
|
|
usage()
|
|
sys.exit(10)
|
|
|
|
for opt, arg in opts:
|
|
if opt in ("-h", "--help"):
|
|
usage()
|
|
sys.exit(0)
|
|
|
|
if len(args) != 2:
|
|
usage()
|
|
sys.exit(10)
|
|
|
|
filename = args[1]
|
|
try:
|
|
f = open(filename)
|
|
except IOError:
|
|
print("Sorry, can't find file '%s'" % filename)
|
|
sys.exit(10)
|
|
f.close()
|
|
|
|
test = TestCPU()
|
|
test.main(filename)
|