Emualtor improements
This commit is contained in:
@@ -5,6 +5,8 @@ from .constants import (
|
||||
HEARTBEAT_FRAME,
|
||||
FRT_TCR_OCIEA,
|
||||
FRT_TCSR_OCFA,
|
||||
FRT1_TCR,
|
||||
FRT1_TCSR,
|
||||
FRT2_TCR,
|
||||
FRT2_TCSR,
|
||||
IPRA,
|
||||
@@ -32,6 +34,7 @@ from .constants import (
|
||||
SCI1_SMR,
|
||||
SCI1_SSR,
|
||||
SCI1_TDR,
|
||||
VECTOR_FRT1_OCIA,
|
||||
VECTOR_INTERVAL_TIMER,
|
||||
VECTOR_FRT2_OCIA,
|
||||
VECTOR_SCI1_TXI,
|
||||
@@ -47,6 +50,8 @@ from .sci import SCI1, SciTxEvent
|
||||
__all__ = [
|
||||
"CPUState",
|
||||
"EmulatorError",
|
||||
"FRT1_TCR",
|
||||
"FRT1_TCSR",
|
||||
"FRT2_TCR",
|
||||
"FRT2_TCSR",
|
||||
"FRT_TCR_OCIEA",
|
||||
@@ -87,6 +92,7 @@ __all__ = [
|
||||
"SCI_SSR_TDRE",
|
||||
"SciTxEvent",
|
||||
"UnsupportedInstruction",
|
||||
"VECTOR_FRT1_OCIA",
|
||||
"VECTOR_INTERVAL_TIMER",
|
||||
"VECTOR_FRT2_OCIA",
|
||||
"VECTOR_SCI1_TXI",
|
||||
|
||||
@@ -39,6 +39,7 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--stop-on-heartbeat", action="store_true", help="stop only when 00 00 00 00 80 DA is emitted through SCI1 TDR")
|
||||
parser.add_argument("--memory-map", action="store_true", help="print the scaffolded memory map before running")
|
||||
parser.add_argument("--interval-steps", type=int, default=2048, help="rough step period for the scaffolded timer interrupt")
|
||||
parser.add_argument("--frt1-ocia-steps", type=int, default=1024, help="rough step period for the scaffolded FRT1 OCIA interrupt")
|
||||
parser.add_argument("--frt2-ocia-steps", type=int, default=1024, help="rough step period for the scaffolded FRT2 OCIA interrupt")
|
||||
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")
|
||||
@@ -56,6 +57,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
emulator = H8536Emulator(
|
||||
rom_bytes,
|
||||
interval_steps=args.interval_steps,
|
||||
frt1_ocia_steps=args.frt1_ocia_steps,
|
||||
frt2_ocia_steps=args.frt2_ocia_steps,
|
||||
p9_fast_path_enabled=args.p9_fast_path,
|
||||
p9_fast_default_input_byte=args.p9_fast_input,
|
||||
|
||||
@@ -16,6 +16,8 @@ IPRC = 0xFF02
|
||||
IPRE = 0xFF04
|
||||
WDT_TCSR_R = 0xFEEC
|
||||
|
||||
FRT1_TCR = 0xFE90
|
||||
FRT1_TCSR = 0xFE91
|
||||
FRT2_TCR = 0xFEA0
|
||||
FRT2_TCSR = 0xFEA1
|
||||
|
||||
@@ -39,5 +41,6 @@ RAMCR = 0xFF11
|
||||
|
||||
HEARTBEAT_FRAME = bytes([0x00, 0x00, 0x00, 0x00, 0x80, 0xDA])
|
||||
VECTOR_INTERVAL_TIMER = 0x0042
|
||||
VECTOR_FRT1_OCIA = 0x0062
|
||||
VECTOR_FRT2_OCIA = 0x006A
|
||||
VECTOR_SCI1_TXI = 0x0084
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -9,6 +9,8 @@ from ..vectors import read_vectors_min
|
||||
from .constants import (
|
||||
FRT_TCR_OCIEA,
|
||||
FRT_TCSR_OCFA,
|
||||
FRT1_TCR,
|
||||
FRT1_TCSR,
|
||||
FRT2_TCR,
|
||||
FRT2_TCSR,
|
||||
IPRA,
|
||||
@@ -16,6 +18,7 @@ from .constants import (
|
||||
IPRE,
|
||||
SCI_SCR_TIE,
|
||||
SCI_SSR_TDRE,
|
||||
VECTOR_FRT1_OCIA,
|
||||
VECTOR_FRT2_OCIA,
|
||||
VECTOR_INTERVAL_TIMER,
|
||||
VECTOR_SCI1_TXI,
|
||||
@@ -63,6 +66,7 @@ class H8536Emulator:
|
||||
rom_bytes: bytes,
|
||||
*,
|
||||
interval_steps: int = 2048,
|
||||
frt1_ocia_steps: int = 1024,
|
||||
frt2_ocia_steps: int = 1024,
|
||||
p9_fast_path: P9FastPath | None = None,
|
||||
p9_fast_path_enabled: bool = False,
|
||||
@@ -78,8 +82,10 @@ class H8536Emulator:
|
||||
self.cpu = CPUState()
|
||||
self.vectors = read_vectors_min(self.memory.rom)
|
||||
self.interval_steps = max(1, interval_steps)
|
||||
self.frt1_ocia_steps = max(1, frt1_ocia_steps)
|
||||
self.frt2_ocia_steps = max(1, frt2_ocia_steps)
|
||||
self._interval_counter = 0
|
||||
self._frt1_ocia_counter = 0
|
||||
self._frt2_ocia_counter = 0
|
||||
self.reset()
|
||||
|
||||
@@ -372,6 +378,7 @@ class H8536Emulator:
|
||||
def _tick_peripherals(self) -> None:
|
||||
self.sci1.tick()
|
||||
self._interval_counter += 1
|
||||
self._frt1_ocia_counter += 1
|
||||
self._frt2_ocia_counter += 1
|
||||
self._service_pending_interrupt()
|
||||
|
||||
@@ -387,6 +394,10 @@ class H8536Emulator:
|
||||
target = self._vector_target(VECTOR_INTERVAL_TIMER)
|
||||
if target is not None:
|
||||
candidates.append((self._interval_priority(), target, "interval_timer"))
|
||||
if self._frt1_ocia_pending():
|
||||
target = self._vector_target(VECTOR_FRT1_OCIA)
|
||||
if target is not None:
|
||||
candidates.append((self._frt1_priority(), target, "frt1_ocia"))
|
||||
if self._frt2_ocia_pending():
|
||||
target = self._vector_target(VECTOR_FRT2_OCIA)
|
||||
if target is not None:
|
||||
@@ -399,6 +410,9 @@ class H8536Emulator:
|
||||
return
|
||||
if source == "interval_timer":
|
||||
self._interval_counter = 0
|
||||
elif source == "frt1_ocia":
|
||||
self._frt1_ocia_counter = 0
|
||||
self.memory.write8(FRT1_TCSR, self.memory.read8(FRT1_TCSR) | FRT_TCSR_OCFA)
|
||||
elif source == "frt2_ocia":
|
||||
self._frt2_ocia_counter = 0
|
||||
self.memory.write8(FRT2_TCSR, self.memory.read8(FRT2_TCSR) | FRT_TCSR_OCFA)
|
||||
@@ -430,6 +444,14 @@ class H8536Emulator:
|
||||
def _interval_priority(self) -> int:
|
||||
return (self.memory.read8(IPRA) >> 4) & 0x07
|
||||
|
||||
def _frt1_ocia_pending(self) -> bool:
|
||||
if self._frt1_ocia_counter < self.frt1_ocia_steps:
|
||||
return False
|
||||
return bool(self.memory.read8(FRT1_TCR) & FRT_TCR_OCIEA)
|
||||
|
||||
def _frt1_priority(self) -> int:
|
||||
return (self.memory.read8(IPRC) >> 4) & 0x07
|
||||
|
||||
def _frt2_ocia_pending(self) -> bool:
|
||||
if self._frt2_ocia_counter < self.frt2_ocia_steps:
|
||||
return False
|
||||
|
||||
@@ -11,6 +11,9 @@ from .constants import (
|
||||
SCI1_SSR,
|
||||
SCI1_TDR,
|
||||
SCI_SCR_TE,
|
||||
SCI_SSR_FER,
|
||||
SCI_SSR_ORER,
|
||||
SCI_SSR_PER,
|
||||
SCI_SSR_RDRF,
|
||||
SCI_SSR_TDRE,
|
||||
)
|
||||
@@ -34,6 +37,8 @@ class SCI1:
|
||||
- SCR bit 7 TIE, bit 6 RIE, bit 5 TE, bit 4 RE.
|
||||
- SSR bit 7 TDRE, bit 6 RDRF, bits 5..3 ORER/FER/PER.
|
||||
- Software normally writes TDR after TDRE=1, then clears SSR.TDRE.
|
||||
- SSR status flags are R/(W)*: writing 0 clears a latched flag, while
|
||||
writing 1 leaves the hardware-owned flag unchanged.
|
||||
"""
|
||||
|
||||
smr: int = 0x00
|
||||
@@ -47,6 +52,8 @@ class SCI1:
|
||||
tx_frames: list[bytes] = field(default_factory=list)
|
||||
_frame_buffer: bytearray = field(default_factory=bytearray)
|
||||
tx_ready_delay: int = 0
|
||||
tx_ready_ticks: int = 2
|
||||
_tx_ready_pending: bool = False
|
||||
|
||||
def read(self, address: int) -> int:
|
||||
if address == SCI1_SMR:
|
||||
@@ -75,10 +82,7 @@ class SCI1:
|
||||
self.tdr = value
|
||||
self._write_tdr(value)
|
||||
elif address == SCI1_SSR:
|
||||
# The real SSR is R/(W)*: writable zeroes clear latched flags after
|
||||
# the required read sequence. This scaffold applies zero bits
|
||||
# directly so ROM BCLR/BSET style accesses can be modeled.
|
||||
self.ssr = value
|
||||
self._write_ssr(value)
|
||||
elif address == SCI1_RDR:
|
||||
self.rdr = value
|
||||
else:
|
||||
@@ -92,10 +96,14 @@ class SCI1:
|
||||
if len(self._frame_buffer) == len(HEARTBEAT_FRAME):
|
||||
self.tx_frames.append(bytes(self._frame_buffer))
|
||||
self._frame_buffer.clear()
|
||||
self.ssr |= SCI_SSR_TDRE
|
||||
self.tx_ready_delay = 2
|
||||
self.tx_ready_delay = max(0, self.tx_ready_ticks)
|
||||
self._tx_ready_pending = True
|
||||
self.tx_events.append(SciTxEvent(SCI1_TDR, value, self.scr, self.ssr, emitted))
|
||||
|
||||
def _write_ssr(self, value: int) -> None:
|
||||
writable_zero_flags = SCI_SSR_TDRE | SCI_SSR_RDRF | SCI_SSR_ORER | SCI_SSR_FER | SCI_SSR_PER
|
||||
self.ssr = (self.ssr & (writable_zero_flags & value)) | (value & ~writable_zero_flags)
|
||||
|
||||
def inject_rx(self, value: int) -> None:
|
||||
self.rdr = value & 0xFF
|
||||
self.ssr |= SCI_SSR_RDRF
|
||||
@@ -104,7 +112,8 @@ class SCI1:
|
||||
return HEARTBEAT_FRAME in self.tx_frames
|
||||
|
||||
def tick(self) -> None:
|
||||
if self.tx_ready_delay:
|
||||
if self._tx_ready_pending and self.tx_ready_delay:
|
||||
self.tx_ready_delay -= 1
|
||||
if self.tx_ready_delay == 0:
|
||||
self.ssr |= SCI_SSR_TDRE
|
||||
if self._tx_ready_pending and self.tx_ready_delay == 0 and not (self.ssr & SCI_SSR_TDRE):
|
||||
self.ssr |= SCI_SSR_TDRE
|
||||
self._tx_ready_pending = False
|
||||
|
||||
Reference in New Issue
Block a user