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 pc: int | None = None 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.current_pc: int | None = None 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, self.current_pc)) 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)