p9 bus emulation
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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())}")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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__":
|
||||||
|
|||||||
Reference in New Issue
Block a user