EEPROM layout
This commit is contained in:
18
README.md
18
README.md
@@ -34,6 +34,7 @@ To run the newer sidecar protocol and gate/queue analysis tools:
|
|||||||
.\.venv\Scripts\python.exe h8536_report_source_trace.py build\rom_decompiled.json --out build\rom_report_sources.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_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_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_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
|
.\.venv\Scripts\python.exe h8536_protocol_capture.py ROM\rcp-txd-idle-only.txt
|
||||||
```
|
```
|
||||||
@@ -53,6 +54,7 @@ To start the current emulator harness:
|
|||||||
.\.venv\Scripts\python.exe scripts\state_map_runner.py --analyze-log captures\ack-race-000-001.txt
|
.\.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_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_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.
|
The real-device bench helper uses `pyserial`; install repo dependencies with `.\.venv\Scripts\python.exe -m pip install -r requirements.txt` if needed.
|
||||||
@@ -83,6 +85,7 @@ The real-device bench helper uses `pyserial`; install repo dependencies with `.\
|
|||||||
- 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.
|
- 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.
|
- 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 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.
|
- 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.
|
- 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.
|
- Scans unreached ROM ranges for ASCII strings and pointer-table candidates.
|
||||||
@@ -96,7 +99,7 @@ The real-device bench helper uses `pyserial`; install repo dependencies with `.\
|
|||||||
- Handles the E-clock transfer instructions `MOVFPE` and `MOVTPE`.
|
- 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.
|
- 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.
|
- 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 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 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 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.
|
||||||
@@ -119,6 +122,8 @@ 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.
|
- 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`.
|
- 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 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.
|
- 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.
|
- 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.
|
- 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.
|
||||||
@@ -147,7 +152,9 @@ build/rom_serial_gate.txt
|
|||||||
build/rom_report_sources.txt
|
build/rom_report_sources.txt
|
||||||
build/rom_table_xrefs.txt
|
build/rom_table_xrefs.txt
|
||||||
build/rom_ccu_seed_hints.txt
|
build/rom_ccu_seed_hints.txt
|
||||||
|
build/rom_eeprom_layout.txt
|
||||||
build/rom_consistency.txt
|
build/rom_consistency.txt
|
||||||
|
build/emulator-eeprom-boot.txt
|
||||||
build/callgraph.dot
|
build/callgraph.dot
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -212,6 +219,7 @@ python h8536_rx_branch_trace.py --help
|
|||||||
python h8536_report_source_trace.py --help
|
python h8536_report_source_trace.py --help
|
||||||
python h8536_table_xrefs.py --help
|
python h8536_table_xrefs.py --help
|
||||||
python h8536_ccu_seed_hints.py --help
|
python h8536_ccu_seed_hints.py --help
|
||||||
|
python h8536_eeprom_layout.py --help
|
||||||
python h8536_consistency.py --help
|
python h8536_consistency.py --help
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -220,6 +228,7 @@ python h8536_consistency.py --help
|
|||||||
- `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_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_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_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.
|
- `h8536_consistency.py`: flags JSON-to-pseudocode semantic hazards such as byte immediates written to word destinations.
|
||||||
|
|
||||||
For the emulator harness:
|
For the emulator harness:
|
||||||
@@ -241,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.
|
- `--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.
|
- `--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-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.
|
- `--trace-report-gates`, `--trace-report-queue`, and `--trace-ram-lifecycle`: inspect the serial report queue, `loc_4046`/`F9C4` gate, and watched RAM byte history.
|
||||||
- `--target-frame "00 00 00 00 80 DA"`: compare staged/emitted TX bytes against an expected six-byte frame.
|
- `--target-frame "00 00 00 00 80 DA"`: compare staged/emitted TX bytes against an expected six-byte frame.
|
||||||
- `h8536_emulator_rx_probe.py "04 00 00 80 00"`: append the checksum, inject the host frame through SCI1 RX, and summarize responses.
|
- `h8536_emulator_rx_probe.py "04 00 00 80 00"`: append the checksum, inject the host frame through SCI1 RX, and summarize responses.
|
||||||
@@ -289,8 +301,10 @@ python h8536_emulator_rx_probe.py --help
|
|||||||
- `h8536/report_source_trace.py`: direct `loc_3E54` report enqueue source tracer.
|
- `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/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/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/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/`: 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/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/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/board_profile.py`: Sony RCP-TX7 board-trace annotations, including the MAX202 RS232 path.
|
||||||
@@ -301,7 +315,7 @@ python h8536_emulator_rx_probe.py --help
|
|||||||
- `h8536_pseudocode.py`: pseudocode CLI wrapper.
|
- `h8536_pseudocode.py`: pseudocode CLI wrapper.
|
||||||
- `h8536_serial_pseudocode.py`: focused serial 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_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_ccu_seed_hints.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.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.
|
- `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/bench_connect_lcd_sequence.py`: real-device COM5/COM6 bench runner for the CONNECT LCD sequence.
|
||||||
|
|||||||
BIN
build/emulator-eeprom-boot.bin
Normal file
BIN
build/emulator-eeprom-boot.bin
Normal file
Binary file not shown.
158603
build/emulator-eeprom-boot.json
Normal file
158603
build/emulator-eeprom-boot.json
Normal file
File diff suppressed because it is too large
Load Diff
111
build/emulator-eeprom-boot.txt
Normal file
111
build/emulator-eeprom-boot.txt
Normal 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
|
||||||
300
build/emulator-eeprom-loaded-boot.json
Normal file
300
build/emulator-eeprom-loaded-boot.json
Normal 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": []
|
||||||
|
}
|
||||||
31
build/emulator-eeprom-loaded-boot.txt
Normal file
31
build/emulator-eeprom-loaded-boot.txt
Normal 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)
|
||||||
8903
build/rom_eeprom_layout.json
Normal file
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
179
build/rom_eeprom_layout.txt
Normal 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.
|
||||||
730
h8536/eeprom_layout.py
Normal file
730
h8536/eeprom_layout.py
Normal 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",
|
||||||
|
]
|
||||||
@@ -4,6 +4,7 @@ import argparse
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ..formatting import h16, parse_int
|
from ..formatting import h16, parse_int
|
||||||
|
from .eeprom_image import write_eeprom_snapshot
|
||||||
from .memory import describe_regions
|
from .memory import describe_regions
|
||||||
from .runner import H8536Emulator
|
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("--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("--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-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
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@@ -70,6 +76,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
p7_input=args.p7_input,
|
p7_input=args.p7_input,
|
||||||
eeprom_seed=args.eeprom_seed,
|
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"rom={rom_path}")
|
||||||
print(f"reset_vector={h16(emulator.reset_vector())}")
|
print(f"reset_vector={h16(emulator.reset_vector())}")
|
||||||
if args.memory_map:
|
if args.memory_map:
|
||||||
@@ -82,4 +91,20 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
print(line)
|
print(line)
|
||||||
if not report.heartbeat_seen:
|
if not report.heartbeat_seen:
|
||||||
print("heartbeat_status=not reached; no heartbeat is reported unless bytes are emitted via SCI1_TDR")
|
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
|
return 0
|
||||||
|
|||||||
351
h8536/emulator/eeprom_image.py
Normal file
351
h8536/emulator/eeprom_image.py
Normal 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",
|
||||||
|
]
|
||||||
@@ -161,6 +161,20 @@ class MemoryMap:
|
|||||||
self.p9_bus.x24164_bus.seed_factory_defaults_from_rom(self.rom.data)
|
self.p9_bus.x24164_bus.seed_factory_defaults_from_rom(self.rom.data)
|
||||||
self.p9_bus.clear_x24164_trace()
|
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:
|
def _set_register(self, address: int, value: int) -> None:
|
||||||
self.registers[address - REGISTER_FIELD_START] = value & 0xFF
|
self.registers[address - REGISTER_FIELD_START] = value & 0xFF
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from .lcd import LCD, LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS, LCD_LINE_WIDTH
|
from .lcd import LCD, LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS, LCD_LINE_WIDTH
|
||||||
from .p9_bus import P9_ACK_BIT, P9_STROBE_BIT, P9Bus, P9StrobeEvent, P9TraceEvent
|
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__ = [
|
__all__ = [
|
||||||
"LCD_E_CLOCK_DATA",
|
"LCD_E_CLOCK_DATA",
|
||||||
@@ -17,5 +17,6 @@ __all__ = [
|
|||||||
"X24164Bus",
|
"X24164Bus",
|
||||||
"X24164Device",
|
"X24164Device",
|
||||||
"X24164TraceEvent",
|
"X24164TraceEvent",
|
||||||
|
"X24164WriteEvent",
|
||||||
"factory_default_words_from_rom",
|
"factory_default_words_from_rom",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from dataclasses import dataclass, field
|
|||||||
|
|
||||||
|
|
||||||
X24164_SIZE = 2048
|
X24164_SIZE = 2048
|
||||||
|
X24164_LOGICAL_SIZE = 4096
|
||||||
X24164_FACTORY_DEFAULT_BASE = 0xC964
|
X24164_FACTORY_DEFAULT_BASE = 0xC964
|
||||||
X24164_FACTORY_DEFAULT_BYTES = 0x0100
|
X24164_FACTORY_DEFAULT_BYTES = 0x0100
|
||||||
X24164_LOGICAL_PAGE_SIZE = 0x0100
|
X24164_LOGICAL_PAGE_SIZE = 0x0100
|
||||||
@@ -72,12 +73,30 @@ class X24164TraceEvent:
|
|||||||
return " ".join(parts)
|
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:
|
class X24164Bus:
|
||||||
"""Bit-level two-wire bus model for X24164 EEPROMs."""
|
"""Bit-level two-wire bus model for X24164 EEPROMs."""
|
||||||
|
|
||||||
def __init__(self, devices: list[X24164Device] | None = None) -> None:
|
def __init__(self, devices: list[X24164Device] | None = None) -> None:
|
||||||
self.devices = devices if devices is not None else default_x24164_devices()
|
self.devices = devices if devices is not None else default_x24164_devices()
|
||||||
self.trace_events: list[X24164TraceEvent] = []
|
self.trace_events: list[X24164TraceEvent] = []
|
||||||
|
self.write_events: list[X24164WriteEvent] = []
|
||||||
self.active = False
|
self.active = False
|
||||||
self.phase = "idle"
|
self.phase = "idle"
|
||||||
self.selected: X24164Device | None = None
|
self.selected: X24164Device | None = None
|
||||||
@@ -197,6 +216,18 @@ class X24164Bus:
|
|||||||
)
|
)
|
||||||
return True, value
|
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:
|
def write_linear_word(self, address: int, value: int) -> bool:
|
||||||
device = self._device_for_linear_address(address)
|
device = self._device_for_linear_address(address)
|
||||||
if device is None:
|
if device is None:
|
||||||
@@ -205,8 +236,8 @@ class X24164Bus:
|
|||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
offset = address & (X24164_SIZE - 1)
|
offset = address & (X24164_SIZE - 1)
|
||||||
device.write(offset, (value >> 8) & 0xFF)
|
self._write_device_byte(device, offset, (value >> 8) & 0xFF, source="linear_word")
|
||||||
device.write((offset + 1) & (X24164_SIZE - 1), value & 0xFF)
|
self._write_device_byte(device, (offset + 1) & (X24164_SIZE - 1), value & 0xFF, source="linear_word")
|
||||||
self.trace_events.append(
|
self.trace_events.append(
|
||||||
X24164TraceEvent(
|
X24164TraceEvent(
|
||||||
"x24164_linear_write_word",
|
"x24164_linear_write_word",
|
||||||
@@ -218,20 +249,61 @@ class X24164Bus:
|
|||||||
)
|
)
|
||||||
return True
|
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:
|
def seed_factory_defaults_from_rom(self, rom_bytes: bytes) -> None:
|
||||||
for offset, word in factory_default_words_from_rom(rom_bytes):
|
for offset, word in factory_default_words_from_rom(rom_bytes):
|
||||||
for page in range(X24164_LOGICAL_PAGE_COUNT):
|
for page in range(X24164_LOGICAL_PAGE_COUNT):
|
||||||
self.write_linear_word((page * X24164_LOGICAL_PAGE_SIZE) + offset, word)
|
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
|
base = page * X24164_LOGICAL_PAGE_SIZE
|
||||||
for offset in range(0, 8, 2):
|
for offset in range(0, 8, 2):
|
||||||
self.write_linear_word(base + offset, 0x2020)
|
self.write_linear_word(base + offset, 0x2020)
|
||||||
|
self.clear_write_log()
|
||||||
|
|
||||||
def trace_lines(self, limit: int | None = None) -> list[str]:
|
def trace_lines(self, limit: int | None = None) -> list[str]:
|
||||||
events = self.trace_events if limit is None else self.trace_events[-limit:]
|
events = self.trace_events if limit is None else self.trace_events[-limit:]
|
||||||
return [event.line() for event in events]
|
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:
|
def _scl_rising(self, master_sda: bool, master_sda_output: bool) -> None:
|
||||||
if not self.active:
|
if not self.active:
|
||||||
return
|
return
|
||||||
@@ -327,7 +399,7 @@ class X24164Bus:
|
|||||||
self._ack_armed_on_current_clock = True
|
self._ack_armed_on_current_clock = True
|
||||||
self.phase = "ignore"
|
self.phase = "ignore"
|
||||||
return
|
return
|
||||||
self.selected.write(self.address, value)
|
self._write_device_byte(self.selected, self.address, value, source="bit_banged")
|
||||||
self.trace_events.append(
|
self.trace_events.append(
|
||||||
X24164TraceEvent("x24164_write_data", self.selected.name, value=value, address=self.address, ack=True)
|
X24164TraceEvent("x24164_write_data", self.selected.name, value=value, address=self.address, ack=True)
|
||||||
)
|
)
|
||||||
@@ -365,6 +437,24 @@ class X24164Bus:
|
|||||||
return device
|
return device
|
||||||
return None
|
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:
|
def _selected_name(self) -> str | None:
|
||||||
return self.selected.name if self.selected is not None else None
|
return self.selected.name if self.selected is not None else None
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from .constants import (
|
|||||||
SCI_SSR_RDRF,
|
SCI_SSR_RDRF,
|
||||||
VECTOR_SCI1_RXI,
|
VECTOR_SCI1_RXI,
|
||||||
)
|
)
|
||||||
|
from .eeprom_image import write_eeprom_snapshot
|
||||||
from .errors import UnsupportedInstruction
|
from .errors import UnsupportedInstruction
|
||||||
from .memory import MemoryAccess
|
from .memory import MemoryAccess
|
||||||
from .runner import H8536Emulator
|
from .runner import H8536Emulator
|
||||||
@@ -216,6 +217,7 @@ def run_rx_probe(
|
|||||||
p9_fast_optimistic_wrapper: bool = False,
|
p9_fast_optimistic_wrapper: bool = False,
|
||||||
p7_input: int = 0xFF,
|
p7_input: int = 0xFF,
|
||||||
eeprom_seed: str = "blank",
|
eeprom_seed: str = "blank",
|
||||||
|
eeprom_image: bytes | None = None,
|
||||||
stop_after_tx_frame: bool = True,
|
stop_after_tx_frame: bool = True,
|
||||||
) -> tuple[Path, H8536Emulator, str, list[FrameResult]]:
|
) -> tuple[Path, H8536Emulator, str, list[FrameResult]]:
|
||||||
rom_bytes, discovered_rom_path = load_rom(rom_path)
|
rom_bytes, discovered_rom_path = load_rom(rom_path)
|
||||||
@@ -231,6 +233,8 @@ def run_rx_probe(
|
|||||||
p7_input=p7_input,
|
p7_input=p7_input,
|
||||||
eeprom_seed=eeprom_seed,
|
eeprom_seed=eeprom_seed,
|
||||||
)
|
)
|
||||||
|
if eeprom_image is not None:
|
||||||
|
emulator.memory.load_eeprom_image(eeprom_image)
|
||||||
|
|
||||||
boot_context = RunContext()
|
boot_context = RunContext()
|
||||||
boot_steps_used, boot_reason = _run_until(emulator, boot_steps, _rx_ready, boot_context)
|
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("--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("--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-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
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@@ -305,16 +314,35 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
p9_fast_optimistic_wrapper=args.p9_fast_optimistic_wrapper,
|
p9_fast_optimistic_wrapper=args.p9_fast_optimistic_wrapper,
|
||||||
p7_input=args.p7_input,
|
p7_input=args.p7_input,
|
||||||
eeprom_seed=args.eeprom_seed,
|
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,
|
stop_after_tx_frame=not args.keep_listening,
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"rom={rom_path}")
|
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(f"reset_vector={h16(emulator.reset_vector())}")
|
||||||
print(boot_summary)
|
print(boot_summary)
|
||||||
for index, result in enumerate(results):
|
for index, result in enumerate(results):
|
||||||
for line in result.lines(index):
|
for line in result.lines(index):
|
||||||
print(line)
|
print(line)
|
||||||
print("total_tx_frames=" + " | ".join(format_frame(frame) for frame in emulator.sci1.tx_frames))
|
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
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
8
h8536_eeprom_layout.py
Normal file
8
h8536_eeprom_layout.py
Normal 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())
|
||||||
120
tests/test_eeprom_layout.py
Normal file
120
tests/test_eeprom_layout.py
Normal 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()
|
||||||
@@ -37,7 +37,8 @@ class EmulatorAddressingTest(unittest.TestCase):
|
|||||||
self.assertEqual(memory.read16(0xF402), 0x6B6F)
|
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(0x0010), (True, 0x8000))
|
||||||
self.assertEqual(memory.p9_bus.fast_read_word(0x0110), (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):
|
def test_txi_indexed_byte_load_uses_signed_word_displacement_and_full_index_register(self):
|
||||||
rom = rom_with_reset()
|
rom = rom_with_reset()
|
||||||
|
|||||||
66
tests/test_emulator_eeprom_image.py
Normal file
66
tests/test_emulator_eeprom_image.py
Normal 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()
|
||||||
Reference in New Issue
Block a user