emualtor working
This commit is contained in:
14
README.md
14
README.md
@@ -32,6 +32,7 @@ To run the newer sidecar protocol and gate/queue analysis tools:
|
|||||||
.\.venv\Scripts\python.exe h8536_serial_gate.py build\rom_decompiled.json --out build\rom_serial_gate.txt
|
.\.venv\Scripts\python.exe h8536_serial_gate.py build\rom_decompiled.json --out build\rom_serial_gate.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_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_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
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -60,6 +61,8 @@ To start the current emulator harness:
|
|||||||
- Reconstructs evidence-supported SCI1 serial frame candidates, including the apparent six-byte TX/RX units and XOR checksum seeded by `0x5A`.
|
- Reconstructs evidence-supported SCI1 serial frame candidates, including the apparent six-byte TX/RX units and XOR checksum seeded by `0x5A`.
|
||||||
- Infers candidate serial protocol semantics from validated frames, including `RX[0] & 0x07` command dispatch, likely index/value byte roles, and response staging through `F850-F854`.
|
- Infers candidate serial protocol semantics from validated frames, including `RX[0] & 0x07` command dispatch, likely index/value byte roles, and response staging through `F850-F854`.
|
||||||
- Generates a focused RX/TX serial-path pseudocode view from those serial reconstruction and protocol-semantic candidates.
|
- Generates a focused RX/TX serial-path pseudocode view from those serial reconstruction and protocol-semantic candidates.
|
||||||
|
- Marks H8 word-destination writes fed by byte immediates as explicit zero-extension in pseudocode, including the heartbeat queue write at `loc_4067`.
|
||||||
|
- Emits a decompiler/pseudocode consistency report for width semantics that are easy to misread.
|
||||||
- Decodes observed serial byte captures into six-byte frames, validates checksums, labels capture-observed heartbeat/call/camera-power candidates, and summarizes heartbeat cadence.
|
- Decodes observed serial byte captures into six-byte frames, validates checksums, labels capture-observed heartbeat/call/camera-power candidates, and summarizes heartbeat cadence.
|
||||||
- Accepts both analyzer-style lines such as `RX 006 bytes ...` and the idle reference `frame 006 ...` format in `ROM/rcp-txd-idle-only.txt`.
|
- Accepts both analyzer-style lines such as `RX 006 bytes ...` and the idle reference `frame 006 ...` format in `ROM/rcp-txd-idle-only.txt`.
|
||||||
- Reconstructs the autonomous serial gate/queue state-machine around `loc_3FD3`, `loc_BAF2`, `F9B0/F9B5`, `FAA2/FAA3/FAA5`, the `F9C4`/FRT2 idle heartbeat gate at `loc_4046`, and the resend path through `BE9E/BED5`.
|
- Reconstructs the autonomous serial gate/queue state-machine around `loc_3FD3`, `loc_BAF2`, `F9B0/F9B5`, `FAA2/FAA3/FAA5`, the `F9C4`/FRT2 idle heartbeat gate at `loc_4046`, and the resend path through `BE9E/BED5`.
|
||||||
@@ -87,7 +90,8 @@ Current serial observations:
|
|||||||
- Idle frame: `00 00 00 00 80 DA`.
|
- Idle frame: `00 00 00 00 80 DA`.
|
||||||
- Capture-side label: `heartbeat_alive_candidate`.
|
- Capture-side label: `heartbeat_alive_candidate`.
|
||||||
- Idle cadence from the reference file: 54 frames, average about 699.9 ms, min 601 ms, max 803 ms.
|
- Idle cadence from the reference file: 54 frames, average about 699.9 ms, min 601 ms, max 803 ms.
|
||||||
- Static/runtime finding: `F9C4` is a candidate idle heartbeat/report countdown. Init loads `H'14`, `loc_BA26` reloads `H'07` after a send, FRT2 OCIA decrements it, and `loc_4046` can enqueue report `H'00FF` when it reaches zero and the queue is empty.
|
- Static/runtime finding: `F9C4` is a candidate idle heartbeat/report countdown. Init loads `H'14`, `loc_BA26` reloads `H'07` after a send, FRT2 OCIA decrements it, and `loc_4046` can enqueue report `H'0000` when it reaches zero and the queue is empty.
|
||||||
|
- Runtime-confirmed heartbeat path: `loc_4067` writes `H'0000` into the queue via a zero-extended word move, `loc_BAF2/loc_BB08` dequeue it, `loc_BB1C/loc_BB20/loc_BB2B` stage the TX bytes, and `loc_BA26` emits `00 00 00 00 80 DA`.
|
||||||
- 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.
|
||||||
|
|
||||||
The generated listing is written to:
|
The generated listing is written to:
|
||||||
@@ -110,6 +114,7 @@ build/rom_serial_pseudocode.c
|
|||||||
build/rom_serial_gate.txt
|
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_consistency.txt
|
||||||
build/callgraph.dot
|
build/callgraph.dot
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -172,11 +177,13 @@ For gate/queue and table reports:
|
|||||||
python h8536_serial_gate.py --help
|
python h8536_serial_gate.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_consistency.py --help
|
||||||
```
|
```
|
||||||
|
|
||||||
- `h8536_serial_gate.py`: reports the autonomous TX gate and report queue evidence.
|
- `h8536_serial_gate.py`: reports the autonomous TX gate and report queue evidence.
|
||||||
- `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_consistency.py`: flags JSON-to-pseudocode semantic hazards such as byte immediates written to word destinations.
|
||||||
|
|
||||||
For the emulator harness:
|
For the emulator harness:
|
||||||
|
|
||||||
@@ -194,7 +201,7 @@ python h8536_emulator_probe.py --help
|
|||||||
- `--p9-fast-path`: shortcut known P9 transfer routines for exploration.
|
- `--p9-fast-path`: shortcut known P9 transfer routines for exploration.
|
||||||
- `--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.
|
||||||
- Current status: boots from `H'1000`, initializes SCI1, models the first P9 bit-banged handshakes, captures P9 byte candidates, can optionally fast-path known P9 routines, and schedules FRT1/FRT2 OCIA. The probe now reaches the SCI1 transmit path, but still needs more timing/peripheral fidelity before it emits the full observed heartbeat frame.
|
- Current status: boots from `H'1000`, initializes SCI1, models the first P9 bit-banged handshakes, captures P9 byte candidates, can optionally fast-path known P9 routines, and schedules FRT1/FRT2 OCIA. With the P9 fast path and current timer cadence, the emulator reaches the SCI1 transmit path and emits the observed heartbeat frame `00 00 00 00 80 DA`.
|
||||||
|
|
||||||
## Code Layout
|
## Code Layout
|
||||||
|
|
||||||
@@ -224,6 +231,7 @@ python h8536_emulator_probe.py --help
|
|||||||
- `h8536/serial_gate.py`: autonomous TX gate/queue state-machine reconstruction.
|
- `h8536/serial_gate.py`: autonomous TX gate/queue state-machine reconstruction.
|
||||||
- `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/consistency.py`: decompiler/pseudocode semantic consistency checks.
|
||||||
- `h8536/emulator/`: early H8/536 emulator package split into CPU state, memory map, SCI1 TX capture, P9 bus model, runner, probe, CLI, and peripheral scaffolding.
|
- `h8536/emulator/`: early H8/536 emulator package split into CPU state, memory map, SCI1 TX capture, P9 bus model, runner, probe, CLI, and peripheral scaffolding.
|
||||||
- `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.
|
||||||
- `h8536/peripheral_access.py`: FRT/A-D TEMP-register access analysis.
|
- `h8536/peripheral_access.py`: FRT/A-D TEMP-register access analysis.
|
||||||
@@ -233,5 +241,5 @@ python h8536_emulator_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`: 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`: emulator CLI wrappers.
|
- `h8536_emulator.py`, `h8536_emulator_probe.py`: emulator CLI wrappers.
|
||||||
|
|||||||
9
build/rom_consistency.txt
Normal file
9
build/rom_consistency.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
Decompiler/Pseudocode Consistency
|
||||||
|
3 byte-immediate-to-word destination case(s) require explicit zero-extension in pseudocode.
|
||||||
|
|
||||||
|
- H'1043: MOV:G.W #H'00, @FRT1_FRC_H [requires_zero_extend8_to16_pseudocode]
|
||||||
|
Word-sized MOV with an 8-bit immediate writes a zero-extended word. Pseudocode should not model this as a one-byte write or preserve the old low byte.
|
||||||
|
- H'1058: MOV:G.W #H'00, @FRT2_FRC_H [requires_zero_extend8_to16_pseudocode]
|
||||||
|
Word-sized MOV with an 8-bit immediate writes a zero-extended word. Pseudocode should not model this as a one-byte write or preserve the old low byte.
|
||||||
|
- H'4067: MOV:G.W #H'00, @(-H'0790,R2) [requires_zero_extend8_to16_pseudocode]
|
||||||
|
Word-sized MOV with an 8-bit immediate writes a zero-extended word. Pseudocode should not model this as a one-byte write or preserve the old low byte.
|
||||||
@@ -186062,6 +186062,42 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"decompiler_consistency": {
|
||||||
|
"kind": "decompiler_pseudocode_consistency",
|
||||||
|
"summary": "3 byte-immediate-to-word destination case(s) require explicit zero-extension in pseudocode.",
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"kind": "byte_immediate_to_word_destination",
|
||||||
|
"status": "requires_zero_extend8_to16_pseudocode",
|
||||||
|
"address": 4163,
|
||||||
|
"address_hex": "H'1043",
|
||||||
|
"instruction": "MOV:G.W #H'00, @FRT1_FRC_H",
|
||||||
|
"expected_pseudocode_hint": "zero_extend8_to16",
|
||||||
|
"zero_extended_value_hex": "0x0000",
|
||||||
|
"summary": "Word-sized MOV with an 8-bit immediate writes a zero-extended word. Pseudocode should not model this as a one-byte write or preserve the old low byte."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "byte_immediate_to_word_destination",
|
||||||
|
"status": "requires_zero_extend8_to16_pseudocode",
|
||||||
|
"address": 4184,
|
||||||
|
"address_hex": "H'1058",
|
||||||
|
"instruction": "MOV:G.W #H'00, @FRT2_FRC_H",
|
||||||
|
"expected_pseudocode_hint": "zero_extend8_to16",
|
||||||
|
"zero_extended_value_hex": "0x0000",
|
||||||
|
"summary": "Word-sized MOV with an 8-bit immediate writes a zero-extended word. Pseudocode should not model this as a one-byte write or preserve the old low byte."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "byte_immediate_to_word_destination",
|
||||||
|
"status": "requires_zero_extend8_to16_pseudocode",
|
||||||
|
"address": 16487,
|
||||||
|
"address_hex": "H'4067",
|
||||||
|
"instruction": "MOV:G.W #H'00, @(-H'0790,R2)",
|
||||||
|
"expected_pseudocode_hint": "zero_extend8_to16",
|
||||||
|
"zero_extended_value_hex": "0x0000",
|
||||||
|
"summary": "Word-sized MOV with an 8-bit immediate writes a zero-extended word. Pseudocode should not model this as a one-byte write or preserve the old low byte."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"serial_semantics": {
|
"serial_semantics": {
|
||||||
"kind": "serial_semantics",
|
"kind": "serial_semantics",
|
||||||
"protocol_semantics": [
|
"protocol_semantics": [
|
||||||
@@ -196427,7 +196463,7 @@
|
|||||||
"entry_label": "loc_4046",
|
"entry_label": "loc_4046",
|
||||||
"target_label": "loc_4067",
|
"target_label": "loc_4067",
|
||||||
"condition_candidate": "F9C4 == 0 && ((FAA5.bit7 == 0) || (F9C3 == 0)) && F9B0 == F9B5",
|
"condition_candidate": "F9C4 == 0 && ((FAA5.bit7 == 0) || (F9C3 == 0)) && F9B0 == F9B5",
|
||||||
"summary": "Idle/default report gate; when the FRT2 countdown clears and the queue is empty, loc_4046 can enqueue H'00FF for the later loc_BAF2 -> loc_BA26 send path.",
|
"summary": "Idle/default report gate; when the FRT2 countdown clears and the queue is empty, loc_4046 can enqueue H'0000 for the later loc_BAF2 -> loc_BA26 send path.",
|
||||||
"state_addresses_hex": [
|
"state_addresses_hex": [
|
||||||
"H'F9C4",
|
"H'F9C4",
|
||||||
"H'FAA5",
|
"H'FAA5",
|
||||||
@@ -196435,7 +196471,26 @@
|
|||||||
"H'F9B0",
|
"H'F9B0",
|
||||||
"H'F9B5"
|
"H'F9B5"
|
||||||
],
|
],
|
||||||
"enqueued_report_candidate_hex": "H'00FF",
|
"enqueued_report_candidate_hex": "H'0000",
|
||||||
|
"write_semantics_candidate": "loc_4067 is MOV:G.W #H'00, @(-H'0790,R2): the byte immediate is zero-extended by the word destination, so the queue slot becomes H'0000.",
|
||||||
|
"runtime_trace_confirmation": {
|
||||||
|
"source": "h8536_emulator_probe target-frame run",
|
||||||
|
"report_id_hex": "H'0000",
|
||||||
|
"queue_write_address_hex": "H'4067",
|
||||||
|
"queue_write_semantics": "H'FFFF -> H'0000, not H'00FF",
|
||||||
|
"dequeue_path": [
|
||||||
|
"loc_4046",
|
||||||
|
"loc_BAF2",
|
||||||
|
"loc_BB08",
|
||||||
|
"loc_BB1C",
|
||||||
|
"loc_BB20",
|
||||||
|
"loc_BB2B",
|
||||||
|
"loc_BA26"
|
||||||
|
],
|
||||||
|
"emitted_frame_hex": "00 00 00 00 80 DA",
|
||||||
|
"checksum_seed_hex": "H'5A",
|
||||||
|
"checksum_hex": "H'DA"
|
||||||
|
},
|
||||||
"evidence_addresses": [
|
"evidence_addresses": [
|
||||||
16454,
|
16454,
|
||||||
16458,
|
16458,
|
||||||
@@ -196847,6 +196902,32 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"runtime_confirmed_paths": [
|
||||||
|
{
|
||||||
|
"name": "idle_heartbeat_report_runtime_confirmation",
|
||||||
|
"report_id_hex": "H'0000",
|
||||||
|
"queue_write_address_hex": "H'4067",
|
||||||
|
"queue_write_semantics": "MOV:G.W #H'00 writes H'0000 to the queue slot",
|
||||||
|
"staging_path": [
|
||||||
|
"loc_4046",
|
||||||
|
"loc_BAF2",
|
||||||
|
"loc_BB08",
|
||||||
|
"loc_BB1C",
|
||||||
|
"loc_BB20",
|
||||||
|
"loc_BB2B",
|
||||||
|
"loc_BA26"
|
||||||
|
],
|
||||||
|
"emitted_frame_hex": "00 00 00 00 80 DA",
|
||||||
|
"checksum_hex": "H'DA"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consistency_checks": [
|
||||||
|
{
|
||||||
|
"name": "idle_heartbeat_report_id_width",
|
||||||
|
"status": "pass",
|
||||||
|
"summary": "Decompiler mnemonic MOV:G.W and emulator execution now agree that the H'00 immediate at loc_4067 is zero-extended to report H'0000."
|
||||||
|
}
|
||||||
|
],
|
||||||
"observed_autonomous_output_caveat": "Real captures supplied so far show only heartbeat/idle, call, and camera-power autonomous TX frames. Other panel controls may require a host/device request or state transition before the firmware reports them.",
|
"observed_autonomous_output_caveat": "Real captures supplied so far show only heartbeat/idle, call, and camera-power autonomous TX frames. Other panel controls may require a host/device request or state transition before the firmware reports them.",
|
||||||
"confidence": "candidate-medium",
|
"confidence": "candidate-medium",
|
||||||
"caveat": "This is a TX/report model for the BB43 -> BA26 path, separate from RX command dispatch. Observed report names are a capture overlay candidate only, not hard-coded source truth.",
|
"caveat": "This is a TX/report model for the BB43 -> BA26 path, separate from RX command dispatch. Observed report names are a capture overlay candidate only, not hard-coded source truth.",
|
||||||
@@ -206677,7 +206758,7 @@
|
|||||||
"entry_label": "loc_4046",
|
"entry_label": "loc_4046",
|
||||||
"target_label": "loc_4067",
|
"target_label": "loc_4067",
|
||||||
"condition_candidate": "F9C4 == 0 && ((FAA5.bit7 == 0) || (F9C3 == 0)) && F9B0 == F9B5",
|
"condition_candidate": "F9C4 == 0 && ((FAA5.bit7 == 0) || (F9C3 == 0)) && F9B0 == F9B5",
|
||||||
"summary": "Idle/default report gate; when the FRT2 countdown clears and the queue is empty, loc_4046 can enqueue H'00FF for the later loc_BAF2 -> loc_BA26 send path.",
|
"summary": "Idle/default report gate; when the FRT2 countdown clears and the queue is empty, loc_4046 can enqueue H'0000 for the later loc_BAF2 -> loc_BA26 send path.",
|
||||||
"state_addresses_hex": [
|
"state_addresses_hex": [
|
||||||
"H'F9C4",
|
"H'F9C4",
|
||||||
"H'FAA5",
|
"H'FAA5",
|
||||||
@@ -206685,7 +206766,26 @@
|
|||||||
"H'F9B0",
|
"H'F9B0",
|
||||||
"H'F9B5"
|
"H'F9B5"
|
||||||
],
|
],
|
||||||
"enqueued_report_candidate_hex": "H'00FF",
|
"enqueued_report_candidate_hex": "H'0000",
|
||||||
|
"write_semantics_candidate": "loc_4067 is MOV:G.W #H'00, @(-H'0790,R2): the byte immediate is zero-extended by the word destination, so the queue slot becomes H'0000.",
|
||||||
|
"runtime_trace_confirmation": {
|
||||||
|
"source": "h8536_emulator_probe target-frame run",
|
||||||
|
"report_id_hex": "H'0000",
|
||||||
|
"queue_write_address_hex": "H'4067",
|
||||||
|
"queue_write_semantics": "H'FFFF -> H'0000, not H'00FF",
|
||||||
|
"dequeue_path": [
|
||||||
|
"loc_4046",
|
||||||
|
"loc_BAF2",
|
||||||
|
"loc_BB08",
|
||||||
|
"loc_BB1C",
|
||||||
|
"loc_BB20",
|
||||||
|
"loc_BB2B",
|
||||||
|
"loc_BA26"
|
||||||
|
],
|
||||||
|
"emitted_frame_hex": "00 00 00 00 80 DA",
|
||||||
|
"checksum_seed_hex": "H'5A",
|
||||||
|
"checksum_hex": "H'DA"
|
||||||
|
},
|
||||||
"evidence_addresses": [
|
"evidence_addresses": [
|
||||||
16454,
|
16454,
|
||||||
16458,
|
16458,
|
||||||
@@ -207097,6 +207197,32 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"runtime_confirmed_paths": [
|
||||||
|
{
|
||||||
|
"name": "idle_heartbeat_report_runtime_confirmation",
|
||||||
|
"report_id_hex": "H'0000",
|
||||||
|
"queue_write_address_hex": "H'4067",
|
||||||
|
"queue_write_semantics": "MOV:G.W #H'00 writes H'0000 to the queue slot",
|
||||||
|
"staging_path": [
|
||||||
|
"loc_4046",
|
||||||
|
"loc_BAF2",
|
||||||
|
"loc_BB08",
|
||||||
|
"loc_BB1C",
|
||||||
|
"loc_BB20",
|
||||||
|
"loc_BB2B",
|
||||||
|
"loc_BA26"
|
||||||
|
],
|
||||||
|
"emitted_frame_hex": "00 00 00 00 80 DA",
|
||||||
|
"checksum_hex": "H'DA"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consistency_checks": [
|
||||||
|
{
|
||||||
|
"name": "idle_heartbeat_report_id_width",
|
||||||
|
"status": "pass",
|
||||||
|
"summary": "Decompiler mnemonic MOV:G.W and emulator execution now agree that the H'00 immediate at loc_4067 is zero-extended to report H'0000."
|
||||||
|
}
|
||||||
|
],
|
||||||
"observed_autonomous_output_caveat": "Real captures supplied so far show only heartbeat/idle, call, and camera-power autonomous TX frames. Other panel controls may require a host/device request or state transition before the firmware reports them.",
|
"observed_autonomous_output_caveat": "Real captures supplied so far show only heartbeat/idle, call, and camera-power autonomous TX frames. Other panel controls may require a host/device request or state transition before the firmware reports them.",
|
||||||
"confidence": "candidate-medium",
|
"confidence": "candidate-medium",
|
||||||
"caveat": "This is a TX/report model for the BB43 -> BA26 path, separate from RX command dispatch. Observed report names are a capture overlay candidate only, not hard-coded source truth.",
|
"caveat": "This is a TX/report model for the BB43 -> BA26 path, separate from RX command dispatch. Observed report names are a capture overlay candidate only, not hard-coded source truth.",
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ u16 SR;
|
|||||||
u8 CCR, BR, EP, DP, TP;
|
u8 CCR, BR, EP, DP, TP;
|
||||||
int C, Z, N, V;
|
int C, Z, N, V;
|
||||||
|
|
||||||
|
static inline u16 zero_extend8_to16(u8 value) { return (u16)value; }
|
||||||
|
|
||||||
/* H8/536 register field symbols used by this ROM. */
|
/* H8/536 register field symbols used by this ROM. */
|
||||||
extern volatile u8 P1DDR; /* 0xFE80 */
|
extern volatile u8 P1DDR; /* 0xFE80 */
|
||||||
extern volatile u8 P1DR; /* 0xFE82 */
|
extern volatile u8 P1DR; /* 0xFE82 */
|
||||||
@@ -468,11 +470,11 @@ void vec_reset_1000(void)
|
|||||||
SYSCR2 = (uint8_t)(0x84); /* 1034; MOV:G.B #H'84, @SYSCR2; SYSCR2 = H'84 (IRQ5E=0 IRQ4E=0 IRQ3E=0 IRQ2E=0 P6PWME=1 P9PWME=0 P9SCI2E=0; enabled P6 PWM); SYSCR2 write leaves P9SCI2E=0; SCI2 pins are disabled, so SCI2 is not the traced MAX202 path; traced RS232/MAX202 remains SCI1 P95/P96; refs SYSCR2; cycles=9 */
|
SYSCR2 = (uint8_t)(0x84); /* 1034; MOV:G.B #H'84, @SYSCR2; SYSCR2 = H'84 (IRQ5E=0 IRQ4E=0 IRQ3E=0 IRQ2E=0 P6PWME=1 P9PWME=0 P9SCI2E=0; enabled P6 PWM); SYSCR2 write leaves P9SCI2E=0; SCI2 pins are disabled, so SCI2 is not the traced MAX202 path; traced RS232/MAX202 remains SCI1 P95/P96; refs SYSCR2; cycles=9 */
|
||||||
FRT1_TCR = (uint8_t)(0x02); /* 1039; MOV:G.B #H'02, @FRT1_TCR; FRT1_TCR = H'02 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=1 CKS0=0); refs FRT1_TCR; cycles=9 */
|
FRT1_TCR = (uint8_t)(0x02); /* 1039; MOV:G.B #H'02, @FRT1_TCR; FRT1_TCR = H'02 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=1 CKS0=0); refs FRT1_TCR; cycles=9 */
|
||||||
FRT1_TCSR = (uint8_t)(0x01); /* 103E; MOV:G.B #H'01, @FRT1_TCSR; FRT1_TCSR = H'01 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=1); refs FRT1_TCSR; cycles=9 */
|
FRT1_TCSR = (uint8_t)(0x01); /* 103E; MOV:G.B #H'01, @FRT1_TCSR; FRT1_TCSR = H'01 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=1); refs FRT1_TCSR; cycles=9 */
|
||||||
FRT1_FRC_H = (uint16_t)(0x00); /* 1043; MOV:G.W #H'00, @FRT1_FRC_H; FRT1_FRC_H = H'00; refs FRT1_FRC_H; FRT1_FRC W write high TEMP access; cycles=9 */
|
FRT1_FRC_H = zero_extend8_to16(0x00); /* 1043; MOV:G.W #H'00, @FRT1_FRC_H; FRT1_FRC_H = H'00; byte immediate zero-extended into word destination; refs FRT1_FRC_H; FRT1_FRC W write high TEMP access; cycles=9 */
|
||||||
FRT1_OCRA_H = (uint16_t)(0x009C); /* 1048; MOV:G.W #H'009C, @FRT1_OCRA_H; FRT1_OCRA_H = H'9C; refs FRT1_OCRA_H; FRT1_OCRA W write high TEMP access; cycles=11 */
|
FRT1_OCRA_H = (uint16_t)(0x009C); /* 1048; MOV:G.W #H'009C, @FRT1_OCRA_H; FRT1_OCRA_H = H'9C; refs FRT1_OCRA_H; FRT1_OCRA W write high TEMP access; cycles=11 */
|
||||||
FRT2_TCR = (uint8_t)(0x02); /* 104E; MOV:G.B #H'02, @FRT2_TCR; FRT2_TCR = H'02 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=1 CKS0=0); refs FRT2_TCR; cycles=9 */
|
FRT2_TCR = (uint8_t)(0x02); /* 104E; MOV:G.B #H'02, @FRT2_TCR; FRT2_TCR = H'02 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=1 CKS0=0); refs FRT2_TCR; cycles=9 */
|
||||||
FRT2_TCSR = (uint8_t)(0x01); /* 1053; MOV:G.B #H'01, @FRT2_TCSR; FRT2_TCSR = H'01 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=1); refs FRT2_TCSR; cycles=9 */
|
FRT2_TCSR = (uint8_t)(0x01); /* 1053; MOV:G.B #H'01, @FRT2_TCSR; FRT2_TCSR = H'01 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=1); refs FRT2_TCSR; cycles=9 */
|
||||||
FRT2_FRC_H = (uint16_t)(0x00); /* 1058; MOV:G.W #H'00, @FRT2_FRC_H; FRT2_FRC_H = H'00; refs FRT2_FRC_H; FRT2_FRC W write high TEMP access; cycles=11 */
|
FRT2_FRC_H = zero_extend8_to16(0x00); /* 1058; MOV:G.W #H'00, @FRT2_FRC_H; FRT2_FRC_H = H'00; byte immediate zero-extended into word destination; refs FRT2_FRC_H; FRT2_FRC W write high TEMP access; cycles=11 */
|
||||||
FRT2_OCRA_H = (uint16_t)(0x7A12); /* 105D; MOV:G.W #H'7A12, @FRT2_OCRA_H; FRT2_OCRA_H = H'7A12; refs FRT2_OCRA_H; FRT2_OCRA W write high TEMP access; cycles=9 */
|
FRT2_OCRA_H = (uint16_t)(0x7A12); /* 105D; MOV:G.W #H'7A12, @FRT2_OCRA_H; FRT2_OCRA_H = H'7A12; refs FRT2_OCRA_H; FRT2_OCRA W write high TEMP access; cycles=9 */
|
||||||
FRT3_TCR = (uint8_t)(0x00); /* 1063; MOV:G.B #H'00, @FRT3_TCR; FRT3_TCR = H'00 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=0 CKS0=0); refs FRT3_TCR; cycles=9 */
|
FRT3_TCR = (uint8_t)(0x00); /* 1063; MOV:G.B #H'00, @FRT3_TCR; FRT3_TCR = H'00 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=0 CKS0=0); refs FRT3_TCR; cycles=9 */
|
||||||
FRT3_TCSR = (uint8_t)(0x00); /* 1068; MOV:G.B #H'00, @FRT3_TCSR; FRT3_TCSR = H'00 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=0); refs FRT3_TCSR; cycles=9 */
|
FRT3_TCSR = (uint8_t)(0x00); /* 1068; MOV:G.B #H'00, @FRT3_TCSR; FRT3_TCSR = H'00 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=0); refs FRT3_TCSR; cycles=9 */
|
||||||
@@ -3701,7 +3703,7 @@ loc_4059:
|
|||||||
set_flags_cmp8(R2, MEM8[0xF9B5]); /* 405F; CMP:G.B @H'F9B5, R2; refs ram_F9B5; cycles=6 */
|
set_flags_cmp8(R2, MEM8[0xF9B5]); /* 405F; CMP:G.B @H'F9B5, R2; refs ram_F9B5; cycles=6 */
|
||||||
if (Z) { /* 4063; BNE loc_4074; cycles=3/8 nt/t */
|
if (Z) { /* 4063; BNE loc_4074; cycles=3/8 nt/t */
|
||||||
R2 <<= 1; /* 4065; SHLL.B R2; cycles=2 */
|
R2 <<= 1; /* 4065; SHLL.B R2; cycles=2 */
|
||||||
MEM16[R2 - 0x0790] = (uint16_t)(0x00); /* 4067; MOV:G.W #H'00, @(-H'0790,R2); cycles=11 */
|
MEM16[R2 - 0x0790] = zero_extend8_to16(0x00); /* 4067; MOV:G.W #H'00, @(-H'0790,R2); byte immediate zero-extended into word destination; cycles=11 */
|
||||||
MEM8[0xF9B0] += (uint8_t)(1); /* 406C; ADD:Q.B #1, @H'F9B0; refs ram_F9B0; cycles=9 */
|
MEM8[0xF9B0] += (uint8_t)(1); /* 406C; ADD:Q.B #1, @H'F9B0; refs ram_F9B0; cycles=9 */
|
||||||
MEM8[0xF9B0] &= ~BIT(7); /* 4070; BCLR.B #7, @H'F9B0; refs ram_F9B0; cycles=9 */
|
MEM8[0xF9B0] &= ~BIT(7); /* 4070; BCLR.B #7, @H'F9B0; refs ram_F9B0; cycles=9 */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,7 +167,7 @@
|
|||||||
"H'BF23",
|
"H'BF23",
|
||||||
"H'BF2D"
|
"H'BF2D"
|
||||||
],
|
],
|
||||||
"summary": "F9C4 gates the idle/default report enqueue. Reset/init loads H'14, each BA26 send reloads H'07, and the FRT2 OCIA handler decrements it; when it reaches zero loc_4046 can enqueue H'00FF if the queue is empty and the FAA5/F9C3 RX gate permits it. With FRT2 OCRA H'7A12 and CKS=phi/32, a phi near 10 MHz gives about 0.7s for H'07, matching the observed heartbeat cadence.",
|
"summary": "F9C4 gates the idle/default report enqueue. Reset/init loads H'14, each BA26 send reloads H'07, and the FRT2 OCIA handler decrements it; when it reaches zero loc_4046 can enqueue H'0000 if the queue is empty and the FAA5/F9C3 RX gate permits it. With FRT2 OCRA H'7A12 and CKS=phi/32, a phi near 10 MHz gives about 0.7s for H'07, matching the observed heartbeat cadence.",
|
||||||
"tick_handler_address_hex": "H'BF23",
|
"tick_handler_address_hex": "H'BF23",
|
||||||
"timer": {
|
"timer": {
|
||||||
"clock_select": "CKS1=1 CKS0=0 => phi/32",
|
"clock_select": "CKS1=1 CKS0=0 => phi/32",
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ Evidence:
|
|||||||
- H'BDFB: CLR.B @H'FAA3
|
- H'BDFB: CLR.B @H'FAA3
|
||||||
- H'BDFF: CLR.B @H'FAA2
|
- H'BDFF: CLR.B @H'FAA2
|
||||||
- loc_4046 idle heartbeat/report gate: present
|
- loc_4046 idle heartbeat/report gate: present
|
||||||
F9C4 gates the idle/default report enqueue. Reset/init loads H'14, each BA26 send reloads H'07, and the FRT2 OCIA handler decrements it; when it reaches zero loc_4046 can enqueue H'00FF if the queue is empty and the FAA5/F9C3 RX gate permits it. With FRT2 OCRA H'7A12 and CKS=phi/32, a phi near 10 MHz gives about 0.7s for H'07, matching the observed heartbeat cadence.
|
F9C4 gates the idle/default report enqueue. Reset/init loads H'14, each BA26 send reloads H'07, and the FRT2 OCIA handler decrements it; when it reaches zero loc_4046 can enqueue H'0000 if the queue is empty and the FAA5/F9C3 RX gate permits it. With FRT2 OCRA H'7A12 and CKS=phi/32, a phi near 10 MHz gives about 0.7s for H'07, matching the observed heartbeat cadence.
|
||||||
- H'4046: TST.B @H'F9C4
|
- H'4046: TST.B @H'F9C4
|
||||||
- H'404A: BNE loc_4058
|
- H'404A: BNE loc_4058
|
||||||
- H'404C: BTST.B #7, @H'FAA5
|
- H'404C: BTST.B #7, @H'FAA5
|
||||||
|
|||||||
@@ -191,7 +191,10 @@ extern volatile u8 MEM8[0x10000];
|
|||||||
* - evidence: H'BBD8, H'BBDC, H'BBE0, H'BBE4, H'BBE8, H'BBEC, H'BBF0, H'BE4D, H'BE56, H'BE5E, H'BE66, H'BE52, H'BE5A, H'BE62, H'BE6A, H'BE29, H'BE2D, H'BE33, H'BE37, H'BE43, H'BE47, H'BE05, H'BE0D, H'BE15, H'BE09, H'BE11, H'BE19, H'BE22
|
* - evidence: H'BBD8, H'BBDC, H'BBE0, H'BBE4, H'BBE8, H'BBEC, H'BBF0, H'BE4D, H'BE56, H'BE5E, H'BE66, H'BE52, H'BE5A, H'BE62, H'BE6A, H'BE29, H'BE2D, H'BE33, H'BE37, H'BE43, H'BE47, H'BE05, H'BE0D, H'BE15, H'BE09, H'BE11, H'BE19, H'BE22
|
||||||
* gate/queue state machine candidate:
|
* gate/queue state machine candidate:
|
||||||
* - main_loop_may_enter_report_builder: FAA2 == 0 && F9C0 == 0 && ((FAA5.bit7 == 0) || (F9C3 == 0)); Main-loop report gate; session must be idle, TX busy timer clear, and RX gate open.
|
* - main_loop_may_enter_report_builder: FAA2 == 0 && F9C0 == 0 && ((FAA5.bit7 == 0) || (F9C3 == 0)); Main-loop report gate; session must be idle, TX busy timer clear, and RX gate open.
|
||||||
* - idle_heartbeat_report_may_enqueue: F9C4 == 0 && ((FAA5.bit7 == 0) || (F9C3 == 0)) && F9B0 == F9B5; Idle/default report gate; when the FRT2 countdown clears and the queue is empty, loc_4046 can enqueue H'00FF for the later loc_BAF2 -> loc_BA26 send path.
|
* - idle_heartbeat_report_may_enqueue: F9C4 == 0 && ((FAA5.bit7 == 0) || (F9C3 == 0)) && F9B0 == F9B5; Idle/default report gate; when the FRT2 countdown clears and the queue is empty, loc_4046 can enqueue H'0000 for the later loc_BAF2 -> loc_BA26 send path.
|
||||||
|
* enqueues report H'0000
|
||||||
|
* write semantics: loc_4067 is MOV:G.W #H'00, @(-H'0790,R2): the byte immediate is zero-extended by the word destination, so the queue slot becomes H'0000.
|
||||||
|
* runtime-confirmed frame 00 00 00 00 80 DA via loc_4046 -> loc_BAF2 -> loc_BB08 -> loc_BB1C -> loc_BB20 -> loc_BB2B -> loc_BA26
|
||||||
* - queue_has_pending_report: F9B5 != F9B0; Queue/pending cursor gate; non-empty state stages through BB43 before loc_BA26.
|
* - queue_has_pending_report: F9B5 != F9B0; Queue/pending cursor gate; non-empty state stages through BB43 before loc_BA26.
|
||||||
* - periodic_resend_may_fire: (FAA5 & FAA3 & 0x80) != 0 && F9C6 == 0 && F9C8 != 0 after countdown; Resend gate masks pending state with FAA5, checks F9C6/F9C8, then calls BA26 at BED5.
|
* - periodic_resend_may_fire: (FAA5 & FAA3 & 0x80) != 0 && F9C6 == 0 && F9C8 != 0 after countdown; Resend gate masks pending state with FAA5, checks F9C6/F9C8, then calls BA26 at BED5.
|
||||||
* - rx_completion_sets_session_timer: RX completion sets F9C5 (observed reload H'14) after the sixth byte is captured.
|
* - rx_completion_sets_session_timer: RX completion sets F9C5 (observed reload H'14) after the sixth byte is captured.
|
||||||
@@ -204,6 +207,8 @@ extern volatile u8 MEM8[0x10000];
|
|||||||
* TX/autonomous report model candidate:
|
* TX/autonomous report model candidate:
|
||||||
* - loc_BB43 -> loc_BA26: bytes 0..2 encode candidate logical index/report id; bytes 3..4 come from current_value_table_candidate; byte5 is 0x5A XOR checksum
|
* - loc_BB43 -> loc_BA26: bytes 0..2 encode candidate logical index/report id; bytes 3..4 come from current_value_table_candidate; byte5 is 0x5A XOR checksum
|
||||||
* - observed overlay candidates: heartbeat_or_idle_report_candidate: 00 00 00 00 80 DA; call_button_report_candidate: 00 00 15 80 00 CF, 00 00 15 00 00 4F; camera_power_report_candidate: 00 00 07 80 00 DD
|
* - observed overlay candidates: heartbeat_or_idle_report_candidate: 00 00 00 00 80 DA; call_button_report_candidate: 00 00 15 80 00 CF, 00 00 15 00 00 4F; camera_power_report_candidate: 00 00 07 80 00 DD
|
||||||
|
* - runtime confirmation: idle_heartbeat_report_runtime_confirmation: report H'0000 emits 00 00 00 00 80 DA; MOV:G.W #H'00 writes H'0000 to the queue slot
|
||||||
|
* - consistency idle_heartbeat_report_id_width: pass; Decompiler mnemonic MOV:G.W and emulator execution now agree that the H'00 immediate at loc_4067 is zero-extended to report H'0000.
|
||||||
* - caveat: Real captures supplied so far show only heartbeat/idle, call, and camera-power autonomous TX frames. Other panel controls may require a host/device request or state transition before the firmware reports them.
|
* - caveat: Real captures supplied so far show only heartbeat/idle, call, and camera-power autonomous TX frames. Other panel controls may require a host/device request or state transition before the firmware reports them.
|
||||||
* - evidence: H'BB1C, H'BB20, H'BB2B, H'BB39, H'BB3F, H'BB43
|
* - evidence: H'BB1C, H'BB20, H'BB2B, H'BB39, H'BB3F, H'BB43
|
||||||
* heartbeat/periodic resend candidate:
|
* heartbeat/periodic resend candidate:
|
||||||
@@ -273,6 +278,16 @@ static bool sci1_candidate_idle_heartbeat_enqueue_gate_open(void)
|
|||||||
return idle_timer_clear && rx_gate_open && queue_empty;
|
return idle_timer_clear && rx_gate_open && queue_empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sci1_candidate_enqueue_idle_heartbeat_report(void)
|
||||||
|
{
|
||||||
|
if (!sci1_candidate_idle_heartbeat_enqueue_gate_open()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* loc_4067 writes MOV:G.W #H'00, so the queue report id is 0x0000. */
|
||||||
|
candidate_enqueue_report(0x0000u);
|
||||||
|
}
|
||||||
|
|
||||||
static bool sci1_candidate_periodic_resend_gate_open(void)
|
static bool sci1_candidate_periodic_resend_gate_open(void)
|
||||||
{
|
{
|
||||||
bool pending = (MEM8[0xFAA5u] & MEM8[0xFAA3u] & 0x80u) != 0u;
|
bool pending = (MEM8[0xFAA5u] & MEM8[0xFAA3u] & 0x80u) != 0u;
|
||||||
|
|||||||
211
h8536/consistency.py
Normal file
211
h8536/consistency.py
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Mapping
|
||||||
|
|
||||||
|
|
||||||
|
JsonObject = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_decompiler_consistency(payload: Mapping[str, Any]) -> JsonObject:
|
||||||
|
"""Flag decompiler/pseudocode semantic cases that are easy to misread."""
|
||||||
|
width_checks = [
|
||||||
|
_byte_immediate_word_write_check(ins)
|
||||||
|
for ins in _instruction_sequence(payload.get("instructions"))
|
||||||
|
if is_byte_immediate_to_word_destination(ins)
|
||||||
|
]
|
||||||
|
width_checks = [check for check in width_checks if check]
|
||||||
|
return {
|
||||||
|
"kind": "decompiler_pseudocode_consistency",
|
||||||
|
"summary": _summary(width_checks),
|
||||||
|
"checks": width_checks,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def format_consistency_report(analysis: Mapping[str, Any]) -> str:
|
||||||
|
lines = [
|
||||||
|
"Decompiler/Pseudocode Consistency",
|
||||||
|
str(analysis.get("summary") or "No checks emitted."),
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
checks = analysis.get("checks")
|
||||||
|
if not isinstance(checks, list) or not checks:
|
||||||
|
return "\n".join(lines).rstrip() + "\n"
|
||||||
|
for check in checks:
|
||||||
|
if not isinstance(check, Mapping):
|
||||||
|
continue
|
||||||
|
lines.append(
|
||||||
|
f"- {check.get('address_hex', '?')}: {check.get('instruction', '')} "
|
||||||
|
f"[{check.get('status', 'info')}]",
|
||||||
|
)
|
||||||
|
summary = check.get("summary")
|
||||||
|
if summary:
|
||||||
|
lines.append(f" {summary}")
|
||||||
|
return "\n".join(lines).rstrip() + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def is_byte_immediate_to_word_destination(instruction: Mapping[str, Any]) -> bool:
|
||||||
|
mnemonic = str(instruction.get("mnemonic") or "")
|
||||||
|
if _mnemonic_base(mnemonic) not in {"MOV:G", "MOV"} or _mnemonic_size(mnemonic) != "W":
|
||||||
|
return False
|
||||||
|
operands = _split_operands(str(instruction.get("operands") or ""))
|
||||||
|
if len(operands) != 2:
|
||||||
|
return False
|
||||||
|
source = operands[0].strip()
|
||||||
|
if not source.startswith("#"):
|
||||||
|
return False
|
||||||
|
literal = _immediate_literal_text(source[1:])
|
||||||
|
return literal is not None and len(literal) <= 2
|
||||||
|
|
||||||
|
|
||||||
|
def _byte_immediate_word_write_check(instruction: Mapping[str, Any]) -> JsonObject:
|
||||||
|
address = int(instruction.get("address") or 0)
|
||||||
|
immediate = _immediate_value(_split_operands(str(instruction.get("operands") or ""))[0])
|
||||||
|
value_text = f"0x{immediate:04X}" if immediate is not None else "zero-extended byte"
|
||||||
|
return {
|
||||||
|
"kind": "byte_immediate_to_word_destination",
|
||||||
|
"status": "requires_zero_extend8_to16_pseudocode",
|
||||||
|
"address": address,
|
||||||
|
"address_hex": _h16(address),
|
||||||
|
"instruction": str(instruction.get("text") or _instruction_text(instruction)),
|
||||||
|
"expected_pseudocode_hint": "zero_extend8_to16",
|
||||||
|
"zero_extended_value_hex": value_text,
|
||||||
|
"summary": (
|
||||||
|
"Word-sized MOV with an 8-bit immediate writes a zero-extended word. "
|
||||||
|
"Pseudocode should not model this as a one-byte write or preserve the old low byte."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _summary(width_checks: list[JsonObject]) -> str:
|
||||||
|
if not width_checks:
|
||||||
|
return "No byte-immediate-to-word destination cases found."
|
||||||
|
return (
|
||||||
|
f"{len(width_checks)} byte-immediate-to-word destination case(s) require "
|
||||||
|
"explicit zero-extension in pseudocode."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _instruction_sequence(value: object) -> list[Mapping[str, Any]]:
|
||||||
|
if not isinstance(value, list):
|
||||||
|
return []
|
||||||
|
instructions = [item for item in value if isinstance(item, Mapping)]
|
||||||
|
return sorted(instructions, key=lambda item: int(item.get("address") or 0))
|
||||||
|
|
||||||
|
|
||||||
|
def _split_operands(operands: str) -> list[str]:
|
||||||
|
if not operands:
|
||||||
|
return []
|
||||||
|
parts: list[str] = []
|
||||||
|
start = 0
|
||||||
|
depth = 0
|
||||||
|
for idx, char in enumerate(operands):
|
||||||
|
if char in "({":
|
||||||
|
depth += 1
|
||||||
|
elif char in ")}" and depth:
|
||||||
|
depth -= 1
|
||||||
|
elif char == "," and depth == 0:
|
||||||
|
parts.append(operands[start:idx].strip())
|
||||||
|
start = idx + 1
|
||||||
|
parts.append(operands[start:].strip())
|
||||||
|
return [part for part in parts if part]
|
||||||
|
|
||||||
|
|
||||||
|
def _immediate_literal_text(text: str) -> str | None:
|
||||||
|
stripped = text.strip()
|
||||||
|
h_match = re.fullmatch(r"H'([0-9A-Fa-f]+)", stripped)
|
||||||
|
if h_match:
|
||||||
|
return h_match.group(1)
|
||||||
|
x_match = re.fullmatch(r"0x([0-9A-Fa-f]+)", stripped)
|
||||||
|
if x_match:
|
||||||
|
return x_match.group(1)
|
||||||
|
decimal_match = re.fullmatch(r"\d+", stripped)
|
||||||
|
if decimal_match:
|
||||||
|
value = int(stripped, 10)
|
||||||
|
if 0 <= value <= 0xFF:
|
||||||
|
return f"{value:02X}"
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _immediate_value(operand: str) -> int | None:
|
||||||
|
stripped = operand.strip()
|
||||||
|
if stripped.startswith("#"):
|
||||||
|
stripped = stripped[1:].strip()
|
||||||
|
literal = _immediate_literal_text(stripped)
|
||||||
|
if literal is None:
|
||||||
|
return None
|
||||||
|
return int(literal, 16)
|
||||||
|
|
||||||
|
|
||||||
|
def _instruction_text(instruction: Mapping[str, Any]) -> str:
|
||||||
|
mnemonic = str(instruction.get("mnemonic") or "")
|
||||||
|
operands = str(instruction.get("operands") or "")
|
||||||
|
return f"{mnemonic} {operands}".strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _mnemonic_base(mnemonic: str) -> str:
|
||||||
|
return mnemonic.rsplit(".", 1)[0] if "." in mnemonic else mnemonic
|
||||||
|
|
||||||
|
|
||||||
|
def _mnemonic_size(mnemonic: str) -> str:
|
||||||
|
suffix = mnemonic.rsplit(".", 1)[-1] if "." in mnemonic else ""
|
||||||
|
return suffix if suffix in {"B", "W"} else ""
|
||||||
|
|
||||||
|
|
||||||
|
def _h16(value: int) -> str:
|
||||||
|
return f"H'{value & 0xFFFF:04X}"
|
||||||
|
|
||||||
|
|
||||||
|
def load_consistency_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 write_consistency_report(input_path: Path, output_path: Path, *, json_output: bool = False) -> None:
|
||||||
|
analysis = analyze_decompiler_consistency(load_consistency_input(input_path))
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
if json_output:
|
||||||
|
output_path.write_text(json.dumps(analysis, indent=2), encoding="utf-8")
|
||||||
|
else:
|
||||||
|
output_path.write_text(format_consistency_report(analysis), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Report decompiler/pseudocode semantic consistency checks.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"input",
|
||||||
|
nargs="?",
|
||||||
|
type=Path,
|
||||||
|
default=Path("build/rom_decompiled.json"),
|
||||||
|
help="structured JSON emitted by h8536_decompiler.py",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--out",
|
||||||
|
type=Path,
|
||||||
|
default=Path("build/rom_consistency.txt"),
|
||||||
|
help="consistency report output path",
|
||||||
|
)
|
||||||
|
parser.add_argument("--json", action="store_true", help="write JSON instead of text")
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
write_consistency_report(args.input, args.out, json_output=args.json)
|
||||||
|
print(f"wrote {args.out}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"analyze_decompiler_consistency",
|
||||||
|
"format_consistency_report",
|
||||||
|
"is_byte_immediate_to_word_destination",
|
||||||
|
"load_consistency_input",
|
||||||
|
"main",
|
||||||
|
"write_consistency_report",
|
||||||
|
]
|
||||||
@@ -829,7 +829,7 @@ def _report_gate_decision(pc: int, *, f9c4: int, faa5: int, f9c3: int, head: int
|
|||||||
if pc == 0x4063:
|
if pc == 0x4063:
|
||||||
return "return_queue_not_empty" if not z else "enqueue_zero_report"
|
return "return_queue_not_empty" if not z else "enqueue_zero_report"
|
||||||
if pc == 0x4067:
|
if pc == 0x4067:
|
||||||
return "write_report_00ff_to_queue_slot"
|
return "write_report_0000_to_queue_slot"
|
||||||
if pc == 0x406C:
|
if pc == 0x406C:
|
||||||
return "advance_report_queue_head"
|
return "advance_report_queue_head"
|
||||||
if pc == 0x4070:
|
if pc == 0x4070:
|
||||||
@@ -1197,7 +1197,7 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
|||||||
action="append",
|
action="append",
|
||||||
type=parse_watch_pc,
|
type=parse_watch_pc,
|
||||||
default=[],
|
default=[],
|
||||||
help="highlight queue traces involving a logical report id, e.g. 00FF or 0x00FF",
|
help="highlight queue traces involving a logical report id, e.g. 0015 or 0x0015",
|
||||||
)
|
)
|
||||||
parser.add_argument("--report-queue-watch-hit-limit", type=int, default=32)
|
parser.add_argument("--report-queue-watch-hit-limit", type=int, default=32)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
|||||||
@@ -207,8 +207,9 @@ class H8536Emulator:
|
|||||||
|
|
||||||
if op in (0x06, 0x07):
|
if op in (0x06, 0x07):
|
||||||
value = raw[-1] if op == 0x06 else int.from_bytes(raw[-2:], "big")
|
value = raw[-1] if op == 0x06 else int.from_bytes(raw[-2:], "big")
|
||||||
self._write_ea(ea, value, 1 if op == 0x06 else 2)
|
write_size = size if op == 0x06 else 2
|
||||||
self._set_logic_flags(value, 1 if op == 0x06 else 2)
|
self._write_ea(ea, value, write_size)
|
||||||
|
self._set_logic_flags(value, write_size)
|
||||||
return next_pc
|
return next_pc
|
||||||
|
|
||||||
base = op & 0xF8
|
base = op & 0xF8
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ from dataclasses import dataclass
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from .consistency import is_byte_immediate_to_word_destination
|
||||||
|
|
||||||
|
|
||||||
JsonObject = dict[str, Any]
|
JsonObject = dict[str, Any]
|
||||||
|
|
||||||
@@ -195,6 +197,8 @@ def _file_header(source_name: str, payload: JsonObject) -> list[str]:
|
|||||||
"u8 CCR, BR, EP, DP, TP;",
|
"u8 CCR, BR, EP, DP, TP;",
|
||||||
"int C, Z, N, V;",
|
"int C, Z, N, V;",
|
||||||
"",
|
"",
|
||||||
|
"static inline u16 zero_extend8_to16(u8 value) { return (u16)value; }",
|
||||||
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -648,6 +652,8 @@ def _translate_instruction(ins: JsonObject, labels: dict[int, str]) -> str:
|
|||||||
if base in {"MOV", "MOV:G", "MOV:I", "MOV:E", "MOV:L", "MOV:S", "MOV:F"} and len(ops) == 2:
|
if base in {"MOV", "MOV:G", "MOV:I", "MOV:E", "MOV:L", "MOV:S", "MOV:F"} and len(ops) == 2:
|
||||||
source = _format_operand(ops[0], size)
|
source = _format_operand(ops[0], size)
|
||||||
dest = _format_operand(ops[1], size, lvalue=True)
|
dest = _format_operand(ops[1], size, lvalue=True)
|
||||||
|
if is_byte_immediate_to_word_destination(ins):
|
||||||
|
return f"{dest} = zero_extend8_to16({source});"
|
||||||
return f"{dest} = {_cast(source, size)};"
|
return f"{dest} = {_cast(source, size)};"
|
||||||
|
|
||||||
if base in {"MOVFPE"} and len(ops) == 2:
|
if base in {"MOVFPE"} and len(ops) == 2:
|
||||||
@@ -908,6 +914,9 @@ def _metadata_comments(ins: JsonObject) -> list[str]:
|
|||||||
if isinstance(item, dict) and item.get("comment"):
|
if isinstance(item, dict) and item.get("comment"):
|
||||||
comments.append(str(item["comment"]))
|
comments.append(str(item["comment"]))
|
||||||
|
|
||||||
|
if is_byte_immediate_to_word_destination(ins):
|
||||||
|
comments.append("byte immediate zero-extended into word destination")
|
||||||
|
|
||||||
board_profile = ins.get("board_profile")
|
board_profile = ins.get("board_profile")
|
||||||
if isinstance(board_profile, dict) and board_profile.get("comment"):
|
if isinstance(board_profile, dict) and board_profile.get("comment"):
|
||||||
comments.append(str(board_profile["comment"]))
|
comments.append(str(board_profile["comment"]))
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import json
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .board_profile import board_comment_for_instruction, board_json_payload, board_metadata_for_instruction
|
from .board_profile import board_comment_for_instruction, board_json_payload, board_metadata_for_instruction
|
||||||
|
from .consistency import analyze_decompiler_consistency
|
||||||
from .cycles import cycle_comment
|
from .cycles import cycle_comment
|
||||||
from .dataflow import state_for_instruction
|
from .dataflow import state_for_instruction
|
||||||
from .dtc import DtcEndpointInfo, DtcRegisterInfo
|
from .dtc import DtcEndpointInfo, DtcRegisterInfo
|
||||||
@@ -491,6 +492,7 @@ def write_json(
|
|||||||
for ins in (instructions[addr] for addr in sorted(instructions))
|
for ins in (instructions[addr] for addr in sorted(instructions))
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
payload["decompiler_consistency"] = analyze_decompiler_consistency(payload)
|
||||||
payload["serial_semantics"] = analyze_serial_semantics(payload)
|
payload["serial_semantics"] = analyze_serial_semantics(payload)
|
||||||
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
||||||
|
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ def _idle_heartbeat_gate(payload: dict[str, Any], by_address: dict[int, JsonObje
|
|||||||
"summary": (
|
"summary": (
|
||||||
"F9C4 gates the idle/default report enqueue. Reset/init loads H'14, each BA26 send "
|
"F9C4 gates the idle/default report enqueue. Reset/init loads H'14, each BA26 send "
|
||||||
"reloads H'07, and the FRT2 OCIA handler decrements it; when it reaches zero loc_4046 "
|
"reloads H'07, and the FRT2 OCIA handler decrements it; when it reaches zero loc_4046 "
|
||||||
"can enqueue H'00FF if the queue is empty and the FAA5/F9C3 RX gate permits it. With "
|
"can enqueue H'0000 if the queue is empty and the FAA5/F9C3 RX gate permits it. With "
|
||||||
"FRT2 OCRA H'7A12 and CKS=phi/32, a phi near 10 MHz gives about 0.7s for H'07, matching "
|
"FRT2 OCRA H'7A12 and CKS=phi/32, a phi near 10 MHz gives about 0.7s for H'07, matching "
|
||||||
"the observed heartbeat cadence."
|
"the observed heartbeat cadence."
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -635,6 +635,20 @@ def _gate_queue_comment_lines(
|
|||||||
condition = _comment_text(str(predicate.get("condition_candidate") or "condition unknown"))
|
condition = _comment_text(str(predicate.get("condition_candidate") or "condition unknown"))
|
||||||
summary = _comment_text(str(predicate.get("summary") or "candidate gate"))
|
summary = _comment_text(str(predicate.get("summary") or "candidate gate"))
|
||||||
lines.append(f"{prefix}- {name}: {condition}; {summary}")
|
lines.append(f"{prefix}- {name}: {condition}; {summary}")
|
||||||
|
enqueued = predicate.get("enqueued_report_candidate_hex")
|
||||||
|
if enqueued:
|
||||||
|
lines.append(f"{prefix} enqueues report {enqueued}")
|
||||||
|
write_semantics = str(predicate.get("write_semantics_candidate") or "").strip()
|
||||||
|
if write_semantics:
|
||||||
|
lines.append(f"{prefix} write semantics: {_comment_text(write_semantics)}")
|
||||||
|
runtime = predicate.get("runtime_trace_confirmation")
|
||||||
|
if isinstance(runtime, dict):
|
||||||
|
frame = runtime.get("emitted_frame_hex")
|
||||||
|
path = " -> ".join(str(item) for item in runtime.get("dequeue_path", []) if item)
|
||||||
|
detail = f"runtime-confirmed frame {frame}" if frame else "runtime-confirmed path"
|
||||||
|
if path:
|
||||||
|
detail += f" via {path}"
|
||||||
|
lines.append(f"{prefix} {detail}")
|
||||||
for effect in _object_list(value.get("session_effects")):
|
for effect in _object_list(value.get("session_effects")):
|
||||||
name = effect.get("name") or "session_effect_candidate"
|
name = effect.get("name") or "session_effect_candidate"
|
||||||
summary = _comment_text(str(effect.get("summary") or "candidate session effect"))
|
summary = _comment_text(str(effect.get("summary") or "candidate session effect"))
|
||||||
@@ -677,6 +691,16 @@ def _gate_queue_predicate_function_lines(value: object) -> list[str]:
|
|||||||
" return idle_timer_clear && rx_gate_open && queue_empty;",
|
" return idle_timer_clear && rx_gate_open && queue_empty;",
|
||||||
"}",
|
"}",
|
||||||
"",
|
"",
|
||||||
|
"static void sci1_candidate_enqueue_idle_heartbeat_report(void)",
|
||||||
|
"{",
|
||||||
|
" if (!sci1_candidate_idle_heartbeat_enqueue_gate_open()) {",
|
||||||
|
" return;",
|
||||||
|
" }",
|
||||||
|
"",
|
||||||
|
" /* loc_4067 writes MOV:G.W #H'00, so the queue report id is 0x0000. */",
|
||||||
|
" candidate_enqueue_report(0x0000u);",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
"static bool sci1_candidate_periodic_resend_gate_open(void)",
|
"static bool sci1_candidate_periodic_resend_gate_open(void)",
|
||||||
"{",
|
"{",
|
||||||
" bool pending = (MEM8[0xFAA5u] & MEM8[0xFAA3u] & 0x80u) != 0u;",
|
" bool pending = (MEM8[0xFAA5u] & MEM8[0xFAA3u] & 0x80u) != 0u;",
|
||||||
@@ -711,6 +735,21 @@ def _tx_report_comment_lines(
|
|||||||
observed.append(f"{name}: {frames}")
|
observed.append(f"{name}: {frames}")
|
||||||
if observed:
|
if observed:
|
||||||
lines.append(f"{prefix}- observed overlay candidates: {_comment_text('; '.join(observed))}")
|
lines.append(f"{prefix}- observed overlay candidates: {_comment_text('; '.join(observed))}")
|
||||||
|
for runtime in _object_list(value.get("runtime_confirmed_paths")):
|
||||||
|
name = runtime.get("name") or "runtime_confirmation"
|
||||||
|
frame = runtime.get("emitted_frame_hex") or "frame?"
|
||||||
|
report = runtime.get("report_id_hex") or "report?"
|
||||||
|
summary = f"{name}: report {report} emits {frame}"
|
||||||
|
semantics = runtime.get("queue_write_semantics")
|
||||||
|
if semantics:
|
||||||
|
summary += f"; {semantics}"
|
||||||
|
lines.append(f"{prefix}- runtime confirmation: {_comment_text(summary)}")
|
||||||
|
checks = _object_list(value.get("consistency_checks"))
|
||||||
|
for check in checks:
|
||||||
|
name = check.get("name") or "consistency_check"
|
||||||
|
status = check.get("status") or "info"
|
||||||
|
summary = _comment_text(str(check.get("summary") or ""))
|
||||||
|
lines.append(f"{prefix}- consistency {name}: {status}; {summary}")
|
||||||
caveat = str(value.get("observed_autonomous_output_caveat") or value.get("caveat") or "").strip()
|
caveat = str(value.get("observed_autonomous_output_caveat") or value.get("caveat") or "").strip()
|
||||||
if caveat:
|
if caveat:
|
||||||
lines.append(f"{prefix}- caveat: {_comment_text(caveat)}")
|
lines.append(f"{prefix}- caveat: {_comment_text(caveat)}")
|
||||||
|
|||||||
@@ -1683,10 +1683,24 @@ def _gate_queue_model(ordered: list[JsonObject], commands: list[JsonObject]) ->
|
|||||||
),
|
),
|
||||||
"summary": (
|
"summary": (
|
||||||
"Idle/default report gate; when the FRT2 countdown clears and the queue is "
|
"Idle/default report gate; when the FRT2 countdown clears and the queue is "
|
||||||
"empty, loc_4046 can enqueue H'00FF for the later loc_BAF2 -> loc_BA26 send path."
|
"empty, loc_4046 can enqueue H'0000 for the later loc_BAF2 -> loc_BA26 send path."
|
||||||
),
|
),
|
||||||
"state_addresses_hex": [_h16(0xF9C4), _h16(0xFAA5), _h16(0xF9C3), _h16(0xF9B0), _h16(0xF9B5)],
|
"state_addresses_hex": [_h16(0xF9C4), _h16(0xFAA5), _h16(0xF9C3), _h16(0xF9B0), _h16(0xF9B5)],
|
||||||
"enqueued_report_candidate_hex": _h16(0x00FF),
|
"enqueued_report_candidate_hex": _h16(0x0000),
|
||||||
|
"write_semantics_candidate": (
|
||||||
|
"loc_4067 is MOV:G.W #H'00, @(-H'0790,R2): the byte immediate is "
|
||||||
|
"zero-extended by the word destination, so the queue slot becomes H'0000."
|
||||||
|
),
|
||||||
|
"runtime_trace_confirmation": {
|
||||||
|
"source": "h8536_emulator_probe target-frame run",
|
||||||
|
"report_id_hex": _h16(0x0000),
|
||||||
|
"queue_write_address_hex": _h16(IDLE_REPORT_QUEUE_WRITE),
|
||||||
|
"queue_write_semantics": "H'FFFF -> H'0000, not H'00FF",
|
||||||
|
"dequeue_path": ["loc_4046", "loc_BAF2", "loc_BB08", "loc_BB1C", "loc_BB20", "loc_BB2B", "loc_BA26"],
|
||||||
|
"emitted_frame_hex": "00 00 00 00 80 DA",
|
||||||
|
"checksum_seed_hex": _h16(CHECKSUM_SEED, width=2),
|
||||||
|
"checksum_hex": "H'DA",
|
||||||
|
},
|
||||||
"evidence_addresses": _addresses_in_ranges(
|
"evidence_addresses": _addresses_in_ranges(
|
||||||
ordered,
|
ordered,
|
||||||
[(IDLE_REPORT_GATE_ENTRY, IDLE_REPORT_GATE_END)],
|
[(IDLE_REPORT_GATE_ENTRY, IDLE_REPORT_GATE_END)],
|
||||||
@@ -1965,6 +1979,27 @@ def _tx_report_model(ordered: list[JsonObject], responses: list[JsonObject]) ->
|
|||||||
"value_source_candidate": "current_value_table_candidate",
|
"value_source_candidate": "current_value_table_candidate",
|
||||||
"checksum_formula": "checksum = 0x5A ^ byte0 ^ byte1 ^ byte2 ^ byte3 ^ byte4",
|
"checksum_formula": "checksum = 0x5A ^ byte0 ^ byte1 ^ byte2 ^ byte3 ^ byte4",
|
||||||
"observed_capture_overlay_candidates": OBSERVED_TX_REPORT_OVERLAY,
|
"observed_capture_overlay_candidates": OBSERVED_TX_REPORT_OVERLAY,
|
||||||
|
"runtime_confirmed_paths": [
|
||||||
|
{
|
||||||
|
"name": "idle_heartbeat_report_runtime_confirmation",
|
||||||
|
"report_id_hex": _h16(0x0000),
|
||||||
|
"queue_write_address_hex": _h16(IDLE_REPORT_QUEUE_WRITE),
|
||||||
|
"queue_write_semantics": "MOV:G.W #H'00 writes H'0000 to the queue slot",
|
||||||
|
"staging_path": ["loc_4046", "loc_BAF2", "loc_BB08", "loc_BB1C", "loc_BB20", "loc_BB2B", "loc_BA26"],
|
||||||
|
"emitted_frame_hex": "00 00 00 00 80 DA",
|
||||||
|
"checksum_hex": "H'DA",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consistency_checks": [
|
||||||
|
{
|
||||||
|
"name": "idle_heartbeat_report_id_width",
|
||||||
|
"status": "pass",
|
||||||
|
"summary": (
|
||||||
|
"Decompiler mnemonic MOV:G.W and emulator execution now agree that the "
|
||||||
|
"H'00 immediate at loc_4067 is zero-extended to report H'0000."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
],
|
||||||
"observed_autonomous_output_caveat": (
|
"observed_autonomous_output_caveat": (
|
||||||
"Real captures supplied so far show only heartbeat/idle, call, and camera-power "
|
"Real captures supplied so far show only heartbeat/idle, call, and camera-power "
|
||||||
"autonomous TX frames. Other panel controls may require a host/device request or "
|
"autonomous TX frames. Other panel controls may require a host/device request or "
|
||||||
|
|||||||
5
h8536_consistency.py
Normal file
5
h8536_consistency.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from h8536.consistency import main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
36
tests/test_consistency.py
Normal file
36
tests/test_consistency.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from h8536.consistency import analyze_decompiler_consistency, format_consistency_report
|
||||||
|
|
||||||
|
|
||||||
|
class ConsistencyTest(unittest.TestCase):
|
||||||
|
def test_flags_byte_immediate_word_destination_cases(self):
|
||||||
|
payload = {
|
||||||
|
"instructions": [
|
||||||
|
{
|
||||||
|
"address": 0x4067,
|
||||||
|
"text": "MOV:G.W #H'00, @(-H'0790,R2)",
|
||||||
|
"mnemonic": "MOV:G.W",
|
||||||
|
"operands": "#H'00, @(-H'0790,R2)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": 0x5000,
|
||||||
|
"text": "MOV:I.W #H'1234, R0",
|
||||||
|
"mnemonic": "MOV:I.W",
|
||||||
|
"operands": "#H'1234, R0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
analysis = analyze_decompiler_consistency(payload)
|
||||||
|
|
||||||
|
self.assertEqual(len(analysis["checks"]), 1)
|
||||||
|
check = analysis["checks"][0]
|
||||||
|
self.assertEqual(check["address"], 0x4067)
|
||||||
|
self.assertEqual(check["zero_extended_value_hex"], "0x0000")
|
||||||
|
self.assertIn("zero-extended word", check["summary"])
|
||||||
|
self.assertIn("H'4067", format_consistency_report(analysis))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -47,6 +47,17 @@ class EmulatorAddressingTest(unittest.TestCase):
|
|||||||
self.assertEqual(emulator.cpu.regs[0], 0x00C7)
|
self.assertEqual(emulator.cpu.regs[0], 0x00C7)
|
||||||
self.assertEqual(emulator.cpu.pc, 0x1006)
|
self.assertEqual(emulator.cpu.pc, 0x1006)
|
||||||
|
|
||||||
|
def test_byte_immediate_to_word_destination_writes_zero_extended_word(self):
|
||||||
|
rom = rom_with_reset()
|
||||||
|
rom[0x1000:0x1005] = b"\x1D\xF8\x70\x06\xAB" # MOV:G.W #H'AB, @H'F870
|
||||||
|
|
||||||
|
emulator = H8536Emulator(bytes(rom))
|
||||||
|
emulator.memory.write16(0xF870, 0xFFFF)
|
||||||
|
emulator.step()
|
||||||
|
|
||||||
|
self.assertEqual(emulator.memory.read16(0xF870), 0x00AB)
|
||||||
|
self.assertEqual(emulator.cpu.pc, 0x1005)
|
||||||
|
|
||||||
def test_signed_byte_displacement_addresses_below_base(self):
|
def test_signed_byte_displacement_addresses_below_base(self):
|
||||||
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
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ class EmulatorProbeTest(unittest.TestCase):
|
|||||||
self.assertEqual(decisions[0x4046], "f9c4_zero_continue")
|
self.assertEqual(decisions[0x4046], "f9c4_zero_continue")
|
||||||
self.assertEqual(decisions[0x4050], "enqueue_candidate_faa5_clear")
|
self.assertEqual(decisions[0x4050], "enqueue_candidate_faa5_clear")
|
||||||
self.assertEqual(decisions[0x4063], "enqueue_zero_report")
|
self.assertEqual(decisions[0x4063], "enqueue_zero_report")
|
||||||
self.assertEqual(decisions[0x4067], "write_report_00ff_to_queue_slot")
|
self.assertEqual(decisions[0x4067], "write_report_0000_to_queue_slot")
|
||||||
self.assertTrue(any("recent_report_gates:" == line for line in report.lines()))
|
self.assertTrue(any("recent_report_gates:" == line for line in report.lines()))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,41 @@ class PseudocodeTest(unittest.TestCase):
|
|||||||
self.assertIn("loc_0110:", text)
|
self.assertIn("loc_0110:", text)
|
||||||
self.assertIn("return;", text)
|
self.assertIn("return;", text)
|
||||||
|
|
||||||
|
def test_zero_extends_byte_immediate_to_word_destination(self):
|
||||||
|
payload = {
|
||||||
|
"vectors": [{"address": 0, "name": "reset", "target": 0x4067, "target_label": "loc_4067"}],
|
||||||
|
"call_graph": {
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"start": 0x4067,
|
||||||
|
"end": 0x4067,
|
||||||
|
"label": "loc_4067",
|
||||||
|
"sources": ["reset"],
|
||||||
|
"instruction_count": 1,
|
||||||
|
"calls": [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"edges": [],
|
||||||
|
},
|
||||||
|
"instructions": [
|
||||||
|
{
|
||||||
|
"address": 0x4067,
|
||||||
|
"text": "MOV:G.W #H'00, @(-H'0790,R2)",
|
||||||
|
"mnemonic": "MOV:G.W",
|
||||||
|
"operands": "#H'00, @(-H'0790,R2)",
|
||||||
|
"kind": "normal",
|
||||||
|
"targets": [],
|
||||||
|
"references": [],
|
||||||
|
"comment": "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
text = generate_pseudocode(payload, options=PseudocodeOptions(structured=False))
|
||||||
|
|
||||||
|
self.assertIn("zero_extend8_to16(0x00)", text)
|
||||||
|
self.assertIn("byte immediate zero-extended into word destination", text)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -334,6 +334,21 @@ class SerialPseudocodeTest(unittest.TestCase):
|
|||||||
"observed_frames_hex": ["00 00 07 80 00 DD"],
|
"observed_frames_hex": ["00 00 07 80 00 DD"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"runtime_confirmed_paths": [
|
||||||
|
{
|
||||||
|
"name": "idle_heartbeat_report_runtime_confirmation",
|
||||||
|
"report_id_hex": "H'0000",
|
||||||
|
"queue_write_semantics": "MOV:G.W #H'00 writes H'0000 to the queue slot",
|
||||||
|
"emitted_frame_hex": "00 00 00 00 80 DA",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consistency_checks": [
|
||||||
|
{
|
||||||
|
"name": "idle_heartbeat_report_id_width",
|
||||||
|
"status": "pass",
|
||||||
|
"summary": "MOV:G.W zero-extends the H'00 immediate.",
|
||||||
|
}
|
||||||
|
],
|
||||||
"observed_autonomous_output_caveat": "Observed autonomous output is limited to heartbeat/call/cam-power; other controls may require host/device requests first.",
|
"observed_autonomous_output_caveat": "Observed autonomous output is limited to heartbeat/call/cam-power; other controls may require host/device requests first.",
|
||||||
"evidence_addresses_hex": ["H'BB20", "H'BB43"],
|
"evidence_addresses_hex": ["H'BB20", "H'BB43"],
|
||||||
},
|
},
|
||||||
@@ -404,10 +419,13 @@ class SerialPseudocodeTest(unittest.TestCase):
|
|||||||
self.assertIn("static bool sci1_candidate_main_report_gate_open(void)", text)
|
self.assertIn("static bool sci1_candidate_main_report_gate_open(void)", text)
|
||||||
self.assertIn("static bool sci1_candidate_report_queue_nonempty(void)", text)
|
self.assertIn("static bool sci1_candidate_report_queue_nonempty(void)", text)
|
||||||
self.assertIn("static bool sci1_candidate_idle_heartbeat_enqueue_gate_open(void)", text)
|
self.assertIn("static bool sci1_candidate_idle_heartbeat_enqueue_gate_open(void)", text)
|
||||||
|
self.assertIn("candidate_enqueue_report(0x0000u);", text)
|
||||||
self.assertIn("static bool sci1_candidate_periodic_resend_gate_open(void)", text)
|
self.assertIn("static bool sci1_candidate_periodic_resend_gate_open(void)", text)
|
||||||
self.assertIn("TX/autonomous report model candidate:", text)
|
self.assertIn("TX/autonomous report model candidate:", text)
|
||||||
self.assertIn("loc_BB43 -> loc_BA26: bytes 0..2 encode candidate logical index/report id; bytes 3..4 come from current_value_table_candidate", text)
|
self.assertIn("loc_BB43 -> loc_BA26: bytes 0..2 encode candidate logical index/report id; bytes 3..4 come from current_value_table_candidate", text)
|
||||||
self.assertIn("heartbeat_or_idle_report_candidate: 00 00 00 00 80 DA", text)
|
self.assertIn("heartbeat_or_idle_report_candidate: 00 00 00 00 80 DA", text)
|
||||||
|
self.assertIn("runtime confirmation: idle_heartbeat_report_runtime_confirmation: report H'0000 emits 00 00 00 00 80 DA", text)
|
||||||
|
self.assertIn("consistency idle_heartbeat_report_id_width: pass", text)
|
||||||
self.assertIn("heartbeat/periodic resend candidate:", text)
|
self.assertIn("heartbeat/periodic resend candidate:", text)
|
||||||
self.assertIn("F9C6 reload H'01F4", text)
|
self.assertIn("F9C6 reload H'01F4", text)
|
||||||
self.assertIn("BED5 resend path", text)
|
self.assertIn("BED5 resend path", text)
|
||||||
|
|||||||
@@ -354,6 +354,8 @@ class SerialSemanticsTest(unittest.TestCase):
|
|||||||
self.assertIn("bytes 0..2", report_text)
|
self.assertIn("bytes 0..2", report_text)
|
||||||
self.assertIn("current_value_table", report_text)
|
self.assertIn("current_value_table", report_text)
|
||||||
self.assertIn("00 00 15 80 00 cf", report_text)
|
self.assertIn("00 00 15 80 00 cf", report_text)
|
||||||
|
self.assertIn("idle_heartbeat_report_runtime_confirmation", report_text)
|
||||||
|
self.assertIn("idle_heartbeat_report_id_width", report_text)
|
||||||
self.assertIn("host/device request", report_text)
|
self.assertIn("host/device request", report_text)
|
||||||
|
|
||||||
def test_periodic_resend_model_marks_heartbeat_constants(self):
|
def test_periodic_resend_model_marks_heartbeat_constants(self):
|
||||||
@@ -381,7 +383,9 @@ class SerialSemanticsTest(unittest.TestCase):
|
|||||||
self.assertIn("faa2 == 0", gate_text)
|
self.assertIn("faa2 == 0", gate_text)
|
||||||
self.assertIn("f9c0 == 0", gate_text)
|
self.assertIn("f9c0 == 0", gate_text)
|
||||||
self.assertIn("f9c4 == 0", gate_text)
|
self.assertIn("f9c4 == 0", gate_text)
|
||||||
self.assertIn("h'00ff", gate_text)
|
self.assertIn("h'0000", gate_text)
|
||||||
|
self.assertIn("zero-extended", gate_text)
|
||||||
|
self.assertIn("00 00 00 00 80 da", gate_text)
|
||||||
self.assertIn("f9b5 != f9b0", gate_text)
|
self.assertIn("f9b5 != f9b0", gate_text)
|
||||||
self.assertIn("bb43", gate_text)
|
self.assertIn("bb43", gate_text)
|
||||||
self.assertIn("be9e", gate_text)
|
self.assertIn("be9e", gate_text)
|
||||||
|
|||||||
Reference in New Issue
Block a user