emulator improvements
This commit is contained in:
120
h8536/emulator/memory.py
Normal file
120
h8536/emulator/memory.py
Normal file
@@ -0,0 +1,120 @@
|
||||
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,
|
||||
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_E_CLOCK_DATA, LCD_E_CLOCK_STATUS
|
||||
from .peripherals.p9_bus import P9_ACK_BIT
|
||||
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) -> None:
|
||||
self.rom = Rom(rom_bytes, base=0)
|
||||
self.sci1 = sci1 if sci1 is not None else SCI1()
|
||||
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.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 in self.external:
|
||||
value = self.external[address]
|
||||
elif address in (LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS):
|
||||
# LCD E-clock/status space. Default to ready/zero so boot can pass
|
||||
# busy-flag polling until a fuller external bus model exists.
|
||||
value = 0x00
|
||||
elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
|
||||
value = self.ram[address - ON_CHIP_RAM_START]
|
||||
elif REGISTER_FIELD_START <= address <= REGISTER_FIELD_END:
|
||||
value = self.registers[address - REGISTER_FIELD_START]
|
||||
if address == P9DR and not (self.registers[P9DDR - REGISTER_FIELD_START] & 0x80):
|
||||
# P97 is used as an input during the serial panel/camera-side
|
||||
# bit-bang handshake. With no external device modeled, hold the
|
||||
# input low so the firmware sees an idle/acknowledged bus rather
|
||||
# than reading back its previous output latch forever.
|
||||
value &= ~P9_ACK_BIT
|
||||
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 ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
|
||||
self.ram[address - ON_CHIP_RAM_START] = 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 _set_register(self, address: int, value: int) -> None:
|
||||
self.registers[address - REGISTER_FIELD_START] = value & 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)
|
||||
Reference in New Issue
Block a user