193 lines
7.6 KiB
Python
193 lines
7.6 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Iterable
|
|
|
|
from ..formatting import h16
|
|
from ..memory import MEMORY_REGIONS, MemoryRegion, region_for
|
|
from ..rom import Rom
|
|
from .constants import (
|
|
ON_CHIP_RAM_END,
|
|
ON_CHIP_RAM_START,
|
|
P7DDR,
|
|
P7DR,
|
|
P9DDR,
|
|
P9DR,
|
|
REGISTER_FIELD_END,
|
|
REGISTER_FIELD_START,
|
|
SCI1_BRR,
|
|
SCI1_RDR,
|
|
SCI1_SCR,
|
|
SCI1_SMR,
|
|
SCI1_SSR,
|
|
SCI1_TDR,
|
|
)
|
|
from .peripherals.lcd import LCD, LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS
|
|
from .peripherals.p9_bus import P9Bus
|
|
from .peripherals.x24164 import factory_default_words_from_rom
|
|
from .sci import SCI1
|
|
|
|
|
|
@dataclass
|
|
class MemoryAccess:
|
|
address: int
|
|
size: int
|
|
value: int
|
|
kind: str
|
|
region: str
|
|
|
|
|
|
class MemoryMap:
|
|
def __init__(self, rom_bytes: bytes, sci1: SCI1 | None = None, *, p7_input: int = 0xFF) -> None:
|
|
self.rom = Rom(rom_bytes, base=0)
|
|
self.sci1 = sci1 if sci1 is not None else SCI1()
|
|
self.lcd = LCD()
|
|
self.p9_bus = P9Bus()
|
|
self.ram = bytearray(ON_CHIP_RAM_END - ON_CHIP_RAM_START + 1)
|
|
self.registers = bytearray(REGISTER_FIELD_END - REGISTER_FIELD_START + 1)
|
|
self.external: dict[int, int] = {}
|
|
self.port_inputs: dict[int, int] = {P7DR: p7_input & 0xFF}
|
|
self.access_log: list[MemoryAccess] = []
|
|
|
|
self._set_register(SCI1_SMR, self.sci1.smr)
|
|
self._set_register(SCI1_BRR, self.sci1.brr)
|
|
self._set_register(SCI1_SCR, self.sci1.scr)
|
|
self._set_register(SCI1_TDR, self.sci1.tdr)
|
|
self._set_register(SCI1_SSR, self.sci1.ssr)
|
|
self._set_register(SCI1_RDR, self.sci1.rdr)
|
|
|
|
def region(self, address: int) -> MemoryRegion:
|
|
return region_for(address & 0xFFFF)
|
|
|
|
def read8(self, address: int) -> int:
|
|
address &= 0xFFFF
|
|
if address in (SCI1_SMR, SCI1_BRR, SCI1_SCR, SCI1_TDR, SCI1_SSR, SCI1_RDR):
|
|
value = self.sci1.read(address)
|
|
self._set_register(address, value)
|
|
elif address == LCD_E_CLOCK_STATUS:
|
|
value = self.lcd.read_status()
|
|
elif address == LCD_E_CLOCK_DATA:
|
|
value = self.lcd.read_data()
|
|
elif address == P7DR:
|
|
value = self._read_port_data(P7DDR, P7DR)
|
|
elif address in self.external:
|
|
value = self.external[address]
|
|
elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
|
|
value = self.ram[address - ON_CHIP_RAM_START]
|
|
elif address == P9DDR:
|
|
value = self.p9_bus.read_ddr()
|
|
self._set_register(address, value)
|
|
elif address == P9DR:
|
|
value = self.p9_bus.read_dr()
|
|
elif REGISTER_FIELD_START <= address <= REGISTER_FIELD_END:
|
|
value = self.registers[address - REGISTER_FIELD_START]
|
|
elif self.rom.contains(address):
|
|
value = self.rom.u8(address)
|
|
else:
|
|
value = self.external.get(address, 0xFF)
|
|
self._log("read", address, 1, value)
|
|
return value
|
|
|
|
def read16(self, address: int) -> int:
|
|
high = self.read8(address)
|
|
low = self.read8((address + 1) & 0xFFFF)
|
|
return (high << 8) | low
|
|
|
|
def write8(self, address: int, value: int) -> None:
|
|
address &= 0xFFFF
|
|
value &= 0xFF
|
|
if address in (SCI1_SMR, SCI1_BRR, SCI1_SCR, SCI1_TDR, SCI1_SSR, SCI1_RDR):
|
|
self.sci1.write(address, value)
|
|
self._set_register(address, self.sci1.read(address))
|
|
elif address == LCD_E_CLOCK_STATUS:
|
|
self.lcd.write_command(value)
|
|
self.external[address] = value
|
|
elif address == LCD_E_CLOCK_DATA:
|
|
self.lcd.write_data(value)
|
|
self.external[address] = value
|
|
elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
|
|
self.ram[address - ON_CHIP_RAM_START] = value
|
|
elif address in (P7DDR, P7DR):
|
|
self._set_register(address, value)
|
|
elif address == P9DDR:
|
|
self._set_register(address, self.p9_bus.write_ddr(value))
|
|
elif address == P9DR:
|
|
self._set_register(address, self.p9_bus.write_dr(value))
|
|
elif REGISTER_FIELD_START <= address <= REGISTER_FIELD_END:
|
|
self._set_register(address, value)
|
|
elif self.rom.contains(address):
|
|
# The ROM image spans the whole address space, but the H8/536 map
|
|
# can place external RAM/peripherals in these ranges. Keep writes
|
|
# as external overrides while leaving instruction fetch immutable.
|
|
self.external[address] = value
|
|
else:
|
|
self.external[address] = value
|
|
self._log("write", address, 1, value)
|
|
|
|
def write16(self, address: int, value: int) -> None:
|
|
self.write8(address, (value >> 8) & 0xFF)
|
|
self.write8((address + 1) & 0xFFFF, value & 0xFF)
|
|
|
|
def inject_sci1_rx_byte(self, value: int) -> None:
|
|
self.sci1.inject_rx(value)
|
|
self._set_register(SCI1_RDR, self.sci1.read(SCI1_RDR))
|
|
self._set_register(SCI1_SSR, self.sci1.read(SCI1_SSR))
|
|
|
|
def register8(self, address: int) -> int:
|
|
return self.registers[(address & 0xFFFF) - REGISTER_FIELD_START]
|
|
|
|
def register16(self, address: int) -> int:
|
|
address &= 0xFFFF
|
|
return (self.register8(address) << 8) | self.register8((address + 1) & 0xFFFF)
|
|
|
|
def set_register8(self, address: int, value: int) -> None:
|
|
self._set_register(address & 0xFFFF, value)
|
|
|
|
def set_register16(self, address: int, value: int) -> None:
|
|
address &= 0xFFFF
|
|
self._set_register(address, (value >> 8) & 0xFF)
|
|
self._set_register((address + 1) & 0xFFFF, value & 0xFF)
|
|
|
|
def set_port_input(self, data_register: int, value: int, *, mask: int = 0xFF) -> None:
|
|
data_register &= 0xFFFF
|
|
old = self.port_inputs.get(data_register, 0xFF)
|
|
self.port_inputs[data_register] = ((old & ~mask) | (value & mask)) & 0xFF
|
|
|
|
def seed_factory_eeprom_and_shadow(self) -> None:
|
|
for offset, word in factory_default_words_from_rom(self.rom.data):
|
|
address = 0xF400 + offset
|
|
self.external[address & 0xFFFF] = (word >> 8) & 0xFF
|
|
self.external[(address + 1) & 0xFFFF] = word & 0xFF
|
|
self.p9_bus.x24164_bus.seed_factory_defaults_from_rom(self.rom.data)
|
|
self.p9_bus.clear_x24164_trace()
|
|
|
|
def load_eeprom_image(self, data: bytes | bytearray, *, mirror_shadow: bool = True) -> None:
|
|
self.p9_bus.x24164_bus.load_linear(data)
|
|
if mirror_shadow:
|
|
image = self.p9_bus.x24164_bus.dump_linear()
|
|
for offset in range(min(0x0100, len(image))):
|
|
self.external[(0xF400 + offset) & 0xFFFF] = image[offset]
|
|
self.p9_bus.clear_x24164_trace()
|
|
|
|
def dump_eeprom_image(self) -> bytes:
|
|
return self.p9_bus.x24164_bus.dump_linear()
|
|
|
|
def clear_eeprom_write_log(self) -> None:
|
|
self.p9_bus.x24164_bus.clear_write_log()
|
|
|
|
def _set_register(self, address: int, value: int) -> None:
|
|
self.registers[address - REGISTER_FIELD_START] = value & 0xFF
|
|
|
|
def _read_port_data(self, direction_register: int, data_register: int) -> int:
|
|
ddr = self.registers[direction_register - REGISTER_FIELD_START]
|
|
latch = self.registers[data_register - REGISTER_FIELD_START]
|
|
pins = self.port_inputs.get(data_register, 0xFF)
|
|
return ((latch & ddr) | (pins & ~ddr)) & 0xFF
|
|
|
|
def _log(self, kind: str, address: int, size: int, value: int) -> None:
|
|
self.access_log.append(MemoryAccess(address, size, value, kind, self.region(address).name))
|
|
|
|
|
|
def describe_regions(regions: Iterable[MemoryRegion] = MEMORY_REGIONS) -> str:
|
|
return "\n".join(f"{h16(region.start)}-{h16(region.end)} {region.name} {region.kind}" for region in regions)
|