mirror of
https://github.com/KrisKennaway/ii-vision.git
synced 2024-09-27 04:54:59 +00:00
90f696b8e4
simulating the W5100). This will hopefully be useful for troubleshooting and testing player behaviour more precisely, e.g. - trapping read/write access to unexpected memory areas - asserting invariants on the processor state across loops - measuring cycle timing - tracing program execution This already gets as far as negotiating the TCP connect. The major remaining piece seems to be the TCP buffer management on the W5100 side.
135 lines
3.7 KiB
Python
135 lines
3.7 KiB
Python
"""Abstract hardware machine with CPU and memory."""
|
|
|
|
import enum
|
|
from typing import Dict, Tuple, Callable, Optional
|
|
|
|
from py65 import memory as py65_memory
|
|
|
|
|
|
class AccessMode(enum.IntFlag):
|
|
READ = 0x1
|
|
WRITE = 0x2
|
|
RW = READ | WRITE
|
|
|
|
|
|
class Event(object):
|
|
def __init__(self, event_type, details):
|
|
self.event_type = event_type
|
|
self.details = details
|
|
|
|
def __str__(self) -> str:
|
|
return "Event(%s): %s" % (self.event_type, self.details)
|
|
|
|
|
|
def Log(region:str, message:str):
|
|
print("%s event: %s" % (region, message))
|
|
|
|
|
|
# TODO: why?
|
|
def _Event(region:str, message:str):
|
|
def _Event(_):
|
|
Log(region, message)
|
|
|
|
return _Event
|
|
|
|
|
|
class TrapException(Exception):
|
|
def __init__(self, address:int, msg:str):
|
|
self.address = address
|
|
self.msg = msg
|
|
|
|
def __str__(self) -> str:
|
|
return "$%04X: %s" % (self.address, self.msg)
|
|
|
|
|
|
class SoftSwitch:
|
|
def __init__(
|
|
self, name: str, clear_addr: int, set_addr: int,
|
|
status_addr: int, mode: AccessMode = AccessMode.WRITE,
|
|
callback=None):
|
|
self.name = name
|
|
self.clear_addr = clear_addr
|
|
self.set_addr = set_addr
|
|
self.status_addr = status_addr
|
|
|
|
# Whether switch is set/clear by READ/WRITE or both
|
|
self.mode = mode # type: AccessMode
|
|
|
|
self.state = False # type: bool
|
|
self.callback = callback # type: Callable[[bool], Optional[int]]
|
|
|
|
def set(self) -> Optional[int]:
|
|
self.state = True
|
|
Log(self.name, "Setting soft switch")
|
|
return self.callback(True)
|
|
|
|
def clear(self) -> Optional[int]:
|
|
self.state = False
|
|
Log(self.name, "Clearing soft switch")
|
|
return self.callback(False)
|
|
|
|
def get(self) -> int:
|
|
Log(self.name, "Reading soft switch (%s)" % (
|
|
"on" if self.state else "off"))
|
|
return 0x80 & self.state
|
|
|
|
@staticmethod
|
|
def unimplemented(_):
|
|
raise NotImplementedError
|
|
|
|
def register(self, io_map):
|
|
def _clear(mode, value):
|
|
return self.clear()
|
|
|
|
def _set(mode, value):
|
|
return self.set()
|
|
|
|
def _get(mode, value):
|
|
return self.get()
|
|
|
|
io_map[self.clear_addr] = (
|
|
self.mode, "%s OFF" % self.name, _clear)
|
|
io_map[self.set_addr] = (
|
|
self.mode, "%s ON" % self.name, _set)
|
|
io_map[self.status_addr] = (
|
|
AccessMode.READ, "%s READ" % self.name, _get)
|
|
|
|
|
|
class Machine:
|
|
def __init__(self):
|
|
self.memory_manager = None # type: memory.MemoryManager
|
|
self.memory = None # type: py65_memory.ObservableMemory
|
|
self.cpu = None
|
|
|
|
self.io_map = {} # type: Dict[int, Tuple[AccessMode, str, Callable]]
|
|
|
|
@staticmethod
|
|
def unimplemented_io_callback(mode, value):
|
|
raise NotImplementedError
|
|
|
|
def io_interceptor(self, address, value=None):
|
|
access_mode = (
|
|
AccessMode.READ if (value is None) else AccessMode.WRITE)
|
|
|
|
try:
|
|
(mode, name, callback) = self.io_map[address]
|
|
|
|
if access_mode & mode:
|
|
if access_mode & AccessMode.READ:
|
|
print("==== IO EVENT: READ %s" % name)
|
|
else:
|
|
print("==== IO EVENT: WRITE %s -> %02x" % (name, value))
|
|
if callback:
|
|
return callback(access_mode, value)
|
|
else:
|
|
return
|
|
else:
|
|
print("**** IO EVENT with unexpected mode: %s" % access_mode)
|
|
raise TrapException(address, access_mode)
|
|
except KeyError:
|
|
if value:
|
|
raise TrapException(
|
|
address, 'Wrote %02X ("%s")' % (value, chr(value)))
|
|
else:
|
|
raise TrapException(address, 'Read')
|