1
0

p9 bus emulation

This commit is contained in:
Aiden
2026-05-25 22:32:13 +10:00
parent c3eb09ddc8
commit 0c241877eb
12 changed files with 179 additions and 10 deletions

View File

@@ -102,7 +102,7 @@ Current serial observations:
- Emulator timing finding: the ROM initializes FRT2 with `TCR=H'02` and `OCRA=H'7A12`; using the manual's `phi/32` prescaler gives a 1,000,000-cycle OCIA period, so the default `--clock-hz 10000000` models that tick as 100 ms and the post-send `F9C4=H'07` heartbeat delay as about 700 ms. - Emulator timing finding: the ROM initializes FRT2 with `TCR=H'02` and `OCRA=H'7A12`; using the manual's `phi/32` prescaler gives a 1,000,000-cycle OCIA period, so the default `--clock-hz 10000000` models that tick as 100 ms and the post-send `F9C4=H'07` heartbeat delay as about 700 ms.
- Runtime-confirmed heartbeat path: `loc_4067` writes `H'0000` into the queue via a zero-extended word move, `loc_BAF2/loc_BB08` dequeue it, `loc_BB1C/loc_BB20/loc_BB2B` stage the TX bytes, and `loc_BA26` emits `00 00 00 00 80 DA`. - Runtime-confirmed heartbeat path: `loc_4067` writes `H'0000` into the queue via a zero-extended word move, `loc_BAF2/loc_BB08` dequeue it, `loc_BB1C/loc_BB20/loc_BB2B` stage the TX bytes, and `loc_BA26` emits `00 00 00 00 80 DA`.
- Emulator LCD finding: the ROM writes the boot/no-active-session message to the LCD bus as ` CONNECT:NOT ACT` on line 0 by the time SCI1 RX is serviceable. Valid and invalid six-byte host frames leave that display active while normal serial replies/heartbeats continue. - Emulator LCD finding: the ROM writes the boot/no-active-session message to the LCD bus as ` CONNECT:NOT ACT` on line 0 by the time SCI1 RX is serviceable. Valid and invalid six-byte host frames leave that display active while normal serial replies/heartbeats continue.
- RX probe finding: with calibrated FRT timing, the `--preset connect-lcd` sequence reaches the command-`0x04` handler but leaves the emulated LCD at ` CONNECT:NOT ACT` and falls back to heartbeat output; the earlier `CONNECT: OK`/`02 00 02 00 00 5A` result is now treated as a legacy step-timer artifact. - RX probe finding: the `--preset connect-lcd` sequence is sensitive to injection timing and modeled external state. With timed UART injection, the emulator can still reach `CONNECT: OK`/`02 00 02 00 00 5A`, while the real bench remains at `CONNECT NOT ACT`; this points to missing session/P9/external-panel context rather than a simple checksum or UART-spacing issue.
- Bench follow-up: replaying the emulator CONNECT sequence on the real device did not switch the LCD to OK. The real device answered the `04 00 00 80 00 DE` step with `07 80 C0 60 20 5D` in the captured run and remained at `CONNECT NOT ACT`, so the next mismatch to chase is the missing visible `07 80 C0 60 20 5D` response/session context rather than the LCD OK branch. - Bench follow-up: replaying the emulator CONNECT sequence on the real device did not switch the LCD to OK. The real device answered the `04 00 00 80 00 DE` step with `07 80 C0 60 20 5D` in the captured run and remained at `CONNECT NOT ACT`, so the next mismatch to chase is the missing visible `07 80 C0 60 20 5D` response/session context rather than the LCD OK branch.
- Observed capture labels such as `cam_power_button_candidate` and `call_button_candidate` are deliberately treated as capture overlays, not protocol facts hard-coded in ROM. - Observed capture labels such as `cam_power_button_candidate` and `call_button_candidate` are deliberately treated as capture overlays, not protocol facts hard-coded in ROM.
@@ -212,7 +212,8 @@ python h8536_emulator_rx_probe.py --help
- `--interval-steps N`: tune the scaffolded interval timer cadence. - `--interval-steps N`: tune the scaffolded interval timer cadence.
- `--clock-hz N`: set the CPU/phi clock used for calibrated FRT1/FRT2 compare timing; the default is 10 MHz. - `--clock-hz N`: set the CPU/phi clock used for calibrated FRT1/FRT2 compare timing; the default is 10 MHz.
- `--frt1-ocia-steps N` / `--frt2-ocia-steps N`: optional legacy overrides for forcing rough FRT compare cadence in targeted tests. - `--frt1-ocia-steps N` / `--frt2-ocia-steps N`: optional legacy overrides for forcing rough FRT compare cadence in targeted tests.
- `--p9-fast-path`: shortcut known P9 transfer routines for exploration. - `--p9-fast-path`: shortcut known P9 transfer routines for exploration. Fast-path wrapper calls now default to timeout unless a modeled P9 response is queued, and the probe reports decoded P9 transaction/fast-path traces.
- `--p9-fast-optimistic-wrapper`: restore the older behavior where P9 fast-path wrapper calls succeed without a modeled external-panel response.
- `--trace-report-gates`, `--trace-report-queue`, and `--trace-ram-lifecycle`: inspect the serial report queue, `loc_4046`/`F9C4` gate, and watched RAM byte history. - `--trace-report-gates`, `--trace-report-queue`, and `--trace-ram-lifecycle`: inspect the serial report queue, `loc_4046`/`F9C4` gate, and watched RAM byte history.
- `--target-frame "00 00 00 00 80 DA"`: compare staged/emitted TX bytes against an expected six-byte frame. - `--target-frame "00 00 00 00 80 DA"`: compare staged/emitted TX bytes against an expected six-byte frame.
- `h8536_emulator_rx_probe.py "04 00 00 80 00"`: append the checksum, inject the host frame through SCI1 RX, and summarize responses. - `h8536_emulator_rx_probe.py "04 00 00 80 00"`: append the checksum, inject the host frame through SCI1 RX, and summarize responses.

View File

@@ -55,7 +55,7 @@ from .cpu import CPUState
from .errors import EmulatorError, UnsupportedInstruction from .errors import EmulatorError, UnsupportedInstruction
from .fast_paths import P9FastPath, P9FastPathConfig, P9FastPathEvent from .fast_paths import P9FastPath, P9FastPathConfig, P9FastPathEvent
from .memory import MemoryAccess, MemoryMap, describe_regions from .memory import MemoryAccess, MemoryMap, describe_regions
from .peripherals import LCD from .peripherals import LCD, P9TraceEvent
from .runner import H8536Emulator, RunReport from .runner import H8536Emulator, RunReport
from .sci import SCI1, SciTxEvent from .sci import SCI1, SciTxEvent
from .uart import UartTiming from .uart import UartTiming
@@ -93,6 +93,7 @@ __all__ = [
"P9FastPath", "P9FastPath",
"P9FastPathConfig", "P9FastPathConfig",
"P9FastPathEvent", "P9FastPathEvent",
"P9TraceEvent",
"RAMCR", "RAMCR",
"REGISTER_FIELD_END", "REGISTER_FIELD_END",
"REGISTER_FIELD_START", "REGISTER_FIELD_START",

View File

@@ -180,6 +180,7 @@ class ReplayConfig:
uart_baud: int = 38_400 uart_baud: int = 38_400
p9_fast_path: bool = True p9_fast_path: bool = True
p9_fast_input: int = 0xFF p9_fast_input: int = 0xFF
p9_fast_optimistic_wrapper: bool = False
def parse_bench_replay_log_text(text: str) -> BenchReplayLog: def parse_bench_replay_log_text(text: str) -> BenchReplayLog:
@@ -242,6 +243,7 @@ def run_bench_replay(log_path: Path, *, rom_path: Path | None = None, config: Re
clock_hz=config.clock_hz, clock_hz=config.clock_hz,
p9_fast_path_enabled=config.p9_fast_path, p9_fast_path_enabled=config.p9_fast_path,
p9_fast_default_input_byte=config.p9_fast_input, p9_fast_default_input_byte=config.p9_fast_input,
p9_fast_default_wrapper_success=config.p9_fast_optimistic_wrapper,
) )
context = RunContext() context = RunContext()
@@ -366,6 +368,7 @@ def build_arg_parser() -> argparse.ArgumentParser:
parser.add_argument("--frt2-ocia-steps", type=int, default=ReplayConfig.frt2_ocia_steps) parser.add_argument("--frt2-ocia-steps", type=int, default=ReplayConfig.frt2_ocia_steps)
parser.add_argument("--no-p9-fast-path", action="store_true", help="disable shortcut handling for known P9 routines") 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-input", type=lambda text: int(text, 0), default=ReplayConfig.p9_fast_input)
parser.add_argument("--p9-fast-optimistic-wrapper", action="store_true", help="make P9 fast-path wrapper calls succeed when no modeled P9 response is queued")
parser.add_argument("--assert-bench-parity", action="store_true", help="exit nonzero if emulator behavior diverges from the bench log") 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") parser.add_argument("--json", action="store_true", help="emit JSON")
return parser return parser
@@ -388,6 +391,7 @@ def main(argv: list[str] | None = None) -> int:
uart_baud=args.uart_baud, uart_baud=args.uart_baud,
p9_fast_path=not args.no_p9_fast_path, p9_fast_path=not args.no_p9_fast_path,
p9_fast_input=args.p9_fast_input, p9_fast_input=args.p9_fast_input,
p9_fast_optimistic_wrapper=args.p9_fast_optimistic_wrapper,
), ),
) )
if args.json: if args.json:

View File

@@ -44,6 +44,7 @@ def build_arg_parser() -> argparse.ArgumentParser:
parser.add_argument("--frt2-ocia-steps", type=int, default=None, help="legacy step-period override for FRT2 OCIA") parser.add_argument("--frt2-ocia-steps", type=int, default=None, help="legacy step-period override for FRT2 OCIA")
parser.add_argument("--p9-fast-path", action="store_true", help="shortcut known P9 bit-banged transfer routines for exploration") 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-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="make P9 fast-path wrapper calls succeed when no modeled P9 response is queued")
return parser return parser
@@ -63,6 +64,7 @@ def main(argv: list[str] | None = None) -> int:
clock_hz=args.clock_hz, clock_hz=args.clock_hz,
p9_fast_path_enabled=args.p9_fast_path, p9_fast_path_enabled=args.p9_fast_path,
p9_fast_default_input_byte=args.p9_fast_input, p9_fast_default_input_byte=args.p9_fast_input,
p9_fast_default_wrapper_success=args.p9_fast_optimistic_wrapper,
) )
print(f"rom={rom_path}") print(f"rom={rom_path}")
print(f"reset_vector={h16(emulator.reset_vector())}") print(f"reset_vector={h16(emulator.reset_vector())}")

View File

@@ -53,12 +53,14 @@ class P9FastPathEvent:
value: int | None = None value: int | None = None
source: str | None = None source: str | None = None
queue_depth: int | None = None queue_depth: int | None = None
success: bool | None = None
def line(self) -> str: def line(self) -> str:
value = "" if self.value is None else f" value={self.value:02X}" value = "" if self.value is None else f" value={self.value:02X}"
success = "" if self.success is None else f" success={int(self.success)}"
source = "" if self.source is None else f" source={self.source}" source = "" if self.source is None else f" source={self.source}"
queue_depth = "" if self.queue_depth is None else f" queued={self.queue_depth}" queue_depth = "" if self.queue_depth is None else f" queued={self.queue_depth}"
return f"{self.kind} pc={self.pc:04X}{value}{source}{queue_depth}" return f"{self.kind} pc={self.pc:04X}{value}{success}{source}{queue_depth}"
@dataclass @dataclass
@@ -101,10 +103,7 @@ class P9FastPath:
self.events.append(P9FastPathEvent("marker", pc)) self.events.append(P9FastPathEvent("marker", pc))
self._return_from_subroutine(emulator) self._return_from_subroutine(emulator)
elif pc in self.config.wrapper_pcs: elif pc in self.config.wrapper_pcs:
self.events.append(P9FastPathEvent("wrapper_success", pc)) self._handle_wrapper(emulator)
emulator.cpu.regs[0] = 1
self._set_logic_flags(emulator.cpu, 1, 1)
self._return_from_subroutine(emulator)
else: else:
return False return False
@@ -140,6 +139,25 @@ class P9FastPath:
self._set_logic_flags(emulator.cpu, value, 1) self._set_logic_flags(emulator.cpu, value, 1)
self._return_from_subroutine(emulator) self._return_from_subroutine(emulator)
def _handle_wrapper(self, emulator: Any) -> None:
pc = emulator.cpu.pc & 0xFFFF
success, source, queue_depth = emulator.memory.p9_bus.consume_wrapper_result()
value = 1 if success else 0
self.events.append(
P9FastPathEvent(
"wrapper_success" if success else "wrapper_timeout",
pc,
value=value,
source=source,
queue_depth=queue_depth,
success=success,
)
)
emulator.cpu.regs[0] = value
self._set_logic_flags(emulator.cpu, value, 1)
self._return_from_subroutine(emulator)
def _return_from_subroutine(self, emulator: Any) -> None: def _return_from_subroutine(self, emulator: Any) -> None:
sp = emulator.cpu.regs[7] & 0xFFFF sp = emulator.cpu.regs[7] & 0xFFFF
emulator.cpu.pc = emulator.memory.read16(sp) & 0xFFFF emulator.cpu.pc = emulator.memory.read16(sp) & 0xFFFF

View File

@@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from .lcd import LCD, LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS, LCD_LINE_WIDTH 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 from .p9_bus import P9_ACK_BIT, P9_STROBE_BIT, P9Bus, P9StrobeEvent, P9TraceEvent
__all__ = [ __all__ = [
"LCD_E_CLOCK_DATA", "LCD_E_CLOCK_DATA",
@@ -12,4 +12,5 @@ __all__ = [
"P9_STROBE_BIT", "P9_STROBE_BIT",
"P9Bus", "P9Bus",
"P9StrobeEvent", "P9StrobeEvent",
"P9TraceEvent",
] ]

View File

@@ -17,25 +17,63 @@ class P9StrobeEvent:
bit7_output: bool bit7_output: bool
@dataclass(frozen=True)
class P9TraceEvent:
kind: str
ddr: int
dr: int
value: int | None = None
bit: int | None = None
source: str | None = None
success: bool | None = None
queue_depth: int | None = None
def line(self) -> str:
parts = [self.kind, f"ddr={self.ddr:02X}", f"dr={self.dr:02X}"]
if self.value is not None:
parts.append(f"value={self.value:02X}")
if self.bit is not None:
parts.append(f"bit={self.bit}")
if self.success is not None:
parts.append(f"success={int(self.success)}")
if self.source is not None:
parts.append(f"source={self.source}")
if self.queue_depth is not None:
parts.append(f"queued={self.queue_depth}")
return " ".join(parts)
class P9Bus: class P9Bus:
"""Small model for the ROM's P9 bit-banged serial handshake.""" """Small model for the ROM's P9 bit-banged serial handshake."""
def __init__(self, ddr: int = 0x00, dr: int = 0x00, input_bits: Iterable[int] = ()) -> None: def __init__(
self,
ddr: int = 0x00,
dr: int = 0x00,
input_bits: Iterable[int] = (),
wrapper_results: Iterable[bool] = (),
) -> None:
self.ddr = ddr & 0xFF self.ddr = ddr & 0xFF
self.dr_latch = dr & 0xFF self.dr_latch = dr & 0xFF
self.input_bits: list[int] = [1 if bit else 0 for bit in input_bits] self.input_bits: list[int] = [1 if bit else 0 for bit in input_bits]
self.default_input_bit = 0 self.default_input_bit = 0
self.wrapper_results: list[bool] = [bool(result) for result in wrapper_results]
self.wrapper_sources: list[str] = ["initial"] * len(self.wrapper_results)
self.default_wrapper_success = False
self.strobe_edges: list[P9StrobeEvent] = [] self.strobe_edges: list[P9StrobeEvent] = []
self.trace_events: list[P9TraceEvent] = []
self.transmitted_bits: list[int] = [] self.transmitted_bits: list[int] = []
self.byte_candidates: list[int] = [] self.byte_candidates: list[int] = []
def write_ddr(self, value: int) -> int: def write_ddr(self, value: int) -> int:
self.ddr = value & 0xFF self.ddr = value & 0xFF
self.trace_events.append(P9TraceEvent("write_ddr", self.ddr, self.dr_latch, value=self.ddr))
return self.ddr return self.ddr
def write_dr(self, value: int) -> int: def write_dr(self, value: int) -> int:
previous = self.dr_latch previous = self.dr_latch
self.dr_latch = value & 0xFF self.dr_latch = value & 0xFF
self.trace_events.append(P9TraceEvent("write_dr", self.ddr, self.dr_latch, value=self.dr_latch))
previous_strobe = bool(previous & P9_STROBE_BIT) previous_strobe = bool(previous & P9_STROBE_BIT)
current_strobe = bool(self.dr_latch & P9_STROBE_BIT) current_strobe = bool(self.dr_latch & P9_STROBE_BIT)
@@ -44,6 +82,7 @@ class P9Bus:
data_bit = 1 if self.dr_latch & P9_ACK_BIT else 0 data_bit = 1 if self.dr_latch & P9_ACK_BIT else 0
bit7_output = bool(self.ddr & P9_ACK_BIT) bit7_output = bool(self.ddr & P9_ACK_BIT)
self.strobe_edges.append(P9StrobeEvent(edge, self.ddr, self.dr_latch, data_bit, bit7_output)) self.strobe_edges.append(P9StrobeEvent(edge, self.ddr, self.dr_latch, data_bit, bit7_output))
self.trace_events.append(P9TraceEvent(f"strobe_{edge}", self.ddr, self.dr_latch, bit=data_bit))
if edge == "rising" and bit7_output: if edge == "rising" and bit7_output:
self._record_transmitted_bit(data_bit) self._record_transmitted_bit(data_bit)
@@ -54,15 +93,20 @@ class P9Bus:
def read_dr(self) -> int: def read_dr(self) -> int:
value = self.dr_latch value = self.dr_latch
input_bit = None
source = None
if not (self.ddr & P9_ACK_BIT): if not (self.ddr & P9_ACK_BIT):
if self.input_bits: if self.input_bits:
input_bit = self.input_bits.pop(0) input_bit = self.input_bits.pop(0)
source = "queued_bit"
else: else:
input_bit = self.default_input_bit input_bit = self.default_input_bit
source = "default_bit"
if input_bit: if input_bit:
value |= P9_ACK_BIT value |= P9_ACK_BIT
else: else:
value &= ~P9_ACK_BIT value &= ~P9_ACK_BIT
self.trace_events.append(P9TraceEvent("read_dr", self.ddr, value & 0xFF, value=value, bit=input_bit, source=source))
return value & 0xFF return value & 0xFF
def queue_input_bits(self, bits: Iterable[int]) -> None: def queue_input_bits(self, bits: Iterable[int]) -> None:
@@ -71,10 +115,42 @@ class P9Bus:
def set_default_input_bit(self, bit: int) -> None: def set_default_input_bit(self, bit: int) -> None:
self.default_input_bit = 1 if bit else 0 self.default_input_bit = 1 if bit else 0
def queue_wrapper_results(self, results: Iterable[bool], source: str = "queued_wrapper") -> None:
for result in results:
self.wrapper_results.append(bool(result))
self.wrapper_sources.append(source)
def consume_wrapper_result(self) -> tuple[bool, str, int]:
if self.wrapper_results:
success = self.wrapper_results.pop(0)
source = self.wrapper_sources.pop(0) if self.wrapper_sources else "queued_wrapper"
else:
success = self.default_wrapper_success
source = "default_success" if success else "default_timeout"
queue_depth = len(self.wrapper_results)
self.trace_events.append(
P9TraceEvent(
"wrapper_result",
self.ddr,
self.dr_latch,
value=1 if success else 0,
success=success,
source=source,
queue_depth=queue_depth,
)
)
return success, source, queue_depth
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]
def _record_transmitted_bit(self, bit: int) -> None: def _record_transmitted_bit(self, bit: int) -> None:
self.transmitted_bits.append(bit) self.transmitted_bits.append(bit)
self.trace_events.append(P9TraceEvent("tx_bit", self.ddr, self.dr_latch, bit=bit))
if len(self.transmitted_bits) % 8 == 0: if len(self.transmitted_bits) % 8 == 0:
byte = 0 byte = 0
for data_bit in self.transmitted_bits[-8:]: for data_bit in self.transmitted_bits[-8:]:
byte = (byte << 1) | data_bit byte = (byte << 1) | data_bit
self.byte_candidates.append(byte) self.byte_candidates.append(byte)
self.trace_events.append(P9TraceEvent("tx_byte", self.ddr, self.dr_latch, value=byte))

View File

@@ -430,6 +430,8 @@ class ProbeReport:
p9_fast_bytes: list[int] = field(default_factory=list) p9_fast_bytes: list[int] = field(default_factory=list)
p9_fast_events: int = 0 p9_fast_events: int = 0
p9_accesses: list[str] = field(default_factory=list) p9_accesses: list[str] = field(default_factory=list)
p9_trace: list[str] = field(default_factory=list)
p9_fast_trace: list[str] = field(default_factory=list)
sci_accesses: list[str] = field(default_factory=list) sci_accesses: list[str] = field(default_factory=list)
sci1: SCI1Snapshot | None = None sci1: SCI1Snapshot | None = None
sci1_txi: SCI1TXISummary | None = None sci1_txi: SCI1TXISummary | None = None
@@ -475,6 +477,12 @@ class ProbeReport:
if self.p9_accesses: if self.p9_accesses:
lines.append("recent_p9:") lines.append("recent_p9:")
lines.extend(" " + line for line in self.p9_accesses[-24:]) lines.extend(" " + line for line in self.p9_accesses[-24:])
if self.p9_trace:
lines.append("recent_p9_trace:")
lines.extend(" " + line for line in self.p9_trace[-24:])
if self.p9_fast_trace:
lines.append("recent_p9_fast:")
lines.extend(" " + line for line in self.p9_fast_trace[-24:])
if self.sci_accesses: if self.sci_accesses:
lines.append("recent_sci:") lines.append("recent_sci:")
lines.extend(" " + line for line in self.sci_accesses[-16:]) lines.extend(" " + line for line in self.sci_accesses[-16:])
@@ -905,6 +913,7 @@ def run_probe(
clock_hz: int = 10_000_000, clock_hz: int = 10_000_000,
p9_fast_path: bool = False, p9_fast_path: bool = False,
p9_fast_input: int = 0xFF, p9_fast_input: int = 0xFF,
p9_fast_optimistic_wrapper: bool = False,
sci_log_limit: int = 32, sci_log_limit: int = 32,
watch_pcs: list[int] | tuple[int, ...] | None = None, watch_pcs: list[int] | tuple[int, ...] | None = None,
watch_snapshot_limit: int = 32, watch_snapshot_limit: int = 32,
@@ -933,6 +942,7 @@ def run_probe(
clock_hz=clock_hz, clock_hz=clock_hz,
p9_fast_path_enabled=p9_fast_path, p9_fast_path_enabled=p9_fast_path,
p9_fast_default_input_byte=p9_fast_input, p9_fast_default_input_byte=p9_fast_input,
p9_fast_default_wrapper_success=p9_fast_optimistic_wrapper,
) )
hot_pcs: Counter[int] = Counter() hot_pcs: Counter[int] = Counter()
p9_accesses: list[str] = [] p9_accesses: list[str] = []
@@ -1120,6 +1130,8 @@ def run_probe(
p9_fast_bytes=list(emulator.p9_fast_path.output_bytes), p9_fast_bytes=list(emulator.p9_fast_path.output_bytes),
p9_fast_events=len(emulator.p9_fast_path.events), p9_fast_events=len(emulator.p9_fast_path.events),
p9_accesses=p9_accesses, p9_accesses=p9_accesses,
p9_trace=emulator.memory.p9_bus.trace_lines(p9_log_limit),
p9_fast_trace=emulator.p9_fast_path.trace_lines(p9_log_limit),
sci_accesses=sci_accesses, sci_accesses=sci_accesses,
sci1=_sci1_snapshot(emulator), sci1=_sci1_snapshot(emulator),
sci1_txi=_sci1_txi_summary(emulator), sci1_txi=_sci1_txi_summary(emulator),
@@ -1158,6 +1170,7 @@ def build_arg_parser() -> argparse.ArgumentParser:
parser.add_argument("--stop-on-tx", action="store_true", help="stop when SCI1 TDR emits the first byte") 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-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-fast-input", type=parse_int, default=0xFF)
parser.add_argument("--p9-fast-optimistic-wrapper", action="store_true", help="make P9 fast-path wrapper calls succeed when no modeled P9 response is queued")
parser.add_argument("--p9-log-limit", type=int, default=80) parser.add_argument("--p9-log-limit", type=int, default=80)
parser.add_argument("--sci-log-limit", type=int, default=32) parser.add_argument("--sci-log-limit", type=int, default=32)
parser.add_argument("--hot-limit", type=int, default=12) parser.add_argument("--hot-limit", type=int, default=12)
@@ -1244,6 +1257,7 @@ def main(argv: list[str] | None = None) -> int:
p9_log_limit=args.p9_log_limit, p9_log_limit=args.p9_log_limit,
p9_fast_path=args.p9_fast_path, p9_fast_path=args.p9_fast_path,
p9_fast_input=args.p9_fast_input, p9_fast_input=args.p9_fast_input,
p9_fast_optimistic_wrapper=args.p9_fast_optimistic_wrapper,
sci_log_limit=args.sci_log_limit, sci_log_limit=args.sci_log_limit,
watch_pcs=tuple(dict.fromkeys((*DEFAULT_WATCH_PCS, *args.watch_pc))), watch_pcs=tuple(dict.fromkeys((*DEFAULT_WATCH_PCS, *args.watch_pc))),
watch_snapshot_limit=args.watch_snapshot_limit, watch_snapshot_limit=args.watch_snapshot_limit,

View File

@@ -91,11 +91,13 @@ class H8536Emulator:
p9_fast_path: P9FastPath | None = None, p9_fast_path: P9FastPath | None = None,
p9_fast_path_enabled: bool = False, p9_fast_path_enabled: bool = False,
p9_fast_default_input_byte: int = 0xFF, p9_fast_default_input_byte: int = 0xFF,
p9_fast_default_wrapper_success: bool = False,
) -> None: ) -> None:
if not rom_bytes: if not rom_bytes:
raise ValueError("ROM image is empty") raise ValueError("ROM image is empty")
self.sci1 = SCI1() self.sci1 = SCI1()
self.memory = MemoryMap(rom_bytes, self.sci1) self.memory = MemoryMap(rom_bytes, self.sci1)
self.memory.p9_bus.default_wrapper_success = bool(p9_fast_default_wrapper_success)
self.p9_fast_path = p9_fast_path or P9FastPath( self.p9_fast_path = p9_fast_path or P9FastPath(
P9FastPathConfig(enabled=p9_fast_path_enabled, default_input_byte=p9_fast_default_input_byte) P9FastPathConfig(enabled=p9_fast_path_enabled, default_input_byte=p9_fast_default_input_byte)
) )

View File

@@ -213,6 +213,7 @@ def run_rx_probe(
clock_hz: int = 10_000_000, clock_hz: int = 10_000_000,
p9_fast_path: bool = True, p9_fast_path: bool = True,
p9_fast_input: int = 0xFF, p9_fast_input: int = 0xFF,
p9_fast_optimistic_wrapper: bool = False,
stop_after_tx_frame: bool = True, stop_after_tx_frame: bool = True,
) -> tuple[Path, H8536Emulator, str, list[FrameResult]]: ) -> tuple[Path, H8536Emulator, str, list[FrameResult]]:
rom_bytes, discovered_rom_path = load_rom(rom_path) rom_bytes, discovered_rom_path = load_rom(rom_path)
@@ -224,6 +225,7 @@ def run_rx_probe(
clock_hz=clock_hz, clock_hz=clock_hz,
p9_fast_path_enabled=p9_fast_path, p9_fast_path_enabled=p9_fast_path,
p9_fast_default_input_byte=p9_fast_input, p9_fast_default_input_byte=p9_fast_input,
p9_fast_default_wrapper_success=p9_fast_optimistic_wrapper,
) )
boot_context = RunContext() boot_context = RunContext()
@@ -268,6 +270,7 @@ def build_arg_parser() -> argparse.ArgumentParser:
parser.add_argument("--frt2-ocia-steps", type=int, default=None, help="legacy step-period override for FRT2 OCIA") parser.add_argument("--frt2-ocia-steps", type=int, default=None, help="legacy step-period override for FRT2 OCIA")
parser.add_argument("--no-p9-fast-path", action="store_true", help="disable shortcut handling for known P9 routines") 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-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="make P9 fast-path wrapper calls succeed when no modeled P9 response is queued")
return parser return parser
@@ -293,6 +296,7 @@ def main(argv: list[str] | None = None) -> int:
clock_hz=args.clock_hz, clock_hz=args.clock_hz,
p9_fast_path=not args.no_p9_fast_path, p9_fast_path=not args.no_p9_fast_path,
p9_fast_input=args.p9_fast_input, p9_fast_input=args.p9_fast_input,
p9_fast_optimistic_wrapper=args.p9_fast_optimistic_wrapper,
stop_after_tx_frame=not args.keep_listening, stop_after_tx_frame=not args.keep_listening,
) )

View File

@@ -1,6 +1,7 @@
import unittest import unittest
from h8536.emulator.fast_paths import ( from h8536.emulator.fast_paths import (
LOC_BFE0_TRANSFER_WRAPPER,
LOC_C08B_P9_WRITE_BYTE, LOC_C08B_P9_WRITE_BYTE,
LOC_C0DB_P9_READ_BYTE, LOC_C0DB_P9_READ_BYTE,
P9FastPath, P9FastPath,
@@ -105,6 +106,41 @@ class P9FastPathTest(unittest.TestCase):
self.assertEqual(event.queue_depth, 1) self.assertEqual(event.queue_depth, 1)
self.assertEqual(fast_path.trace_lines(), ["read_byte pc=C0DB value=00 source=script:idle-panel queued=1"]) self.assertEqual(fast_path.trace_lines(), ["read_byte pc=C0DB value=00 source=script:idle-panel queued=1"])
def test_wrapper_defaults_to_timeout_when_no_p9_device_response_is_queued(self):
emulator = H8536Emulator(bytes(rom_with_reset()))
emulator.cpu.pc = LOC_BFE0_TRANSFER_WRAPPER
emulator.cpu.regs[7] = 0xFE84
emulator.memory.write16(0xFE84, 0x789A)
fast_path = P9FastPath(P9FastPathConfig(enabled=True))
self.assertTrue(fast_path.try_handle(emulator))
event = fast_path.events[-1]
self.assertEqual(event.kind, "wrapper_timeout")
self.assertFalse(event.success)
self.assertEqual(event.source, "default_timeout")
self.assertEqual(emulator.cpu.regs[0], 0)
self.assertTrue(emulator.cpu.z)
self.assertEqual(emulator.cpu.pc, 0x789A)
def test_wrapper_can_succeed_from_queued_p9_device_response(self):
emulator = H8536Emulator(bytes(rom_with_reset()))
emulator.cpu.pc = LOC_BFE0_TRANSFER_WRAPPER
emulator.cpu.regs[7] = 0xFE86
emulator.memory.write16(0xFE86, 0x89AB)
emulator.memory.p9_bus.queue_wrapper_results([True], source="panel-script")
fast_path = P9FastPath(P9FastPathConfig(enabled=True))
self.assertTrue(fast_path.try_handle(emulator))
event = fast_path.events[-1]
self.assertEqual(event.kind, "wrapper_success")
self.assertTrue(event.success)
self.assertEqual(event.source, "panel-script")
self.assertEqual(emulator.cpu.regs[0], 1)
self.assertFalse(emulator.cpu.z)
self.assertEqual(emulator.cpu.pc, 0x89AB)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -36,6 +36,16 @@ class P9BusTest(unittest.TestCase):
self.assertEqual(bus.transmitted_bits, [1, 0, 1, 0, 0, 1, 0, 1]) self.assertEqual(bus.transmitted_bits, [1, 0, 1, 0, 0, 1, 0, 1])
self.assertEqual(bus.byte_candidates, [0xA5]) self.assertEqual(bus.byte_candidates, [0xA5])
self.assertEqual([event.edge for event in bus.strobe_edges[:2]], ["rising", "falling"]) self.assertEqual([event.edge for event in bus.strobe_edges[:2]], ["rising", "falling"])
self.assertIn("tx_byte ddr=93 dr=82 value=A5", bus.trace_lines())
def test_wrapper_results_are_queued_then_default_timeout(self):
bus = P9Bus()
bus.queue_wrapper_results([True], source="panel-script")
self.assertEqual(bus.consume_wrapper_result(), (True, "panel-script", 0))
self.assertEqual(bus.consume_wrapper_result(), (False, "default_timeout", 0))
self.assertIn("wrapper_result ddr=00 dr=00 value=01 success=1 source=panel-script queued=0", bus.trace_lines())
self.assertIn("wrapper_result ddr=00 dr=00 value=00 success=0 source=default_timeout queued=0", bus.trace_lines())
if __name__ == "__main__": if __name__ == "__main__":