ii-vision/simulator/memory.py

108 lines
3.0 KiB
Python

from collections import defaultdict
from py65 import memory
class WriteProtectedException(Exception):
def __init__(self, name, addr, value):
self.name = name
self.addr = addr
self.value = value
def __str__(self):
return 'Write denied to %s ($%04X): $%02X' % (
self.name, self.addr, self.value)
class UndefinedEntryPointException(Exception):
def __init__(self, region, prev_addr, addr):
self.region = region
self.prev_addr = prev_addr
self.addr = addr
def __str__(self):
return 'Entered region %s via undefined entry point: $%04X --> $%04X' % (
self.region.name, self.prev_addr, self.addr)
class MemoryRegion:
def __init__(
self, name, start, end, read_interceptor=None,
write_interceptor=None, entrypoints=None,
writable=True):
self.name = name
self.start = start
self.end = end
self.read_interceptor = read_interceptor
self.write_interceptor = write_interceptor
self.writable = writable
# Maps PC to handler
self.entrypoints = entrypoints or {}
class MemoryManager:
def __init__(self, memory_map):
self.entrypoints = defaultdict(list)
default_region = MemoryRegion('default', 0x0, 0xffff)
def _default_region():
return default_region
self.regions = defaultdict(_default_region)
self.memory = memory.ObservableMemory()
self._memory_map = memory_map
def enable(self):
for region in self._memory_map:
self.RegisterRegion(region)
def MaybeInterceptExecution(self, cpu, old_pc):
pc = cpu.pc
if pc in self.entrypoints:
handlers = self.entrypoints[pc]
else:
handlers = []
if self.regions[old_pc] != self.regions[pc]:
print("Entering region %s" % self.regions[pc].name)
# Don't worry if last instruction was RTS
if self.memory[old_pc] != 0x60 and not handlers:
raise UndefinedEntryPointException(self.regions[pc], old_pc, pc)
for handler in handlers:
handler(cpu)
def RegisterRegion(self, region):
addr_range = range(region.start, region.end + 1)
if region.read_interceptor:
self.memory.subscribe_to_read(addr_range, region.read_interceptor)
if region.write_interceptor:
self.memory.subscribe_to_write(addr_range, region.write_interceptor)
if not region.writable:
self.memory.subscribe_to_write(
addr_range, self.DenyWritesToRegion(region))
for addr in addr_range:
self.regions[addr] = region
for addr, handler in region.entrypoints.items():
self.entrypoints[addr].append(handler)
# TODO: should trap by default
@staticmethod
def DenyWritesToRegion(region):
def _DenyWritesInterceptor(addr, value):
raise WriteProtectedException(region.name, addr, value)
return _DenyWritesInterceptor