1
0

Compare commits

...

4 Commits

Author SHA1 Message Date
Aiden
6d68a87e4e emulator testing 2026-05-26 11:51:10 +10:00
Aiden
edb8ed78f3 EEPROM layout 2026-05-26 11:35:21 +10:00
Aiden
1ad03d5692 More ccu based mining 2026-05-26 11:07:36 +10:00
Aiden
421c9f4567 RX-tx understanding 2026-05-26 10:48:39 +10:00
58 changed files with 336182 additions and 8 deletions

View File

@@ -30,8 +30,11 @@ To run the newer sidecar protocol and gate/queue analysis tools:
```powershell
.\.venv\Scripts\python.exe h8536_serial_gate.py build\rom_decompiled.json --out build\rom_serial_gate.txt
.\.venv\Scripts\python.exe h8536_rx_branch_trace.py build\rom_decompiled.json --out build\rom_rx_branch_trace.txt
.\.venv\Scripts\python.exe h8536_report_source_trace.py build\rom_decompiled.json --out build\rom_report_sources.txt
.\.venv\Scripts\python.exe h8536_table_xrefs.py --out build\rom_table_xrefs.txt
.\.venv\Scripts\python.exe h8536_ccu_seed_hints.py build\rom_decompiled.json --out build\rom_ccu_seed_hints.txt
.\.venv\Scripts\python.exe h8536_eeprom_layout.py build\rom_decompiled.json --out build\rom_eeprom_layout.txt
.\.venv\Scripts\python.exe h8536_consistency.py build\rom_decompiled.json --out build\rom_consistency.txt
.\.venv\Scripts\python.exe h8536_protocol_capture.py ROM\rcp-txd-idle-only.txt
```
@@ -47,7 +50,11 @@ To start the current emulator harness:
.\.venv\Scripts\python.exe scripts\serial_ack_probe.py --ack-frame "05 00 40 00 00 1F"
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\ack-race-000-001.json --log captures\ack-race-000-001.txt --result-json captures\ack-race-000-001-result.json
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\table-sweep-ack-000-07f.json --log captures\table-sweep-ack-000-07f.txt --result-json captures\table-sweep-ack-000-07f-result.json
.\.venv\Scripts\python.exe scripts\state_map_runner.py --preset ok --prompt-screen
.\.venv\Scripts\python.exe scripts\state_map_runner.py --analyze-log captures\ack-race-000-001.txt
.\.venv\Scripts\python.exe h8536_emulator_state_search.py --preset connect-queue --target ok --first-hit --json-out build\connect-state-search-ok.json
.\.venv\Scripts\python.exe h8536_emulator_bench_replay.py captures\bench-connect-lcd-sequence-20260525-214411.txt --assert-bench-parity
.\.venv\Scripts\python.exe h8536_emulator.py --max-steps 250000 --p9-fast-path --eeprom-seed blank --eeprom-save build\emulator-eeprom-boot.bin --eeprom-report build\emulator-eeprom-boot.txt --eeprom-report-json build\emulator-eeprom-boot.json
```
The real-device bench helper uses `pyserial`; install repo dependencies with `.\.venv\Scripts\python.exe -m pip install -r requirements.txt` if needed.
@@ -74,8 +81,11 @@ The real-device bench helper uses `pyserial`; install repo dependencies with `.\
- Decodes observed serial byte captures into six-byte frames, validates checksums, labels capture-observed heartbeat/call/camera-power candidates, and summarizes heartbeat cadence.
- Accepts both analyzer-style lines such as `RX 006 bytes ...` and the idle reference `frame 006 ...` format in `ROM/rcp-txd-idle-only.txt`.
- Reconstructs the autonomous serial gate/queue state-machine around `loc_3FD3`, `loc_BAF2`, `F9B0/F9B5`, `FAA2/FAA3/FAA5`, the `F9C4`/FRT2 idle heartbeat gate at `loc_4046`, and the resend path through `BE9E/BED5`.
- Emits a focused SCI1 RX branch trace covering RXI/ERI byte capture, six-byte checksum validation, selector decode, the `FAA2 == 0` initial dispatcher, the `FAA2 != 0` continuation dispatcher, command `0x00/0x01/0x02/0x04/0x05/0x06/0x07` handlers, table surfaces, retry/error echoes, the separate `BE70/F970` selector-processing and `3E54/F870` serial-report queues, the TXI/RXI continuation-collapse interlock, RX-to-TX feedback loops, and session-timeout side effects.
- Traces direct callers to `loc_3E54` to identify report queue sources and conservatively flags whether observed report indexes such as `0x0007` are ROM-proven constants or runtime/capture observations.
- Generates table/index cross-reference reports for candidate value/current/secondary/flag tables and LCD text correlations.
- Mines ROM-backed CCU seed hints from table xrefs, selector dispatch, LCD text terms, and observed report overlays, then proposes syntactically valid command-0 seed frames and command-1 readback frames for high-value selectors.
- Mines the ROM-backed X24164 EEPROM layout, including the factory F400-F4FF shadow defaults, the page-0 EEPROM signature/options header, the fifteen blank-by-default 8-byte record slots loaded into F7B8-F82F, and the serial selector-to-persistent-offset map used by command 0/4 handlers.
- Adds a Sony RCP-TX7 board profile that ties H8/536 pin 66 `P95/TXD` and pin 67 `P96/RXD` to the MAX202 RS232 transceiver.
- Flags/manual-annotates TEMP-register access ordering for FRT and A/D 16-bit peripheral registers.
- Scans unreached ROM ranges for ASCII strings and pointer-table candidates.
@@ -89,13 +99,15 @@ 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, 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.
- 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`, logical EEPROM image load/save/reporting, 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.
- Includes a bench ACK probe that reproduces the `01 00 00...` -> `01 00 01...` visible retry burst, waits for `07 80 40 20 90 2D`, then sends a candidate command-5 ACK and reports whether the target keeps repeating.
- Includes a checksum-resynchronizing bench receiver that scans RX byte streams for valid six-byte frames, avoids common shifted-heartbeat false locks, and can fall back to the old fixed six-byte slicer with `--sync fixed`.
- Includes a JSON scenario bench runner for repeatable multi-step serial tests, including low-latency ACK-aware command-1 probes that can send the current command-5 ACK candidate immediately after the retry frame appears, with explicit max-ACK/max-target guardrails.
- Includes a PT2 state-map-aware bench runner/analyzer for the current CONNECT gate proof: it hunts a fresh device `07...` visible-drain token candidate, sends exactly one selector-zero command-4 force, probes `E000[0]` with command 1, optionally uses command 7 to recover a hidden finalized response, and labels likely token-destroying turns.
- Includes a bounded emulator CONNECT state-search tool that patches small ROM-derived RAM/table surfaces, runs either the direct CONNECT branch or the selector-zero queue dispatch path, and classifies LCD outcomes as OK, DXC, NOT ACT, or other.
- Includes a bench-log replay harness that feeds recorded host TX frames back into the ROM emulator with bench-style UART byte timing by default and asserts parity against the real device's observed response/LCD state.
Current serial observations:
@@ -110,9 +122,13 @@ Current serial observations:
- 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.
- EEPROM layout finding: `build\rom_eeprom_layout.txt` currently identifies the ROM factory table at `H'C964-H'CA63`, the F400 shadow defaults, page 0 offset `0x000-0x007` as the signature/options header (`00 00 6B 6F FE 00 00 00`), pages 1-F offset `0x00-0x07` as blank-by-default record slots, and 89 selector mappings from the `H'C564` table into F400/EEPROM offsets. `F404` defaults to `H'FE00` and is tested as option/feature bits, while `F76E` combines persistence enable, dispatch suppression, and low-nibble EEPROM page selection.
- Emulator EEPROM-image finding: `build\emulator-eeprom-boot.txt` captures a blank-EEPROM boot defaulting pass. The ROM writes 2108 words, leaves page 0's signature/options header intact, blanks page 1-F record headers, and the final image matches the ROM factory/default baseline. Use `--eeprom-load`/`--eeprom-save` to persist an emulated EEPROM image across runs and compare command-induced changes.
- Emulator board-state finding: P7 now reads external pin state for input bits, so the DIP-off default is modeled as `--p7-input 0xFF`; `--eeprom-seed factory` can pre-seed the X24164 devices and `F400-F4FF` shadow from the ROM default table for already-initialized-state experiments.
- 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.
- Emulator state-search finding: the minimum ROM-visible OK display condition is now reproducible without serial. Direct entry at `loc_2CB9` with `E000[0]=0x8080` and unsuppressed `F730=0` reaches `CONNECT: OK`; the queued selector-zero path also reaches OK when `F970[0]=0`, `F9B9=0`, `F9B4=1`, `E000[0]=0x8080`, and `F730=0`. This makes the bench problem sharper: prove whether serial can retain `E000[0]=0x8080` and enqueue selector zero without the reset/clobber path clearing it first.
- 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.
- CCU seed-hint finding: `build\rom_ccu_seed_hints.txt` currently ranks selector `0x000`, `0x0F6`, `0x003`, and `0x040` as the highest-value fake-CCU stream candidates. The generated seed frames are `00 00 00 80 80 5A`, `00 01 76 20 00 0D`, `00 00 03 80 00 D9`, and `00 00 40 FF FF 1A`, with command-1 readbacks listed beside them.
- 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.
The generated listing is written to:
@@ -135,7 +151,10 @@ build/rom_serial_pseudocode.c
build/rom_serial_gate.txt
build/rom_report_sources.txt
build/rom_table_xrefs.txt
build/rom_ccu_seed_hints.txt
build/rom_eeprom_layout.txt
build/rom_consistency.txt
build/emulator-eeprom-boot.txt
build/callgraph.dot
```
@@ -196,14 +215,20 @@ For gate/queue and table reports:
```powershell
python h8536_serial_gate.py --help
python h8536_rx_branch_trace.py --help
python h8536_report_source_trace.py --help
python h8536_table_xrefs.py --help
python h8536_ccu_seed_hints.py --help
python h8536_eeprom_layout.py --help
python h8536_consistency.py --help
```
- `h8536_serial_gate.py`: reports the autonomous TX gate and report queue evidence.
- `h8536_rx_branch_trace.py`: reports the SCI1 RX branch tree. Current finding: command `0x04/0x05/0x06` are continuation-path commands behind `FAA2 != 0`, so a standalone command-4 force from idle should not reach `BD0E`.
- `h8536_report_source_trace.py`: traces direct `loc_3E54` report enqueue sources. Current finding: no direct static `R3 = 0x0007` enqueue in the JSON, so CAM power `0x0007` remains runtime/capture-observed unless a later indirect/table path proves it.
- `h8536_table_xrefs.py`: emits candidate table/index xrefs and LCD text correlation hints.
- `h8536_ccu_seed_hints.py`: mines table, dispatch, LCD, and observed-report hints for the CCU-side state stream the RCP may expect before active displays/reports.
- `h8536_eeprom_layout.py`: mines the X24164 EEPROM layout, ROM factory defaults, persistent record slots, and serial selector-to-EEPROM offset mapping.
- `h8536_consistency.py`: flags JSON-to-pseudocode semantic hazards such as byte immediates written to word destinations.
For the emulator harness:
@@ -225,6 +250,9 @@ python h8536_emulator_rx_probe.py --help
- `--p9-fast-optimistic-wrapper`: legacy fallback for older wrapper experiments; the known `BFE0/BFFE` EEPROM wrappers now use the X24164 model instead.
- `--p7-input 0xFF`: set external P7 input pin state; this matters for the EEPROM defaulting gate at `P7DR.7` and the DIP-switch style inputs.
- `--eeprom-seed blank|factory`: choose blank X24164 power-on state or pre-seed the X24164/shadow tables from the ROM defaults before reset.
- `--eeprom-load PATH`: load a 0x1000-byte logical X24164 EEPROM image before boot/probe; page 0 is also mirrored into the F400 shadow so the ROM's early `F402` signature check sees the loaded state.
- `--eeprom-save PATH`: save the final 0x1000-byte logical EEPROM image after boot/probe.
- `--eeprom-report PATH` / `--eeprom-report-json PATH`: write a ROM-layout-aware EEPROM snapshot with page records, write logs, factory diffs, and F400 shadow diffs.
- `--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.
@@ -234,6 +262,10 @@ python h8536_emulator_rx_probe.py --help
- `scripts\serial_scenario.py scenarios\ack-race-000-001.json --log captures\ack-race-000-001.txt --result-json captures\ack-race-000-001-result.json`: run the focused `0x000 -> 0x001` retry probe with immediate reactive ACK and a 2 ms poll interval, to test whether command 5 can arrive before the second `07 80 40 20 90 2D` retry.
- `scripts\serial_scenario.py scenarios\early-ack-000-001.json --log captures\early-ack-000-001.txt --result-json captures\early-ack-000-001-result.json`: send the same command-1 pair, then send command-5 ACK immediately without waiting for the retry frame.
- `scripts\serial_scenario.py scenarios\table-sweep-ack-000-07f.json --log captures\table-sweep-ack-000-07f.txt --result-json captures\table-sweep-ack-000-07f-result.json`: run a repeatable bench scenario that sweeps selectors `0x000-0x07F` and sends `05 00 40 00 00 1F` only after `07 80 40 20 90 2D` appears. The checked-in scenario stops if it reaches 8 ACKs or 32 target hits. Use `--sync fixed` only when comparing against the old non-resyncing receiver.
- `scripts\state_map_runner.py --preset ok --prime-frame "01 80 40 40 30 EB" --prime-repeat 1 --prompt-screen`: run the state-map proof sequence against the bench device. The runner waits for a device `07...` visible-drain candidate, guards briefly so TXI can finish, sends the selector-zero force, then probes direct readback and command-7 recovery without inserting a command-0/command-1 destroyer before the force.
- `scripts\state_map_runner.py --analyze-log captures\ack-race-000-001.txt --json-out captures\ack-race-000-001-state-map.json`: classify an existing capture using the same state-map rules and report whether the selector-zero `BD0E -> E000[0]` edge was proven.
- `h8536_emulator_state_search.py --preset connect-queue --target ok --first-hit --json-out build\connect-state-search-ok.json`: run the bounded emulator state search for the minimum selector-zero queue condition that reaches `CONNECT: OK`. The default matrix varies `E000[0]` and `F730`, seeds `F970[0]=0`, starts at `loc_2806`, and executes real ROM code into the LCD handler.
- `h8536_emulator_state_search.py --preset custom --pc 0x2CB9 --word E000=0x8080 --byte F730=0 --target ok`: directly test the CONNECT handler branch with explicit internal state patches.
- `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 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`.
@@ -264,12 +296,17 @@ python h8536_emulator_rx_probe.py --help
- `h8536/protocol_trace.py`: raw six-byte protocol frame decoder/checksum validator.
- `h8536/protocol_capture.py`: timestamped serial capture parser, frame recombiner, and cadence/gate-session analyzer.
- `h8536/serial_scenario.py`: JSON-driven bench scenario engine shared by real-device serial scripts.
- `h8536/state_map_runner.py`: PT2 state-map proof runner and bench-log analyzer for visible-drain token, selector-zero force, `E000[0]` readback, and command-7 recovery experiments.
- `h8536/serial_gate.py`: autonomous TX gate/queue state-machine reconstruction.
- `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/ccu_seed_hints.py`: ROM miner for likely fake-CCU state seed selectors and candidate command/readback frames.
- `h8536/eeprom_layout.py`: ROM miner for X24164 EEPROM defaults, 8-byte record slots, and serial persistence mapping.
- `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/X24164 EEPROM bus model, LCD model, manual-derived FRT timer scheduling, runner, probe, CLI, and peripheral scaffolding.
- `h8536/emulator/eeprom_image.py`: logical EEPROM image dump/report helpers for emulator runs, including factory diffs and record-slot summaries.
- `h8536/emulator/rx_probe.py`: host-frame injection and response/listener probe for SCI1 RX experiments.
- `h8536/emulator/state_search.py`: bounded internal-state search for CONNECT LCD outcomes using ROM execution plus explicit RAM/table patches.
- `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.
- `h8536/pseudocode.py`: JSON-to-C-like pseudocode generation.
@@ -278,8 +315,10 @@ python h8536_emulator_rx_probe.py --help
- `h8536_pseudocode.py`: pseudocode CLI wrapper.
- `h8536_serial_pseudocode.py`: focused serial pseudocode CLI wrapper.
- `h8536_protocol_trace.py`, `h8536_protocol_capture.py`: protocol analysis CLI wrappers.
- `h8536_serial_gate.py`, `h8536_report_source_trace.py`, `h8536_table_xrefs.py`, `h8536_consistency.py`: sidecar analysis CLI wrappers.
- `h8536_serial_gate.py`, `h8536_report_source_trace.py`, `h8536_table_xrefs.py`, `h8536_ccu_seed_hints.py`, `h8536_eeprom_layout.py`, `h8536_consistency.py`: sidecar analysis CLI wrappers.
- `h8536_emulator.py`, `h8536_emulator_probe.py`, `h8536_emulator_rx_probe.py`, `h8536_emulator_bench_replay.py`: emulator CLI wrappers.
- `h8536_emulator_state_search.py`: emulator CONNECT state-search CLI wrapper.
- `scripts/bench_connect_lcd_sequence.py`: real-device COM5/COM6 bench runner for the CONNECT LCD sequence.
- `scripts/serial_table_dump.py`: read-only COM5/COM6 command-1 table sweep for inferring live EEPROM-backed parameter state.
- `scripts/serial_scenario.py`: JSON-driven COM5/COM6 bench scenario runner for chained probes, waits, read sweeps, and ACK-on-target experiments.
- `scripts/state_map_runner.py`: COM5/COM6 PT2 state-map proof runner and offline bench-log analyzer.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,111 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=4bed7704e1ea085487ca325c43bd60da75d37b6ae6f8292544e069a8825c64c6
writes: bytes=4216 words=2108 factory_diff_words=0
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=00 00 6B 6F FE 00 00 00 text='..ko....'
- page 0x1 EEPROM 0x100-0x107 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x2 EEPROM 0x200-0x207 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x3 EEPROM 0x300-0x307 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x4 EEPROM 0x400-0x407 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x5 EEPROM 0x500-0x507 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x6 EEPROM 0x600-0x607 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x7 EEPROM 0x700-0x707 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x8 EEPROM 0x800-0x807 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x9 EEPROM 0x900-0x907 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xA EEPROM 0xA00-0xA07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xB EEPROM 0xB00-0xB07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xC EEPROM 0xC00-0xC07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xD EEPROM 0xD00-0xD07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xE EEPROM 0xE00-0xE07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xF EEPROM 0xF00-0xF07 bytes=20 20 20 20 20 20 20 20 text=' '
EEPROM Word Writes:
- 0x0FE page=0x0 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FE page=0x1 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FE page=0x2 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FE page=0x3 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FE page=0x4 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FE page=0x5 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FE page=0x6 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FE page=0x7 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FE page=0x8 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FE page=0x9 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFE page=0xA offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFE page=0xB offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFE page=0xC offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFE page=0xD offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFE page=0xE offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFE page=0xF offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0FC page=0x0 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FC page=0x1 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FC page=0x2 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FC page=0x3 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FC page=0x4 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FC page=0x5 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FC page=0x6 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FC page=0x7 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FC page=0x8 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FC page=0x9 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFC page=0xA offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFC page=0xB offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFC page=0xC offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFC page=0xD offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFC page=0xE offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFC page=0xF offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0FA page=0x0 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FA page=0x1 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FA page=0x2 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FA page=0x3 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FA page=0x4 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FA page=0x5 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FA page=0x6 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FA page=0x7 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FA page=0x8 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FA page=0x9 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFA page=0xA offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFA page=0xB offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFA page=0xC offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFA page=0xD offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFA page=0xE offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFA page=0xF offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0F8 page=0x0 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1F8 page=0x1 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2F8 page=0x2 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3F8 page=0x3 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4F8 page=0x4 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5F8 page=0x5 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6F8 page=0x6 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7F8 page=0x7 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8F8 page=0x8 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9F8 page=0x9 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAF8 page=0xA offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBF8 page=0xB offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCF8 page=0xC offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDF8 page=0xD offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEF8 page=0xE offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFF8 page=0xF offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0F6 page=0x0 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1F6 page=0x1 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2F6 page=0x2 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3F6 page=0x3 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4F6 page=0x4 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5F6 page=0x5 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6F6 page=0x6 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7F6 page=0x7 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8F6 page=0x8 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9F6 page=0x9 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAF6 page=0xA offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBF6 page=0xB offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCF6 page=0xC offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDF6 page=0xD offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEF6 page=0xE offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFF6 page=0xF offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- ... 2028 more word writes omitted
Factory Diffs:
- current EEPROM image matches ROM factory/default image
F400 Shadow Diffs:
- F400-F4FF shadow matches ROM factory words or no ROM factory baseline was supplied

Binary file not shown.

View File

@@ -0,0 +1,31 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=4bed7704e1ea085487ca325c43bd60da75d37b6ae6f8292544e069a8825c64c6
writes: bytes=0 words=0 factory_diff_words=0
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=00 00 6B 6F FE 00 00 00 text='..ko....'
- page 0x1 EEPROM 0x100-0x107 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x2 EEPROM 0x200-0x207 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x3 EEPROM 0x300-0x307 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x4 EEPROM 0x400-0x407 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x5 EEPROM 0x500-0x507 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x6 EEPROM 0x600-0x607 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x7 EEPROM 0x700-0x707 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x8 EEPROM 0x800-0x807 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x9 EEPROM 0x900-0x907 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xA EEPROM 0xA00-0xA07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xB EEPROM 0xB00-0xB07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xC EEPROM 0xC00-0xC07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xD EEPROM 0xD00-0xD07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xE EEPROM 0xE00-0xE07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xF EEPROM 0xF00-0xF07 bytes=20 20 20 20 20 20 20 20 text=' '
EEPROM Word Writes:
- none since EEPROM setup/load
Factory Diffs:
- current EEPROM image matches ROM factory/default image
F400 Shadow Diffs:
- H'F4AA offset=0xAA expected=0x8000 actual=0x5500 (factory_shadow_offset; selectors=0x112)

View File

@@ -0,0 +1,31 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=4bed7704e1ea085487ca325c43bd60da75d37b6ae6f8292544e069a8825c64c6
writes: bytes=0 words=0 factory_diff_words=0
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=00 00 6B 6F FE 00 00 00 text='..ko....'
- page 0x1 EEPROM 0x100-0x107 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x2 EEPROM 0x200-0x207 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x3 EEPROM 0x300-0x307 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x4 EEPROM 0x400-0x407 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x5 EEPROM 0x500-0x507 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x6 EEPROM 0x600-0x607 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x7 EEPROM 0x700-0x707 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x8 EEPROM 0x800-0x807 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x9 EEPROM 0x900-0x907 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xA EEPROM 0xA00-0xA07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xB EEPROM 0xB00-0xB07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xC EEPROM 0xC00-0xC07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xD EEPROM 0xD00-0xD07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xE EEPROM 0xE00-0xE07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xF EEPROM 0xF00-0xF07 bytes=20 20 20 20 20 20 20 20 text=' '
EEPROM Word Writes:
- none since EEPROM setup/load
Factory Diffs:
- current EEPROM image matches ROM factory/default image
F400 Shadow Diffs:
- H'F4AA offset=0xAA expected=0x8000 actual=0x5500 (factory_shadow_offset; selectors=0x112)

View File

@@ -0,0 +1,31 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=4bed7704e1ea085487ca325c43bd60da75d37b6ae6f8292544e069a8825c64c6
writes: bytes=0 words=0 factory_diff_words=0
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=00 00 6B 6F FE 00 00 00 text='..ko....'
- page 0x1 EEPROM 0x100-0x107 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x2 EEPROM 0x200-0x207 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x3 EEPROM 0x300-0x307 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x4 EEPROM 0x400-0x407 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x5 EEPROM 0x500-0x507 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x6 EEPROM 0x600-0x607 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x7 EEPROM 0x700-0x707 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x8 EEPROM 0x800-0x807 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x9 EEPROM 0x900-0x907 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xA EEPROM 0xA00-0xA07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xB EEPROM 0xB00-0xB07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xC EEPROM 0xC00-0xC07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xD EEPROM 0xD00-0xD07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xE EEPROM 0xE00-0xE07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xF EEPROM 0xF00-0xF07 bytes=20 20 20 20 20 20 20 20 text=' '
EEPROM Word Writes:
- none since EEPROM setup/load
Factory Diffs:
- current EEPROM image matches ROM factory/default image
F400 Shadow Diffs:
- H'F4AA offset=0xAA expected=0x8000 actual=0x5500 (factory_shadow_offset; selectors=0x112)

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,50 @@
{
"case_count": 1,
"description": "Use only --byte/--word/--matrix-* patches and --pc.",
"hits": [
{
"case_index": 0,
"display": " CONNECT: OK | | | ",
"e000": "0x8080",
"f730": "0x81",
"f9b4": "0x00",
"f9b9": "0x00",
"final_pc": "H'FFFF",
"line0": " CONNECT: OK ",
"outcome": "ok",
"patches": [
"byte:H'F730=0x00",
"word:H'E000=0x8080"
],
"pc": "H'2CB9",
"steps": 37097,
"stopped_reason": "stop_pc",
"unsupported": null
}
],
"kind": "h8536_emulator_state_search",
"preset": "custom",
"result_count": 1,
"results": [
{
"case_index": 0,
"display": " CONNECT: OK | | | ",
"e000": "0x8080",
"f730": "0x81",
"f9b4": "0x00",
"f9b9": "0x00",
"final_pc": "H'FFFF",
"line0": " CONNECT: OK ",
"outcome": "ok",
"patches": [
"byte:H'F730=0x00",
"word:H'E000=0x8080"
],
"pc": "H'2CB9",
"steps": 37097,
"stopped_reason": "stop_pc",
"unsupported": null
}
],
"target": "ok"
}

View File

@@ -0,0 +1,56 @@
{
"case_count": 1,
"description": "Use only --byte/--word/--matrix-* patches and --pc.",
"hits": [
{
"case_index": 0,
"display": " CONNECT: OK | | | ",
"e000": "0x8080",
"f730": "0x81",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'FFFF",
"line0": " CONNECT: OK ",
"outcome": "ok",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"byte:H'F730=0x00",
"word:H'F970=0x0000",
"word:H'E000=0x8080"
],
"pc": "H'2806",
"steps": 37121,
"stopped_reason": "stop_pc",
"unsupported": null
}
],
"kind": "h8536_emulator_state_search",
"preset": "custom",
"result_count": 1,
"results": [
{
"case_index": 0,
"display": " CONNECT: OK | | | ",
"e000": "0x8080",
"f730": "0x81",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'FFFF",
"line0": " CONNECT: OK ",
"outcome": "ok",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"byte:H'F730=0x00",
"word:H'F970=0x0000",
"word:H'E000=0x8080"
],
"pc": "H'2806",
"steps": 37121,
"stopped_reason": "stop_pc",
"unsupported": null
}
],
"target": "ok"
}

View File

@@ -0,0 +1,31 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=4bed7704e1ea085487ca325c43bd60da75d37b6ae6f8292544e069a8825c64c6
writes: bytes=0 words=0 factory_diff_words=0
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=00 00 6B 6F FE 00 00 00 text='..ko....'
- page 0x1 EEPROM 0x100-0x107 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x2 EEPROM 0x200-0x207 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x3 EEPROM 0x300-0x307 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x4 EEPROM 0x400-0x407 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x5 EEPROM 0x500-0x507 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x6 EEPROM 0x600-0x607 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x7 EEPROM 0x700-0x707 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x8 EEPROM 0x800-0x807 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x9 EEPROM 0x900-0x907 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xA EEPROM 0xA00-0xA07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xB EEPROM 0xB00-0xB07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xC EEPROM 0xC00-0xC07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xD EEPROM 0xD00-0xD07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xE EEPROM 0xE00-0xE07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xF EEPROM 0xF00-0xF07 bytes=20 20 20 20 20 20 20 20 text=' '
EEPROM Word Writes:
- none since EEPROM setup/load
Factory Diffs:
- current EEPROM image matches ROM factory/default image
F400 Shadow Diffs:
- H'F4AA offset=0xAA expected=0x8000 actual=0x5500 (factory_shadow_offset; selectors=0x112)

Binary file not shown.

View File

@@ -0,0 +1,31 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=4bed7704e1ea085487ca325c43bd60da75d37b6ae6f8292544e069a8825c64c6
writes: bytes=0 words=0 factory_diff_words=0
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=00 00 6B 6F FE 00 00 00 text='..ko....'
- page 0x1 EEPROM 0x100-0x107 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x2 EEPROM 0x200-0x207 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x3 EEPROM 0x300-0x307 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x4 EEPROM 0x400-0x407 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x5 EEPROM 0x500-0x507 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x6 EEPROM 0x600-0x607 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x7 EEPROM 0x700-0x707 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x8 EEPROM 0x800-0x807 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x9 EEPROM 0x900-0x907 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xA EEPROM 0xA00-0xA07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xB EEPROM 0xB00-0xB07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xC EEPROM 0xC00-0xC07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xD EEPROM 0xD00-0xD07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xE EEPROM 0xE00-0xE07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xF EEPROM 0xF00-0xF07 bytes=20 20 20 20 20 20 20 20 text=' '
EEPROM Word Writes:
- none since EEPROM setup/load
Factory Diffs:
- current EEPROM image matches ROM factory/default image
F400 Shadow Diffs:
- H'F4AA offset=0xAA expected=0x8000 actual=0x5500 (factory_shadow_offset; selectors=0x112)

Binary file not shown.

View File

@@ -0,0 +1,386 @@
{
"case_count": 25,
"description": "Queue selector zero in F970, start at loc_2806, then enter loc_2CB9 through the ROM dispatch.",
"hits": [
{
"case_index": 15,
"display": " CONNECT: OK | | | ",
"e000": "0x8080",
"f730": "0x81",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'FFFF",
"line0": " CONNECT: OK ",
"outcome": "ok",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x8080",
"byte:H'F730=0x00"
],
"pc": "H'2806",
"steps": 37121,
"stopped_reason": "stop_pc",
"unsupported": null
}
],
"kind": "h8536_emulator_state_search",
"preset": "connect-queue",
"result_count": 16,
"results": [
{
"case_index": 0,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x0080",
"f730": "0x00",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'2D37",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x0000",
"byte:H'F730=0x00"
],
"pc": "H'2806",
"steps": 5109,
"stopped_reason": "unsupported_instruction",
"unsupported": "unsupported instruction at H'2D37: 6E C6 MOV:L.W @H'00C6, R6"
},
{
"case_index": 1,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x0080",
"f730": "0x00",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'2D37",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x0000",
"byte:H'F730=0x01"
],
"pc": "H'2806",
"steps": 5109,
"stopped_reason": "unsupported_instruction",
"unsupported": "unsupported instruction at H'2D37: 6E C6 MOV:L.W @H'00C6, R6"
},
{
"case_index": 2,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x0080",
"f730": "0x00",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'2D37",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x0000",
"byte:H'F730=0x41"
],
"pc": "H'2806",
"steps": 5109,
"stopped_reason": "unsupported_instruction",
"unsupported": "unsupported instruction at H'2D37: 6E C6 MOV:L.W @H'00C6, R6"
},
{
"case_index": 3,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x0080",
"f730": "0x00",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'2D37",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x0000",
"byte:H'F730=0x81"
],
"pc": "H'2806",
"steps": 5109,
"stopped_reason": "unsupported_instruction",
"unsupported": "unsupported instruction at H'2D37: 6E C6 MOV:L.W @H'00C6, R6"
},
{
"case_index": 4,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x0080",
"f730": "0x00",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'2D37",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x0000",
"byte:H'F730=0xC1"
],
"pc": "H'2806",
"steps": 5109,
"stopped_reason": "unsupported_instruction",
"unsupported": "unsupported instruction at H'2D37: 6E C6 MOV:L.W @H'00C6, R6"
},
{
"case_index": 5,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x0080",
"f730": "0x00",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'2D37",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x0080",
"byte:H'F730=0x00"
],
"pc": "H'2806",
"steps": 5109,
"stopped_reason": "unsupported_instruction",
"unsupported": "unsupported instruction at H'2D37: 6E C6 MOV:L.W @H'00C6, R6"
},
{
"case_index": 6,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x0080",
"f730": "0x00",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'2D37",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x0080",
"byte:H'F730=0x01"
],
"pc": "H'2806",
"steps": 5109,
"stopped_reason": "unsupported_instruction",
"unsupported": "unsupported instruction at H'2D37: 6E C6 MOV:L.W @H'00C6, R6"
},
{
"case_index": 7,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x0080",
"f730": "0x00",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'2D37",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x0080",
"byte:H'F730=0x41"
],
"pc": "H'2806",
"steps": 5109,
"stopped_reason": "unsupported_instruction",
"unsupported": "unsupported instruction at H'2D37: 6E C6 MOV:L.W @H'00C6, R6"
},
{
"case_index": 8,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x0080",
"f730": "0x00",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'2D37",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x0080",
"byte:H'F730=0x81"
],
"pc": "H'2806",
"steps": 5109,
"stopped_reason": "unsupported_instruction",
"unsupported": "unsupported instruction at H'2D37: 6E C6 MOV:L.W @H'00C6, R6"
},
{
"case_index": 9,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x0080",
"f730": "0x00",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'2D37",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x0080",
"byte:H'F730=0xC1"
],
"pc": "H'2806",
"steps": 5109,
"stopped_reason": "unsupported_instruction",
"unsupported": "unsupported instruction at H'2D37: 6E C6 MOV:L.W @H'00C6, R6"
},
{
"case_index": 10,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x4080",
"f730": "0x00",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'A9E8",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x4080",
"byte:H'F730=0x00"
],
"pc": "H'2806",
"steps": 5366,
"stopped_reason": "unsupported_instruction",
"unsupported": "unsupported instruction at H'A9E8: 7E 58 MOV:S.W R6, @H'0058"
},
{
"case_index": 11,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x4080",
"f730": "0x01",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'A9E8",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x4080",
"byte:H'F730=0x01"
],
"pc": "H'2806",
"steps": 5366,
"stopped_reason": "unsupported_instruction",
"unsupported": "unsupported instruction at H'A9E8: 7E 58 MOV:S.W R6, @H'0058"
},
{
"case_index": 12,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x4080",
"f730": "0x41",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'FFFF",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x4080",
"byte:H'F730=0x41"
],
"pc": "H'2806",
"steps": 45,
"stopped_reason": "stop_pc",
"unsupported": null
},
{
"case_index": 13,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x4080",
"f730": "0x81",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'A9E8",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x4080",
"byte:H'F730=0x81"
],
"pc": "H'2806",
"steps": 5366,
"stopped_reason": "unsupported_instruction",
"unsupported": "unsupported instruction at H'A9E8: 7E 58 MOV:S.W R6, @H'0058"
},
{
"case_index": 14,
"display": " CONNECT:NOT ACT | | | ",
"e000": "0x4080",
"f730": "0xC1",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'FFFF",
"line0": " CONNECT:NOT ACT",
"outcome": "not-act",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x4080",
"byte:H'F730=0xC1"
],
"pc": "H'2806",
"steps": 45,
"stopped_reason": "stop_pc",
"unsupported": null
},
{
"case_index": 15,
"display": " CONNECT: OK | | | ",
"e000": "0x8080",
"f730": "0x81",
"f9b4": "0x01",
"f9b9": "0x01",
"final_pc": "H'FFFF",
"line0": " CONNECT: OK ",
"outcome": "ok",
"patches": [
"byte:H'F9B9=0x00",
"byte:H'F9B4=0x01",
"word:H'F970=0x0000",
"word:H'E000=0x8080",
"byte:H'F730=0x00"
],
"pc": "H'2806",
"steps": 37121,
"stopped_reason": "stop_pc",
"unsupported": null
}
],
"target": "ok"
}

Binary file not shown.

158603
build/emulator-eeprom-boot.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,111 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=4bed7704e1ea085487ca325c43bd60da75d37b6ae6f8292544e069a8825c64c6
writes: bytes=4216 words=2108 factory_diff_words=0
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=00 00 6B 6F FE 00 00 00 text='..ko....'
- page 0x1 EEPROM 0x100-0x107 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x2 EEPROM 0x200-0x207 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x3 EEPROM 0x300-0x307 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x4 EEPROM 0x400-0x407 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x5 EEPROM 0x500-0x507 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x6 EEPROM 0x600-0x607 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x7 EEPROM 0x700-0x707 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x8 EEPROM 0x800-0x807 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x9 EEPROM 0x900-0x907 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xA EEPROM 0xA00-0xA07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xB EEPROM 0xB00-0xB07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xC EEPROM 0xC00-0xC07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xD EEPROM 0xD00-0xD07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xE EEPROM 0xE00-0xE07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xF EEPROM 0xF00-0xF07 bytes=20 20 20 20 20 20 20 20 text=' '
EEPROM Word Writes:
- 0x0FE page=0x0 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FE page=0x1 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FE page=0x2 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FE page=0x3 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FE page=0x4 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FE page=0x5 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FE page=0x6 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FE page=0x7 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FE page=0x8 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FE page=0x9 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFE page=0xA offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFE page=0xB offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFE page=0xC offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFE page=0xD offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFE page=0xE offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFE page=0xF offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0FC page=0x0 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FC page=0x1 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FC page=0x2 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FC page=0x3 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FC page=0x4 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FC page=0x5 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FC page=0x6 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FC page=0x7 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FC page=0x8 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FC page=0x9 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFC page=0xA offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFC page=0xB offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFC page=0xC offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFC page=0xD offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFC page=0xE offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFC page=0xF offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0FA page=0x0 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FA page=0x1 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FA page=0x2 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FA page=0x3 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FA page=0x4 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FA page=0x5 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FA page=0x6 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FA page=0x7 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FA page=0x8 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FA page=0x9 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFA page=0xA offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFA page=0xB offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFA page=0xC offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFA page=0xD offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFA page=0xE offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFA page=0xF offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0F8 page=0x0 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1F8 page=0x1 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2F8 page=0x2 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3F8 page=0x3 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4F8 page=0x4 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5F8 page=0x5 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6F8 page=0x6 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7F8 page=0x7 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8F8 page=0x8 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9F8 page=0x9 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAF8 page=0xA offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBF8 page=0xB offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCF8 page=0xC offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDF8 page=0xD offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEF8 page=0xE offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFF8 page=0xF offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0F6 page=0x0 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1F6 page=0x1 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2F6 page=0x2 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3F6 page=0x3 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4F6 page=0x4 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5F6 page=0x5 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6F6 page=0x6 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7F6 page=0x7 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8F6 page=0x8 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9F6 page=0x9 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAF6 page=0xA offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBF6 page=0xB offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCF6 page=0xC offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDF6 page=0xD offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEF6 page=0xE offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFF6 page=0xF offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- ... 2028 more word writes omitted
Factory Diffs:
- current EEPROM image matches ROM factory/default image
F400 Shadow Diffs:
- F400-F4FF shadow matches ROM factory words or no ROM factory baseline was supplied

View File

@@ -0,0 +1,300 @@
{
"factory_diffs": [],
"kind": "emulator_eeprom_snapshot",
"records": [
{
"address": 0,
"address_hex": "0x000",
"ascii": "..ko....",
"bytes_hex": "00 00 6B 6F FE 00 00 00",
"is_blank_spaces": false,
"page": 0,
"page_hex": "0x0",
"range_hex": "0x000-0x007",
"words_hex": [
"0x0000",
"0x6B6F",
"0xFE00",
"0x0000"
]
},
{
"address": 256,
"address_hex": "0x100",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 1,
"page_hex": "0x1",
"range_hex": "0x100-0x107",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
},
{
"address": 512,
"address_hex": "0x200",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 2,
"page_hex": "0x2",
"range_hex": "0x200-0x207",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
},
{
"address": 768,
"address_hex": "0x300",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 3,
"page_hex": "0x3",
"range_hex": "0x300-0x307",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
},
{
"address": 1024,
"address_hex": "0x400",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 4,
"page_hex": "0x4",
"range_hex": "0x400-0x407",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
},
{
"address": 1280,
"address_hex": "0x500",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 5,
"page_hex": "0x5",
"range_hex": "0x500-0x507",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
},
{
"address": 1536,
"address_hex": "0x600",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 6,
"page_hex": "0x6",
"range_hex": "0x600-0x607",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
},
{
"address": 1792,
"address_hex": "0x700",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 7,
"page_hex": "0x7",
"range_hex": "0x700-0x707",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
},
{
"address": 2048,
"address_hex": "0x800",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 8,
"page_hex": "0x8",
"range_hex": "0x800-0x807",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
},
{
"address": 2304,
"address_hex": "0x900",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 9,
"page_hex": "0x9",
"range_hex": "0x900-0x907",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
},
{
"address": 2560,
"address_hex": "0xA00",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 10,
"page_hex": "0xA",
"range_hex": "0xA00-0xA07",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
},
{
"address": 2816,
"address_hex": "0xB00",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 11,
"page_hex": "0xB",
"range_hex": "0xB00-0xB07",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
},
{
"address": 3072,
"address_hex": "0xC00",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 12,
"page_hex": "0xC",
"range_hex": "0xC00-0xC07",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
},
{
"address": 3328,
"address_hex": "0xD00",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 13,
"page_hex": "0xD",
"range_hex": "0xD00-0xD07",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
},
{
"address": 3584,
"address_hex": "0xE00",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 14,
"page_hex": "0xE",
"range_hex": "0xE00-0xE07",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
},
{
"address": 3840,
"address_hex": "0xF00",
"ascii": " ",
"bytes_hex": "20 20 20 20 20 20 20 20",
"is_blank_spaces": true,
"page": 15,
"page_hex": "0xF",
"range_hex": "0xF00-0xF07",
"words_hex": [
"0x2020",
"0x2020",
"0x2020",
"0x2020"
]
}
],
"shadow_f400": {
"diff_count": 1,
"diffs": [
{
"actual_word": 21760,
"actual_word_hex": "0x5500",
"address": 62634,
"address_hex": "H'F4AA",
"aligned_offset": 170,
"aligned_offset_hex": "0xAA",
"expected_word": 32768,
"expected_word_hex": "0x8000",
"mapped_selectors": [
274
],
"mapped_selectors_hex": [
"0x112"
],
"offset": 170,
"offset_hex": "0xAA",
"page": 0,
"page_hex": "0x0",
"record_byte": null,
"role": "factory_shadow_offset"
}
]
},
"summary": {
"factory_diff_word_count": 0,
"logical_size": 4096,
"logical_size_hex": "0x1000",
"record_count": 16,
"sha256": "4bed7704e1ea085487ca325c43bd60da75d37b6ae6f8292544e069a8825c64c6",
"write_byte_count": 0,
"write_word_count": 0
},
"write_events": [],
"write_word_events": []
}

View File

@@ -0,0 +1,31 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=4bed7704e1ea085487ca325c43bd60da75d37b6ae6f8292544e069a8825c64c6
writes: bytes=0 words=0 factory_diff_words=0
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=00 00 6B 6F FE 00 00 00 text='..ko....'
- page 0x1 EEPROM 0x100-0x107 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x2 EEPROM 0x200-0x207 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x3 EEPROM 0x300-0x307 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x4 EEPROM 0x400-0x407 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x5 EEPROM 0x500-0x507 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x6 EEPROM 0x600-0x607 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x7 EEPROM 0x700-0x707 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x8 EEPROM 0x800-0x807 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x9 EEPROM 0x900-0x907 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xA EEPROM 0xA00-0xA07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xB EEPROM 0xB00-0xB07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xC EEPROM 0xC00-0xC07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xD EEPROM 0xD00-0xD07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xE EEPROM 0xE00-0xE07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xF EEPROM 0xF00-0xF07 bytes=20 20 20 20 20 20 20 20 text=' '
EEPROM Word Writes:
- none since EEPROM setup/load
Factory Diffs:
- current EEPROM image matches ROM factory/default image
F400 Shadow Diffs:
- H'F4AA offset=0xAA expected=0x8000 actual=0x5500 (factory_shadow_offset; selectors=0x112)

View File

@@ -0,0 +1,191 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=f47a8ec3e9aff2318d896942282ad4fe37d6391c82914f54a5da8a37de1300c6
writes: bytes=0 words=0 factory_diff_words=2048
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x1 EEPROM 0x100-0x107 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x2 EEPROM 0x200-0x207 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x3 EEPROM 0x300-0x307 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x4 EEPROM 0x400-0x407 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x5 EEPROM 0x500-0x507 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x6 EEPROM 0x600-0x607 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x7 EEPROM 0x700-0x707 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x8 EEPROM 0x800-0x807 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x9 EEPROM 0x900-0x907 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0xA EEPROM 0xA00-0xA07 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0xB EEPROM 0xB00-0xB07 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0xC EEPROM 0xC00-0xC07 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0xD EEPROM 0xD00-0xD07 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0xE EEPROM 0xE00-0xE07 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0xF EEPROM 0xF00-0xF07 bytes=FF FF FF FF FF FF FF FF text='........'
EEPROM Word Writes:
- none since EEPROM setup/load
Factory Diffs:
- 0x000 page=0x0 offset=0x00 expected=0x0000 actual=0xFFFF (record_header_or_label)
- 0x002 page=0x0 offset=0x02 expected=0x6B6F actual=0xFFFF (record_header_or_label)
- 0x004 page=0x0 offset=0x04 expected=0xFE00 actual=0xFFFF (record_header_or_label)
- 0x006 page=0x0 offset=0x06 expected=0x0000 actual=0xFFFF (record_header_or_label)
- 0x008 page=0x0 offset=0x08 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x004)
- 0x00A page=0x0 offset=0x0A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x012)
- 0x00C page=0x0 offset=0x0C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x013)
- 0x00E page=0x0 offset=0x0E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x017)
- 0x010 page=0x0 offset=0x10 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x018)
- 0x012 page=0x0 offset=0x12 expected=0x0808 actual=0xFFFF (factory_shadow_offset; selectors=0x01A)
- 0x014 page=0x0 offset=0x14 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x01F)
- 0x016 page=0x0 offset=0x16 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x020)
- 0x018 page=0x0 offset=0x18 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x023)
- 0x01A page=0x0 offset=0x1A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x037)
- 0x01C page=0x0 offset=0x1C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x038)
- 0x01E page=0x0 offset=0x1E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x080)
- 0x020 page=0x0 offset=0x20 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x081)
- 0x022 page=0x0 offset=0x22 expected=0x0020 actual=0xFFFF (factory_shadow_offset; selectors=0x083)
- 0x024 page=0x0 offset=0x24 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x088)
- 0x026 page=0x0 offset=0x26 expected=0x0400 actual=0xFFFF (factory_shadow_offset; selectors=0x089)
- 0x028 page=0x0 offset=0x28 expected=0x0800 actual=0xFFFF (factory_shadow_offset; selectors=0x08B)
- 0x02A page=0x0 offset=0x2A expected=0x0040 actual=0xFFFF (factory_shadow_offset; selectors=0x08D)
- 0x02C page=0x0 offset=0x2C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x08F)
- 0x02E page=0x0 offset=0x2E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x091)
- 0x030 page=0x0 offset=0x30 expected=0xFF80 actual=0xFFFF (factory_shadow_offset; selectors=0x092)
- 0x032 page=0x0 offset=0x32 expected=0x4040 actual=0xFFFF (factory_shadow_offset; selectors=0x093)
- 0x034 page=0x0 offset=0x34 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x095)
- 0x036 page=0x0 offset=0x36 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x098)
- 0x038 page=0x0 offset=0x38 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x09A)
- 0x03A page=0x0 offset=0x3A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x09D)
- 0x03C page=0x0 offset=0x3C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x09E)
- 0x03E page=0x0 offset=0x3E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x09F)
- 0x040 page=0x0 offset=0x40 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A3)
- 0x042 page=0x0 offset=0x42 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A4)
- 0x044 page=0x0 offset=0x44 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A5)
- 0x046 page=0x0 offset=0x46 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A6)
- 0x048 page=0x0 offset=0x48 expected=0xF000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A7)
- 0x04A page=0x0 offset=0x4A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A9)
- 0x04C page=0x0 offset=0x4C expected=0x2000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AA)
- 0x04E page=0x0 offset=0x4E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AC)
- 0x050 page=0x0 offset=0x50 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AD)
- 0x052 page=0x0 offset=0x52 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AE)
- 0x054 page=0x0 offset=0x54 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AF)
- 0x056 page=0x0 offset=0x56 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B0)
- 0x058 page=0x0 offset=0x58 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B2)
- 0x05A page=0x0 offset=0x5A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B3)
- 0x05C page=0x0 offset=0x5C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B4)
- 0x05E page=0x0 offset=0x5E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B6)
- 0x060 page=0x0 offset=0x60 expected=0xF800 actual=0xFFFF (factory_shadow_offset; selectors=0x0B7)
- 0x062 page=0x0 offset=0x62 expected=0x4000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B9)
- 0x064 page=0x0 offset=0x64 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0BC)
- 0x066 page=0x0 offset=0x66 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0BD)
- 0x068 page=0x0 offset=0x68 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C0)
- 0x06A page=0x0 offset=0x6A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C1)
- 0x06C page=0x0 offset=0x6C expected=0x4000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C3)
- 0x06E page=0x0 offset=0x6E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C4)
- 0x070 page=0x0 offset=0x70 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C5)
- 0x072 page=0x0 offset=0x72 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C6)
- 0x074 page=0x0 offset=0x74 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C7)
- 0x076 page=0x0 offset=0x76 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C8)
- 0x078 page=0x0 offset=0x78 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C9)
- 0x07A page=0x0 offset=0x7A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CA)
- 0x07C page=0x0 offset=0x7C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CB)
- 0x07E page=0x0 offset=0x7E expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CC)
- 0x080 page=0x0 offset=0x80 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CD)
- 0x082 page=0x0 offset=0x82 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D4)
- 0x084 page=0x0 offset=0x84 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D5)
- 0x086 page=0x0 offset=0x86 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D6)
- 0x088 page=0x0 offset=0x88 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D7)
- 0x08A page=0x0 offset=0x8A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D8)
- 0x08C page=0x0 offset=0x8C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D9)
- 0x08E page=0x0 offset=0x8E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0DA)
- 0x090 page=0x0 offset=0x90 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0F6)
- 0x092 page=0x0 offset=0x92 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0F9)
- 0x094 page=0x0 offset=0x94 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FA)
- 0x096 page=0x0 offset=0x96 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FB)
- 0x098 page=0x0 offset=0x98 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FC)
- 0x09A page=0x0 offset=0x9A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FD)
- 0x09C page=0x0 offset=0x9C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FE)
- 0x09E page=0x0 offset=0x9E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FF)
- ... 1968 more factory diffs omitted
F400 Shadow Diffs:
- H'F400 offset=0x00 expected=0x0000 actual=0xFFFF (record_header_or_label)
- H'F402 offset=0x02 expected=0x6B6F actual=0xFFFF (record_header_or_label)
- H'F404 offset=0x04 expected=0xFE00 actual=0xFFFF (record_header_or_label)
- H'F406 offset=0x06 expected=0x0000 actual=0xFFFF (record_header_or_label)
- H'F408 offset=0x08 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x004)
- H'F40A offset=0x0A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x012)
- H'F40C offset=0x0C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x013)
- H'F40E offset=0x0E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x017)
- H'F410 offset=0x10 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x018)
- H'F412 offset=0x12 expected=0x0808 actual=0xFFFF (factory_shadow_offset; selectors=0x01A)
- H'F414 offset=0x14 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x01F)
- H'F416 offset=0x16 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x020)
- H'F418 offset=0x18 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x023)
- H'F41A offset=0x1A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x037)
- H'F41C offset=0x1C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x038)
- H'F41E offset=0x1E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x080)
- H'F420 offset=0x20 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x081)
- H'F422 offset=0x22 expected=0x0020 actual=0xFFFF (factory_shadow_offset; selectors=0x083)
- H'F424 offset=0x24 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x088)
- H'F426 offset=0x26 expected=0x0400 actual=0xFFFF (factory_shadow_offset; selectors=0x089)
- H'F428 offset=0x28 expected=0x0800 actual=0xFFFF (factory_shadow_offset; selectors=0x08B)
- H'F42A offset=0x2A expected=0x0040 actual=0xFFFF (factory_shadow_offset; selectors=0x08D)
- H'F42C offset=0x2C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x08F)
- H'F42E offset=0x2E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x091)
- H'F430 offset=0x30 expected=0xFF80 actual=0xFFFF (factory_shadow_offset; selectors=0x092)
- H'F432 offset=0x32 expected=0x4040 actual=0xFFFF (factory_shadow_offset; selectors=0x093)
- H'F434 offset=0x34 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x095)
- H'F436 offset=0x36 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x098)
- H'F438 offset=0x38 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x09A)
- H'F43A offset=0x3A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x09D)
- H'F43C offset=0x3C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x09E)
- H'F43E offset=0x3E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x09F)
- H'F440 offset=0x40 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A3)
- H'F442 offset=0x42 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A4)
- H'F444 offset=0x44 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A5)
- H'F446 offset=0x46 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A6)
- H'F448 offset=0x48 expected=0xF000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A7)
- H'F44A offset=0x4A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A9)
- H'F44C offset=0x4C expected=0x2000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AA)
- H'F44E offset=0x4E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AC)
- H'F450 offset=0x50 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AD)
- H'F452 offset=0x52 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AE)
- H'F454 offset=0x54 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AF)
- H'F456 offset=0x56 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B0)
- H'F458 offset=0x58 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B2)
- H'F45A offset=0x5A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B3)
- H'F45C offset=0x5C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B4)
- H'F45E offset=0x5E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B6)
- H'F460 offset=0x60 expected=0xF800 actual=0xFFFF (factory_shadow_offset; selectors=0x0B7)
- H'F462 offset=0x62 expected=0x4000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B9)
- H'F464 offset=0x64 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0BC)
- H'F466 offset=0x66 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0BD)
- H'F468 offset=0x68 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C0)
- H'F46A offset=0x6A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C1)
- H'F46C offset=0x6C expected=0x4000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C3)
- H'F46E offset=0x6E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C4)
- H'F470 offset=0x70 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C5)
- H'F472 offset=0x72 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C6)
- H'F474 offset=0x74 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C7)
- H'F476 offset=0x76 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C8)
- H'F478 offset=0x78 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C9)
- H'F47A offset=0x7A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CA)
- H'F47C offset=0x7C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CB)
- H'F47E offset=0x7E expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CC)
- H'F480 offset=0x80 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CD)
- H'F482 offset=0x82 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D4)
- H'F484 offset=0x84 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D5)
- H'F486 offset=0x86 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D6)
- H'F488 offset=0x88 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D7)
- H'F48A offset=0x8A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D8)
- H'F48C offset=0x8C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D9)
- H'F48E offset=0x8E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0DA)
- H'F490 offset=0x90 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0F6)
- H'F492 offset=0x92 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0F9)
- H'F494 offset=0x94 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FA)
- H'F496 offset=0x96 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FB)
- H'F498 offset=0x98 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FC)
- H'F49A offset=0x9A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FD)
- H'F49C offset=0x9C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FE)
- H'F49E offset=0x9E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FF)
- ... 47 more shadow diffs omitted

View File

@@ -0,0 +1,111 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=4bed7704e1ea085487ca325c43bd60da75d37b6ae6f8292544e069a8825c64c6
writes: bytes=4216 words=2108 factory_diff_words=0
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=00 00 6B 6F FE 00 00 00 text='..ko....'
- page 0x1 EEPROM 0x100-0x107 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x2 EEPROM 0x200-0x207 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x3 EEPROM 0x300-0x307 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x4 EEPROM 0x400-0x407 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x5 EEPROM 0x500-0x507 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x6 EEPROM 0x600-0x607 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x7 EEPROM 0x700-0x707 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x8 EEPROM 0x800-0x807 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x9 EEPROM 0x900-0x907 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xA EEPROM 0xA00-0xA07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xB EEPROM 0xB00-0xB07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xC EEPROM 0xC00-0xC07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xD EEPROM 0xD00-0xD07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xE EEPROM 0xE00-0xE07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xF EEPROM 0xF00-0xF07 bytes=20 20 20 20 20 20 20 20 text=' '
EEPROM Word Writes:
- 0x0FE page=0x0 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FE page=0x1 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FE page=0x2 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FE page=0x3 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FE page=0x4 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FE page=0x5 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FE page=0x6 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FE page=0x7 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FE page=0x8 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FE page=0x9 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFE page=0xA offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFE page=0xB offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFE page=0xC offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFE page=0xD offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFE page=0xE offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFE page=0xF offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0FC page=0x0 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FC page=0x1 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FC page=0x2 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FC page=0x3 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FC page=0x4 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FC page=0x5 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FC page=0x6 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FC page=0x7 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FC page=0x8 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FC page=0x9 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFC page=0xA offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFC page=0xB offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFC page=0xC offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFC page=0xD offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFC page=0xE offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFC page=0xF offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0FA page=0x0 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FA page=0x1 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FA page=0x2 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FA page=0x3 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FA page=0x4 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FA page=0x5 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FA page=0x6 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FA page=0x7 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FA page=0x8 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FA page=0x9 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFA page=0xA offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFA page=0xB offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFA page=0xC offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFA page=0xD offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFA page=0xE offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFA page=0xF offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0F8 page=0x0 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1F8 page=0x1 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2F8 page=0x2 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3F8 page=0x3 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4F8 page=0x4 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5F8 page=0x5 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6F8 page=0x6 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7F8 page=0x7 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8F8 page=0x8 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9F8 page=0x9 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAF8 page=0xA offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBF8 page=0xB offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCF8 page=0xC offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDF8 page=0xD offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEF8 page=0xE offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFF8 page=0xF offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0F6 page=0x0 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1F6 page=0x1 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2F6 page=0x2 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3F6 page=0x3 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4F6 page=0x4 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5F6 page=0x5 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6F6 page=0x6 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7F6 page=0x7 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8F6 page=0x8 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9F6 page=0x9 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAF6 page=0xA offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBF6 page=0xB offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCF6 page=0xC offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDF6 page=0xD offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEF6 page=0xE offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFF6 page=0xF offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- ... 2028 more word writes omitted
Factory Diffs:
- current EEPROM image matches ROM factory/default image
F400 Shadow Diffs:
- F400-F4FF shadow matches ROM factory words or no ROM factory baseline was supplied

View File

@@ -0,0 +1,31 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=4bed7704e1ea085487ca325c43bd60da75d37b6ae6f8292544e069a8825c64c6
writes: bytes=0 words=0 factory_diff_words=0
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=00 00 6B 6F FE 00 00 00 text='..ko....'
- page 0x1 EEPROM 0x100-0x107 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x2 EEPROM 0x200-0x207 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x3 EEPROM 0x300-0x307 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x4 EEPROM 0x400-0x407 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x5 EEPROM 0x500-0x507 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x6 EEPROM 0x600-0x607 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x7 EEPROM 0x700-0x707 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x8 EEPROM 0x800-0x807 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x9 EEPROM 0x900-0x907 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xA EEPROM 0xA00-0xA07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xB EEPROM 0xB00-0xB07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xC EEPROM 0xC00-0xC07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xD EEPROM 0xD00-0xD07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xE EEPROM 0xE00-0xE07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xF EEPROM 0xF00-0xF07 bytes=20 20 20 20 20 20 20 20 text=' '
EEPROM Word Writes:
- none since EEPROM setup/load
Factory Diffs:
- current EEPROM image matches ROM factory/default image
F400 Shadow Diffs:
- H'F4AA offset=0xAA expected=0x8000 actual=0x5500 (factory_shadow_offset; selectors=0x112)

View File

@@ -0,0 +1,191 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=f47a8ec3e9aff2318d896942282ad4fe37d6391c82914f54a5da8a37de1300c6
writes: bytes=0 words=0 factory_diff_words=2048
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x1 EEPROM 0x100-0x107 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x2 EEPROM 0x200-0x207 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x3 EEPROM 0x300-0x307 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x4 EEPROM 0x400-0x407 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x5 EEPROM 0x500-0x507 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x6 EEPROM 0x600-0x607 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x7 EEPROM 0x700-0x707 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x8 EEPROM 0x800-0x807 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0x9 EEPROM 0x900-0x907 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0xA EEPROM 0xA00-0xA07 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0xB EEPROM 0xB00-0xB07 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0xC EEPROM 0xC00-0xC07 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0xD EEPROM 0xD00-0xD07 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0xE EEPROM 0xE00-0xE07 bytes=FF FF FF FF FF FF FF FF text='........'
- page 0xF EEPROM 0xF00-0xF07 bytes=FF FF FF FF FF FF FF FF text='........'
EEPROM Word Writes:
- none since EEPROM setup/load
Factory Diffs:
- 0x000 page=0x0 offset=0x00 expected=0x0000 actual=0xFFFF (record_header_or_label)
- 0x002 page=0x0 offset=0x02 expected=0x6B6F actual=0xFFFF (record_header_or_label)
- 0x004 page=0x0 offset=0x04 expected=0xFE00 actual=0xFFFF (record_header_or_label)
- 0x006 page=0x0 offset=0x06 expected=0x0000 actual=0xFFFF (record_header_or_label)
- 0x008 page=0x0 offset=0x08 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x004)
- 0x00A page=0x0 offset=0x0A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x012)
- 0x00C page=0x0 offset=0x0C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x013)
- 0x00E page=0x0 offset=0x0E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x017)
- 0x010 page=0x0 offset=0x10 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x018)
- 0x012 page=0x0 offset=0x12 expected=0x0808 actual=0xFFFF (factory_shadow_offset; selectors=0x01A)
- 0x014 page=0x0 offset=0x14 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x01F)
- 0x016 page=0x0 offset=0x16 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x020)
- 0x018 page=0x0 offset=0x18 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x023)
- 0x01A page=0x0 offset=0x1A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x037)
- 0x01C page=0x0 offset=0x1C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x038)
- 0x01E page=0x0 offset=0x1E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x080)
- 0x020 page=0x0 offset=0x20 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x081)
- 0x022 page=0x0 offset=0x22 expected=0x0020 actual=0xFFFF (factory_shadow_offset; selectors=0x083)
- 0x024 page=0x0 offset=0x24 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x088)
- 0x026 page=0x0 offset=0x26 expected=0x0400 actual=0xFFFF (factory_shadow_offset; selectors=0x089)
- 0x028 page=0x0 offset=0x28 expected=0x0800 actual=0xFFFF (factory_shadow_offset; selectors=0x08B)
- 0x02A page=0x0 offset=0x2A expected=0x0040 actual=0xFFFF (factory_shadow_offset; selectors=0x08D)
- 0x02C page=0x0 offset=0x2C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x08F)
- 0x02E page=0x0 offset=0x2E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x091)
- 0x030 page=0x0 offset=0x30 expected=0xFF80 actual=0xFFFF (factory_shadow_offset; selectors=0x092)
- 0x032 page=0x0 offset=0x32 expected=0x4040 actual=0xFFFF (factory_shadow_offset; selectors=0x093)
- 0x034 page=0x0 offset=0x34 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x095)
- 0x036 page=0x0 offset=0x36 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x098)
- 0x038 page=0x0 offset=0x38 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x09A)
- 0x03A page=0x0 offset=0x3A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x09D)
- 0x03C page=0x0 offset=0x3C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x09E)
- 0x03E page=0x0 offset=0x3E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x09F)
- 0x040 page=0x0 offset=0x40 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A3)
- 0x042 page=0x0 offset=0x42 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A4)
- 0x044 page=0x0 offset=0x44 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A5)
- 0x046 page=0x0 offset=0x46 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A6)
- 0x048 page=0x0 offset=0x48 expected=0xF000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A7)
- 0x04A page=0x0 offset=0x4A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A9)
- 0x04C page=0x0 offset=0x4C expected=0x2000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AA)
- 0x04E page=0x0 offset=0x4E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AC)
- 0x050 page=0x0 offset=0x50 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AD)
- 0x052 page=0x0 offset=0x52 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AE)
- 0x054 page=0x0 offset=0x54 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AF)
- 0x056 page=0x0 offset=0x56 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B0)
- 0x058 page=0x0 offset=0x58 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B2)
- 0x05A page=0x0 offset=0x5A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B3)
- 0x05C page=0x0 offset=0x5C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B4)
- 0x05E page=0x0 offset=0x5E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B6)
- 0x060 page=0x0 offset=0x60 expected=0xF800 actual=0xFFFF (factory_shadow_offset; selectors=0x0B7)
- 0x062 page=0x0 offset=0x62 expected=0x4000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B9)
- 0x064 page=0x0 offset=0x64 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0BC)
- 0x066 page=0x0 offset=0x66 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0BD)
- 0x068 page=0x0 offset=0x68 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C0)
- 0x06A page=0x0 offset=0x6A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C1)
- 0x06C page=0x0 offset=0x6C expected=0x4000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C3)
- 0x06E page=0x0 offset=0x6E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C4)
- 0x070 page=0x0 offset=0x70 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C5)
- 0x072 page=0x0 offset=0x72 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C6)
- 0x074 page=0x0 offset=0x74 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C7)
- 0x076 page=0x0 offset=0x76 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C8)
- 0x078 page=0x0 offset=0x78 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C9)
- 0x07A page=0x0 offset=0x7A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CA)
- 0x07C page=0x0 offset=0x7C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CB)
- 0x07E page=0x0 offset=0x7E expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CC)
- 0x080 page=0x0 offset=0x80 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CD)
- 0x082 page=0x0 offset=0x82 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D4)
- 0x084 page=0x0 offset=0x84 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D5)
- 0x086 page=0x0 offset=0x86 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D6)
- 0x088 page=0x0 offset=0x88 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D7)
- 0x08A page=0x0 offset=0x8A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D8)
- 0x08C page=0x0 offset=0x8C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D9)
- 0x08E page=0x0 offset=0x8E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0DA)
- 0x090 page=0x0 offset=0x90 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0F6)
- 0x092 page=0x0 offset=0x92 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0F9)
- 0x094 page=0x0 offset=0x94 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FA)
- 0x096 page=0x0 offset=0x96 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FB)
- 0x098 page=0x0 offset=0x98 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FC)
- 0x09A page=0x0 offset=0x9A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FD)
- 0x09C page=0x0 offset=0x9C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FE)
- 0x09E page=0x0 offset=0x9E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FF)
- ... 1968 more factory diffs omitted
F400 Shadow Diffs:
- H'F400 offset=0x00 expected=0x0000 actual=0xFFFF (record_header_or_label)
- H'F402 offset=0x02 expected=0x6B6F actual=0xFFFF (record_header_or_label)
- H'F404 offset=0x04 expected=0xFE00 actual=0xFFFF (record_header_or_label)
- H'F406 offset=0x06 expected=0x0000 actual=0xFFFF (record_header_or_label)
- H'F408 offset=0x08 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x004)
- H'F40A offset=0x0A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x012)
- H'F40C offset=0x0C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x013)
- H'F40E offset=0x0E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x017)
- H'F410 offset=0x10 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x018)
- H'F412 offset=0x12 expected=0x0808 actual=0xFFFF (factory_shadow_offset; selectors=0x01A)
- H'F414 offset=0x14 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x01F)
- H'F416 offset=0x16 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x020)
- H'F418 offset=0x18 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x023)
- H'F41A offset=0x1A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x037)
- H'F41C offset=0x1C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x038)
- H'F41E offset=0x1E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x080)
- H'F420 offset=0x20 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x081)
- H'F422 offset=0x22 expected=0x0020 actual=0xFFFF (factory_shadow_offset; selectors=0x083)
- H'F424 offset=0x24 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x088)
- H'F426 offset=0x26 expected=0x0400 actual=0xFFFF (factory_shadow_offset; selectors=0x089)
- H'F428 offset=0x28 expected=0x0800 actual=0xFFFF (factory_shadow_offset; selectors=0x08B)
- H'F42A offset=0x2A expected=0x0040 actual=0xFFFF (factory_shadow_offset; selectors=0x08D)
- H'F42C offset=0x2C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x08F)
- H'F42E offset=0x2E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x091)
- H'F430 offset=0x30 expected=0xFF80 actual=0xFFFF (factory_shadow_offset; selectors=0x092)
- H'F432 offset=0x32 expected=0x4040 actual=0xFFFF (factory_shadow_offset; selectors=0x093)
- H'F434 offset=0x34 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x095)
- H'F436 offset=0x36 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x098)
- H'F438 offset=0x38 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x09A)
- H'F43A offset=0x3A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x09D)
- H'F43C offset=0x3C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x09E)
- H'F43E offset=0x3E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x09F)
- H'F440 offset=0x40 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A3)
- H'F442 offset=0x42 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A4)
- H'F444 offset=0x44 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A5)
- H'F446 offset=0x46 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A6)
- H'F448 offset=0x48 expected=0xF000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A7)
- H'F44A offset=0x4A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0A9)
- H'F44C offset=0x4C expected=0x2000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AA)
- H'F44E offset=0x4E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AC)
- H'F450 offset=0x50 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AD)
- H'F452 offset=0x52 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AE)
- H'F454 offset=0x54 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0AF)
- H'F456 offset=0x56 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B0)
- H'F458 offset=0x58 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B2)
- H'F45A offset=0x5A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B3)
- H'F45C offset=0x5C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B4)
- H'F45E offset=0x5E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B6)
- H'F460 offset=0x60 expected=0xF800 actual=0xFFFF (factory_shadow_offset; selectors=0x0B7)
- H'F462 offset=0x62 expected=0x4000 actual=0xFFFF (factory_shadow_offset; selectors=0x0B9)
- H'F464 offset=0x64 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0BC)
- H'F466 offset=0x66 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0BD)
- H'F468 offset=0x68 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C0)
- H'F46A offset=0x6A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C1)
- H'F46C offset=0x6C expected=0x4000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C3)
- H'F46E offset=0x6E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C4)
- H'F470 offset=0x70 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C5)
- H'F472 offset=0x72 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C6)
- H'F474 offset=0x74 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C7)
- H'F476 offset=0x76 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C8)
- H'F478 offset=0x78 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0C9)
- H'F47A offset=0x7A expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CA)
- H'F47C offset=0x7C expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CB)
- H'F47E offset=0x7E expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CC)
- H'F480 offset=0x80 expected=0x0000 actual=0xFFFF (factory_shadow_offset; selectors=0x0CD)
- H'F482 offset=0x82 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D4)
- H'F484 offset=0x84 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D5)
- H'F486 offset=0x86 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D6)
- H'F488 offset=0x88 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D7)
- H'F48A offset=0x8A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D8)
- H'F48C offset=0x8C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0D9)
- H'F48E offset=0x8E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0DA)
- H'F490 offset=0x90 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0F6)
- H'F492 offset=0x92 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0F9)
- H'F494 offset=0x94 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FA)
- H'F496 offset=0x96 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FB)
- H'F498 offset=0x98 expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FC)
- H'F49A offset=0x9A expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FD)
- H'F49C offset=0x9C expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FE)
- H'F49E offset=0x9E expected=0x8000 actual=0xFFFF (factory_shadow_offset; selectors=0x0FF)
- ... 47 more shadow diffs omitted

View File

@@ -0,0 +1,31 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=4bed7704e1ea085487ca325c43bd60da75d37b6ae6f8292544e069a8825c64c6
writes: bytes=0 words=0 factory_diff_words=0
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=00 00 6B 6F FE 00 00 00 text='..ko....'
- page 0x1 EEPROM 0x100-0x107 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x2 EEPROM 0x200-0x207 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x3 EEPROM 0x300-0x307 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x4 EEPROM 0x400-0x407 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x5 EEPROM 0x500-0x507 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x6 EEPROM 0x600-0x607 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x7 EEPROM 0x700-0x707 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x8 EEPROM 0x800-0x807 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x9 EEPROM 0x900-0x907 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xA EEPROM 0xA00-0xA07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xB EEPROM 0xB00-0xB07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xC EEPROM 0xC00-0xC07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xD EEPROM 0xD00-0xD07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xE EEPROM 0xE00-0xE07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xF EEPROM 0xF00-0xF07 bytes=20 20 20 20 20 20 20 20 text=' '
EEPROM Word Writes:
- none since EEPROM setup/load
Factory Diffs:
- current EEPROM image matches ROM factory/default image
F400 Shadow Diffs:
- H'F4AA offset=0xAA expected=0x8000 actual=0x5500 (factory_shadow_offset; selectors=0x112)

View File

@@ -0,0 +1,111 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=4bed7704e1ea085487ca325c43bd60da75d37b6ae6f8292544e069a8825c64c6
writes: bytes=4216 words=2108 factory_diff_words=0
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=00 00 6B 6F FE 00 00 00 text='..ko....'
- page 0x1 EEPROM 0x100-0x107 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x2 EEPROM 0x200-0x207 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x3 EEPROM 0x300-0x307 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x4 EEPROM 0x400-0x407 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x5 EEPROM 0x500-0x507 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x6 EEPROM 0x600-0x607 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x7 EEPROM 0x700-0x707 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x8 EEPROM 0x800-0x807 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x9 EEPROM 0x900-0x907 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xA EEPROM 0xA00-0xA07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xB EEPROM 0xB00-0xB07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xC EEPROM 0xC00-0xC07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xD EEPROM 0xD00-0xD07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xE EEPROM 0xE00-0xE07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xF EEPROM 0xF00-0xF07 bytes=20 20 20 20 20 20 20 20 text=' '
EEPROM Word Writes:
- 0x0FE page=0x0 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FE page=0x1 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FE page=0x2 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FE page=0x3 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FE page=0x4 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FE page=0x5 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FE page=0x6 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FE page=0x7 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FE page=0x8 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FE page=0x9 offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFE page=0xA offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFE page=0xB offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFE page=0xC offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFE page=0xD offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFE page=0xE offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFE page=0xF offset=0xFE 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0FC page=0x0 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FC page=0x1 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FC page=0x2 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FC page=0x3 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FC page=0x4 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FC page=0x5 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FC page=0x6 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FC page=0x7 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FC page=0x8 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FC page=0x9 offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFC page=0xA offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFC page=0xB offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFC page=0xC offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFC page=0xD offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFC page=0xE offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFC page=0xF offset=0xFC 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0FA page=0x0 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FA page=0x1 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FA page=0x2 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FA page=0x3 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FA page=0x4 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FA page=0x5 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FA page=0x6 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FA page=0x7 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FA page=0x8 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FA page=0x9 offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFA page=0xA offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFA page=0xB offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFA page=0xC offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFA page=0xD offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFA page=0xE offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFA page=0xF offset=0xFA 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0F8 page=0x0 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1F8 page=0x1 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2F8 page=0x2 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3F8 page=0x3 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4F8 page=0x4 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5F8 page=0x5 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6F8 page=0x6 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7F8 page=0x7 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8F8 page=0x8 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9F8 page=0x9 offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAF8 page=0xA offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBF8 page=0xB offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCF8 page=0xC offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDF8 page=0xD offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEF8 page=0xE offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFF8 page=0xF offset=0xF8 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x0F6 page=0x0 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x1F6 page=0x1 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x2F6 page=0x2 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x3F6 page=0x3 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x4F6 page=0x4 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x5F6 page=0x5 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x6F6 page=0x6 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x7F6 page=0x7 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x8F6 page=0x8 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0x9F6 page=0x9 offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xAF6 page=0xA offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xBF6 page=0xB offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xCF6 page=0xC offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xDF6 page=0xD offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xEF6 page=0xE offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- 0xFF6 page=0xF offset=0xF6 0xFFFF->0x0000 source=linear_word (factory_shadow_offset)
- ... 2028 more word writes omitted
Factory Diffs:
- current EEPROM image matches ROM factory/default image
F400 Shadow Diffs:
- F400-F4FF shadow matches ROM factory words or no ROM factory baseline was supplied

View File

@@ -0,0 +1,111 @@
Emulator EEPROM Snapshot
size=0x1000 sha256=4bed7704e1ea085487ca325c43bd60da75d37b6ae6f8292544e069a8825c64c6
writes: bytes=4216 words=2108 factory_diff_words=0
Persistent Records:
- page 0x0 EEPROM 0x000-0x007 bytes=00 00 6B 6F FE 00 00 00 text='..ko....'
- page 0x1 EEPROM 0x100-0x107 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x2 EEPROM 0x200-0x207 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x3 EEPROM 0x300-0x307 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x4 EEPROM 0x400-0x407 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x5 EEPROM 0x500-0x507 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x6 EEPROM 0x600-0x607 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x7 EEPROM 0x700-0x707 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x8 EEPROM 0x800-0x807 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0x9 EEPROM 0x900-0x907 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xA EEPROM 0xA00-0xA07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xB EEPROM 0xB00-0xB07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xC EEPROM 0xC00-0xC07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xD EEPROM 0xD00-0xD07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xE EEPROM 0xE00-0xE07 bytes=20 20 20 20 20 20 20 20 text=' '
- page 0xF EEPROM 0xF00-0xF07 bytes=20 20 20 20 20 20 20 20 text=' '
EEPROM Word Writes:
- 0x0FE page=0x0 offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FE page=0x1 offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FE page=0x2 offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FE page=0x3 offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FE page=0x4 offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FE page=0x5 offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FE page=0x6 offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FE page=0x7 offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FE page=0x8 offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FE page=0x9 offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFE page=0xA offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFE page=0xB offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFE page=0xC offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFE page=0xD offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFE page=0xE offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFE page=0xF offset=0xFE 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x0FC page=0x0 offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FC page=0x1 offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FC page=0x2 offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FC page=0x3 offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FC page=0x4 offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FC page=0x5 offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FC page=0x6 offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FC page=0x7 offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FC page=0x8 offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FC page=0x9 offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFC page=0xA offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFC page=0xB offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFC page=0xC offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFC page=0xD offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFC page=0xE offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFC page=0xF offset=0xFC 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x0FA page=0x0 offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x1FA page=0x1 offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x2FA page=0x2 offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x3FA page=0x3 offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x4FA page=0x4 offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x5FA page=0x5 offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x6FA page=0x6 offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x7FA page=0x7 offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x8FA page=0x8 offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x9FA page=0x9 offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xAFA page=0xA offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xBFA page=0xB offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xCFA page=0xC offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xDFA page=0xD offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xEFA page=0xE offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xFFA page=0xF offset=0xFA 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x0F8 page=0x0 offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x1F8 page=0x1 offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x2F8 page=0x2 offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x3F8 page=0x3 offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x4F8 page=0x4 offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x5F8 page=0x5 offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x6F8 page=0x6 offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x7F8 page=0x7 offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x8F8 page=0x8 offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x9F8 page=0x9 offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xAF8 page=0xA offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xBF8 page=0xB offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xCF8 page=0xC offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xDF8 page=0xD offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xEF8 page=0xE offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xFF8 page=0xF offset=0xF8 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x0F6 page=0x0 offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x1F6 page=0x1 offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x2F6 page=0x2 offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x3F6 page=0x3 offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x4F6 page=0x4 offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x5F6 page=0x5 offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x6F6 page=0x6 offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x7F6 page=0x7 offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x8F6 page=0x8 offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0x9F6 page=0x9 offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xAF6 page=0xA offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xBF6 page=0xB offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xCF6 page=0xC offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xDF6 page=0xD offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xEF6 page=0xE offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- 0xFF6 page=0xF offset=0xF6 0x0000->0x0000 source=linear_word (factory_shadow_offset)
- ... 2028 more word writes omitted
Factory Diffs:
- current EEPROM image matches ROM factory/default image
F400 Shadow Diffs:
- F400-F4FF shadow matches ROM factory words or no ROM factory baseline was supplied

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,168 @@
H8/536 CCU Seed Hint Report
Summary: The RCP likely waits for the CCU to seed mirrored state tables, then uses those selector values to update LCD text, panel lamps, and report state changes.
Confidence: medium
Table Model:
- primary_value_table_candidate: H'E000-H'E3FF; accesses=31 static selectors=0x000, 0x002, 0x003, 0x023, 0x040, 0x081, 0x092, 0x093, 0x0A7, 0x0B7, 0x0B9, 0x0F6
- secondary_value_table_candidate: H'E400-H'E7FF; accesses=8 static selectors=none
- current_value_table_candidate: H'E800-H'EBFF; accesses=14 static selectors=0x000, 0x003, 0x040, 0x081, 0x092, 0x0F6
- flag_table_candidate: H'EC00-H'EFFF; accesses=6 static selectors=0x000
Highest-Value Selector Candidates:
- 0x000 heartbeat_or_idle_report_candidate: score=18 tables=primary_value_table_candidate, current_value_table_candidate, flag_table_candidate
- primary_value_table_candidate write in loc_4096: MOV:G.W #H'0080, @H'E000
- current_value_table_candidate write in loc_4096: MOV:G.W #H'0080, @H'E800
- flag_table_candidate write in loc_4075: CLR.W @(-H'1400,R0)
- idle report selector and CONNECT OK emulator condition both center on selector zero
seed frames: 0x0080 -> 00 00 00 00 80 DA; 0x8080 -> 00 00 00 80 80 5A
readback frame: 01 00 00 00 00 5B
- 0x093 state_selector_candidate: score=15 tables=primary_value_table_candidate
- primary_value_table_candidate read in loc_17C9: BTST.W #12, @H'E126
- primary_value_table_candidate read in loc_17FB: BTST.W #12, @H'E126
- primary_value_table_candidate read in loc_182D: BTST.W #5, @H'E126
- primary_value_table_candidate read in loc_1891: BTST.W #5, @H'E126
readback frame: 01 01 13 00 00 49
- 0x0F6 active_status_bridge_candidate: score=14 tables=primary_value_table_candidate, current_value_table_candidate
- primary_value_table_candidate read in loc_48FA: BTST.W #13, @H'E1EC
- primary_value_table_candidate read in loc_48FA: MOV:G.W @H'E1EC, R0
- current_value_table_candidate write in loc_48FA: MOV:G.W R0, @H'E9EC
- loc_48FA tests E1EC bit13 and can enqueue report selector 0x00F6
seed frames: 0x2000 -> 00 01 76 20 00 0D
readback frame: 01 01 76 00 00 2C
- 0x003 default_enabled_bit_candidate: score=11 tables=primary_value_table_candidate, current_value_table_candidate
- primary_value_table_candidate write in loc_4096: MOV:G.W #H'8000, @H'E006
- current_value_table_candidate write in loc_4096: MOV:G.W #H'8000, @H'E806
- ROM default table writes E000/E800 selector 0x003 to 0x8000
seed frames: 0x8000 -> 00 00 03 80 00 D9
readback frame: 01 00 03 00 00 58
- 0x040 default_all_ones_or_status_block_candidate: score=11 tables=primary_value_table_candidate, current_value_table_candidate
- primary_value_table_candidate write in loc_4096: MOV:G.W #H'FFFF, @H'E080
- current_value_table_candidate write in loc_4096: MOV:G.W #H'FFFF, @H'E880
- ROM default table writes E000/E800 selector 0x040 to 0xFFFF and bench tests repeatedly touched the 0x40 family
seed frames: 0xFFFF -> 00 00 40 FF FF 1A; 0x4030 -> 00 00 40 40 30 6A
readback frame: 01 00 40 00 00 1B
- 0x081 state_selector_candidate: score=9 tables=primary_value_table_candidate, current_value_table_candidate
- primary_value_table_candidate read in vec_ad_adi_3D99: MOV:G.W @H'E102, R0
- primary_value_table_candidate read in vec_ad_adi_3D99: CMP:G.W @H'E102, R1
- current_value_table_candidate write in loc_15E0: MOV:G.W R1, @H'E902
readback frame: 01 01 01 00 00 5B
- 0x092 state_selector_candidate: score=9 tables=primary_value_table_candidate, current_value_table_candidate
- primary_value_table_candidate read in loc_2650: MOV:G.W @H'E124, R0
- primary_value_table_candidate read in loc_2650: CMP:G.W @H'E124, R0
- current_value_table_candidate write in loc_2650: MOV:G.W R0, @H'E924
readback frame: 01 01 12 00 00 48
- 0x06B connection_latch_clear_candidate: score=7 tables=none
- when F731.7 is set, command 5 on this selector clears F731.7/F790.7
- selector dispatches to H'2F72
readback frame: 01 00 6B 00 00 30
- 0x06C command5_be70_candidate: score=7 tables=none
- continuation command 5 calls BE70 for selector 0x006C
- selector dispatches to H'2FAF
readback frame: 01 00 6C 00 00 37
- 0x06D command5_be70_candidate: score=7 tables=none
- continuation command 5 calls BE70 for selector 0x006D
- selector dispatches to H'3015
readback frame: 01 00 6D 00 00 36
- 0x007 camera_power_report_candidate: score=5 tables=none
- observed RCP autonomous report frame(s): 00 00 07 80 00 DD
- selector dispatches to H'2DC3
readback frame: 01 00 07 00 00 5C
- 0x015 call_button_report_candidate: score=5 tables=none
- observed RCP autonomous report frame(s): 00 00 15 80 00 CF, 00 00 15 00 00 4F
- selector dispatches to H'2E39
readback frame: 01 00 15 00 00 4E
- 0x023 state_selector_candidate: score=5 tables=primary_value_table_candidate
- primary_value_table_candidate write in loc_400C: CLR.W @H'E046
- selector dispatches to H'2EE6
readback frame: 01 00 23 00 00 78
- 0x06E command5_be70_candidate: score=5 tables=none
- continuation command 5 calls BE70 for selector 0x006E
readback frame: 01 00 6E 00 00 35
- 0x096 connection_latch_clear_candidate: score=5 tables=none
- when F731.7 is set, command 5 on this selector clears F731.7/F790.7
readback frame: 01 01 16 00 00 4C
- 0x097 connection_latch_clear_candidate: score=5 tables=none
- when F731.7 is set, command 5 on this selector clears F731.7/F790.7
readback frame: 01 01 17 00 00 4D
- 0x0C6 connection_latch_clear_candidate: score=5 tables=none
- when F731.7 is set, command 5 on this selector clears F731.7/F790.7
readback frame: 01 01 46 00 00 1C
- 0x0F8 connection_latch_clear_candidate: score=5 tables=none
- when F731.7 is set, command 5 on this selector clears F731.7/F790.7
readback frame: 01 01 78 00 00 22
- 0x002 state_selector_candidate: score=3 tables=primary_value_table_candidate
- primary_value_table_candidate read in loc_2650: BTST.W #13, @H'E004
readback frame: 01 00 02 00 00 59
- 0x0A7 state_selector_candidate: score=3 tables=primary_value_table_candidate
- primary_value_table_candidate read in loc_1705: BTST.W #15, @H'E14E
readback frame: 01 01 27 00 00 7D
- 0x0B7 state_selector_candidate: score=3 tables=primary_value_table_candidate
- primary_value_table_candidate read in loc_174D: BTST.W #13, @H'E16E
readback frame: 01 01 37 00 00 6D
- 0x0B9 state_selector_candidate: score=3 tables=primary_value_table_candidate
- primary_value_table_candidate read in loc_1795: BTST.W #13, @H'E172
readback frame: 01 01 39 00 00 63
- 0x110 state_selector_candidate: score=3 tables=primary_value_table_candidate
- primary_value_table_candidate read in loc_1795: BTST.W #15, @H'E220
readback frame: 01 01 90 00 00 CA
- 0x012 state_selector_candidate: score=2 tables=none
- selector dispatches to H'2E03
readback frame: 01 00 12 00 00 49
Display Text Hints:
- CONNECT: 0 hit(s)
- COMM LINK: 4 hit(s) - H'77F4 'literal COMM LINK', H'78F4 'literal COMM LINK', H'77F4 'COMM LINK ITEM-1Xw'
- COMPLETED: 2 hit(s) - H'A027 'literal COMPLETED', H'A025 'COMPLETED'
- CAM: 6 hit(s) - H'7149 'literal CAM', H'71FC 'literal CAM', H'72C7 'literal CAM'
- BARS: 12 hit(s) - H'72D1 'literal BARS', H'757D 'literal BARS', H'9C61 'literal BARS'
- BLACK: 22 hit(s) - H'65CC 'literal BLACK', H'6647 'literal BLACK', H'6709 'literal BLACK'
- IRIS: 6 hit(s) - H'6461 'literal IRIS', H'6A92 'literal IRIS', H'A5CA 'literal IRIS'
- GAIN: 10 hit(s) - H'6825 'literal GAIN', H'7813 'literal GAIN', H'98A1 'literal GAIN'
- SHUTTER: 4 hit(s) - H'6FB2 'literal SHUTTER', H'781A 'literal SHUTTER', H'6FAE 'SHUTTER Xo'
- CALL: 8 hit(s) - H'B53E 'literal CALL', H'B563 'literal CALL', H'B62F 'literal CALL'
- POWER: 0 hit(s)
- AUTO: 34 hit(s) - H'693E 'literal AUTO', H'6A52 'literal AUTO', H'6B40 'literal AUTO'
- DIAG: 6 hit(s) - H'6BF5 'literal DIAG', H'6C19 'literal DIAG', H'6E46 'literal DIAG'
- DXC: 0 hit(s)
Selector Dispatch Hints:
- table H'28A6: 25 non-default/interesting entries
- selector 0x000 -> H'2CB9 (dispatch index 0x000)
- selector 0x007 -> H'2DC3 (dispatch index 0x007)
- selector 0x012 -> H'2E03 (dispatch index 0x012)
- selector 0x013 -> H'2E06 (dispatch index 0x013)
- selector 0x015 -> H'2E39 (dispatch index 0x015)
- selector 0x016 -> H'2E5A (dispatch index 0x016)
- selector 0x017 -> H'2E85 (dispatch index 0x017)
- selector 0x018 -> H'2E6F (dispatch index 0x018)
- selector 0x01A -> H'2EC4 (dispatch index 0x01A)
- selector 0x023 -> H'2EE6 (dispatch index 0x023)
- selector 0x024 -> H'2F0C (dispatch index 0x024)
- selector 0x025 -> H'2F1C (dispatch index 0x025)
- selector 0x043 -> H'2F4A (dispatch index 0x043)
- selector 0x04A -> H'2F5C (dispatch index 0x04A)
- selector 0x04E -> H'2F5C (dispatch index 0x04E)
- selector 0x052 -> H'2F5C (dispatch index 0x052)
Candidate Fake-CCU Seed Plan:
- cmd0 seed selector 0x000 = 0x8080: 00 00 00 80 80 5A
selector zero active/connect candidate from emulator state search
- cmd0 seed selector 0x003 = 0x8000: 00 00 03 80 00 D9
ROM default state also sets selector 0x003 high bit
- cmd0 seed selector 0x040 = 0xFFFF: 00 00 40 FF FF 1A
ROM default all-ones/status candidate touched by bench 0x40 family
- cmd0 seed selector 0x0F6 = 0x2000: 00 01 76 20 00 0D
sets E1EC bit13 candidate used by loc_48FA report bridge
Bench Implications:
- Do not wait for non-heartbeat reports as the only activation source; the CCU may be expected to push initial table state first.
- Use command 0 writes for initial seeding, then command 1 readbacks for verification. Treat command 4/5/6 as continuation-only until a live report proves otherwise.
- Selector zero remains the highest-value activation candidate because the emulator reaches CONNECT OK when E000[0]=0x8080 and the selector-zero processing queue runs.
- E1EC/selector 0x00F6 is a strong follow-up candidate because loc_48FA tests bit13 there and can enqueue report 0x00F6.
- LCD text terms such as CAM/BARS/BLACK/COMM LINK appear in ROM records, but they are not direct serial payload strings; they point to selector-driven display builders.
Caveats:
- Selector names are candidates, not confirmed protocol labels.
- Static table xrefs prove that firmware reads/writes a selector; they do not prove the external CCU must seed it on boot.
- Generated frames are syntactically valid six-byte host frames; bench safety still depends on timing and current RCP state.

8903
build/rom_eeprom_layout.json Normal file

File diff suppressed because it is too large Load Diff

179
build/rom_eeprom_layout.txt Normal file
View File

@@ -0,0 +1,179 @@
H8/536 EEPROM Layout Report
Summary: The ROM treats the traced P9 bus as two X24164-style EEPROM banks, mirrors a 0x100-byte factory/default block into F400-F4FF, and loads sixteen 8-byte persistent records into F7B0-F82F at boot.
Confidence: medium-high
Physical / Logical Model:
- lower_x24164_candidate: logical 0x000-0x7FF control A0/A1
- upper_x24164_candidate: logical 0x800-0xFFF control E0/E1
- bus: P91/SCL and P97/SDA bit-banged two-wire bus through ROM routines C121/C08B/C0DB/C10C/C142
- page model: 16 logical pages of 0x100 bytes; low 8 address bits are sent as the EEPROM word address
Boot Flow:
- H'40BB eeprom_boot_gate: initializes queue/table scratch, checks P7DR.7, then checks F402 == H'6B6F before trusting persisted state
- H'4103 factory_default_fill: copies ROM C964-CA63 into F400-F4FF and writes the same 0x100-byte defaults to each EEPROM page
- H'4187 record_header_blank: overwrites page offsets 0x00-0x07 on pages 0x1-0xF with four H'2020 words after factory replication; page 0 keeps the signature/options header
- H'41D2 persistent_record_load: reads page offsets 0x00-0x07 from pages 0x0-0xF into F7B0-F82F; record 0 is a signature/options header, records 1-F are label/identity-like slots
- H'BD2B serial_persist_path: command-4 continuation writes the live value into F400+map[selector] and persists it through BFE0 when F76E.7 is set
Factory Shadow Block:
- ROM H'C964 length 0x100 mirrors to H'F400-H'F4FF
- H'F402 offset 0x02 default 0x6B6F (ascii='ko'; xrefs=1)
- H'F404 offset 0x04 default 0xFE00 (xrefs=8)
- H'F408 offset 0x08 default 0x8000 (selectors=0x004)
- H'F40A offset 0x0A default 0x0000 (selectors=0x012)
- H'F40C offset 0x0C default 0x0000 (selectors=0x013)
- H'F40E offset 0x0E default 0x8000 (selectors=0x017)
- H'F410 offset 0x10 default 0x8000 (selectors=0x018)
- H'F412 offset 0x12 default 0x0808 (selectors=0x01A)
- H'F414 offset 0x14 default 0x0000 (selectors=0x01F)
- H'F416 offset 0x16 default 0x0000 (selectors=0x020)
- H'F418 offset 0x18 default 0x0000 (selectors=0x023)
- H'F41A offset 0x1A default 0x0000 (selectors=0x037)
- H'F41C offset 0x1C default 0x0000 (selectors=0x038)
- H'F41E offset 0x1E default 0x8000 (selectors=0x080)
- H'F420 offset 0x20 default 0x8000 (selectors=0x081)
- H'F422 offset 0x22 default 0x0020 (ascii='. '; selectors=0x083)
- H'F424 offset 0x24 default 0x0000 (selectors=0x088)
- H'F426 offset 0x26 default 0x0400 (selectors=0x089)
- H'F428 offset 0x28 default 0x0800 (selectors=0x08B)
- H'F42A offset 0x2A default 0x0040 (ascii='.@'; selectors=0x08D)
- H'F42C offset 0x2C default 0x0000 (selectors=0x08F)
- H'F42E offset 0x2E default 0x8000 (selectors=0x091)
- H'F430 offset 0x30 default 0xFF80 (selectors=0x092)
- H'F432 offset 0x32 default 0x4040 (ascii='@@'; selectors=0x093)
- H'F434 offset 0x34 default 0x0000 (selectors=0x095)
- H'F436 offset 0x36 default 0x0000 (selectors=0x098)
- H'F438 offset 0x38 default 0x8000 (selectors=0x09A)
- H'F43A offset 0x3A default 0x0000 (selectors=0x09D)
- H'F43C offset 0x3C default 0x8000 (selectors=0x09E)
- H'F43E offset 0x3E default 0x8000 (selectors=0x09F)
- H'F440 offset 0x40 default 0x8000 (selectors=0x0A3)
- H'F442 offset 0x42 default 0x8000 (selectors=0x0A4)
Persistent 8-Byte Records:
- 16 records: EEPROM 0x000-0x007 .. 0xF00-0xF07 load into RAM H'F7B0-H'F7B7 .. H'F828-H'F82F
- record 0x0: EEPROM 0x000-0x007 -> RAM H'F7B0-H'F7B7 default '..ko....'
- record 0x1: EEPROM 0x100-0x107 -> RAM H'F7B8-H'F7BF default ' '
- record 0x2: EEPROM 0x200-0x207 -> RAM H'F7C0-H'F7C7 default ' '
- record 0x3: EEPROM 0x300-0x307 -> RAM H'F7C8-H'F7CF default ' '
- record 0x4: EEPROM 0x400-0x407 -> RAM H'F7D0-H'F7D7 default ' '
- record 0x5: EEPROM 0x500-0x507 -> RAM H'F7D8-H'F7DF default ' '
- record 0x6: EEPROM 0x600-0x607 -> RAM H'F7E0-H'F7E7 default ' '
- record 0x7: EEPROM 0x700-0x707 -> RAM H'F7E8-H'F7EF default ' '
- record 0x8: EEPROM 0x800-0x807 -> RAM H'F7F0-H'F7F7 default ' '
- record 0x9: EEPROM 0x900-0x907 -> RAM H'F7F8-H'F7FF default ' '
- record 0xA: EEPROM 0xA00-0xA07 -> RAM H'F800-H'F807 default ' '
- record 0xB: EEPROM 0xB00-0xB07 -> RAM H'F808-H'F80F default ' '
- record 0xC: EEPROM 0xC00-0xC07 -> RAM H'F810-H'F817 default ' '
- record 0xD: EEPROM 0xD00-0xD07 -> RAM H'F818-H'F81F default ' '
- record 0xE: EEPROM 0xE00-0xE07 -> RAM H'F820-H'F827 default ' '
- record 0xF: EEPROM 0xF00-0xF07 -> RAM H'F828-H'F82F default ' '
Serial Selector -> Shadow/EEPROM Mapping:
- table H'C564 has 89 nonzero low-byte mappings
- formula: command 4 persists to EEPROM address (((F76E & 0x0F) << 8) | (mapping_low_byte & 0xFE)) when F76E.7 is set
- selector 0x004 map_word=0x4808 -> H'F408 page+0x08 default=0x8000
- selector 0x012 map_word=0x080A -> H'F40A page+0x0A default=0x0000
- selector 0x013 map_word=0x080C -> H'F40C page+0x0C default=0x0000
- selector 0x017 map_word=0x600E -> H'F40E page+0x0E default=0x8000
- selector 0x018 map_word=0x6010 -> H'F410 page+0x10 default=0x8000
- selector 0x01A map_word=0x1012 -> H'F412 page+0x12 default=0x0808
- selector 0x01F map_word=0x4414 -> H'F414 page+0x14 default=0x0000
- selector 0x020 map_word=0x4416 -> H'F416 page+0x16 default=0x0000
- selector 0x023 map_word=0x0418 -> H'F418 page+0x18 default=0x0000
- selector 0x037 map_word=0x641A -> H'F41A page+0x1A default=0x0000
- selector 0x038 map_word=0x641C -> H'F41C page+0x1C default=0x0000
- selector 0x080 map_word=0xE01E -> H'F41E page+0x1E default=0x8000
- selector 0x081 map_word=0x6020 -> H'F420 page+0x20 default=0x8000
- selector 0x083 map_word=0x6022 -> H'F422 page+0x22 default=0x0020
- selector 0x088 map_word=0x4024 -> H'F424 page+0x24 default=0x0000
- selector 0x089 map_word=0x4426 -> H'F426 page+0x26 default=0x0400
- selector 0x08B map_word=0x4428 -> H'F428 page+0x28 default=0x0800
- selector 0x08D map_word=0x442A -> H'F42A page+0x2A default=0x0040
- selector 0x08F map_word=0x602C -> H'F42C page+0x2C default=0x0000
- selector 0x091 map_word=0x602E -> H'F42E page+0x2E default=0x8000
- selector 0x092 map_word=0x6030 -> H'F430 page+0x30 default=0xFF80
- selector 0x093 map_word=0x6032 -> H'F432 page+0x32 default=0x4040
- selector 0x095 map_word=0x6034 -> H'F434 page+0x34 default=0x0000
- selector 0x098 map_word=0x4036 -> H'F436 page+0x36 default=0x0000
- selector 0x09A map_word=0x6038 -> H'F438 page+0x38 default=0x8000
- selector 0x09D map_word=0x443A -> H'F43A page+0x3A default=0x0000
- selector 0x09E map_word=0x603C -> H'F43C page+0x3C default=0x8000
- selector 0x09F map_word=0x603E -> H'F43E page+0x3E default=0x8000
- selector 0x0A3 map_word=0x6040 -> H'F440 page+0x40 default=0x8000
- selector 0x0A4 map_word=0x6042 -> H'F442 page+0x42 default=0x8000
- selector 0x0A5 map_word=0x6044 -> H'F444 page+0x44 default=0x8000
- selector 0x0A6 map_word=0x6046 -> H'F446 page+0x46 default=0x8000
- selector 0x0A7 map_word=0x4048 -> H'F448 page+0x48 default=0xF000
- selector 0x0A9 map_word=0xE04A -> H'F44A page+0x4A default=0x8000
- selector 0x0AA map_word=0xC44C -> H'F44C page+0x4C default=0x2000
- selector 0x0AC map_word=0xC44E -> H'F44E page+0x4E default=0x8000
- selector 0x0AD map_word=0xC450 -> H'F450 page+0x50 default=0x8000
- selector 0x0AE map_word=0xC452 -> H'F452 page+0x52 default=0x8000
- selector 0x0AF map_word=0xC454 -> H'F454 page+0x54 default=0x8000
- selector 0x0B0 map_word=0xC456 -> H'F456 page+0x56 default=0x8000
- selector 0x0B2 map_word=0xC458 -> H'F458 page+0x58 default=0x8000
- selector 0x0B3 map_word=0xC45A -> H'F45A page+0x5A default=0x8000
- selector 0x0B4 map_word=0xC45C -> H'F45C page+0x5C default=0x8000
- selector 0x0B6 map_word=0xC45E -> H'F45E page+0x5E default=0x8000
- selector 0x0B7 map_word=0x4060 -> H'F460 page+0x60 default=0xF800
- selector 0x0B9 map_word=0x6862 -> H'F462 page+0x62 default=0x4000
- selector 0x0BC map_word=0xE064 -> H'F464 page+0x64 default=0x8000
- selector 0x0BD map_word=0xC066 -> H'F466 page+0x66 default=0x8000
- selector 0x0C0 map_word=0x4468 -> H'F468 page+0x68 default=0x8000
- selector 0x0C1 map_word=0xC46A -> H'F46A page+0x6A default=0x8000
- selector 0x0C3 map_word=0xE46C -> H'F46C page+0x6C default=0x4000
- selector 0x0C4 map_word=0x446E -> H'F46E page+0x6E default=0x8000
- selector 0x0C5 map_word=0xC070 -> H'F470 page+0x70 default=0x8000
- selector 0x0C6 map_word=0x4472 -> H'F472 page+0x72 default=0x8000
- selector 0x0C7 map_word=0xC474 -> H'F474 page+0x74 default=0x8000
- selector 0x0C8 map_word=0xC476 -> H'F476 page+0x76 default=0x0000
- selector 0x0C9 map_word=0xC478 -> H'F478 page+0x78 default=0x0000
- selector 0x0CA map_word=0xC47A -> H'F47A page+0x7A default=0x0000
- selector 0x0CB map_word=0xC47C -> H'F47C page+0x7C default=0x0000
- selector 0x0CC map_word=0xC47E -> H'F47E page+0x7E default=0x0000
- selector 0x0CD map_word=0xC480 -> H'F480 page+0x80 default=0x0000
- selector 0x0D4 map_word=0xC482 -> H'F482 page+0x82 default=0x8000
- selector 0x0D5 map_word=0xC484 -> H'F484 page+0x84 default=0x8000
- selector 0x0D6 map_word=0xC086 -> H'F486 page+0x86 default=0x8000
- selector 0x0D7 map_word=0xC088 -> H'F488 page+0x88 default=0x8000
- selector 0x0D8 map_word=0x408A -> H'F48A page+0x8A default=0x8000
- selector 0x0D9 map_word=0x408C -> H'F48C page+0x8C default=0x8000
- selector 0x0DA map_word=0x408E -> H'F48E page+0x8E default=0x8000
- selector 0x0F6 map_word=0x4090 -> H'F490 page+0x90 default=0x8000
- selector 0x0F9 map_word=0x4492 -> H'F492 page+0x92 default=0x8000
- selector 0x0FA map_word=0x4494 -> H'F494 page+0x94 default=0x8000
- selector 0x0FB map_word=0x4496 -> H'F496 page+0x96 default=0x8000
- selector 0x0FC map_word=0x4498 -> H'F498 page+0x98 default=0x8000
- selector 0x0FD map_word=0x409A -> H'F49A page+0x9A default=0x8000
- selector 0x0FE map_word=0x449C -> H'F49C page+0x9C default=0x8000
- selector 0x0FF map_word=0x449E -> H'F49E page+0x9E default=0x8000
- selector 0x100 map_word=0x44A0 -> H'F4A0 page+0xA0 default=0x8000
- selector 0x101 map_word=0x44A2 -> H'F4A2 page+0xA2 default=0x8000
- selector 0x10F map_word=0xC4A4 -> H'F4A4 page+0xA4 default=0x8000
- selector 0x110 map_word=0x48A6 -> H'F4A6 page+0xA6 default=0x0000
- ... 9 more mapped selectors omitted
State Byte Hints:
- H'F402 factory_signature_word: factory 0x6B6F; boot accepts persisted state only when this word is H'6B6F (xrefs=1)
- H'F404 feature_or_option_flags_candidate: factory 0xFE00; bits 1-4 are tested with F791 gates in display/status routines (xrefs=8)
- H'F730 connect_display_state_candidate: volatile/no factory word (xrefs=3)
- H'F731 session_latch_candidate: volatile/no factory word (xrefs=22)
- H'F732 display_dispatch_selector_candidate: volatile display dispatch selector feeding the 493E pointer table and 48FA report bridge (xrefs=11)
- H'F76E eeprom_page_and_persist_flags: bit7 enables command-4 EEPROM persistence, bit6 suppresses 48FA dispatch, low nibble selects EEPROM page (xrefs=4)
- H'F790 connection_latch_shadow_candidate: volatile/no factory word (xrefs=1)
- H'F791 feature_flag_gate_candidate: volatile gate tested alongside F404 option bits before setting report/display flags (xrefs=12)
- H'FB03 report_bridge_suppress_candidate: volatile/no factory word (xrefs=10)
Bench Implications:
- A live EEPROM dump should first compare F400-F4FF shadow-equivalent offsets against the ROM factory table, especially F402/F404 and mapped offsets.
- Pages 0x0-0xF offset 0x00-0x07 are loaded into F7B0-F82F; page 0 carries the signature/options header and pages 1-F default to spaces, so non-space bytes there are high-value identity/config data.
- Serial command 0/4 can mirror values into F400 offsets selected by the ROM mapping table, but command 4 only persists when the continuation path reaches BD2B and F76E.7 is set.
- F76E is not just a page byte: bit7 gates EEPROM persistence, bit6 suppresses the 48FA dispatch path, and only its low nibble survives into the EEPROM page address.
- CONNECT OK is still likely gated by volatile table/session state as well as EEPROM-backed defaults; EEPROM differences may explain why bench and emulator diverge, but are unlikely to be the whole protocol by themselves.
Caveats:
- The selector map proves where firmware mirrors/persists serial values, not what every field means to the panel UI.
- High bytes in the C564 selector map look structured, but the observed command-0/command-4 paths only use the low byte for F400/EEPROM offsets.
- Indexed F7B0-F82F record consumers can be missed by a static xref pass; dynamic emulator traces should be used once interesting record bytes are found.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,218 @@
H8/536 SCI1 RX Branch Trace
Summary: The ROM captures six SCI1 bytes, validates a 0x5A-seeded XOR checksum, decodes RX[0] & 0x07, then splits into initial commands while FAA2 == 0 and continuation commands while FAA2 != 0.
Confidence: high
Frame Model:
- capture buffer: H'F868-H'F86D
- validation buffer: H'F860-H'F865
- command: RX[0] & 0x07
- logical index candidate: loc_622B(RX[1], RX[2])
- value candidate: RX[3:4]
- checksum: 0x5A XOR RX[0..4] == RX[5]
Selector Decode:
- loc_622B logical selector decode: present
The RX handler builds a raw word from RX[1:2], uses RX[1] bit0-bit2 as a page, keeps RX[2] as the low selector byte, and maps the result into a 0x000-0x1FF logical selector.
- page 0, or pages 4-7, and low <= 0x7F: selector = 0x000 + low [H'6234, H'6236, H'6244, H'6248, H'6264]
- page 1 and low <= 0xFF: selector = 0x080 + low [H'6238, H'623A, H'624D, H'6251, H'6264]
- page 2 and low <= 0x7F: selector = 0x180 + low [H'623C, H'623E, H'6256, H'625A, H'6264]
- page 3, page/range failure, or page 0/4-7 with low > 0x7F: selector forced to 0x01FF [H'6240, H'6242, H'625F, H'6261]
- implication: RX[1].7 is not part of the selector here because the dispatcher rejects byte1 bit7 before command handling.
- implication: Commands shaped as 01 80 xx ... are rejected before loc_622B-derived command handling even if the low byte looks useful.
- implication: Page 4-7 encodings appear to alias the page-0 decode path unless the low byte is out of range.
Stages:
- SCI1 RXI/ERI byte capture: present
ERI latches FAA4.7 and clears physical error flags; RXI clears RDRF, reads SCI1_RDR, stores bytes into F868-F86D, reloads F9C1, and sets F9C5 when six bytes are captured.
- ERI taken: set FAA4.7, clear ORER/FER/PER, then fall into RXI byte capture [H'BB57, H'BB5B, H'BB5F, H'BB63]
- F9C1 == 0 before byte: clear F9C3 so the byte starts a fresh frame [H'BB71, H'BB75, H'BB77]
- F9C1 != 0 and F9C3 <= 5: append byte at F868 + F9C3 and increment F9C3 [H'BB7D, H'BB82, H'BB8A, H'BB90, H'BB96]
- F9C1 != 0 and F9C3 > 5: clear FAA4 and skip storing this byte [H'BB7D, H'BB82, H'BB84, H'BB88]
- incremented F9C3 == 6: load F9C5 with 0x14 as the RX/session timeout window [H'BB9A, H'BB9C, H'BB9E]
- any RXI exit: reload F9C1 with 0x05 as the inter-byte timeout [H'BBA3]
- six-byte validation and checksum: present
The main-loop processor only runs when F9C3 is six, copies F868-F86D to F860-F865, clears F9C3, rejects physical-error frames, checks the 0x5A XOR checksum, and decodes the index.
- F9C3 != 6: return without processing [H'BBAB, H'BBB0]
- FAA4.7 set after capture: enter retry/error path at BE29 [H'BBCF, H'BBD3]
- checksum mismatch: enter retry/error path at BE29 [H'BBD6, H'BBD8, H'BBDC, H'BBE0, H'BBE4, H'BBE8, H'BBEC, H'BBF0]
- checksum valid: clear FAA6, decode selector from RX[1:2] through loc_622B, and dispatch on RX[0] & 0x07 [H'BBF3, H'BBF7, H'BBFD, H'BC01, H'BC08, H'BC0C]
- FAA2 split dispatcher: present
FAA2 == 0 enters the initial dispatcher for commands 0, 1, 2, and 7. FAA2 != 0 enters the continuation dispatcher; commands 4, 5, and 6 only live there.
- FAA2 == 0: set FAA2.7 and test initial commands 0, 1, 2, 7 [H'BC0F, H'BC13, H'BC15, H'BC20, H'BC24, H'BC29, H'BC2E]
- FAA2 != 0 and command bit2 set: continuation commands 4, 5, 6, 7 are possible [H'BC0F, H'BC13, H'BC3A, H'BC45, H'BC4A, H'BC4F, H'BC54]
- FAA2 != 0 and command bit2 clear: BCLR FAA2.3; if that bit was set, clear FAA3 and re-enter initial dispatcher [H'BC3A, H'BC3C, H'BC5C, H'BC60, H'BC63, H'BC67]
- byte1 bit7 set on initial path: branch to BD0B and return without normal command handling [H'BC19, H'BC1D, H'BD0B]
- byte1 bit7 set on continuation path: branch to BE27 and return without normal command handling [H'BC3E, H'BC42, H'BE27]
- checksum/error retry path: present
Physical RX errors or checksum failures can either be ignored, clear the session after two retries, or stage a command-7 retry/error echo of RX[1:4].
- FAA5.7 == 0: return after clearing FAA4.7; retry echo is disabled [H'BE29, H'BE2D, H'BE31]
- FAA5.7 == 1 and FAA6 < 2: stage F850=0x07 and F851-F854=RX[1:4], then call BA26 [H'BE33, H'BE37, H'BE3C, H'BE4D, H'BE52, H'BE5A, H'BE62, H'BE6A]
- FAA5.7 == 1 and FAA6 >= 2: load F9C0=0x1F and clear FAA3/FAA2 [H'BE37, H'BE3C, H'BE3E, H'BE43, H'BE47]
- pending selector ring at loc_BE70: present
Several write/ACK paths call BE70 with R5 as the logical selector. BE70 deduplicates that selector against a small F970-like ring using cursors F9B9/F9B4, then appends it if absent.
- selector already present: exit without appending [H'BE84, H'BE88, H'BE9D]
- ring scan reaches F9B4 cursor: store R5 into the pending ring and advance F9B4 [H'BE80, H'BE82, H'BE91, H'BE95, H'BE99]
- session timeout and resend side paths: present
After any complete RX frame, F9C5 keeps FAA5.7/session-gate state alive for a short window. When that window expires, 3FEF can clear queue cursors and call 400C, while BE9E handles resend countdowns.
- F9C5 == 0 at loc_3FEF: clear F9B5/F9B0 and clear FAA5.7; if FAA5.7 was set, call 400C reset/NOT-ACT state clear [H'3FEF, H'3FF3, H'3FF5, H'3FF9, H'3FFD, H'4001, H'4003]
- F9C5 != 0 at loc_3FEF: set FAA5.7, allowing retry/resend/session-gated paths [H'3FEF, H'3FF3, H'4007]
- BE9E sees no pending FAA5 & FAA3 & 0x80: clear FAA2 and return [H'BE9E, H'BEA5, H'BEA9, H'BEAD, H'BEAF]
- BE9E pending and F9C6==0 and F9C8!=0: decrement F9C8, reload F9C6, and possibly resend staged TX through BA26 [H'BEB5, H'BEBB, H'BEC1, H'BEC5, H'BECB, H'BED1, H'BED5]
- BE9E pending but F9C8==0: clear F9C5, which lets 3FEF collapse the session gate later [H'BEBB, H'BEBF, H'BEE4]
Command Branches:
- cmd 0x00 set_value_acked_candidate: initial path only: checksum valid, FAA2 == 0, RX[1].7 == 0; handler H'BC69; writes RX[3:4] into primary/current tables, flags the selector, calls BE70, and sends an echo-style 0x04 response
response: F850=0x04; F851-F854 mostly echo RX[1:4]; BA26 sends it
- selector zero is special: the low byte is forced to 0x80 after the high byte is taken from RX[3]
- nonzero selectors can mirror into an auxiliary table via a mapping table
- clears FAA2.7 before exit
- cmd 0x01 read_value_candidate: initial path only: checksum valid, FAA2 == 0, RX[1].7 == 0; handler H'BCD7; reads primary table E000 + 2*selector and stages a 0x04 response
response: F850=0x04; F851 is overwritten with RX[2]; F853/F854 receive table high/low; F852 is not freshly written here
- does not enter continuation command handling
- clears FAA2.7 before exit
- cmd 0x02 initial_clear_or_noop_candidate: initial path only: checksum valid, FAA2 == 0, RX[1].7 == 0; handler H'BD04; clears FAA2.7 and returns without staging a response
response: no immediate serial response
- likely a quiet/session-clear style command on the initial path
- cmd 0x04 continuation_set_value_candidate: continuation path only: checksum valid, FAA2 != 0, command bit2 set, RX[1].7 == 0; handler H'BD0E; writes a value into the primary table without an immediate serial response; selector zero also updates the current/report table
response: no immediate serial response
- selector zero is special: RX[3] becomes the high byte and low byte is forced to 0x80
- nonzero selectors write E000 and flag EC00.7; the matching E800 current/report write is not present in this handler
- nonzero selectors can mirror/persist through F400/BFE0 when mapping and F76E.7 allow it
- if FAA2.3 was set from a queued report, advances F9B5 to consume that report
- clears FAA3 and FAA2 before exit
- cmd 0x05 continuation_ack_or_clear_pending_candidate: continuation path only: checksum valid, FAA2 != 0, command bit2 set, RX[1].7 == 0; handler H'BD80; ACK/session-clear path; usually no response, but selected logical indexes feed BE70 or clear connection latches
response: no immediate serial response
- selectors 0x006C, 0x006D, and 0x006E call BE70
- with F731.7 set, selectors 0x006B, 0x0096, 0x0097, 0x00C6, and 0x00F8 clear F731.7/F790.7
- if FAA2.3 was set from a queued report, advances F9B5
- clears FAA3 and FAA2 before exit
- cmd 0x06 continuation_set_secondary_candidate: continuation path only: checksum valid, FAA2 != 0, command bit2 set, RX[1].7 == 0; handler H'BDDB; writes RX[3:4] into the secondary table and sets flag-table bit 6
response: no immediate serial response
- if FAA2.3 was set from a queued report, advances F9B5
- clears FAA3 and FAA2 before exit
- cmd 0x07 retransmit_previous_tx_candidate: initial or continuation path; handler H'BE05; copies the previous finalized TX frame bytes back into staging and sends them again
response: previous TX frame retransmitted through BA26
- loads F9C0 with 0x1F before sending
Table Surfaces:
- primary_value_table H'E000-H'E3FF: present; logical selector word table; command 0 and continuation command 4 write it, command 1 reads it [H'BC75, H'BC95, H'BCEC, H'BD1A, H'BD35]
- indexed as E000 + 2*selector after loc_622B
- selector zero writes force the low byte to 0x80 on commands 0 and 4
- this is the table that must contain E000[0]=0x8080 for the emulator-correlated CONNECT OK branch
- current_report_value_table H'E800-H'EBFF: present; current/report value table used when queued serial reports are converted into TX frames [H'BC79, H'BC99, H'BD1E, H'BB35, H'BB39, H'BB3F]
- command 0 writes both primary E000 and current E800 for zero and nonzero selectors
- command 4 writes E800 only on the selector-zero special path; the nonzero command-4 path does not show a matching E800 write here
- loc_BAF2 reads E800 + 2*queued_selector when building autonomous report frames
- secondary_value_table H'E400-H'E7FF: present; secondary logical selector word table written by continuation command 6 [H'BDE5, H'BDE9]
- command 6 writes RX[3:4] to E400 + 2*selector
- the matching EC00 flag bit is bit6 rather than bit7
- dirty_flag_table H'EC00-H'EDFF: present; per-selector flag bytes; command 0/4 set bit7 and command 6 sets bit6 [H'BC82, H'BC9D, H'BD22, H'BD39, H'BDE9]
- the same logical selector indexes this byte table directly, not as a word offset
- loc_48FA and other consumers test these table bits before raising follow-on reports
- mapped_shadow_or_eeprom_surface H'F400-H'F4FF: present; optional mapped mirror/persistence surface selected through ROM tables around C564/C565 [H'BCA1, H'BCA9, H'BD3D, H'BD45, H'BD49, H'BD5F]
- nonzero command-0 and command-4 writes consult mapping bytes/words before mirroring into F400
- command 4 can call BFE0 to persist the mapped word when F76E.7 is set
- selector zero bypasses this mapped mirror path
Downstream Flow Traces:
- immediate response staging through loc_BA26: present
Command 0, command 1, command 7, and the retry/error path stage F850-F854, call BA26, copy that staging area to F858-F85C, compute F85D, send the first byte, then let TXI send bytes 1-5.
- stage command-0 echo: F850=0x04 and F851-F854 mirror the accepted host fields before BA26 [H'BCB0, H'BCB5, H'BCC1, H'BCC9, H'BCCD]
- stage command-1 readback: F850=0x04, F853/F854 receive the E000 table word, and BA26 sends it [H'BCD7, H'BCEC, H'BCF0, H'BCF6, H'BCFA]
- finalize TX frame: BA26 copies F850-F854 to F858-F85C and computes F85D as 0x5A XOR bytes 0-4 [H'BA36, H'BA3A, H'BA42, H'BA4A, H'BA4E, H'BA64]
- start SCI1 transmission: BA26 waits for TDRE, writes F858 to SCI1_TDR, sets F9C2=1, clears TDRE, and enables TIE [H'BA68, H'BA72, H'BA76, H'BA7B, H'BA7F]
- finish SCI1 transmission: TXI indexes F858+F9C2 until six bytes are sent, then disables TIE [H'BAAB, H'BAB1, H'BAB5, H'BABF, H'BAC3, H'BACA]
- selector-processing queue BE70/F970 into loc_2806: present
BE70 appends unique logical selectors into the F970 ring. The main loop later consumes that ring at loc_2806 and dispatches selector-specific behavior through the 28A6 jump table.
- append unique selector: BE70 scans from F9B9 to F9B4, skips duplicates, writes R5 to F970+2*cursor, and advances F9B4 [H'BE70, H'BE78, H'BE84, H'BE91, H'BE95, H'BE99]
- consume selector: loc_2806 reads F970+2*F9B9, advances F9B9, masks the selector to 0x01FF, and keeps it in R5 [H'2806, H'280C, H'2819, H'281D, H'2822, H'2826, H'282A]
- active-selector side path: selectors matching F736/F738/F73A/F73C/F73E/F740/F742/F754 call loc_48FA before the jump table [H'2837, H'285E, H'2878, H'2892, H'2CAB, H'2CAD]
- selector jump table: the consumer jumps through table 28A6; selector zero is emulator-correlated with the CONNECT handler window [H'289F, H'28A3]
- serial-report queue loc_3E54/F870 into loc_BAF2: present
loc_3E54 is a shared enqueue helper. R2.bit7 queues serial-visible report indexes in F870, while R2.bit6 queues selector-processing work in F970. loc_BAF2 is the path that turns F870 entries into outbound 6-byte frames.
- enqueue serial report: when R2.7 is set, loc_3E54 deduplicates R3 in F870 and advances F9B0 [H'3E54, H'3E58, H'3E6C, H'3E76, H'3E7A, H'3E7E]
- queue backpressure drain: if the serial queue is nearly full, loc_3E54 calls loc_3FD3 until there is space [H'3E82, H'3E8B, H'3E91, H'3E93]
- enqueue selector processing: when R2.6 is set, loc_3E54 deduplicates R3 in F970 and advances F9B4 [H'3E9A, H'3E9E, H'3EB2, H'3EBF, H'3EC3, H'3EC7]
- dequeue serial report: loc_BAF2 compares F9B5/F9B0, reads F870+2*F9B5, builds a TX report from E800, and calls BA26 [H'BAF2, H'BAF8, H'BB00, H'BB08, H'BB1C, H'BB35, H'BB43]
- open continuation latch: after a report send, loc_BAF2 sets FAA2.3, FAA3.7, F9C6, and F9C8; command 4/5/6 can then consume or ACK that report [H'BB00, H'BB46, H'BB4C, H'BB51]
- TXI/RXI race and continuation collapse: present
The TX interrupt handler has an interlock: if a queued report is awaiting continuation and RX bytes are already arriving, it clears the report/continuation state and stops TXI before the normal frame-finish path.
- detect overlap: TXI tests FAA2.3, FAA5.7, and nonzero F9C3 before continuing the TX frame [H'BA84, H'BA8A, H'BA90]
- collapse continuation: on overlap it clears FAA2.3 and FAA3, disables TIE, and loads F9C0=0x1F [H'BA96, H'BA9A, H'BA9E, H'BAA2]
- normal completion path: without the overlap, TXI sends bytes until F9C2 reaches six and then starts the post-TX delay path [H'BAA9, H'BAB5, H'BAC3, H'BACA, H'BADA, H'BAED]
- session expiry into reset/not-active state: present
When F9C5 reaches zero, loc_3FEF clears queue cursors and FAA5.7; if FAA5.7 had been set, loc_400C clears connection/session RAM and refreshes the inactive display state.
- expire RX session: F9C5==0 clears F9B5/F9B0 and clears FAA5.7 [H'3FEF, H'3FF5, H'3FF9, H'3FFD]
- call reset side path: if clearing FAA5.7 changed the bit, loc_3FEF calls loc_400C [H'4001, H'4003]
- clear connection state: loc_400C clears F730/F756-F759/F732/F75C/FB03/F791/F795/F76E and calls follow-on display/session refresh routines [H'400C, H'4010, H'4020, H'4028, H'4034, H'403C, H'4040, H'4042]
RX-to-TX Feedback Loops:
- command-0 write echo and selector-processing loop: present
trigger: valid command 0 while FAA2 == 0
path: RX validation -> BC69 table write -> BE70 appends selector to F970 -> BCB0/BA26 sends immediate 0x04 echo -> later loc_2806 consumes F970
TX outcome: immediate command-4-style echo frame plus possible later selector-driven reports/display work
timing/gate: BA26 sets F9C0=0x64 and F9C4=0x07, temporarily delaying queued TX and heartbeat enqueue
bench read: a command-0 probe is stateful: it both writes table state and spends time in the post-TX delay, so it can disturb the continuation window being tested
evidence: H'BC69, H'BC75, H'BC86, H'BCB0, H'BCCD, H'BA2C, H'BA31, H'BE70, H'2819, H'28A3
- command-1 readback and previous-frame loop: present
trigger: valid command 1 while FAA2 == 0, followed by optional command 7
path: RX validation -> BCD7 reads E000[selector] -> BA26 finalizes TX in F858-F85D -> BE05 can copy F858-F85C back to F850-F854
TX outcome: direct 0x04 readback, then command 7 can retransmit the exact last finalized TX frame
timing/gate: command 1 clears FAA2.7 and never enters continuation handling; it is a readback path, not an ACK path
bench read: command 7 after command 1 proves what was last finalized, but does not prove a hidden continuation token by itself
evidence: H'BCD7, H'BCEC, H'BCFA, H'BA36, H'BA64, H'BE05, H'BE09, H'BE22
- retry/error 07 echo loop: present
trigger: physical RX error or checksum mismatch while FAA5.7 is set and retry count FAA6 is below two
path: RX validation error -> BE29 retry gate -> BE4D stages F850=0x07 and copies RX[1:4] into F851-F854 -> BA26 sends
TX outcome: a 0x07 frame that can echo the host payload bytes, independent of E000/E800 table contents
timing/gate: after two retries, the ROM loads F9C0=0x1F and clears FAA3/FAA2 instead of sending another echo
bench read: visible 07 frames in a noisy/tight timing run can be retry echoes, not necessarily device status or a continuation token
evidence: H'BE29, H'BE2D, H'BE33, H'BE37, H'BE3E, H'BE43, H'BE47, H'BE4D, H'BE6A
- autonomous report to host continuation loop: present
trigger: firmware enqueues a serial-visible report via loc_3E54 with R2.7 set
path: loc_3E54 appends report selector to F870 -> loc_3FD3 allows BAF2 when FAA2/F9C0 gates are clear -> BAF2 sends report -> command 4/5/6 continuation can advance F9B5
TX outcome: autonomous 6-byte report frame built from E800[selector], with FAA2.3/FAA3.7 left set to await host continuation or ACK
timing/gate: F9C0 must count down before BAF2 can send, and F9C6/F9C8/BE9E control repeated sends while FAA3.7 remains live
bench read: the actual ACK/write target is not just the selector; it is the report that is live under FAA2.3 before TXI/RXI or BE9E clears it
evidence: H'3E54, H'3E76, H'3E7A, H'3FD3, H'3FE5, H'3FEB, H'BAF2, H'BB00, H'BB35, H'BB43, H'BB46, H'BB51, H'BD67, H'BDC2, H'BDED
- selector-processing to report loop: present
trigger: command 0/4/selected command 5 calls BE70, or loc_3E54 is called with R2.6 set
path: BE70/F970 selector queue -> loc_2806 selector dispatch -> active-selector side path loc_48FA -> loc_3E54 can enqueue report 0x00F6 when E1EC.13 is set
TX outcome: possible later serial report produced through the F870/BAF2 loop rather than an immediate response to the original RX frame
timing/gate: loc_48FA is gated by FB03.7, F732 values, E1EC.13, and F76E.6 before reaching its indirect table
bench read: selector-zero CONNECT work and visible serial reports can be separated in time; lack of immediate TX does not mean the selector queue did nothing
evidence: H'BE70, H'BE91, H'2806, H'2819, H'2CAD, H'48FA, H'490F, H'4921, H'4923, H'4926
- TXI/RXI overlap cancellation loop: present
trigger: host RX bytes begin while a report TX is active, FAA2.3 is set, and FAA5.7 is set
path: TXI observes FAA2.3 + FAA5.7 + F9C3 != 0 -> clears FAA2.3/FAA3 -> disables TIE -> loads F9C0=0x1F
TX outcome: the pending report/continuation state can be canceled before the host command reaches the continuation dispatcher
timing/gate: this depends on byte timing relative to TXI and F9C3, so polite emulator injection can miss it
bench read: this is a concrete ROM reason why bench timing might see latches/retries while a too-polite emulator reaches cleaner continuation paths
evidence: H'BA84, H'BA8A, H'BA90, H'BA96, H'BA9A, H'BA9E, H'BAA2
- session-expiry to heartbeat/not-active loop: present
trigger: any complete six-byte RX frame loads F9C5, then FRT2 decrements it to zero
path: RX complete -> F9C5=0x14 -> FRT2 decrements F9C5 -> loc_3FEF clears session/queues and calls 400C -> loc_4046 can later enqueue heartbeat selector 0
TX outcome: eventual return to idle heartbeat/report behavior and inactive-session display state
timing/gate: F9C4 gates heartbeat enqueue; BA26 reloads it to 0x07 after each send, matching the roughly 700 ms heartbeat cadence
bench read: the common CONNECT NOT ACT after arbitrary six-byte traffic is consistent with this expiry/reset loop
evidence: H'BB9E, H'BF31, H'BF37, H'3FEF, H'3FFD, H'4003, H'400C, H'4046, H'4067, H'BA31
State Bits:
- H'FAA2.7: rx_command_in_progress_candidate - set on initial-path parse; cleared by command 0/1/2 exits or by continuation cleanup
- H'FAA2.3: queued_report_ack_needed_candidate - set by the autonomous queue send path at BB00; continuation command 4/5/6 can advance F9B5 only when this bit was set
- H'FAA3.7: pending_resend_mask_candidate - set after queued report send; BE9E masks it with FAA5.7 before resend/clear decisions
- H'FAA4.7: rx_physical_error_latch_candidate - set by SCI1 ERI and tested before checksum dispatch
- H'FAA5.7: rx_session_gate_candidate - set while F9C5 is alive after a complete RX frame; gates retry/resend and heartbeat/report enqueue behavior
Bench Implications:
- A standalone command 4 frame from idle should not hit BD0E; command 4 is continuation-only and initial dispatch does not accept it.
- Command 5 is not a generic always-live ACK. It only performs ACK/session-clear work on the continuation path while FAA2 != 0.
- A valid six-byte RX frame loads F9C5 with 0x14, which temporarily sets FAA5.7; when that window expires, loc_3FEF can clear queue/session state and call loc_400C.
- The observed 07 retry/error family can be produced by BE4D from RX[1:4] after error/checksum retry conditions; it is not automatically proof of a table value or a command-4 continuation token.
- To prove CONNECT OK through serial, the bench has to create or preserve FAA2 != 0 at the moment command 4 arrives, and ideally FAA2.3 if it expects queued-report advancement.
Caveats:
- This is an address-driven static branch map, not proof of every runtime predicate value.
- Semantic names are candidates; the branch destinations and RAM side effects are ROM evidence.
- loc_BE70/F970 and loc_3E54/F870 are separate queues; this report names them separately because mixing them up changes the command-4/5 interpretation.

597
h8536/ccu_seed_hints.py Normal file
View File

@@ -0,0 +1,597 @@
from __future__ import annotations
import argparse
import json
from collections import Counter, defaultdict
from collections.abc import Mapping
from pathlib import Path
from typing import Any
from .formatting import h16
from .lcd_text import analyze_lcd_text
from .rom import Rom
from .serial_semantics import OBSERVED_TX_REPORT_OVERLAY
from .table_xrefs import analyze_table_xrefs
JsonObject = dict[str, Any]
DEFAULT_INPUT = Path("build/rom_decompiled.json")
DEFAULT_ROM = Path("ROM/M27C512@DIP28_1.BIN")
CHECKSUM_SEED = 0x5A
DISPLAY_TERMS = (
"CONNECT",
"COMM LINK",
"COMPLETED",
"CAM",
"BARS",
"BLACK",
"IRIS",
"GAIN",
"SHUTTER",
"CALL",
"POWER",
"AUTO",
"DIAG",
"DXC",
)
SPECIAL_SELECTORS: tuple[JsonObject, ...] = (
{
"selector": 0x000,
"name": "connection_or_heartbeat_root_candidate",
"reason": "idle report selector and CONNECT OK emulator condition both center on selector zero",
"seed_values": [0x0080, 0x8080],
},
{
"selector": 0x003,
"name": "default_enabled_bit_candidate",
"reason": "ROM default table writes E000/E800 selector 0x003 to 0x8000",
"seed_values": [0x8000],
},
{
"selector": 0x040,
"name": "default_all_ones_or_status_block_candidate",
"reason": "ROM default table writes E000/E800 selector 0x040 to 0xFFFF and bench tests repeatedly touched the 0x40 family",
"seed_values": [0xFFFF, 0x4030],
},
{
"selector": 0x0F6,
"name": "active_status_bridge_candidate",
"reason": "loc_48FA tests E1EC bit13 and can enqueue report selector 0x00F6",
"seed_values": [0x2000],
},
{
"selector": 0x006C,
"name": "command5_be70_candidate",
"reason": "continuation command 5 calls BE70 for selector 0x006C",
"seed_values": [],
},
{
"selector": 0x006D,
"name": "command5_be70_candidate",
"reason": "continuation command 5 calls BE70 for selector 0x006D",
"seed_values": [],
},
{
"selector": 0x006E,
"name": "command5_be70_candidate",
"reason": "continuation command 5 calls BE70 for selector 0x006E",
"seed_values": [],
},
{
"selector": 0x006B,
"name": "connection_latch_clear_candidate",
"reason": "when F731.7 is set, command 5 on this selector clears F731.7/F790.7",
"seed_values": [],
},
{
"selector": 0x0096,
"name": "connection_latch_clear_candidate",
"reason": "when F731.7 is set, command 5 on this selector clears F731.7/F790.7",
"seed_values": [],
},
{
"selector": 0x0097,
"name": "connection_latch_clear_candidate",
"reason": "when F731.7 is set, command 5 on this selector clears F731.7/F790.7",
"seed_values": [],
},
{
"selector": 0x00C6,
"name": "connection_latch_clear_candidate",
"reason": "when F731.7 is set, command 5 on this selector clears F731.7/F790.7",
"seed_values": [],
},
{
"selector": 0x00F8,
"name": "connection_latch_clear_candidate",
"reason": "when F731.7 is set, command 5 on this selector clears F731.7/F790.7",
"seed_values": [],
},
)
def load_seed_hints_input(path: Path) -> JsonObject:
with path.open("r", encoding="utf-8") as handle:
payload = json.load(handle)
if not isinstance(payload, dict) or "instructions" not in payload:
raise ValueError(f"{path} does not look like h8536_decompiler JSON output")
return payload
def analyze_ccu_seed_hints(payload: Mapping[str, Any], *, rom_path: Path | None = DEFAULT_ROM) -> JsonObject:
table_analysis = analyze_table_xrefs(payload)
selector_hints = _selector_hints_from_tables(table_analysis)
_merge_special_selectors(selector_hints)
_merge_observed_reports(selector_hints)
dispatch = _dispatch_table_summary(payload, rom_path)
for entry in dispatch.get("interesting_entries", []):
selector = int(entry["selector"])
hint = selector_hints.setdefault(selector, _new_selector_hint(selector))
hint["score"] += 2
hint["reasons"].append(f"selector dispatches to {entry['target_label_or_hex']}")
hint["dispatch_target"] = entry
display = _display_hint_summary(payload, rom_path)
seed_plan = _seed_plan(selector_hints)
candidates = sorted(
selector_hints.values(),
key=lambda item: (-int(item["score"]), int(item["selector"])),
)
return {
"kind": "ccu_seed_hints",
"summary": {
"candidate_count": len(candidates),
"core_model": (
"The RCP likely waits for the CCU to seed mirrored state tables, then uses those "
"selector values to update LCD text, panel lamps, and report state changes."
),
"confidence": "medium",
},
"table_model": _table_model_summary(table_analysis),
"selector_candidates": candidates[:80],
"display_text_hints": display,
"dispatch_table": dispatch,
"seed_plan": seed_plan,
"bench_implications": [
"Do not wait for non-heartbeat reports as the only activation source; the CCU may be expected to push initial table state first.",
"Use command 0 writes for initial seeding, then command 1 readbacks for verification. Treat command 4/5/6 as continuation-only until a live report proves otherwise.",
"Selector zero remains the highest-value activation candidate because the emulator reaches CONNECT OK when E000[0]=0x8080 and the selector-zero processing queue runs.",
"E1EC/selector 0x00F6 is a strong follow-up candidate because loc_48FA tests bit13 there and can enqueue report 0x00F6.",
"LCD text terms such as CAM/BARS/BLACK/COMM LINK appear in ROM records, but they are not direct serial payload strings; they point to selector-driven display builders.",
],
"caveats": [
"Selector names are candidates, not confirmed protocol labels.",
"Static table xrefs prove that firmware reads/writes a selector; they do not prove the external CCU must seed it on boot.",
"Generated frames are syntactically valid six-byte host frames; bench safety still depends on timing and current RCP state.",
],
}
def format_text_report(analysis: Mapping[str, Any]) -> str:
summary = analysis["summary"]
lines = [
"H8/536 CCU Seed Hint Report",
"",
f"Summary: {summary['core_model']}",
f"Confidence: {summary['confidence']}",
"",
"Table Model:",
]
for table in analysis.get("table_model", []):
lines.append(
f"- {table['name']}: {table['logical_range_hex']}; accesses={table['access_count']} "
f"static selectors={', '.join(table.get('static_selectors_hex', [])[:12]) or 'none'}"
)
lines.extend(["", "Highest-Value Selector Candidates:"])
for hint in analysis.get("selector_candidates", [])[:24]:
lines.append(
f"- {hint['selector_hex']} {hint['name']}: score={hint['score']} "
f"tables={', '.join(hint.get('tables', [])) or 'none'}"
)
for reason in hint.get("reasons", [])[:4]:
lines.append(f" - {reason}")
frames = hint.get("seed_frames", [])
if frames:
frame_text = "; ".join(f"{frame['value_hex']} -> {frame['cmd0_frame']}" for frame in frames[:3])
lines.append(f" seed frames: {frame_text}")
read_frame = hint.get("cmd1_read_frame")
if read_frame:
lines.append(f" readback frame: {read_frame}")
display = analysis.get("display_text_hints", {})
lines.extend(["", "Display Text Hints:"])
for hit in display.get("term_hits", [])[:16]:
samples = ", ".join(
f"{sample['address_hex']} {sample['text']!r}"
for sample in hit.get("samples", [])[:3]
)
lines.append(f"- {hit['term']}: {hit['hit_count']} hit(s){f' - {samples}' if samples else ''}")
dispatch = analysis.get("dispatch_table", {})
lines.extend(["", "Selector Dispatch Hints:"])
lines.append(
f"- table {dispatch.get('table_base_hex', 'unknown')}: "
f"{dispatch.get('interesting_count', 0)} non-default/interesting entries"
)
for entry in dispatch.get("interesting_entries", [])[:16]:
lines.append(
f" - selector {entry['selector_hex']} -> {entry['target_label_or_hex']} "
f"(dispatch index {entry['dispatch_index_hex']})"
)
seed_plan = analysis.get("seed_plan", {})
lines.extend(["", "Candidate Fake-CCU Seed Plan:"])
for step in seed_plan.get("steps", []):
lines.append(f"- {step['name']}: {step['frame']}")
lines.append(f" {step['why']}")
lines.extend(["", "Bench Implications:"])
for item in analysis.get("bench_implications", []):
lines.append(f"- {item}")
lines.extend(["", "Caveats:"])
for item in analysis.get("caveats", []):
lines.append(f"- {item}")
return "\n".join(lines).rstrip() + "\n"
def write_ccu_seed_hints(
input_path: Path,
output_path: Path,
*,
rom_path: Path | None = DEFAULT_ROM,
as_json: bool = False,
) -> JsonObject:
analysis = analyze_ccu_seed_hints(load_seed_hints_input(input_path), rom_path=rom_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
if as_json:
output_path.write_text(json.dumps(analysis, indent=2, sort_keys=True) + "\n", encoding="utf-8")
else:
output_path.write_text(format_text_report(analysis), encoding="utf-8")
return analysis
def main(argv: list[str] | None = None, stdout: Any | None = None) -> int:
parser = argparse.ArgumentParser(description="Mine ROM hints for CCU-to-RCP state seeding candidates.")
parser.add_argument("input", nargs="?", type=Path, default=DEFAULT_INPUT)
parser.add_argument("--rom", type=Path, default=DEFAULT_ROM, help="ROM binary used for LCD text and dispatch-table mining")
parser.add_argument("--json", action="store_true", help="emit structured JSON instead of readable text")
parser.add_argument("--out", type=Path, default=None, help="write report to this path")
args = parser.parse_args(argv)
stream = stdout
if stream is None:
import sys
stream = sys.stdout
rom_path = args.rom if args.rom and args.rom.exists() else None
analysis = analyze_ccu_seed_hints(load_seed_hints_input(args.input), rom_path=rom_path)
rendered = json.dumps(analysis, indent=2, sort_keys=True) + "\n" if args.json else format_text_report(analysis)
if args.out:
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text(rendered, encoding="utf-8")
print(f"wrote {args.out}", file=stream)
else:
print(rendered, end="", file=stream)
return 0
def encode_host_frame(command: int, selector: int, value: int = 0) -> list[int]:
byte1, byte2 = selector_bytes(selector)
frame = [command & 0x07, byte1, byte2, (value >> 8) & 0xFF, value & 0xFF]
frame.append(checksum(frame))
return frame
def frame_hex(frame: list[int]) -> str:
return " ".join(f"{byte & 0xFF:02X}" for byte in frame)
def selector_bytes(selector: int) -> tuple[int, int]:
selector &= 0x01FF
if selector <= 0x007F:
return 0x00, selector
if selector <= 0x017F:
return 0x01, selector - 0x0080
return 0x02, selector - 0x0180
def checksum(frame_without_checksum: list[int]) -> int:
value = CHECKSUM_SEED
for byte in frame_without_checksum[:5]:
value ^= byte & 0xFF
return value & 0xFF
def _selector_hints_from_tables(table_analysis: Mapping[str, Any]) -> dict[int, JsonObject]:
hints: dict[int, JsonObject] = {}
for table in table_analysis.get("tables", []):
if not isinstance(table, Mapping):
continue
table_name = str(table.get("name", "unknown_table"))
element = str(table.get("element_candidate", ""))
for access in table.get("accesses", []):
if not isinstance(access, Mapping) or not isinstance(access.get("offset"), int):
continue
selector = _selector_from_offset(int(access["offset"]), element)
if selector is None:
continue
hint = hints.setdefault(selector, _new_selector_hint(selector))
hint["score"] += _access_score(access, table_name)
if table_name not in hint["tables"]:
hint["tables"].append(table_name)
hint["accesses"].append(
{
"address_hex": access.get("instruction_address_hex"),
"function": access.get("function_label"),
"table": table_name,
"access": access.get("access"),
"instruction": access.get("instruction"),
}
)
reason = _access_reason(access, table_name)
if reason not in hint["reasons"]:
hint["reasons"].append(reason)
return hints
def _merge_special_selectors(hints: dict[int, JsonObject]) -> None:
for item in SPECIAL_SELECTORS:
selector = int(item["selector"])
hint = hints.setdefault(selector, _new_selector_hint(selector))
hint["score"] += 5
hint["name"] = str(item["name"])
hint["reasons"].append(str(item["reason"]))
for value in item.get("seed_values", []):
_add_seed_value(hint, int(value))
def _merge_observed_reports(hints: dict[int, JsonObject]) -> None:
for report in OBSERVED_TX_REPORT_OVERLAY:
selector = int(report["logical_index"])
hint = hints.setdefault(selector, _new_selector_hint(selector))
hint["score"] += 3
hint["name"] = str(report["name_candidate"])
frames = ", ".join(str(frame) for frame in report.get("observed_frames_hex", []))
hint["reasons"].append(f"observed RCP autonomous report frame(s): {frames}")
def _seed_plan(hints: Mapping[int, JsonObject]) -> JsonObject:
planned = [
(0x000, 0x8080, "selector zero active/connect candidate from emulator state search"),
(0x003, 0x8000, "ROM default state also sets selector 0x003 high bit"),
(0x040, 0xFFFF, "ROM default all-ones/status candidate touched by bench 0x40 family"),
(0x0F6, 0x2000, "sets E1EC bit13 candidate used by loc_48FA report bridge"),
]
steps: list[JsonObject] = []
for selector, value, why in planned:
frame = frame_hex(encode_host_frame(0x00, selector, value))
readback = frame_hex(encode_host_frame(0x01, selector, 0))
hint = hints.get(selector)
if hint is not None:
_add_seed_value(hint, value)
steps.append(
{
"selector": selector,
"selector_hex": f"0x{selector:03X}",
"value": value,
"value_hex": f"0x{value:04X}",
"name": f"cmd0 seed selector 0x{selector:03X} = 0x{value:04X}",
"frame": frame,
"readback_frame": readback,
"why": why,
}
)
return {
"model": "candidate initial CCU state push using command 0 writes, verified with command 1 reads",
"steps": steps,
}
def _table_model_summary(table_analysis: Mapping[str, Any]) -> list[JsonObject]:
rows: list[JsonObject] = []
for table in table_analysis.get("tables", []):
if not isinstance(table, Mapping):
continue
element = str(table.get("element_candidate", ""))
selectors = []
for offset in table.get("static_offsets", []):
if isinstance(offset, int):
selector = _selector_from_offset(offset, element)
if selector is not None:
selectors.append(selector)
rows.append(
{
"name": table.get("name"),
"logical_range_hex": f"{table.get('logical_base_address_hex')}-{table.get('logical_range_end_hex')}",
"access_count": table.get("access_count", 0),
"static_selectors": sorted(set(selectors)),
"static_selectors_hex": [f"0x{selector:03X}" for selector in sorted(set(selectors))],
}
)
return rows
def _display_hint_summary(payload: Mapping[str, Any], rom_path: Path | None) -> JsonObject:
del payload
if rom_path is None or not rom_path.exists():
return {"term_hits": [], "note": "ROM binary was not available for LCD text mining."}
rom = Rom(rom_path.read_bytes())
text = analyze_lcd_text(rom, None, search_terms=DISPLAY_TERMS, max_candidates=360)
term_hits = []
for search in text.get("searches", []):
if not isinstance(search, Mapping):
continue
samples = []
for address in search.get("literal_hits", [])[:4]:
if isinstance(address, int):
samples.append({"address_hex": h16(address), "text": f"literal {search.get('term')}"})
for hit in search.get("candidate_hits", [])[:6]:
if not isinstance(hit, Mapping):
continue
samples.append(
{
"address_hex": h16(int(hit.get("address", 0))),
"text": str(hit.get("trimmed") or hit.get("text") or ""),
}
)
hit_count = len(search.get("literal_hits", []) or []) + len(search.get("candidate_hits", []) or [])
term_hits.append({"term": search.get("term"), "hit_count": hit_count, "samples": samples[:8]})
return {
"term_hits": term_hits,
"regions": text.get("regions", [])[:8],
"note": "Text hits are ROM display resources, not literal serial payloads.",
}
def _dispatch_table_summary(payload: Mapping[str, Any], rom_path: Path | None) -> JsonObject:
table_base = 0x28A6
entries = _indirect_entries(payload, table_base)
if not entries and rom_path is not None and rom_path.exists():
entries = _raw_dispatch_entries(rom_path, table_base, 128)
if not entries:
return {"table_base": table_base, "table_base_hex": h16(table_base), "interesting_count": 0, "interesting_entries": []}
target_counts = Counter(int(entry["target"]) for entry in entries if isinstance(entry.get("target"), int))
default_target, _ = target_counts.most_common(1)[0]
interesting: list[JsonObject] = []
for entry in entries:
index = int(entry["index"])
selector = _selector_from_dispatch_index(index)
if selector is None:
continue
target = int(entry["target"])
label = entry.get("target_label") or h16(target)
decoded = bool(entry.get("decoded_code", True))
if target == default_target and decoded:
continue
interesting.append(
{
"selector": selector,
"selector_hex": f"0x{selector:03X}",
"dispatch_index": index,
"dispatch_index_hex": f"0x{index:03X}",
"entry_address_hex": h16(int(entry.get("entry_address", table_base + index * 2))),
"target": target,
"target_hex": h16(target),
"target_label_or_hex": str(label),
"decoded_code": decoded,
}
)
return {
"table_base": table_base,
"table_base_hex": h16(table_base),
"entry_count": len(entries),
"default_target_hex": h16(default_target),
"interesting_count": len(interesting),
"interesting_entries": interesting[:80],
}
def _indirect_entries(payload: Mapping[str, Any], table_base: int) -> list[JsonObject]:
indirect = payload.get("indirect_flow")
if not isinstance(indirect, Mapping):
return []
for site in indirect.get("sites", []):
if not isinstance(site, Mapping):
continue
table = site.get("table")
if isinstance(table, Mapping) and int(table.get("base", -1)) == table_base:
entries = table.get("entries", [])
if isinstance(entries, list):
return [dict(entry) for entry in entries if isinstance(entry, Mapping)]
return []
def _raw_dispatch_entries(rom_path: Path, table_base: int, count: int) -> list[JsonObject]:
rom = Rom(rom_path.read_bytes())
entries = []
for index in range(count):
address = table_base + index * 2
if not rom.contains(address, 2):
break
target = rom.u16(address)
entries.append({"index": index, "entry_address": address, "target": target, "target_label": None, "decoded_code": True})
return entries
def _selector_from_dispatch_index(index: int) -> int | None:
if 0 <= index <= 0x007F:
return index
if 0x0100 <= index <= 0x01FF:
return index - 0x0080
if 0x0200 <= index <= 0x027F:
return index - 0x0080
return None
def _selector_from_offset(offset: int, element: str) -> int | None:
if element == "word_value":
if offset % 2:
return None
return (offset // 2) & 0x01FF
return offset & 0x01FF
def _new_selector_hint(selector: int) -> JsonObject:
return {
"selector": selector,
"selector_hex": f"0x{selector:03X}",
"name": "state_selector_candidate",
"score": 0,
"tables": [],
"reasons": [],
"accesses": [],
"seed_frames": [],
"cmd1_read_frame": frame_hex(encode_host_frame(0x01, selector, 0)),
}
def _add_seed_value(hint: JsonObject, value: int) -> None:
frames = hint.setdefault("seed_frames", [])
value_hex = f"0x{value & 0xFFFF:04X}"
if any(frame.get("value_hex") == value_hex for frame in frames):
return
frames.append(
{
"value": value & 0xFFFF,
"value_hex": value_hex,
"cmd0_frame": frame_hex(encode_host_frame(0x00, int(hint["selector"]), value)),
}
)
def _access_score(access: Mapping[str, Any], table_name: str) -> int:
score = 1
if access.get("access") == "read":
score += 1
if access.get("access") == "write":
score += 1
if "primary" in table_name or "current" in table_name:
score += 1
return score
def _access_reason(access: Mapping[str, Any], table_name: str) -> str:
function = access.get("function_label") or "<no function>"
instruction = access.get("instruction") or ""
return f"{table_name} {access.get('access')} in {function}: {instruction}"
__all__ = [
"analyze_ccu_seed_hints",
"checksum",
"encode_host_frame",
"format_text_report",
"frame_hex",
"load_seed_hints_input",
"main",
"selector_bytes",
"write_ccu_seed_hints",
]

730
h8536/eeprom_layout.py Normal file
View File

@@ -0,0 +1,730 @@
from __future__ import annotations
import argparse
import json
from collections import Counter
from collections.abc import Iterable, Mapping
from pathlib import Path
from typing import Any
from .emulator.peripherals.x24164 import (
X24164_FACTORY_DEFAULT_BASE,
X24164_FACTORY_DEFAULT_BYTES,
X24164_LOGICAL_PAGE_COUNT,
X24164_LOGICAL_PAGE_SIZE,
factory_default_words_from_rom,
)
from .formatting import h16, label_for
from .rom import Rom
JsonObject = dict[str, Any]
DEFAULT_INPUT = Path("build/rom_decompiled.json")
DEFAULT_ROM = Path("ROM/M27C512@DIP28_1.BIN")
SHADOW_BASE = 0xF400
SHADOW_SIZE = 0x0100
SELECTOR_MAP_BASE = 0xC564
SELECTOR_MAP_COUNT = 0x0200
RECORD_RAM_BASE = 0xF7B0
RECORD_BYTES = 8
STATE_BYTES: tuple[tuple[int, str], ...] = (
(0xF402, "factory_signature_word"),
(0xF404, "feature_or_option_flags_candidate"),
(0xF730, "connect_display_state_candidate"),
(0xF731, "session_latch_candidate"),
(0xF732, "display_dispatch_selector_candidate"),
(0xF76E, "eeprom_page_and_persist_flags"),
(0xF790, "connection_latch_shadow_candidate"),
(0xF791, "feature_flag_gate_candidate"),
(0xFB03, "report_bridge_suppress_candidate"),
)
def load_eeprom_layout_input(path: Path) -> JsonObject:
with path.open("r", encoding="utf-8") as handle:
payload = json.load(handle)
if not isinstance(payload, dict) or "instructions" not in payload:
raise ValueError(f"{path} does not look like h8536_decompiler JSON output")
return payload
def analyze_eeprom_layout(payload: Mapping[str, Any], *, rom_path: Path | None = DEFAULT_ROM) -> JsonObject:
rom = _load_rom(rom_path)
factory_entries = _factory_entries(rom)
selector_map = _selector_map_entries(rom, factory_entries)
xrefs = _xref_summary(payload)
return {
"kind": "eeprom_layout",
"summary": {
"confidence": "medium-high",
"model": (
"The ROM treats the traced P9 bus as two X24164-style EEPROM banks, "
"mirrors a 0x100-byte factory/default block into F400-F4FF, and loads "
"sixteen 8-byte persistent records into F7B0-F82F at boot."
),
"factory_default_count": len(factory_entries),
"selector_persistence_mapping_count": len(selector_map),
"persistent_record_count": X24164_LOGICAL_PAGE_COUNT,
},
"physical_model": _physical_model(),
"boot_flow": _boot_flow(),
"factory_defaults": {
"rom_base": X24164_FACTORY_DEFAULT_BASE,
"rom_base_hex": h16(X24164_FACTORY_DEFAULT_BASE),
"byte_count": X24164_FACTORY_DEFAULT_BYTES,
"shadow_base": SHADOW_BASE,
"shadow_base_hex": h16(SHADOW_BASE),
"shadow_range_hex": f"{h16(SHADOW_BASE)}-{h16(SHADOW_BASE + SHADOW_SIZE - 1)}",
"entries": factory_entries,
"notable_entries": _notable_factory_entries(factory_entries, xrefs, selector_map),
},
"persistent_records": _persistent_records(factory_entries),
"serial_persistence_mapping": {
"table_base": SELECTOR_MAP_BASE,
"table_base_hex": h16(SELECTOR_MAP_BASE),
"entry_count": SELECTOR_MAP_COUNT,
"mapped_entry_count": len(selector_map),
"entries": selector_map,
"offset_histogram": _offset_histogram(selector_map),
"high_byte_histogram": _high_byte_histogram(selector_map),
"address_formula": (
"command 4 persists to EEPROM address "
"(((F76E & 0x0F) << 8) | (mapping_low_byte & 0xFE)) when F76E.7 is set"
),
},
"xrefs": xrefs,
"state_byte_hints": _state_byte_hints(factory_entries, xrefs),
"bench_implications": [
"A live EEPROM dump should first compare F400-F4FF shadow-equivalent offsets against the ROM factory table, especially F402/F404 and mapped offsets.",
"Pages 0x0-0xF offset 0x00-0x07 are loaded into F7B0-F82F; page 0 carries the signature/options header and pages 1-F default to spaces, so non-space bytes there are high-value identity/config data.",
"Serial command 0/4 can mirror values into F400 offsets selected by the ROM mapping table, but command 4 only persists when the continuation path reaches BD2B and F76E.7 is set.",
"F76E is not just a page byte: bit7 gates EEPROM persistence, bit6 suppresses the 48FA dispatch path, and only its low nibble survives into the EEPROM page address.",
"CONNECT OK is still likely gated by volatile table/session state as well as EEPROM-backed defaults; EEPROM differences may explain why bench and emulator diverge, but are unlikely to be the whole protocol by themselves.",
],
"caveats": [
"The selector map proves where firmware mirrors/persists serial values, not what every field means to the panel UI.",
"High bytes in the C564 selector map look structured, but the observed command-0/command-4 paths only use the low byte for F400/EEPROM offsets.",
"Indexed F7B0-F82F record consumers can be missed by a static xref pass; dynamic emulator traces should be used once interesting record bytes are found.",
],
}
def format_text_report(analysis: Mapping[str, Any]) -> str:
summary = analysis["summary"]
lines = [
"H8/536 EEPROM Layout Report",
"",
f"Summary: {summary['model']}",
f"Confidence: {summary['confidence']}",
"",
"Physical / Logical Model:",
]
physical = analysis.get("physical_model", {})
for row in physical.get("banks", []):
lines.append(
f"- {row['name']}: logical {row['logical_range_hex']} control {row['control_write_hex']}/{row['control_read_hex']}"
)
lines.append(f"- bus: {physical.get('bus', 'unknown')}")
lines.append(f"- page model: {physical.get('page_model', 'unknown')}")
lines.extend(["", "Boot Flow:"])
for step in analysis.get("boot_flow", []):
lines.append(f"- {step['address_hex']} {step['name']}: {step['summary']}")
defaults = analysis.get("factory_defaults", {})
lines.extend(["", "Factory Shadow Block:"])
lines.append(
f"- ROM {defaults.get('rom_base_hex')} length 0x{int(defaults.get('byte_count', 0)):02X} "
f"mirrors to {defaults.get('shadow_range_hex')}"
)
for entry in defaults.get("notable_entries", [])[:32]:
details = []
if entry.get("ascii"):
details.append(f"ascii={entry['ascii']!r}")
if entry.get("mapped_selectors_hex"):
details.append(f"selectors={', '.join(entry['mapped_selectors_hex'][:8])}")
if entry.get("xref_count"):
details.append(f"xrefs={entry['xref_count']}")
suffix = f" ({'; '.join(details)})" if details else ""
lines.append(
f"- {entry['shadow_address_hex']} offset {entry['offset_hex']} "
f"default {entry['factory_word_hex']}{suffix}"
)
lines.extend(["", "Persistent 8-Byte Records:"])
records = analysis.get("persistent_records", [])
if records:
first = records[0]
last = records[-1]
lines.append(
f"- {len(records)} records: EEPROM {first['eeprom_range_hex']} .. {last['eeprom_range_hex']} "
f"load into RAM {first['ram_range_hex']} .. {last['ram_range_hex']}"
)
for record in records[:16]:
lines.append(
f" - record {record['record_index_hex']}: EEPROM {record['eeprom_range_hex']} "
f"-> RAM {record['ram_range_hex']} default {record['default_text']!r}"
)
mapping = analysis.get("serial_persistence_mapping", {})
lines.extend(["", "Serial Selector -> Shadow/EEPROM Mapping:"])
lines.append(
f"- table {mapping.get('table_base_hex')} has {mapping.get('mapped_entry_count', 0)} nonzero low-byte mappings"
)
lines.append(f"- formula: {mapping.get('address_formula')}")
for entry in mapping.get("entries", [])[:80]:
lines.append(
f" - selector {entry['selector_hex']} map_word={entry['mapping_word_hex']} "
f"-> {entry['shadow_address_hex']} page+{entry['eeprom_word_offset_hex']} "
f"default={entry.get('factory_word_hex', 'unknown')}"
)
omitted = int(mapping.get("mapped_entry_count", 0)) - min(80, len(mapping.get("entries", [])))
if omitted > 0:
lines.append(f" - ... {omitted} more mapped selectors omitted")
lines.extend(["", "State Byte Hints:"])
for hint in analysis.get("state_byte_hints", []):
lines.append(
f"- {hint['address_hex']} {hint['name']}: {hint['summary']} "
f"(xrefs={hint['xref_count']})"
)
lines.extend(["", "Bench Implications:"])
for item in analysis.get("bench_implications", []):
lines.append(f"- {item}")
lines.extend(["", "Caveats:"])
for item in analysis.get("caveats", []):
lines.append(f"- {item}")
return "\n".join(lines).rstrip() + "\n"
def write_eeprom_layout(
input_path: Path,
output_path: Path,
*,
rom_path: Path | None = DEFAULT_ROM,
as_json: bool = False,
) -> JsonObject:
analysis = analyze_eeprom_layout(load_eeprom_layout_input(input_path), rom_path=rom_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
if as_json:
output_path.write_text(json.dumps(analysis, indent=2, sort_keys=True) + "\n", encoding="utf-8")
else:
output_path.write_text(format_text_report(analysis), encoding="utf-8")
return analysis
def main(argv: list[str] | None = None, stdout: Any | None = None) -> int:
parser = argparse.ArgumentParser(description="Mine ROM-backed X24164 EEPROM layout and persistence hints.")
parser.add_argument("input", nargs="?", type=Path, default=DEFAULT_INPUT)
parser.add_argument("--rom", type=Path, default=DEFAULT_ROM, help="ROM binary to mine")
parser.add_argument("--json", action="store_true", help="emit structured JSON instead of readable text")
parser.add_argument("--out", type=Path, default=None, help="write report to this path")
args = parser.parse_args(argv)
stream = stdout
if stream is None:
import sys
stream = sys.stdout
rom_path = args.rom if args.rom and args.rom.exists() else None
analysis = analyze_eeprom_layout(load_eeprom_layout_input(args.input), rom_path=rom_path)
rendered = json.dumps(analysis, indent=2, sort_keys=True) + "\n" if args.json else format_text_report(analysis)
if args.out:
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text(rendered, encoding="utf-8")
print(f"wrote {args.out}", file=stream)
else:
print(rendered, end="", file=stream)
return 0
def _load_rom(path: Path | None) -> Rom | None:
if path is None or not path.exists():
return None
return Rom(path.read_bytes())
def _physical_model() -> JsonObject:
return {
"bus": "P91/SCL and P97/SDA bit-banged two-wire bus through ROM routines C121/C08B/C0DB/C10C/C142",
"page_size": X24164_LOGICAL_PAGE_SIZE,
"page_count": X24164_LOGICAL_PAGE_COUNT,
"page_model": "16 logical pages of 0x100 bytes; low 8 address bits are sent as the EEPROM word address",
"banks": [
{
"name": "lower_x24164_candidate",
"logical_range": [0x0000, 0x07FF],
"logical_range_hex": "0x000-0x7FF",
"control_write": 0xA0,
"control_write_hex": "A0",
"control_read": 0xA1,
"control_read_hex": "A1",
},
{
"name": "upper_x24164_candidate",
"logical_range": [0x0800, 0x0FFF],
"logical_range_hex": "0x800-0xFFF",
"control_write": 0xE0,
"control_write_hex": "E0",
"control_read": 0xE1,
"control_read_hex": "E1",
},
],
}
def _boot_flow() -> list[JsonObject]:
return [
{
"address": 0x40BB,
"address_hex": h16(0x40BB),
"name": "eeprom_boot_gate",
"summary": "initializes queue/table scratch, checks P7DR.7, then checks F402 == H'6B6F before trusting persisted state",
},
{
"address": 0x4103,
"address_hex": h16(0x4103),
"name": "factory_default_fill",
"summary": "copies ROM C964-CA63 into F400-F4FF and writes the same 0x100-byte defaults to each EEPROM page",
},
{
"address": 0x4187,
"address_hex": h16(0x4187),
"name": "record_header_blank",
"summary": "overwrites page offsets 0x00-0x07 on pages 0x1-0xF with four H'2020 words after factory replication; page 0 keeps the signature/options header",
},
{
"address": 0x41D2,
"address_hex": h16(0x41D2),
"name": "persistent_record_load",
"summary": "reads page offsets 0x00-0x07 from pages 0x0-0xF into F7B0-F82F; record 0 is a signature/options header, records 1-F are label/identity-like slots",
},
{
"address": 0xBD2B,
"address_hex": h16(0xBD2B),
"name": "serial_persist_path",
"summary": "command-4 continuation writes the live value into F400+map[selector] and persists it through BFE0 when F76E.7 is set",
},
]
def _factory_entries(rom: Rom | None) -> list[JsonObject]:
if rom is None:
return []
entries = []
for offset, word in factory_default_words_from_rom(rom.data):
shadow_address = SHADOW_BASE + offset
bytes_pair = [(word >> 8) & 0xFF, word & 0xFF]
entries.append(
{
"offset": offset,
"offset_hex": f"0x{offset:02X}",
"rom_address": X24164_FACTORY_DEFAULT_BASE + offset,
"rom_address_hex": h16(X24164_FACTORY_DEFAULT_BASE + offset),
"shadow_address": shadow_address,
"shadow_address_hex": h16(shadow_address),
"factory_word": word,
"factory_word_hex": f"0x{word:04X}",
"bytes_hex": " ".join(f"{byte:02X}" for byte in bytes_pair),
"ascii": _word_ascii(word),
"default_eeprom_word_after_record_blank": 0x2020 if offset < RECORD_BYTES else word,
"default_eeprom_word_after_record_blank_hex": "0x2020" if offset < RECORD_BYTES else f"0x{word:04X}",
}
)
return entries
def _selector_map_entries(rom: Rom | None, factory_entries: list[JsonObject]) -> list[JsonObject]:
if rom is None:
return []
defaults_by_offset = {int(entry["offset"]): entry for entry in factory_entries}
entries: list[JsonObject] = []
for selector in range(SELECTOR_MAP_COUNT):
address = SELECTOR_MAP_BASE + selector * 2
if not rom.contains(address, 2):
break
word = rom.u16(address)
low = word & 0xFF
if low == 0:
continue
aligned = low & 0xFE
shadow_address = SHADOW_BASE + low
aligned_shadow_address = SHADOW_BASE + aligned
default = defaults_by_offset.get(aligned)
entry: JsonObject = {
"selector": selector,
"selector_hex": f"0x{selector:03X}",
"entry_address": address,
"entry_address_hex": h16(address),
"mapping_word": word,
"mapping_word_hex": f"0x{word:04X}",
"mapping_high_byte": (word >> 8) & 0xFF,
"mapping_high_byte_hex": f"0x{(word >> 8) & 0xFF:02X}",
"mapping_low_byte": low,
"mapping_low_byte_hex": f"0x{low:02X}",
"shadow_offset": low,
"shadow_offset_hex": f"0x{low:02X}",
"shadow_address": shadow_address,
"shadow_address_hex": h16(shadow_address),
"aligned_shadow_address": aligned_shadow_address,
"aligned_shadow_address_hex": h16(aligned_shadow_address),
"eeprom_word_offset": aligned,
"eeprom_word_offset_hex": f"0x{aligned:02X}",
"command0_shadow_mirror": True,
"command4_shadow_mirror": True,
"command4_persist_when_f76e_bit7": True,
}
if default is not None:
entry["factory_word"] = default["factory_word"]
entry["factory_word_hex"] = default["factory_word_hex"]
entry["factory_ascii"] = default["ascii"]
entries.append(entry)
return entries
def _notable_factory_entries(
factory_entries: list[JsonObject],
xrefs: Mapping[str, Any],
selector_map: list[JsonObject],
) -> list[JsonObject]:
direct_counts: Counter[int] = Counter()
for item in xrefs.get("direct_xrefs", []):
if not isinstance(item, Mapping) or not isinstance(item.get("address"), int):
continue
address = int(item["address"])
if SHADOW_BASE <= address < SHADOW_BASE + SHADOW_SIZE:
direct_counts[address & 0xFF] += 1
selectors_by_offset: dict[int, list[str]] = {}
for entry in selector_map:
selectors_by_offset.setdefault(int(entry["eeprom_word_offset"]), []).append(str(entry["selector_hex"]))
notable = []
for entry in factory_entries:
offset = int(entry["offset"])
word = int(entry["factory_word"])
mapped = selectors_by_offset.get(offset, [])
xref_count = direct_counts.get(offset, 0)
if word in (0x0000, 0xFFFF) and not mapped and not xref_count and offset not in (0x02, 0x04):
continue
item = dict(entry)
item["xref_count"] = xref_count
item["mapped_selectors_hex"] = mapped[:24]
notable.append(item)
return notable
def _persistent_records(factory_entries: list[JsonObject]) -> list[JsonObject]:
factory_by_offset = {int(entry["offset"]): int(entry["factory_word"]) for entry in factory_entries}
records = []
for index in range(X24164_LOGICAL_PAGE_COUNT):
eeprom_base = index * X24164_LOGICAL_PAGE_SIZE
ram_base = RECORD_RAM_BASE + index * RECORD_BYTES
default_words = [
factory_by_offset.get(offset, 0xFFFF) if index == 0 else 0x2020
for offset in range(0, RECORD_BYTES, 2)
]
default_bytes = bytearray()
for word in default_words:
default_bytes.extend([(word >> 8) & 0xFF, word & 0xFF])
records.append(
{
"record_index": index,
"record_index_hex": f"0x{index:X}",
"eeprom_base": eeprom_base,
"eeprom_base_hex": f"0x{eeprom_base:03X}",
"eeprom_range_hex": f"0x{eeprom_base:03X}-0x{eeprom_base + RECORD_BYTES - 1:03X}",
"ram_base": ram_base,
"ram_base_hex": h16(ram_base),
"ram_range_hex": f"{h16(ram_base)}-{h16(ram_base + RECORD_BYTES - 1)}",
"word_offsets": [0, 2, 4, 6],
"default_words": default_words,
"default_words_hex": [f"0x{word:04X}" for word in default_words],
"default_bytes_hex": default_bytes.hex(" ").upper(),
"default_text": _ascii(default_bytes),
"role_candidate": "page-0 signature/options header" if index == 0 else "8-byte persistent label/identity slot",
}
)
return records
def _offset_histogram(entries: Iterable[Mapping[str, Any]]) -> list[JsonObject]:
counts = Counter(int(entry["eeprom_word_offset"]) for entry in entries)
return [
{"offset": offset, "offset_hex": f"0x{offset:02X}", "selector_count": count}
for offset, count in sorted(counts.items())
]
def _high_byte_histogram(entries: Iterable[Mapping[str, Any]]) -> list[JsonObject]:
counts = Counter(int(entry["mapping_high_byte"]) for entry in entries)
return [
{"high_byte": value, "high_byte_hex": f"0x{value:02X}", "selector_count": count}
for value, count in sorted(counts.items())
]
def _state_byte_hints(factory_entries: list[JsonObject], xrefs: Mapping[str, Any]) -> list[JsonObject]:
defaults = {int(entry["shadow_address"]): entry for entry in factory_entries}
refs_by_address: Counter[int] = Counter()
examples_by_address: dict[int, list[JsonObject]] = {}
for item in xrefs.get("direct_xrefs", []):
if not isinstance(item, Mapping) or not isinstance(item.get("address"), int):
continue
address = int(item["address"])
refs_by_address[address] += 1
examples_by_address.setdefault(address, []).append(dict(item))
hints = []
for address, name in STATE_BYTES:
default = defaults.get(address)
summary = _state_summary(address, default)
hints.append(
{
"address": address,
"address_hex": h16(address),
"name": name,
"summary": summary,
"factory_word_hex": default.get("factory_word_hex") if default else None,
"xref_count": refs_by_address.get(address, 0),
"xrefs": examples_by_address.get(address, [])[:12],
}
)
return hints
def _state_summary(address: int, default: Mapping[str, Any] | None) -> str:
factory = f"factory {default['factory_word_hex']}" if default else "volatile/no factory word"
if address == 0xF402:
return f"{factory}; boot accepts persisted state only when this word is H'6B6F"
if address == 0xF404:
return f"{factory}; bits 1-4 are tested with F791 gates in display/status routines"
if address == 0xF76E:
return "bit7 enables command-4 EEPROM persistence, bit6 suppresses 48FA dispatch, low nibble selects EEPROM page"
if address == 0xF791:
return "volatile gate tested alongside F404 option bits before setting report/display flags"
if address == 0xF732:
return "volatile display dispatch selector feeding the 493E pointer table and 48FA report bridge"
return factory
def _xref_summary(payload: Mapping[str, Any]) -> JsonObject:
instructions = _instruction_sequence(payload.get("instructions"))
functions = _function_ranges(payload)
direct = []
dynamic = []
for ins in instructions:
for ref in _references(ins):
if _is_interesting_address(ref):
direct.append(_xref_item(ins, ref, functions))
operands = str(ins.get("operands", ""))
dynamic_kind = _dynamic_kind(operands)
if dynamic_kind:
item = _base_instruction_item(ins, functions)
item["kind"] = dynamic_kind
item["operand"] = operands
dynamic.append(item)
return {
"direct_xref_count": len(direct),
"dynamic_xref_count": len(dynamic),
"direct_xrefs": direct,
"dynamic_xrefs": dynamic,
}
def _xref_item(ins: Mapping[str, Any], address: int, functions: list[JsonObject]) -> JsonObject:
item = _base_instruction_item(ins, functions)
item.update(
{
"address": address,
"address_hex": h16(address),
"region": _region_for_address(address),
"access": _access_direction(ins, address) or "read_write_candidate",
}
)
return item
def _base_instruction_item(ins: Mapping[str, Any], functions: list[JsonObject]) -> JsonObject:
address = int(ins["address"])
function = _function_for_address(functions, address)
item: JsonObject = {
"instruction_address": address,
"instruction_address_hex": h16(address),
"mnemonic": str(ins.get("mnemonic", "")),
"operands": str(ins.get("operands", "")),
"instruction": str(ins.get("text") or _instruction_text(ins)),
}
if function:
item["function_start"] = function["start"]
item["function_start_hex"] = h16(int(function["start"]))
item["function_label"] = function["label"]
return item
def _is_interesting_address(address: int) -> bool:
return (
SHADOW_BASE <= address < SHADOW_BASE + SHADOW_SIZE
or RECORD_RAM_BASE <= address < RECORD_RAM_BASE + X24164_LOGICAL_PAGE_COUNT * RECORD_BYTES
or any(address == item[0] for item in STATE_BYTES)
)
def _region_for_address(address: int) -> str:
if SHADOW_BASE <= address < SHADOW_BASE + SHADOW_SIZE:
return "f400_shadow_defaults"
if RECORD_RAM_BASE <= address < RECORD_RAM_BASE + X24164_LOGICAL_PAGE_COUNT * RECORD_BYTES:
return "persistent_record_ram"
return "state_or_gate_ram"
def _dynamic_kind(operands: str) -> str | None:
patterns = (
("@(-H'0C00", "indexed_f400_shadow_access"),
("@(-H'0850", "indexed_persistent_record_store"),
("@(-H'084E", "indexed_persistent_record_store"),
("@(-H'084C", "indexed_persistent_record_store"),
("@(-H'084A", "indexed_persistent_record_store"),
("@(-H'3A9C", "selector_to_shadow_mapping_word"),
("@(-H'3A9B", "selector_to_shadow_mapping_low_byte"),
)
upper = operands.upper()
for pattern, kind in patterns:
if pattern in upper:
return kind
return None
def _function_ranges(payload: Mapping[str, Any]) -> list[JsonObject]:
call_graph = payload.get("call_graph")
if not isinstance(call_graph, Mapping):
return []
nodes = call_graph.get("nodes")
if not isinstance(nodes, list):
return []
ranges: list[JsonObject] = []
for node in nodes:
if not isinstance(node, Mapping):
continue
start = node.get("start")
end = node.get("end")
if isinstance(start, int) and isinstance(end, int):
ranges.append({"start": start, "end": end, "label": str(node.get("label") or label_for(start))})
return sorted(ranges, key=lambda item: int(item["start"]))
def _function_for_address(functions: list[JsonObject], address: int) -> JsonObject | None:
for function in functions:
if int(function["start"]) <= address <= int(function["end"]):
return function
return None
def _instruction_sequence(value: object) -> list[JsonObject]:
if isinstance(value, Mapping):
values: Iterable[Any] = value.values()
elif isinstance(value, list):
values = value
else:
values = []
return sorted(
[item for item in values if isinstance(item, dict) and isinstance(item.get("address"), int)],
key=lambda item: int(item["address"]),
)
def _references(ins: Mapping[str, Any]) -> list[int]:
refs = ins.get("references", [])
if not isinstance(refs, list):
return []
output: list[int] = []
for ref in refs:
if isinstance(ref, Mapping) and isinstance(ref.get("address"), int):
output.append(int(ref["address"]))
elif isinstance(ref, int):
output.append(ref)
return output
def _access_direction(ins: Mapping[str, Any], address: int) -> str | None:
root = _mnemonic_root(str(ins.get("mnemonic", "")))
if root in {"BTST", "CMP", "CMP:E", "CMP:G", "CMP:I", "MOVFPE", "TST"}:
return "read"
if root in {"BCLR", "BNOT", "BSET", "CLR", "INC", "INC:G", "NEG", "NOT"}:
return "write"
if root in {"ADD:Q", "ADD:G", "ADDS", "ADDX", "AND", "OR", "SUB", "SUBS", "SUBX", "XOR"}:
return "write"
if root in {"MOV:G", "MOV:S", "MOVTPE"}:
source, destination = _source_destination_operands(str(ins.get("operands", "")))
if _operand_mentions_address(destination, address):
return "write"
if _operand_mentions_address(source, address):
return "read"
return None
def _source_destination_operands(operands: str) -> tuple[str, str]:
depth = 0
split_at: int | None = None
for index, char in enumerate(operands):
if char in "({":
depth += 1
elif char in ")}" and depth:
depth -= 1
elif char == "," and depth == 0:
split_at = index
if split_at is None:
operand = operands.strip()
return "", operand
return operands[:split_at].strip(), operands[split_at + 1 :].strip()
def _operand_mentions_address(operand: str, address: int) -> bool:
operand_upper = operand.upper().replace(" ", "")
negative = (0x10000 - address) & 0xFFFF
return (
f"H'{address:04X}" in operand_upper
or f"0X{address:04X}" in operand_upper
or f"${address:04X}" in operand_upper
or f"-H'{negative:04X}" in operand_upper
or f"-0X{negative:04X}" in operand_upper
or f"-${negative:04X}" in operand_upper
)
def _instruction_text(ins: Mapping[str, Any]) -> str:
operands = str(ins.get("operands", ""))
return f"{ins.get('mnemonic', '')} {operands}".strip()
def _mnemonic_root(mnemonic: str) -> str:
return mnemonic.rsplit(".", 1)[0].upper()
def _word_ascii(word: int) -> str:
chars = [(word >> 8) & 0xFF, word & 0xFF]
if all(0x20 <= byte <= 0x7E for byte in chars):
return "".join(chr(byte) for byte in chars)
if any(0x20 <= byte <= 0x7E for byte in chars):
return "".join(chr(byte) if 0x20 <= byte <= 0x7E else "." for byte in chars)
return ""
def _ascii(data: bytes | bytearray) -> str:
return "".join(chr(value) if 0x20 <= value <= 0x7E else "." for value in data)
__all__ = [
"analyze_eeprom_layout",
"format_text_report",
"load_eeprom_layout_input",
"main",
"write_eeprom_layout",
]

View File

@@ -4,6 +4,7 @@ import argparse
from pathlib import Path
from ..formatting import h16, parse_int
from .eeprom_image import write_eeprom_snapshot
from .memory import describe_regions
from .runner import H8536Emulator
@@ -47,6 +48,11 @@ def build_arg_parser() -> argparse.ArgumentParser:
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("--p7-input", type=parse_int, default=0xFF, help="external P7 pin state for input bits; DIP-off board default is 0xFF")
parser.add_argument("--eeprom-seed", choices=("blank", "factory"), default="blank", help="initial X24164/shadow state before reset")
parser.add_argument("--eeprom-load", type=Path, help="load a 0x1000-byte logical EEPROM image before running")
parser.add_argument("--eeprom-save", type=Path, help="save the final 0x1000-byte logical EEPROM image after running")
parser.add_argument("--eeprom-report", type=Path, help="write a readable EEPROM snapshot report after running")
parser.add_argument("--eeprom-report-json", type=Path, help="write a structured EEPROM snapshot report after running")
parser.add_argument("--eeprom-report-include-image", action="store_true", help="include the full EEPROM image as hex in JSON reports")
return parser
@@ -70,6 +76,9 @@ def main(argv: list[str] | None = None) -> int:
p7_input=args.p7_input,
eeprom_seed=args.eeprom_seed,
)
if args.eeprom_load:
emulator.memory.load_eeprom_image(args.eeprom_load.read_bytes())
print(f"eeprom_loaded={args.eeprom_load}")
print(f"rom={rom_path}")
print(f"reset_vector={h16(emulator.reset_vector())}")
if args.memory_map:
@@ -82,4 +91,20 @@ def main(argv: list[str] | None = None) -> int:
print(line)
if not report.heartbeat_seen:
print("heartbeat_status=not reached; no heartbeat is reported unless bytes are emitted via SCI1_TDR")
if args.eeprom_save:
args.eeprom_save.parent.mkdir(parents=True, exist_ok=True)
args.eeprom_save.write_bytes(emulator.memory.dump_eeprom_image())
print(f"eeprom_saved={args.eeprom_save}")
if args.eeprom_report:
write_eeprom_snapshot(emulator.memory, args.eeprom_report, rom_bytes=rom_bytes)
print(f"eeprom_report={args.eeprom_report}")
if args.eeprom_report_json:
write_eeprom_snapshot(
emulator.memory,
args.eeprom_report_json,
rom_bytes=rom_bytes,
as_json=True,
include_image_hex=args.eeprom_report_include_image,
)
print(f"eeprom_report_json={args.eeprom_report_json}")
return 0

View File

@@ -0,0 +1,351 @@
from __future__ import annotations
import hashlib
import json
from collections import defaultdict
from collections.abc import Mapping
from pathlib import Path
from typing import Any
from ..formatting import h16
from .memory import MemoryMap
from .peripherals.x24164 import (
X24164_FACTORY_DEFAULT_BYTES,
X24164_LOGICAL_PAGE_COUNT,
X24164_LOGICAL_PAGE_SIZE,
X24164_LOGICAL_SIZE,
factory_default_words_from_rom,
)
JsonObject = dict[str, Any]
SELECTOR_MAP_BASE = 0xC564
SELECTOR_MAP_COUNT = 0x0200
RECORD_BYTES = 8
SHADOW_BASE = 0xF400
def build_eeprom_snapshot(
memory: MemoryMap,
*,
rom_bytes: bytes | None = None,
include_image_hex: bool = False,
) -> JsonObject:
image = memory.dump_eeprom_image()
selectors_by_offset = _selectors_by_offset(rom_bytes)
factory_image = _factory_image(rom_bytes)
write_events = _write_events(memory, selectors_by_offset)
write_words = _coalesced_write_words(memory, selectors_by_offset)
factory_diffs = _factory_diffs(image, factory_image, selectors_by_offset)
report: JsonObject = {
"kind": "emulator_eeprom_snapshot",
"summary": {
"logical_size": len(image),
"logical_size_hex": f"0x{len(image):04X}",
"sha256": hashlib.sha256(image).hexdigest(),
"write_byte_count": len(write_events),
"write_word_count": len(write_words),
"factory_diff_word_count": len(factory_diffs),
"record_count": X24164_LOGICAL_PAGE_COUNT,
},
"records": _records(image),
"write_events": write_events,
"write_word_events": write_words,
"factory_diffs": factory_diffs,
"shadow_f400": _shadow_summary(memory, rom_bytes, selectors_by_offset),
}
if include_image_hex:
report["image_hex"] = image.hex()
return report
def format_eeprom_snapshot(report: Mapping[str, Any], *, limit: int = 80) -> str:
summary = report["summary"]
lines = [
"Emulator EEPROM Snapshot",
"",
f"size={summary['logical_size_hex']} sha256={summary['sha256']}",
(
f"writes: bytes={summary['write_byte_count']} words={summary['write_word_count']} "
f"factory_diff_words={summary['factory_diff_word_count']}"
),
"",
"Persistent Records:",
]
for record in report.get("records", []):
lines.append(
f"- page {record['page_hex']} EEPROM {record['range_hex']} "
f"bytes={record['bytes_hex']} text={record['ascii']!r}"
)
lines.extend(["", "EEPROM Word Writes:"])
word_events = list(report.get("write_word_events", []))
if not word_events:
lines.append("- none since EEPROM setup/load")
for event in word_events[:limit]:
suffix = _event_suffix(event)
lines.append(
f"- {event['address_hex']} page={event['page_hex']} offset={event['offset_hex']} "
f"{event['old_word_hex']}->{event['new_word_hex']} source={event['source']}{suffix}"
)
if len(word_events) > limit:
lines.append(f"- ... {len(word_events) - limit} more word writes omitted")
lines.extend(["", "Factory Diffs:"])
diffs = list(report.get("factory_diffs", []))
if not diffs:
lines.append("- current EEPROM image matches ROM factory/default image")
for diff in diffs[:limit]:
suffix = _event_suffix(diff)
lines.append(
f"- {diff['address_hex']} page={diff['page_hex']} offset={diff['offset_hex']} "
f"expected={diff['expected_word_hex']} actual={diff['actual_word_hex']}{suffix}"
)
if len(diffs) > limit:
lines.append(f"- ... {len(diffs) - limit} more factory diffs omitted")
shadow = report.get("shadow_f400", {})
lines.extend(["", "F400 Shadow Diffs:"])
shadow_diffs = list(shadow.get("diffs", [])) if isinstance(shadow, Mapping) else []
if not shadow_diffs:
lines.append("- F400-F4FF shadow matches ROM factory words or no ROM factory baseline was supplied")
for diff in shadow_diffs[:limit]:
suffix = _event_suffix(diff)
lines.append(
f"- {diff['address_hex']} offset={diff['offset_hex']} "
f"expected={diff['expected_word_hex']} actual={diff['actual_word_hex']}{suffix}"
)
if len(shadow_diffs) > limit:
lines.append(f"- ... {len(shadow_diffs) - limit} more shadow diffs omitted")
return "\n".join(lines).rstrip() + "\n"
def write_eeprom_snapshot(
memory: MemoryMap,
output_path: Path,
*,
rom_bytes: bytes | None = None,
as_json: bool = False,
include_image_hex: bool = False,
) -> JsonObject:
report = build_eeprom_snapshot(memory, rom_bytes=rom_bytes, include_image_hex=include_image_hex)
output_path.parent.mkdir(parents=True, exist_ok=True)
if as_json:
output_path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
else:
output_path.write_text(format_eeprom_snapshot(report), encoding="utf-8")
return report
def _write_events(memory: MemoryMap, selectors_by_offset: Mapping[int, list[int]]) -> list[JsonObject]:
events = []
for index, event in enumerate(memory.p9_bus.x24164_bus.write_events):
item = _address_info(event.logical_address, selectors_by_offset)
item.update(
{
"index": index,
"device": event.device,
"device_offset": event.device_offset,
"device_offset_hex": f"0x{event.device_offset:03X}",
"old_value": event.old_value,
"old_value_hex": f"0x{event.old_value & 0xFF:02X}",
"new_value": event.new_value,
"new_value_hex": f"0x{event.new_value & 0xFF:02X}",
"source": event.source,
}
)
events.append(item)
return events
def _coalesced_write_words(memory: MemoryMap, selectors_by_offset: Mapping[int, list[int]]) -> list[JsonObject]:
events = memory.p9_bus.x24164_bus.write_events
words: list[JsonObject] = []
index = 0
while index < len(events):
event = events[index]
next_event = events[index + 1] if index + 1 < len(events) else None
if (
next_event is not None
and (event.logical_address & 1) == 0
and next_event.logical_address == ((event.logical_address + 1) & 0x0FFF)
and next_event.device == event.device
and next_event.source == event.source
):
old_word = ((event.old_value & 0xFF) << 8) | (next_event.old_value & 0xFF)
new_word = ((event.new_value & 0xFF) << 8) | (next_event.new_value & 0xFF)
item = _address_info(event.logical_address, selectors_by_offset)
item.update(
{
"index": index,
"device": event.device,
"old_word": old_word,
"old_word_hex": f"0x{old_word:04X}",
"new_word": new_word,
"new_word_hex": f"0x{new_word:04X}",
"source": event.source,
}
)
words.append(item)
index += 2
else:
index += 1
return words
def _factory_diffs(
image: bytes,
factory_image: bytes | None,
selectors_by_offset: Mapping[int, list[int]],
) -> list[JsonObject]:
if factory_image is None:
return []
diffs = []
for address in range(0, min(len(image), len(factory_image)), 2):
expected = (factory_image[address] << 8) | factory_image[address + 1]
actual = (image[address] << 8) | image[address + 1]
if expected == actual:
continue
item = _address_info(address, selectors_by_offset)
item.update(
{
"expected_word": expected,
"expected_word_hex": f"0x{expected:04X}",
"actual_word": actual,
"actual_word_hex": f"0x{actual:04X}",
}
)
diffs.append(item)
return diffs
def _shadow_summary(
memory: MemoryMap,
rom_bytes: bytes | None,
selectors_by_offset: Mapping[int, list[int]],
) -> JsonObject:
if rom_bytes is None:
return {"diffs": [], "note": "no ROM factory baseline supplied"}
factory_words = dict(factory_default_words_from_rom(rom_bytes))
diffs = []
for offset in range(0, X24164_FACTORY_DEFAULT_BYTES, 2):
expected = factory_words[offset]
address = SHADOW_BASE + offset
high = memory.external.get(address & 0xFFFF, 0xFF)
low = memory.external.get((address + 1) & 0xFFFF, 0xFF)
actual = ((high & 0xFF) << 8) | (low & 0xFF)
if expected == actual:
continue
item = _address_info(offset, selectors_by_offset)
item.update(
{
"address": address,
"address_hex": h16(address),
"expected_word": expected,
"expected_word_hex": f"0x{expected:04X}",
"actual_word": actual,
"actual_word_hex": f"0x{actual:04X}",
}
)
diffs.append(item)
return {"diffs": diffs, "diff_count": len(diffs)}
def _records(image: bytes) -> list[JsonObject]:
records = []
for page in range(X24164_LOGICAL_PAGE_COUNT):
base = page * X24164_LOGICAL_PAGE_SIZE
data = image[base : base + RECORD_BYTES]
records.append(
{
"page": page,
"page_hex": f"0x{page:X}",
"address": base,
"address_hex": f"0x{base:03X}",
"range_hex": f"0x{base:03X}-0x{base + RECORD_BYTES - 1:03X}",
"bytes_hex": data.hex(" ").upper(),
"words_hex": [
f"0x{((data[index] << 8) | data[index + 1]):04X}"
for index in range(0, len(data), 2)
],
"ascii": _ascii(data),
"is_blank_spaces": data == (b" " * RECORD_BYTES),
}
)
return records
def _factory_image(rom_bytes: bytes | None) -> bytes | None:
if rom_bytes is None:
return None
image = bytearray([0xFF] * X24164_LOGICAL_SIZE)
for offset, word in factory_default_words_from_rom(rom_bytes):
for page in range(X24164_LOGICAL_PAGE_COUNT):
address = (page * X24164_LOGICAL_PAGE_SIZE) + offset
image[address] = (word >> 8) & 0xFF
image[address + 1] = word & 0xFF
for page in range(1, X24164_LOGICAL_PAGE_COUNT):
base = page * X24164_LOGICAL_PAGE_SIZE
for offset in range(0, RECORD_BYTES, 2):
image[base + offset] = 0x20
image[base + offset + 1] = 0x20
return bytes(image)
def _selectors_by_offset(rom_bytes: bytes | None) -> dict[int, list[int]]:
if rom_bytes is None:
return {}
result: dict[int, list[int]] = defaultdict(list)
for selector in range(SELECTOR_MAP_COUNT):
address = SELECTOR_MAP_BASE + selector * 2
if address + 1 >= len(rom_bytes):
break
low = rom_bytes[address + 1]
if low:
result[low & 0xFE].append(selector)
return dict(result)
def _address_info(address: int, selectors_by_offset: Mapping[int, list[int]]) -> JsonObject:
address &= 0x0FFF
page = (address // X24164_LOGICAL_PAGE_SIZE) & 0x0F
offset = address & 0xFF
aligned_offset = offset & 0xFE
selectors = selectors_by_offset.get(aligned_offset, [])
return {
"address": address,
"address_hex": f"0x{address:03X}",
"page": page,
"page_hex": f"0x{page:X}",
"offset": offset,
"offset_hex": f"0x{offset:02X}",
"aligned_offset": aligned_offset,
"aligned_offset_hex": f"0x{aligned_offset:02X}",
"record_byte": offset if offset < RECORD_BYTES else None,
"role": "record_header_or_label" if offset < RECORD_BYTES else "factory_shadow_offset",
"mapped_selectors": selectors[:24],
"mapped_selectors_hex": [f"0x{selector:03X}" for selector in selectors[:24]],
}
def _event_suffix(event: Mapping[str, Any]) -> str:
parts = []
if event.get("role"):
parts.append(str(event["role"]))
selectors = event.get("mapped_selectors_hex")
if isinstance(selectors, list) and selectors:
parts.append("selectors=" + ",".join(str(item) for item in selectors[:6]))
return f" ({'; '.join(parts)})" if parts else ""
def _ascii(data: bytes) -> str:
return "".join(chr(value) if 0x20 <= value <= 0x7E else "." for value in data)
__all__ = [
"build_eeprom_snapshot",
"format_eeprom_snapshot",
"write_eeprom_snapshot",
]

View File

@@ -161,6 +161,20 @@ class MemoryMap:
self.p9_bus.x24164_bus.seed_factory_defaults_from_rom(self.rom.data)
self.p9_bus.clear_x24164_trace()
def load_eeprom_image(self, data: bytes | bytearray, *, mirror_shadow: bool = True) -> None:
self.p9_bus.x24164_bus.load_linear(data)
if mirror_shadow:
image = self.p9_bus.x24164_bus.dump_linear()
for offset in range(min(0x0100, len(image))):
self.external[(0xF400 + offset) & 0xFFFF] = image[offset]
self.p9_bus.clear_x24164_trace()
def dump_eeprom_image(self) -> bytes:
return self.p9_bus.x24164_bus.dump_linear()
def clear_eeprom_write_log(self) -> None:
self.p9_bus.x24164_bus.clear_write_log()
def _set_register(self, address: int, value: int) -> None:
self.registers[address - REGISTER_FIELD_START] = value & 0xFF

View File

@@ -2,7 +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, factory_default_words_from_rom
from .x24164 import X24164Bus, X24164Device, X24164TraceEvent, X24164WriteEvent, factory_default_words_from_rom
__all__ = [
"LCD_E_CLOCK_DATA",
@@ -17,5 +17,6 @@ __all__ = [
"X24164Bus",
"X24164Device",
"X24164TraceEvent",
"X24164WriteEvent",
"factory_default_words_from_rom",
]

View File

@@ -4,6 +4,7 @@ from dataclasses import dataclass, field
X24164_SIZE = 2048
X24164_LOGICAL_SIZE = 4096
X24164_FACTORY_DEFAULT_BASE = 0xC964
X24164_FACTORY_DEFAULT_BYTES = 0x0100
X24164_LOGICAL_PAGE_SIZE = 0x0100
@@ -72,12 +73,30 @@ class X24164TraceEvent:
return " ".join(parts)
@dataclass(frozen=True)
class X24164WriteEvent:
logical_address: int
device: str
device_offset: int
old_value: int
new_value: int
source: str
def line(self) -> str:
return (
f"addr={self.logical_address & 0x0FFF:03X} device={self.device} "
f"offset={self.device_offset & (X24164_SIZE - 1):03X} "
f"{self.old_value & 0xFF:02X}->{self.new_value & 0xFF:02X} source={self.source}"
)
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.write_events: list[X24164WriteEvent] = []
self.active = False
self.phase = "idle"
self.selected: X24164Device | None = None
@@ -197,6 +216,18 @@ class X24164Bus:
)
return True, value
def read_linear_byte(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, 0xFF
offset = address & (X24164_SIZE - 1)
value = device.read(offset)
self.trace_events.append(X24164TraceEvent("x24164_linear_read_byte", device.name, value=value, address=offset))
return True, value
def write_linear_word(self, address: int, value: int) -> bool:
device = self._device_for_linear_address(address)
if device is None:
@@ -205,8 +236,8 @@ class X24164Bus:
)
return False
offset = address & (X24164_SIZE - 1)
device.write(offset, (value >> 8) & 0xFF)
device.write((offset + 1) & (X24164_SIZE - 1), value & 0xFF)
self._write_device_byte(device, offset, (value >> 8) & 0xFF, source="linear_word")
self._write_device_byte(device, (offset + 1) & (X24164_SIZE - 1), value & 0xFF, source="linear_word")
self.trace_events.append(
X24164TraceEvent(
"x24164_linear_write_word",
@@ -218,20 +249,61 @@ class X24164Bus:
)
return True
def write_linear_byte(self, address: int, value: int, *, source: str = "linear_byte") -> 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)
self._write_device_byte(device, offset, value, source=source)
self.trace_events.append(X24164TraceEvent("x24164_linear_write_byte", device.name, value=value & 0xFF, address=offset))
return True
def dump_linear(self) -> bytes:
data = bytearray()
for address in range(X24164_LOGICAL_SIZE):
device = self._device_for_linear_address(address)
if device is None:
data.append(0xFF)
else:
data.append(device.read(address & (X24164_SIZE - 1)))
return bytes(data)
def load_linear(self, data: bytes | bytearray, *, fill: int = 0xFF) -> None:
if len(data) > X24164_LOGICAL_SIZE:
raise ValueError(f"EEPROM image is too large: {len(data)} > {X24164_LOGICAL_SIZE}")
padded = bytearray([fill & 0xFF] * X24164_LOGICAL_SIZE)
padded[: len(data)] = data
for address, value in enumerate(padded):
device = self._device_for_linear_address(address)
if device is not None:
device.write(address & (X24164_SIZE - 1), value)
self.clear_write_log()
def clear_write_log(self) -> None:
self.write_events.clear()
def seed_factory_defaults_from_rom(self, rom_bytes: bytes) -> None:
for offset, word in factory_default_words_from_rom(rom_bytes):
for page in range(X24164_LOGICAL_PAGE_COUNT):
self.write_linear_word((page * X24164_LOGICAL_PAGE_SIZE) + offset, word)
for page in range(X24164_LOGICAL_PAGE_COUNT):
for page in range(1, X24164_LOGICAL_PAGE_COUNT):
base = page * X24164_LOGICAL_PAGE_SIZE
for offset in range(0, 8, 2):
self.write_linear_word(base + offset, 0x2020)
self.clear_write_log()
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 write_log_lines(self, limit: int | None = None) -> list[str]:
events = self.write_events if limit is None else self.write_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
@@ -327,7 +399,7 @@ class X24164Bus:
self._ack_armed_on_current_clock = True
self.phase = "ignore"
return
self.selected.write(self.address, value)
self._write_device_byte(self.selected, self.address, value, source="bit_banged")
self.trace_events.append(
X24164TraceEvent("x24164_write_data", self.selected.name, value=value, address=self.address, ack=True)
)
@@ -365,6 +437,24 @@ class X24164Bus:
return device
return None
def _linear_base_for_device(self, device: X24164Device) -> int:
return 0x0800 if device.control_base == 0xE0 else 0x0000
def _write_device_byte(self, device: X24164Device, offset: int, value: int, *, source: str) -> None:
offset &= X24164_SIZE - 1
old_value = device.read(offset)
device.write(offset, value)
self.write_events.append(
X24164WriteEvent(
logical_address=(self._linear_base_for_device(device) + offset) & 0x0FFF,
device=device.name,
device_offset=offset,
old_value=old_value,
new_value=value & 0xFF,
source=source,
)
)
def _selected_name(self) -> str | None:
return self.selected.name if self.selected is not None else None

View File

@@ -15,6 +15,7 @@ from .constants import (
SCI_SSR_RDRF,
VECTOR_SCI1_RXI,
)
from .eeprom_image import write_eeprom_snapshot
from .errors import UnsupportedInstruction
from .memory import MemoryAccess
from .runner import H8536Emulator
@@ -216,6 +217,7 @@ def run_rx_probe(
p9_fast_optimistic_wrapper: bool = False,
p7_input: int = 0xFF,
eeprom_seed: str = "blank",
eeprom_image: bytes | None = None,
stop_after_tx_frame: bool = True,
) -> tuple[Path, H8536Emulator, str, list[FrameResult]]:
rom_bytes, discovered_rom_path = load_rom(rom_path)
@@ -231,6 +233,8 @@ def run_rx_probe(
p7_input=p7_input,
eeprom_seed=eeprom_seed,
)
if eeprom_image is not None:
emulator.memory.load_eeprom_image(eeprom_image)
boot_context = RunContext()
boot_steps_used, boot_reason = _run_until(emulator, boot_steps, _rx_ready, boot_context)
@@ -277,6 +281,11 @@ def build_arg_parser() -> argparse.ArgumentParser:
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("--p7-input", type=parse_int, default=0xFF, help="external P7 pin state for input bits; DIP-off board default is 0xFF")
parser.add_argument("--eeprom-seed", choices=("blank", "factory"), default="blank", help="initial X24164/shadow state before reset")
parser.add_argument("--eeprom-load", type=Path, help="load a 0x1000-byte logical EEPROM image before booting the ROM")
parser.add_argument("--eeprom-save", type=Path, help="save the final 0x1000-byte logical EEPROM image after probing")
parser.add_argument("--eeprom-report", type=Path, help="write a readable EEPROM snapshot report after probing")
parser.add_argument("--eeprom-report-json", type=Path, help="write a structured EEPROM snapshot report after probing")
parser.add_argument("--eeprom-report-include-image", action="store_true", help="include the full EEPROM image as hex in JSON reports")
return parser
@@ -305,16 +314,35 @@ def main(argv: list[str] | None = None) -> int:
p9_fast_optimistic_wrapper=args.p9_fast_optimistic_wrapper,
p7_input=args.p7_input,
eeprom_seed=args.eeprom_seed,
eeprom_image=args.eeprom_load.read_bytes() if args.eeprom_load else None,
stop_after_tx_frame=not args.keep_listening,
)
print(f"rom={rom_path}")
if args.eeprom_load:
print(f"eeprom_loaded={args.eeprom_load}")
print(f"reset_vector={h16(emulator.reset_vector())}")
print(boot_summary)
for index, result in enumerate(results):
for line in result.lines(index):
print(line)
print("total_tx_frames=" + " | ".join(format_frame(frame) for frame in emulator.sci1.tx_frames))
if args.eeprom_save:
args.eeprom_save.parent.mkdir(parents=True, exist_ok=True)
args.eeprom_save.write_bytes(emulator.memory.dump_eeprom_image())
print(f"eeprom_saved={args.eeprom_save}")
if args.eeprom_report:
write_eeprom_snapshot(emulator.memory, args.eeprom_report, rom_bytes=emulator.memory.rom.data)
print(f"eeprom_report={args.eeprom_report}")
if args.eeprom_report_json:
write_eeprom_snapshot(
emulator.memory,
args.eeprom_report_json,
rom_bytes=emulator.memory.rom.data,
as_json=True,
include_image_hex=args.eeprom_report_include_image,
)
print(f"eeprom_report_json={args.eeprom_report_json}")
return 0

View File

@@ -0,0 +1,393 @@
from __future__ import annotations
import argparse
import itertools
import json
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, TextIO
from ..formatting import h16, parse_int
from .cli import load_rom
from .errors import UnsupportedInstruction
from .runner import H8536Emulator
from .rx_probe import RunContext, _run_until, _rx_ready
CONNECT_WORDS = (0x0000, 0x0080, 0x4080, 0x8080, 0xC080)
F730_LATCH_VALUES = (0x00, 0x01, 0x41, 0x81, 0xC1)
PRESET_DESCRIPTIONS = {
"connect-branch": "Start at loc_2CB9 with E000[0]/F730 matrix patches.",
"connect-queue": "Queue selector zero in F970, start at loc_2806, then enter loc_2CB9 through the ROM dispatch.",
"custom": "Use only --byte/--word/--matrix-* patches and --pc.",
}
@dataclass(frozen=True)
class StatePatch:
size: int
address: int
value: int
source: str = "user"
def label(self) -> str:
kind = "byte" if self.size == 1 else "word"
width = 2 if self.size == 1 else 4
return f"{kind}:{h16(self.address)}=0x{self.value:0{width}X}"
@dataclass(frozen=True)
class SearchCase:
patches: tuple[StatePatch, ...]
pc: int
def name(self) -> str:
return ", ".join(patch.label() for patch in self.patches)
@dataclass(frozen=True)
class SearchResult:
case_index: int
patches: tuple[StatePatch, ...]
pc: int
steps: int
stopped_reason: str
final_pc: int
display: str
line0: str
outcome: str
f730: int
e000: int
f9b4: int
f9b9: int
unsupported: str | None = None
def payload(self) -> dict[str, object]:
return {
"case_index": self.case_index,
"patches": [patch.label() for patch in self.patches],
"pc": h16(self.pc),
"steps": self.steps,
"stopped_reason": self.stopped_reason,
"final_pc": h16(self.final_pc),
"display": self.display,
"line0": self.line0,
"outcome": self.outcome,
"f730": f"0x{self.f730:02X}",
"e000": f"0x{self.e000:04X}",
"f9b4": f"0x{self.f9b4:02X}",
"f9b9": f"0x{self.f9b9:02X}",
"unsupported": self.unsupported,
}
def build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description=(
"Patch internal emulator state and search for CONNECT LCD outcomes. "
"This is bounded ROM execution, not an unbounded serial fuzzer."
)
)
parser.add_argument("--preset", choices=sorted(PRESET_DESCRIPTIONS), default="connect-queue")
parser.add_argument("--rom", type=Path, help="ROM image path; defaults to ROM/M27C512@DIP28_1.BIN when present")
parser.add_argument("--pc", type=parse_int, help="entry PC for custom/direct tests")
parser.add_argument("--stop-pc", type=parse_int, default=0xFFFF, help="sentinel PC used to stop after RTS")
parser.add_argument("--stack", type=parse_int, default=0xFF00, help="temporary stack pointer for direct function entry")
parser.add_argument("--boot-steps", type=int, default=250_000, help="steps to boot before patching state")
parser.add_argument("--max-steps", type=int, default=120_000, help="steps to run each patched case")
parser.add_argument("--clock-hz", type=parse_int, default=10_000_000)
parser.add_argument("--interval-steps", type=int, default=512)
parser.add_argument("--frt1-ocia-steps", type=int, default=None)
parser.add_argument("--frt2-ocia-steps", type=int, default=None)
parser.add_argument("--no-p9-fast-path", action="store_true")
parser.add_argument("--p9-fast-input", type=parse_int, default=0xFF)
parser.add_argument("--p9-fast-optimistic-wrapper", action="store_true")
parser.add_argument("--p7-input", type=parse_int, default=0xFF)
parser.add_argument("--eeprom-seed", choices=("blank", "factory"), default="blank")
parser.add_argument("--byte", action="append", default=[], help="fixed byte patch, e.g. F730=0")
parser.add_argument("--word", action="append", default=[], help="fixed word patch, e.g. E000=0x8080")
parser.add_argument("--matrix-byte", action="append", default=[], help="byte matrix patch, e.g. F730=0,1,0x41")
parser.add_argument("--matrix-word", action="append", default=[], help="word matrix patch, e.g. E000=0x4080,0x8080")
parser.add_argument("--target", choices=("ok", "dxc", "not-act", "any-connect", "changed"), default="ok")
parser.add_argument("--first-hit", action="store_true", help="stop after the first target hit")
parser.add_argument("--show-all", action="store_true", help="print every case, not only hits/non-baseline outcomes")
parser.add_argument("--limit", type=int, help="maximum number of cases to run")
parser.add_argument("--json-out", type=Path, help="write machine-readable results")
parser.add_argument("--dry-run", action="store_true", help="print planned cases without running the emulator")
return parser
def main(argv: list[str] | None = None, *, stdout: TextIO = sys.stdout) -> int:
args = build_arg_parser().parse_args(argv)
cases = build_cases(args)
if args.limit is not None:
cases = cases[: max(0, args.limit)]
if args.dry_run:
print(f"preset={args.preset} cases={len(cases)}", file=stdout)
print(PRESET_DESCRIPTIONS[args.preset], file=stdout)
for index, case in enumerate(cases[:32]):
print(f"case[{index}] pc={h16(case.pc)} {case.name()}", file=stdout)
if len(cases) > 32:
print(f"... {len(cases) - 32} more cases", file=stdout)
return 0
results = run_search(args, cases)
print(format_results(results, target=args.target, show_all=args.show_all), file=stdout)
if args.json_out:
write_json(args.json_out, args, cases, results)
return 0
def build_cases(args: argparse.Namespace) -> list[SearchCase]:
fixed = []
fixed.extend(parse_patch_list(args.byte, size=1, source="user"))
fixed.extend(parse_patch_list(args.word, size=2, source="user"))
matrix_groups: list[list[StatePatch]] = []
if args.preset == "connect-branch":
matrix_groups.extend(
[
[StatePatch(2, 0xE000, value, "preset") for value in CONNECT_WORDS],
[StatePatch(1, 0xF730, value, "preset") for value in F730_LATCH_VALUES],
]
)
default_pc = 0x2CB9
elif args.preset == "connect-queue":
fixed.extend(
[
StatePatch(1, 0xF9B9, 0x00, "preset"),
StatePatch(1, 0xF9B4, 0x01, "preset"),
StatePatch(2, 0xF970, 0x0000, "preset"),
]
)
matrix_groups.extend(
[
[StatePatch(2, 0xE000, value, "preset") for value in CONNECT_WORDS],
[StatePatch(1, 0xF730, value, "preset") for value in F730_LATCH_VALUES],
]
)
default_pc = 0x2806
else:
default_pc = 0x2CB9
matrix_groups.extend(parse_matrix_list(args.matrix_byte, size=1, source="user"))
matrix_groups.extend(parse_matrix_list(args.matrix_word, size=2, source="user"))
pc = args.pc if args.pc is not None else default_pc
if not matrix_groups:
return [SearchCase(tuple(fixed), pc)]
return [
SearchCase(tuple(fixed + list(combo)), pc)
for combo in itertools.product(*matrix_groups)
]
def run_search(args: argparse.Namespace, cases: Iterable[SearchCase]) -> list[SearchResult]:
rom_bytes, _rom_path = load_rom(args.rom)
results: list[SearchResult] = []
for index, case in enumerate(cases):
emulator = H8536Emulator(
rom_bytes,
interval_steps=args.interval_steps,
frt1_ocia_steps=args.frt1_ocia_steps,
frt2_ocia_steps=args.frt2_ocia_steps,
clock_hz=args.clock_hz,
p9_fast_path_enabled=not args.no_p9_fast_path,
p9_fast_default_input_byte=args.p9_fast_input,
p9_fast_default_wrapper_success=args.p9_fast_optimistic_wrapper,
p7_input=args.p7_input,
eeprom_seed=args.eeprom_seed,
)
_run_until(emulator, args.boot_steps, _rx_ready, RunContext())
result = run_case(emulator, case, max_steps=args.max_steps, stop_pc=args.stop_pc, stack=args.stack)
results.append(SearchResult(index, case.patches, case.pc, *result))
if args.first_hit and target_matches(results[-1].outcome, args.target):
break
return results
def run_case(
emulator: H8536Emulator,
case: SearchCase,
*,
max_steps: int,
stop_pc: int,
stack: int,
) -> tuple[int, str, int, str, str, str, int, int, int, int, str | None]:
for patch in case.patches:
apply_patch(emulator, patch)
emulator.cpu.sr |= 0x0700
emulator.cpu.regs[7] = stack & 0xFFFF
emulator.memory.write16(stack, stop_pc)
emulator.cpu.pc = case.pc & 0xFFFF
stopped_reason = "max_steps"
unsupported: str | None = None
steps = 0
for index in range(max(0, max_steps)):
if emulator.cpu.pc == (stop_pc & 0xFFFF):
stopped_reason = "stop_pc"
break
try:
emulator.step()
except UnsupportedInstruction as exc:
stopped_reason = "unsupported_instruction"
unsupported = str(exc)
break
steps = index + 1
display = emulator.memory.lcd.display_text(lines=4, width=16)
line0 = emulator.memory.lcd.line_text(0)
outcome = classify_display(display)
return (
steps,
stopped_reason,
emulator.cpu.pc,
display,
line0,
outcome,
emulator.memory.read8(0xF730),
emulator.memory.read16(0xE000),
emulator.memory.read8(0xF9B4),
emulator.memory.read8(0xF9B9),
unsupported,
)
def apply_patch(emulator: H8536Emulator, patch: StatePatch) -> None:
if patch.size == 1:
emulator.memory.write8(patch.address, patch.value)
elif patch.size == 2:
emulator.memory.write16(patch.address, patch.value)
else:
raise ValueError(f"unsupported patch size {patch.size}")
def classify_display(display: str) -> str:
if "CONNECT: OK" in display:
return "ok"
if "CONNECT:DXC-637" in display:
return "dxc"
if "CONNECT:NOT ACT" in display or "CONNECT NOT ACT" in display:
return "not-act"
if "CONNECT" in display:
return "other-connect"
return "other"
def target_matches(outcome: str, target: str) -> bool:
if target == "any-connect":
return outcome in {"ok", "dxc", "not-act", "other-connect"}
if target == "changed":
return outcome != "not-act"
return outcome == target
def format_results(results: list[SearchResult], *, target: str, show_all: bool = False) -> str:
lines = [
"Emulator CONNECT state search",
f"cases={len(results)} target={target}",
]
hits = [result for result in results if target_matches(result.outcome, target)]
lines.append(f"hits={len(hits)}")
selected = results if show_all else [result for result in results if result.outcome != "not-act"]
if not selected:
selected = hits[:]
for result in selected:
hit = "hit" if target_matches(result.outcome, target) else "miss"
lines.append(
f"[{result.case_index:03d}] {hit} outcome={result.outcome} stopped={result.stopped_reason} "
f"steps={result.steps} pc={h16(result.final_pc)} E000=0x{result.e000:04X} "
f"F730=0x{result.f730:02X} line0={result.line0!r}"
)
lines.append(" " + ", ".join(patch.label() for patch in result.patches))
return "\n".join(lines)
def parse_patch_list(values: Iterable[str], *, size: int, source: str) -> list[StatePatch]:
return [parse_single_patch(value, size=size, source=source) for value in values]
def parse_matrix_list(values: Iterable[str], *, size: int, source: str) -> list[list[StatePatch]]:
return [parse_matrix_patch(value, size=size, source=source) for value in values]
def parse_single_patch(text: str, *, size: int, source: str = "user") -> StatePatch:
address, value_text = split_assignment(text)
value = parse_int(value_text)
return StatePatch(size, parse_address(address), value & mask_for_size(size), source)
def parse_matrix_patch(text: str, *, size: int, source: str = "user") -> list[StatePatch]:
address, values_text = split_assignment(text)
address_value = parse_address(address)
patches = []
for value_text in values_text.split(","):
if not value_text.strip():
continue
patches.append(StatePatch(size, address_value, parse_int(value_text) & mask_for_size(size), source))
if not patches:
raise argparse.ArgumentTypeError(f"matrix patch has no values: {text!r}")
return patches
def split_assignment(text: str) -> tuple[str, str]:
if "=" not in text:
raise argparse.ArgumentTypeError(f"patch must be ADDRESS=VALUE, got {text!r}")
left, right = text.split("=", 1)
if not left.strip() or not right.strip():
raise argparse.ArgumentTypeError(f"patch must be ADDRESS=VALUE, got {text!r}")
return left.strip(), right.strip()
def parse_address(text: str) -> int:
token = text.strip()
if token.upper().startswith("H'"):
token = "0x" + token[2:]
elif not token.lower().startswith("0x") and any(char in token.upper() for char in "ABCDEF"):
token = "0x" + token
return parse_int(token) & 0xFFFF
def mask_for_size(size: int) -> int:
if size == 1:
return 0xFF
if size == 2:
return 0xFFFF
raise ValueError(f"unsupported patch size {size}")
def write_json(path: Path, args: argparse.Namespace, cases: list[SearchCase], results: list[SearchResult]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
payload = {
"kind": "h8536_emulator_state_search",
"preset": args.preset,
"description": PRESET_DESCRIPTIONS[args.preset],
"target": args.target,
"case_count": len(cases),
"result_count": len(results),
"hits": [result.payload() for result in results if target_matches(result.outcome, args.target)],
"results": [result.payload() for result in results],
}
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
__all__ = [
"CONNECT_WORDS",
"F730_LATCH_VALUES",
"SearchCase",
"SearchResult",
"StatePatch",
"apply_patch",
"build_arg_parser",
"build_cases",
"classify_display",
"main",
"parse_address",
"parse_matrix_patch",
"parse_single_patch",
"run_case",
"run_search",
"target_matches",
]

754
h8536/rx_branch_trace.py Normal file
View File

@@ -0,0 +1,754 @@
from __future__ import annotations
import argparse
import json
from collections.abc import Mapping
from pathlib import Path
from typing import Any
from .formatting import h16
JsonObject = dict[str, Any]
DEFAULT_INPUT = Path("build/rom_decompiled.json")
RX_CAPTURE_START = 0xF868
RX_CAPTURE_END = 0xF86D
RX_VALIDATION_START = 0xF860
RX_VALIDATION_END = 0xF865
TX_STAGING_START = 0xF850
TX_STAGING_END = 0xF854
CHECKSUM_SEED = 0x5A
def load_rx_branch_input(path: Path) -> JsonObject:
with path.open("r", encoding="utf-8") as handle:
payload = json.load(handle)
if not isinstance(payload, dict) or "instructions" not in payload:
raise ValueError(f"{path} does not look like h8536_decompiler JSON output")
return payload
def analyze_rx_branch_trace(payload: Mapping[str, Any]) -> JsonObject:
instructions = _instruction_sequence(payload.get("instructions"))
by_address = {int(ins["address"]): ins for ins in instructions if "address" in ins}
selector_decode = _selector_decode(by_address)
stages = [
_rx_interrupt_capture(by_address),
_frame_validation(by_address),
_dispatcher(by_address),
_error_retry_path(by_address),
_pending_selector_ring(by_address),
_resend_and_timeout_paths(by_address),
]
commands = _command_branches(by_address)
table_surfaces = _table_surfaces(by_address)
downstream_traces = _downstream_traces(by_address)
feedback_loops = _feedback_loops(by_address)
confidence = _confidence(stages, commands)
return {
"kind": "rx_branch_trace",
"summary": {
"title": "SCI1 RX frame branch map",
"confidence": confidence,
"core_finding": (
"The ROM captures six SCI1 bytes, validates a 0x5A-seeded XOR checksum, "
"decodes RX[0] & 0x07, then splits into initial commands while FAA2 == 0 "
"and continuation commands while FAA2 != 0."
),
},
"frame_model": {
"channel": "SCI1",
"capture_buffer": _range_payload(RX_CAPTURE_START, RX_CAPTURE_END),
"validation_buffer": _range_payload(RX_VALIDATION_START, RX_VALIDATION_END),
"tx_staging_buffer": _range_payload(TX_STAGING_START, TX_STAGING_END),
"frame_length": 6,
"checksum_seed": CHECKSUM_SEED,
"checksum_seed_hex": f"0x{CHECKSUM_SEED:02X}",
"command_expression": "RX[0] & 0x07",
"index_expression_candidate": "loc_622B(RX[1], RX[2])",
"value_expression_candidate": "RX[3:4]",
},
"selector_decode": selector_decode,
"stages": stages,
"commands": commands,
"table_surfaces": table_surfaces,
"downstream_traces": downstream_traces,
"feedback_loops": feedback_loops,
"state_bits": _state_bits(),
"bench_implications": _bench_implications(),
"caveats": [
"This is an address-driven static branch map, not proof of every runtime predicate value.",
"Semantic names are candidates; the branch destinations and RAM side effects are ROM evidence.",
"loc_BE70/F970 and loc_3E54/F870 are separate queues; this report names them separately because mixing them up changes the command-4/5 interpretation.",
],
}
def format_text_report(analysis: Mapping[str, Any]) -> str:
summary = analysis["summary"]
frame = analysis["frame_model"]
lines = [
"H8/536 SCI1 RX Branch Trace",
"",
f"Summary: {summary['core_finding']}",
f"Confidence: {summary['confidence']}",
"",
"Frame Model:",
f"- capture buffer: {frame['capture_buffer']['range_hex']}",
f"- validation buffer: {frame['validation_buffer']['range_hex']}",
f"- command: {frame['command_expression']}",
f"- logical index candidate: {frame['index_expression_candidate']}",
f"- value candidate: {frame['value_expression_candidate']}",
f"- checksum: 0x5A XOR RX[0..4] == RX[5]",
"",
"Selector Decode:",
]
selector_decode = analysis.get("selector_decode", {})
if selector_decode:
status = "present" if selector_decode.get("present") else "missing"
lines.append(f"- {selector_decode['title']}: {status}")
lines.append(f" {selector_decode['summary']}")
for rule in selector_decode.get("rules", []):
evidence = _join_hex(rule.get("evidence_addresses_hex", []))
suffix = f" [{evidence}]" if evidence else ""
lines.append(f" - {rule['condition']}: {rule['outcome']}{suffix}")
for implication in selector_decode.get("implications", []):
lines.append(f" - implication: {implication}")
lines.extend([
"",
"Stages:",
])
for stage in analysis.get("stages", []):
status = "present" if stage.get("present") else "missing"
lines.append(f"- {stage['title']}: {status}")
lines.append(f" {stage['summary']}")
for branch in stage.get("branches", []):
condition = branch.get("condition")
outcome = branch.get("outcome")
evidence = _join_hex(branch.get("evidence_addresses_hex", []))
suffix = f" [{evidence}]" if evidence else ""
lines.append(f" - {condition}: {outcome}{suffix}")
lines.extend(["", "Command Branches:"])
for command in analysis.get("commands", []):
response = command.get("response_candidate") or "no immediate serial response"
lines.append(
f"- cmd {command['command_hex']} {command['name']}: {command['availability']}; "
f"handler {command['handler_address_hex']}; {command['summary']}"
)
lines.append(f" response: {response}")
for effect in command.get("side_effects", []):
lines.append(f" - {effect}")
lines.extend(["", "Table Surfaces:"])
for surface in analysis.get("table_surfaces", []):
status = "present" if surface.get("present") else "missing"
evidence = _join_hex(surface.get("evidence_addresses_hex", []))
suffix = f" [{evidence}]" if evidence else ""
lines.append(f"- {surface['name']} {surface['range_hex']}: {status}; {surface['summary']}{suffix}")
for detail in surface.get("details", []):
lines.append(f" - {detail}")
lines.extend(["", "Downstream Flow Traces:"])
for trace in analysis.get("downstream_traces", []):
status = "present" if trace.get("present") else "missing"
lines.append(f"- {trace['title']}: {status}")
lines.append(f" {trace['summary']}")
for step in trace.get("steps", []):
evidence = _join_hex(step.get("evidence_addresses_hex", []))
suffix = f" [{evidence}]" if evidence else ""
lines.append(f" - {step['name']}: {step['effect']}{suffix}")
lines.extend(["", "RX-to-TX Feedback Loops:"])
for loop in analysis.get("feedback_loops", []):
status = "present" if loop.get("present") else "missing"
lines.append(f"- {loop['name']}: {status}")
lines.append(f" trigger: {loop['trigger']}")
lines.append(f" path: {loop['path']}")
lines.append(f" TX outcome: {loop['tx_outcome']}")
if loop.get("timing_gate"):
lines.append(f" timing/gate: {loop['timing_gate']}")
if loop.get("bench_read"):
lines.append(f" bench read: {loop['bench_read']}")
evidence = _join_hex(loop.get("evidence_addresses_hex", []))
if evidence:
lines.append(f" evidence: {evidence}")
lines.extend(["", "State Bits:"])
for bit in analysis.get("state_bits", []):
lines.append(f"- {bit['address_hex']}.{bit['bit']}: {bit['name']} - {bit['meaning']}")
lines.extend(["", "Bench Implications:"])
for implication in analysis.get("bench_implications", []):
lines.append(f"- {implication}")
lines.extend(["", "Caveats:"])
for caveat in analysis.get("caveats", []):
lines.append(f"- {caveat}")
return "\n".join(lines).rstrip() + "\n"
def write_rx_branch_trace(input_path: Path, output_path: Path, *, as_json: bool = False) -> JsonObject:
analysis = analyze_rx_branch_trace(load_rx_branch_input(input_path))
output_path.parent.mkdir(parents=True, exist_ok=True)
if as_json:
output_path.write_text(json.dumps(analysis, indent=2, sort_keys=True) + "\n", encoding="utf-8")
else:
output_path.write_text(format_text_report(analysis), encoding="utf-8")
return analysis
def main(argv: list[str] | None = None, stdout: Any | None = None) -> int:
parser = argparse.ArgumentParser(description="Trace the H8/536 SCI1 RX command branch tree.")
parser.add_argument("input", nargs="?", type=Path, default=DEFAULT_INPUT)
parser.add_argument("--json", action="store_true", help="emit structured JSON instead of readable text")
parser.add_argument("--out", type=Path, default=None, help="write report to this path")
args = parser.parse_args(argv)
stream = stdout
if stream is None:
import sys
stream = sys.stdout
analysis = analyze_rx_branch_trace(load_rx_branch_input(args.input))
rendered = json.dumps(analysis, indent=2, sort_keys=True) + "\n" if args.json else format_text_report(analysis)
if args.out:
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text(rendered, encoding="utf-8")
print(f"wrote {args.out}", file=stream)
else:
print(rendered, end="", file=stream)
return 0
def _selector_decode(by_address: Mapping[int, JsonObject]) -> JsonObject:
return {
"title": "loc_622B logical selector decode",
"present": _has_all(by_address, (0x622B, 0x622D, 0x6231, 0x6244, 0x624D, 0x6256, 0x625F, 0x6264)),
"summary": (
"The RX handler builds a raw word from RX[1:2], uses RX[1] bit0-bit2 as a page, "
"keeps RX[2] as the low selector byte, and maps the result into a 0x000-0x1FF logical selector."
),
"input_expression_candidate": "raw = (RX[1] << 8) | RX[2]; page = RX[1] & 0x07; low = RX[2]",
"rules": [
_branch("page 0, or pages 4-7, and low <= 0x7F", "selector = 0x000 + low", [0x6234, 0x6236, 0x6244, 0x6248, 0x6264]),
_branch("page 1 and low <= 0xFF", "selector = 0x080 + low", [0x6238, 0x623A, 0x624D, 0x6251, 0x6264]),
_branch("page 2 and low <= 0x7F", "selector = 0x180 + low", [0x623C, 0x623E, 0x6256, 0x625A, 0x6264]),
_branch("page 3, page/range failure, or page 0/4-7 with low > 0x7F", "selector forced to 0x01FF", [0x6240, 0x6242, 0x625F, 0x6261]),
],
"implications": [
"RX[1].7 is not part of the selector here because the dispatcher rejects byte1 bit7 before command handling.",
"Commands shaped as 01 80 xx ... are rejected before loc_622B-derived command handling even if the low byte looks useful.",
"Page 4-7 encodings appear to alias the page-0 decode path unless the low byte is out of range.",
],
}
def _rx_interrupt_capture(by_address: Mapping[int, JsonObject]) -> JsonObject:
return {
"title": "SCI1 RXI/ERI byte capture",
"present": _has_all(by_address, (0xBB57, 0xBB67, 0xBB6D, 0xBB90, 0xBB96, 0xBB9E)),
"summary": (
"ERI latches FAA4.7 and clears physical error flags; RXI clears RDRF, reads SCI1_RDR, "
"stores bytes into F868-F86D, reloads F9C1, and sets F9C5 when six bytes are captured."
),
"branches": [
_branch("ERI taken", "set FAA4.7, clear ORER/FER/PER, then fall into RXI byte capture", [0xBB57, 0xBB5B, 0xBB5F, 0xBB63]),
_branch("F9C1 == 0 before byte", "clear F9C3 so the byte starts a fresh frame", [0xBB71, 0xBB75, 0xBB77]),
_branch("F9C1 != 0 and F9C3 <= 5", "append byte at F868 + F9C3 and increment F9C3", [0xBB7D, 0xBB82, 0xBB8A, 0xBB90, 0xBB96]),
_branch("F9C1 != 0 and F9C3 > 5", "clear FAA4 and skip storing this byte", [0xBB7D, 0xBB82, 0xBB84, 0xBB88]),
_branch("incremented F9C3 == 6", "load F9C5 with 0x14 as the RX/session timeout window", [0xBB9A, 0xBB9C, 0xBB9E]),
_branch("any RXI exit", "reload F9C1 with 0x05 as the inter-byte timeout", [0xBBA3]),
],
}
def _frame_validation(by_address: Mapping[int, JsonObject]) -> JsonObject:
return {
"title": "six-byte validation and checksum",
"present": _has_all(by_address, (0xBBAB, 0xBBB3, 0xBBCB, 0xBBCF, 0xBBD6, 0xBBEC, 0xBBF0, 0xBC01)),
"summary": (
"The main-loop processor only runs when F9C3 is six, copies F868-F86D to F860-F865, "
"clears F9C3, rejects physical-error frames, checks the 0x5A XOR checksum, and decodes the index."
),
"branches": [
_branch("F9C3 != 6", "return without processing", [0xBBAB, 0xBBB0]),
_branch("FAA4.7 set after capture", "enter retry/error path at BE29", [0xBBCF, 0xBBD3]),
_branch("checksum mismatch", "enter retry/error path at BE29", [0xBBD6, 0xBBD8, 0xBBDC, 0xBBE0, 0xBBE4, 0xBBE8, 0xBBEC, 0xBBF0]),
_branch("checksum valid", "clear FAA6, decode selector from RX[1:2] through loc_622B, and dispatch on RX[0] & 0x07", [0xBBF3, 0xBBF7, 0xBBFD, 0xBC01, 0xBC08, 0xBC0C]),
],
}
def _dispatcher(by_address: Mapping[int, JsonObject]) -> JsonObject:
return {
"title": "FAA2 split dispatcher",
"present": _has_all(by_address, (0xBC0F, 0xBC15, 0xBC3A, 0xBC5C, 0xBC63)),
"summary": (
"FAA2 == 0 enters the initial dispatcher for commands 0, 1, 2, and 7. "
"FAA2 != 0 enters the continuation dispatcher; commands 4, 5, and 6 only live there."
),
"branches": [
_branch("FAA2 == 0", "set FAA2.7 and test initial commands 0, 1, 2, 7", [0xBC0F, 0xBC13, 0xBC15, 0xBC20, 0xBC24, 0xBC29, 0xBC2E]),
_branch("FAA2 != 0 and command bit2 set", "continuation commands 4, 5, 6, 7 are possible", [0xBC0F, 0xBC13, 0xBC3A, 0xBC45, 0xBC4A, 0xBC4F, 0xBC54]),
_branch("FAA2 != 0 and command bit2 clear", "BCLR FAA2.3; if that bit was set, clear FAA3 and re-enter initial dispatcher", [0xBC3A, 0xBC3C, 0xBC5C, 0xBC60, 0xBC63, 0xBC67]),
_branch("byte1 bit7 set on initial path", "branch to BD0B and return without normal command handling", [0xBC19, 0xBC1D, 0xBD0B]),
_branch("byte1 bit7 set on continuation path", "branch to BE27 and return without normal command handling", [0xBC3E, 0xBC42, 0xBE27]),
],
}
def _error_retry_path(by_address: Mapping[int, JsonObject]) -> JsonObject:
return {
"title": "checksum/error retry path",
"present": _has_all(by_address, (0xBE29, 0xBE2D, 0xBE33, 0xBE37, 0xBE4D, 0xBE6A)),
"summary": (
"Physical RX errors or checksum failures can either be ignored, clear the session after two retries, "
"or stage a command-7 retry/error echo of RX[1:4]."
),
"branches": [
_branch("FAA5.7 == 0", "return after clearing FAA4.7; retry echo is disabled", [0xBE29, 0xBE2D, 0xBE31]),
_branch("FAA5.7 == 1 and FAA6 < 2", "stage F850=0x07 and F851-F854=RX[1:4], then call BA26", [0xBE33, 0xBE37, 0xBE3C, 0xBE4D, 0xBE52, 0xBE5A, 0xBE62, 0xBE6A]),
_branch("FAA5.7 == 1 and FAA6 >= 2", "load F9C0=0x1F and clear FAA3/FAA2", [0xBE37, 0xBE3C, 0xBE3E, 0xBE43, 0xBE47]),
],
}
def _pending_selector_ring(by_address: Mapping[int, JsonObject]) -> JsonObject:
return {
"title": "pending selector ring at loc_BE70",
"present": _has_all(by_address, (0xBE70, 0xBE78, 0xBE84, 0xBE91, 0xBE95)),
"summary": (
"Several write/ACK paths call BE70 with R5 as the logical selector. BE70 deduplicates that selector "
"against a small F970-like ring using cursors F9B9/F9B4, then appends it if absent."
),
"branches": [
_branch("selector already present", "exit without appending", [0xBE84, 0xBE88, 0xBE9D]),
_branch("ring scan reaches F9B4 cursor", "store R5 into the pending ring and advance F9B4", [0xBE80, 0xBE82, 0xBE91, 0xBE95, 0xBE99]),
],
}
def _resend_and_timeout_paths(by_address: Mapping[int, JsonObject]) -> JsonObject:
return {
"title": "session timeout and resend side paths",
"present": _has_all(by_address, (0x3FEF, 0x3FF5, 0x3FFD, 0x4003, 0xBE9E, 0xBEB5, 0xBED5, 0xBEE4)),
"summary": (
"After any complete RX frame, F9C5 keeps FAA5.7/session-gate state alive for a short window. "
"When that window expires, 3FEF can clear queue cursors and call 400C, while BE9E handles resend countdowns."
),
"branches": [
_branch("F9C5 == 0 at loc_3FEF", "clear F9B5/F9B0 and clear FAA5.7; if FAA5.7 was set, call 400C reset/NOT-ACT state clear", [0x3FEF, 0x3FF3, 0x3FF5, 0x3FF9, 0x3FFD, 0x4001, 0x4003]),
_branch("F9C5 != 0 at loc_3FEF", "set FAA5.7, allowing retry/resend/session-gated paths", [0x3FEF, 0x3FF3, 0x4007]),
_branch("BE9E sees no pending FAA5 & FAA3 & 0x80", "clear FAA2 and return", [0xBE9E, 0xBEA5, 0xBEA9, 0xBEAD, 0xBEAF]),
_branch("BE9E pending and F9C6==0 and F9C8!=0", "decrement F9C8, reload F9C6, and possibly resend staged TX through BA26", [0xBEB5, 0xBEBB, 0xBEC1, 0xBEC5, 0xBECB, 0xBED1, 0xBED5]),
_branch("BE9E pending but F9C8==0", "clear F9C5, which lets 3FEF collapse the session gate later", [0xBEBB, 0xBEBF, 0xBEE4]),
],
}
def _table_surfaces(by_address: Mapping[int, JsonObject]) -> list[JsonObject]:
return [
{
"name": "primary_value_table",
"range_hex": "H'E000-H'E3FF",
"present": _has_all(by_address, (0xBC75, 0xBC95, 0xBCEC, 0xBD1A, 0xBD35)),
"summary": "logical selector word table; command 0 and continuation command 4 write it, command 1 reads it",
"details": [
"indexed as E000 + 2*selector after loc_622B",
"selector zero writes force the low byte to 0x80 on commands 0 and 4",
"this is the table that must contain E000[0]=0x8080 for the emulator-correlated CONNECT OK branch",
],
"evidence_addresses_hex": _hexes([0xBC75, 0xBC95, 0xBCEC, 0xBD1A, 0xBD35], by_address),
},
{
"name": "current_report_value_table",
"range_hex": "H'E800-H'EBFF",
"present": _has_all(by_address, (0xBC79, 0xBC99, 0xBD1E, 0xBB35)),
"summary": "current/report value table used when queued serial reports are converted into TX frames",
"details": [
"command 0 writes both primary E000 and current E800 for zero and nonzero selectors",
"command 4 writes E800 only on the selector-zero special path; the nonzero command-4 path does not show a matching E800 write here",
"loc_BAF2 reads E800 + 2*queued_selector when building autonomous report frames",
],
"evidence_addresses_hex": _hexes([0xBC79, 0xBC99, 0xBD1E, 0xBB35, 0xBB39, 0xBB3F], by_address),
},
{
"name": "secondary_value_table",
"range_hex": "H'E400-H'E7FF",
"present": _has_all(by_address, (0xBDE5, 0xBDE9)),
"summary": "secondary logical selector word table written by continuation command 6",
"details": [
"command 6 writes RX[3:4] to E400 + 2*selector",
"the matching EC00 flag bit is bit6 rather than bit7",
],
"evidence_addresses_hex": _hexes([0xBDE5, 0xBDE9], by_address),
},
{
"name": "dirty_flag_table",
"range_hex": "H'EC00-H'EDFF",
"present": _has_all(by_address, (0xBC82, 0xBC9D, 0xBD22, 0xBD39, 0xBDE9)),
"summary": "per-selector flag bytes; command 0/4 set bit7 and command 6 sets bit6",
"details": [
"the same logical selector indexes this byte table directly, not as a word offset",
"loc_48FA and other consumers test these table bits before raising follow-on reports",
],
"evidence_addresses_hex": _hexes([0xBC82, 0xBC9D, 0xBD22, 0xBD39, 0xBDE9, 0x4911], by_address),
},
{
"name": "mapped_shadow_or_eeprom_surface",
"range_hex": "H'F400-H'F4FF",
"present": _has_all(by_address, (0xBCA1, 0xBCA9, 0xBD3D, 0xBD45, 0xBD49, 0xBD5F)),
"summary": "optional mapped mirror/persistence surface selected through ROM tables around C564/C565",
"details": [
"nonzero command-0 and command-4 writes consult mapping bytes/words before mirroring into F400",
"command 4 can call BFE0 to persist the mapped word when F76E.7 is set",
"selector zero bypasses this mapped mirror path",
],
"evidence_addresses_hex": _hexes([0xBCA1, 0xBCA9, 0xBD3D, 0xBD45, 0xBD49, 0xBD5F], by_address),
},
]
def _downstream_traces(by_address: Mapping[int, JsonObject]) -> list[JsonObject]:
return [
{
"title": "immediate response staging through loc_BA26",
"present": _has_all(by_address, (0xBCB0, 0xBCCD, 0xBCD7, 0xBCFA, 0xBA36, 0xBA64, 0xBA72, 0xBA7F)),
"summary": (
"Command 0, command 1, command 7, and the retry/error path stage F850-F854, call BA26, "
"copy that staging area to F858-F85C, compute F85D, send the first byte, then let TXI send bytes 1-5."
),
"steps": [
_step("stage command-0 echo", "F850=0x04 and F851-F854 mirror the accepted host fields before BA26", [0xBCB0, 0xBCB5, 0xBCC1, 0xBCC9, 0xBCCD]),
_step("stage command-1 readback", "F850=0x04, F853/F854 receive the E000 table word, and BA26 sends it", [0xBCD7, 0xBCEC, 0xBCF0, 0xBCF6, 0xBCFA]),
_step("finalize TX frame", "BA26 copies F850-F854 to F858-F85C and computes F85D as 0x5A XOR bytes 0-4", [0xBA36, 0xBA3A, 0xBA42, 0xBA4A, 0xBA4E, 0xBA64]),
_step("start SCI1 transmission", "BA26 waits for TDRE, writes F858 to SCI1_TDR, sets F9C2=1, clears TDRE, and enables TIE", [0xBA68, 0xBA72, 0xBA76, 0xBA7B, 0xBA7F]),
_step("finish SCI1 transmission", "TXI indexes F858+F9C2 until six bytes are sent, then disables TIE", [0xBAAB, 0xBAB1, 0xBAB5, 0xBABF, 0xBAC3, 0xBACA]),
],
},
{
"title": "selector-processing queue BE70/F970 into loc_2806",
"present": _has_all(by_address, (0xBE70, 0xBE91, 0xBE95, 0x2806, 0x2819, 0x2822, 0x289F)),
"summary": (
"BE70 appends unique logical selectors into the F970 ring. The main loop later consumes that ring at "
"loc_2806 and dispatches selector-specific behavior through the 28A6 jump table."
),
"steps": [
_step("append unique selector", "BE70 scans from F9B9 to F9B4, skips duplicates, writes R5 to F970+2*cursor, and advances F9B4", [0xBE70, 0xBE78, 0xBE84, 0xBE91, 0xBE95, 0xBE99]),
_step("consume selector", "loc_2806 reads F970+2*F9B9, advances F9B9, masks the selector to 0x01FF, and keeps it in R5", [0x2806, 0x280C, 0x2819, 0x281D, 0x2822, 0x2826, 0x282A]),
_step("active-selector side path", "selectors matching F736/F738/F73A/F73C/F73E/F740/F742/F754 call loc_48FA before the jump table", [0x2837, 0x285E, 0x2878, 0x2892, 0x2CAB, 0x2CAD]),
_step("selector jump table", "the consumer jumps through table 28A6; selector zero is emulator-correlated with the CONNECT handler window", [0x289F, 0x28A3]),
],
},
{
"title": "serial-report queue loc_3E54/F870 into loc_BAF2",
"present": _has_all(by_address, (0x3E54, 0x3E76, 0x3E7A, 0x3E9A, 0x3EBF, 0x3EC3, 0xBAF2, 0xBB00)),
"summary": (
"loc_3E54 is a shared enqueue helper. R2.bit7 queues serial-visible report indexes in F870, "
"while R2.bit6 queues selector-processing work in F970. loc_BAF2 is the path that turns F870 entries into outbound 6-byte frames."
),
"steps": [
_step("enqueue serial report", "when R2.7 is set, loc_3E54 deduplicates R3 in F870 and advances F9B0", [0x3E54, 0x3E58, 0x3E6C, 0x3E76, 0x3E7A, 0x3E7E]),
_step("queue backpressure drain", "if the serial queue is nearly full, loc_3E54 calls loc_3FD3 until there is space", [0x3E82, 0x3E8B, 0x3E91, 0x3E93]),
_step("enqueue selector processing", "when R2.6 is set, loc_3E54 deduplicates R3 in F970 and advances F9B4", [0x3E9A, 0x3E9E, 0x3EB2, 0x3EBF, 0x3EC3, 0x3EC7]),
_step("dequeue serial report", "loc_BAF2 compares F9B5/F9B0, reads F870+2*F9B5, builds a TX report from E800, and calls BA26", [0xBAF2, 0xBAF8, 0xBB00, 0xBB08, 0xBB1C, 0xBB35, 0xBB43]),
_step("open continuation latch", "after a report send, loc_BAF2 sets FAA2.3, FAA3.7, F9C6, and F9C8; command 4/5/6 can then consume or ACK that report", [0xBB00, 0xBB46, 0xBB4C, 0xBB51]),
],
},
{
"title": "TXI/RXI race and continuation collapse",
"present": _has_all(by_address, (0xBA84, 0xBA8A, 0xBA90, 0xBA96, 0xBA9A, 0xBA9E, 0xBAA2)),
"summary": (
"The TX interrupt handler has an interlock: if a queued report is awaiting continuation and RX bytes are already arriving, "
"it clears the report/continuation state and stops TXI before the normal frame-finish path."
),
"steps": [
_step("detect overlap", "TXI tests FAA2.3, FAA5.7, and nonzero F9C3 before continuing the TX frame", [0xBA84, 0xBA8A, 0xBA90]),
_step("collapse continuation", "on overlap it clears FAA2.3 and FAA3, disables TIE, and loads F9C0=0x1F", [0xBA96, 0xBA9A, 0xBA9E, 0xBAA2]),
_step("normal completion path", "without the overlap, TXI sends bytes until F9C2 reaches six and then starts the post-TX delay path", [0xBAA9, 0xBAB5, 0xBAC3, 0xBACA, 0xBADA, 0xBAED]),
],
},
{
"title": "session expiry into reset/not-active state",
"present": _has_all(by_address, (0x3FEF, 0x3FFD, 0x4003, 0x400C, 0x4028, 0x4042)),
"summary": (
"When F9C5 reaches zero, loc_3FEF clears queue cursors and FAA5.7; if FAA5.7 had been set, "
"loc_400C clears connection/session RAM and refreshes the inactive display state."
),
"steps": [
_step("expire RX session", "F9C5==0 clears F9B5/F9B0 and clears FAA5.7", [0x3FEF, 0x3FF5, 0x3FF9, 0x3FFD]),
_step("call reset side path", "if clearing FAA5.7 changed the bit, loc_3FEF calls loc_400C", [0x4001, 0x4003]),
_step("clear connection state", "loc_400C clears F730/F756-F759/F732/F75C/FB03/F791/F795/F76E and calls follow-on display/session refresh routines", [0x400C, 0x4010, 0x4020, 0x4028, 0x4034, 0x403C, 0x4040, 0x4042]),
],
},
]
def _feedback_loops(by_address: Mapping[int, JsonObject]) -> list[JsonObject]:
return [
{
"name": "command-0 write echo and selector-processing loop",
"present": _has_all(by_address, (0xBC69, 0xBC86, 0xBCB0, 0xBCCD, 0xBE70, 0x2806, 0x28A3)),
"trigger": "valid command 0 while FAA2 == 0",
"path": "RX validation -> BC69 table write -> BE70 appends selector to F970 -> BCB0/BA26 sends immediate 0x04 echo -> later loc_2806 consumes F970",
"tx_outcome": "immediate command-4-style echo frame plus possible later selector-driven reports/display work",
"timing_gate": "BA26 sets F9C0=0x64 and F9C4=0x07, temporarily delaying queued TX and heartbeat enqueue",
"bench_read": "a command-0 probe is stateful: it both writes table state and spends time in the post-TX delay, so it can disturb the continuation window being tested",
"evidence_addresses_hex": _hexes([0xBC69, 0xBC75, 0xBC86, 0xBCB0, 0xBCCD, 0xBA2C, 0xBA31, 0xBE70, 0x2819, 0x28A3], by_address),
},
{
"name": "command-1 readback and previous-frame loop",
"present": _has_all(by_address, (0xBCD7, 0xBCEC, 0xBCFA, 0xBA36, 0xBE05, 0xBE22)),
"trigger": "valid command 1 while FAA2 == 0, followed by optional command 7",
"path": "RX validation -> BCD7 reads E000[selector] -> BA26 finalizes TX in F858-F85D -> BE05 can copy F858-F85C back to F850-F854",
"tx_outcome": "direct 0x04 readback, then command 7 can retransmit the exact last finalized TX frame",
"timing_gate": "command 1 clears FAA2.7 and never enters continuation handling; it is a readback path, not an ACK path",
"bench_read": "command 7 after command 1 proves what was last finalized, but does not prove a hidden continuation token by itself",
"evidence_addresses_hex": _hexes([0xBCD7, 0xBCEC, 0xBCFA, 0xBA36, 0xBA64, 0xBE05, 0xBE09, 0xBE22], by_address),
},
{
"name": "retry/error 07 echo loop",
"present": _has_all(by_address, (0xBE29, 0xBE2D, 0xBE4D, 0xBE6A, 0xBA26)),
"trigger": "physical RX error or checksum mismatch while FAA5.7 is set and retry count FAA6 is below two",
"path": "RX validation error -> BE29 retry gate -> BE4D stages F850=0x07 and copies RX[1:4] into F851-F854 -> BA26 sends",
"tx_outcome": "a 0x07 frame that can echo the host payload bytes, independent of E000/E800 table contents",
"timing_gate": "after two retries, the ROM loads F9C0=0x1F and clears FAA3/FAA2 instead of sending another echo",
"bench_read": "visible 07 frames in a noisy/tight timing run can be retry echoes, not necessarily device status or a continuation token",
"evidence_addresses_hex": _hexes([0xBE29, 0xBE2D, 0xBE33, 0xBE37, 0xBE3E, 0xBE43, 0xBE47, 0xBE4D, 0xBE6A], by_address),
},
{
"name": "autonomous report to host continuation loop",
"present": _has_all(by_address, (0x3E54, 0x3E76, 0x3FD3, 0xBAF2, 0xBB00, 0xBB43, 0xBD67)),
"trigger": "firmware enqueues a serial-visible report via loc_3E54 with R2.7 set",
"path": "loc_3E54 appends report selector to F870 -> loc_3FD3 allows BAF2 when FAA2/F9C0 gates are clear -> BAF2 sends report -> command 4/5/6 continuation can advance F9B5",
"tx_outcome": "autonomous 6-byte report frame built from E800[selector], with FAA2.3/FAA3.7 left set to await host continuation or ACK",
"timing_gate": "F9C0 must count down before BAF2 can send, and F9C6/F9C8/BE9E control repeated sends while FAA3.7 remains live",
"bench_read": "the actual ACK/write target is not just the selector; it is the report that is live under FAA2.3 before TXI/RXI or BE9E clears it",
"evidence_addresses_hex": _hexes([0x3E54, 0x3E76, 0x3E7A, 0x3FD3, 0x3FE5, 0x3FEB, 0xBAF2, 0xBB00, 0xBB35, 0xBB43, 0xBB46, 0xBB51, 0xBD67, 0xBDC2, 0xBDED], by_address),
},
{
"name": "selector-processing to report loop",
"present": _has_all(by_address, (0xBE70, 0x2806, 0x2CAD, 0x48FA, 0x4926, 0x3E54)),
"trigger": "command 0/4/selected command 5 calls BE70, or loc_3E54 is called with R2.6 set",
"path": "BE70/F970 selector queue -> loc_2806 selector dispatch -> active-selector side path loc_48FA -> loc_3E54 can enqueue report 0x00F6 when E1EC.13 is set",
"tx_outcome": "possible later serial report produced through the F870/BAF2 loop rather than an immediate response to the original RX frame",
"timing_gate": "loc_48FA is gated by FB03.7, F732 values, E1EC.13, and F76E.6 before reaching its indirect table",
"bench_read": "selector-zero CONNECT work and visible serial reports can be separated in time; lack of immediate TX does not mean the selector queue did nothing",
"evidence_addresses_hex": _hexes([0xBE70, 0xBE91, 0x2806, 0x2819, 0x2CAD, 0x48FA, 0x490F, 0x4921, 0x4923, 0x4926], by_address),
},
{
"name": "TXI/RXI overlap cancellation loop",
"present": _has_all(by_address, (0xBA84, 0xBA8A, 0xBA90, 0xBA96, 0xBA9A, 0xBAA2)),
"trigger": "host RX bytes begin while a report TX is active, FAA2.3 is set, and FAA5.7 is set",
"path": "TXI observes FAA2.3 + FAA5.7 + F9C3 != 0 -> clears FAA2.3/FAA3 -> disables TIE -> loads F9C0=0x1F",
"tx_outcome": "the pending report/continuation state can be canceled before the host command reaches the continuation dispatcher",
"timing_gate": "this depends on byte timing relative to TXI and F9C3, so polite emulator injection can miss it",
"bench_read": "this is a concrete ROM reason why bench timing might see latches/retries while a too-polite emulator reaches cleaner continuation paths",
"evidence_addresses_hex": _hexes([0xBA84, 0xBA8A, 0xBA90, 0xBA96, 0xBA9A, 0xBA9E, 0xBAA2], by_address),
},
{
"name": "session-expiry to heartbeat/not-active loop",
"present": _has_all(by_address, (0xBB9E, 0xBF31, 0xBF37, 0x3FEF, 0x400C, 0x4046, 0x4067)),
"trigger": "any complete six-byte RX frame loads F9C5, then FRT2 decrements it to zero",
"path": "RX complete -> F9C5=0x14 -> FRT2 decrements F9C5 -> loc_3FEF clears session/queues and calls 400C -> loc_4046 can later enqueue heartbeat selector 0",
"tx_outcome": "eventual return to idle heartbeat/report behavior and inactive-session display state",
"timing_gate": "F9C4 gates heartbeat enqueue; BA26 reloads it to 0x07 after each send, matching the roughly 700 ms heartbeat cadence",
"bench_read": "the common CONNECT NOT ACT after arbitrary six-byte traffic is consistent with this expiry/reset loop",
"evidence_addresses_hex": _hexes([0xBB9E, 0xBF31, 0xBF37, 0x3FEF, 0x3FFD, 0x4003, 0x400C, 0x4046, 0x4067, 0xBA31], by_address),
},
]
def _command_branches(by_address: Mapping[int, JsonObject]) -> list[JsonObject]:
return [
{
"command": 0x00,
"command_hex": "0x00",
"name": "set_value_acked_candidate",
"handler_address_hex": h16(0xBC69),
"availability": "initial path only: checksum valid, FAA2 == 0, RX[1].7 == 0",
"summary": "writes RX[3:4] into primary/current tables, flags the selector, calls BE70, and sends an echo-style 0x04 response",
"response_candidate": "F850=0x04; F851-F854 mostly echo RX[1:4]; BA26 sends it",
"side_effects": [
"selector zero is special: the low byte is forced to 0x80 after the high byte is taken from RX[3]",
"nonzero selectors can mirror into an auxiliary table via a mapping table",
"clears FAA2.7 before exit",
],
"evidence_addresses_hex": _hexes([0xBC69, 0xBC75, 0xBC79, 0xBC82, 0xBC86, 0xBCB0, 0xBCCD, 0xBCD0], by_address),
},
{
"command": 0x01,
"command_hex": "0x01",
"name": "read_value_candidate",
"handler_address_hex": h16(0xBCD7),
"availability": "initial path only: checksum valid, FAA2 == 0, RX[1].7 == 0",
"summary": "reads primary table E000 + 2*selector and stages a 0x04 response",
"response_candidate": "F850=0x04; F851 is overwritten with RX[2]; F853/F854 receive table high/low; F852 is not freshly written here",
"side_effects": [
"does not enter continuation command handling",
"clears FAA2.7 before exit",
],
"evidence_addresses_hex": _hexes([0xBCD7, 0xBCE0, 0xBCE8, 0xBCEC, 0xBCF0, 0xBCF6, 0xBCFA, 0xBCFD], by_address),
},
{
"command": 0x02,
"command_hex": "0x02",
"name": "initial_clear_or_noop_candidate",
"handler_address_hex": h16(0xBD04),
"availability": "initial path only: checksum valid, FAA2 == 0, RX[1].7 == 0",
"summary": "clears FAA2.7 and returns without staging a response",
"response_candidate": None,
"side_effects": ["likely a quiet/session-clear style command on the initial path"],
"evidence_addresses_hex": _hexes([0xBD04, 0xBD08], by_address),
},
{
"command": 0x04,
"command_hex": "0x04",
"name": "continuation_set_value_candidate",
"handler_address_hex": h16(0xBD0E),
"availability": "continuation path only: checksum valid, FAA2 != 0, command bit2 set, RX[1].7 == 0",
"summary": "writes a value into the primary table without an immediate serial response; selector zero also updates the current/report table",
"response_candidate": None,
"side_effects": [
"selector zero is special: RX[3] becomes the high byte and low byte is forced to 0x80",
"nonzero selectors write E000 and flag EC00.7; the matching E800 current/report write is not present in this handler",
"nonzero selectors can mirror/persist through F400/BFE0 when mapping and F76E.7 allow it",
"if FAA2.3 was set from a queued report, advances F9B5 to consume that report",
"clears FAA3 and FAA2 before exit",
],
"evidence_addresses_hex": _hexes([0xBD0E, 0xBD1A, 0xBD1E, 0xBD22, 0xBD26, 0xBD35, 0xBD64, 0xBD67, 0xBD6D, 0xBD75, 0xBD79], by_address),
},
{
"command": 0x05,
"command_hex": "0x05",
"name": "continuation_ack_or_clear_pending_candidate",
"handler_address_hex": h16(0xBD80),
"availability": "continuation path only: checksum valid, FAA2 != 0, command bit2 set, RX[1].7 == 0",
"summary": "ACK/session-clear path; usually no response, but selected logical indexes feed BE70 or clear connection latches",
"response_candidate": None,
"side_effects": [
"selectors 0x006C, 0x006D, and 0x006E call BE70",
"with F731.7 set, selectors 0x006B, 0x0096, 0x0097, 0x00C6, and 0x00F8 clear F731.7/F790.7",
"if FAA2.3 was set from a queued report, advances F9B5",
"clears FAA3 and FAA2 before exit",
],
"evidence_addresses_hex": _hexes([0xBD80, 0xBD85, 0xBD94, 0xBD9A, 0xBDB5, 0xBDBF, 0xBDC2, 0xBDC8, 0xBDD0, 0xBDD4], by_address),
},
{
"command": 0x06,
"command_hex": "0x06",
"name": "continuation_set_secondary_candidate",
"handler_address_hex": h16(0xBDDB),
"availability": "continuation path only: checksum valid, FAA2 != 0, command bit2 set, RX[1].7 == 0",
"summary": "writes RX[3:4] into the secondary table and sets flag-table bit 6",
"response_candidate": None,
"side_effects": [
"if FAA2.3 was set from a queued report, advances F9B5",
"clears FAA3 and FAA2 before exit",
],
"evidence_addresses_hex": _hexes([0xBDDB, 0xBDE5, 0xBDE9, 0xBDED, 0xBDF3, 0xBDFB, 0xBDFF], by_address),
},
{
"command": 0x07,
"command_hex": "0x07",
"name": "retransmit_previous_tx_candidate",
"handler_address_hex": h16(0xBE05),
"availability": "initial or continuation path",
"summary": "copies the previous finalized TX frame bytes back into staging and sends them again",
"response_candidate": "previous TX frame retransmitted through BA26",
"side_effects": ["loads F9C0 with 0x1F before sending"],
"evidence_addresses_hex": _hexes([0xBE05, 0xBE09, 0xBE0D, 0xBE11, 0xBE15, 0xBE19, 0xBE1D, 0xBE22], by_address),
},
]
def _state_bits() -> list[JsonObject]:
return [
{"address": 0xFAA2, "address_hex": h16(0xFAA2), "bit": 7, "name": "rx_command_in_progress_candidate", "meaning": "set on initial-path parse; cleared by command 0/1/2 exits or by continuation cleanup"},
{"address": 0xFAA2, "address_hex": h16(0xFAA2), "bit": 3, "name": "queued_report_ack_needed_candidate", "meaning": "set by the autonomous queue send path at BB00; continuation command 4/5/6 can advance F9B5 only when this bit was set"},
{"address": 0xFAA3, "address_hex": h16(0xFAA3), "bit": 7, "name": "pending_resend_mask_candidate", "meaning": "set after queued report send; BE9E masks it with FAA5.7 before resend/clear decisions"},
{"address": 0xFAA4, "address_hex": h16(0xFAA4), "bit": 7, "name": "rx_physical_error_latch_candidate", "meaning": "set by SCI1 ERI and tested before checksum dispatch"},
{"address": 0xFAA5, "address_hex": h16(0xFAA5), "bit": 7, "name": "rx_session_gate_candidate", "meaning": "set while F9C5 is alive after a complete RX frame; gates retry/resend and heartbeat/report enqueue behavior"},
]
def _bench_implications() -> list[str]:
return [
"A standalone command 4 frame from idle should not hit BD0E; command 4 is continuation-only and initial dispatch does not accept it.",
"Command 5 is not a generic always-live ACK. It only performs ACK/session-clear work on the continuation path while FAA2 != 0.",
"A valid six-byte RX frame loads F9C5 with 0x14, which temporarily sets FAA5.7; when that window expires, loc_3FEF can clear queue/session state and call loc_400C.",
"The observed 07 retry/error family can be produced by BE4D from RX[1:4] after error/checksum retry conditions; it is not automatically proof of a table value or a command-4 continuation token.",
"To prove CONNECT OK through serial, the bench has to create or preserve FAA2 != 0 at the moment command 4 arrives, and ideally FAA2.3 if it expects queued-report advancement.",
]
def _branch(condition: str, outcome: str, evidence: list[int]) -> JsonObject:
return {
"condition": condition,
"outcome": outcome,
"evidence_addresses_hex": [h16(address) for address in evidence],
}
def _step(name: str, effect: str, evidence: list[int]) -> JsonObject:
return {
"name": name,
"effect": effect,
"evidence_addresses_hex": [h16(address) for address in evidence],
}
def _range_payload(start: int, end: int) -> JsonObject:
return {
"start": start,
"end": end,
"start_hex": h16(start),
"end_hex": h16(end),
"range_hex": f"{h16(start)}-{h16(end)}",
}
def _instruction_sequence(value: Any) -> list[JsonObject]:
if isinstance(value, list):
return [dict(item) for item in value if isinstance(item, Mapping) and "address" in item]
return []
def _has_all(by_address: Mapping[int, JsonObject], addresses: tuple[int, ...]) -> bool:
return all(address in by_address for address in addresses)
def _hexes(addresses: list[int], by_address: Mapping[int, JsonObject]) -> list[str]:
return [h16(address) for address in addresses if address in by_address]
def _join_hex(values: Any) -> str:
if not isinstance(values, list):
return ""
return ", ".join(str(value) for value in values)
def _confidence(stages: list[JsonObject], commands: list[JsonObject]) -> str:
present = sum(1 for stage in stages if stage.get("present"))
command_evidence = sum(1 for command in commands if command.get("evidence_addresses_hex"))
if present >= 5 and command_evidence >= 6:
return "high"
if present >= 3 and command_evidence >= 4:
return "medium"
return "low"
__all__ = [
"analyze_rx_branch_trace",
"format_text_report",
"load_rx_branch_input",
"main",
"write_rx_branch_trace",
]

732
h8536/state_map_runner.py Normal file
View File

@@ -0,0 +1,732 @@
from __future__ import annotations
import argparse
import json
import re
import sys
import time
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any, Iterable, TextIO
from .bench_connect_lcd import (
BenchLogger,
FrameDetector,
_import_serial,
_relay_command,
_relay_settle,
_wait_for_ready,
format_frame,
frame_checksum,
frame_checksum_ok,
label_frame,
parse_frame,
)
READBACK_E000_FRAME = bytes.fromhex("01000000005B")
COMMAND7_REPEAT_FRAME = bytes.fromhex("07000000005D")
HEARTBEAT_FRAME = bytes.fromhex("0000000080DA")
CONNECT_FORCE_PRESETS: dict[str, tuple[bytes, int, str]] = {
"clear": (bytes.fromhex("04000000005E"), 0x0080, "selector-zero no-bit clear/inactive primer"),
"dxc": (bytes.fromhex("04000040001E"), 0x4080, "selector-zero CONNECT:DXC-637 candidate"),
"ok": (bytes.fromhex("0400008000DE"), 0x8080, "selector-zero CONNECT: OK candidate"),
"both": (bytes.fromhex("040000C0009E"), 0xC080, "selector-zero bit14+bit15 priority test"),
}
@dataclass(frozen=True)
class StateMapEvent:
direction: str
frame: bytes
timestamp_ms: int | None = None
label: str = ""
source: str = ""
@property
def frame_text(self) -> str:
return format_frame(self.frame)
@dataclass
class StateMapRunContext:
args: argparse.Namespace
logger: BenchLogger
detector: FrameDetector
device: Any
relay: Any | None = None
events: list[StateMapEvent] = field(default_factory=list)
def default_log_path() -> Path:
return Path("captures") / f"state-map-runner-{datetime.now().strftime('%Y%m%d-%H%M%S')}.txt"
def build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description=(
"Run or analyze the PT2 state-map proof sequence: visible 07 drain candidate, "
"selector-zero force, E000[0] readback, and command-7 hidden-response probe."
)
)
parser.add_argument("--analyze-log", type=Path, help="analyze an existing bench log instead of opening serial ports")
parser.add_argument("--json-out", type=Path, help="write machine-readable state-map analysis")
parser.add_argument("--preset", choices=sorted(CONNECT_FORCE_PRESETS), default="ok", help="selector-zero force preset")
parser.add_argument("--force-frame", type=parse_frame, help="override preset with a custom selector-zero command-4 frame")
parser.add_argument("--expected-word", type=_int_arg, help="expected E000[0] readback word; default follows --preset")
parser.add_argument("--port", default="COM5", help="RS232 serial port connected to the RCP")
parser.add_argument("--baud", type=int, default=38400, help="RCP serial baud rate")
parser.add_argument("--relay-port", default="COM6", help="Pico relay serial port")
parser.add_argument("--relay-baud", type=int, default=115200, help="Pico relay serial baud rate")
parser.add_argument("--no-power-cycle", action="store_true", help="do not send relay off/on before the test")
parser.add_argument("--power-off-command", default="off", help="relay command used to remove DUT power")
parser.add_argument("--power-on-command", default="on", help="relay command used to apply DUT power")
parser.add_argument("--off-seconds", type=float, default=1.5, help="seconds to hold the DUT powered off")
parser.add_argument("--relay-settle", type=float, default=2.0, help="seconds to wait after opening the relay port")
parser.add_argument("--ready-timeout", type=float, default=10.0, help="seconds to wait for heartbeat before sending")
parser.add_argument("--ready-heartbeats", type=int, default=2, help="heartbeat frames to observe before sending")
parser.add_argument("--require-ready", action="store_true", help="abort if ready heartbeat count is not observed")
parser.add_argument("--sync", choices=("checksum", "fixed"), default="checksum", help="RX frame sync strategy")
parser.add_argument("--pre-drain", type=float, default=0.250, help="seconds to drain/log RX before priming")
parser.add_argument("--prime-frame", action="append", type=parse_frame, help="optional trigger/tickle frame; repeatable")
parser.add_argument("--prime-repeat", type=int, default=0, help="times to send each prime frame while hunting a trigger")
parser.add_argument("--prime-gap", type=float, default=0.120, help="seconds to listen after each prime frame")
parser.add_argument("--trigger-timeout", type=float, default=3.0, help="seconds to wait for a visible 07 drain candidate")
parser.add_argument("--trigger-prefix", default="07", help="hex prefix for the trigger frame; default any device 07...")
parser.add_argument(
"--trigger-poll-interval",
type=float,
default=0.002,
help="seconds between non-blocking serial polls while hunting a trigger",
)
parser.add_argument(
"--read-poll-interval",
type=float,
default=0.002,
help="seconds between non-blocking serial polls during timed listen/guard windows",
)
parser.add_argument("--force-guard", type=float, default=0.005, help="seconds after the detected trigger before force TX")
parser.add_argument("--post-force-listen", type=float, default=0.050, help="seconds to listen before readback")
parser.add_argument("--readback-frame", type=parse_frame, default=READBACK_E000_FRAME, help="E000[0] readback frame")
parser.add_argument("--readback-window", type=float, default=0.300, help="seconds to listen after readback")
parser.add_argument("--no-command7-probe", action="store_true", help="skip command-7 previous-frame probe")
parser.add_argument("--command7-window", type=float, default=0.300, help="seconds to listen after command-7 probe")
parser.add_argument("--final-read", type=float, default=2.0, help="seconds to listen after the proof sequence")
parser.add_argument("--prompt-screen", action="store_true", help="prompt for observed LCD text after the sequence")
parser.add_argument("--log", type=Path, help="capture log path")
parser.add_argument("--dry-run", action="store_true", help="print the planned state-map sequence without opening ports")
return parser
def main(argv: list[str] | None = None, *, stdout: TextIO = sys.stdout) -> int:
args = build_arg_parser().parse_args(argv)
force_frame, expected_word, preset_note = resolve_force(args)
if args.analyze_log:
text = args.analyze_log.read_text(encoding="utf-8")
events = parse_bench_log(text)
analysis = analyze_events(events, expected_word=expected_word)
print(format_analysis_report(analysis), file=stdout)
if args.json_out:
_write_json(args.json_out, analysis)
return 0
log_path = args.log or default_log_path()
if args.dry_run:
_print_dry_run(args, log_path, force_frame, expected_word, preset_note, stdout)
return 0
serial = _import_serial()
logger = BenchLogger(log_path, stdout=stdout)
detector = FrameDetector(sync_mode=args.sync)
try:
logger.emit("PT2 state-map proof runner")
logger.emit(f"device={args.port} {args.baud} 8N1 relay={args.relay_port} {args.relay_baud} sync={args.sync}")
logger.emit(f"log={log_path}")
_emit_plan(logger, args, force_frame, expected_word, preset_note)
with serial.Serial(args.port, args.baud, bytesize=8, parity="N", stopbits=1, timeout=0.05) as device:
ctx = StateMapRunContext(args=args, logger=logger, detector=detector, device=device)
try:
_prepare_device(ctx)
trigger = _hunt_trigger(ctx, _parse_prefix(args.trigger_prefix))
if trigger is None:
logger.event("STATE no visible-drain token candidate; force/readback skipped")
_read_for_collect(ctx, args.final_read)
return _finish(ctx, logger, expected_word, args.json_out)
logger.event(f"STATE visible-drain token candidate {trigger.frame_text}")
if args.force_guard > 0:
logger.event(f"STATE force guard {args.force_guard:.3f}s")
_read_for_collect(ctx, args.force_guard)
_send_and_record(ctx, force_frame, "selector_zero_force")
if args.post_force_listen > 0:
_read_for_collect(ctx, args.post_force_listen)
_send_and_record(ctx, args.readback_frame, "e000_readback")
_read_for_collect(ctx, args.readback_window)
if not args.no_command7_probe:
_send_and_record(ctx, COMMAND7_REPEAT_FRAME, "command7_previous_frame_probe")
_read_for_collect(ctx, args.command7_window)
_read_for_collect(ctx, args.final_read)
if args.prompt_screen:
_prompt_screen("LCD after state-map proof sequence", logger)
finally:
if ctx.relay is not None:
ctx.relay.close()
return _finish(ctx, logger, expected_word, args.json_out)
finally:
logger.close()
def resolve_force(args: argparse.Namespace) -> tuple[bytes, int, str]:
preset_frame, preset_word, preset_note = CONNECT_FORCE_PRESETS[args.preset]
frame = args.force_frame or preset_frame
expected_word = args.expected_word if args.expected_word is not None else _expected_word_for_force(frame, preset_word)
return frame, expected_word, preset_note
def analyze_events(events: Iterable[StateMapEvent], *, expected_word: int | None = None) -> dict[str, Any]:
event_list = list(events)
trigger_indexes = [index for index, event in enumerate(event_list) if is_visible_drain_candidate(event)]
first_trigger = trigger_indexes[0] if trigger_indexes else None
force_indexes = [index for index, event in enumerate(event_list) if is_selector_zero_force(event)]
first_force_after_trigger = _first_after(force_indexes, first_trigger)
readback_tx_indexes = [index for index, event in enumerate(event_list) if is_tx_frame(event, READBACK_E000_FRAME)]
command7_tx_indexes = [index for index, event in enumerate(event_list) if is_tx_frame(event, COMMAND7_REPEAT_FRAME)]
readback_rx_indexes = [index for index, event in enumerate(event_list) if is_selector_zero_readback(event)]
direct_readbacks = [_readback_info(event_list[index], expected_word) for index in readback_rx_indexes]
first_direct_after_force = _first_after(readback_rx_indexes, first_force_after_trigger)
command7_after_force = _first_after(command7_tx_indexes, first_force_after_trigger)
command7_replay = _first_after(readback_rx_indexes, command7_after_force) if command7_after_force is not None else None
warnings = _state_warnings(event_list, first_trigger, first_force_after_trigger)
outcome = _outcome(
first_trigger=first_trigger,
first_force=first_force_after_trigger,
first_direct=first_direct_after_force,
command7_replay=command7_replay,
events=event_list,
expected_word=expected_word,
)
facts = {
"kind": "pt2_state_map_analysis",
"event_count": len(event_list),
"expected_word": _word_payload(expected_word),
"outcome": outcome,
"warnings": warnings,
"trigger_candidates": [_event_payload(event_list[index]) for index in trigger_indexes],
"selector_zero_forces": [_event_payload(event_list[index]) for index in force_indexes],
"readback_tx": [_event_payload(event_list[index]) for index in readback_tx_indexes],
"command7_tx": [_event_payload(event_list[index]) for index in command7_tx_indexes],
"direct_readbacks": direct_readbacks,
"first_trigger_index": first_trigger,
"first_force_after_trigger_index": first_force_after_trigger,
"first_direct_readback_after_force_index": first_direct_after_force,
"command7_replay_readback_index": command7_replay,
"post_force_rx_labels": _post_force_rx_labels(event_list, first_force_after_trigger),
"state_map_notes": [
"A device 07... frame is treated as a visible F870 drain/token candidate, not proof by itself.",
"The proof target is a retained selector-zero readback: RX 04 00 QQ HH LL after the force/readback turn.",
"Command 0/1 or overlapping RX before the force can spend or clear the FAA2/FAA3 opportunity.",
],
}
return facts
def format_analysis_report(analysis: dict[str, Any]) -> str:
lines = [
"PT2 state-map analysis",
f"outcome={analysis['outcome']['name']} confidence={analysis['outcome']['confidence']}",
f"reason={analysis['outcome']['reason']}",
f"events={analysis['event_count']} triggers={len(analysis['trigger_candidates'])} "
f"forces={len(analysis['selector_zero_forces'])} readbacks={len(analysis['direct_readbacks'])}",
]
expected = analysis.get("expected_word") or {}
if expected:
lines.append(f"expected_e0000={expected['hex']}")
if analysis["trigger_candidates"]:
first = analysis["trigger_candidates"][0]
lines.append(f"first_trigger={first['frame']} label={first['label'] or '(unlabeled)'}")
if analysis["selector_zero_forces"]:
first = analysis["selector_zero_forces"][0]
lines.append(f"first_force={first['frame']}")
for readback in analysis["direct_readbacks"]:
match = " expected" if readback.get("matches_expected") else ""
lines.append(
f"readback frame={readback['frame']} qq=0x{readback['qq']:02X} "
f"value={readback['value_hex']}{match}"
)
for warning in analysis["warnings"]:
lines.append(f"warning={warning}")
labels = analysis.get("post_force_rx_labels", {})
if labels:
joined = ", ".join(f"{name}={count}" for name, count in sorted(labels.items()))
lines.append(f"post_force_rx={joined}")
return "\n".join(lines)
def parse_bench_log(text: str) -> list[StateMapEvent]:
lines = text.splitlines()
events: list[StateMapEvent] = []
rx_detect_seen = False
for line in lines:
detect = _DETECT_RE.match(line.strip())
if detect:
rx_detect_seen = True
frame = _parse_hex_bytes(detect.group("hex"))
if len(frame) == 6:
events.append(
StateMapEvent(
direction="rx",
frame=frame,
timestamp_ms=_timestamp_to_ms(detect.group("ts")),
label=detect.group("label"),
source="detect",
)
)
continue
chunk = _CHUNK_RE.match(line.strip())
if not chunk or chunk.group("direction") != "TX":
continue
frame = _parse_hex_bytes(chunk.group("hex"))
if len(frame) == 6:
events.append(
StateMapEvent(
direction="tx",
frame=frame,
timestamp_ms=_timestamp_to_ms(chunk.group("ts")),
label="",
source="tx_chunk",
)
)
if rx_detect_seen:
return events
detector = FrameDetector()
for line in lines:
chunk = _CHUNK_RE.match(line.strip())
if not chunk or chunk.group("direction") != "RX":
continue
data = _parse_hex_bytes(chunk.group("hex"))
for frame, label in detector.feed(data):
events.append(
StateMapEvent(
direction="rx",
frame=frame,
timestamp_ms=_timestamp_to_ms(chunk.group("ts")),
label=label,
source="rx_chunk_resync",
)
)
return events
def is_visible_drain_candidate(event: StateMapEvent) -> bool:
return event.direction == "rx" and len(event.frame) == 6 and frame_checksum_ok(event.frame) and event.frame[0] == 0x07
def is_selector_zero_force(event: StateMapEvent) -> bool:
return (
event.direction == "tx"
and len(event.frame) == 6
and frame_checksum_ok(event.frame)
and event.frame[0] == 0x04
and event.frame[1] == 0x00
and event.frame[2] == 0x00
)
def is_selector_zero_readback(event: StateMapEvent) -> bool:
return (
event.direction == "rx"
and len(event.frame) == 6
and frame_checksum_ok(event.frame)
and event.frame[0] == 0x04
and event.frame[1] == 0x00
)
def is_tx_frame(event: StateMapEvent, frame: bytes) -> bool:
return event.direction == "tx" and event.frame == frame
def _prepare_device(ctx: StateMapRunContext) -> None:
args = ctx.args
if not args.no_power_cycle:
serial = _import_serial()
ctx.relay = serial.Serial(args.relay_port, args.relay_baud, timeout=0.25)
_relay_settle(ctx.relay, args.relay_settle, ctx.logger)
_relay_command(ctx.relay, args.power_off_command, ctx.logger)
time.sleep(args.off_seconds)
ctx.device.reset_input_buffer()
ctx.detector = FrameDetector(sync_mode=args.sync)
_relay_command(ctx.relay, args.power_on_command, ctx.logger)
else:
ctx.device.reset_input_buffer()
ready = _wait_for_ready(ctx.device, ctx.detector, ctx.logger, args.ready_timeout, args.ready_heartbeats)
if args.require_ready and not ready:
raise SystemExit(2)
if args.pre_drain > 0:
ctx.logger.event(f"STATE pre-drain {args.pre_drain:.3f}s")
_read_for_collect(ctx, args.pre_drain)
def _hunt_trigger(ctx: StateMapRunContext, prefix: bytes) -> StateMapEvent | None:
args = ctx.args
primes = list(args.prime_frame or [])
for repeat_index in range(max(0, args.prime_repeat)):
for prime_index, prime in enumerate(primes, start=1):
ctx.logger.event(f"STATE prime {repeat_index + 1}/{args.prime_repeat}.{prime_index}")
_send_and_record(ctx, prime, "prime")
trigger = _read_until_trigger(ctx, args.prime_gap, prefix)
if trigger is not None:
return trigger
return _read_until_trigger(ctx, args.trigger_timeout, prefix)
def _read_until_trigger(ctx: StateMapRunContext, seconds: float, prefix: bytes) -> StateMapEvent | None:
ctx.logger.event(f"STATE wait visible-drain prefix={format_frame(prefix)} timeout={seconds:.3f}s")
deadline = time.monotonic() + max(0.0, seconds)
poll_interval = max(0.001, float(getattr(ctx.args, "trigger_poll_interval", 0.002)))
while time.monotonic() < deadline:
events = _read_available_for_collect(ctx)
for event in events:
if is_visible_drain_candidate(event) and event.frame.startswith(prefix):
return event
remaining = deadline - time.monotonic()
if remaining > 0:
time.sleep(min(poll_interval, remaining))
return None
def _read_for_collect(ctx: StateMapRunContext, seconds: float) -> list[StateMapEvent]:
observed: list[StateMapEvent] = []
deadline = time.monotonic() + max(0.0, seconds)
poll_interval = max(0.001, float(getattr(ctx.args, "read_poll_interval", 0.002)))
while time.monotonic() < deadline:
events = _read_available_for_collect(ctx)
if events:
observed.extend(events)
continue
remaining = deadline - time.monotonic()
if remaining > 0:
time.sleep(min(poll_interval, remaining))
return observed
def _read_available_for_collect(ctx: StateMapRunContext) -> list[StateMapEvent]:
waiting = getattr(ctx.device, "in_waiting", 0)
if waiting <= 0:
return []
return _record_rx_data(ctx, ctx.device.read(waiting))
def _record_rx_data(ctx: StateMapRunContext, data: bytes) -> list[StateMapEvent]:
observed: list[StateMapEvent] = []
dropped_before = ctx.detector.dropped_bytes
ctx.logger.chunk("RX", data)
for frame, label in ctx.detector.feed(data):
event = StateMapEvent(
direction="rx",
frame=frame,
timestamp_ms=_now_ms(),
label=label,
source="live",
)
ctx.events.append(event)
observed.append(event)
state_label = _state_frame_label(event)
ctx.logger.event(f"DETECT {label} {format_frame(frame)}")
if state_label:
ctx.logger.event(f"STATE_FRAME {state_label} {format_frame(frame)}")
dropped_now = ctx.detector.dropped_bytes - dropped_before
if dropped_now:
ctx.logger.event(
f"RESYNC dropped_bytes={dropped_now} total_dropped={ctx.detector.dropped_bytes} "
f"buffered={len(ctx.detector.buffer)}"
)
return observed
def _send_and_record(ctx: StateMapRunContext, frame: bytes, label: str) -> None:
ctx.device.write(frame)
ctx.device.flush()
ctx.logger.chunk("TX", frame)
ctx.logger.event(f"SENT {label} checksum_ok={int(frame_checksum_ok(frame))}")
ctx.events.append(
StateMapEvent(direction="tx", frame=frame, timestamp_ms=_now_ms(), label=label, source="live")
)
def _finish(ctx: StateMapRunContext, logger: BenchLogger, expected_word: int | None, json_path: Path | None) -> int:
analysis = analyze_events(ctx.events, expected_word=expected_word)
logger.emit()
logger.emit(format_analysis_report(analysis))
logger.emit()
logger.emit("Summary")
logger.emit(f"rx_frames={len(ctx.detector.frames)} trailing_unframed_bytes={len(ctx.detector.buffer)}")
logger.emit(f"resync_events={ctx.detector.resync_events} dropped_bytes={ctx.detector.dropped_bytes}")
for label, count in sorted(ctx.detector.labels.items()):
logger.emit(f"{label}={count}")
if json_path:
_write_json(json_path, analysis)
return 0
def _outcome(
*,
first_trigger: int | None,
first_force: int | None,
first_direct: int | None,
command7_replay: int | None,
events: list[StateMapEvent],
expected_word: int | None,
) -> dict[str, str]:
if first_trigger is None:
return {
"name": "no_visible_drain_token",
"confidence": "high",
"reason": "No device 07... frame was observed, so the alternate RX opportunity was not demonstrated.",
}
if first_force is None:
return {
"name": "token_observed_but_not_forced",
"confidence": "high",
"reason": "A device 07... frame was observed, but no selector-zero command-4 force followed it.",
}
readback_index = first_direct if first_direct is not None else command7_replay
if readback_index is not None:
value = _readback_value(events[readback_index].frame)
if expected_word is not None and value == expected_word:
return {
"name": "selector_zero_retained",
"confidence": "high",
"reason": f"E000[0] readback matched expected 0x{expected_word:04X}.",
}
return {
"name": "selector_zero_readback_unexpected",
"confidence": "medium",
"reason": f"A selector-zero readback appeared, but value 0x{value:04X} did not match the expected word.",
}
if _only_heartbeat_after_force(events, first_force):
return {
"name": "force_not_proven_heartbeat_only",
"confidence": "medium",
"reason": "After the force/readback turn, only heartbeat frames were observed.",
}
return {
"name": "force_not_proven",
"confidence": "medium",
"reason": "No direct or command-7-recovered selector-zero readback was observed after the force.",
}
def _state_warnings(events: list[StateMapEvent], trigger_index: int | None, force_index: int | None) -> list[str]:
warnings: list[str] = []
if trigger_index is None or force_index is None:
return warnings
trigger = events[trigger_index]
force = events[force_index]
if trigger.timestamp_ms is not None and force.timestamp_ms is not None:
guard_ms = force.timestamp_ms - trigger.timestamp_ms
if guard_ms < 2:
warnings.append(f"force_guard_short_{guard_ms}ms_may_overlap_TXI")
between = events[trigger_index + 1 : force_index]
for event in between:
if event.direction != "tx" or not event.frame:
continue
command = event.frame[0]
if command == 0x00:
warnings.append("command0_between_trigger_and_force_can_destroy_token")
elif command == 0x01:
warnings.append("command1_readback_between_trigger_and_force_can_spend_token")
elif command in {0x04, 0x05, 0x06}:
warnings.append(f"command{command}_between_trigger_and_force_can_spend_alternate_tail")
return sorted(set(warnings))
def _post_force_rx_labels(events: list[StateMapEvent], force_index: int | None) -> dict[str, int]:
if force_index is None:
return {}
counts: dict[str, int] = {}
for event in events[force_index + 1 :]:
if event.direction != "rx":
continue
label = _state_frame_label(event) or event.label or label_frame(event.frame) or "rx_unlabeled"
counts[label] = counts.get(label, 0) + 1
return counts
def _state_frame_label(event: StateMapEvent) -> str:
if event.direction == "rx" and is_visible_drain_candidate(event):
return "visible_drain_token_candidate"
if event.direction == "rx" and is_selector_zero_readback(event):
return "selector_zero_readback_proof_candidate"
if event.direction == "tx" and is_selector_zero_force(event):
return "selector_zero_force"
if event.direction == "tx" and event.frame == READBACK_E000_FRAME:
return "e000_readback_probe"
if event.direction == "tx" and event.frame == COMMAND7_REPEAT_FRAME:
return "command7_previous_frame_probe"
return ""
def _readback_info(event: StateMapEvent, expected_word: int | None = None) -> dict[str, Any]:
value = _readback_value(event.frame)
return {
"frame": event.frame_text,
"timestamp_ms": event.timestamp_ms,
"qq": event.frame[2],
"value": value,
"value_hex": f"0x{value:04X}",
"matches_expected": expected_word is not None and value == expected_word,
}
def _readback_value(frame: bytes) -> int:
return ((frame[3] << 8) | frame[4]) & 0xFFFF
def _first_after(indexes: list[int], anchor: int | None) -> int | None:
if anchor is None:
return indexes[0] if indexes else None
for index in indexes:
if index > anchor:
return index
return None
def _only_heartbeat_after_force(events: list[StateMapEvent], force_index: int) -> bool:
rx_after = [event for event in events[force_index + 1 :] if event.direction == "rx"]
return bool(rx_after) and all(event.frame == HEARTBEAT_FRAME for event in rx_after)
def _event_payload(event: StateMapEvent) -> dict[str, Any]:
return {
"direction": event.direction,
"frame": event.frame_text,
"timestamp_ms": event.timestamp_ms,
"label": event.label,
"state_label": _state_frame_label(event),
"source": event.source,
}
def _word_payload(word: int | None) -> dict[str, Any] | None:
if word is None:
return None
return {"value": word & 0xFFFF, "hex": f"0x{word & 0xFFFF:04X}"}
def _emit_plan(logger: BenchLogger, args: argparse.Namespace, force_frame: bytes, expected_word: int, preset_note: str) -> None:
logger.emit(f"preset={args.preset} note={preset_note}")
logger.emit(f"force={format_frame(force_frame)} checksum_ok={int(frame_checksum_ok(force_frame))}")
logger.emit(f"expected_e0000=0x{expected_word:04X}")
logger.emit(f"readback={format_frame(args.readback_frame)} checksum_ok={int(frame_checksum_ok(args.readback_frame))}")
logger.emit(f"command7_probe={int(not args.no_command7_probe)} frame={format_frame(COMMAND7_REPEAT_FRAME)}")
logger.emit("guardrails=no command-0/command-1 is sent between trigger and force by this runner")
def _print_dry_run(
args: argparse.Namespace,
log_path: Path,
force_frame: bytes,
expected_word: int,
preset_note: str,
stdout: TextIO,
) -> None:
print("PT2 state-map proof runner", file=stdout)
print(f"device={args.port} {args.baud} 8N1", file=stdout)
print(f"relay={args.relay_port} {args.relay_baud}", file=stdout)
print(f"power_cycle={int(not args.no_power_cycle)}", file=stdout)
print(f"preset={args.preset} note={preset_note}", file=stdout)
print(f"force={format_frame(force_frame)} checksum_ok={int(frame_checksum_ok(force_frame))}", file=stdout)
print(f"expected_e0000=0x{expected_word:04X}", file=stdout)
print(f"readback={format_frame(args.readback_frame)} checksum_ok={int(frame_checksum_ok(args.readback_frame))}", file=stdout)
for prime in args.prime_frame or []:
print(f"prime={format_frame(prime)} checksum_ok={int(frame_checksum_ok(prime))}", file=stdout)
print(f"prime_repeat={args.prime_repeat} prime_gap={args.prime_gap:.3f}", file=stdout)
print(f"trigger_prefix={format_frame(_parse_prefix(args.trigger_prefix))} timeout={args.trigger_timeout:.3f}", file=stdout)
print(f"force_guard={args.force_guard:.3f} post_force_listen={args.post_force_listen:.3f}", file=stdout)
print(f"command7_probe={int(not args.no_command7_probe)} frame={format_frame(COMMAND7_REPEAT_FRAME)}", file=stdout)
print(f"log={log_path}", file=stdout)
def _expected_word_for_force(frame: bytes, default: int) -> int:
if len(frame) == 6 and frame_checksum_ok(frame) and frame[0] == 0x04 and frame[1] == 0 and frame[2] == 0:
return ((frame[3] << 8) | 0x0080) & 0xFFFF
return default
def _parse_prefix(text: str) -> bytes:
normalized = text.strip().replace(",", " ").replace(":", " ").replace("-", " ").replace("_", " ")
if not normalized:
return b""
parts = normalized.split()
if len(parts) == 1:
compact = parts[0]
if compact.lower().startswith("0x"):
compact = compact[2:]
if compact.upper().startswith("H'"):
compact = compact[2:]
if len(compact) % 2:
compact = "0" + compact
return bytes(int(compact[index : index + 2], 16) for index in range(0, len(compact), 2))
return bytes(int(part, 16) for part in parts)
def _parse_hex_bytes(text: str) -> bytes:
normalized = text.strip().replace(",", " ").replace(":", " ").replace("-", " ").replace("_", " ")
if not normalized:
return b""
return bytes(int(part, 16) for part in normalized.split())
def _timestamp_to_ms(text: str) -> int:
hour, minute, rest = text.split(":")
second, milli = rest.split(".")
return ((int(hour) * 60 + int(minute)) * 60 + int(second)) * 1000 + int(milli)
def _now_ms() -> int:
now = datetime.now()
return ((now.hour * 60 + now.minute) * 60 + now.second) * 1000 + now.microsecond // 1000
def _int_arg(text: str) -> int:
return int(text, 0)
def _prompt_screen(label: str, logger: BenchLogger) -> None:
note = input(f"{label}: type observed LCD text, or press Enter to skip: ").strip()
logger.event(f"SCREEN {label}: {note or '(no note)'}")
def _write_json(path: Path, analysis: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(analysis, indent=2, sort_keys=True) + "\n", encoding="utf-8")
_CHUNK_RE = re.compile(
r"^(?P<ts>\d\d:\d\d:\d\d\.\d{3})\s+(?P<direction>TX|RX)\s+\d+\s+bytes\s+(?P<hex>[0-9A-Fa-f ]+)$"
)
_DETECT_RE = re.compile(
r"^(?P<ts>\d\d:\d\d:\d\d\.\d{3})\s+DETECT\s+(?P<label>\S+)\s+(?P<hex>[0-9A-Fa-f ]+)$"
)
__all__ = [
"COMMAND7_REPEAT_FRAME",
"CONNECT_FORCE_PRESETS",
"READBACK_E000_FRAME",
"StateMapEvent",
"analyze_events",
"build_arg_parser",
"format_analysis_report",
"main",
"parse_bench_log",
"resolve_force",
]

8
h8536_ccu_seed_hints.py Normal file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env python3
"""Compatibility wrapper for the H8/536 CCU seed-hint miner."""
from h8536.ccu_seed_hints import main
if __name__ == "__main__":
raise SystemExit(main())

8
h8536_eeprom_layout.py Normal file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env python3
"""Compatibility wrapper for the H8/536 EEPROM layout miner."""
from h8536.eeprom_layout import main
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,5 @@
from h8536.emulator.state_search import main
if __name__ == "__main__":
raise SystemExit(main())

8
h8536_rx_branch_trace.py Normal file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env python3
"""Compatibility wrapper for the H8/536 SCI1 RX branch trace CLI."""
from h8536.rx_branch_trace import main
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env python3
"""Run or analyze the PT2 state-map selector-zero proof sequence."""
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from h8536.state_map_runner import main
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,118 @@
import io
import json
import tempfile
import unittest
from pathlib import Path
from h8536.ccu_seed_hints import (
analyze_ccu_seed_hints,
checksum,
encode_host_frame,
frame_hex,
main,
selector_bytes,
write_ccu_seed_hints,
)
def reference(address: int) -> dict:
return {"address": address}
def instruction(address: int, mnemonic: str, operands: str = "", refs: list[int] | None = None) -> dict:
return {
"address": address,
"mnemonic": mnemonic,
"operands": operands,
"text": f"{mnemonic} {operands}".strip(),
"references": [reference(item) for item in (refs or [])],
"targets": [],
}
def payload() -> dict:
return {
"instructions": [
instruction(0xC000, "MOV:G.W", "#H'0006, R3"),
instruction(0xC004, "MOV:G.W", "@(-H'2000,R3), R0"),
instruction(0xC008, "MOV:G.W", "R1, @H'E1EC", [0xE1EC]),
],
"call_graph": {
"nodes": [
{"start": 0xC000, "end": 0xC0FF, "label": "loc_C000"},
],
},
"indirect_flow": {
"sites": [
{
"table": {
"base": 0x28A6,
"entries": [
{"index": 0, "entry_address": 0x28A6, "target": 0x2CB9, "target_label": "loc_2CB9"},
{"index": 1, "entry_address": 0x28A8, "target": 0x1234, "target_label": "loc_1234"},
{"index": 2, "entry_address": 0x28AA, "target": 0x1234, "target_label": "loc_1234"},
],
},
}
],
},
}
class CcuSeedHintsTest(unittest.TestCase):
def test_selector_encoding_matches_loc_622b_ranges(self):
self.assertEqual(selector_bytes(0x000), (0x00, 0x00))
self.assertEqual(selector_bytes(0x07F), (0x00, 0x7F))
self.assertEqual(selector_bytes(0x080), (0x01, 0x00))
self.assertEqual(selector_bytes(0x17F), (0x01, 0xFF))
self.assertEqual(selector_bytes(0x180), (0x02, 0x00))
self.assertEqual(selector_bytes(0x1FF), (0x02, 0x7F))
def test_frame_encoding_uses_xor_seed(self):
frame = encode_host_frame(0x00, 0x000, 0x8080)
self.assertEqual(frame, [0x00, 0x00, 0x00, 0x80, 0x80, 0x5A])
self.assertEqual(checksum(frame[:5]), frame[5])
self.assertEqual(frame_hex(frame), "00 00 00 80 80 5A")
def test_analysis_emits_seed_plan_and_selector_reasons(self):
analysis = analyze_ccu_seed_hints(payload(), rom_path=None)
by_selector = {
int(item["selector"]): item
for item in analysis["selector_candidates"]
}
self.assertEqual(analysis["kind"], "ccu_seed_hints")
self.assertIn(0x000, by_selector)
self.assertIn(0x0F6, by_selector)
self.assertIn("00 00 00 80 80 5A", [step["frame"] for step in analysis["seed_plan"]["steps"]])
self.assertIn("01 01 76 00 00 2C", [step["readback_frame"] for step in analysis["seed_plan"]["steps"]])
def test_write_json_output(self):
with tempfile.TemporaryDirectory() as tmp:
input_path = Path(tmp) / "rom.json"
output_path = Path(tmp) / "hints.json"
input_path.write_text(json.dumps(payload()), encoding="utf-8")
write_ccu_seed_hints(input_path, output_path, rom_path=None, as_json=True)
written = json.loads(output_path.read_text(encoding="utf-8"))
self.assertEqual(written["kind"], "ccu_seed_hints")
self.assertGreaterEqual(written["summary"]["candidate_count"], 1)
def test_cli_writes_text_report(self):
with tempfile.TemporaryDirectory() as tmp:
input_path = Path(tmp) / "rom.json"
output_path = Path(tmp) / "hints.txt"
input_path.write_text(json.dumps(payload()), encoding="utf-8")
stdout = io.StringIO()
rc = main([str(input_path), "--rom", str(Path(tmp) / "missing.bin"), "--out", str(output_path)], stdout=stdout)
self.assertEqual(rc, 0)
self.assertIn("wrote", stdout.getvalue())
self.assertIn("Candidate Fake-CCU Seed Plan", output_path.read_text(encoding="utf-8"))
if __name__ == "__main__":
unittest.main()

120
tests/test_eeprom_layout.py Normal file
View File

@@ -0,0 +1,120 @@
import io
import json
import tempfile
import unittest
from pathlib import Path
from h8536.eeprom_layout import (
analyze_eeprom_layout,
format_text_report,
main,
write_eeprom_layout,
)
def reference(address: int) -> dict:
return {"address": address}
def instruction(address: int, mnemonic: str, operands: str = "", refs: list[int] | None = None) -> dict:
return {
"address": address,
"mnemonic": mnemonic,
"operands": operands,
"text": f"{mnemonic} {operands}".strip(),
"references": [reference(item) for item in (refs or [])],
"targets": [],
}
def payload() -> dict:
return {
"instructions": [
instruction(0x410C, "MOV:G.W", "R5, @(-H'0C00,R0)"),
instruction(0x41E8, "MOV:G.W", "R5, @(-H'0850,R1)"),
instruction(0x40FA, "CMP:G.W", "#H'6B6F, @H'F402", [0xF402]),
instruction(0xBD3D, "MOV:G.B", "@(-H'3A9B,R4), R1"),
instruction(0xBD45, "MOV:G.W", "R0, @(-H'0C00,R1)"),
instruction(0xBD49, "BTST.B", "#7, @H'F76E", [0xF76E]),
],
"call_graph": {
"nodes": [
{"start": 0x4000, "end": 0x4216, "label": "loc_4000"},
{"start": 0xBD0E, "end": 0xBD67, "label": "loc_BD0E"},
],
},
}
def rom_bytes() -> bytes:
rom = bytearray([0xFF] * 0xCB00)
rom[0xC564 : 0xC564 + 0x400] = b"\x00" * 0x400
rom[0xC564 + 0x015 * 2 : 0xC564 + 0x015 * 2 + 2] = b"\x40\xAA"
rom[0xC966:0xC968] = b"\x6B\x6F"
rom[0xC968:0xC96A] = b"\xFE\x00"
rom[0xCA0E:0xCA10] = b"\x80\x00"
return bytes(rom)
class EepromLayoutTest(unittest.TestCase):
def test_analysis_extracts_factory_defaults_records_and_selector_mapping(self):
with tempfile.TemporaryDirectory() as tmp:
rom_path = Path(tmp) / "rom.bin"
rom_path.write_bytes(rom_bytes())
analysis = analyze_eeprom_layout(payload(), rom_path=rom_path)
self.assertEqual(analysis["kind"], "eeprom_layout")
self.assertEqual(analysis["factory_defaults"]["entries"][1]["factory_word_hex"], "0x6B6F")
self.assertEqual(analysis["persistent_records"][3]["ram_base_hex"], "H'F7C8")
self.assertEqual(analysis["persistent_records"][3]["eeprom_base_hex"], "0x300")
mapped = analysis["serial_persistence_mapping"]["entries"][0]
self.assertEqual(mapped["selector_hex"], "0x015")
self.assertEqual(mapped["shadow_address_hex"], "H'F4AA")
self.assertEqual(mapped["eeprom_word_offset_hex"], "0xAA")
self.assertEqual(mapped["factory_word_hex"], "0x8000")
def test_text_report_mentions_core_layout(self):
with tempfile.TemporaryDirectory() as tmp:
rom_path = Path(tmp) / "rom.bin"
rom_path.write_bytes(rom_bytes())
analysis = analyze_eeprom_layout(payload(), rom_path=rom_path)
text = format_text_report(analysis)
self.assertIn("Persistent 8-Byte Records", text)
self.assertIn("selector 0x015", text)
self.assertIn("F76E", text)
def test_write_json_output(self):
with tempfile.TemporaryDirectory() as tmp:
input_path = Path(tmp) / "rom.json"
output_path = Path(tmp) / "eeprom.json"
rom_path = Path(tmp) / "rom.bin"
input_path.write_text(json.dumps(payload()), encoding="utf-8")
rom_path.write_bytes(rom_bytes())
write_eeprom_layout(input_path, output_path, rom_path=rom_path, as_json=True)
written = json.loads(output_path.read_text(encoding="utf-8"))
self.assertEqual(written["kind"], "eeprom_layout")
self.assertEqual(written["summary"]["persistent_record_count"], 16)
def test_cli_writes_text_report(self):
with tempfile.TemporaryDirectory() as tmp:
input_path = Path(tmp) / "rom.json"
output_path = Path(tmp) / "eeprom.txt"
rom_path = Path(tmp) / "rom.bin"
input_path.write_text(json.dumps(payload()), encoding="utf-8")
rom_path.write_bytes(rom_bytes())
stdout = io.StringIO()
rc = main([str(input_path), "--rom", str(rom_path), "--out", str(output_path)], stdout=stdout)
self.assertEqual(rc, 0)
self.assertIn("wrote", stdout.getvalue())
self.assertIn("EEPROM Layout Report", output_path.read_text(encoding="utf-8"))
if __name__ == "__main__":
unittest.main()

View File

@@ -37,7 +37,8 @@ class EmulatorAddressingTest(unittest.TestCase):
self.assertEqual(memory.read16(0xF402), 0x6B6F)
self.assertEqual(memory.p9_bus.fast_read_word(0x0010), (True, 0x8000))
self.assertEqual(memory.p9_bus.fast_read_word(0x0110), (True, 0x8000))
self.assertEqual(memory.p9_bus.fast_read_word(0x0002), (True, 0x2020))
self.assertEqual(memory.p9_bus.fast_read_word(0x0002), (True, 0x6B6F))
self.assertEqual(memory.p9_bus.fast_read_word(0x0102), (True, 0x2020))
def test_txi_indexed_byte_load_uses_signed_word_displacement_and_full_index_register(self):
rom = rom_with_reset()

View File

@@ -0,0 +1,66 @@
import json
import tempfile
import unittest
from pathlib import Path
from h8536.emulator.eeprom_image import build_eeprom_snapshot, format_eeprom_snapshot, write_eeprom_snapshot
from h8536.emulator.memory import MemoryMap
from h8536.emulator.peripherals.x24164 import X24164_LOGICAL_SIZE
def rom_bytes() -> bytes:
rom = bytearray([0xFF] * 0xCB00)
rom[0xC564 : 0xC564 + 0x400] = b"\x00" * 0x400
rom[0xC564 + 0x18 * 2 : 0xC564 + 0x18 * 2 + 2] = b"\x60\x10"
rom[0xC964 : 0xC964 + 0x100] = b"\x00" * 0x100
rom[0xC966:0xC968] = b"\x6B\x6F"
rom[0xC974:0xC976] = b"\x80\x00"
return bytes(rom)
class EmulatorEepromImageTest(unittest.TestCase):
def test_load_dump_and_write_log_cover_full_logical_image(self):
memory = MemoryMap(rom_bytes())
image = bytes([index & 0xFF for index in range(X24164_LOGICAL_SIZE)])
memory.load_eeprom_image(image)
self.assertEqual(memory.dump_eeprom_image(), image)
self.assertEqual(memory.external[0xF400], 0x00)
self.assertEqual(memory.external[0xF402], 0x02)
self.assertTrue(memory.p9_bus.fast_write_word(0x0810, 0x1234))
self.assertEqual(memory.p9_bus.fast_read_word(0x0810), (True, 0x1234))
self.assertEqual(len(memory.p9_bus.x24164_bus.write_events), 2)
self.assertIn("addr=810", memory.p9_bus.x24164_bus.write_log_lines()[-2])
def test_snapshot_reports_records_factory_diffs_and_mapped_writes(self):
memory = MemoryMap(rom_bytes())
memory.seed_factory_eeprom_and_shadow()
memory.p9_bus.fast_write_word(0x0110, 0x1234)
report = build_eeprom_snapshot(memory, rom_bytes=rom_bytes())
text = format_eeprom_snapshot(report)
self.assertEqual(report["summary"]["write_word_count"], 1)
self.assertEqual(report["write_word_events"][0]["new_word_hex"], "0x1234")
self.assertEqual(report["write_word_events"][0]["mapped_selectors_hex"], ["0x018"])
self.assertEqual(report["factory_diffs"][0]["address_hex"], "0x110")
self.assertIn("EEPROM Word Writes", text)
self.assertIn("selector", text)
def test_write_snapshot_json(self):
memory = MemoryMap(rom_bytes())
memory.seed_factory_eeprom_and_shadow()
with tempfile.TemporaryDirectory() as tmp:
output = Path(tmp) / "eeprom.json"
write_eeprom_snapshot(memory, output, rom_bytes=rom_bytes(), as_json=True, include_image_hex=True)
payload = json.loads(output.read_text(encoding="utf-8"))
self.assertEqual(payload["kind"], "emulator_eeprom_snapshot")
self.assertEqual(len(payload["image_hex"]), X24164_LOGICAL_SIZE * 2)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,120 @@
import io
import json
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from h8536.emulator.state_search import (
SearchCase,
SearchResult,
StatePatch,
build_cases,
classify_display,
main,
parse_address,
parse_matrix_patch,
parse_single_patch,
target_matches,
)
class EmulatorStateSearchTest(unittest.TestCase):
def test_parse_patch_accepts_hex_address_and_value(self):
patch = parse_single_patch("F730=0x41", size=1)
self.assertEqual(patch, StatePatch(1, 0xF730, 0x41, "user"))
self.assertEqual(patch.label(), "byte:H'F730=0x41")
def test_parse_matrix_patch_expands_values(self):
patches = parse_matrix_patch("E000=0x4080,0x8080", size=2)
self.assertEqual(patches, [
StatePatch(2, 0xE000, 0x4080, "user"),
StatePatch(2, 0xE000, 0x8080, "user"),
])
def test_parse_address_accepts_h_quote(self):
self.assertEqual(parse_address("H'F970"), 0xF970)
def test_connect_queue_preset_builds_small_rom_driven_matrix(self):
parser = __import__("h8536.emulator.state_search", fromlist=["build_arg_parser"]).build_arg_parser()
args = parser.parse_args(["--preset", "connect-queue"])
cases = build_cases(args)
self.assertEqual(len(cases), 25)
first = cases[0]
self.assertEqual(first.pc, 0x2806)
self.assertIn(StatePatch(2, 0xF970, 0x0000, "preset"), first.patches)
self.assertIn(StatePatch(2, 0xE000, 0x0000, "preset"), first.patches)
def test_custom_matrix_combines_fixed_and_matrix_patches(self):
parser = __import__("h8536.emulator.state_search", fromlist=["build_arg_parser"]).build_arg_parser()
args = parser.parse_args([
"--preset",
"custom",
"--pc",
"0x2CB9",
"--byte",
"F730=0",
"--matrix-word",
"E000=0x4080,0x8080",
])
cases = build_cases(args)
self.assertEqual(len(cases), 2)
self.assertEqual(cases[0], SearchCase((StatePatch(1, 0xF730, 0, "user"), StatePatch(2, 0xE000, 0x4080, "user")), 0x2CB9))
def test_classify_display(self):
self.assertEqual(classify_display(" CONNECT: OK | "), "ok")
self.assertEqual(classify_display(" CONNECT:DXC-637 | "), "dxc")
self.assertEqual(classify_display(" CONNECT:NOT ACT | "), "not-act")
def test_target_matching(self):
self.assertTrue(target_matches("ok", "ok"))
self.assertTrue(target_matches("dxc", "any-connect"))
self.assertTrue(target_matches("ok", "changed"))
self.assertFalse(target_matches("not-act", "changed"))
def test_cli_dry_run_lists_cases(self):
stdout = io.StringIO()
rc = main(["--dry-run", "--preset", "connect-branch", "--limit", "2"], stdout=stdout)
self.assertEqual(rc, 0)
output = stdout.getvalue()
self.assertIn("preset=connect-branch cases=2", output)
self.assertIn("case[0] pc=H'2CB9", output)
def test_cli_json_output_uses_results_from_run_search(self):
fake_result = SearchResult(
case_index=0,
patches=(StatePatch(2, 0xE000, 0x8080, "preset"),),
pc=0x2CB9,
steps=10,
stopped_reason="stop_pc",
final_pc=0xFFFF,
display=" CONNECT: OK | ",
line0=" CONNECT: OK ",
outcome="ok",
f730=0x81,
e000=0x8080,
f9b4=0,
f9b9=0,
)
with tempfile.TemporaryDirectory() as tmpdir:
path = Path(tmpdir) / "out.json"
stdout = io.StringIO()
with patch("h8536.emulator.state_search.run_search", return_value=[fake_result]):
rc = main(["--preset", "custom", "--word", "E000=0x8080", "--json-out", str(path)], stdout=stdout)
payload = json.loads(path.read_text(encoding="utf-8"))
self.assertEqual(rc, 0)
self.assertIn("hits=1", stdout.getvalue())
self.assertEqual(payload["hits"][0]["outcome"], "ok")
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,93 @@
import io
import json
import tempfile
import unittest
from pathlib import Path
from h8536.rx_branch_trace import analyze_rx_branch_trace, format_text_report, main
def ins(address: int, text: str | None = None) -> dict[str, object]:
return {
"address": address,
"text": text or f"INS_{address:04X}",
"mnemonic": (text or "NOP").split()[0],
"operands": "",
"kind": "normal",
"targets": [],
"references": [],
}
def fixture_payload() -> dict[str, object]:
addresses = {
0x3FEF, 0x3FF3, 0x3FF5, 0x3FF9, 0x3FFD, 0x4001, 0x4003, 0x4007,
0xBB57, 0xBB5B, 0xBB5F, 0xBB63, 0xBB67, 0xBB6D, 0xBB71, 0xBB75,
0xBB77, 0xBB7D, 0xBB82, 0xBB84, 0xBB88, 0xBB8A, 0xBB90, 0xBB96,
0xBB9A, 0xBB9C, 0xBB9E, 0xBBA3, 0xBBAB, 0xBBB0, 0xBBB3, 0xBBCB,
0xBBCF, 0xBBD3, 0xBBD6, 0xBBD8, 0xBBDC, 0xBBE0, 0xBBE4, 0xBBE8,
0xBBEC, 0xBBF0, 0xBBF3, 0xBBF7, 0xBBFD, 0xBC01, 0xBC08, 0xBC0C,
0xBC0F, 0xBC13, 0xBC15, 0xBC19, 0xBC1D, 0xBC20, 0xBC24, 0xBC29,
0xBC2E, 0xBC33, 0xBC37, 0xBC3A, 0xBC3C, 0xBC3E, 0xBC42, 0xBC45,
0xBC4A, 0xBC4F, 0xBC54, 0xBC5C, 0xBC60, 0xBC63, 0xBC67, 0xBC69,
0xBC75, 0xBC79, 0xBC82, 0xBC86, 0xBCB0, 0xBCCD, 0xBCD0, 0xBCD7,
0xBCE0, 0xBCE8, 0xBCEC, 0xBCF0, 0xBCF6, 0xBCFA, 0xBCFD, 0xBD04,
0xBD08, 0xBD0B, 0xBD0E, 0xBD1A, 0xBD1E, 0xBD22, 0xBD26, 0xBD35,
0xBD64, 0xBD67, 0xBD6D, 0xBD75, 0xBD79, 0xBD80, 0xBD85, 0xBD94,
0xBD9A, 0xBDB5, 0xBDBF, 0xBDC2, 0xBDC8, 0xBDD0, 0xBDD4, 0xBDDB,
0xBDE5, 0xBDE9, 0xBDED, 0xBDF3, 0xBDFB, 0xBDFF, 0xBE05, 0xBE09,
0xBE0D, 0xBE11, 0xBE15, 0xBE19, 0xBE1D, 0xBE22, 0xBE27, 0xBE29,
0xBE2D, 0xBE31, 0xBE33, 0xBE37, 0xBE3C, 0xBE3E, 0xBE43, 0xBE47,
0xBE4D, 0xBE52, 0xBE5A, 0xBE62, 0xBE6A, 0xBE70, 0xBE78, 0xBE80,
0xBE82, 0xBE84, 0xBE88, 0xBE91, 0xBE95, 0xBE99, 0xBE9D, 0xBE9E,
0xBEA5, 0xBEA9, 0xBEAD, 0xBEAF, 0xBEB5, 0xBEBB, 0xBEBF, 0xBEC1,
0xBEC5, 0xBECB, 0xBED1, 0xBED5, 0xBEE4,
}
return {"instructions": [ins(address) for address in sorted(addresses)]}
class RxBranchTraceTest(unittest.TestCase):
def test_analyzes_dispatch_split_and_commands(self):
analysis = analyze_rx_branch_trace(fixture_payload())
self.assertEqual(analysis["summary"]["confidence"], "high")
self.assertEqual(analysis["frame_model"]["checksum_seed"], 0x5A)
self.assertTrue(analysis["stages"][2]["present"])
self.assertIn("FAA2 != 0", analysis["stages"][2]["summary"])
commands = {command["command"]: command for command in analysis["commands"]}
self.assertIn("continuation path only", commands[0x04]["availability"])
self.assertIn("selector zero is special", "\n".join(commands[0x04]["side_effects"]))
self.assertIn("selectors 0x006C", "\n".join(commands[0x05]["side_effects"]))
self.assertIn("previous finalized TX frame", commands[0x07]["summary"])
def test_text_report_mentions_bench_implications(self):
text = format_text_report(analyze_rx_branch_trace(fixture_payload()))
self.assertIn("H8/536 SCI1 RX Branch Trace", text)
self.assertIn("cmd 0x04 continuation_set_value_candidate", text)
self.assertIn("standalone command 4 frame from idle should not hit BD0E", text)
self.assertIn("Command 5 is not a generic always-live ACK", text)
self.assertIn("Selector Decode", text)
self.assertIn("TXI/RXI race and continuation collapse", text)
self.assertIn("RX-to-TX Feedback Loops", text)
def test_cli_writes_json_output(self):
with tempfile.TemporaryDirectory() as tmp:
input_path = Path(tmp) / "rom.json"
output_path = Path(tmp) / "rx.json"
input_path.write_text(json.dumps(fixture_payload()), encoding="utf-8")
stdout = io.StringIO()
rc = main(["--json", "--out", str(output_path), str(input_path)], stdout=stdout)
self.assertEqual(rc, 0)
self.assertIn("wrote", stdout.getvalue())
payload = json.loads(output_path.read_text(encoding="utf-8"))
self.assertEqual(payload["kind"], "rx_branch_trace")
self.assertIn("downstream_traces", payload)
self.assertIn("feedback_loops", payload)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,114 @@
import io
import json
import tempfile
import unittest
from pathlib import Path
from h8536.state_map_runner import (
CONNECT_FORCE_PRESETS,
StateMapEvent,
analyze_events,
format_analysis_report,
main,
parse_bench_log,
)
class StateMapRunnerTest(unittest.TestCase):
def test_force_presets_match_expected_selector_zero_words(self):
self.assertEqual(CONNECT_FORCE_PRESETS["dxc"][0], bytes.fromhex("04000040001E"))
self.assertEqual(CONNECT_FORCE_PRESETS["dxc"][1], 0x4080)
self.assertEqual(CONNECT_FORCE_PRESETS["ok"][0], bytes.fromhex("0400008000DE"))
self.assertEqual(CONNECT_FORCE_PRESETS["ok"][1], 0x8080)
self.assertEqual(CONNECT_FORCE_PRESETS["both"][0], bytes.fromhex("040000C0009E"))
self.assertEqual(CONNECT_FORCE_PRESETS["both"][1], 0xC080)
def test_analyzes_successful_selector_zero_readback(self):
events = [
StateMapEvent("rx", bytes.fromhex("0780C060205D"), timestamp_ms=1000),
StateMapEvent("tx", bytes.fromhex("0400008000DE"), timestamp_ms=1006),
StateMapEvent("tx", bytes.fromhex("01000000005B"), timestamp_ms=1060),
StateMapEvent("rx", bytes.fromhex("04001280804C"), timestamp_ms=1070),
]
analysis = analyze_events(events, expected_word=0x8080)
self.assertEqual(analysis["outcome"]["name"], "selector_zero_retained")
self.assertEqual(analysis["direct_readbacks"][0]["value"], 0x8080)
self.assertTrue(analysis["direct_readbacks"][0]["matches_expected"])
def test_warns_when_command1_readback_is_between_trigger_and_force(self):
events = [
StateMapEvent("rx", bytes.fromhex("07804040A07D"), timestamp_ms=1000),
StateMapEvent("tx", bytes.fromhex("01000000005B"), timestamp_ms=1005),
StateMapEvent("tx", bytes.fromhex("04000040001E"), timestamp_ms=1010),
]
analysis = analyze_events(events, expected_word=0x4080)
self.assertIn("command1_readback_between_trigger_and_force_can_spend_token", analysis["warnings"])
self.assertEqual(analysis["outcome"]["name"], "force_not_proven")
def test_parse_bench_log_uses_detect_lines_for_rx_and_tx_chunks_for_host_frames(self):
log = (
"00:00:01.000 RX 006 bytes 07 80 C0 60 20 5D\n"
"00:00:01.000 DETECT visible_C0_6020_family_candidate 07 80 C0 60 20 5D\n"
"00:00:01.006 TX 006 bytes 04 00 00 80 00 DE\n"
)
events = parse_bench_log(log)
self.assertEqual([(event.direction, event.frame.hex()) for event in events], [
("rx", "0780c060205d"),
("tx", "0400008000de"),
])
def test_format_report_mentions_outcome_and_readback_value(self):
analysis = analyze_events(
[
StateMapEvent("rx", bytes.fromhex("0780C060205D"), timestamp_ms=1000),
StateMapEvent("tx", bytes.fromhex("0400008000DE"), timestamp_ms=1006),
StateMapEvent("rx", bytes.fromhex("04001280804C"), timestamp_ms=1070),
],
expected_word=0x8080,
)
report = format_analysis_report(analysis)
self.assertIn("outcome=selector_zero_retained", report)
self.assertIn("value=0x8080 expected", report)
def test_cli_dry_run_prints_state_map_sequence(self):
stdout = io.StringIO()
rc = main(["--dry-run", "--preset", "dxc", "--prime-frame", "01 80 40 40 30 EB"], stdout=stdout)
output = stdout.getvalue()
self.assertEqual(rc, 0)
self.assertIn("PT2 state-map proof runner", output)
self.assertIn("force=04 00 00 40 00 1E", output)
self.assertIn("expected_e0000=0x4080", output)
self.assertIn("prime=01 80 40 40 30 EB", output)
def test_cli_analyze_log_writes_json(self):
log = (
"00:00:01.000 DETECT visible_C0_6020_family_candidate 07 80 C0 60 20 5D\n"
"00:00:01.006 TX 006 bytes 04 00 00 80 00 DE\n"
"00:00:01.070 DETECT table_readback_candidate 04 00 12 80 80 4C\n"
)
with tempfile.TemporaryDirectory() as tmpdir:
log_path = Path(tmpdir) / "capture.txt"
json_path = Path(tmpdir) / "analysis.json"
log_path.write_text(log, encoding="utf-8")
stdout = io.StringIO()
rc = main(["--analyze-log", str(log_path), "--preset", "ok", "--json-out", str(json_path)], stdout=stdout)
payload = json.loads(json_path.read_text(encoding="utf-8"))
self.assertEqual(rc, 0)
self.assertIn("outcome=selector_zero_retained", stdout.getvalue())
self.assertEqual(payload["outcome"]["name"], "selector_zero_retained")
if __name__ == "__main__":
unittest.main()