Eprom emulation
This commit is contained in:
@@ -23,6 +23,8 @@ from .constants import (
|
||||
IPRE,
|
||||
ON_CHIP_RAM_END,
|
||||
ON_CHIP_RAM_START,
|
||||
P7DDR,
|
||||
P7DR,
|
||||
P9DDR,
|
||||
P9DR,
|
||||
RAMCR,
|
||||
@@ -55,7 +57,7 @@ from .cpu import CPUState
|
||||
from .errors import EmulatorError, UnsupportedInstruction
|
||||
from .fast_paths import P9FastPath, P9FastPathConfig, P9FastPathEvent
|
||||
from .memory import MemoryAccess, MemoryMap, describe_regions
|
||||
from .peripherals import LCD, P9TraceEvent, X24164Bus, X24164Device, X24164TraceEvent
|
||||
from .peripherals import LCD, P9TraceEvent, X24164Bus, X24164Device, X24164TraceEvent, factory_default_words_from_rom
|
||||
from .runner import H8536Emulator, RunReport
|
||||
from .sci import SCI1, SciTxEvent
|
||||
from .uart import UartTiming
|
||||
@@ -88,6 +90,8 @@ __all__ = [
|
||||
"MemoryMap",
|
||||
"ON_CHIP_RAM_END",
|
||||
"ON_CHIP_RAM_START",
|
||||
"P7DDR",
|
||||
"P7DR",
|
||||
"P9DDR",
|
||||
"P9DR",
|
||||
"P9FastPath",
|
||||
@@ -130,6 +134,7 @@ __all__ = [
|
||||
"build_arg_parser",
|
||||
"describe_regions",
|
||||
"discover_rom_path",
|
||||
"factory_default_words_from_rom",
|
||||
"load_rom",
|
||||
"main",
|
||||
]
|
||||
|
||||
@@ -181,6 +181,8 @@ class ReplayConfig:
|
||||
p9_fast_path: bool = True
|
||||
p9_fast_input: int = 0xFF
|
||||
p9_fast_optimistic_wrapper: bool = False
|
||||
p7_input: int = 0xFF
|
||||
eeprom_seed: str = "blank"
|
||||
|
||||
|
||||
def parse_bench_replay_log_text(text: str) -> BenchReplayLog:
|
||||
@@ -244,6 +246,8 @@ def run_bench_replay(log_path: Path, *, rom_path: Path | None = None, config: Re
|
||||
p9_fast_path_enabled=config.p9_fast_path,
|
||||
p9_fast_default_input_byte=config.p9_fast_input,
|
||||
p9_fast_default_wrapper_success=config.p9_fast_optimistic_wrapper,
|
||||
p7_input=config.p7_input,
|
||||
eeprom_seed=config.eeprom_seed,
|
||||
)
|
||||
|
||||
context = RunContext()
|
||||
@@ -369,6 +373,8 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--no-p9-fast-path", action="store_true", help="disable shortcut handling for known P9 routines")
|
||||
parser.add_argument("--p9-fast-input", type=lambda text: int(text, 0), default=ReplayConfig.p9_fast_input)
|
||||
parser.add_argument("--p9-fast-optimistic-wrapper", action="store_true", help="legacy fallback for older wrapper experiments; known BFE0/BFFE wrappers use the X24164 model")
|
||||
parser.add_argument("--p7-input", type=lambda text: int(text, 0), default=ReplayConfig.p7_input, help="external P7 pin state for input bits; DIP-off board default is 0xFF")
|
||||
parser.add_argument("--eeprom-seed", choices=("blank", "factory"), default=ReplayConfig.eeprom_seed, help="initial X24164/shadow state before reset")
|
||||
parser.add_argument("--assert-bench-parity", action="store_true", help="exit nonzero if emulator behavior diverges from the bench log")
|
||||
parser.add_argument("--json", action="store_true", help="emit JSON")
|
||||
return parser
|
||||
@@ -392,6 +398,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
p9_fast_path=not args.no_p9_fast_path,
|
||||
p9_fast_input=args.p9_fast_input,
|
||||
p9_fast_optimistic_wrapper=args.p9_fast_optimistic_wrapper,
|
||||
p7_input=args.p7_input,
|
||||
eeprom_seed=args.eeprom_seed,
|
||||
),
|
||||
)
|
||||
if args.json:
|
||||
|
||||
@@ -45,6 +45,8 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--p9-fast-path", action="store_true", help="shortcut known P9 bit-banged transfer routines for exploration")
|
||||
parser.add_argument("--p9-fast-input", type=parse_int, default=0xFF, help="default byte returned by the P9 fast-path read routine")
|
||||
parser.add_argument("--p9-fast-optimistic-wrapper", action="store_true", help="legacy fallback for older wrapper experiments; known BFE0/BFFE wrappers use the X24164 model")
|
||||
parser.add_argument("--p7-input", type=parse_int, default=0xFF, help="external P7 pin state for input bits; DIP-off board default is 0xFF")
|
||||
parser.add_argument("--eeprom-seed", choices=("blank", "factory"), default="blank", help="initial X24164/shadow state before reset")
|
||||
return parser
|
||||
|
||||
|
||||
@@ -65,6 +67,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
p9_fast_path_enabled=args.p9_fast_path,
|
||||
p9_fast_default_input_byte=args.p9_fast_input,
|
||||
p9_fast_default_wrapper_success=args.p9_fast_optimistic_wrapper,
|
||||
p7_input=args.p7_input,
|
||||
eeprom_seed=args.eeprom_seed,
|
||||
)
|
||||
print(f"rom={rom_path}")
|
||||
print(f"reset_vector={h16(emulator.reset_vector())}")
|
||||
|
||||
@@ -8,6 +8,8 @@ SCI1_TDR = 0xFEDB
|
||||
SCI1_SSR = 0xFEDC
|
||||
SCI1_RDR = 0xFEDD
|
||||
|
||||
P7DDR = 0xFE8C
|
||||
P7DR = 0xFE8E
|
||||
P9DDR = 0xFEFE
|
||||
P9DR = 0xFEFF
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ from ..rom import Rom
|
||||
from .constants import (
|
||||
ON_CHIP_RAM_END,
|
||||
ON_CHIP_RAM_START,
|
||||
P7DDR,
|
||||
P7DR,
|
||||
P9DDR,
|
||||
P9DR,
|
||||
REGISTER_FIELD_END,
|
||||
@@ -22,6 +24,7 @@ from .constants import (
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
@@ -35,7 +38,7 @@ class MemoryAccess:
|
||||
|
||||
|
||||
class MemoryMap:
|
||||
def __init__(self, rom_bytes: bytes, sci1: SCI1 | None = None) -> None:
|
||||
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()
|
||||
@@ -43,6 +46,7 @@ class MemoryMap:
|
||||
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)
|
||||
@@ -64,6 +68,8 @@ class MemoryMap:
|
||||
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:
|
||||
@@ -101,6 +107,8 @@ class MemoryMap:
|
||||
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:
|
||||
@@ -140,9 +148,28 @@ class MemoryMap:
|
||||
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 _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))
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from .lcd import LCD, LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS, LCD_LINE_WIDTH
|
||||
from .p9_bus import P9_ACK_BIT, P9_STROBE_BIT, P9Bus, P9StrobeEvent, P9TraceEvent
|
||||
from .x24164 import X24164Bus, X24164Device, X24164TraceEvent
|
||||
from .x24164 import X24164Bus, X24164Device, X24164TraceEvent, factory_default_words_from_rom
|
||||
|
||||
__all__ = [
|
||||
"LCD_E_CLOCK_DATA",
|
||||
@@ -17,4 +17,5 @@ __all__ = [
|
||||
"X24164Bus",
|
||||
"X24164Device",
|
||||
"X24164TraceEvent",
|
||||
"factory_default_words_from_rom",
|
||||
]
|
||||
|
||||
@@ -237,6 +237,10 @@ class P9Bus:
|
||||
self._append_x24164_trace()
|
||||
return success
|
||||
|
||||
def clear_x24164_trace(self) -> None:
|
||||
self.x24164_bus.trace_events.clear()
|
||||
self._x24164_trace_index = 0
|
||||
|
||||
def _record_transmitted_bit(self, bit: int) -> None:
|
||||
self.transmitted_bits.append(bit)
|
||||
self.trace_events.append(P9TraceEvent("tx_bit", self.ddr, self.dr_latch, bit=bit))
|
||||
|
||||
@@ -4,6 +4,10 @@ from dataclasses import dataclass, field
|
||||
|
||||
|
||||
X24164_SIZE = 2048
|
||||
X24164_FACTORY_DEFAULT_BASE = 0xC964
|
||||
X24164_FACTORY_DEFAULT_BYTES = 0x0100
|
||||
X24164_LOGICAL_PAGE_SIZE = 0x0100
|
||||
X24164_LOGICAL_PAGE_COUNT = 0x10
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -214,6 +218,16 @@ class X24164Bus:
|
||||
)
|
||||
return True
|
||||
|
||||
def seed_factory_defaults_from_rom(self, rom_bytes: bytes) -> None:
|
||||
for offset, word in factory_default_words_from_rom(rom_bytes):
|
||||
for page in range(X24164_LOGICAL_PAGE_COUNT):
|
||||
self.write_linear_word((page * X24164_LOGICAL_PAGE_SIZE) + offset, word)
|
||||
|
||||
for page in range(X24164_LOGICAL_PAGE_COUNT):
|
||||
base = page * X24164_LOGICAL_PAGE_SIZE
|
||||
for offset in range(0, 8, 2):
|
||||
self.write_linear_word(base + offset, 0x2020)
|
||||
|
||||
def trace_lines(self, limit: int | None = None) -> list[str]:
|
||||
events = self.trace_events if limit is None else self.trace_events[-limit:]
|
||||
return [event.line() for event in events]
|
||||
@@ -360,3 +374,15 @@ def default_x24164_devices() -> list[X24164Device]:
|
||||
X24164Device("x24164_a0_lower_2k", 0xA0),
|
||||
X24164Device("x24164_e0_upper_2k", 0xE0),
|
||||
]
|
||||
|
||||
|
||||
def factory_default_words_from_rom(rom_bytes: bytes) -> list[tuple[int, int]]:
|
||||
end = X24164_FACTORY_DEFAULT_BASE + X24164_FACTORY_DEFAULT_BYTES
|
||||
if len(rom_bytes) < end:
|
||||
raise ValueError(f"ROM is too small for X24164 factory default table at {X24164_FACTORY_DEFAULT_BASE:04X}")
|
||||
words: list[tuple[int, int]] = []
|
||||
for offset in range(0, X24164_FACTORY_DEFAULT_BYTES, 2):
|
||||
address = X24164_FACTORY_DEFAULT_BASE + offset
|
||||
word = (rom_bytes[address] << 8) | rom_bytes[address + 1]
|
||||
words.append((offset, word))
|
||||
return words
|
||||
|
||||
@@ -92,11 +92,17 @@ class H8536Emulator:
|
||||
p9_fast_path_enabled: bool = False,
|
||||
p9_fast_default_input_byte: int = 0xFF,
|
||||
p9_fast_default_wrapper_success: bool = False,
|
||||
p7_input: int = 0xFF,
|
||||
eeprom_seed: str = "blank",
|
||||
) -> None:
|
||||
if not rom_bytes:
|
||||
raise ValueError("ROM image is empty")
|
||||
if eeprom_seed not in {"blank", "factory"}:
|
||||
raise ValueError("eeprom_seed must be 'blank' or 'factory'")
|
||||
self.sci1 = SCI1()
|
||||
self.memory = MemoryMap(rom_bytes, self.sci1)
|
||||
self.memory = MemoryMap(rom_bytes, self.sci1, p7_input=p7_input)
|
||||
if eeprom_seed == "factory":
|
||||
self.memory.seed_factory_eeprom_and_shadow()
|
||||
self.memory.p9_bus.default_wrapper_success = bool(p9_fast_default_wrapper_success)
|
||||
self.p9_fast_path = p9_fast_path or P9FastPath(
|
||||
P9FastPathConfig(enabled=p9_fast_path_enabled, default_input_byte=p9_fast_default_input_byte)
|
||||
|
||||
@@ -214,6 +214,8 @@ def run_rx_probe(
|
||||
p9_fast_path: bool = True,
|
||||
p9_fast_input: int = 0xFF,
|
||||
p9_fast_optimistic_wrapper: bool = False,
|
||||
p7_input: int = 0xFF,
|
||||
eeprom_seed: str = "blank",
|
||||
stop_after_tx_frame: bool = True,
|
||||
) -> tuple[Path, H8536Emulator, str, list[FrameResult]]:
|
||||
rom_bytes, discovered_rom_path = load_rom(rom_path)
|
||||
@@ -226,6 +228,8 @@ def run_rx_probe(
|
||||
p9_fast_path_enabled=p9_fast_path,
|
||||
p9_fast_default_input_byte=p9_fast_input,
|
||||
p9_fast_default_wrapper_success=p9_fast_optimistic_wrapper,
|
||||
p7_input=p7_input,
|
||||
eeprom_seed=eeprom_seed,
|
||||
)
|
||||
|
||||
boot_context = RunContext()
|
||||
@@ -271,6 +275,8 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--no-p9-fast-path", action="store_true", help="disable shortcut handling for known P9 routines")
|
||||
parser.add_argument("--p9-fast-input", type=parse_int, default=0xFF, help="default byte returned by the P9 fast-path read routine")
|
||||
parser.add_argument("--p9-fast-optimistic-wrapper", action="store_true", help="legacy fallback for older wrapper experiments; known BFE0/BFFE wrappers use the X24164 model")
|
||||
parser.add_argument("--p7-input", type=parse_int, default=0xFF, help="external P7 pin state for input bits; DIP-off board default is 0xFF")
|
||||
parser.add_argument("--eeprom-seed", choices=("blank", "factory"), default="blank", help="initial X24164/shadow state before reset")
|
||||
return parser
|
||||
|
||||
|
||||
@@ -297,6 +303,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
p9_fast_path=not args.no_p9_fast_path,
|
||||
p9_fast_input=args.p9_fast_input,
|
||||
p9_fast_optimistic_wrapper=args.p9_fast_optimistic_wrapper,
|
||||
p7_input=args.p7_input,
|
||||
eeprom_seed=args.eeprom_seed,
|
||||
stop_after_tx_frame=not args.keep_listening,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user