1
0

Emualtor improements

This commit is contained in:
Aiden
2026-05-25 18:55:50 +10:00
parent 05e1237acc
commit 1fabf6587d
10 changed files with 413 additions and 15 deletions

View File

@@ -7,12 +7,93 @@ from pathlib import Path
from ..formatting import h16, parse_int
from .cli import load_rom
from .constants import P9DDR, P9DR, SCI1_TDR
from .constants import (
P9DDR,
P9DR,
SCI1_BRR,
SCI1_RDR,
SCI1_SCR,
SCI1_SMR,
SCI1_SSR,
SCI1_TDR,
SCI_SCR_TE,
SCI_SCR_TIE,
SCI_SSR_TDRE,
VECTOR_SCI1_TXI,
)
from .errors import UnsupportedInstruction
from .runner import H8536Emulator
DEFAULT_WATCH_PCS = (0xC08B, 0xC0DB, 0xC121, 0xBFE0, 0xBFFE, 0xC059)
SCI1_PROBE_REGISTERS = {
SCI1_SCR: "SCR",
SCI1_TDR: "TDR",
SCI1_SSR: "SSR",
SCI1_RDR: "RDR",
}
def _format_bytes(data: bytes, *, limit: int = 96) -> str:
if len(data) <= limit:
return data.hex(" ").upper()
head = data[: limit // 2].hex(" ").upper()
tail = data[-(limit // 2) :].hex(" ").upper()
return f"{head} ... {tail} ({len(data)} bytes)"
def _format_six_byte_frames(data: bytes, *, limit: int = 8) -> str:
frames = [data[index : index + 6] for index in range(0, len(data) - 5, 6)]
if not frames:
return ""
recent = frames[-limit:]
return " | ".join(frame.hex(" ").upper() for frame in recent)
@dataclass(frozen=True)
class SCI1Snapshot:
smr: int
brr: int
scr: int
ssr: int
tdr: int
rdr: int
tx_ready_delay: int | None = None
def line(self) -> str:
fields = [
f"SMR={self.smr:02X}",
f"BRR={self.brr:02X}",
f"SCR={self.scr:02X}",
f"SSR={self.ssr:02X}",
f"TDR={self.tdr:02X}",
f"RDR={self.rdr:02X}",
]
if self.tx_ready_delay is not None:
fields.append(f"tx_ready_delay={self.tx_ready_delay}")
return "sci1=" + " ".join(fields)
@dataclass(frozen=True)
class SCI1TXISummary:
tie: bool
te: bool
tdre: bool
vector_target: int | None
priority: int
interrupt_mask: int
interrupt_depth: int
def line(self) -> str:
pending = self.tie and self.tdre and self.vector_target is not None
serviceable = pending and self.interrupt_depth == 0 and self.priority > self.interrupt_mask
vector = h16(self.vector_target) if self.vector_target is not None else "none"
return (
"sci1_txi="
f"TIE={int(self.tie)} TE={int(self.te)} TDRE={int(self.tdre)} "
f"vector={vector} priority={self.priority} mask={self.interrupt_mask} "
f"depth={self.interrupt_depth} pending={int(pending)} serviceable={int(serviceable)}"
)
@dataclass(frozen=True)
@@ -49,6 +130,8 @@ class ProbeReport:
p9_fast_events: int = 0
p9_accesses: list[str] = field(default_factory=list)
sci_accesses: list[str] = field(default_factory=list)
sci1: SCI1Snapshot | None = None
sci1_txi: SCI1TXISummary | None = None
watch_snapshots: list[WatchSnapshot] = field(default_factory=list)
unsupported: str | None = None
@@ -57,12 +140,17 @@ class ProbeReport:
f"steps={self.steps}",
f"pc={h16(self.pc)}",
f"stopped={self.stopped_reason}",
"tx_bytes=" + self.tx_bytes.hex(" ").upper(),
"tx_bytes=" + _format_bytes(self.tx_bytes),
"tx_6byte_recent=" + _format_six_byte_frames(self.tx_bytes),
"p9_bytes=" + " ".join(f"{byte:02X}" for byte in self.p9_bytes[-32:]),
"p9_fast_bytes=" + " ".join(f"{byte:02X}" for byte in self.p9_fast_bytes[-32:]),
f"p9_fast_events={self.p9_fast_events}",
"hot_pcs=" + ", ".join(f"{h16(pc)}:{count}" for pc, count in self.hot_pcs.most_common(hot_limit)),
]
if self.sci1:
lines.append(self.sci1.line())
if self.sci1_txi:
lines.append(self.sci1_txi.line())
if self.unsupported:
lines.append(f"unsupported={self.unsupported}")
if self.p9_accesses:
@@ -77,6 +165,31 @@ class ProbeReport:
return lines
def _sci1_snapshot(emulator: H8536Emulator) -> SCI1Snapshot:
sci1 = emulator.sci1
return SCI1Snapshot(
smr=sci1.smr,
brr=sci1.brr,
scr=sci1.scr,
ssr=sci1.ssr,
tdr=sci1.tdr,
rdr=sci1.rdr,
tx_ready_delay=getattr(sci1, "tx_ready_delay", None),
)
def _sci1_txi_summary(emulator: H8536Emulator) -> SCI1TXISummary:
return SCI1TXISummary(
tie=bool(emulator.sci1.scr & SCI_SCR_TIE),
te=bool(emulator.sci1.scr & SCI_SCR_TE),
tdre=bool(emulator.sci1.ssr & SCI_SSR_TDRE),
vector_target=emulator._vector_target(VECTOR_SCI1_TXI),
priority=emulator._sci1_priority(),
interrupt_mask=emulator._interrupt_mask(),
interrupt_depth=emulator.cpu.interrupt_depth,
)
def parse_watch_pc(text: str) -> int:
try:
value = parse_int(text)
@@ -132,9 +245,11 @@ def run_probe(
interval_steps: int,
stop_on_tx: bool,
p9_log_limit: int,
frt1_ocia_steps: int = 1024,
frt2_ocia_steps: int = 1024,
p9_fast_path: bool = False,
p9_fast_input: int = 0xFF,
sci_log_limit: int = 32,
watch_pcs: list[int] | tuple[int, ...] | None = None,
watch_snapshot_limit: int = 32,
watch_pc_limit: int = 8,
@@ -143,6 +258,7 @@ def run_probe(
emulator = H8536Emulator(
rom_bytes,
interval_steps=interval_steps,
frt1_ocia_steps=frt1_ocia_steps,
frt2_ocia_steps=frt2_ocia_steps,
p9_fast_path_enabled=p9_fast_path,
p9_fast_default_input_byte=p9_fast_input,
@@ -183,7 +299,12 @@ def run_probe(
if len(p9_accesses) > p9_log_limit:
del p9_accesses[: len(p9_accesses) - p9_log_limit]
elif access.address == SCI1_TDR:
sci_accesses.append(f"{h16(pc)} {access.kind} {h16(access.address)}={access.value:02X}")
sci_accesses.append(f"{h16(pc)} {access.kind} TDR={access.value:02X}")
elif access.address in SCI1_PROBE_REGISTERS:
name = SCI1_PROBE_REGISTERS[access.address]
sci_accesses.append(f"{h16(pc)} {access.kind} {name}={access.value:02X}")
if len(sci_accesses) > sci_log_limit:
del sci_accesses[: len(sci_accesses) - sci_log_limit]
last_access_index = len(emulator.memory.access_log)
if stop_on_tx and emulator.sci1.tx_bytes:
@@ -204,6 +325,8 @@ def run_probe(
p9_fast_events=len(emulator.p9_fast_path.events),
p9_accesses=p9_accesses,
sci_accesses=sci_accesses,
sci1=_sci1_snapshot(emulator),
sci1_txi=_sci1_txi_summary(emulator),
watch_snapshots=snapshots,
unsupported=unsupported,
)
@@ -214,11 +337,13 @@ def build_arg_parser() -> argparse.ArgumentParser:
parser.add_argument("--rom", type=Path, help="ROM image path; defaults to the repo ROM image")
parser.add_argument("--max-steps", type=int, default=250_000)
parser.add_argument("--interval-steps", type=int, default=512)
parser.add_argument("--frt1-ocia-steps", type=int, default=1024)
parser.add_argument("--frt2-ocia-steps", type=int, default=1024)
parser.add_argument("--stop-on-tx", action="store_true", help="stop when SCI1 TDR emits the first byte")
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)
parser.add_argument("--p9-log-limit", type=int, default=80)
parser.add_argument("--sci-log-limit", type=int, default=32)
parser.add_argument("--hot-limit", type=int, default=12)
parser.add_argument(
"--watch-pc",
@@ -245,11 +370,13 @@ def main(argv: list[str] | None = None) -> int:
rom_bytes,
max_steps=args.max_steps,
interval_steps=args.interval_steps,
frt1_ocia_steps=args.frt1_ocia_steps,
frt2_ocia_steps=args.frt2_ocia_steps,
stop_on_tx=args.stop_on_tx,
p9_log_limit=args.p9_log_limit,
p9_fast_path=args.p9_fast_path,
p9_fast_input=args.p9_fast_input,
sci_log_limit=args.sci_log_limit,
watch_pcs=tuple(dict.fromkeys((*DEFAULT_WATCH_PCS, *args.watch_pc))),
watch_snapshot_limit=args.watch_snapshot_limit,
watch_pc_limit=args.watch_pc_limit,