from __future__ import annotations from dataclasses import dataclass from typing import Iterable from .x24164 import X24164Bus P9_ACK_BIT = 0x80 P9_STROBE_BIT = 0x02 @dataclass(frozen=True) class P9StrobeEvent: edge: str ddr: int dr: int data_bit: int 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 message: str | 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}") if self.message is not None: parts.append(self.message) return " ".join(parts) class P9Bus: """Small model for the ROM's P9 bit-banged serial handshake. Board tracing ties P91/P97 to X24164 SCL/SDA. The legacy bit queue is retained for tests and exploratory scripts, while the X24164 model now drives SDA during recognized EEPROM transactions. """ def __init__( self, ddr: int = 0x00, dr: int = 0x00, input_bits: Iterable[int] = (), wrapper_results: Iterable[bool] = (), x24164_bus: X24164Bus | None = None, ) -> None: self.ddr = ddr & 0xFF self.dr_latch = dr & 0xFF self.input_bits: list[int] = [1 if bit else 0 for bit in input_bits] 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.trace_events: list[P9TraceEvent] = [] self.transmitted_bits: list[int] = [] self.byte_candidates: list[int] = [] self.x24164_bus = x24164_bus if x24164_bus is not None else X24164Bus() self._x24164_trace_index = 0 def write_ddr(self, value: int) -> int: self.ddr = value & 0xFF self.trace_events.append(P9TraceEvent("write_ddr", self.ddr, self.dr_latch, value=self.ddr)) return self.ddr def write_dr(self, value: int) -> int: previous = self.dr_latch previous_ddr = self.ddr 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) current_strobe = bool(self.dr_latch & P9_STROBE_BIT) if previous_strobe != current_strobe: edge = "rising" if current_strobe else "falling" data_bit = 1 if self.dr_latch & P9_ACK_BIT else 0 bit7_output = bool(self.ddr & P9_ACK_BIT) 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: self._record_transmitted_bit(data_bit) self.x24164_bus.observe( previous_scl=bool(previous & P9_STROBE_BIT), previous_master_sda=bool(previous & P9_ACK_BIT) if previous_ddr & P9_ACK_BIT else True, current_scl=bool(self.dr_latch & P9_STROBE_BIT), current_master_sda=bool(self.dr_latch & P9_ACK_BIT) if self.ddr & P9_ACK_BIT else True, master_sda_output=bool(self.ddr & P9_ACK_BIT), ) self._append_x24164_trace() return self.dr_latch def read_ddr(self) -> int: return self.ddr def read_dr(self) -> int: value = self.dr_latch input_bit = None source = None if not (self.ddr & P9_ACK_BIT): x24164_bit = self.x24164_bus.sda_bit() if self.input_bits: input_bit = self.input_bits.pop(0) source = "queued_bit" elif x24164_bit is not None: input_bit = x24164_bit source = "x24164" else: input_bit = self.default_input_bit source = "default_bit" if input_bit: value |= P9_ACK_BIT else: 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 def queue_input_bits(self, bits: Iterable[int]) -> None: self.input_bits.extend(1 if bit else 0 for bit in bits) def set_default_input_bit(self, bit: int) -> None: 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 fast_start(self) -> None: self.x24164_bus.fast_start() self._append_x24164_trace() def fast_stop(self) -> None: self.x24164_bus.fast_stop() self._append_x24164_trace() def fast_master_ack(self, ack: bool = True) -> None: self.x24164_bus.fast_master_ack(ack) self._append_x24164_trace() def fast_write_byte(self, value: int) -> bool: success = self.x24164_bus.fast_write_byte(value) self.trace_events.append( P9TraceEvent( "fast_write_byte", self.ddr, self.dr_latch, value=value & 0xFF, success=success, source="x24164", ) ) self._append_x24164_trace() return success def fast_read_byte(self) -> int | None: value = self.x24164_bus.fast_read_byte() if value is not None: self.trace_events.append(P9TraceEvent("fast_read_byte", self.ddr, self.dr_latch, value=value, source="x24164")) self._append_x24164_trace() return value def fast_read_word(self, address: int) -> tuple[bool, int]: success, value = self.x24164_bus.read_linear_word(address) self.trace_events.append( P9TraceEvent( "fast_read_word", self.ddr, self.dr_latch, value=(value >> 8) & 0xFF, success=success, source="x24164", message=f"addr={address & 0x0FFF:03X} word={value & 0xFFFF:04X}", ) ) self._append_x24164_trace() return success, value def fast_write_word(self, address: int, value: int) -> bool: success = self.x24164_bus.write_linear_word(address, value) self.trace_events.append( P9TraceEvent( "fast_write_word", self.ddr, self.dr_latch, value=(value >> 8) & 0xFF, success=success, source="x24164", message=f"addr={address & 0x0FFF:03X} word={value & 0xFFFF:04X}", ) ) self._append_x24164_trace() return success 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)) if len(self.transmitted_bits) % 8 == 0: byte = 0 for data_bit in self.transmitted_bits[-8:]: byte = (byte << 1) | data_bit self.byte_candidates.append(byte) self.trace_events.append(P9TraceEvent("tx_byte", self.ddr, self.dr_latch, value=byte)) def _append_x24164_trace(self) -> None: new_events = self.x24164_bus.trace_events[self._x24164_trace_index :] self._x24164_trace_index = len(self.x24164_bus.trace_events) for event in new_events: self.trace_events.append(P9TraceEvent("x24164", self.ddr, self.dr_latch, message=event.line()))