Compare commits
3 Commits
0819701b22
...
7c211f8112
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c211f8112 | ||
|
|
1e4f87675d | ||
|
|
3f9f03388c |
@@ -44,6 +44,7 @@ To start the current emulator harness:
|
|||||||
.\.venv\Scripts\python.exe h8536_emulator_probe.py --max-steps 1000000 --stop-on-tx --p9-fast-path
|
.\.venv\Scripts\python.exe h8536_emulator_probe.py --max-steps 1000000 --stop-on-tx --p9-fast-path
|
||||||
.\.venv\Scripts\python.exe h8536_emulator_rx_probe.py --preset connect-lcd
|
.\.venv\Scripts\python.exe h8536_emulator_rx_probe.py --preset connect-lcd
|
||||||
.\.venv\Scripts\python.exe scripts\bench_connect_lcd_sequence.py --port COM5 --relay-port COM6 --prompt-screen
|
.\.venv\Scripts\python.exe scripts\bench_connect_lcd_sequence.py --port COM5 --relay-port COM6 --prompt-screen
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_ack_probe.py --ack-frame "05 00 40 00 00 1F"
|
||||||
.\.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
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -90,6 +91,7 @@ The real-device bench helper uses `pyserial`; install repo dependencies with `.\
|
|||||||
- 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.
|
||||||
|
- Includes a bench ACK probe that reproduces the `01 00 00...` -> `01 00 01...` visible retry burst, waits for `07 80 40 20 90 2D`, then sends a candidate command-5 ACK and reports whether the target keeps repeating.
|
||||||
- Includes a bench-log replay harness that feeds recorded host TX frames back into the ROM emulator with bench-style UART byte timing by default and asserts parity against the real device's observed response/LCD state.
|
- Includes a bench-log replay harness that feeds recorded host TX frames back into the ROM emulator with bench-style UART byte timing by default and asserts parity against the real device's observed response/LCD state.
|
||||||
|
|
||||||
Current serial observations:
|
Current serial observations:
|
||||||
@@ -104,6 +106,7 @@ 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.
|
||||||
|
- 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.
|
||||||
- Bench follow-up: replaying the emulator CONNECT sequence on the real device did not switch the LCD to OK. The real device answered the `04 00 00 80 00 DE` step with `07 80 C0 60 20 5D` in the captured run and remained at `CONNECT NOT ACT`, so the next mismatch to chase is the missing visible `07 80 C0 60 20 5D` response/session context rather than the LCD OK branch.
|
- Bench follow-up: replaying the emulator CONNECT sequence on the real device did not switch the LCD to OK. The real device answered the `04 00 00 80 00 DE` step with `07 80 C0 60 20 5D` in the captured run and remained at `CONNECT NOT ACT`, so the next mismatch to chase is the missing visible `07 80 C0 60 20 5D` response/session context rather than the LCD OK branch.
|
||||||
- Observed capture labels such as `cam_power_button_candidate` and `call_button_candidate` are deliberately treated as capture overlays, not protocol facts hard-coded in ROM.
|
- Observed capture labels such as `cam_power_button_candidate` and `call_button_candidate` are deliberately treated as capture overlays, not protocol facts hard-coded in ROM.
|
||||||
@@ -216,11 +219,14 @@ python h8536_emulator_rx_probe.py --help
|
|||||||
- `--frt1-ocia-steps N` / `--frt2-ocia-steps N`: optional legacy overrides for forcing rough FRT compare cadence in targeted tests.
|
- `--frt1-ocia-steps N` / `--frt2-ocia-steps N`: optional legacy overrides for forcing rough FRT compare cadence in targeted tests.
|
||||||
- `--p9-fast-path`: shortcut known P9 transfer routines for exploration. Fast-path byte/marker calls now feed the X24164 EEPROM model, and `BFE0/BFFE` wrapper shortcuts perform EEPROM word write-verify/read operations against the modeled banks.
|
- `--p9-fast-path`: shortcut known P9 transfer routines for exploration. Fast-path byte/marker calls now feed the X24164 EEPROM model, and `BFE0/BFFE` wrapper shortcuts perform EEPROM word write-verify/read operations against the modeled banks.
|
||||||
- `--p9-fast-optimistic-wrapper`: legacy fallback for older wrapper experiments; the known `BFE0/BFFE` EEPROM wrappers now use the X24164 model instead.
|
- `--p9-fast-optimistic-wrapper`: legacy fallback for older wrapper experiments; the known `BFE0/BFFE` EEPROM wrappers now use the X24164 model instead.
|
||||||
|
- `--p7-input 0xFF`: set external P7 input pin state; this matters for the EEPROM defaulting gate at `P7DR.7` and the DIP-switch style inputs.
|
||||||
|
- `--eeprom-seed blank|factory`: choose blank X24164 power-on state or pre-seed the X24164/shadow tables from the ROM defaults before reset.
|
||||||
- `--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.
|
||||||
- `h8536_emulator_rx_probe.py --uart-timing --uart-baud 38400 "04 00 00 80 00"`: inject all six host bytes with 8N1 wire spacing of about 260 us per byte, letting RXI/TXI/timers interleave; if the ROM has not cleared `RDRF` before the next byte, the SCI model raises `ORER`.
|
- `h8536_emulator_rx_probe.py --uart-timing --uart-baud 38400 "04 00 00 80 00"`: inject all six host bytes with 8N1 wire spacing of about 260 us per byte, letting RXI/TXI/timers interleave; if the ROM has not cleared `RDRF` before the next byte, the SCI model raises `ORER`.
|
||||||
- `h8536_emulator_rx_probe.py --preset connect-lcd`: replay the current CONNECT LCD activation candidates.
|
- `h8536_emulator_rx_probe.py --preset connect-lcd`: replay the current CONNECT LCD activation candidates.
|
||||||
|
- `scripts\serial_table_dump.py --port COM5 --relay-port COM6 --start 0x000 --count 0x200 --log captures\table-read.txt`: read-only command-1 sweep of the firmware-exposed serial table state for EEPROM/shadow inference.
|
||||||
- `scripts\bench_connect_lcd_sequence.py --port COM5 --relay-port COM6 --prompt-screen`: power-cycle the bench device, wait for heartbeat readiness, send `04 00 00 40 00 1E`, `04 00 00 80 00 DE`, `04 00 00 C0 00 9E`, log RX/TX, and prompt for observed LCD text.
|
- `scripts\bench_connect_lcd_sequence.py --port COM5 --relay-port COM6 --prompt-screen`: power-cycle the bench device, wait for heartbeat readiness, send `04 00 00 40 00 1E`, `04 00 00 80 00 DE`, `04 00 00 C0 00 9E`, log RX/TX, and prompt for observed LCD text.
|
||||||
- `h8536_emulator_bench_replay.py captures\bench-connect-lcd-sequence-20260525-214411.txt --assert-bench-parity`: replay a real bench log into the emulator using timed UART RX by default and intentionally fail while any response/LCD state still diverges from the bench-observed `CONNECT NOT ACT` plus `07 80 C0 60 20 5D` path. Pass `--polite-rx` for the old wait-until-consumed injection mode.
|
- `h8536_emulator_bench_replay.py captures\bench-connect-lcd-sequence-20260525-214411.txt --assert-bench-parity`: replay a real bench log into the emulator using timed UART RX by default and intentionally fail while any response/LCD state still diverges from the bench-observed `CONNECT NOT ACT` plus `07 80 C0 60 20 5D` path. Pass `--polite-rx` for the old wait-until-consumed injection mode.
|
||||||
- Current status: boots from `H'1000`, initializes SCI1, models the traced X24164 EEPROM bus on P9, captures P9 byte candidates, can optionally fast-path known P9 EEPROM routines, schedules FRT1/FRT2 OCIA from timer registers and `--clock-hz`, captures the ROM-driven LCD line ` CONNECT:NOT ACT`, and emits the observed heartbeat frame `00 00 00 00 80 DA`.
|
- Current status: boots from `H'1000`, initializes SCI1, models the traced X24164 EEPROM bus on P9, captures P9 byte candidates, can optionally fast-path known P9 EEPROM routines, schedules FRT1/FRT2 OCIA from timer registers and `--clock-hz`, captures the ROM-driven LCD line ` CONNECT:NOT ACT`, and emits the observed heartbeat frame `00 00 00 00 80 DA`.
|
||||||
@@ -267,3 +273,4 @@ python h8536_emulator_rx_probe.py --help
|
|||||||
- `h8536_serial_gate.py`, `h8536_report_source_trace.py`, `h8536_table_xrefs.py`, `h8536_consistency.py`: sidecar analysis CLI wrappers.
|
- `h8536_serial_gate.py`, `h8536_report_source_trace.py`, `h8536_table_xrefs.py`, `h8536_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.
|
||||||
- `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.
|
||||||
|
- `scripts/serial_table_dump.py`: read-only COM5/COM6 command-1 table sweep for inferring live EEPROM-backed parameter state.
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ def label_frame(frame: bytes) -> str:
|
|||||||
bytes.fromhex("07804040A07D"): "visible_40A0_family_40",
|
bytes.fromhex("07804040A07D"): "visible_40A0_family_40",
|
||||||
bytes.fromhex("07808040A0BD"): "visible_40A0_family_80",
|
bytes.fromhex("07808040A0BD"): "visible_40A0_family_80",
|
||||||
bytes.fromhex("0780C040A0FD"): "visible_40A0_family_C0",
|
bytes.fromhex("0780C040A0FD"): "visible_40A0_family_C0",
|
||||||
|
bytes.fromhex("07804020902D"): "visible_retry_0040_2090_candidate",
|
||||||
bytes.fromhex("0780C060205D"): "visible_C0_6020_family_candidate",
|
bytes.fromhex("0780C060205D"): "visible_C0_6020_family_candidate",
|
||||||
}
|
}
|
||||||
label = labels.get(frame, "")
|
label = labels.get(frame, "")
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ from .constants import (
|
|||||||
IPRE,
|
IPRE,
|
||||||
ON_CHIP_RAM_END,
|
ON_CHIP_RAM_END,
|
||||||
ON_CHIP_RAM_START,
|
ON_CHIP_RAM_START,
|
||||||
|
P7DDR,
|
||||||
|
P7DR,
|
||||||
P9DDR,
|
P9DDR,
|
||||||
P9DR,
|
P9DR,
|
||||||
RAMCR,
|
RAMCR,
|
||||||
@@ -55,7 +57,7 @@ from .cpu import CPUState
|
|||||||
from .errors import EmulatorError, UnsupportedInstruction
|
from .errors import EmulatorError, UnsupportedInstruction
|
||||||
from .fast_paths import P9FastPath, P9FastPathConfig, P9FastPathEvent
|
from .fast_paths import P9FastPath, P9FastPathConfig, P9FastPathEvent
|
||||||
from .memory import MemoryAccess, MemoryMap, describe_regions
|
from .memory import MemoryAccess, MemoryMap, describe_regions
|
||||||
from .peripherals import LCD, P9TraceEvent, X24164Bus, X24164Device, X24164TraceEvent
|
from .peripherals import LCD, P9TraceEvent, X24164Bus, X24164Device, X24164TraceEvent, factory_default_words_from_rom
|
||||||
from .runner import H8536Emulator, RunReport
|
from .runner import H8536Emulator, RunReport
|
||||||
from .sci import SCI1, SciTxEvent
|
from .sci import SCI1, SciTxEvent
|
||||||
from .uart import UartTiming
|
from .uart import UartTiming
|
||||||
@@ -88,6 +90,8 @@ __all__ = [
|
|||||||
"MemoryMap",
|
"MemoryMap",
|
||||||
"ON_CHIP_RAM_END",
|
"ON_CHIP_RAM_END",
|
||||||
"ON_CHIP_RAM_START",
|
"ON_CHIP_RAM_START",
|
||||||
|
"P7DDR",
|
||||||
|
"P7DR",
|
||||||
"P9DDR",
|
"P9DDR",
|
||||||
"P9DR",
|
"P9DR",
|
||||||
"P9FastPath",
|
"P9FastPath",
|
||||||
@@ -130,6 +134,7 @@ __all__ = [
|
|||||||
"build_arg_parser",
|
"build_arg_parser",
|
||||||
"describe_regions",
|
"describe_regions",
|
||||||
"discover_rom_path",
|
"discover_rom_path",
|
||||||
|
"factory_default_words_from_rom",
|
||||||
"load_rom",
|
"load_rom",
|
||||||
"main",
|
"main",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -181,6 +181,8 @@ class ReplayConfig:
|
|||||||
p9_fast_path: bool = True
|
p9_fast_path: bool = True
|
||||||
p9_fast_input: int = 0xFF
|
p9_fast_input: int = 0xFF
|
||||||
p9_fast_optimistic_wrapper: bool = False
|
p9_fast_optimistic_wrapper: bool = False
|
||||||
|
p7_input: int = 0xFF
|
||||||
|
eeprom_seed: str = "blank"
|
||||||
|
|
||||||
|
|
||||||
def parse_bench_replay_log_text(text: str) -> BenchReplayLog:
|
def parse_bench_replay_log_text(text: str) -> BenchReplayLog:
|
||||||
@@ -244,6 +246,8 @@ def run_bench_replay(log_path: Path, *, rom_path: Path | None = None, config: Re
|
|||||||
p9_fast_path_enabled=config.p9_fast_path,
|
p9_fast_path_enabled=config.p9_fast_path,
|
||||||
p9_fast_default_input_byte=config.p9_fast_input,
|
p9_fast_default_input_byte=config.p9_fast_input,
|
||||||
p9_fast_default_wrapper_success=config.p9_fast_optimistic_wrapper,
|
p9_fast_default_wrapper_success=config.p9_fast_optimistic_wrapper,
|
||||||
|
p7_input=config.p7_input,
|
||||||
|
eeprom_seed=config.eeprom_seed,
|
||||||
)
|
)
|
||||||
|
|
||||||
context = RunContext()
|
context = RunContext()
|
||||||
@@ -369,6 +373,8 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
|||||||
parser.add_argument("--no-p9-fast-path", action="store_true", help="disable shortcut handling for known P9 routines")
|
parser.add_argument("--no-p9-fast-path", action="store_true", help="disable shortcut handling for known P9 routines")
|
||||||
parser.add_argument("--p9-fast-input", type=lambda text: int(text, 0), default=ReplayConfig.p9_fast_input)
|
parser.add_argument("--p9-fast-input", type=lambda text: int(text, 0), default=ReplayConfig.p9_fast_input)
|
||||||
parser.add_argument("--p9-fast-optimistic-wrapper", action="store_true", help="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=lambda text: int(text, 0), default=ReplayConfig.p7_input, help="external P7 pin state for input bits; DIP-off board default is 0xFF")
|
||||||
|
parser.add_argument("--eeprom-seed", choices=("blank", "factory"), default=ReplayConfig.eeprom_seed, help="initial X24164/shadow state before reset")
|
||||||
parser.add_argument("--assert-bench-parity", action="store_true", help="exit nonzero if emulator behavior diverges from the bench log")
|
parser.add_argument("--assert-bench-parity", action="store_true", help="exit nonzero if emulator behavior diverges from the bench log")
|
||||||
parser.add_argument("--json", action="store_true", help="emit JSON")
|
parser.add_argument("--json", action="store_true", help="emit JSON")
|
||||||
return parser
|
return parser
|
||||||
@@ -392,6 +398,8 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
p9_fast_path=not args.no_p9_fast_path,
|
p9_fast_path=not args.no_p9_fast_path,
|
||||||
p9_fast_input=args.p9_fast_input,
|
p9_fast_input=args.p9_fast_input,
|
||||||
p9_fast_optimistic_wrapper=args.p9_fast_optimistic_wrapper,
|
p9_fast_optimistic_wrapper=args.p9_fast_optimistic_wrapper,
|
||||||
|
p7_input=args.p7_input,
|
||||||
|
eeprom_seed=args.eeprom_seed,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if args.json:
|
if args.json:
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
|||||||
parser.add_argument("--p9-fast-path", action="store_true", help="shortcut known P9 bit-banged transfer routines for exploration")
|
parser.add_argument("--p9-fast-path", action="store_true", help="shortcut known P9 bit-banged transfer routines for exploration")
|
||||||
parser.add_argument("--p9-fast-input", type=parse_int, default=0xFF, help="default byte returned by the P9 fast-path read routine")
|
parser.add_argument("--p9-fast-input", type=parse_int, default=0xFF, help="default byte returned by the P9 fast-path read routine")
|
||||||
parser.add_argument("--p9-fast-optimistic-wrapper", action="store_true", help="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("--eeprom-seed", choices=("blank", "factory"), default="blank", help="initial X24164/shadow state before reset")
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@@ -65,6 +67,8 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
p9_fast_path_enabled=args.p9_fast_path,
|
p9_fast_path_enabled=args.p9_fast_path,
|
||||||
p9_fast_default_input_byte=args.p9_fast_input,
|
p9_fast_default_input_byte=args.p9_fast_input,
|
||||||
p9_fast_default_wrapper_success=args.p9_fast_optimistic_wrapper,
|
p9_fast_default_wrapper_success=args.p9_fast_optimistic_wrapper,
|
||||||
|
p7_input=args.p7_input,
|
||||||
|
eeprom_seed=args.eeprom_seed,
|
||||||
)
|
)
|
||||||
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())}")
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ SCI1_TDR = 0xFEDB
|
|||||||
SCI1_SSR = 0xFEDC
|
SCI1_SSR = 0xFEDC
|
||||||
SCI1_RDR = 0xFEDD
|
SCI1_RDR = 0xFEDD
|
||||||
|
|
||||||
|
P7DDR = 0xFE8C
|
||||||
|
P7DR = 0xFE8E
|
||||||
P9DDR = 0xFEFE
|
P9DDR = 0xFEFE
|
||||||
P9DR = 0xFEFF
|
P9DR = 0xFEFF
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ from ..rom import Rom
|
|||||||
from .constants import (
|
from .constants import (
|
||||||
ON_CHIP_RAM_END,
|
ON_CHIP_RAM_END,
|
||||||
ON_CHIP_RAM_START,
|
ON_CHIP_RAM_START,
|
||||||
|
P7DDR,
|
||||||
|
P7DR,
|
||||||
P9DDR,
|
P9DDR,
|
||||||
P9DR,
|
P9DR,
|
||||||
REGISTER_FIELD_END,
|
REGISTER_FIELD_END,
|
||||||
@@ -22,6 +24,7 @@ from .constants import (
|
|||||||
)
|
)
|
||||||
from .peripherals.lcd import LCD, LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS
|
from .peripherals.lcd import LCD, LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS
|
||||||
from .peripherals.p9_bus import P9Bus
|
from .peripherals.p9_bus import P9Bus
|
||||||
|
from .peripherals.x24164 import factory_default_words_from_rom
|
||||||
from .sci import SCI1
|
from .sci import SCI1
|
||||||
|
|
||||||
|
|
||||||
@@ -35,7 +38,7 @@ class MemoryAccess:
|
|||||||
|
|
||||||
|
|
||||||
class MemoryMap:
|
class MemoryMap:
|
||||||
def __init__(self, rom_bytes: bytes, sci1: SCI1 | None = None) -> None:
|
def __init__(self, rom_bytes: bytes, sci1: SCI1 | None = None, *, p7_input: int = 0xFF) -> None:
|
||||||
self.rom = Rom(rom_bytes, base=0)
|
self.rom = Rom(rom_bytes, base=0)
|
||||||
self.sci1 = sci1 if sci1 is not None else SCI1()
|
self.sci1 = sci1 if sci1 is not None else SCI1()
|
||||||
self.lcd = LCD()
|
self.lcd = LCD()
|
||||||
@@ -43,6 +46,7 @@ class MemoryMap:
|
|||||||
self.ram = bytearray(ON_CHIP_RAM_END - ON_CHIP_RAM_START + 1)
|
self.ram = bytearray(ON_CHIP_RAM_END - ON_CHIP_RAM_START + 1)
|
||||||
self.registers = bytearray(REGISTER_FIELD_END - REGISTER_FIELD_START + 1)
|
self.registers = bytearray(REGISTER_FIELD_END - REGISTER_FIELD_START + 1)
|
||||||
self.external: dict[int, int] = {}
|
self.external: dict[int, int] = {}
|
||||||
|
self.port_inputs: dict[int, int] = {P7DR: p7_input & 0xFF}
|
||||||
self.access_log: list[MemoryAccess] = []
|
self.access_log: list[MemoryAccess] = []
|
||||||
|
|
||||||
self._set_register(SCI1_SMR, self.sci1.smr)
|
self._set_register(SCI1_SMR, self.sci1.smr)
|
||||||
@@ -64,6 +68,8 @@ class MemoryMap:
|
|||||||
value = self.lcd.read_status()
|
value = self.lcd.read_status()
|
||||||
elif address == LCD_E_CLOCK_DATA:
|
elif address == LCD_E_CLOCK_DATA:
|
||||||
value = self.lcd.read_data()
|
value = self.lcd.read_data()
|
||||||
|
elif address == P7DR:
|
||||||
|
value = self._read_port_data(P7DDR, P7DR)
|
||||||
elif address in self.external:
|
elif address in self.external:
|
||||||
value = self.external[address]
|
value = self.external[address]
|
||||||
elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
|
elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
|
||||||
@@ -101,6 +107,8 @@ class MemoryMap:
|
|||||||
self.external[address] = value
|
self.external[address] = value
|
||||||
elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
|
elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
|
||||||
self.ram[address - ON_CHIP_RAM_START] = value
|
self.ram[address - ON_CHIP_RAM_START] = value
|
||||||
|
elif address in (P7DDR, P7DR):
|
||||||
|
self._set_register(address, value)
|
||||||
elif address == P9DDR:
|
elif address == P9DDR:
|
||||||
self._set_register(address, self.p9_bus.write_ddr(value))
|
self._set_register(address, self.p9_bus.write_ddr(value))
|
||||||
elif address == P9DR:
|
elif address == P9DR:
|
||||||
@@ -140,9 +148,28 @@ class MemoryMap:
|
|||||||
self._set_register(address, (value >> 8) & 0xFF)
|
self._set_register(address, (value >> 8) & 0xFF)
|
||||||
self._set_register((address + 1) & 0xFFFF, value & 0xFF)
|
self._set_register((address + 1) & 0xFFFF, value & 0xFF)
|
||||||
|
|
||||||
|
def set_port_input(self, data_register: int, value: int, *, mask: int = 0xFF) -> None:
|
||||||
|
data_register &= 0xFFFF
|
||||||
|
old = self.port_inputs.get(data_register, 0xFF)
|
||||||
|
self.port_inputs[data_register] = ((old & ~mask) | (value & mask)) & 0xFF
|
||||||
|
|
||||||
|
def seed_factory_eeprom_and_shadow(self) -> None:
|
||||||
|
for offset, word in factory_default_words_from_rom(self.rom.data):
|
||||||
|
address = 0xF400 + offset
|
||||||
|
self.external[address & 0xFFFF] = (word >> 8) & 0xFF
|
||||||
|
self.external[(address + 1) & 0xFFFF] = word & 0xFF
|
||||||
|
self.p9_bus.x24164_bus.seed_factory_defaults_from_rom(self.rom.data)
|
||||||
|
self.p9_bus.clear_x24164_trace()
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
def _read_port_data(self, direction_register: int, data_register: int) -> int:
|
||||||
|
ddr = self.registers[direction_register - REGISTER_FIELD_START]
|
||||||
|
latch = self.registers[data_register - REGISTER_FIELD_START]
|
||||||
|
pins = self.port_inputs.get(data_register, 0xFF)
|
||||||
|
return ((latch & ddr) | (pins & ~ddr)) & 0xFF
|
||||||
|
|
||||||
def _log(self, kind: str, address: int, size: int, value: int) -> None:
|
def _log(self, kind: str, address: int, size: int, value: int) -> None:
|
||||||
self.access_log.append(MemoryAccess(address, size, value, kind, self.region(address).name))
|
self.access_log.append(MemoryAccess(address, size, value, kind, self.region(address).name))
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
from .x24164 import X24164Bus, X24164Device, X24164TraceEvent, factory_default_words_from_rom
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"LCD_E_CLOCK_DATA",
|
"LCD_E_CLOCK_DATA",
|
||||||
@@ -17,4 +17,5 @@ __all__ = [
|
|||||||
"X24164Bus",
|
"X24164Bus",
|
||||||
"X24164Device",
|
"X24164Device",
|
||||||
"X24164TraceEvent",
|
"X24164TraceEvent",
|
||||||
|
"factory_default_words_from_rom",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -237,6 +237,10 @@ class P9Bus:
|
|||||||
self._append_x24164_trace()
|
self._append_x24164_trace()
|
||||||
return success
|
return success
|
||||||
|
|
||||||
|
def clear_x24164_trace(self) -> None:
|
||||||
|
self.x24164_bus.trace_events.clear()
|
||||||
|
self._x24164_trace_index = 0
|
||||||
|
|
||||||
def _record_transmitted_bit(self, bit: int) -> None:
|
def _record_transmitted_bit(self, bit: int) -> None:
|
||||||
self.transmitted_bits.append(bit)
|
self.transmitted_bits.append(bit)
|
||||||
self.trace_events.append(P9TraceEvent("tx_bit", self.ddr, self.dr_latch, bit=bit))
|
self.trace_events.append(P9TraceEvent("tx_bit", self.ddr, self.dr_latch, bit=bit))
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ from dataclasses import dataclass, field
|
|||||||
|
|
||||||
|
|
||||||
X24164_SIZE = 2048
|
X24164_SIZE = 2048
|
||||||
|
X24164_FACTORY_DEFAULT_BASE = 0xC964
|
||||||
|
X24164_FACTORY_DEFAULT_BYTES = 0x0100
|
||||||
|
X24164_LOGICAL_PAGE_SIZE = 0x0100
|
||||||
|
X24164_LOGICAL_PAGE_COUNT = 0x10
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -214,6 +218,16 @@ class X24164Bus:
|
|||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def seed_factory_defaults_from_rom(self, rom_bytes: bytes) -> None:
|
||||||
|
for offset, word in factory_default_words_from_rom(rom_bytes):
|
||||||
|
for page in range(X24164_LOGICAL_PAGE_COUNT):
|
||||||
|
self.write_linear_word((page * X24164_LOGICAL_PAGE_SIZE) + offset, word)
|
||||||
|
|
||||||
|
for page in range(X24164_LOGICAL_PAGE_COUNT):
|
||||||
|
base = page * X24164_LOGICAL_PAGE_SIZE
|
||||||
|
for offset in range(0, 8, 2):
|
||||||
|
self.write_linear_word(base + offset, 0x2020)
|
||||||
|
|
||||||
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]
|
||||||
@@ -360,3 +374,15 @@ def default_x24164_devices() -> list[X24164Device]:
|
|||||||
X24164Device("x24164_a0_lower_2k", 0xA0),
|
X24164Device("x24164_a0_lower_2k", 0xA0),
|
||||||
X24164Device("x24164_e0_upper_2k", 0xE0),
|
X24164Device("x24164_e0_upper_2k", 0xE0),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def factory_default_words_from_rom(rom_bytes: bytes) -> list[tuple[int, int]]:
|
||||||
|
end = X24164_FACTORY_DEFAULT_BASE + X24164_FACTORY_DEFAULT_BYTES
|
||||||
|
if len(rom_bytes) < end:
|
||||||
|
raise ValueError(f"ROM is too small for X24164 factory default table at {X24164_FACTORY_DEFAULT_BASE:04X}")
|
||||||
|
words: list[tuple[int, int]] = []
|
||||||
|
for offset in range(0, X24164_FACTORY_DEFAULT_BYTES, 2):
|
||||||
|
address = X24164_FACTORY_DEFAULT_BASE + offset
|
||||||
|
word = (rom_bytes[address] << 8) | rom_bytes[address + 1]
|
||||||
|
words.append((offset, word))
|
||||||
|
return words
|
||||||
|
|||||||
@@ -92,11 +92,17 @@ class H8536Emulator:
|
|||||||
p9_fast_path_enabled: bool = False,
|
p9_fast_path_enabled: bool = False,
|
||||||
p9_fast_default_input_byte: int = 0xFF,
|
p9_fast_default_input_byte: int = 0xFF,
|
||||||
p9_fast_default_wrapper_success: bool = False,
|
p9_fast_default_wrapper_success: bool = False,
|
||||||
|
p7_input: int = 0xFF,
|
||||||
|
eeprom_seed: str = "blank",
|
||||||
) -> None:
|
) -> None:
|
||||||
if not rom_bytes:
|
if not rom_bytes:
|
||||||
raise ValueError("ROM image is empty")
|
raise ValueError("ROM image is empty")
|
||||||
|
if eeprom_seed not in {"blank", "factory"}:
|
||||||
|
raise ValueError("eeprom_seed must be 'blank' or 'factory'")
|
||||||
self.sci1 = SCI1()
|
self.sci1 = SCI1()
|
||||||
self.memory = MemoryMap(rom_bytes, self.sci1)
|
self.memory = MemoryMap(rom_bytes, self.sci1, p7_input=p7_input)
|
||||||
|
if eeprom_seed == "factory":
|
||||||
|
self.memory.seed_factory_eeprom_and_shadow()
|
||||||
self.memory.p9_bus.default_wrapper_success = bool(p9_fast_default_wrapper_success)
|
self.memory.p9_bus.default_wrapper_success = bool(p9_fast_default_wrapper_success)
|
||||||
self.p9_fast_path = p9_fast_path or P9FastPath(
|
self.p9_fast_path = p9_fast_path or P9FastPath(
|
||||||
P9FastPathConfig(enabled=p9_fast_path_enabled, default_input_byte=p9_fast_default_input_byte)
|
P9FastPathConfig(enabled=p9_fast_path_enabled, default_input_byte=p9_fast_default_input_byte)
|
||||||
|
|||||||
@@ -214,6 +214,8 @@ def run_rx_probe(
|
|||||||
p9_fast_path: bool = True,
|
p9_fast_path: bool = True,
|
||||||
p9_fast_input: int = 0xFF,
|
p9_fast_input: int = 0xFF,
|
||||||
p9_fast_optimistic_wrapper: bool = False,
|
p9_fast_optimistic_wrapper: bool = False,
|
||||||
|
p7_input: int = 0xFF,
|
||||||
|
eeprom_seed: str = "blank",
|
||||||
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)
|
||||||
@@ -226,6 +228,8 @@ def run_rx_probe(
|
|||||||
p9_fast_path_enabled=p9_fast_path,
|
p9_fast_path_enabled=p9_fast_path,
|
||||||
p9_fast_default_input_byte=p9_fast_input,
|
p9_fast_default_input_byte=p9_fast_input,
|
||||||
p9_fast_default_wrapper_success=p9_fast_optimistic_wrapper,
|
p9_fast_default_wrapper_success=p9_fast_optimistic_wrapper,
|
||||||
|
p7_input=p7_input,
|
||||||
|
eeprom_seed=eeprom_seed,
|
||||||
)
|
)
|
||||||
|
|
||||||
boot_context = RunContext()
|
boot_context = RunContext()
|
||||||
@@ -271,6 +275,8 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
|||||||
parser.add_argument("--no-p9-fast-path", action="store_true", help="disable shortcut handling for known P9 routines")
|
parser.add_argument("--no-p9-fast-path", action="store_true", help="disable shortcut handling for known P9 routines")
|
||||||
parser.add_argument("--p9-fast-input", type=parse_int, default=0xFF, help="default byte returned by the P9 fast-path read routine")
|
parser.add_argument("--p9-fast-input", type=parse_int, default=0xFF, help="default byte returned by the P9 fast-path read routine")
|
||||||
parser.add_argument("--p9-fast-optimistic-wrapper", action="store_true", help="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("--eeprom-seed", choices=("blank", "factory"), default="blank", help="initial X24164/shadow state before reset")
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@@ -297,6 +303,8 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
p9_fast_path=not args.no_p9_fast_path,
|
p9_fast_path=not args.no_p9_fast_path,
|
||||||
p9_fast_input=args.p9_fast_input,
|
p9_fast_input=args.p9_fast_input,
|
||||||
p9_fast_optimistic_wrapper=args.p9_fast_optimistic_wrapper,
|
p9_fast_optimistic_wrapper=args.p9_fast_optimistic_wrapper,
|
||||||
|
p7_input=args.p7_input,
|
||||||
|
eeprom_seed=args.eeprom_seed,
|
||||||
stop_after_tx_frame=not args.keep_listening,
|
stop_after_tx_frame=not args.keep_listening,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
188
h8536/serial_ack_probe.py
Normal file
188
h8536/serial_ack_probe.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from collections import Counter
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TextIO
|
||||||
|
|
||||||
|
from .bench_connect_lcd import (
|
||||||
|
BenchLogger,
|
||||||
|
FrameDetector,
|
||||||
|
_import_serial,
|
||||||
|
_read_for,
|
||||||
|
_relay_command,
|
||||||
|
_relay_settle,
|
||||||
|
_send_frame,
|
||||||
|
_wait_for_ready,
|
||||||
|
format_frame,
|
||||||
|
frame_checksum_ok,
|
||||||
|
parse_frame,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_PRIME_FRAME = bytes.fromhex("01000000005B")
|
||||||
|
DEFAULT_TRIGGER_FRAME = bytes.fromhex("01000100005A")
|
||||||
|
DEFAULT_TARGET_FRAME = bytes.fromhex("07804020902D")
|
||||||
|
DEFAULT_ACK_FRAME = bytes.fromhex("05004000001F")
|
||||||
|
|
||||||
|
|
||||||
|
def default_log_path() -> Path:
|
||||||
|
return Path("captures") / f"serial-ack-probe-{datetime.now().strftime('%Y%m%d-%H%M%S')}.txt"
|
||||||
|
|
||||||
|
|
||||||
|
def build_arg_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=(
|
||||||
|
"Reproduce the command-1 visible retry burst and send one candidate ACK "
|
||||||
|
"after a target device frame is observed."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
parser.add_argument("--port", default="COM5", help="RS232 serial port connected to the RCP")
|
||||||
|
parser.add_argument("--baud", type=int, default=38400, help="RCP serial baud rate")
|
||||||
|
parser.add_argument("--relay-port", default="COM6", help="Pico relay serial port")
|
||||||
|
parser.add_argument("--relay-baud", type=int, default=115200, help="Pico relay serial baud rate")
|
||||||
|
parser.add_argument("--no-power-cycle", action="store_true", help="do not send relay off/on before the test")
|
||||||
|
parser.add_argument("--power-off-command", default="off", help="relay command used to remove DUT power")
|
||||||
|
parser.add_argument("--power-on-command", default="on", help="relay command used to apply DUT power")
|
||||||
|
parser.add_argument("--off-seconds", type=float, default=1.5, help="seconds to hold the DUT powered off")
|
||||||
|
parser.add_argument("--relay-settle", type=float, default=2.0, help="seconds to wait after opening the relay port")
|
||||||
|
parser.add_argument("--ready-timeout", type=float, default=10.0, help="seconds to wait for heartbeat before sending")
|
||||||
|
parser.add_argument("--ready-heartbeats", type=int, default=2, help="heartbeat frames to observe before sending")
|
||||||
|
parser.add_argument("--require-ready", action="store_true", help="abort if ready heartbeat count is not observed")
|
||||||
|
parser.add_argument("--pre-drain", type=float, default=0.250, help="seconds to drain/log RX before priming")
|
||||||
|
parser.add_argument("--prime-frame", type=parse_frame, default=DEFAULT_PRIME_FRAME, help="first host frame")
|
||||||
|
parser.add_argument("--prime-gap", type=float, default=1.25, help="seconds to listen after the prime frame")
|
||||||
|
parser.add_argument("--trigger-frame", type=parse_frame, default=DEFAULT_TRIGGER_FRAME, help="second host frame")
|
||||||
|
parser.add_argument("--target-frame", type=parse_frame, default=DEFAULT_TARGET_FRAME, help="device frame to wait for")
|
||||||
|
parser.add_argument("--target-timeout", type=float, default=3.0, help="seconds to wait for target after trigger")
|
||||||
|
parser.add_argument("--ack-frame", type=parse_frame, default=DEFAULT_ACK_FRAME, help="candidate ACK frame")
|
||||||
|
parser.add_argument("--ack-guard", type=float, default=0.020, help="seconds to wait after target before ACK")
|
||||||
|
parser.add_argument("--post-ack-read", type=float, default=3.0, help="seconds to listen after ACK")
|
||||||
|
parser.add_argument("--log", type=Path, help="capture log path")
|
||||||
|
parser.add_argument("--dry-run", action="store_true", help="print planned frames without opening serial ports")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None, *, stdout: TextIO = sys.stdout) -> int:
|
||||||
|
args = build_arg_parser().parse_args(argv)
|
||||||
|
log_path = args.log or default_log_path()
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
_print_plan(args, log_path, stdout)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
serial = _import_serial()
|
||||||
|
logger = BenchLogger(log_path, stdout=stdout)
|
||||||
|
detector = FrameDetector()
|
||||||
|
try:
|
||||||
|
logger.emit("Serial ACK probe")
|
||||||
|
logger.emit(f"device={args.port} {args.baud} 8N1 relay={args.relay_port} {args.relay_baud}")
|
||||||
|
logger.emit(f"log={log_path}")
|
||||||
|
_emit_plan(args, logger)
|
||||||
|
|
||||||
|
with serial.Serial(args.port, args.baud, bytesize=8, parity="N", stopbits=1, timeout=0.05) as device:
|
||||||
|
relay = None
|
||||||
|
try:
|
||||||
|
if not args.no_power_cycle:
|
||||||
|
relay = serial.Serial(args.relay_port, args.relay_baud, timeout=0.25)
|
||||||
|
_relay_settle(relay, args.relay_settle, logger)
|
||||||
|
_relay_command(relay, args.power_off_command, logger)
|
||||||
|
time.sleep(args.off_seconds)
|
||||||
|
device.reset_input_buffer()
|
||||||
|
detector = FrameDetector()
|
||||||
|
_relay_command(relay, args.power_on_command, logger)
|
||||||
|
else:
|
||||||
|
device.reset_input_buffer()
|
||||||
|
|
||||||
|
ready = _wait_for_ready(device, detector, logger, args.ready_timeout, args.ready_heartbeats)
|
||||||
|
if args.require_ready and not ready:
|
||||||
|
logger.event("ABORT ready heartbeat threshold was not observed")
|
||||||
|
return 2
|
||||||
|
if args.pre_drain > 0:
|
||||||
|
logger.event(f"DRAIN before ACK probe {args.pre_drain:.3f}s")
|
||||||
|
_read_for(device, detector, logger, args.pre_drain)
|
||||||
|
|
||||||
|
_send_frame(device, args.prime_frame, logger, "prime")
|
||||||
|
_read_for(device, detector, logger, args.prime_gap)
|
||||||
|
|
||||||
|
before_trigger = len(detector.frames)
|
||||||
|
_send_frame(device, args.trigger_frame, logger, "trigger")
|
||||||
|
target_seen = _wait_for_target(device, detector, logger, args.target_frame, args.target_timeout)
|
||||||
|
before_ack = len(detector.frames)
|
||||||
|
if not target_seen:
|
||||||
|
logger.event("TARGET_TIMEOUT no ACK sent")
|
||||||
|
_read_for(device, detector, logger, args.post_ack_read)
|
||||||
|
_summary(detector, logger, args.target_frame, before_trigger, before_ack, len(detector.frames))
|
||||||
|
return 3
|
||||||
|
|
||||||
|
if args.ack_guard > 0:
|
||||||
|
logger.event(f"ACK guard {args.ack_guard:.3f}s")
|
||||||
|
_read_for(device, detector, logger, args.ack_guard)
|
||||||
|
before_ack = len(detector.frames)
|
||||||
|
_send_frame(device, args.ack_frame, logger, "ack")
|
||||||
|
_read_for(device, detector, logger, args.post_ack_read)
|
||||||
|
_summary(detector, logger, args.target_frame, before_trigger, before_ack, len(detector.frames))
|
||||||
|
finally:
|
||||||
|
if relay is not None:
|
||||||
|
relay.close()
|
||||||
|
return 0
|
||||||
|
finally:
|
||||||
|
logger.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _wait_for_target(device, detector: FrameDetector, logger: BenchLogger, target: bytes, timeout_seconds: float) -> bool:
|
||||||
|
logger.event(f"WAIT target={format_frame(target)} timeout={timeout_seconds:.3f}s")
|
||||||
|
start_index = len(detector.frames)
|
||||||
|
deadline = time.monotonic() + max(0.0, timeout_seconds)
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
before = len(detector.frames)
|
||||||
|
_read_for(device, detector, logger, 0.050)
|
||||||
|
for frame in detector.frames[max(start_index, before) :]:
|
||||||
|
if frame == target:
|
||||||
|
logger.event(f"TARGET seen {format_frame(frame)}")
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _summary(detector: FrameDetector, logger: BenchLogger, target: bytes, trigger_start: int, ack_start: int, end: int) -> None:
|
||||||
|
trigger_frames = detector.frames[trigger_start:ack_start]
|
||||||
|
post_ack_frames = detector.frames[ack_start:end]
|
||||||
|
logger.emit()
|
||||||
|
logger.emit("Summary")
|
||||||
|
logger.emit(f"rx_frames={len(detector.frames)} trailing_unframed_bytes={len(detector.buffer)}")
|
||||||
|
logger.emit(f"target_before_ack={sum(1 for frame in trigger_frames if frame == target)}")
|
||||||
|
logger.emit(f"target_after_ack={sum(1 for frame in post_ack_frames if frame == target)}")
|
||||||
|
labels = Counter(label for label in detector.labels.elements())
|
||||||
|
for label, count in sorted(labels.items()):
|
||||||
|
logger.emit(f"{label}={count}")
|
||||||
|
|
||||||
|
|
||||||
|
def _emit_plan(args: argparse.Namespace, logger: BenchLogger) -> None:
|
||||||
|
logger.emit(f"prime={format_frame(args.prime_frame)} checksum_ok={int(frame_checksum_ok(args.prime_frame))}")
|
||||||
|
logger.emit(f"trigger={format_frame(args.trigger_frame)} checksum_ok={int(frame_checksum_ok(args.trigger_frame))}")
|
||||||
|
logger.emit(f"target={format_frame(args.target_frame)} checksum_ok={int(frame_checksum_ok(args.target_frame))}")
|
||||||
|
logger.emit(f"ack={format_frame(args.ack_frame)} checksum_ok={int(frame_checksum_ok(args.ack_frame))}")
|
||||||
|
|
||||||
|
|
||||||
|
def _print_plan(args: argparse.Namespace, log_path: Path, stdout: TextIO) -> None:
|
||||||
|
print(f"device={args.port} {args.baud} 8N1", file=stdout)
|
||||||
|
print(f"relay={args.relay_port} {args.relay_baud}", file=stdout)
|
||||||
|
print(f"power_cycle={int(not args.no_power_cycle)}", file=stdout)
|
||||||
|
print(f"prime={format_frame(args.prime_frame)} checksum_ok={int(frame_checksum_ok(args.prime_frame))}", file=stdout)
|
||||||
|
print(f"trigger={format_frame(args.trigger_frame)} checksum_ok={int(frame_checksum_ok(args.trigger_frame))}", file=stdout)
|
||||||
|
print(f"target={format_frame(args.target_frame)} checksum_ok={int(frame_checksum_ok(args.target_frame))}", file=stdout)
|
||||||
|
print(f"ack={format_frame(args.ack_frame)} checksum_ok={int(frame_checksum_ok(args.ack_frame))}", file=stdout)
|
||||||
|
print(f"log={log_path}", file=stdout)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"DEFAULT_ACK_FRAME",
|
||||||
|
"DEFAULT_PRIME_FRAME",
|
||||||
|
"DEFAULT_TARGET_FRAME",
|
||||||
|
"DEFAULT_TRIGGER_FRAME",
|
||||||
|
"build_arg_parser",
|
||||||
|
"main",
|
||||||
|
]
|
||||||
198
h8536/serial_table_dump.py
Normal file
198
h8536/serial_table_dump.py
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TextIO
|
||||||
|
|
||||||
|
from .bench_connect_lcd import (
|
||||||
|
BenchLogger,
|
||||||
|
FrameDetector,
|
||||||
|
_import_serial,
|
||||||
|
_read_for,
|
||||||
|
_relay_command,
|
||||||
|
_relay_settle,
|
||||||
|
_wait_for_ready,
|
||||||
|
format_frame,
|
||||||
|
frame_checksum,
|
||||||
|
frame_checksum_ok,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
READ_COMMAND = 0x01
|
||||||
|
FRAME_LENGTH = 6
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class SelectorEncoding:
|
||||||
|
selector: int
|
||||||
|
frame_hi: int
|
||||||
|
frame_lo: int
|
||||||
|
|
||||||
|
|
||||||
|
def encode_selector(selector: int) -> SelectorEncoding:
|
||||||
|
selector &= 0x01FF
|
||||||
|
if selector <= 0x007F:
|
||||||
|
return SelectorEncoding(selector, 0x00, selector)
|
||||||
|
if selector <= 0x017F:
|
||||||
|
return SelectorEncoding(selector, 0x01, selector - 0x0080)
|
||||||
|
return SelectorEncoding(selector, 0x02, selector - 0x0180)
|
||||||
|
|
||||||
|
|
||||||
|
def build_read_frame(selector: int) -> bytes:
|
||||||
|
encoded = encode_selector(selector)
|
||||||
|
body = bytes([READ_COMMAND, encoded.frame_hi, encoded.frame_lo, 0x00, 0x00])
|
||||||
|
return body + bytes([frame_checksum(body)])
|
||||||
|
|
||||||
|
|
||||||
|
def decode_table_read_response(frame: bytes) -> tuple[int, int] | None:
|
||||||
|
if len(frame) != FRAME_LENGTH or not frame_checksum_ok(frame):
|
||||||
|
return None
|
||||||
|
if frame[0] != 0x04:
|
||||||
|
return None
|
||||||
|
return frame[2], (frame[3] << 8) | frame[4]
|
||||||
|
|
||||||
|
|
||||||
|
def default_log_path() -> Path:
|
||||||
|
return Path("captures") / f"serial-table-dump-{datetime.now().strftime('%Y%m%d-%H%M%S')}.txt"
|
||||||
|
|
||||||
|
|
||||||
|
def build_arg_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=(
|
||||||
|
"Read-only serial table sweep. This uses command 1 frames only; it does not write EEPROM."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
parser.add_argument("--port", default="COM5", help="RS232 serial port connected to the RCP")
|
||||||
|
parser.add_argument("--baud", type=int, default=38400, help="RCP serial baud rate")
|
||||||
|
parser.add_argument("--relay-port", default="COM6", help="Pico relay serial port")
|
||||||
|
parser.add_argument("--relay-baud", type=int, default=115200, help="Pico relay serial baud rate")
|
||||||
|
parser.add_argument("--no-power-cycle", action="store_true", help="do not send relay off/on before the sweep")
|
||||||
|
parser.add_argument("--power-off-command", default="off", help="relay command used to remove DUT power")
|
||||||
|
parser.add_argument("--power-on-command", default="on", help="relay command used to apply DUT power")
|
||||||
|
parser.add_argument("--off-seconds", type=float, default=1.5, help="seconds to hold the DUT powered off")
|
||||||
|
parser.add_argument("--relay-settle", type=float, default=2.0, help="seconds to wait after opening the relay port")
|
||||||
|
parser.add_argument("--ready-timeout", type=float, default=10.0, help="seconds to wait for heartbeat before reading")
|
||||||
|
parser.add_argument("--ready-heartbeats", type=int, default=2, help="heartbeat frames to observe before reading")
|
||||||
|
parser.add_argument("--require-ready", action="store_true", help="abort if ready heartbeat count is not observed")
|
||||||
|
parser.add_argument("--start", type=lambda text: int(text, 0), default=0x000, help="first logical selector")
|
||||||
|
parser.add_argument("--count", type=lambda text: int(text, 0), default=0x80, help="number of selectors to read")
|
||||||
|
parser.add_argument("--selector", action="append", type=lambda text: int(text, 0), help="specific selector to read; repeatable")
|
||||||
|
parser.add_argument("--gap", type=float, default=0.080, help="seconds to listen after each read frame")
|
||||||
|
parser.add_argument("--pre-drain", type=float, default=0.250, help="seconds to drain/log RX before the sweep")
|
||||||
|
parser.add_argument("--post-read", type=float, default=1.0, help="seconds to listen after the sweep")
|
||||||
|
parser.add_argument("--log", type=Path, help="capture log path")
|
||||||
|
parser.add_argument("--dry-run", action="store_true", help="print planned read frames without opening serial ports")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None, *, stdout: TextIO = sys.stdout) -> int:
|
||||||
|
args = build_arg_parser().parse_args(argv)
|
||||||
|
selectors = _selectors(args)
|
||||||
|
log_path = args.log or default_log_path()
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
print(f"device={args.port} {args.baud} 8N1", file=stdout)
|
||||||
|
print(f"relay={args.relay_port} {args.relay_baud}", file=stdout)
|
||||||
|
print(f"power_cycle={int(not args.no_power_cycle)}", file=stdout)
|
||||||
|
for selector in selectors:
|
||||||
|
encoded = encode_selector(selector)
|
||||||
|
frame = build_read_frame(selector)
|
||||||
|
print(
|
||||||
|
f"selector=0x{selector:03X} encoded={encoded.frame_hi:02X} {encoded.frame_lo:02X} "
|
||||||
|
f"frame={format_frame(frame)} checksum_ok={int(frame_checksum_ok(frame))}",
|
||||||
|
file=stdout,
|
||||||
|
)
|
||||||
|
print(f"log={log_path}", file=stdout)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
serial = _import_serial()
|
||||||
|
logger = BenchLogger(log_path, stdout=stdout)
|
||||||
|
detector = FrameDetector()
|
||||||
|
response_rows: list[tuple[int, bytes, tuple[int, int] | None]] = []
|
||||||
|
try:
|
||||||
|
logger.emit("Read-only serial table sweep")
|
||||||
|
logger.emit(f"device={args.port} {args.baud} 8N1 relay={args.relay_port} {args.relay_baud}")
|
||||||
|
logger.emit(f"log={log_path}")
|
||||||
|
logger.emit(f"selectors={len(selectors)} command=01 write_frames=0")
|
||||||
|
|
||||||
|
with serial.Serial(args.port, args.baud, bytesize=8, parity="N", stopbits=1, timeout=0.05) as device:
|
||||||
|
relay = None
|
||||||
|
try:
|
||||||
|
if not args.no_power_cycle:
|
||||||
|
relay = serial.Serial(args.relay_port, args.relay_baud, timeout=0.25)
|
||||||
|
_relay_settle(relay, args.relay_settle, logger)
|
||||||
|
_relay_command(relay, args.power_off_command, logger)
|
||||||
|
time.sleep(args.off_seconds)
|
||||||
|
device.reset_input_buffer()
|
||||||
|
detector = FrameDetector()
|
||||||
|
_relay_command(relay, args.power_on_command, logger)
|
||||||
|
else:
|
||||||
|
device.reset_input_buffer()
|
||||||
|
|
||||||
|
ready = _wait_for_ready(device, detector, logger, args.ready_timeout, args.ready_heartbeats)
|
||||||
|
if args.require_ready and not ready:
|
||||||
|
logger.event("ABORT ready heartbeat threshold was not observed")
|
||||||
|
return 2
|
||||||
|
if args.pre_drain > 0:
|
||||||
|
logger.event(f"DRAIN before read sweep {args.pre_drain:.3f}s")
|
||||||
|
_read_for(device, detector, logger, args.pre_drain)
|
||||||
|
|
||||||
|
for selector in selectors:
|
||||||
|
frame = build_read_frame(selector)
|
||||||
|
before = len(detector.frames)
|
||||||
|
logger.event(f"READ selector=0x{selector:03X} frame={format_frame(frame)}")
|
||||||
|
device.write(frame)
|
||||||
|
device.flush()
|
||||||
|
logger.chunk("TX", frame)
|
||||||
|
_read_for(device, detector, logger, args.gap)
|
||||||
|
for response in detector.frames[before:]:
|
||||||
|
decoded = decode_table_read_response(response)
|
||||||
|
if decoded is not None:
|
||||||
|
page_or_echo, value = decoded
|
||||||
|
logger.event(
|
||||||
|
f"TABLE selector=0x{selector:03X} echo={page_or_echo:02X} value={value:04X}"
|
||||||
|
)
|
||||||
|
response_rows.append((selector, response, decoded))
|
||||||
|
|
||||||
|
_read_for(device, detector, logger, args.post_read)
|
||||||
|
finally:
|
||||||
|
if relay is not None:
|
||||||
|
relay.close()
|
||||||
|
_summary(response_rows, detector, logger)
|
||||||
|
return 0
|
||||||
|
finally:
|
||||||
|
logger.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _selectors(args: argparse.Namespace) -> list[int]:
|
||||||
|
if args.selector:
|
||||||
|
return [selector & 0x01FF for selector in args.selector]
|
||||||
|
start = args.start & 0x01FF
|
||||||
|
count = max(0, args.count)
|
||||||
|
return [((start + offset) & 0x01FF) for offset in range(count)]
|
||||||
|
|
||||||
|
|
||||||
|
def _summary(
|
||||||
|
response_rows: list[tuple[int, bytes, tuple[int, int] | None]],
|
||||||
|
detector: FrameDetector,
|
||||||
|
logger: BenchLogger,
|
||||||
|
) -> None:
|
||||||
|
table_rows = [(selector, decoded[1]) for selector, _frame, decoded in response_rows if decoded is not None]
|
||||||
|
logger.emit()
|
||||||
|
logger.emit("Summary")
|
||||||
|
logger.emit(f"rx_frames={len(detector.frames)} table_response_rows={len(table_rows)}")
|
||||||
|
for selector, value in table_rows:
|
||||||
|
logger.emit(f"table selector=0x{selector:03X} value=0x{value:04X}")
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"build_arg_parser",
|
||||||
|
"build_read_frame",
|
||||||
|
"decode_table_read_response",
|
||||||
|
"encode_selector",
|
||||||
|
"main",
|
||||||
|
]
|
||||||
14
scripts/serial_ack_probe.py
Normal file
14
scripts/serial_ack_probe.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
"""Bench ACK probe for the stateful H8/536 serial retry frame."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
if str(ROOT) not in sys.path:
|
||||||
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
|
from h8536.serial_ack_probe import main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
14
scripts/serial_table_dump.py
Normal file
14
scripts/serial_table_dump.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Read-only command-1 serial table sweep helper."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||||
|
|
||||||
|
from h8536.serial_table_dump import main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -49,6 +49,9 @@ class BenchConnectLcdTest(unittest.TestCase):
|
|||||||
def test_label_frame_marks_real_bench_c0_6020_response(self):
|
def test_label_frame_marks_real_bench_c0_6020_response(self):
|
||||||
self.assertEqual(label_frame(bytes.fromhex("0780C060205D")), "visible_C0_6020_family_candidate")
|
self.assertEqual(label_frame(bytes.fromhex("0780C060205D")), "visible_C0_6020_family_candidate")
|
||||||
|
|
||||||
|
def test_label_frame_marks_visible_retry_ack_target(self):
|
||||||
|
self.assertEqual(label_frame(bytes.fromhex("07804020902D")), "visible_retry_0040_2090_candidate")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from h8536.emulator import H8536Emulator
|
from h8536.emulator import H8536Emulator, MemoryMap, P7DDR, P7DR
|
||||||
|
|
||||||
|
|
||||||
def rom_with_reset(*, reset: int = 0x1000, size: int = 0x1100) -> bytearray:
|
def rom_with_reset(*, reset: int = 0x1000, size: int = 0x1100) -> bytearray:
|
||||||
@@ -10,6 +10,35 @@ def rom_with_reset(*, reset: int = 0x1000, size: int = 0x1100) -> bytearray:
|
|||||||
|
|
||||||
|
|
||||||
class EmulatorAddressingTest(unittest.TestCase):
|
class EmulatorAddressingTest(unittest.TestCase):
|
||||||
|
def test_p7dr_reads_external_pin_state_for_input_bits(self):
|
||||||
|
memory = MemoryMap(b"\x00" * 4, p7_input=0xFF)
|
||||||
|
|
||||||
|
memory.write8(P7DDR, 0x00)
|
||||||
|
memory.write8(P7DR, 0x00)
|
||||||
|
|
||||||
|
self.assertEqual(memory.read8(P7DR), 0xFF)
|
||||||
|
|
||||||
|
def test_p7dr_output_bits_read_latch_while_input_bits_read_pins(self):
|
||||||
|
memory = MemoryMap(b"\x00" * 4, p7_input=0x7F)
|
||||||
|
|
||||||
|
memory.write8(P7DDR, 0x80)
|
||||||
|
memory.write8(P7DR, 0x00)
|
||||||
|
|
||||||
|
self.assertEqual(memory.read8(P7DR), 0x7F)
|
||||||
|
|
||||||
|
def test_factory_eeprom_seed_populates_shadow_and_page_labels(self):
|
||||||
|
rom = bytearray([0xFF] * 0xCB00)
|
||||||
|
rom[0xC966:0xC968] = b"\x6B\x6F"
|
||||||
|
rom[0xC974:0xC976] = b"\x80\x00"
|
||||||
|
|
||||||
|
memory = MemoryMap(bytes(rom))
|
||||||
|
memory.seed_factory_eeprom_and_shadow()
|
||||||
|
|
||||||
|
self.assertEqual(memory.read16(0xF402), 0x6B6F)
|
||||||
|
self.assertEqual(memory.p9_bus.fast_read_word(0x0010), (True, 0x8000))
|
||||||
|
self.assertEqual(memory.p9_bus.fast_read_word(0x0110), (True, 0x8000))
|
||||||
|
self.assertEqual(memory.p9_bus.fast_read_word(0x0002), (True, 0x2020))
|
||||||
|
|
||||||
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()
|
||||||
rom[0x1000:0x1004] = b"\xF0\xF8\x58\x80" # MOV:G.B @(-H'07A8,R0), R0
|
rom[0x1000:0x1004] = b"\xF0\xF8\x58\x80" # MOV:G.B @(-H'07A8,R0), R0
|
||||||
@@ -62,7 +91,7 @@ class EmulatorAddressingTest(unittest.TestCase):
|
|||||||
rom = rom_with_reset()
|
rom = rom_with_reset()
|
||||||
rom[0x1000:0x1003] = b"\xE1\xFE\x81" # MOV:G.B @(H'-02,R1), R1
|
rom[0x1000:0x1003] = b"\xE1\xFE\x81" # MOV:G.B @(H'-02,R1), R1
|
||||||
|
|
||||||
emulator = H8536Emulator(bytes(rom))
|
emulator = H8536Emulator(bytes(rom), p7_input=0x37)
|
||||||
emulator.cpu.regs[1] = 0xFE90
|
emulator.cpu.regs[1] = 0xFE90
|
||||||
emulator.memory.write8(0xFE8E, 0x37)
|
emulator.memory.write8(0xFE8E, 0x37)
|
||||||
emulator.step()
|
emulator.step()
|
||||||
|
|||||||
32
tests/test_serial_table_dump.py
Normal file
32
tests/test_serial_table_dump.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from h8536.bench_connect_lcd import frame_checksum_ok
|
||||||
|
from h8536.serial_table_dump import build_read_frame, decode_table_read_response, encode_selector
|
||||||
|
|
||||||
|
|
||||||
|
class SerialTableDumpTest(unittest.TestCase):
|
||||||
|
def test_encode_selector_matches_rom_loc_622b_ranges(self):
|
||||||
|
self.assertEqual((encode_selector(0x000).frame_hi, encode_selector(0x000).frame_lo), (0x00, 0x00))
|
||||||
|
self.assertEqual((encode_selector(0x07F).frame_hi, encode_selector(0x07F).frame_lo), (0x00, 0x7F))
|
||||||
|
self.assertEqual((encode_selector(0x080).frame_hi, encode_selector(0x080).frame_lo), (0x01, 0x00))
|
||||||
|
self.assertEqual((encode_selector(0x17F).frame_hi, encode_selector(0x17F).frame_lo), (0x01, 0xFF))
|
||||||
|
self.assertEqual((encode_selector(0x180).frame_hi, encode_selector(0x180).frame_lo), (0x02, 0x00))
|
||||||
|
self.assertEqual((encode_selector(0x1FF).frame_hi, encode_selector(0x1FF).frame_lo), (0x02, 0x7F))
|
||||||
|
|
||||||
|
def test_build_read_frame_uses_command_1_and_checksum(self):
|
||||||
|
frame = build_read_frame(0x180)
|
||||||
|
|
||||||
|
self.assertEqual(frame[:5], bytes.fromhex("01 02 00 00 00"))
|
||||||
|
self.assertTrue(frame_checksum_ok(frame))
|
||||||
|
|
||||||
|
def test_decode_table_read_response_extracts_value_candidate(self):
|
||||||
|
frame = bytes.fromhex("04 00 12 80 80 4C")
|
||||||
|
|
||||||
|
self.assertEqual(decode_table_read_response(frame), (0x12, 0x8080))
|
||||||
|
|
||||||
|
def test_decode_table_read_response_ignores_non_readback(self):
|
||||||
|
self.assertIsNone(decode_table_read_response(bytes.fromhex("00 00 00 00 80 DA")))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user