non-volatile storage emulation
This commit is contained in:
12
README.md
12
README.md
@@ -86,7 +86,7 @@ The real-device bench helper uses `pyserial`; install repo dependencies with `.\
|
||||
- Handles the E-clock transfer instructions `MOVFPE` and `MOVTPE`.
|
||||
- Recognizes likely LCD E-clock access routines at `H'F200`/`H'F201`, including busy-flag polling and data/control writes.
|
||||
- Generates a separate C-like pseudocode view from the JSON, preserving labels, calls, branches, register names, inferred symbols, metadata comments, optional cycle notes, and simple structured `if`/`do while` patterns.
|
||||
- Provides an early H8/536 emulator harness with ROM/RAM/register memory mapping, reset-vector boot, SCI1 transmit capture, MOV condition-code updates, `SCB/F`, stack/call/return support, indirect `JMP/JSR @Rn` dispatch, scaffolded SCI1 RXI/ERI/TXI and interval timer scheduling, manual-derived FRT1/FRT2 OCIA cycle scheduling, a P9 bit-banged bus model, a 16x4 LCD bus/DDRAM model for `H'F200`/`H'F201`, and an opt-in P9 transfer fast path.
|
||||
- Provides an early H8/536 emulator harness with ROM/RAM/register memory mapping, reset-vector boot, SCI1 transmit capture, MOV condition-code updates, `SCB/F`, stack/call/return support, indirect `JMP/JSR @Rn` dispatch, scaffolded SCI1 RXI/ERI/TXI and interval timer scheduling, manual-derived FRT1/FRT2 OCIA cycle scheduling, a P9 bit-banged bus model, an X24164 two-wire EEPROM model on traced `P91/SCL` and `P97/SDA`, a 16x4 LCD bus/DDRAM model for `H'F200`/`H'F201`, and an opt-in P9 transfer fast path.
|
||||
- Includes an emulator probe that reports hot PCs, recent P9/SCI accesses, serial report queue/gate traces, RAM lifecycle watches, final SCI1/TXI state, and captured P9 byte candidates while running the real ROM.
|
||||
- Includes an RX command probe that boots until SCI1 RXI is serviceable, injects host six-byte frames through RDR/RDRF, can optionally schedule 38400 8N1 byte arrivals at real UART spacing, listens for device TX frames, and reports serial latch/table/LCD-buffer and emulated-LCD effects.
|
||||
- Includes a bench helper for replaying the emulator-derived CONNECT LCD frame sequence against the real device through COM5, with optional COM6 relay power cycling and timestamped capture logs.
|
||||
@@ -102,6 +102,8 @@ 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.
|
||||
- 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.
|
||||
- Board/P9 finding: traced MCU pin 62 `P91` reaches X24164 pin 6 `SCL`, and MCU pin 68 `P97` reaches the shared X24164 pin 5 `SDA` node. The emulator now treats the ROM's `C121/C08B/C0DB/C10C/C142` P9 routines as an X24164-style two-wire EEPROM bus, with ROM logical addresses `0x000-0x7FF` on the `H'A0/H'A1` control-byte family and `0x800-0xFFF` on `H'E0/H'E1`.
|
||||
- EEPROM role finding: `loc_40BB` checks `P7DR.7` and the `F402 == H'6B6F` signature before defaulting EEPROM/shadow tables; `loc_4103` writes ROM default words through `BFE0`, `loc_41D2` reads sixteen 8-byte records into `F7B0-F82F`, and the command-4 path at `BD2B-BD5F` can persist serial table writes when `F76E.7` is set.
|
||||
- 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.
|
||||
- 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,8 +214,8 @@ python h8536_emulator_rx_probe.py --help
|
||||
- `--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.
|
||||
- `--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. 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.
|
||||
- `--p9-fast-path`: shortcut known P9 transfer routines for exploration. Fast-path byte/marker calls now feed the X24164 EEPROM model, and `BFE0/BFFE` wrapper shortcuts perform EEPROM word write-verify/read operations against the modeled banks.
|
||||
- `--p9-fast-optimistic-wrapper`: legacy fallback for older wrapper experiments; the known `BFE0/BFFE` EEPROM wrappers now use the X24164 model instead.
|
||||
- `--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.
|
||||
- `h8536_emulator_rx_probe.py "04 00 00 80 00"`: append the checksum, inject the host frame through SCI1 RX, and summarize responses.
|
||||
@@ -221,7 +223,7 @@ python h8536_emulator_rx_probe.py --help
|
||||
- `h8536_emulator_rx_probe.py --preset connect-lcd`: replay the current CONNECT LCD activation candidates.
|
||||
- `scripts\bench_connect_lcd_sequence.py --port COM5 --relay-port COM6 --prompt-screen`: power-cycle the bench device, wait for heartbeat readiness, send `04 00 00 40 00 1E`, `04 00 00 80 00 DE`, `04 00 00 C0 00 9E`, log RX/TX, and prompt for observed LCD text.
|
||||
- `h8536_emulator_bench_replay.py captures\bench-connect-lcd-sequence-20260525-214411.txt --assert-bench-parity`: replay a real bench log into the emulator using timed UART RX by default and intentionally fail while any response/LCD state still diverges from the bench-observed `CONNECT NOT ACT` plus `07 80 C0 60 20 5D` path. Pass `--polite-rx` for the old wait-until-consumed injection mode.
|
||||
- Current status: boots from `H'1000`, initializes SCI1, models the first P9 bit-banged handshakes, captures P9 byte candidates, can optionally fast-path known P9 routines, schedules FRT1/FRT2 OCIA from timer registers and `--clock-hz`, captures the ROM-driven LCD line ` CONNECT:NOT ACT`, and emits the observed heartbeat frame `00 00 00 00 80 DA`.
|
||||
- Current status: boots from `H'1000`, initializes SCI1, models the traced X24164 EEPROM bus on P9, captures P9 byte candidates, can optionally fast-path known P9 EEPROM routines, schedules FRT1/FRT2 OCIA from timer registers and `--clock-hz`, captures the ROM-driven LCD line ` CONNECT:NOT ACT`, and emits the observed heartbeat frame `00 00 00 00 80 DA`.
|
||||
|
||||
## Code Layout
|
||||
|
||||
@@ -252,7 +254,7 @@ python h8536_emulator_rx_probe.py --help
|
||||
- `h8536/report_source_trace.py`: direct `loc_3E54` report enqueue source tracer.
|
||||
- `h8536/table_xrefs.py`: table/index xrefs and LCD correlation report generation.
|
||||
- `h8536/consistency.py`: decompiler/pseudocode semantic consistency checks.
|
||||
- `h8536/emulator/`: early H8/536 emulator package split into CPU state, memory map, SCI1 TX capture, 38400 8N1 UART injection timing, P9 bus model, LCD model, manual-derived FRT timer scheduling, runner, probe, CLI, and peripheral scaffolding.
|
||||
- `h8536/emulator/`: early H8/536 emulator package split into CPU state, memory map, SCI1 TX capture, 38400 8N1 UART injection timing, P9/X24164 EEPROM bus model, LCD model, manual-derived FRT timer scheduling, runner, probe, CLI, and peripheral scaffolding.
|
||||
- `h8536/emulator/rx_probe.py`: host-frame injection and response/listener probe for SCI1 RX experiments.
|
||||
- `h8536/board_profile.py`: Sony RCP-TX7 board-trace annotations, including the MAX202 RS232 path.
|
||||
- `h8536/peripheral_access.py`: FRT/A-D TEMP-register access analysis.
|
||||
|
||||
@@ -55,7 +55,7 @@ from .cpu import CPUState
|
||||
from .errors import EmulatorError, UnsupportedInstruction
|
||||
from .fast_paths import P9FastPath, P9FastPathConfig, P9FastPathEvent
|
||||
from .memory import MemoryAccess, MemoryMap, describe_regions
|
||||
from .peripherals import LCD, P9TraceEvent
|
||||
from .peripherals import LCD, P9TraceEvent, X24164Bus, X24164Device, X24164TraceEvent
|
||||
from .runner import H8536Emulator, RunReport
|
||||
from .sci import SCI1, SciTxEvent
|
||||
from .uart import UartTiming
|
||||
@@ -124,6 +124,9 @@ __all__ = [
|
||||
"VECTOR_SCI1_RXI",
|
||||
"VECTOR_SCI1_TXI",
|
||||
"WDT_TCSR_R",
|
||||
"X24164Bus",
|
||||
"X24164Device",
|
||||
"X24164TraceEvent",
|
||||
"build_arg_parser",
|
||||
"describe_regions",
|
||||
"discover_rom_path",
|
||||
|
||||
@@ -368,7 +368,7 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
||||
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("--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("--p9-fast-optimistic-wrapper", action="store_true", help="legacy fallback for older wrapper experiments; known BFE0/BFFE wrappers use the X24164 model")
|
||||
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")
|
||||
return parser
|
||||
|
||||
@@ -44,7 +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("--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-optimistic-wrapper", action="store_true", help="make P9 fast-path wrapper calls succeed when no modeled P9 response is queued")
|
||||
parser.add_argument("--p9-fast-optimistic-wrapper", action="store_true", help="legacy fallback for older wrapper experiments; known BFE0/BFFE wrappers use the X24164 model")
|
||||
return parser
|
||||
|
||||
|
||||
|
||||
@@ -10,19 +10,22 @@ LOC_BFE0_TRANSFER_WRAPPER = 0xBFE0
|
||||
LOC_BFFE_TRANSFER_WRAPPER = 0xBFFE
|
||||
LOC_C08B_P9_WRITE_BYTE = 0xC08B
|
||||
LOC_C0DB_P9_READ_BYTE = 0xC0DB
|
||||
LOC_C10C_P9_MASTER_ACK = 0xC10C
|
||||
LOC_C10C_P9_MARKER = 0xC10C
|
||||
LOC_C121_P9_MARKER = 0xC121
|
||||
LOC_C142_P9_MARKER = 0xC142
|
||||
LOC_C121_P9_START = 0xC121
|
||||
LOC_C121_P9_MARKER = LOC_C121_P9_START
|
||||
LOC_C142_P9_STOP = 0xC142
|
||||
LOC_C142_P9_MARKER = LOC_C142_P9_STOP
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class P9FastPathConfig:
|
||||
"""Configuration for optional ROM P9 transfer shortcuts.
|
||||
|
||||
The helper assumes the CPU PC is exactly at a known routine entry. It
|
||||
models the routine as if it completed successfully and returned via RTS.
|
||||
Integration should keep this disabled unless the runner intentionally opts
|
||||
into skipping these ROM routines.
|
||||
The helper assumes the CPU PC is exactly at a known routine entry. It feeds
|
||||
those byte/marker/wrapper operations into the modeled P9/X24164 bus and
|
||||
returns via RTS. Integration should keep this disabled unless the runner
|
||||
intentionally opts into skipping these ROM routines.
|
||||
"""
|
||||
|
||||
enabled: bool = False
|
||||
@@ -100,8 +103,7 @@ class P9FastPath:
|
||||
elif pc == (self.config.read_byte_pc & 0xFFFF):
|
||||
self._handle_read_byte(emulator)
|
||||
elif pc in self.config.marker_pcs:
|
||||
self.events.append(P9FastPathEvent("marker", pc))
|
||||
self._return_from_subroutine(emulator)
|
||||
self._handle_marker(emulator, pc)
|
||||
elif pc in self.config.wrapper_pcs:
|
||||
self._handle_wrapper(emulator)
|
||||
else:
|
||||
@@ -116,10 +118,12 @@ class P9FastPath:
|
||||
pc = emulator.cpu.pc & 0xFFFF
|
||||
value = emulator.cpu.regs[0] & 0xFF
|
||||
self.output_bytes.append(value)
|
||||
self.events.append(P9FastPathEvent("write_byte", pc, value))
|
||||
success = emulator.memory.p9_bus.fast_write_byte(value)
|
||||
self.events.append(P9FastPathEvent("write_byte", pc, value, source="x24164", success=success))
|
||||
|
||||
emulator.cpu.regs[0] = 1
|
||||
self._set_logic_flags(emulator.cpu, 1, 1)
|
||||
result = 1 if success else 0
|
||||
emulator.cpu.regs[0] = result
|
||||
self._set_logic_flags(emulator.cpu, result, 1)
|
||||
self._return_from_subroutine(emulator)
|
||||
|
||||
def _handle_read_byte(self, emulator: Any) -> None:
|
||||
@@ -127,6 +131,9 @@ class P9FastPath:
|
||||
if self.input_bytes:
|
||||
value = self.input_bytes.pop(0)
|
||||
source = self.input_sources.pop(0) if self.input_sources else "queued"
|
||||
elif (x24164_value := emulator.memory.p9_bus.fast_read_byte()) is not None:
|
||||
value = x24164_value
|
||||
source = "x24164"
|
||||
else:
|
||||
value = self.config.default_input_byte
|
||||
source = "default_input_byte"
|
||||
@@ -141,6 +148,26 @@ class P9FastPath:
|
||||
|
||||
def _handle_wrapper(self, emulator: Any) -> None:
|
||||
pc = emulator.cpu.pc & 0xFFFF
|
||||
success: bool
|
||||
source: str
|
||||
queue_depth: int | None
|
||||
if pc == (LOC_BFE0_TRANSFER_WRAPPER & 0xFFFF):
|
||||
address = emulator.cpu.regs[4] & 0x0FFF
|
||||
value = emulator.cpu.regs[5] & 0xFFFF
|
||||
write_success = emulator.memory.p9_bus.fast_write_word(address, value)
|
||||
read_success, readback = emulator.memory.p9_bus.fast_read_word(address)
|
||||
success = write_success and read_success and readback == value
|
||||
source = "x24164_write_verify"
|
||||
queue_depth = None
|
||||
elif pc == (LOC_BFFE_TRANSFER_WRAPPER & 0xFFFF):
|
||||
address = emulator.cpu.regs[4] & 0x0FFF
|
||||
read_success, value = emulator.memory.p9_bus.fast_read_word(address)
|
||||
if read_success:
|
||||
emulator.cpu.regs[5] = value & 0xFFFF
|
||||
success = read_success
|
||||
source = "x24164_read_word"
|
||||
queue_depth = None
|
||||
else:
|
||||
success, source, queue_depth = emulator.memory.p9_bus.consume_wrapper_result()
|
||||
value = 1 if success else 0
|
||||
self.events.append(
|
||||
@@ -163,6 +190,20 @@ class P9FastPath:
|
||||
emulator.cpu.pc = emulator.memory.read16(sp) & 0xFFFF
|
||||
emulator.cpu.regs[7] = (sp + 2) & 0xFFFF
|
||||
|
||||
def _handle_marker(self, emulator: Any, pc: int) -> None:
|
||||
if pc == (LOC_C121_P9_START & 0xFFFF):
|
||||
emulator.memory.p9_bus.fast_start()
|
||||
self.events.append(P9FastPathEvent("start", pc, source="x24164"))
|
||||
elif pc == (LOC_C10C_P9_MASTER_ACK & 0xFFFF):
|
||||
emulator.memory.p9_bus.fast_master_ack(True)
|
||||
self.events.append(P9FastPathEvent("master_ack", pc, source="x24164"))
|
||||
elif pc == (LOC_C142_P9_STOP & 0xFFFF):
|
||||
emulator.memory.p9_bus.fast_stop()
|
||||
self.events.append(P9FastPathEvent("stop", pc, source="x24164"))
|
||||
else:
|
||||
self.events.append(P9FastPathEvent("marker", pc))
|
||||
self._return_from_subroutine(emulator)
|
||||
|
||||
def _set_logic_flags(self, cpu: Any, value: int, size: int) -> None:
|
||||
value &= mask(size)
|
||||
cpu.z = value == 0
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
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, P9TraceEvent
|
||||
from .x24164 import X24164Bus, X24164Device, X24164TraceEvent
|
||||
|
||||
__all__ = [
|
||||
"LCD_E_CLOCK_DATA",
|
||||
@@ -13,4 +14,7 @@ __all__ = [
|
||||
"P9Bus",
|
||||
"P9StrobeEvent",
|
||||
"P9TraceEvent",
|
||||
"X24164Bus",
|
||||
"X24164Device",
|
||||
"X24164TraceEvent",
|
||||
]
|
||||
|
||||
@@ -3,6 +3,8 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterable
|
||||
|
||||
from .x24164 import X24164Bus
|
||||
|
||||
|
||||
P9_ACK_BIT = 0x80
|
||||
P9_STROBE_BIT = 0x02
|
||||
@@ -27,6 +29,7 @@ class P9TraceEvent:
|
||||
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}"]
|
||||
@@ -40,11 +43,18 @@ class P9TraceEvent:
|
||||
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."""
|
||||
"""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,
|
||||
@@ -52,6 +62,7 @@ class P9Bus:
|
||||
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
|
||||
@@ -64,6 +75,8 @@ class P9Bus:
|
||||
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
|
||||
@@ -72,6 +85,7 @@ class P9Bus:
|
||||
|
||||
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))
|
||||
|
||||
@@ -86,6 +100,14 @@ class P9Bus:
|
||||
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:
|
||||
@@ -96,9 +118,13 @@ class P9Bus:
|
||||
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"
|
||||
@@ -145,6 +171,72 @@ class P9Bus:
|
||||
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))
|
||||
@@ -154,3 +246,9 @@ class P9Bus:
|
||||
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()))
|
||||
|
||||
362
h8536/emulator/peripherals/x24164.py
Normal file
362
h8536/emulator/peripherals/x24164.py
Normal file
@@ -0,0 +1,362 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
X24164_SIZE = 2048
|
||||
|
||||
|
||||
@dataclass
|
||||
class X24164Device:
|
||||
"""Small Xicor X24164 serial EEPROM model.
|
||||
|
||||
The ROM uses two control-byte families on the P91/P97 two-wire bus:
|
||||
H'A0/H'A1 for the low logical half and H'E0/H'E1 for the high logical half.
|
||||
X24164 has unusual device-select encoding compared with later 24Cxx parts,
|
||||
so the emulator stores the accepted high-nibble control family directly.
|
||||
"""
|
||||
|
||||
name: str
|
||||
control_base: int
|
||||
data: bytearray = field(default_factory=lambda: bytearray([0xFF] * X24164_SIZE))
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.control_base &= 0xF0
|
||||
if len(self.data) < X24164_SIZE:
|
||||
self.data.extend([0xFF] * (X24164_SIZE - len(self.data)))
|
||||
elif len(self.data) > X24164_SIZE:
|
||||
del self.data[X24164_SIZE:]
|
||||
|
||||
def matches_control(self, value: int) -> bool:
|
||||
return (value & 0xF0) == self.control_base
|
||||
|
||||
def offset_from_control(self, value: int, word_address: int) -> int:
|
||||
high_address = (value >> 1) & 0x07
|
||||
return ((high_address << 8) | (word_address & 0xFF)) & (X24164_SIZE - 1)
|
||||
|
||||
def read(self, offset: int) -> int:
|
||||
return self.data[offset & (X24164_SIZE - 1)]
|
||||
|
||||
def write(self, offset: int, value: int) -> None:
|
||||
self.data[offset & (X24164_SIZE - 1)] = value & 0xFF
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class X24164TraceEvent:
|
||||
kind: str
|
||||
device: str | None = None
|
||||
value: int | None = None
|
||||
address: int | None = None
|
||||
bit: int | None = None
|
||||
ack: bool | None = None
|
||||
message: str | None = None
|
||||
|
||||
def line(self) -> str:
|
||||
parts = [self.kind]
|
||||
if self.device is not None:
|
||||
parts.append(f"device={self.device}")
|
||||
if self.address is not None:
|
||||
parts.append(f"addr={self.address:03X}")
|
||||
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.ack is not None:
|
||||
parts.append(f"ack={int(self.ack)}")
|
||||
if self.message is not None:
|
||||
parts.append(self.message)
|
||||
return " ".join(parts)
|
||||
|
||||
|
||||
class X24164Bus:
|
||||
"""Bit-level two-wire bus model for X24164 EEPROMs."""
|
||||
|
||||
def __init__(self, devices: list[X24164Device] | None = None) -> None:
|
||||
self.devices = devices if devices is not None else default_x24164_devices()
|
||||
self.trace_events: list[X24164TraceEvent] = []
|
||||
self.active = False
|
||||
self.phase = "idle"
|
||||
self.selected: X24164Device | None = None
|
||||
self.control_byte = 0
|
||||
self.address = 0
|
||||
self._rx_bits: list[int] = []
|
||||
self._ack_pending: bool | None = None
|
||||
self._ack_armed_on_current_clock = False
|
||||
self._read_byte = 0xFF
|
||||
self._read_bit_index = 0
|
||||
self._read_prepared_on_current_clock = False
|
||||
self._awaiting_master_ack = False
|
||||
|
||||
def observe(
|
||||
self,
|
||||
*,
|
||||
previous_scl: bool,
|
||||
previous_master_sda: bool,
|
||||
current_scl: bool,
|
||||
current_master_sda: bool,
|
||||
master_sda_output: bool,
|
||||
) -> None:
|
||||
if previous_scl and current_scl and previous_master_sda != current_master_sda:
|
||||
if previous_master_sda and not current_master_sda:
|
||||
self.start()
|
||||
elif not previous_master_sda and current_master_sda:
|
||||
self.stop()
|
||||
|
||||
if not previous_scl and current_scl:
|
||||
self._scl_rising(current_master_sda, master_sda_output)
|
||||
elif previous_scl and not current_scl:
|
||||
self._scl_falling()
|
||||
|
||||
def start(self) -> None:
|
||||
self.active = True
|
||||
self.phase = "control"
|
||||
self.selected = None
|
||||
self._rx_bits.clear()
|
||||
self._ack_pending = None
|
||||
self._ack_armed_on_current_clock = False
|
||||
self._read_prepared_on_current_clock = False
|
||||
self._awaiting_master_ack = False
|
||||
self.trace_events.append(X24164TraceEvent("x24164_start"))
|
||||
|
||||
def stop(self) -> None:
|
||||
if self.active:
|
||||
self.trace_events.append(X24164TraceEvent("x24164_stop", device=self._selected_name()))
|
||||
self.active = False
|
||||
self.phase = "idle"
|
||||
self.selected = None
|
||||
self._rx_bits.clear()
|
||||
self._ack_pending = None
|
||||
self._ack_armed_on_current_clock = False
|
||||
self._read_prepared_on_current_clock = False
|
||||
self._awaiting_master_ack = False
|
||||
|
||||
def sda_bit(self) -> int | None:
|
||||
if not self.active:
|
||||
return None
|
||||
if self._ack_pending is not None:
|
||||
return 0 if self._ack_pending else 1
|
||||
if self.phase == "read_data" and not self._awaiting_master_ack:
|
||||
return (self._read_byte >> (7 - self._read_bit_index)) & 1
|
||||
return 1
|
||||
|
||||
def fast_start(self) -> None:
|
||||
self.start()
|
||||
|
||||
def fast_stop(self) -> None:
|
||||
self.stop()
|
||||
|
||||
def fast_write_byte(self, value: int) -> bool:
|
||||
self._accept_byte(value & 0xFF)
|
||||
ack = bool(self._ack_pending)
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent("x24164_fast_write_byte", self._selected_name(), value=value & 0xFF, ack=ack)
|
||||
)
|
||||
self._ack_pending = None
|
||||
self._ack_armed_on_current_clock = False
|
||||
if self.phase == "read_data" and self.selected is not None:
|
||||
self._prepare_read_byte()
|
||||
return ack
|
||||
|
||||
def fast_read_byte(self) -> int | None:
|
||||
if not self.active or self.phase != "read_data" or self.selected is None:
|
||||
return None
|
||||
value = self.selected.read(self.address)
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent("x24164_fast_read_byte", self.selected.name, value=value, address=self.address)
|
||||
)
|
||||
self.address = (self.address + 1) & (X24164_SIZE - 1)
|
||||
self._prepare_read_byte()
|
||||
return value
|
||||
|
||||
def fast_master_ack(self, ack: bool = True) -> None:
|
||||
if not self.active:
|
||||
return
|
||||
self.trace_events.append(X24164TraceEvent("x24164_fast_master_ack", self._selected_name(), ack=ack))
|
||||
if ack and self.phase == "read_data" and self.selected is not None:
|
||||
self._prepare_read_byte()
|
||||
elif not ack:
|
||||
self.phase = "idle"
|
||||
|
||||
def read_linear_word(self, address: int) -> tuple[bool, int]:
|
||||
device = self._device_for_linear_address(address)
|
||||
if device is None:
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent("x24164_linear_read_miss", address=address & 0x0FFF, message="no_mapped_device")
|
||||
)
|
||||
return False, 0xFFFF
|
||||
offset = address & (X24164_SIZE - 1)
|
||||
high = device.read(offset)
|
||||
low = device.read((offset + 1) & (X24164_SIZE - 1))
|
||||
value = (high << 8) | low
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent("x24164_linear_read_word", device.name, value=high, address=offset, message=f"word={value:04X}")
|
||||
)
|
||||
return True, value
|
||||
|
||||
def write_linear_word(self, address: int, value: int) -> bool:
|
||||
device = self._device_for_linear_address(address)
|
||||
if device is None:
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent("x24164_linear_write_miss", address=address & 0x0FFF, message="no_mapped_device")
|
||||
)
|
||||
return False
|
||||
offset = address & (X24164_SIZE - 1)
|
||||
device.write(offset, (value >> 8) & 0xFF)
|
||||
device.write((offset + 1) & (X24164_SIZE - 1), value & 0xFF)
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent(
|
||||
"x24164_linear_write_word",
|
||||
device.name,
|
||||
value=(value >> 8) & 0xFF,
|
||||
address=offset,
|
||||
message=f"word={value & 0xFFFF:04X}",
|
||||
)
|
||||
)
|
||||
return True
|
||||
|
||||
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 _scl_rising(self, master_sda: bool, master_sda_output: bool) -> None:
|
||||
if not self.active:
|
||||
return
|
||||
if self._ack_pending is not None:
|
||||
self.trace_events.append(X24164TraceEvent("x24164_ack_bit", self._selected_name(), ack=self._ack_pending))
|
||||
return
|
||||
if self.phase == "read_data":
|
||||
if self._awaiting_master_ack:
|
||||
ack = not master_sda if master_sda_output else False
|
||||
self.trace_events.append(X24164TraceEvent("x24164_master_ack", self._selected_name(), ack=ack))
|
||||
self._awaiting_master_ack = False
|
||||
if ack and self.selected is not None:
|
||||
self._prepare_read_byte(skip_current_falling=True)
|
||||
else:
|
||||
self.phase = "idle"
|
||||
return
|
||||
if master_sda_output:
|
||||
self._rx_bits.append(1 if master_sda else 0)
|
||||
self.trace_events.append(X24164TraceEvent("x24164_rx_bit", self._selected_name(), bit=self._rx_bits[-1]))
|
||||
if len(self._rx_bits) == 8:
|
||||
value = 0
|
||||
for bit in self._rx_bits:
|
||||
value = (value << 1) | bit
|
||||
self._rx_bits.clear()
|
||||
self._accept_byte(value)
|
||||
|
||||
def _scl_falling(self) -> None:
|
||||
if not self.active:
|
||||
return
|
||||
if self._ack_pending is not None:
|
||||
if self._ack_armed_on_current_clock:
|
||||
self._ack_armed_on_current_clock = False
|
||||
return
|
||||
self._ack_pending = None
|
||||
if self.phase == "read_data" and self.selected is not None:
|
||||
self._prepare_read_byte()
|
||||
return
|
||||
if self.phase == "read_data" and not self._awaiting_master_ack:
|
||||
if self._read_prepared_on_current_clock:
|
||||
self._read_prepared_on_current_clock = False
|
||||
return
|
||||
if self._read_bit_index < 7:
|
||||
self._read_bit_index += 1
|
||||
else:
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent("x24164_tx_byte_done", self._selected_name(), value=self._read_byte)
|
||||
)
|
||||
if self.selected is not None:
|
||||
self.address = (self.address + 1) & (X24164_SIZE - 1)
|
||||
self._awaiting_master_ack = True
|
||||
|
||||
def _accept_byte(self, value: int) -> None:
|
||||
value &= 0xFF
|
||||
if self.phase == "control":
|
||||
self.control_byte = value
|
||||
self.selected = self._device_for_control(value)
|
||||
read_mode = bool(value & 1)
|
||||
self._ack_pending = self.selected is not None
|
||||
self._ack_armed_on_current_clock = True
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent(
|
||||
"x24164_control",
|
||||
self._selected_name(),
|
||||
value=value,
|
||||
ack=self._ack_pending,
|
||||
message="read" if read_mode else "write",
|
||||
)
|
||||
)
|
||||
if self.selected is None:
|
||||
self.phase = "ignore"
|
||||
elif read_mode:
|
||||
self.phase = "read_data"
|
||||
else:
|
||||
self.phase = "word_address"
|
||||
return
|
||||
if self.phase == "word_address":
|
||||
if self.selected is None:
|
||||
self._ack_pending = False
|
||||
self._ack_armed_on_current_clock = True
|
||||
self.phase = "ignore"
|
||||
return
|
||||
self.address = self.selected.offset_from_control(self.control_byte, value)
|
||||
self._ack_pending = True
|
||||
self._ack_armed_on_current_clock = True
|
||||
self.phase = "write_data"
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent("x24164_word_address", self.selected.name, value=value, address=self.address, ack=True)
|
||||
)
|
||||
return
|
||||
if self.phase == "write_data":
|
||||
if self.selected is None:
|
||||
self._ack_pending = False
|
||||
self._ack_armed_on_current_clock = True
|
||||
self.phase = "ignore"
|
||||
return
|
||||
self.selected.write(self.address, value)
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent("x24164_write_data", self.selected.name, value=value, address=self.address, ack=True)
|
||||
)
|
||||
self.address = (self.address + 1) & (X24164_SIZE - 1)
|
||||
self._ack_pending = True
|
||||
self._ack_armed_on_current_clock = True
|
||||
return
|
||||
self._ack_pending = False
|
||||
self._ack_armed_on_current_clock = True
|
||||
self.trace_events.append(X24164TraceEvent("x24164_ignored_byte", self._selected_name(), value=value, ack=False))
|
||||
|
||||
def _prepare_read_byte(self, *, skip_current_falling: bool = False) -> None:
|
||||
if self.selected is None:
|
||||
self._read_byte = 0xFF
|
||||
return
|
||||
self._read_byte = self.selected.read(self.address)
|
||||
self._read_bit_index = 0
|
||||
self._read_prepared_on_current_clock = skip_current_falling
|
||||
self._awaiting_master_ack = False
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent("x24164_prepare_read", self.selected.name, value=self._read_byte, address=self.address)
|
||||
)
|
||||
|
||||
def _device_for_control(self, value: int) -> X24164Device | None:
|
||||
for device in self.devices:
|
||||
if device.matches_control(value):
|
||||
return device
|
||||
return None
|
||||
|
||||
def _device_for_linear_address(self, address: int) -> X24164Device | None:
|
||||
bank = (address >> 11) & 1
|
||||
wanted_base = 0xA0 if bank == 0 else 0xE0
|
||||
for device in self.devices:
|
||||
if device.control_base == wanted_base:
|
||||
return device
|
||||
return None
|
||||
|
||||
def _selected_name(self) -> str | None:
|
||||
return self.selected.name if self.selected is not None else None
|
||||
|
||||
|
||||
def default_x24164_devices() -> list[X24164Device]:
|
||||
return [
|
||||
X24164Device("x24164_a0_lower_2k", 0xA0),
|
||||
X24164Device("x24164_e0_upper_2k", 0xE0),
|
||||
]
|
||||
@@ -1170,7 +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("--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-optimistic-wrapper", action="store_true", help="make P9 fast-path wrapper calls succeed when no modeled P9 response is queued")
|
||||
parser.add_argument("--p9-fast-optimistic-wrapper", action="store_true", help="legacy fallback for older wrapper experiments; known BFE0/BFFE wrappers use the X24164 model")
|
||||
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)
|
||||
|
||||
@@ -270,7 +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("--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-optimistic-wrapper", action="store_true", help="make P9 fast-path wrapper calls succeed when no modeled P9 response is queued")
|
||||
parser.add_argument("--p9-fast-optimistic-wrapper", action="store_true", help="legacy fallback for older wrapper experiments; known BFE0/BFFE wrappers use the X24164 model")
|
||||
return parser
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import unittest
|
||||
|
||||
from h8536.emulator.fast_paths import (
|
||||
LOC_BFE0_TRANSFER_WRAPPER,
|
||||
LOC_BFFE_TRANSFER_WRAPPER,
|
||||
LOC_C08B_P9_WRITE_BYTE,
|
||||
LOC_C0DB_P9_READ_BYTE,
|
||||
P9FastPath,
|
||||
@@ -26,7 +27,7 @@ class P9FastPathTest(unittest.TestCase):
|
||||
self.assertFalse(fast_path.try_handle(emulator))
|
||||
self.assertEqual(emulator.cpu.pc, LOC_C08B_P9_WRITE_BYTE)
|
||||
|
||||
def test_c08b_write_byte_logs_r0_sets_success_and_returns_to_caller(self):
|
||||
def test_c08b_write_byte_logs_r0_sets_timeout_without_active_eeprom_transaction(self):
|
||||
emulator = H8536Emulator(bytes(rom_with_reset()))
|
||||
emulator.cpu.pc = LOC_C08B_P9_WRITE_BYTE
|
||||
emulator.cpu.regs[0] = 0x12A5
|
||||
@@ -41,10 +42,11 @@ class P9FastPathTest(unittest.TestCase):
|
||||
self.assertEqual(fast_path.output_bytes, [0xA5])
|
||||
self.assertEqual(fast_path.events[-1].kind, "write_byte")
|
||||
self.assertEqual(fast_path.events[-1].value, 0xA5)
|
||||
self.assertEqual(emulator.cpu.regs[0], 1)
|
||||
self.assertFalse(fast_path.events[-1].success)
|
||||
self.assertEqual(emulator.cpu.regs[0], 0)
|
||||
self.assertEqual(emulator.cpu.pc, 0x3456)
|
||||
self.assertEqual(emulator.cpu.regs[7], 0xFE80)
|
||||
self.assertFalse(emulator.cpu.z)
|
||||
self.assertTrue(emulator.cpu.z)
|
||||
self.assertFalse(emulator.cpu.n)
|
||||
self.assertFalse(emulator.cpu.v)
|
||||
self.assertTrue(emulator.cpu.c)
|
||||
@@ -106,9 +108,11 @@ class P9FastPathTest(unittest.TestCase):
|
||||
self.assertEqual(event.queue_depth, 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):
|
||||
def test_wrapper_writes_and_verifies_against_x24164_model(self):
|
||||
emulator = H8536Emulator(bytes(rom_with_reset()))
|
||||
emulator.cpu.pc = LOC_BFE0_TRANSFER_WRAPPER
|
||||
emulator.cpu.regs[4] = 0x0812
|
||||
emulator.cpu.regs[5] = 0x3456
|
||||
emulator.cpu.regs[7] = 0xFE84
|
||||
emulator.memory.write16(0xFE84, 0x789A)
|
||||
|
||||
@@ -116,30 +120,40 @@ class P9FastPathTest(unittest.TestCase):
|
||||
|
||||
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(event.kind, "wrapper_success")
|
||||
self.assertTrue(event.success)
|
||||
self.assertEqual(event.source, "x24164_write_verify")
|
||||
self.assertEqual(emulator.cpu.regs[0], 1)
|
||||
self.assertFalse(emulator.cpu.z)
|
||||
self.assertEqual(emulator.cpu.pc, 0x789A)
|
||||
self.assertEqual(emulator.memory.p9_bus.fast_read_word(0x0812), (True, 0x3456))
|
||||
|
||||
def test_wrapper_can_succeed_from_queued_p9_device_response(self):
|
||||
def test_read_wrapper_puts_x24164_word_in_r5(self):
|
||||
emulator = H8536Emulator(bytes(rom_with_reset()))
|
||||
emulator.cpu.pc = LOC_BFE0_TRANSFER_WRAPPER
|
||||
emulator.cpu.regs[4] = 0x0010
|
||||
emulator.cpu.regs[5] = 0xCAFE
|
||||
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))
|
||||
emulator.cpu.pc = LOC_BFFE_TRANSFER_WRAPPER
|
||||
emulator.cpu.regs[4] = 0x0010
|
||||
emulator.cpu.regs[5] = 0
|
||||
emulator.cpu.regs[7] = 0xFE86
|
||||
emulator.memory.write16(0xFE86, 0x9ABC)
|
||||
|
||||
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(event.source, "x24164_read_word")
|
||||
self.assertEqual(emulator.cpu.regs[5], 0xCAFE)
|
||||
self.assertEqual(emulator.cpu.regs[0], 1)
|
||||
self.assertFalse(emulator.cpu.z)
|
||||
self.assertEqual(emulator.cpu.pc, 0x89AB)
|
||||
self.assertEqual(emulator.cpu.pc, 0x9ABC)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -4,6 +4,55 @@ from h8536.emulator import MemoryMap, P9DDR, P9DR
|
||||
from h8536.emulator.peripherals import P9Bus
|
||||
|
||||
|
||||
def p9_start(bus: P9Bus) -> None:
|
||||
bus.write_ddr(0x93)
|
||||
bus.write_dr(0x80)
|
||||
bus.write_dr(0x82)
|
||||
bus.write_dr(0x02)
|
||||
bus.write_dr(0x00)
|
||||
|
||||
|
||||
def p9_stop(bus: P9Bus) -> None:
|
||||
bus.write_ddr(0x93)
|
||||
bus.write_dr(0x00)
|
||||
bus.write_dr(0x02)
|
||||
bus.write_dr(0x82)
|
||||
bus.write_dr(0x80)
|
||||
|
||||
|
||||
def p9_write_byte(bus: P9Bus, value: int) -> bool:
|
||||
bus.write_ddr(0x93)
|
||||
for bit_index in range(7, -1, -1):
|
||||
bit = (value >> bit_index) & 1
|
||||
low = 0x80 if bit else 0x00
|
||||
bus.write_dr(low)
|
||||
bus.write_dr(low | 0x02)
|
||||
bus.write_dr(low)
|
||||
bus.write_ddr(0x13)
|
||||
bus.write_dr(bus.dr_latch | 0x02)
|
||||
ack_low = not bool(bus.read_dr() & 0x80)
|
||||
bus.write_dr(bus.dr_latch & ~0x02)
|
||||
bus.write_ddr(0x93)
|
||||
return ack_low
|
||||
|
||||
|
||||
def p9_read_byte(bus: P9Bus, *, master_ack: bool) -> int:
|
||||
value = 0
|
||||
bus.write_ddr(0x13)
|
||||
for _ in range(8):
|
||||
bus.write_dr(bus.dr_latch | 0x02)
|
||||
value = (value << 1) | (1 if bus.read_dr() & 0x80 else 0)
|
||||
bus.write_dr(bus.dr_latch & ~0x02)
|
||||
bus.write_ddr(0x93)
|
||||
if master_ack:
|
||||
bus.write_dr(bus.dr_latch & ~0x80)
|
||||
else:
|
||||
bus.write_dr(bus.dr_latch | 0x80)
|
||||
bus.write_dr(bus.dr_latch | 0x02)
|
||||
bus.write_dr(bus.dr_latch & ~0x02)
|
||||
return value
|
||||
|
||||
|
||||
class P9BusTest(unittest.TestCase):
|
||||
def test_bit7_input_uses_queued_then_default_low_response(self):
|
||||
memory = MemoryMap(b"\x00" * 4)
|
||||
@@ -47,6 +96,44 @@ class P9BusTest(unittest.TestCase):
|
||||
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())
|
||||
|
||||
def test_x24164_bit_banged_write_acknowledges_and_stores_data(self):
|
||||
bus = P9Bus()
|
||||
|
||||
p9_start(bus)
|
||||
self.assertTrue(p9_write_byte(bus, 0xA0))
|
||||
self.assertTrue(p9_write_byte(bus, 0x12))
|
||||
self.assertTrue(p9_write_byte(bus, 0x34))
|
||||
p9_stop(bus)
|
||||
|
||||
device = bus.x24164_bus.devices[0]
|
||||
self.assertEqual(device.read(0x12), 0x34)
|
||||
self.assertTrue(any("x24164_write_data" in line and "addr=012" in line for line in bus.trace_lines()))
|
||||
|
||||
def test_x24164_bit_banged_random_read_uses_p91_p97_lines(self):
|
||||
bus = P9Bus()
|
||||
bus.x24164_bus.devices[0].write(0x12, 0xAB)
|
||||
|
||||
p9_start(bus)
|
||||
self.assertTrue(p9_write_byte(bus, 0xA0))
|
||||
self.assertTrue(p9_write_byte(bus, 0x12))
|
||||
p9_start(bus)
|
||||
self.assertTrue(p9_write_byte(bus, 0xA1))
|
||||
self.assertEqual(p9_read_byte(bus, master_ack=False), 0xAB)
|
||||
p9_stop(bus)
|
||||
|
||||
self.assertTrue(any("x24164_prepare_read" in line and "value=AB" in line for line in bus.trace_lines()))
|
||||
|
||||
def test_x24164_fast_word_mapping_matches_rom_address_banks(self):
|
||||
bus = P9Bus()
|
||||
|
||||
self.assertTrue(bus.fast_write_word(0x0012, 0x3456))
|
||||
self.assertTrue(bus.fast_write_word(0x0812, 0xABCD))
|
||||
|
||||
self.assertEqual(bus.fast_read_word(0x0012), (True, 0x3456))
|
||||
self.assertEqual(bus.fast_read_word(0x0812), (True, 0xABCD))
|
||||
self.assertEqual(bus.x24164_bus.devices[0].read(0x12), 0x34)
|
||||
self.assertEqual(bus.x24164_bus.devices[1].read(0x12), 0xAB)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user