From 6ceed81765ab55831f55fbe44461c3e179a9a9d2 Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Mon, 25 May 2026 16:05:45 +1000 Subject: [PATCH] serial improvements --- README.md | 5 +- build/rom_decompiled.json | 4864 ++++++++++++++++++++++++++++++- build/rom_serial_pseudocode.c | 105 + h8536/render.py | 2 + h8536/serial_pseudocode.py | 154 + h8536/serial_semantics.py | 1262 ++++++++ tests/test_serial_pseudocode.py | 28 + tests/test_serial_semantics.py | 160 + 8 files changed, 6578 insertions(+), 2 deletions(-) create mode 100644 h8536/serial_semantics.py create mode 100644 tests/test_serial_semantics.py diff --git a/README.md b/README.md index d076a8a..293bad7 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,8 @@ To generate a focused RX/TX serial-path pseudocode view from the reconstruction - Tracks SCI setup writes and can infer baud rates from SMR/BRR when `--clock-hz` is supplied. - Annotates SCI protocol actions such as TDRE waits, TDR writes, RDR reads, RX/TX interrupt enables, and receive-error clears. - Reconstructs evidence-supported SCI1 serial frame candidates, including the apparent six-byte TX/RX units and XOR checksum seeded by `0x5A`. -- Generates a focused RX/TX serial-path pseudocode view from those serial reconstruction candidates. +- 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. - Adds a Sony RCP-TX7 board profile that ties H8/536 pin 66 `P95/TXD` and pin 67 `P96/RXD` to the MAX202 RS232 transceiver. - Flags/manual-annotates TEMP-register access ordering for FRT and A/D 16-bit peripheral registers. - Scans unreached ROM ranges for ASCII strings and pointer-table candidates. @@ -108,6 +109,7 @@ python h8536_serial_pseudocode.py --help - `--no-evidence`: omit evidence-address comments. - `--no-manual`: omit manual-reference comments. - `--no-board`: omit board/MAX202 comments. +- `--no-semantics`: omit candidate command/field semantics. ## Code Layout @@ -130,6 +132,7 @@ python h8536_serial_pseudocode.py --help - `h8536/sci.py`: SCI setup tracking and baud inference. - `h8536/sci_protocol.py`: SCI transmit/receive/status semantic annotations. - `h8536/serial_reconstruction.py`: cautious higher-level SCI frame reconstruction from decompiled evidence. +- `h8536/serial_semantics.py`: candidate command/field semantics inferred from serial frame use. - `h8536/serial_pseudocode.py`: focused RX/TX protocol pseudocode generation from reconstruction metadata. - `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. diff --git a/build/rom_decompiled.json b/build/rom_decompiled.json index 64954c8..2918a1c 100644 --- a/build/rom_decompiled.json +++ b/build/rom_decompiled.json @@ -185182,5 +185182,4867 @@ "notes": [] } } - ] + ], + "serial_semantics": { + "kind": "serial_semantics", + "protocol_semantics": [ + { + "kind": "serial_semantics", + "scope": "evidence_supported_sci1_6_byte_frame", + "confidence": "medium-high", + "confidence_score": 0.9, + "caveat": "Semantic names are candidates only. The analyzer reports byte roles, command values, dispatch targets, and response staging patterns observed in code; it does not prove source-level intent or protocol documentation.", + "frame_candidate": { + "channel": "SCI1", + "rx_frame_start": 63584, + "rx_frame_start_hex": "H'F860", + "rx_frame_end": 63589, + "rx_frame_end_hex": "H'F865", + "tx_staging_start": 63568, + "tx_staging_start_hex": "H'F850", + "tx_staging_end": 63572, + "tx_staging_end_hex": "H'F854", + "tx_frame_start": 63576, + "tx_frame_start_hex": "H'F858", + "tx_frame_end": 63581, + "tx_frame_end_hex": "H'F85D", + "frame_length": 6, + "tx_staging_length": 5, + "checksum_seed": 90, + "checksum_seed_hex": "H'005A", + "serial_reconstruction_supported": true, + "rx_reconstruction_candidate_id": "sci1_rx_frame_f868_len6_candidate", + "tx_reconstruction_candidate_id": "sci1_tx_frame_f858_len6_candidate" + }, + "byte_layout": [ + { + "offset": 0, + "rx_address": 63584, + "tx_staging_address": 63568, + "name_candidate": "op_flags", + "semantic": "low three bits select a command; upper bits are preserved or gated in some paths", + "confidence": "medium-high" + }, + { + "offset": 1, + "rx_address": 63585, + "tx_staging_address": 63569, + "name_candidate": "addr_page_flags", + "semantic": "candidate high/page byte for logical point/index; bit 7 is tested as a control flag", + "confidence": "medium" + }, + { + "offset": 2, + "rx_address": 63586, + "tx_staging_address": 63570, + "name_candidate": "addr_offset", + "semantic": "candidate low/offset byte for logical point/index", + "confidence": "medium" + }, + { + "offset": 3, + "rx_address": 63587, + "tx_staging_address": 63571, + "name_candidate": "value_hi", + "semantic": "candidate high byte of a word value", + "confidence": "medium" + }, + { + "offset": 4, + "rx_address": 63588, + "tx_staging_address": 63572, + "name_candidate": "value_lo", + "semantic": "candidate low byte of a word value", + "confidence": "medium" + }, + { + "offset": 5, + "rx_address": 63589, + "tx_staging_address": null, + "name_candidate": "checksum", + "semantic": "0x5A-seeded XOR of bytes 0..4", + "confidence": "high" + } + ], + "fields": [ + { + "id": "rx_0", + "kind": "rx_frame_field_candidate", + "offset": 0, + "address": 63584, + "address_hex": "H'F860", + "role_candidate": "command_selector_candidate", + "evidence_addresses": [ + 48088, + 48136, + 48055, + 48160, + 48162, + 48164, + 48197, + 48166, + 48199, + 48169, + 48202, + 48171, + 48140, + 48204, + 48174, + 48207, + 48176, + 48209, + 48212, + 48214 + ], + "evidence_addresses_hex": [ + "H'BBD8", + "H'BC08", + "H'BBB7", + "H'BC20", + "H'BC22", + "H'BC24", + "H'BC45", + "H'BC26", + "H'BC47", + "H'BC29", + "H'BC4A", + "H'BC2B", + "H'BC0C", + "H'BC4C", + "H'BC2E", + "H'BC4F", + "H'BC30", + "H'BC51", + "H'BC54", + "H'BC56" + ], + "read_count": 2, + "write_count": 2, + "confidence": "medium", + "caveat": "RX[0] is masked with 0x07 before command comparisons." + }, + { + "id": "rx_1", + "kind": "rx_frame_field_candidate", + "offset": 1, + "address": 63585, + "address_hex": "H'F861", + "role_candidate": "payload_byte_candidate", + "evidence_addresses": [ + 48092, + 48119, + 48153, + 48190, + 48309, + 48348, + 48722 + ], + "evidence_addresses_hex": [ + "H'BBDC", + "H'BBF7", + "H'BC19", + "H'BC3E", + "H'BCB5", + "H'BCDC", + "H'BE52" + ], + "read_count": 7, + "write_count": 1, + "confidence": "medium", + "caveat": "Role is inferred from reads in command processing." + }, + { + "id": "rx_2", + "kind": "rx_frame_field_candidate", + "offset": 2, + "address": 63586, + "address_hex": "H'F862", + "role_candidate": "payload_byte_candidate", + "evidence_addresses": [ + 48096, + 48125, + 48317, + 48356, + 48730, + 48063 + ], + "evidence_addresses_hex": [ + "H'BBE0", + "H'BBFD", + "H'BCBD", + "H'BCE4", + "H'BE5A", + "H'BBBF" + ], + "read_count": 5, + "write_count": 2, + "confidence": "medium", + "caveat": "Role is inferred from reads in command processing." + }, + { + "id": "rx_3", + "kind": "rx_frame_field_candidate", + "offset": 3, + "address": 63587, + "address_hex": "H'F863", + "role_candidate": "payload_byte_candidate", + "evidence_addresses": [ + 48100, + 48237, + 48267, + 48402, + 48427, + 48603 + ], + "evidence_addresses_hex": [ + "H'BBE4", + "H'BC6D", + "H'BC8B", + "H'BD12", + "H'BD2B", + "H'BDDB" + ], + "read_count": 6, + "write_count": 1, + "confidence": "medium", + "caveat": "Role is inferred from reads in command processing." + }, + { + "id": "rx_4", + "kind": "rx_frame_field_candidate", + "offset": 4, + "address": 63588, + "address_hex": "H'F864", + "role_candidate": "payload_byte_candidate", + "evidence_addresses": [ + 48104, + 48273, + 48325, + 48433, + 48609, + 48738, + 48071, + 48253 + ], + "evidence_addresses_hex": [ + "H'BBE8", + "H'BC91", + "H'BCC5", + "H'BD31", + "H'BDE1", + "H'BE62", + "H'BBC7", + "H'BC7D" + ], + "read_count": 6, + "write_count": 3, + "confidence": "medium", + "caveat": "Role is inferred from reads in command processing." + }, + { + "id": "rx_5", + "kind": "rx_frame_field_candidate", + "offset": 5, + "address": 63589, + "address_hex": "H'F865", + "role_candidate": "checksum_byte_candidate", + "evidence_addresses": [ + 48108 + ], + "evidence_addresses_hex": [ + "H'BBEC" + ], + "read_count": 1, + "write_count": 0, + "confidence": "medium", + "caveat": "RX[5] is compared with a checksum over RX[0..4]." + }, + { + "id": "tx_staging_0", + "kind": "tx_staging_field_candidate", + "offset": 0, + "address": 63568, + "address_hex": "H'F850", + "role_candidate": "response_staging_byte_candidate", + "evidence_addresses": [ + 47900, + 48304, + 48343, + 48649, + 48717 + ], + "evidence_addresses_hex": [ + "H'BB1C", + "H'BCB0", + "H'BCD7", + "H'BE09", + "H'BE4D" + ], + "write_count": 5, + "confidence": "medium", + "caveat": "This byte is staged before calls to loc_BA26; the analyzer does not infer a stable field name beyond response position." + }, + { + "id": "tx_staging_1", + "kind": "tx_staging_field_candidate", + "offset": 1, + "address": 63569, + "address_hex": "H'F851", + "role_candidate": "response_staging_byte_candidate", + "evidence_addresses": [ + 47915, + 48313, + 48352, + 48360, + 48649, + 48726 + ], + "evidence_addresses_hex": [ + "H'BB2B", + "H'BCB9", + "H'BCE0", + "H'BCE8", + "H'BE09", + "H'BE56" + ], + "write_count": 6, + "confidence": "medium", + "caveat": "This byte is staged before calls to loc_BA26; the analyzer does not infer a stable field name beyond response position." + }, + { + "id": "tx_staging_2", + "kind": "tx_staging_field_candidate", + "offset": 2, + "address": 63570, + "address_hex": "H'F852", + "role_candidate": "response_staging_byte_candidate", + "evidence_addresses": [ + 47904, + 48321, + 48657, + 48734 + ], + "evidence_addresses_hex": [ + "H'BB20", + "H'BCC1", + "H'BE11", + "H'BE5E" + ], + "write_count": 4, + "confidence": "medium", + "caveat": "This byte is staged before calls to loc_BA26; the analyzer does not infer a stable field name beyond response position." + }, + { + "id": "tx_staging_3", + "kind": "tx_staging_field_candidate", + "offset": 3, + "address": 63571, + "address_hex": "H'F853", + "role_candidate": "response_staging_byte_candidate", + "evidence_addresses": [ + 47935, + 48321, + 48374, + 48657, + 48734 + ], + "evidence_addresses_hex": [ + "H'BB3F", + "H'BCC1", + "H'BCF6", + "H'BE11", + "H'BE5E" + ], + "write_count": 5, + "confidence": "medium", + "caveat": "This byte is staged before calls to loc_BA26; the analyzer does not infer a stable field name beyond response position." + }, + { + "id": "tx_staging_4", + "kind": "tx_staging_field_candidate", + "offset": 4, + "address": 63572, + "address_hex": "H'F854", + "role_candidate": "response_staging_byte_candidate", + "evidence_addresses": [ + 47929, + 48329, + 48368, + 48665, + 48742 + ], + "evidence_addresses_hex": [ + "H'BB39", + "H'BCC9", + "H'BCF0", + "H'BE19", + "H'BE66" + ], + "write_count": 5, + "confidence": "medium", + "caveat": "This byte is staged before calls to loc_BA26; the analyzer does not infer a stable field name beyond response position." + } + ], + "command_dispatch": { + "kind": "command_dispatch_candidate", + "selector": "rx0_low3_bits", + "field": "command_low3", + "rx_offset": 0, + "rx_address": 63584, + "rx_address_hex": "H'F860", + "source_address": 63584, + "source_address_hex": "H'F860", + "source_field": "byte0", + "mask": 7, + "mask_hex": "H'0007", + "selector_register": "R0", + "read_address": 48136, + "read_address_hex": "H'BC08", + "mask_address": 48140, + "mask_address_hex": "H'BC0C", + "command_values": [ + 0, + 1, + 2, + 4, + 5, + 6, + 7 + ], + "command_values_hex": [ + "H'00", + "H'01", + "H'02", + "H'04", + "H'05", + "H'06", + "H'07" + ], + "comparisons": [ + { + "command_value": 0, + "command_value_hex": "H'00", + "compare_address": 48160, + "compare_address_hex": "H'BC20", + "branch_address": 48162, + "branch_address_hex": "H'BC22", + "handler_start": 48233, + "handler_start_hex": "H'BC69", + "evidence_addresses": [ + 48160, + 48162 + ], + "evidence_addresses_hex": [ + "H'BC20", + "H'BC22" + ], + "handler_start_index": 2147 + }, + { + "command_value": 1, + "command_value_hex": "H'01", + "compare_address": 48164, + "compare_address_hex": "H'BC24", + "branch_address": 48166, + "branch_address_hex": "H'BC26", + "handler_start": 48343, + "handler_start_hex": "H'BCD7", + "evidence_addresses": [ + 48164, + 48166 + ], + "evidence_addresses_hex": [ + "H'BC24", + "H'BC26" + ], + "handler_start_index": 2179 + }, + { + "command_value": 2, + "command_value_hex": "H'02", + "compare_address": 48169, + "compare_address_hex": "H'BC29", + "branch_address": 48171, + "branch_address_hex": "H'BC2B", + "handler_start": 48388, + "handler_start_hex": "H'BD04", + "evidence_addresses": [ + 48169, + 48171 + ], + "evidence_addresses_hex": [ + "H'BC29", + "H'BC2B" + ], + "handler_start_index": 2191 + }, + { + "command_value": 7, + "command_value_hex": "H'07", + "compare_address": 48174, + "compare_address_hex": "H'BC2E", + "branch_address": 48176, + "branch_address_hex": "H'BC30", + "handler_start": 48645, + "handler_start_hex": "H'BE05", + "evidence_addresses": [ + 48174, + 48176 + ], + "evidence_addresses_hex": [ + "H'BC2E", + "H'BC30" + ], + "handler_start_index": 2275 + }, + { + "command_value": 4, + "command_value_hex": "H'04", + "compare_address": 48197, + "compare_address_hex": "H'BC45", + "branch_address": 48199, + "branch_address_hex": "H'BC47", + "handler_start": 48398, + "handler_start_hex": "H'BD0E", + "evidence_addresses": [ + 48197, + 48199 + ], + "evidence_addresses_hex": [ + "H'BC45", + "H'BC47" + ], + "handler_start_index": 2194 + }, + { + "command_value": 5, + "command_value_hex": "H'05", + "compare_address": 48202, + "compare_address_hex": "H'BC4A", + "branch_address": 48204, + "branch_address_hex": "H'BC4C", + "handler_start": 48512, + "handler_start_hex": "H'BD80", + "evidence_addresses": [ + 48202, + 48204 + ], + "evidence_addresses_hex": [ + "H'BC4A", + "H'BC4C" + ], + "handler_start_index": 2231 + }, + { + "command_value": 6, + "command_value_hex": "H'06", + "compare_address": 48207, + "compare_address_hex": "H'BC4F", + "branch_address": 48209, + "branch_address_hex": "H'BC51", + "handler_start": 48603, + "handler_start_hex": "H'BDDB", + "evidence_addresses": [ + 48207, + 48209 + ], + "evidence_addresses_hex": [ + "H'BC4F", + "H'BC51" + ], + "handler_start_index": 2263 + }, + { + "command_value": 7, + "command_value_hex": "H'07", + "compare_address": 48212, + "compare_address_hex": "H'BC54", + "branch_address": 48214, + "branch_address_hex": "H'BC56", + "handler_start": 48645, + "handler_start_hex": "H'BE05", + "evidence_addresses": [ + 48212, + 48214 + ], + "evidence_addresses_hex": [ + "H'BC54", + "H'BC56" + ], + "handler_start_index": 2275 + } + ], + "cases": [ + { + "value": 0, + "value_hex": "H'00", + "target": 48233, + "target_hex": "H'BC69", + "compare_address": 48160, + "branch_address": 48162 + }, + { + "value": 1, + "value_hex": "H'01", + "target": 48343, + "target_hex": "H'BCD7", + "compare_address": 48164, + "branch_address": 48166 + }, + { + "value": 2, + "value_hex": "H'02", + "target": 48388, + "target_hex": "H'BD04", + "compare_address": 48169, + "branch_address": 48171 + }, + { + "value": 7, + "value_hex": "H'07", + "target": 48645, + "target_hex": "H'BE05", + "compare_address": 48174, + "branch_address": 48176 + }, + { + "value": 4, + "value_hex": "H'04", + "target": 48398, + "target_hex": "H'BD0E", + "compare_address": 48197, + "branch_address": 48199 + }, + { + "value": 5, + "value_hex": "H'05", + "target": 48512, + "target_hex": "H'BD80", + "compare_address": 48202, + "branch_address": 48204 + }, + { + "value": 6, + "value_hex": "H'06", + "target": 48603, + "target_hex": "H'BDDB", + "compare_address": 48207, + "branch_address": 48209 + }, + { + "value": 7, + "value_hex": "H'07", + "target": 48645, + "target_hex": "H'BE05", + "compare_address": 48212, + "branch_address": 48214 + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48160, + 48162, + 48164, + 48166, + 48169, + 48171, + 48174, + 48176, + 48197, + 48199, + 48202, + 48204, + 48207, + 48209, + 48212, + 48214 + ], + "confidence": "medium", + "caveat": "Dispatch is inferred from a read of RX[0], an AND 0x07 mask, and nearby compare/branch pairs. Gating state around the dispatch may affect reachability.", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC20", + "H'BC22", + "H'BC24", + "H'BC26", + "H'BC29", + "H'BC2B", + "H'BC2E", + "H'BC30", + "H'BC45", + "H'BC47", + "H'BC4A", + "H'BC4C", + "H'BC4F", + "H'BC51", + "H'BC54", + "H'BC56" + ] + }, + "commands": [ + { + "kind": "command_candidate", + "command_value": 0, + "command_value_hex": "H'00", + "name_candidate": "set_value_acked", + "summary": "candidate write of RX[3:4] into primary/current tables, followed by a response", + "handler_alternatives": [ + { + "handler_start": 48233, + "handler_start_hex": "H'BC69", + "handler_end": 48340, + "handler_end_hex": "H'BCD4", + "dispatch_compare_address": 48160, + "dispatch_compare_address_hex": "H'BC20", + "dispatch_branch_address": 48162, + "dispatch_branch_address_hex": "H'BC22" + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48160, + 48162, + 48304, + 48313, + 48321, + 48329, + 48309, + 48317, + 48325, + 48333 + ], + "response_candidates": [ + "response_at_BCCD" + ], + "rx_reads": [ + { + "instruction_address": 48237, + "instruction_address_hex": "H'BC6D", + "rx_offset": 3, + "rx_address": 63587, + "rx_address_hex": "H'F863", + "instruction": "MOV:G.B @H'F863, R0" + }, + { + "instruction_address": 48267, + "instruction_address_hex": "H'BC8B", + "rx_offset": 3, + "rx_address": 63587, + "rx_address_hex": "H'F863", + "instruction": "MOV:G.B @H'F863, R0" + }, + { + "instruction_address": 48273, + "instruction_address_hex": "H'BC91", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "instruction": "MOV:G.B @H'F864, R0" + }, + { + "instruction_address": 48309, + "instruction_address_hex": "H'BCB5", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "instruction": "MOV:G.B @H'F861, R0" + }, + { + "instruction_address": 48317, + "instruction_address_hex": "H'BCBD", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "instruction": "MOV:G.W @H'F862, R0" + }, + { + "instruction_address": 48325, + "instruction_address_hex": "H'BCC5", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "instruction": "MOV:G.B @H'F864, R0" + } + ], + "confidence": "medium", + "caveat": "Command value and handler range are inferred from compare/BEQ dispatch. No command name or intent is asserted.", + "handler_start": 48233, + "handler_start_hex": "H'BC69", + "handler_end": 48340, + "handler_end_hex": "H'BCD4", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC20", + "H'BC22", + "H'BCB0", + "H'BCB9", + "H'BCC1", + "H'BCC9", + "H'BCB5", + "H'BCBD", + "H'BCC5", + "H'BCCD" + ] + }, + { + "kind": "command_candidate", + "command_value": 1, + "command_value_hex": "H'01", + "name_candidate": "read_value", + "summary": "candidate read from the primary table, followed by a response carrying the value", + "handler_alternatives": [ + { + "handler_start": 48343, + "handler_start_hex": "H'BCD7", + "handler_end": 48385, + "handler_end_hex": "H'BD01", + "dispatch_compare_address": 48164, + "dispatch_compare_address_hex": "H'BC24", + "dispatch_branch_address": 48166, + "dispatch_branch_address_hex": "H'BC26" + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48164, + 48166, + 48304, + 48313, + 48321, + 48329, + 48343, + 48352, + 48360, + 48368, + 48374, + 48309, + 48317, + 48325, + 48348, + 48356, + 48378 + ], + "response_candidates": [ + "response_at_BCFA" + ], + "rx_reads": [ + { + "instruction_address": 48348, + "instruction_address_hex": "H'BCDC", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "instruction": "MOV:G.B @H'F861, R0" + }, + { + "instruction_address": 48356, + "instruction_address_hex": "H'BCE4", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "instruction": "MOV:G.B @H'F862, R0" + } + ], + "confidence": "medium", + "caveat": "Command value and handler range are inferred from compare/BEQ dispatch. No command name or intent is asserted.", + "handler_start": 48343, + "handler_start_hex": "H'BCD7", + "handler_end": 48385, + "handler_end_hex": "H'BD01", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC24", + "H'BC26", + "H'BCB0", + "H'BCB9", + "H'BCC1", + "H'BCC9", + "H'BCD7", + "H'BCE0", + "H'BCE8", + "H'BCF0", + "H'BCF6", + "H'BCB5", + "H'BCBD", + "H'BCC5", + "H'BCDC", + "H'BCE4", + "H'BCFA" + ] + }, + { + "kind": "command_candidate", + "command_value": 2, + "command_value_hex": "H'02", + "name_candidate": "clear_or_abort", + "summary": "candidate clear/abort path with no immediate response builder", + "handler_alternatives": [ + { + "handler_start": 48388, + "handler_start_hex": "H'BD04", + "handler_end": 48395, + "handler_end_hex": "H'BD0B", + "dispatch_compare_address": 48169, + "dispatch_compare_address_hex": "H'BC29", + "dispatch_branch_address": 48171, + "dispatch_branch_address_hex": "H'BC2B" + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48169, + 48171 + ], + "response_candidates": [], + "rx_reads": [], + "confidence": "medium", + "caveat": "Command value and handler range are inferred from compare/BEQ dispatch. No command name or intent is asserted.", + "handler_start": 48388, + "handler_start_hex": "H'BD04", + "handler_end": 48395, + "handler_end_hex": "H'BD0B", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC29", + "H'BC2B" + ] + }, + { + "kind": "command_candidate", + "command_value": 4, + "command_value_hex": "H'04", + "name_candidate": "set_value_no_immediate_reply", + "summary": "candidate write/update path that stores a value without an immediate serial response", + "handler_alternatives": [ + { + "handler_start": 48398, + "handler_start_hex": "H'BD0E", + "handler_end": 48509, + "handler_end_hex": "H'BD7D", + "dispatch_compare_address": 48197, + "dispatch_compare_address_hex": "H'BC45", + "dispatch_branch_address": 48199, + "dispatch_branch_address_hex": "H'BC47" + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48197, + 48199 + ], + "response_candidates": [], + "rx_reads": [ + { + "instruction_address": 48402, + "instruction_address_hex": "H'BD12", + "rx_offset": 3, + "rx_address": 63587, + "rx_address_hex": "H'F863", + "instruction": "MOV:G.B @H'F863, R0" + }, + { + "instruction_address": 48427, + "instruction_address_hex": "H'BD2B", + "rx_offset": 3, + "rx_address": 63587, + "rx_address_hex": "H'F863", + "instruction": "MOV:G.B @H'F863, R0" + }, + { + "instruction_address": 48433, + "instruction_address_hex": "H'BD31", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "instruction": "MOV:G.B @H'F864, R0" + } + ], + "confidence": "medium", + "caveat": "Command value and handler range are inferred from compare/BEQ dispatch. No command name or intent is asserted.", + "handler_start": 48398, + "handler_start_hex": "H'BD0E", + "handler_end": 48509, + "handler_end_hex": "H'BD7D", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC45", + "H'BC47" + ] + }, + { + "kind": "command_candidate", + "command_value": 5, + "command_value_hex": "H'05", + "name_candidate": "ack_or_clear_pending", + "summary": "candidate pending/event acknowledgement path", + "handler_alternatives": [ + { + "handler_start": 48512, + "handler_start_hex": "H'BD80", + "handler_end": 48600, + "handler_end_hex": "H'BDD8", + "dispatch_compare_address": 48202, + "dispatch_compare_address_hex": "H'BC4A", + "dispatch_branch_address": 48204, + "dispatch_branch_address_hex": "H'BC4C" + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48202, + 48204 + ], + "response_candidates": [], + "rx_reads": [], + "confidence": "medium", + "caveat": "Command value and handler range are inferred from compare/BEQ dispatch. No command name or intent is asserted.", + "handler_start": 48512, + "handler_start_hex": "H'BD80", + "handler_end": 48600, + "handler_end_hex": "H'BDD8", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC4A", + "H'BC4C" + ] + }, + { + "kind": "command_candidate", + "command_value": 6, + "command_value_hex": "H'06", + "name_candidate": "set_secondary_value", + "summary": "candidate secondary-table value write path", + "handler_alternatives": [ + { + "handler_start": 48603, + "handler_start_hex": "H'BDDB", + "handler_end": 48643, + "handler_end_hex": "H'BE03", + "dispatch_compare_address": 48207, + "dispatch_compare_address_hex": "H'BC4F", + "dispatch_branch_address": 48209, + "dispatch_branch_address_hex": "H'BC51" + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48207, + 48209 + ], + "response_candidates": [], + "rx_reads": [ + { + "instruction_address": 48603, + "instruction_address_hex": "H'BDDB", + "rx_offset": 3, + "rx_address": 63587, + "rx_address_hex": "H'F863", + "instruction": "MOV:G.B @H'F863, R0" + }, + { + "instruction_address": 48609, + "instruction_address_hex": "H'BDE1", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "instruction": "MOV:G.B @H'F864, R0" + } + ], + "confidence": "medium", + "caveat": "Command value and handler range are inferred from compare/BEQ dispatch. No command name or intent is asserted.", + "handler_start": 48603, + "handler_start_hex": "H'BDDB", + "handler_end": 48643, + "handler_end_hex": "H'BE03", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC4F", + "H'BC51" + ] + }, + { + "kind": "command_candidate", + "command_value": 7, + "command_value_hex": "H'07", + "name_candidate": "retransmit_or_error_reply", + "summary": "candidate retransmit/NAK-style path; error handling also builds command 0x07 responses", + "handler_alternatives": [ + { + "handler_start": 48645, + "handler_start_hex": "H'BE05", + "handler_end": 48677, + "handler_end_hex": "H'BE25", + "dispatch_compare_address": 48174, + "dispatch_compare_address_hex": "H'BC2E", + "dispatch_branch_address": 48176, + "dispatch_branch_address_hex": "H'BC30" + }, + { + "handler_start": 48645, + "handler_start_hex": "H'BE05", + "handler_end": 48677, + "handler_end_hex": "H'BE25", + "dispatch_compare_address": 48212, + "dispatch_compare_address_hex": "H'BC54", + "dispatch_branch_address": 48214, + "dispatch_branch_address_hex": "H'BC56" + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48174, + 48176, + 48212, + 48214, + 48649, + 48657, + 48665, + 48674 + ], + "response_candidates": [ + "response_at_BE22" + ], + "rx_reads": [], + "confidence": "medium", + "caveat": "Command value and handler range are inferred from compare/BEQ dispatch. No command name or intent is asserted.", + "handler_start": 48645, + "handler_start_hex": "H'BE05", + "handler_end": 48677, + "handler_end_hex": "H'BE25", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC2E", + "H'BC30", + "H'BC54", + "H'BC56", + "H'BE09", + "H'BE11", + "H'BE19", + "H'BE22" + ] + } + ], + "index_decoder": { + "kind": "logical_index_decoder_candidate", + "label": "loc_622B", + "address": 25131, + "address_hex": "H'622B", + "input_fields": [ + "addr_page_flags", + "addr_offset" + ], + "output_register": "R5", + "post_scale_register": "R4", + "post_scale": "R4 = R5 << 1", + "mapping_candidate": [ + { + "page": 0, + "offset_range": "0x00-0x7F", + "index_range": "0x000-0x07F" + }, + { + "page": 1, + "offset_range": "0x00-0xFF", + "index_range": "0x080-0x17F" + }, + { + "page": 2, + "offset_range": "0x00-0x7F", + "index_range": "0x180-0x1FF" + }, + { + "page": "other/overflow", + "index": "0x1FF" + } + ], + "evidence_addresses": [ + 48129 + ], + "evidence_addresses_hex": [ + "H'BC01" + ], + "confidence": "medium", + "caveat": "Mapping is inferred from loc_622B behavior and the nearby R4 = R5 << 1 table-index use." + }, + "send_builder": { + "kind": "tx_send_builder_candidate", + "label": "loc_BA26", + "address": 47654, + "address_hex": "H'BA26", + "staging_buffer_start": 63568, + "staging_buffer_start_hex": "H'F850", + "staging_buffer_end": 63572, + "staging_buffer_end_hex": "H'F854", + "tx_frame_start": 63576, + "tx_frame_start_hex": "H'F858", + "tx_frame_end": 63581, + "tx_frame_end_hex": "H'F85D", + "checksum_address": 63581, + "checksum_address_hex": "H'F85D", + "checksum_seed": 90, + "checksum_seed_hex": "H'005A", + "staging_to_frame_copies": [], + "response_call_addresses": [ + 47939, + 48333, + 48378, + 48674, + 48746 + ], + "response_call_addresses_hex": [ + "H'BB43", + "H'BCCD", + "H'BCFA", + "H'BE22", + "H'BE6A" + ], + "serial_reconstruction_candidate_id": "sci1_tx_frame_f858_len6_candidate", + "evidence_addresses": [ + 47674, + 47682, + 47690, + 47696, + 47700, + 47704, + 47708, + 47712, + 47716, + 47726, + 47939, + 48333, + 48378, + 48674, + 48746 + ], + "evidence_addresses_hex": [ + "H'BA3A", + "H'BA42", + "H'BA4A", + "H'BA50", + "H'BA54", + "H'BA58", + "H'BA5C", + "H'BA60", + "H'BA64", + "H'BA6E", + "H'BB43", + "H'BCCD", + "H'BCFA", + "H'BE22", + "H'BE6A" + ], + "confidence": "low", + "caveat": "loc_BA26 is treated as a send builder because it copies F850-F854 into the evidence-supported TX frame and then starts SCI1 transmission." + }, + "response_candidates": [ + { + "id": "response_at_BB43", + "kind": "response_staging_candidate", + "call_address": 47939, + "call_address_hex": "H'BB43", + "send_builder": "loc_BA26", + "send_builder_address": 47654, + "send_builder_address_hex": "H'BA26", + "window_start": 47870, + "window_start_hex": "H'BAFE", + "writes": [ + { + "instruction_address": 47900, + "instruction_address_hex": "H'BB1C", + "addresses": [ + 63568 + ], + "addresses_hex": [ + "H'F850" + ], + "source_operand": "R1", + "source": { + "kind": "register_or_computed", + "operand": "R1" + }, + "instruction": "MOV:G.B R1, @H'F850" + }, + { + "instruction_address": 47904, + "instruction_address_hex": "H'BB20", + "addresses": [ + 63570 + ], + "addresses_hex": [ + "H'F852" + ], + "source_operand": "R5", + "source": { + "kind": "register_or_computed", + "operand": "R5" + }, + "instruction": "MOV:G.B R5, @H'F852" + }, + { + "instruction_address": 47915, + "instruction_address_hex": "H'BB2B", + "addresses": [ + 63569 + ], + "addresses_hex": [ + "H'F851" + ], + "source_operand": "R5", + "source": { + "kind": "register_or_computed", + "operand": "R5" + }, + "instruction": "MOV:G.B R5, @H'F851" + }, + { + "instruction_address": 47929, + "instruction_address_hex": "H'BB39", + "addresses": [ + 63572 + ], + "addresses_hex": [ + "H'F854" + ], + "source_operand": "R4", + "source": { + "kind": "register_or_computed", + "operand": "R4" + }, + "instruction": "MOV:G.B R4, @H'F854" + }, + { + "instruction_address": 47935, + "instruction_address_hex": "H'BB3F", + "addresses": [ + 63571 + ], + "addresses_hex": [ + "H'F853" + ], + "source_operand": "R4", + "source": { + "kind": "register_or_computed", + "operand": "R4" + }, + "instruction": "MOV:G.B R4, @H'F853" + } + ], + "rx_reads": [], + "evidence_addresses": [ + 47900, + 47904, + 47915, + 47929, + 47935, + 47939 + ], + "evidence_addresses_hex": [ + "H'BB1C", + "H'BB20", + "H'BB2B", + "H'BB39", + "H'BB3F", + "H'BB43" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + }, + { + "id": "response_at_BCCD", + "kind": "response_staging_candidate", + "call_address": 48333, + "call_address_hex": "H'BCCD", + "send_builder": "loc_BA26", + "send_builder_address": 47654, + "send_builder_address_hex": "H'BA26", + "window_start": 48297, + "window_start_hex": "H'BCA9", + "writes": [ + { + "instruction_address": 48304, + "instruction_address_hex": "H'BCB0", + "addresses": [ + 63568 + ], + "addresses_hex": [ + "H'F850" + ], + "source_operand": "#H'04", + "source": { + "kind": "immediate", + "value": 4, + "value_hex": "H'04" + }, + "instruction": "MOV:G.B #H'04, @H'F850" + }, + { + "instruction_address": 48313, + "instruction_address_hex": "H'BCB9", + "addresses": [ + 63569 + ], + "addresses_hex": [ + "H'F851" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "evidence_address": 48309, + "evidence_address_hex": "H'BCB5", + "instruction": "MOV:G.B @H'F861, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "instruction_address": 48321, + "instruction_address_hex": "H'BCC1", + "addresses": [ + 63570, + 63571 + ], + "addresses_hex": [ + "H'F852", + "H'F853" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48317, + "evidence_address_hex": "H'BCBD", + "instruction": "MOV:G.W @H'F862, R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "instruction_address": 48329, + "instruction_address_hex": "H'BCC9", + "addresses": [ + 63572 + ], + "addresses_hex": [ + "H'F854" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "evidence_address": 48325, + "evidence_address_hex": "H'BCC5", + "instruction": "MOV:G.B @H'F864, R0" + }, + "instruction": "MOV:G.B R0, @H'F854" + } + ], + "rx_reads": [ + { + "instruction_address": 48309, + "instruction_address_hex": "H'BCB5", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "instruction": "MOV:G.B @H'F861, R0" + }, + { + "instruction_address": 48317, + "instruction_address_hex": "H'BCBD", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "instruction": "MOV:G.W @H'F862, R0" + }, + { + "instruction_address": 48325, + "instruction_address_hex": "H'BCC5", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "instruction": "MOV:G.B @H'F864, R0" + } + ], + "evidence_addresses": [ + 48304, + 48313, + 48321, + 48329, + 48309, + 48317, + 48325, + 48333 + ], + "evidence_addresses_hex": [ + "H'BCB0", + "H'BCB9", + "H'BCC1", + "H'BCC9", + "H'BCB5", + "H'BCBD", + "H'BCC5", + "H'BCCD" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + }, + { + "id": "response_at_BCFA", + "kind": "response_staging_candidate", + "call_address": 48378, + "call_address_hex": "H'BCFA", + "send_builder": "loc_BA26", + "send_builder_address": 47654, + "send_builder_address_hex": "H'BA26", + "window_start": 48297, + "window_start_hex": "H'BCA9", + "writes": [ + { + "instruction_address": 48304, + "instruction_address_hex": "H'BCB0", + "addresses": [ + 63568 + ], + "addresses_hex": [ + "H'F850" + ], + "source_operand": "#H'04", + "source": { + "kind": "immediate", + "value": 4, + "value_hex": "H'04" + }, + "instruction": "MOV:G.B #H'04, @H'F850" + }, + { + "instruction_address": 48313, + "instruction_address_hex": "H'BCB9", + "addresses": [ + 63569 + ], + "addresses_hex": [ + "H'F851" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "evidence_address": 48309, + "evidence_address_hex": "H'BCB5", + "instruction": "MOV:G.B @H'F861, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "instruction_address": 48321, + "instruction_address_hex": "H'BCC1", + "addresses": [ + 63570, + 63571 + ], + "addresses_hex": [ + "H'F852", + "H'F853" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48317, + "evidence_address_hex": "H'BCBD", + "instruction": "MOV:G.W @H'F862, R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "instruction_address": 48329, + "instruction_address_hex": "H'BCC9", + "addresses": [ + 63572 + ], + "addresses_hex": [ + "H'F854" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "evidence_address": 48325, + "evidence_address_hex": "H'BCC5", + "instruction": "MOV:G.B @H'F864, R0" + }, + "instruction": "MOV:G.B R0, @H'F854" + }, + { + "instruction_address": 48343, + "instruction_address_hex": "H'BCD7", + "addresses": [ + 63568 + ], + "addresses_hex": [ + "H'F850" + ], + "source_operand": "#H'04", + "source": { + "kind": "immediate", + "value": 4, + "value_hex": "H'04" + }, + "instruction": "MOV:G.B #H'04, @H'F850" + }, + { + "instruction_address": 48352, + "instruction_address_hex": "H'BCE0", + "addresses": [ + 63569 + ], + "addresses_hex": [ + "H'F851" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "evidence_address": 48348, + "evidence_address_hex": "H'BCDC", + "instruction": "MOV:G.B @H'F861, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "instruction_address": 48360, + "instruction_address_hex": "H'BCE8", + "addresses": [ + 63569 + ], + "addresses_hex": [ + "H'F851" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48356, + "evidence_address_hex": "H'BCE4", + "instruction": "MOV:G.B @H'F862, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "instruction_address": 48368, + "instruction_address_hex": "H'BCF0", + "addresses": [ + 63572 + ], + "addresses_hex": [ + "H'F854" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48356, + "evidence_address_hex": "H'BCE4", + "instruction": "MOV:G.B @H'F862, R0" + }, + "instruction": "MOV:G.B R0, @H'F854" + }, + { + "instruction_address": 48374, + "instruction_address_hex": "H'BCF6", + "addresses": [ + 63571 + ], + "addresses_hex": [ + "H'F853" + ], + "source_operand": "R0", + "source": { + "kind": "register_or_computed", + "operand": "R0" + }, + "instruction": "MOV:G.B R0, @H'F853" + } + ], + "rx_reads": [ + { + "instruction_address": 48309, + "instruction_address_hex": "H'BCB5", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "instruction": "MOV:G.B @H'F861, R0" + }, + { + "instruction_address": 48317, + "instruction_address_hex": "H'BCBD", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "instruction": "MOV:G.W @H'F862, R0" + }, + { + "instruction_address": 48325, + "instruction_address_hex": "H'BCC5", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "instruction": "MOV:G.B @H'F864, R0" + }, + { + "instruction_address": 48348, + "instruction_address_hex": "H'BCDC", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "instruction": "MOV:G.B @H'F861, R0" + }, + { + "instruction_address": 48356, + "instruction_address_hex": "H'BCE4", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "instruction": "MOV:G.B @H'F862, R0" + } + ], + "evidence_addresses": [ + 48304, + 48313, + 48321, + 48329, + 48343, + 48352, + 48360, + 48368, + 48374, + 48309, + 48317, + 48325, + 48348, + 48356, + 48378 + ], + "evidence_addresses_hex": [ + "H'BCB0", + "H'BCB9", + "H'BCC1", + "H'BCC9", + "H'BCD7", + "H'BCE0", + "H'BCE8", + "H'BCF0", + "H'BCF6", + "H'BCB5", + "H'BCBD", + "H'BCC5", + "H'BCDC", + "H'BCE4", + "H'BCFA" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + }, + { + "id": "response_at_BE22", + "kind": "response_staging_candidate", + "call_address": 48674, + "call_address_hex": "H'BE22", + "send_builder": "loc_BA26", + "send_builder_address": 47654, + "send_builder_address_hex": "H'BA26", + "window_start": 48627, + "window_start_hex": "H'BDF3", + "writes": [ + { + "instruction_address": 48649, + "instruction_address_hex": "H'BE09", + "addresses": [ + 63568, + 63569 + ], + "addresses_hex": [ + "H'F850", + "H'F851" + ], + "source_operand": "R0", + "source": { + "kind": "register_or_computed", + "operand": "R0" + }, + "instruction": "MOV:G.W R0, @H'F850" + }, + { + "instruction_address": 48657, + "instruction_address_hex": "H'BE11", + "addresses": [ + 63570, + 63571 + ], + "addresses_hex": [ + "H'F852", + "H'F853" + ], + "source_operand": "R0", + "source": { + "kind": "register_or_computed", + "operand": "R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "instruction_address": 48665, + "instruction_address_hex": "H'BE19", + "addresses": [ + 63572 + ], + "addresses_hex": [ + "H'F854" + ], + "source_operand": "R0", + "source": { + "kind": "register_or_computed", + "operand": "R0" + }, + "instruction": "MOV:G.W R0, @H'F854" + } + ], + "rx_reads": [], + "evidence_addresses": [ + 48649, + 48657, + 48665, + 48674 + ], + "evidence_addresses_hex": [ + "H'BE09", + "H'BE11", + "H'BE19", + "H'BE22" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + }, + { + "id": "response_at_BE6A", + "kind": "response_staging_candidate", + "call_address": 48746, + "call_address_hex": "H'BE6A", + "send_builder": "loc_BA26", + "send_builder_address": 47654, + "send_builder_address_hex": "H'BA26", + "window_start": 48702, + "window_start_hex": "H'BE3E", + "writes": [ + { + "instruction_address": 48717, + "instruction_address_hex": "H'BE4D", + "addresses": [ + 63568 + ], + "addresses_hex": [ + "H'F850" + ], + "source_operand": "#H'07", + "source": { + "kind": "immediate", + "value": 7, + "value_hex": "H'07" + }, + "instruction": "MOV:G.B #H'07, @H'F850" + }, + { + "instruction_address": 48726, + "instruction_address_hex": "H'BE56", + "addresses": [ + 63569 + ], + "addresses_hex": [ + "H'F851" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "evidence_address": 48722, + "evidence_address_hex": "H'BE52", + "instruction": "MOV:G.B @H'F861, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "instruction_address": 48734, + "instruction_address_hex": "H'BE5E", + "addresses": [ + 63570, + 63571 + ], + "addresses_hex": [ + "H'F852", + "H'F853" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48730, + "evidence_address_hex": "H'BE5A", + "instruction": "MOV:G.W @H'F862, R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "instruction_address": 48742, + "instruction_address_hex": "H'BE66", + "addresses": [ + 63572 + ], + "addresses_hex": [ + "H'F854" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "evidence_address": 48738, + "evidence_address_hex": "H'BE62", + "instruction": "MOV:G.B @H'F864, R0" + }, + "instruction": "MOV:G.B R0, @H'F854" + } + ], + "rx_reads": [ + { + "instruction_address": 48722, + "instruction_address_hex": "H'BE52", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "instruction": "MOV:G.B @H'F861, R0" + }, + { + "instruction_address": 48730, + "instruction_address_hex": "H'BE5A", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "instruction": "MOV:G.W @H'F862, R0" + }, + { + "instruction_address": 48738, + "instruction_address_hex": "H'BE62", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "instruction": "MOV:G.B @H'F864, R0" + } + ], + "evidence_addresses": [ + 48717, + 48726, + 48734, + 48742, + 48722, + 48730, + 48738, + 48746 + ], + "evidence_addresses_hex": [ + "H'BE4D", + "H'BE56", + "H'BE5E", + "H'BE66", + "H'BE52", + "H'BE5A", + "H'BE62", + "H'BE6A" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + } + ], + "rx_fields": [ + { + "kind": "rx_field_semantic_candidate", + "offset": 0, + "name": "command_low3", + "address": 63584, + "address_hex": "H'F860", + "confidence": "candidate-high", + "caveat": "RX[0] is masked with 0x07 before command comparisons", + "evidence_addresses": [ + 48088, + 48136, + 48140, + 48160, + 48162, + 48164, + 48166, + 48169, + 48171, + 48174, + 48176, + 48197, + 48199, + 48202, + 48204, + 48207, + 48209, + 48212, + 48214 + ], + "evidence_addresses_hex": [ + "H'BBD8", + "H'BC08", + "H'BC0C", + "H'BC20", + "H'BC22", + "H'BC24", + "H'BC26", + "H'BC29", + "H'BC2B", + "H'BC2E", + "H'BC30", + "H'BC45", + "H'BC47", + "H'BC4A", + "H'BC4C", + "H'BC4F", + "H'BC51", + "H'BC54", + "H'BC56" + ], + "mask": 7, + "mask_hex": "H'07" + }, + { + "kind": "rx_field_semantic_candidate", + "offset": 1, + "name": "likely_id_or_index", + "address": 63585, + "address_hex": "H'F861", + "confidence": "candidate-medium", + "caveat": "RX[1:2] are read near logical point/index and response-echo handling", + "evidence_addresses": [ + 48092, + 48119, + 48153, + 48190, + 48309, + 48348, + 48722 + ], + "evidence_addresses_hex": [ + "H'BBDC", + "H'BBF7", + "H'BC19", + "H'BC3E", + "H'BCB5", + "H'BCDC", + "H'BE52" + ] + }, + { + "kind": "rx_field_semantic_candidate", + "offset": 2, + "name": "likely_id_or_index", + "address": 63586, + "address_hex": "H'F862", + "confidence": "candidate-medium", + "caveat": "RX[1:2] are read near logical point/index and response-echo handling", + "evidence_addresses": [ + 48096, + 48125, + 48317, + 48356, + 48730 + ], + "evidence_addresses_hex": [ + "H'BBE0", + "H'BBFD", + "H'BCBD", + "H'BCE4", + "H'BE5A" + ] + }, + { + "kind": "rx_field_semantic_candidate", + "offset": 3, + "name": "likely_value", + "address": 63587, + "address_hex": "H'F863", + "confidence": "candidate-medium", + "caveat": "RX[3:4] are read near table-value write/read response handling", + "evidence_addresses": [ + 48100, + 48237, + 48267, + 48402, + 48427, + 48603 + ], + "evidence_addresses_hex": [ + "H'BBE4", + "H'BC6D", + "H'BC8B", + "H'BD12", + "H'BD2B", + "H'BDDB" + ] + }, + { + "kind": "rx_field_semantic_candidate", + "offset": 4, + "name": "likely_value", + "address": 63588, + "address_hex": "H'F864", + "confidence": "candidate-medium", + "caveat": "RX[3:4] are read near table-value write/read response handling", + "evidence_addresses": [ + 48104, + 48273, + 48325, + 48433, + 48609, + 48738 + ], + "evidence_addresses_hex": [ + "H'BBE8", + "H'BC91", + "H'BCC5", + "H'BD31", + "H'BDE1", + "H'BE62" + ] + }, + { + "kind": "rx_field_semantic_candidate", + "offset": 5, + "name": "checksum", + "address": 63589, + "address_hex": "H'F865", + "confidence": "candidate-high", + "caveat": "RX[5] is validated by the serial reconstruction checksum evidence", + "evidence_addresses": [ + 48108 + ], + "evidence_addresses_hex": [ + "H'BBEC" + ] + } + ], + "response_builders": [ + { + "kind": "response_builder_candidate", + "buffer_start": 63568, + "buffer_start_hex": "H'F850", + "buffer_end": 63572, + "buffer_end_hex": "H'F854", + "send_call_target": 47654, + "send_call_target_hex": "H'BA26", + "call_address": 47939, + "call_address_hex": "H'BB43", + "writes": [ + { + "address": 63568, + "address_hex": "H'F850", + "instruction_address": 47900, + "instruction_address_hex": "H'BB1C", + "source": { + "kind": "register_or_computed", + "operand": "R1" + }, + "instruction": "MOV:G.B R1, @H'F850" + }, + { + "address": 63570, + "address_hex": "H'F852", + "instruction_address": 47904, + "instruction_address_hex": "H'BB20", + "source": { + "kind": "register_or_computed", + "operand": "R5" + }, + "instruction": "MOV:G.B R5, @H'F852" + }, + { + "address": 63569, + "address_hex": "H'F851", + "instruction_address": 47915, + "instruction_address_hex": "H'BB2B", + "source": { + "kind": "register_or_computed", + "operand": "R5" + }, + "instruction": "MOV:G.B R5, @H'F851" + }, + { + "address": 63572, + "address_hex": "H'F854", + "instruction_address": 47929, + "instruction_address_hex": "H'BB39", + "source": { + "kind": "register_or_computed", + "operand": "R4" + }, + "instruction": "MOV:G.B R4, @H'F854" + }, + { + "address": 63571, + "address_hex": "H'F853", + "instruction_address": 47935, + "instruction_address_hex": "H'BB3F", + "source": { + "kind": "register_or_computed", + "operand": "R4" + }, + "instruction": "MOV:G.B R4, @H'F853" + } + ], + "evidence_addresses": [ + 47900, + 47904, + 47915, + 47929, + 47935, + 47939 + ], + "evidence_addresses_hex": [ + "H'BB1C", + "H'BB20", + "H'BB2B", + "H'BB39", + "H'BB3F", + "H'BB43" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + }, + { + "kind": "response_builder_candidate", + "buffer_start": 63568, + "buffer_start_hex": "H'F850", + "buffer_end": 63572, + "buffer_end_hex": "H'F854", + "send_call_target": 47654, + "send_call_target_hex": "H'BA26", + "call_address": 48333, + "call_address_hex": "H'BCCD", + "writes": [ + { + "address": 63568, + "address_hex": "H'F850", + "instruction_address": 48304, + "instruction_address_hex": "H'BCB0", + "source": { + "kind": "immediate", + "value": 4, + "value_hex": "H'04" + }, + "instruction": "MOV:G.B #H'04, @H'F850" + }, + { + "address": 63569, + "address_hex": "H'F851", + "instruction_address": 48313, + "instruction_address_hex": "H'BCB9", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "evidence_address": 48309, + "evidence_address_hex": "H'BCB5", + "instruction": "MOV:G.B @H'F861, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "address": 63570, + "address_hex": "H'F852", + "instruction_address": 48321, + "instruction_address_hex": "H'BCC1", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48317, + "evidence_address_hex": "H'BCBD", + "instruction": "MOV:G.W @H'F862, R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "address": 63571, + "address_hex": "H'F853", + "instruction_address": 48321, + "instruction_address_hex": "H'BCC1", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48317, + "evidence_address_hex": "H'BCBD", + "instruction": "MOV:G.W @H'F862, R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "address": 63572, + "address_hex": "H'F854", + "instruction_address": 48329, + "instruction_address_hex": "H'BCC9", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "evidence_address": 48325, + "evidence_address_hex": "H'BCC5", + "instruction": "MOV:G.B @H'F864, R0" + }, + "instruction": "MOV:G.B R0, @H'F854" + } + ], + "evidence_addresses": [ + 48304, + 48313, + 48321, + 48329, + 48309, + 48317, + 48325, + 48333 + ], + "evidence_addresses_hex": [ + "H'BCB0", + "H'BCB9", + "H'BCC1", + "H'BCC9", + "H'BCB5", + "H'BCBD", + "H'BCC5", + "H'BCCD" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + }, + { + "kind": "response_builder_candidate", + "buffer_start": 63568, + "buffer_start_hex": "H'F850", + "buffer_end": 63572, + "buffer_end_hex": "H'F854", + "send_call_target": 47654, + "send_call_target_hex": "H'BA26", + "call_address": 48378, + "call_address_hex": "H'BCFA", + "writes": [ + { + "address": 63568, + "address_hex": "H'F850", + "instruction_address": 48304, + "instruction_address_hex": "H'BCB0", + "source": { + "kind": "immediate", + "value": 4, + "value_hex": "H'04" + }, + "instruction": "MOV:G.B #H'04, @H'F850" + }, + { + "address": 63569, + "address_hex": "H'F851", + "instruction_address": 48313, + "instruction_address_hex": "H'BCB9", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "evidence_address": 48309, + "evidence_address_hex": "H'BCB5", + "instruction": "MOV:G.B @H'F861, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "address": 63570, + "address_hex": "H'F852", + "instruction_address": 48321, + "instruction_address_hex": "H'BCC1", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48317, + "evidence_address_hex": "H'BCBD", + "instruction": "MOV:G.W @H'F862, R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "address": 63571, + "address_hex": "H'F853", + "instruction_address": 48321, + "instruction_address_hex": "H'BCC1", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48317, + "evidence_address_hex": "H'BCBD", + "instruction": "MOV:G.W @H'F862, R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "address": 63572, + "address_hex": "H'F854", + "instruction_address": 48329, + "instruction_address_hex": "H'BCC9", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "evidence_address": 48325, + "evidence_address_hex": "H'BCC5", + "instruction": "MOV:G.B @H'F864, R0" + }, + "instruction": "MOV:G.B R0, @H'F854" + }, + { + "address": 63568, + "address_hex": "H'F850", + "instruction_address": 48343, + "instruction_address_hex": "H'BCD7", + "source": { + "kind": "immediate", + "value": 4, + "value_hex": "H'04" + }, + "instruction": "MOV:G.B #H'04, @H'F850" + }, + { + "address": 63569, + "address_hex": "H'F851", + "instruction_address": 48352, + "instruction_address_hex": "H'BCE0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "evidence_address": 48348, + "evidence_address_hex": "H'BCDC", + "instruction": "MOV:G.B @H'F861, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "address": 63569, + "address_hex": "H'F851", + "instruction_address": 48360, + "instruction_address_hex": "H'BCE8", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48356, + "evidence_address_hex": "H'BCE4", + "instruction": "MOV:G.B @H'F862, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "address": 63572, + "address_hex": "H'F854", + "instruction_address": 48368, + "instruction_address_hex": "H'BCF0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48356, + "evidence_address_hex": "H'BCE4", + "instruction": "MOV:G.B @H'F862, R0" + }, + "instruction": "MOV:G.B R0, @H'F854" + }, + { + "address": 63571, + "address_hex": "H'F853", + "instruction_address": 48374, + "instruction_address_hex": "H'BCF6", + "source": { + "kind": "register_or_computed", + "operand": "R0" + }, + "instruction": "MOV:G.B R0, @H'F853" + } + ], + "evidence_addresses": [ + 48304, + 48313, + 48321, + 48329, + 48343, + 48352, + 48360, + 48368, + 48374, + 48309, + 48317, + 48325, + 48348, + 48356, + 48378 + ], + "evidence_addresses_hex": [ + "H'BCB0", + "H'BCB9", + "H'BCC1", + "H'BCC9", + "H'BCD7", + "H'BCE0", + "H'BCE8", + "H'BCF0", + "H'BCF6", + "H'BCB5", + "H'BCBD", + "H'BCC5", + "H'BCDC", + "H'BCE4", + "H'BCFA" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + }, + { + "kind": "response_builder_candidate", + "buffer_start": 63568, + "buffer_start_hex": "H'F850", + "buffer_end": 63572, + "buffer_end_hex": "H'F854", + "send_call_target": 47654, + "send_call_target_hex": "H'BA26", + "call_address": 48674, + "call_address_hex": "H'BE22", + "writes": [ + { + "address": 63568, + "address_hex": "H'F850", + "instruction_address": 48649, + "instruction_address_hex": "H'BE09", + "source": { + "kind": "register_or_computed", + "operand": "R0" + }, + "instruction": "MOV:G.W R0, @H'F850" + }, + { + "address": 63569, + "address_hex": "H'F851", + "instruction_address": 48649, + "instruction_address_hex": "H'BE09", + "source": { + "kind": "register_or_computed", + "operand": "R0" + }, + "instruction": "MOV:G.W R0, @H'F850" + }, + { + "address": 63570, + "address_hex": "H'F852", + "instruction_address": 48657, + "instruction_address_hex": "H'BE11", + "source": { + "kind": "register_or_computed", + "operand": "R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "address": 63571, + "address_hex": "H'F853", + "instruction_address": 48657, + "instruction_address_hex": "H'BE11", + "source": { + "kind": "register_or_computed", + "operand": "R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "address": 63572, + "address_hex": "H'F854", + "instruction_address": 48665, + "instruction_address_hex": "H'BE19", + "source": { + "kind": "register_or_computed", + "operand": "R0" + }, + "instruction": "MOV:G.W R0, @H'F854" + } + ], + "evidence_addresses": [ + 48649, + 48657, + 48665, + 48674 + ], + "evidence_addresses_hex": [ + "H'BE09", + "H'BE11", + "H'BE19", + "H'BE22" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + }, + { + "kind": "response_builder_candidate", + "buffer_start": 63568, + "buffer_start_hex": "H'F850", + "buffer_end": 63572, + "buffer_end_hex": "H'F854", + "send_call_target": 47654, + "send_call_target_hex": "H'BA26", + "call_address": 48746, + "call_address_hex": "H'BE6A", + "writes": [ + { + "address": 63568, + "address_hex": "H'F850", + "instruction_address": 48717, + "instruction_address_hex": "H'BE4D", + "source": { + "kind": "immediate", + "value": 7, + "value_hex": "H'07" + }, + "instruction": "MOV:G.B #H'07, @H'F850" + }, + { + "address": 63569, + "address_hex": "H'F851", + "instruction_address": 48726, + "instruction_address_hex": "H'BE56", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "evidence_address": 48722, + "evidence_address_hex": "H'BE52", + "instruction": "MOV:G.B @H'F861, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "address": 63570, + "address_hex": "H'F852", + "instruction_address": 48734, + "instruction_address_hex": "H'BE5E", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48730, + "evidence_address_hex": "H'BE5A", + "instruction": "MOV:G.W @H'F862, R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "address": 63571, + "address_hex": "H'F853", + "instruction_address": 48734, + "instruction_address_hex": "H'BE5E", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48730, + "evidence_address_hex": "H'BE5A", + "instruction": "MOV:G.W @H'F862, R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "address": 63572, + "address_hex": "H'F854", + "instruction_address": 48742, + "instruction_address_hex": "H'BE66", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "evidence_address": 48738, + "evidence_address_hex": "H'BE62", + "instruction": "MOV:G.B @H'F864, R0" + }, + "instruction": "MOV:G.B R0, @H'F854" + } + ], + "evidence_addresses": [ + 48717, + 48726, + 48734, + 48742, + 48722, + 48730, + 48738, + 48746 + ], + "evidence_addresses_hex": [ + "H'BE4D", + "H'BE56", + "H'BE5E", + "H'BE66", + "H'BE52", + "H'BE5A", + "H'BE62", + "H'BE6A" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + } + ], + "evidence": [ + { + "kind": "rx_frame_reconstruction_present", + "summary": "serial_reconstruction contains an evidence-supported SCI1 RX frame candidate", + "candidate_id": "sci1_rx_frame_f868_len6_candidate" + }, + { + "kind": "tx_frame_reconstruction_present", + "summary": "serial_reconstruction contains an evidence-supported SCI1 TX frame candidate", + "candidate_id": "sci1_tx_frame_f858_len6_candidate" + }, + { + "kind": "rx0_masked_command_dispatch", + "summary": "RX[0] is read, masked with 0x07, and compared against command values", + "addresses": [ + 48136, + 48140, + 48160, + 48162, + 48164, + 48166, + 48169, + 48171, + 48174, + 48176, + 48197, + 48199, + 48202, + 48204, + 48207, + 48209, + 48212, + 48214 + ], + "addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC20", + "H'BC22", + "H'BC24", + "H'BC26", + "H'BC29", + "H'BC2B", + "H'BC2E", + "H'BC30", + "H'BC45", + "H'BC47", + "H'BC4A", + "H'BC4C", + "H'BC4F", + "H'BC51", + "H'BC54", + "H'BC56" + ] + }, + { + "kind": "responses_stage_f850_f854_before_send", + "summary": "F850-F854 writes are observed before calls to loc_BA26", + "addresses": [ + 47900, + 47904, + 47915, + 47929, + 47935, + 47939, + 48304, + 48313, + 48321, + 48329, + 48309, + 48317, + 48325, + 48333, + 48343, + 48352, + 48360, + 48368, + 48374, + 48348, + 48356, + 48378, + 48649, + 48657, + 48665, + 48674, + 48717, + 48726, + 48734, + 48742, + 48722, + 48730, + 48738, + 48746 + ], + "addresses_hex": [ + "H'BB1C", + "H'BB20", + "H'BB2B", + "H'BB39", + "H'BB3F", + "H'BB43", + "H'BCB0", + "H'BCB9", + "H'BCC1", + "H'BCC9", + "H'BCB5", + "H'BCBD", + "H'BCC5", + "H'BCCD", + "H'BCD7", + "H'BCE0", + "H'BCE8", + "H'BCF0", + "H'BCF6", + "H'BCDC", + "H'BCE4", + "H'BCFA", + "H'BE09", + "H'BE11", + "H'BE19", + "H'BE22", + "H'BE4D", + "H'BE56", + "H'BE5E", + "H'BE66", + "H'BE52", + "H'BE5A", + "H'BE62", + "H'BE6A" + ], + "response_count": 5 + }, + { + "kind": "rx_payload_bytes_read", + "summary": "RX[1..4] are read in the command-processing region", + "addresses": [ + 48092, + 48096, + 48100, + 48104, + 48119, + 48125, + 48153, + 48190, + 48237, + 48267, + 48273, + 48309, + 48317, + 48325, + 48348, + 48356, + 48402, + 48427, + 48433, + 48603, + 48609, + 48722, + 48730, + 48738 + ], + "addresses_hex": [ + "H'BBDC", + "H'BBE0", + "H'BBE4", + "H'BBE8", + "H'BBF7", + "H'BBFD", + "H'BC19", + "H'BC3E", + "H'BC6D", + "H'BC8B", + "H'BC91", + "H'BCB5", + "H'BCBD", + "H'BCC5", + "H'BCDC", + "H'BCE4", + "H'BD12", + "H'BD2B", + "H'BD31", + "H'BDDB", + "H'BDE1", + "H'BE52", + "H'BE5A", + "H'BE62" + ] + } + ] + } + ], + "fields": [ + { + "id": "rx_0", + "kind": "rx_frame_field_candidate", + "offset": 0, + "address": 63584, + "address_hex": "H'F860", + "role_candidate": "command_selector_candidate", + "evidence_addresses": [ + 48088, + 48136, + 48055, + 48160, + 48162, + 48164, + 48197, + 48166, + 48199, + 48169, + 48202, + 48171, + 48140, + 48204, + 48174, + 48207, + 48176, + 48209, + 48212, + 48214 + ], + "evidence_addresses_hex": [ + "H'BBD8", + "H'BC08", + "H'BBB7", + "H'BC20", + "H'BC22", + "H'BC24", + "H'BC45", + "H'BC26", + "H'BC47", + "H'BC29", + "H'BC4A", + "H'BC2B", + "H'BC0C", + "H'BC4C", + "H'BC2E", + "H'BC4F", + "H'BC30", + "H'BC51", + "H'BC54", + "H'BC56" + ], + "read_count": 2, + "write_count": 2, + "confidence": "medium", + "caveat": "RX[0] is masked with 0x07 before command comparisons." + }, + { + "id": "rx_1", + "kind": "rx_frame_field_candidate", + "offset": 1, + "address": 63585, + "address_hex": "H'F861", + "role_candidate": "payload_byte_candidate", + "evidence_addresses": [ + 48092, + 48119, + 48153, + 48190, + 48309, + 48348, + 48722 + ], + "evidence_addresses_hex": [ + "H'BBDC", + "H'BBF7", + "H'BC19", + "H'BC3E", + "H'BCB5", + "H'BCDC", + "H'BE52" + ], + "read_count": 7, + "write_count": 1, + "confidence": "medium", + "caveat": "Role is inferred from reads in command processing." + }, + { + "id": "rx_2", + "kind": "rx_frame_field_candidate", + "offset": 2, + "address": 63586, + "address_hex": "H'F862", + "role_candidate": "payload_byte_candidate", + "evidence_addresses": [ + 48096, + 48125, + 48317, + 48356, + 48730, + 48063 + ], + "evidence_addresses_hex": [ + "H'BBE0", + "H'BBFD", + "H'BCBD", + "H'BCE4", + "H'BE5A", + "H'BBBF" + ], + "read_count": 5, + "write_count": 2, + "confidence": "medium", + "caveat": "Role is inferred from reads in command processing." + }, + { + "id": "rx_3", + "kind": "rx_frame_field_candidate", + "offset": 3, + "address": 63587, + "address_hex": "H'F863", + "role_candidate": "payload_byte_candidate", + "evidence_addresses": [ + 48100, + 48237, + 48267, + 48402, + 48427, + 48603 + ], + "evidence_addresses_hex": [ + "H'BBE4", + "H'BC6D", + "H'BC8B", + "H'BD12", + "H'BD2B", + "H'BDDB" + ], + "read_count": 6, + "write_count": 1, + "confidence": "medium", + "caveat": "Role is inferred from reads in command processing." + }, + { + "id": "rx_4", + "kind": "rx_frame_field_candidate", + "offset": 4, + "address": 63588, + "address_hex": "H'F864", + "role_candidate": "payload_byte_candidate", + "evidence_addresses": [ + 48104, + 48273, + 48325, + 48433, + 48609, + 48738, + 48071, + 48253 + ], + "evidence_addresses_hex": [ + "H'BBE8", + "H'BC91", + "H'BCC5", + "H'BD31", + "H'BDE1", + "H'BE62", + "H'BBC7", + "H'BC7D" + ], + "read_count": 6, + "write_count": 3, + "confidence": "medium", + "caveat": "Role is inferred from reads in command processing." + }, + { + "id": "rx_5", + "kind": "rx_frame_field_candidate", + "offset": 5, + "address": 63589, + "address_hex": "H'F865", + "role_candidate": "checksum_byte_candidate", + "evidence_addresses": [ + 48108 + ], + "evidence_addresses_hex": [ + "H'BBEC" + ], + "read_count": 1, + "write_count": 0, + "confidence": "medium", + "caveat": "RX[5] is compared with a checksum over RX[0..4]." + }, + { + "id": "tx_staging_0", + "kind": "tx_staging_field_candidate", + "offset": 0, + "address": 63568, + "address_hex": "H'F850", + "role_candidate": "response_staging_byte_candidate", + "evidence_addresses": [ + 47900, + 48304, + 48343, + 48649, + 48717 + ], + "evidence_addresses_hex": [ + "H'BB1C", + "H'BCB0", + "H'BCD7", + "H'BE09", + "H'BE4D" + ], + "write_count": 5, + "confidence": "medium", + "caveat": "This byte is staged before calls to loc_BA26; the analyzer does not infer a stable field name beyond response position." + }, + { + "id": "tx_staging_1", + "kind": "tx_staging_field_candidate", + "offset": 1, + "address": 63569, + "address_hex": "H'F851", + "role_candidate": "response_staging_byte_candidate", + "evidence_addresses": [ + 47915, + 48313, + 48352, + 48360, + 48649, + 48726 + ], + "evidence_addresses_hex": [ + "H'BB2B", + "H'BCB9", + "H'BCE0", + "H'BCE8", + "H'BE09", + "H'BE56" + ], + "write_count": 6, + "confidence": "medium", + "caveat": "This byte is staged before calls to loc_BA26; the analyzer does not infer a stable field name beyond response position." + }, + { + "id": "tx_staging_2", + "kind": "tx_staging_field_candidate", + "offset": 2, + "address": 63570, + "address_hex": "H'F852", + "role_candidate": "response_staging_byte_candidate", + "evidence_addresses": [ + 47904, + 48321, + 48657, + 48734 + ], + "evidence_addresses_hex": [ + "H'BB20", + "H'BCC1", + "H'BE11", + "H'BE5E" + ], + "write_count": 4, + "confidence": "medium", + "caveat": "This byte is staged before calls to loc_BA26; the analyzer does not infer a stable field name beyond response position." + }, + { + "id": "tx_staging_3", + "kind": "tx_staging_field_candidate", + "offset": 3, + "address": 63571, + "address_hex": "H'F853", + "role_candidate": "response_staging_byte_candidate", + "evidence_addresses": [ + 47935, + 48321, + 48374, + 48657, + 48734 + ], + "evidence_addresses_hex": [ + "H'BB3F", + "H'BCC1", + "H'BCF6", + "H'BE11", + "H'BE5E" + ], + "write_count": 5, + "confidence": "medium", + "caveat": "This byte is staged before calls to loc_BA26; the analyzer does not infer a stable field name beyond response position." + }, + { + "id": "tx_staging_4", + "kind": "tx_staging_field_candidate", + "offset": 4, + "address": 63572, + "address_hex": "H'F854", + "role_candidate": "response_staging_byte_candidate", + "evidence_addresses": [ + 47929, + 48329, + 48368, + 48665, + 48742 + ], + "evidence_addresses_hex": [ + "H'BB39", + "H'BCC9", + "H'BCF0", + "H'BE19", + "H'BE66" + ], + "write_count": 5, + "confidence": "medium", + "caveat": "This byte is staged before calls to loc_BA26; the analyzer does not infer a stable field name beyond response position." + } + ], + "command_dispatch": { + "kind": "command_dispatch_candidate", + "selector": "rx0_low3_bits", + "field": "command_low3", + "rx_offset": 0, + "rx_address": 63584, + "rx_address_hex": "H'F860", + "source_address": 63584, + "source_address_hex": "H'F860", + "source_field": "byte0", + "mask": 7, + "mask_hex": "H'0007", + "selector_register": "R0", + "read_address": 48136, + "read_address_hex": "H'BC08", + "mask_address": 48140, + "mask_address_hex": "H'BC0C", + "command_values": [ + 0, + 1, + 2, + 4, + 5, + 6, + 7 + ], + "command_values_hex": [ + "H'00", + "H'01", + "H'02", + "H'04", + "H'05", + "H'06", + "H'07" + ], + "comparisons": [ + { + "command_value": 0, + "command_value_hex": "H'00", + "compare_address": 48160, + "compare_address_hex": "H'BC20", + "branch_address": 48162, + "branch_address_hex": "H'BC22", + "handler_start": 48233, + "handler_start_hex": "H'BC69", + "evidence_addresses": [ + 48160, + 48162 + ], + "evidence_addresses_hex": [ + "H'BC20", + "H'BC22" + ], + "handler_start_index": 2147 + }, + { + "command_value": 1, + "command_value_hex": "H'01", + "compare_address": 48164, + "compare_address_hex": "H'BC24", + "branch_address": 48166, + "branch_address_hex": "H'BC26", + "handler_start": 48343, + "handler_start_hex": "H'BCD7", + "evidence_addresses": [ + 48164, + 48166 + ], + "evidence_addresses_hex": [ + "H'BC24", + "H'BC26" + ], + "handler_start_index": 2179 + }, + { + "command_value": 2, + "command_value_hex": "H'02", + "compare_address": 48169, + "compare_address_hex": "H'BC29", + "branch_address": 48171, + "branch_address_hex": "H'BC2B", + "handler_start": 48388, + "handler_start_hex": "H'BD04", + "evidence_addresses": [ + 48169, + 48171 + ], + "evidence_addresses_hex": [ + "H'BC29", + "H'BC2B" + ], + "handler_start_index": 2191 + }, + { + "command_value": 7, + "command_value_hex": "H'07", + "compare_address": 48174, + "compare_address_hex": "H'BC2E", + "branch_address": 48176, + "branch_address_hex": "H'BC30", + "handler_start": 48645, + "handler_start_hex": "H'BE05", + "evidence_addresses": [ + 48174, + 48176 + ], + "evidence_addresses_hex": [ + "H'BC2E", + "H'BC30" + ], + "handler_start_index": 2275 + }, + { + "command_value": 4, + "command_value_hex": "H'04", + "compare_address": 48197, + "compare_address_hex": "H'BC45", + "branch_address": 48199, + "branch_address_hex": "H'BC47", + "handler_start": 48398, + "handler_start_hex": "H'BD0E", + "evidence_addresses": [ + 48197, + 48199 + ], + "evidence_addresses_hex": [ + "H'BC45", + "H'BC47" + ], + "handler_start_index": 2194 + }, + { + "command_value": 5, + "command_value_hex": "H'05", + "compare_address": 48202, + "compare_address_hex": "H'BC4A", + "branch_address": 48204, + "branch_address_hex": "H'BC4C", + "handler_start": 48512, + "handler_start_hex": "H'BD80", + "evidence_addresses": [ + 48202, + 48204 + ], + "evidence_addresses_hex": [ + "H'BC4A", + "H'BC4C" + ], + "handler_start_index": 2231 + }, + { + "command_value": 6, + "command_value_hex": "H'06", + "compare_address": 48207, + "compare_address_hex": "H'BC4F", + "branch_address": 48209, + "branch_address_hex": "H'BC51", + "handler_start": 48603, + "handler_start_hex": "H'BDDB", + "evidence_addresses": [ + 48207, + 48209 + ], + "evidence_addresses_hex": [ + "H'BC4F", + "H'BC51" + ], + "handler_start_index": 2263 + }, + { + "command_value": 7, + "command_value_hex": "H'07", + "compare_address": 48212, + "compare_address_hex": "H'BC54", + "branch_address": 48214, + "branch_address_hex": "H'BC56", + "handler_start": 48645, + "handler_start_hex": "H'BE05", + "evidence_addresses": [ + 48212, + 48214 + ], + "evidence_addresses_hex": [ + "H'BC54", + "H'BC56" + ], + "handler_start_index": 2275 + } + ], + "cases": [ + { + "value": 0, + "value_hex": "H'00", + "target": 48233, + "target_hex": "H'BC69", + "compare_address": 48160, + "branch_address": 48162 + }, + { + "value": 1, + "value_hex": "H'01", + "target": 48343, + "target_hex": "H'BCD7", + "compare_address": 48164, + "branch_address": 48166 + }, + { + "value": 2, + "value_hex": "H'02", + "target": 48388, + "target_hex": "H'BD04", + "compare_address": 48169, + "branch_address": 48171 + }, + { + "value": 7, + "value_hex": "H'07", + "target": 48645, + "target_hex": "H'BE05", + "compare_address": 48174, + "branch_address": 48176 + }, + { + "value": 4, + "value_hex": "H'04", + "target": 48398, + "target_hex": "H'BD0E", + "compare_address": 48197, + "branch_address": 48199 + }, + { + "value": 5, + "value_hex": "H'05", + "target": 48512, + "target_hex": "H'BD80", + "compare_address": 48202, + "branch_address": 48204 + }, + { + "value": 6, + "value_hex": "H'06", + "target": 48603, + "target_hex": "H'BDDB", + "compare_address": 48207, + "branch_address": 48209 + }, + { + "value": 7, + "value_hex": "H'07", + "target": 48645, + "target_hex": "H'BE05", + "compare_address": 48212, + "branch_address": 48214 + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48160, + 48162, + 48164, + 48166, + 48169, + 48171, + 48174, + 48176, + 48197, + 48199, + 48202, + 48204, + 48207, + 48209, + 48212, + 48214 + ], + "confidence": "medium", + "caveat": "Dispatch is inferred from a read of RX[0], an AND 0x07 mask, and nearby compare/branch pairs. Gating state around the dispatch may affect reachability.", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC20", + "H'BC22", + "H'BC24", + "H'BC26", + "H'BC29", + "H'BC2B", + "H'BC2E", + "H'BC30", + "H'BC45", + "H'BC47", + "H'BC4A", + "H'BC4C", + "H'BC4F", + "H'BC51", + "H'BC54", + "H'BC56" + ] + }, + "commands": [ + { + "kind": "command_candidate", + "command_value": 0, + "command_value_hex": "H'00", + "name_candidate": "set_value_acked", + "summary": "candidate write of RX[3:4] into primary/current tables, followed by a response", + "handler_alternatives": [ + { + "handler_start": 48233, + "handler_start_hex": "H'BC69", + "handler_end": 48340, + "handler_end_hex": "H'BCD4", + "dispatch_compare_address": 48160, + "dispatch_compare_address_hex": "H'BC20", + "dispatch_branch_address": 48162, + "dispatch_branch_address_hex": "H'BC22" + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48160, + 48162, + 48304, + 48313, + 48321, + 48329, + 48309, + 48317, + 48325, + 48333 + ], + "response_candidates": [ + "response_at_BCCD" + ], + "rx_reads": [ + { + "instruction_address": 48237, + "instruction_address_hex": "H'BC6D", + "rx_offset": 3, + "rx_address": 63587, + "rx_address_hex": "H'F863", + "instruction": "MOV:G.B @H'F863, R0" + }, + { + "instruction_address": 48267, + "instruction_address_hex": "H'BC8B", + "rx_offset": 3, + "rx_address": 63587, + "rx_address_hex": "H'F863", + "instruction": "MOV:G.B @H'F863, R0" + }, + { + "instruction_address": 48273, + "instruction_address_hex": "H'BC91", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "instruction": "MOV:G.B @H'F864, R0" + }, + { + "instruction_address": 48309, + "instruction_address_hex": "H'BCB5", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "instruction": "MOV:G.B @H'F861, R0" + }, + { + "instruction_address": 48317, + "instruction_address_hex": "H'BCBD", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "instruction": "MOV:G.W @H'F862, R0" + }, + { + "instruction_address": 48325, + "instruction_address_hex": "H'BCC5", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "instruction": "MOV:G.B @H'F864, R0" + } + ], + "confidence": "medium", + "caveat": "Command value and handler range are inferred from compare/BEQ dispatch. No command name or intent is asserted.", + "handler_start": 48233, + "handler_start_hex": "H'BC69", + "handler_end": 48340, + "handler_end_hex": "H'BCD4", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC20", + "H'BC22", + "H'BCB0", + "H'BCB9", + "H'BCC1", + "H'BCC9", + "H'BCB5", + "H'BCBD", + "H'BCC5", + "H'BCCD" + ] + }, + { + "kind": "command_candidate", + "command_value": 1, + "command_value_hex": "H'01", + "name_candidate": "read_value", + "summary": "candidate read from the primary table, followed by a response carrying the value", + "handler_alternatives": [ + { + "handler_start": 48343, + "handler_start_hex": "H'BCD7", + "handler_end": 48385, + "handler_end_hex": "H'BD01", + "dispatch_compare_address": 48164, + "dispatch_compare_address_hex": "H'BC24", + "dispatch_branch_address": 48166, + "dispatch_branch_address_hex": "H'BC26" + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48164, + 48166, + 48304, + 48313, + 48321, + 48329, + 48343, + 48352, + 48360, + 48368, + 48374, + 48309, + 48317, + 48325, + 48348, + 48356, + 48378 + ], + "response_candidates": [ + "response_at_BCFA" + ], + "rx_reads": [ + { + "instruction_address": 48348, + "instruction_address_hex": "H'BCDC", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "instruction": "MOV:G.B @H'F861, R0" + }, + { + "instruction_address": 48356, + "instruction_address_hex": "H'BCE4", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "instruction": "MOV:G.B @H'F862, R0" + } + ], + "confidence": "medium", + "caveat": "Command value and handler range are inferred from compare/BEQ dispatch. No command name or intent is asserted.", + "handler_start": 48343, + "handler_start_hex": "H'BCD7", + "handler_end": 48385, + "handler_end_hex": "H'BD01", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC24", + "H'BC26", + "H'BCB0", + "H'BCB9", + "H'BCC1", + "H'BCC9", + "H'BCD7", + "H'BCE0", + "H'BCE8", + "H'BCF0", + "H'BCF6", + "H'BCB5", + "H'BCBD", + "H'BCC5", + "H'BCDC", + "H'BCE4", + "H'BCFA" + ] + }, + { + "kind": "command_candidate", + "command_value": 2, + "command_value_hex": "H'02", + "name_candidate": "clear_or_abort", + "summary": "candidate clear/abort path with no immediate response builder", + "handler_alternatives": [ + { + "handler_start": 48388, + "handler_start_hex": "H'BD04", + "handler_end": 48395, + "handler_end_hex": "H'BD0B", + "dispatch_compare_address": 48169, + "dispatch_compare_address_hex": "H'BC29", + "dispatch_branch_address": 48171, + "dispatch_branch_address_hex": "H'BC2B" + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48169, + 48171 + ], + "response_candidates": [], + "rx_reads": [], + "confidence": "medium", + "caveat": "Command value and handler range are inferred from compare/BEQ dispatch. No command name or intent is asserted.", + "handler_start": 48388, + "handler_start_hex": "H'BD04", + "handler_end": 48395, + "handler_end_hex": "H'BD0B", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC29", + "H'BC2B" + ] + }, + { + "kind": "command_candidate", + "command_value": 4, + "command_value_hex": "H'04", + "name_candidate": "set_value_no_immediate_reply", + "summary": "candidate write/update path that stores a value without an immediate serial response", + "handler_alternatives": [ + { + "handler_start": 48398, + "handler_start_hex": "H'BD0E", + "handler_end": 48509, + "handler_end_hex": "H'BD7D", + "dispatch_compare_address": 48197, + "dispatch_compare_address_hex": "H'BC45", + "dispatch_branch_address": 48199, + "dispatch_branch_address_hex": "H'BC47" + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48197, + 48199 + ], + "response_candidates": [], + "rx_reads": [ + { + "instruction_address": 48402, + "instruction_address_hex": "H'BD12", + "rx_offset": 3, + "rx_address": 63587, + "rx_address_hex": "H'F863", + "instruction": "MOV:G.B @H'F863, R0" + }, + { + "instruction_address": 48427, + "instruction_address_hex": "H'BD2B", + "rx_offset": 3, + "rx_address": 63587, + "rx_address_hex": "H'F863", + "instruction": "MOV:G.B @H'F863, R0" + }, + { + "instruction_address": 48433, + "instruction_address_hex": "H'BD31", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "instruction": "MOV:G.B @H'F864, R0" + } + ], + "confidence": "medium", + "caveat": "Command value and handler range are inferred from compare/BEQ dispatch. No command name or intent is asserted.", + "handler_start": 48398, + "handler_start_hex": "H'BD0E", + "handler_end": 48509, + "handler_end_hex": "H'BD7D", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC45", + "H'BC47" + ] + }, + { + "kind": "command_candidate", + "command_value": 5, + "command_value_hex": "H'05", + "name_candidate": "ack_or_clear_pending", + "summary": "candidate pending/event acknowledgement path", + "handler_alternatives": [ + { + "handler_start": 48512, + "handler_start_hex": "H'BD80", + "handler_end": 48600, + "handler_end_hex": "H'BDD8", + "dispatch_compare_address": 48202, + "dispatch_compare_address_hex": "H'BC4A", + "dispatch_branch_address": 48204, + "dispatch_branch_address_hex": "H'BC4C" + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48202, + 48204 + ], + "response_candidates": [], + "rx_reads": [], + "confidence": "medium", + "caveat": "Command value and handler range are inferred from compare/BEQ dispatch. No command name or intent is asserted.", + "handler_start": 48512, + "handler_start_hex": "H'BD80", + "handler_end": 48600, + "handler_end_hex": "H'BDD8", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC4A", + "H'BC4C" + ] + }, + { + "kind": "command_candidate", + "command_value": 6, + "command_value_hex": "H'06", + "name_candidate": "set_secondary_value", + "summary": "candidate secondary-table value write path", + "handler_alternatives": [ + { + "handler_start": 48603, + "handler_start_hex": "H'BDDB", + "handler_end": 48643, + "handler_end_hex": "H'BE03", + "dispatch_compare_address": 48207, + "dispatch_compare_address_hex": "H'BC4F", + "dispatch_branch_address": 48209, + "dispatch_branch_address_hex": "H'BC51" + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48207, + 48209 + ], + "response_candidates": [], + "rx_reads": [ + { + "instruction_address": 48603, + "instruction_address_hex": "H'BDDB", + "rx_offset": 3, + "rx_address": 63587, + "rx_address_hex": "H'F863", + "instruction": "MOV:G.B @H'F863, R0" + }, + { + "instruction_address": 48609, + "instruction_address_hex": "H'BDE1", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "instruction": "MOV:G.B @H'F864, R0" + } + ], + "confidence": "medium", + "caveat": "Command value and handler range are inferred from compare/BEQ dispatch. No command name or intent is asserted.", + "handler_start": 48603, + "handler_start_hex": "H'BDDB", + "handler_end": 48643, + "handler_end_hex": "H'BE03", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC4F", + "H'BC51" + ] + }, + { + "kind": "command_candidate", + "command_value": 7, + "command_value_hex": "H'07", + "name_candidate": "retransmit_or_error_reply", + "summary": "candidate retransmit/NAK-style path; error handling also builds command 0x07 responses", + "handler_alternatives": [ + { + "handler_start": 48645, + "handler_start_hex": "H'BE05", + "handler_end": 48677, + "handler_end_hex": "H'BE25", + "dispatch_compare_address": 48174, + "dispatch_compare_address_hex": "H'BC2E", + "dispatch_branch_address": 48176, + "dispatch_branch_address_hex": "H'BC30" + }, + { + "handler_start": 48645, + "handler_start_hex": "H'BE05", + "handler_end": 48677, + "handler_end_hex": "H'BE25", + "dispatch_compare_address": 48212, + "dispatch_compare_address_hex": "H'BC54", + "dispatch_branch_address": 48214, + "dispatch_branch_address_hex": "H'BC56" + } + ], + "evidence_addresses": [ + 48136, + 48140, + 48174, + 48176, + 48212, + 48214, + 48649, + 48657, + 48665, + 48674 + ], + "response_candidates": [ + "response_at_BE22" + ], + "rx_reads": [], + "confidence": "medium", + "caveat": "Command value and handler range are inferred from compare/BEQ dispatch. No command name or intent is asserted.", + "handler_start": 48645, + "handler_start_hex": "H'BE05", + "handler_end": 48677, + "handler_end_hex": "H'BE25", + "evidence_addresses_hex": [ + "H'BC08", + "H'BC0C", + "H'BC2E", + "H'BC30", + "H'BC54", + "H'BC56", + "H'BE09", + "H'BE11", + "H'BE19", + "H'BE22" + ] + } + ], + "response_candidates": [ + { + "id": "response_at_BB43", + "kind": "response_staging_candidate", + "call_address": 47939, + "call_address_hex": "H'BB43", + "send_builder": "loc_BA26", + "send_builder_address": 47654, + "send_builder_address_hex": "H'BA26", + "window_start": 47870, + "window_start_hex": "H'BAFE", + "writes": [ + { + "instruction_address": 47900, + "instruction_address_hex": "H'BB1C", + "addresses": [ + 63568 + ], + "addresses_hex": [ + "H'F850" + ], + "source_operand": "R1", + "source": { + "kind": "register_or_computed", + "operand": "R1" + }, + "instruction": "MOV:G.B R1, @H'F850" + }, + { + "instruction_address": 47904, + "instruction_address_hex": "H'BB20", + "addresses": [ + 63570 + ], + "addresses_hex": [ + "H'F852" + ], + "source_operand": "R5", + "source": { + "kind": "register_or_computed", + "operand": "R5" + }, + "instruction": "MOV:G.B R5, @H'F852" + }, + { + "instruction_address": 47915, + "instruction_address_hex": "H'BB2B", + "addresses": [ + 63569 + ], + "addresses_hex": [ + "H'F851" + ], + "source_operand": "R5", + "source": { + "kind": "register_or_computed", + "operand": "R5" + }, + "instruction": "MOV:G.B R5, @H'F851" + }, + { + "instruction_address": 47929, + "instruction_address_hex": "H'BB39", + "addresses": [ + 63572 + ], + "addresses_hex": [ + "H'F854" + ], + "source_operand": "R4", + "source": { + "kind": "register_or_computed", + "operand": "R4" + }, + "instruction": "MOV:G.B R4, @H'F854" + }, + { + "instruction_address": 47935, + "instruction_address_hex": "H'BB3F", + "addresses": [ + 63571 + ], + "addresses_hex": [ + "H'F853" + ], + "source_operand": "R4", + "source": { + "kind": "register_or_computed", + "operand": "R4" + }, + "instruction": "MOV:G.B R4, @H'F853" + } + ], + "rx_reads": [], + "evidence_addresses": [ + 47900, + 47904, + 47915, + 47929, + 47935, + 47939 + ], + "evidence_addresses_hex": [ + "H'BB1C", + "H'BB20", + "H'BB2B", + "H'BB39", + "H'BB3F", + "H'BB43" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + }, + { + "id": "response_at_BCCD", + "kind": "response_staging_candidate", + "call_address": 48333, + "call_address_hex": "H'BCCD", + "send_builder": "loc_BA26", + "send_builder_address": 47654, + "send_builder_address_hex": "H'BA26", + "window_start": 48297, + "window_start_hex": "H'BCA9", + "writes": [ + { + "instruction_address": 48304, + "instruction_address_hex": "H'BCB0", + "addresses": [ + 63568 + ], + "addresses_hex": [ + "H'F850" + ], + "source_operand": "#H'04", + "source": { + "kind": "immediate", + "value": 4, + "value_hex": "H'04" + }, + "instruction": "MOV:G.B #H'04, @H'F850" + }, + { + "instruction_address": 48313, + "instruction_address_hex": "H'BCB9", + "addresses": [ + 63569 + ], + "addresses_hex": [ + "H'F851" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "evidence_address": 48309, + "evidence_address_hex": "H'BCB5", + "instruction": "MOV:G.B @H'F861, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "instruction_address": 48321, + "instruction_address_hex": "H'BCC1", + "addresses": [ + 63570, + 63571 + ], + "addresses_hex": [ + "H'F852", + "H'F853" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48317, + "evidence_address_hex": "H'BCBD", + "instruction": "MOV:G.W @H'F862, R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "instruction_address": 48329, + "instruction_address_hex": "H'BCC9", + "addresses": [ + 63572 + ], + "addresses_hex": [ + "H'F854" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "evidence_address": 48325, + "evidence_address_hex": "H'BCC5", + "instruction": "MOV:G.B @H'F864, R0" + }, + "instruction": "MOV:G.B R0, @H'F854" + } + ], + "rx_reads": [ + { + "instruction_address": 48309, + "instruction_address_hex": "H'BCB5", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "instruction": "MOV:G.B @H'F861, R0" + }, + { + "instruction_address": 48317, + "instruction_address_hex": "H'BCBD", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "instruction": "MOV:G.W @H'F862, R0" + }, + { + "instruction_address": 48325, + "instruction_address_hex": "H'BCC5", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "instruction": "MOV:G.B @H'F864, R0" + } + ], + "evidence_addresses": [ + 48304, + 48313, + 48321, + 48329, + 48309, + 48317, + 48325, + 48333 + ], + "evidence_addresses_hex": [ + "H'BCB0", + "H'BCB9", + "H'BCC1", + "H'BCC9", + "H'BCB5", + "H'BCBD", + "H'BCC5", + "H'BCCD" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + }, + { + "id": "response_at_BCFA", + "kind": "response_staging_candidate", + "call_address": 48378, + "call_address_hex": "H'BCFA", + "send_builder": "loc_BA26", + "send_builder_address": 47654, + "send_builder_address_hex": "H'BA26", + "window_start": 48297, + "window_start_hex": "H'BCA9", + "writes": [ + { + "instruction_address": 48304, + "instruction_address_hex": "H'BCB0", + "addresses": [ + 63568 + ], + "addresses_hex": [ + "H'F850" + ], + "source_operand": "#H'04", + "source": { + "kind": "immediate", + "value": 4, + "value_hex": "H'04" + }, + "instruction": "MOV:G.B #H'04, @H'F850" + }, + { + "instruction_address": 48313, + "instruction_address_hex": "H'BCB9", + "addresses": [ + 63569 + ], + "addresses_hex": [ + "H'F851" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "evidence_address": 48309, + "evidence_address_hex": "H'BCB5", + "instruction": "MOV:G.B @H'F861, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "instruction_address": 48321, + "instruction_address_hex": "H'BCC1", + "addresses": [ + 63570, + 63571 + ], + "addresses_hex": [ + "H'F852", + "H'F853" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48317, + "evidence_address_hex": "H'BCBD", + "instruction": "MOV:G.W @H'F862, R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "instruction_address": 48329, + "instruction_address_hex": "H'BCC9", + "addresses": [ + 63572 + ], + "addresses_hex": [ + "H'F854" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "evidence_address": 48325, + "evidence_address_hex": "H'BCC5", + "instruction": "MOV:G.B @H'F864, R0" + }, + "instruction": "MOV:G.B R0, @H'F854" + }, + { + "instruction_address": 48343, + "instruction_address_hex": "H'BCD7", + "addresses": [ + 63568 + ], + "addresses_hex": [ + "H'F850" + ], + "source_operand": "#H'04", + "source": { + "kind": "immediate", + "value": 4, + "value_hex": "H'04" + }, + "instruction": "MOV:G.B #H'04, @H'F850" + }, + { + "instruction_address": 48352, + "instruction_address_hex": "H'BCE0", + "addresses": [ + 63569 + ], + "addresses_hex": [ + "H'F851" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "evidence_address": 48348, + "evidence_address_hex": "H'BCDC", + "instruction": "MOV:G.B @H'F861, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "instruction_address": 48360, + "instruction_address_hex": "H'BCE8", + "addresses": [ + 63569 + ], + "addresses_hex": [ + "H'F851" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48356, + "evidence_address_hex": "H'BCE4", + "instruction": "MOV:G.B @H'F862, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "instruction_address": 48368, + "instruction_address_hex": "H'BCF0", + "addresses": [ + 63572 + ], + "addresses_hex": [ + "H'F854" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48356, + "evidence_address_hex": "H'BCE4", + "instruction": "MOV:G.B @H'F862, R0" + }, + "instruction": "MOV:G.B R0, @H'F854" + }, + { + "instruction_address": 48374, + "instruction_address_hex": "H'BCF6", + "addresses": [ + 63571 + ], + "addresses_hex": [ + "H'F853" + ], + "source_operand": "R0", + "source": { + "kind": "register_or_computed", + "operand": "R0" + }, + "instruction": "MOV:G.B R0, @H'F853" + } + ], + "rx_reads": [ + { + "instruction_address": 48309, + "instruction_address_hex": "H'BCB5", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "instruction": "MOV:G.B @H'F861, R0" + }, + { + "instruction_address": 48317, + "instruction_address_hex": "H'BCBD", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "instruction": "MOV:G.W @H'F862, R0" + }, + { + "instruction_address": 48325, + "instruction_address_hex": "H'BCC5", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "instruction": "MOV:G.B @H'F864, R0" + }, + { + "instruction_address": 48348, + "instruction_address_hex": "H'BCDC", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "instruction": "MOV:G.B @H'F861, R0" + }, + { + "instruction_address": 48356, + "instruction_address_hex": "H'BCE4", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "instruction": "MOV:G.B @H'F862, R0" + } + ], + "evidence_addresses": [ + 48304, + 48313, + 48321, + 48329, + 48343, + 48352, + 48360, + 48368, + 48374, + 48309, + 48317, + 48325, + 48348, + 48356, + 48378 + ], + "evidence_addresses_hex": [ + "H'BCB0", + "H'BCB9", + "H'BCC1", + "H'BCC9", + "H'BCD7", + "H'BCE0", + "H'BCE8", + "H'BCF0", + "H'BCF6", + "H'BCB5", + "H'BCBD", + "H'BCC5", + "H'BCDC", + "H'BCE4", + "H'BCFA" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + }, + { + "id": "response_at_BE22", + "kind": "response_staging_candidate", + "call_address": 48674, + "call_address_hex": "H'BE22", + "send_builder": "loc_BA26", + "send_builder_address": 47654, + "send_builder_address_hex": "H'BA26", + "window_start": 48627, + "window_start_hex": "H'BDF3", + "writes": [ + { + "instruction_address": 48649, + "instruction_address_hex": "H'BE09", + "addresses": [ + 63568, + 63569 + ], + "addresses_hex": [ + "H'F850", + "H'F851" + ], + "source_operand": "R0", + "source": { + "kind": "register_or_computed", + "operand": "R0" + }, + "instruction": "MOV:G.W R0, @H'F850" + }, + { + "instruction_address": 48657, + "instruction_address_hex": "H'BE11", + "addresses": [ + 63570, + 63571 + ], + "addresses_hex": [ + "H'F852", + "H'F853" + ], + "source_operand": "R0", + "source": { + "kind": "register_or_computed", + "operand": "R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "instruction_address": 48665, + "instruction_address_hex": "H'BE19", + "addresses": [ + 63572 + ], + "addresses_hex": [ + "H'F854" + ], + "source_operand": "R0", + "source": { + "kind": "register_or_computed", + "operand": "R0" + }, + "instruction": "MOV:G.W R0, @H'F854" + } + ], + "rx_reads": [], + "evidence_addresses": [ + 48649, + 48657, + 48665, + 48674 + ], + "evidence_addresses_hex": [ + "H'BE09", + "H'BE11", + "H'BE19", + "H'BE22" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + }, + { + "id": "response_at_BE6A", + "kind": "response_staging_candidate", + "call_address": 48746, + "call_address_hex": "H'BE6A", + "send_builder": "loc_BA26", + "send_builder_address": 47654, + "send_builder_address_hex": "H'BA26", + "window_start": 48702, + "window_start_hex": "H'BE3E", + "writes": [ + { + "instruction_address": 48717, + "instruction_address_hex": "H'BE4D", + "addresses": [ + 63568 + ], + "addresses_hex": [ + "H'F850" + ], + "source_operand": "#H'07", + "source": { + "kind": "immediate", + "value": 7, + "value_hex": "H'07" + }, + "instruction": "MOV:G.B #H'07, @H'F850" + }, + { + "instruction_address": 48726, + "instruction_address_hex": "H'BE56", + "addresses": [ + 63569 + ], + "addresses_hex": [ + "H'F851" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "evidence_address": 48722, + "evidence_address_hex": "H'BE52", + "instruction": "MOV:G.B @H'F861, R0" + }, + "instruction": "MOV:G.B R0, @H'F851" + }, + { + "instruction_address": 48734, + "instruction_address_hex": "H'BE5E", + "addresses": [ + 63570, + 63571 + ], + "addresses_hex": [ + "H'F852", + "H'F853" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "evidence_address": 48730, + "evidence_address_hex": "H'BE5A", + "instruction": "MOV:G.W @H'F862, R0" + }, + "instruction": "MOV:G.W R0, @H'F852" + }, + { + "instruction_address": 48742, + "instruction_address_hex": "H'BE66", + "addresses": [ + 63572 + ], + "addresses_hex": [ + "H'F854" + ], + "source_operand": "R0", + "source": { + "kind": "rx_frame_byte", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "evidence_address": 48738, + "evidence_address_hex": "H'BE62", + "instruction": "MOV:G.B @H'F864, R0" + }, + "instruction": "MOV:G.B R0, @H'F854" + } + ], + "rx_reads": [ + { + "instruction_address": 48722, + "instruction_address_hex": "H'BE52", + "rx_offset": 1, + "rx_address": 63585, + "rx_address_hex": "H'F861", + "instruction": "MOV:G.B @H'F861, R0" + }, + { + "instruction_address": 48730, + "instruction_address_hex": "H'BE5A", + "rx_offset": 2, + "rx_address": 63586, + "rx_address_hex": "H'F862", + "instruction": "MOV:G.W @H'F862, R0" + }, + { + "instruction_address": 48738, + "instruction_address_hex": "H'BE62", + "rx_offset": 4, + "rx_address": 63588, + "rx_address_hex": "H'F864", + "instruction": "MOV:G.B @H'F864, R0" + } + ], + "evidence_addresses": [ + 48717, + 48726, + 48734, + 48742, + 48722, + 48730, + 48738, + 48746 + ], + "evidence_addresses_hex": [ + "H'BE4D", + "H'BE56", + "H'BE5E", + "H'BE66", + "H'BE52", + "H'BE5A", + "H'BE62", + "H'BE6A" + ], + "confidence": "medium", + "caveat": "Response candidate means F850-F854 are written shortly before loc_BA26. The analyzer does not prove every byte is meaningful for every path." + } + ], + "send_builder": { + "kind": "tx_send_builder_candidate", + "label": "loc_BA26", + "address": 47654, + "address_hex": "H'BA26", + "staging_buffer_start": 63568, + "staging_buffer_start_hex": "H'F850", + "staging_buffer_end": 63572, + "staging_buffer_end_hex": "H'F854", + "tx_frame_start": 63576, + "tx_frame_start_hex": "H'F858", + "tx_frame_end": 63581, + "tx_frame_end_hex": "H'F85D", + "checksum_address": 63581, + "checksum_address_hex": "H'F85D", + "checksum_seed": 90, + "checksum_seed_hex": "H'005A", + "staging_to_frame_copies": [], + "response_call_addresses": [ + 47939, + 48333, + 48378, + 48674, + 48746 + ], + "response_call_addresses_hex": [ + "H'BB43", + "H'BCCD", + "H'BCFA", + "H'BE22", + "H'BE6A" + ], + "serial_reconstruction_candidate_id": "sci1_tx_frame_f858_len6_candidate", + "evidence_addresses": [ + 47674, + 47682, + 47690, + 47696, + 47700, + 47704, + 47708, + 47712, + 47716, + 47726, + 47939, + 48333, + 48378, + 48674, + 48746 + ], + "evidence_addresses_hex": [ + "H'BA3A", + "H'BA42", + "H'BA4A", + "H'BA50", + "H'BA54", + "H'BA58", + "H'BA5C", + "H'BA60", + "H'BA64", + "H'BA6E", + "H'BB43", + "H'BCCD", + "H'BCFA", + "H'BE22", + "H'BE6A" + ], + "confidence": "low", + "caveat": "loc_BA26 is treated as a send builder because it copies F850-F854 into the evidence-supported TX frame and then starts SCI1 transmission." + }, + "confidence": "medium-high", + "confidence_score": 0.9, + "caveat": "Semantic names are candidates only. The analyzer reports byte roles, command values, dispatch targets, and response staging patterns observed in code; it does not prove source-level intent or protocol documentation." + } } \ No newline at end of file diff --git a/build/rom_serial_pseudocode.c b/build/rom_serial_pseudocode.c index 2a113dc..85ffa8b 100644 --- a/build/rom_serial_pseudocode.c +++ b/build/rom_serial_pseudocode.c @@ -89,6 +89,111 @@ extern volatile u8 MEM8[0x10000]; #define RX_INTERBYTE_TIMEOUT MEM8[0xF9C1u] #define RX_COMPLETE_TIMER MEM8[0xF9C5u] +/* Candidate Protocol Semantics + * confidence: medium-high (0.9) + * caveat: Semantic names are candidates only. The analyzer reports byte roles, command values, dispatch targets, and response staging patterns observed in code; it does not prove source-level intent or protocol documentation. + * byte layout: + * - byte0: op_flags (medium-high) - low three bits select a command; upper bits are preserved or gated in some paths + * - byte1: addr_page_flags (medium) - candidate high/page byte for logical point/index; bit 7 is tested as a control flag + * - byte2: addr_offset (medium) - candidate low/offset byte for logical point/index + * - byte3: value_hi (medium) - candidate high byte of a word value + * - byte4: value_lo (medium) - candidate low byte of a word value + * - byte5: checksum (high) - 0x5A-seeded XOR of bytes 0..4 + * dispatch: command_low3 = RX_FRAME(0) & 0x07; observed H'00, H'01, H'02, H'04, H'05, H'06, H'07 + * dispatch evidence: H'BC08, H'BC0C, H'BC20, H'BC22, H'BC24, H'BC26, H'BC29, H'BC2B, H'BC2E, H'BC30, H'BC45, H'BC47, H'BC4A, H'BC4C, H'BC4F, H'BC51, H'BC54, H'BC56 + * index decoder: RX[1:2] -> logical index via loc_622B (medium) + * command candidates: + * - H'00 set_value_acked: candidate write of RX[3:4] into primary/current tables, followed by a response; handler H'BC69; responses response_at_BCCD + * - H'01 read_value: candidate read from the primary table, followed by a response carrying the value; handler H'BCD7; responses response_at_BCFA + * - H'02 clear_or_abort: candidate clear/abort path with no immediate response builder; handler H'BD04; responses none + * - H'04 set_value_no_immediate_reply: candidate write/update path that stores a value without an immediate serial response; handler H'BD0E; responses none + * - H'05 ack_or_clear_pending: candidate pending/event acknowledgement path; handler H'BD80; responses none + * - H'06 set_secondary_value: candidate secondary-table value write path; handler H'BDDB; responses none + * - H'07 retransmit_or_error_reply: candidate retransmit/NAK-style path; error handling also builds command 0x07 responses; handler H'BE05; responses response_at_BE22 + */ + +static u8 sci1_rx_candidate_command(void) +{ + return (u8)(RX_FRAME(0) & 0x07u); +} + +static u16 sci1_rx_candidate_value(void) +{ + return (u16)(((u16)RX_FRAME(3) << 8) | RX_FRAME(4)); +} + +static u16 sci1_rx_candidate_logical_index(void) +{ + u8 page = RX_FRAME(1); + u8 offset = RX_FRAME(2); + + if (page == 0u && offset <= 0x7Fu) { + return offset; + } + if (page == 1u) { + return (u16)(0x0080u + offset); + } + if (page == 2u && offset <= 0x7Fu) { + return (u16)(0x0180u + offset); + } + return 0x01FFu; +} + +void sci1_process_candidate_protocol_command(void) +{ + u8 command = sci1_rx_candidate_command(); + u16 logical_index = sci1_rx_candidate_logical_index(); + u16 value = sci1_rx_candidate_value(); + + switch (command) { + case 0x00u: + /* set_value_acked: candidate write of RX[3:4] into primary/current tables, followed by a response + * evidence: H'BC08, H'BC0C, H'BC20, H'BC22, H'BCB0, H'BCB9, H'BCC1, H'BCC9, H'BCB5, H'BCBD, H'BCC5, H'BCCD + */ + candidate_set_value_acked(logical_index, value); + break; + case 0x01u: + /* read_value: candidate read from the primary table, followed by a response carrying the value + * evidence: H'BC08, H'BC0C, H'BC24, H'BC26, H'BCB0, H'BCB9, H'BCC1, H'BCC9, H'BCD7, H'BCE0, H'BCE8, H'BCF0, H'BCF6, H'BCB5, H'BCBD, H'BCC5, H'BCDC, H'BCE4, H'BCFA + */ + candidate_read_value(logical_index, value); + break; + case 0x02u: + /* clear_or_abort: candidate clear/abort path with no immediate response builder + * evidence: H'BC08, H'BC0C, H'BC29, H'BC2B + */ + candidate_clear_or_abort(logical_index, value); + break; + case 0x04u: + /* set_value_no_immediate_reply: candidate write/update path that stores a value without an immediate serial response + * evidence: H'BC08, H'BC0C, H'BC45, H'BC47 + */ + candidate_set_value_no_immediate_reply(logical_index, value); + break; + case 0x05u: + /* ack_or_clear_pending: candidate pending/event acknowledgement path + * evidence: H'BC08, H'BC0C, H'BC4A, H'BC4C + */ + candidate_ack_or_clear_pending(logical_index, value); + break; + case 0x06u: + /* set_secondary_value: candidate secondary-table value write path + * evidence: H'BC08, H'BC0C, H'BC4F, H'BC51 + */ + candidate_set_secondary_value(logical_index, value); + break; + case 0x07u: + /* retransmit_or_error_reply: candidate retransmit/NAK-style path; error handling also builds command 0x07 responses + * evidence: H'BC08, H'BC0C, H'BC2E, H'BC30, H'BC54, H'BC56, H'BE09, H'BE11, H'BE19, H'BE22 + */ + candidate_retransmit_or_error_reply(logical_index, value); + break; + default: + candidate_unknown_command(command, logical_index, value); + break; + } +} + /* * TX reconstruction evidence * candidate/evidence-supported SCI1 6-byte TX frame hypothesis using buffer H'F858-H'F85D with checksum byte H'F85D seeded by H'005A diff --git a/h8536/render.py b/h8536/render.py index 886c486..39f01c8 100644 --- a/h8536/render.py +++ b/h8536/render.py @@ -30,6 +30,7 @@ from .serial_reconstruction import ( serial_reconstruction_json_payload, serial_reconstruction_metadata_for_instruction, ) +from .serial_semantics import analyze_serial_semantics from .symbols import symbol_for_address from .tables import IO_REGISTERS from .timing import format_timing_summary @@ -474,6 +475,7 @@ def write_json( for ins in (instructions[addr] for addr in sorted(instructions)) ], } + payload["serial_semantics"] = analyze_serial_semantics(payload) path.write_text(json.dumps(payload, indent=2), encoding="utf-8") diff --git a/h8536/serial_pseudocode.py b/h8536/serial_pseudocode.py index 08a4c85..af5ed95 100644 --- a/h8536/serial_pseudocode.py +++ b/h8536/serial_pseudocode.py @@ -2,10 +2,13 @@ from __future__ import annotations import argparse import json +import re from dataclasses import dataclass from pathlib import Path from typing import Any +from .serial_semantics import analyze_serial_semantics + JsonObject = dict[str, Any] @@ -17,6 +20,7 @@ class SerialPseudocodeOptions: include_evidence: bool = True include_manual: bool = True include_board: bool = True + include_semantics: bool = True def generate_serial_pseudocode( @@ -28,6 +32,7 @@ def generate_serial_pseudocode( opts = options or SerialPseudocodeOptions() tx_candidate = _find_candidate(payload, "candidate_sci1_tx_frame") rx_candidate = _find_candidate(payload, "candidate_sci1_rx_frame") + serial_semantics = analyze_serial_semantics(payload) if opts.include_semantics else None lines: list[str] = [] lines.extend(_file_header(source_name, tx_candidate, rx_candidate)) @@ -36,6 +41,8 @@ def generate_serial_pseudocode( if opts.include_manual: lines.extend(_manual_reference_lines(payload)) lines.extend(_declarations(tx_candidate, rx_candidate)) + if opts.include_semantics: + lines.extend(_semantics_lines(serial_semantics, opts)) emitted = False if opts.include_tx and tx_candidate: @@ -96,6 +103,7 @@ def main(argv: list[str] | None = None) -> int: parser.add_argument("--no-evidence", action="store_true", help="omit evidence-address comments") parser.add_argument("--no-manual", action="store_true", help="omit manual-reference comments") parser.add_argument("--no-board", action="store_true", help="omit board/MAX202 comments") + parser.add_argument("--no-semantics", action="store_true", help="omit candidate command/field semantics") args = parser.parse_args(argv) options = SerialPseudocodeOptions( @@ -104,6 +112,7 @@ def main(argv: list[str] | None = None) -> int: include_evidence=not args.no_evidence, include_manual=not args.no_manual, include_board=not args.no_board, + include_semantics=not args.no_semantics, ) write_serial_pseudocode(args.input, args.out, options) print(f"wrote {args.out}") @@ -256,6 +265,135 @@ def _declarations(tx_candidate: JsonObject | None, rx_candidate: JsonObject | No return lines +def _semantics_lines( + analysis: JsonObject | None, + opts: SerialPseudocodeOptions, +) -> list[str]: + if not isinstance(analysis, dict): + return [] + protocols = analysis.get("protocol_semantics") + if not isinstance(protocols, list) or not protocols: + return [] + protocol = protocols[0] + if not isinstance(protocol, dict): + return [] + + lines: list[str] = ["/* Candidate Protocol Semantics"] + lines.append( + f" * confidence: {protocol.get('confidence', 'unknown')} " + f"({protocol.get('confidence_score', 'n/a')})", + ) + caveat = str(protocol.get("caveat") or "").strip() + if caveat: + lines.append(f" * caveat: {_comment_text(caveat)}") + + layout = protocol.get("byte_layout") + if isinstance(layout, list) and layout: + lines.append(" * byte layout:") + for item in layout: + if not isinstance(item, dict): + continue + offset = item.get("offset", "?") + name = item.get("name_candidate", "byte") + semantic = item.get("semantic", "") + confidence = item.get("confidence", "unknown") + lines.append(f" * - byte{offset}: {name} ({confidence}) - {_comment_text(str(semantic))}") + + dispatch = protocol.get("command_dispatch") + if isinstance(dispatch, dict): + values = ", ".join(str(value) for value in dispatch.get("command_values_hex", [])) + lines.append( + " * dispatch: command_low3 = RX_FRAME(0) & 0x07" + + (f"; observed {values}" if values else ""), + ) + if opts.include_evidence: + lines.append(f" * dispatch evidence: {_hex_join(dispatch.get('evidence_addresses_hex'))}") + + index_decoder = protocol.get("index_decoder") + if isinstance(index_decoder, dict): + lines.append( + " * index decoder: RX[1:2] -> logical index via " + f"{index_decoder.get('label', 'loc_622B')} ({index_decoder.get('confidence', 'unknown')})", + ) + + commands = [item for item in protocol.get("commands", []) if isinstance(item, dict)] + if commands: + lines.append(" * command candidates:") + for command in commands: + value = command.get("command_value_hex", "??") + name = command.get("name_candidate", "unknown") + summary = _comment_text(str(command.get("summary") or "")) + handler = command.get("handler_start_hex") or "multiple" + responses = ", ".join(str(item) for item in command.get("response_candidates", [])) or "none" + lines.append(f" * - {value} {name}: {summary}; handler {handler}; responses {responses}") + lines.append(" */") + lines.append("") + + lines.extend( + [ + "static u8 sci1_rx_candidate_command(void)", + "{", + " return (u8)(RX_FRAME(0) & 0x07u);", + "}", + "", + "static u16 sci1_rx_candidate_value(void)", + "{", + " return (u16)(((u16)RX_FRAME(3) << 8) | RX_FRAME(4));", + "}", + "", + "static u16 sci1_rx_candidate_logical_index(void)", + "{", + " u8 page = RX_FRAME(1);", + " u8 offset = RX_FRAME(2);", + "", + " if (page == 0u && offset <= 0x7Fu) {", + " return offset;", + " }", + " if (page == 1u) {", + " return (u16)(0x0080u + offset);", + " }", + " if (page == 2u && offset <= 0x7Fu) {", + " return (u16)(0x0180u + offset);", + " }", + " return 0x01FFu;", + "}", + "", + "void sci1_process_candidate_protocol_command(void)", + "{", + " u8 command = sci1_rx_candidate_command();", + " u16 logical_index = sci1_rx_candidate_logical_index();", + " u16 value = sci1_rx_candidate_value();", + "", + " switch (command) {", + ], + ) + for command in commands: + value = command.get("command_value") + if not isinstance(value, int): + continue + name = _safe_identifier(str(command.get("name_candidate") or f"command_{value:02X}")) + summary = _comment_text(str(command.get("summary") or "candidate command semantics unknown")) + evidence = _hex_join(command.get("evidence_addresses_hex")) + lines.append(f" case 0x{value:02X}u:") + lines.append(f" /* {name}: {summary}") + if opts.include_evidence and evidence: + lines.append(f" * evidence: {evidence}") + lines.append(" */") + lines.append(f" candidate_{name}(logical_index, value);") + lines.append(" break;") + lines.extend( + [ + " default:", + " candidate_unknown_command(command, logical_index, value);", + " break;", + " }", + "}", + "", + ], + ) + return lines + + def _tx_functions(candidate: JsonObject, opts: SerialPseudocodeOptions) -> list[str]: length = _int_field(candidate, "frame_length", 6) seed = _int_field(candidate, "checksum_seed", 0x5A) @@ -473,5 +611,21 @@ def _dedupe(items: list[str]) -> list[str]: return output +def _hex_join(value: object) -> str: + if not isinstance(value, list): + return "" + return ", ".join(str(item) for item in value) + + +def _safe_identifier(value: str) -> str: + cleaned = re.sub(r"[^0-9A-Za-z_]", "_", value.strip()) + cleaned = re.sub(r"_+", "_", cleaned).strip("_") + if not cleaned: + return "unknown" + if cleaned[0].isdigit(): + return "_" + cleaned + return cleaned + + def _comment_text(text: str) -> str: return text.replace("*/", "* /").replace("\r", " ").replace("\n", " ") diff --git a/h8536/serial_semantics.py b/h8536/serial_semantics.py new file mode 100644 index 0000000..0a8c168 --- /dev/null +++ b/h8536/serial_semantics.py @@ -0,0 +1,1262 @@ +from __future__ import annotations + +from collections.abc import Iterable, Mapping +from typing import Any + + +JsonObject = dict[str, Any] + +RX_FRAME_START = 0xF860 +RX_FRAME_END = 0xF865 +RX_CHECKSUM_ADDRESS = RX_FRAME_END +RX_FRAME_LENGTH = 6 + +TX_STAGING_START = 0xF850 +TX_STAGING_END = 0xF854 +TX_STAGING_LENGTH = 5 +TX_FRAME_START = 0xF858 +TX_FRAME_END = 0xF85D +TX_CHECKSUM_ADDRESS = TX_FRAME_END + +SEND_BUILDER_ADDRESS = 0xBA26 +SEND_BUILDER_LABEL = "loc_BA26" +INDEX_DECODER_ADDRESS = 0x622B +INDEX_DECODER_LABEL = "loc_622B" +CHECKSUM_SEED = 0x5A + + +def analyze_serial_semantics(payload: Mapping[str, Any]) -> JsonObject: + """Infer conservative SCI1 frame/command semantics from decompiler JSON.""" + ordered = _instruction_sequence(payload.get("instructions")) + reconstruction = _serial_reconstruction(payload) + + rx_candidate = _candidate_by_kind(reconstruction, "candidate_sci1_rx_frame") + tx_candidate = _candidate_by_kind(reconstruction, "candidate_sci1_tx_frame") + frame_supported = bool(rx_candidate and tx_candidate) + if not frame_supported: + return { + "kind": "serial_semantics", + "protocol_semantics": [], + "fields": [], + "command_dispatch": None, + "commands": [], + "response_candidates": [], + "confidence": "low", + "confidence_score": 0.0, + "caveat": "No protocol semantics are emitted without both RX and TX serial reconstruction candidates.", + } + + dispatch = _find_command_dispatch(ordered) + responses = _response_candidates(ordered) + commands = _command_candidates(ordered, dispatch, responses) + fields = _field_candidates(ordered, dispatch, responses) + send_builder = _send_builder_candidate(ordered, responses, tx_candidate) + evidence = _top_level_evidence(ordered, dispatch, responses, rx_candidate, tx_candidate) + + confidence_score = _confidence_score(frame_supported, dispatch, responses, commands) + protocol = { + "kind": "serial_semantics", + "scope": "evidence_supported_sci1_6_byte_frame", + "confidence": _confidence_label(confidence_score), + "confidence_score": confidence_score, + "caveat": ( + "Semantic names are candidates only. The analyzer reports byte roles, command values, " + "dispatch targets, and response staging patterns observed in code; it does not prove " + "source-level intent or protocol documentation." + ), + "frame_candidate": { + "channel": "SCI1", + "rx_frame_start": RX_FRAME_START, + "rx_frame_start_hex": _h16(RX_FRAME_START), + "rx_frame_end": RX_FRAME_END, + "rx_frame_end_hex": _h16(RX_FRAME_END), + "tx_staging_start": TX_STAGING_START, + "tx_staging_start_hex": _h16(TX_STAGING_START), + "tx_staging_end": TX_STAGING_END, + "tx_staging_end_hex": _h16(TX_STAGING_END), + "tx_frame_start": TX_FRAME_START, + "tx_frame_start_hex": _h16(TX_FRAME_START), + "tx_frame_end": TX_FRAME_END, + "tx_frame_end_hex": _h16(TX_FRAME_END), + "frame_length": RX_FRAME_LENGTH, + "tx_staging_length": TX_STAGING_LENGTH, + "checksum_seed": CHECKSUM_SEED, + "checksum_seed_hex": _h16(CHECKSUM_SEED), + "serial_reconstruction_supported": frame_supported, + "rx_reconstruction_candidate_id": rx_candidate.get("id") if rx_candidate else None, + "tx_reconstruction_candidate_id": tx_candidate.get("id") if tx_candidate else None, + }, + "byte_layout": _byte_layout(), + "fields": fields, + "command_dispatch": dispatch, + "commands": commands, + "index_decoder": _index_decoder_candidate(ordered), + "send_builder": send_builder, + "response_candidates": responses, + "rx_fields": _rx_field_candidates(ordered, dispatch), + "response_builders": _response_builder_aliases(responses), + "evidence": evidence, + } + return { + "kind": "serial_semantics", + "protocol_semantics": [protocol], + "fields": protocol["fields"], + "command_dispatch": protocol["command_dispatch"], + "commands": protocol["commands"], + "response_candidates": protocol["response_candidates"], + "send_builder": protocol["send_builder"], + "confidence": protocol["confidence"], + "confidence_score": protocol["confidence_score"], + "caveat": protocol["caveat"], + } + + +def _field_candidates( + ordered: list[JsonObject], + dispatch: JsonObject | None, + responses: list[JsonObject], +) -> list[JsonObject]: + fields: list[JsonObject] = [] + response_write_map: dict[int, list[int]] = {} + for response in responses: + for write in response.get("writes", []): + if not isinstance(write, Mapping): + continue + for address in write.get("addresses", []): + if isinstance(address, int): + response_write_map.setdefault(address, []).append(int(write["instruction_address"])) + + rx_reads = { + address: [ins["address"] for ins in ordered if _is_read_from_address(ins, address)] + for address in range(RX_FRAME_START, RX_FRAME_END + 1) + } + rx_writes = { + address: [ins["address"] for ins in ordered if _is_write_to_address(ins, address)] + for address in range(RX_FRAME_START, RX_FRAME_END + 1) + } + + dispatch_addresses = set(dispatch.get("evidence_addresses", []) if dispatch else []) + for offset, address in enumerate(range(RX_FRAME_START, RX_FRAME_END + 1)): + role = "payload_byte_candidate" + caveat = "Role is inferred from reads in command processing." + if offset == 0: + role = "command_selector_candidate" + caveat = "RX[0] is masked with 0x07 before command comparisons." + elif address == RX_CHECKSUM_ADDRESS: + role = "checksum_byte_candidate" + caveat = "RX[5] is compared with a checksum over RX[0..4]." + fields.append( + { + "id": f"rx_{offset}", + "kind": "rx_frame_field_candidate", + "offset": offset, + "address": address, + "address_hex": _h16(address), + "role_candidate": role, + "evidence_addresses": _dedupe_ints( + rx_reads[address] + + rx_writes[address] + + ([addr for addr in dispatch_addresses if offset == 0]) + ), + "evidence_addresses_hex": _hlist( + rx_reads[address] + + rx_writes[address] + + ([addr for addr in dispatch_addresses if offset == 0]) + ), + "read_count": len(rx_reads[address]), + "write_count": len(rx_writes[address]), + "confidence": "medium" if rx_reads[address] else "low", + "caveat": caveat, + } + ) + + for offset, address in enumerate(range(TX_STAGING_START, TX_STAGING_END + 1)): + write_addresses = _dedupe_ints(response_write_map.get(address, [])) + fields.append( + { + "id": f"tx_staging_{offset}", + "kind": "tx_staging_field_candidate", + "offset": offset, + "address": address, + "address_hex": _h16(address), + "role_candidate": "response_staging_byte_candidate", + "evidence_addresses": write_addresses, + "evidence_addresses_hex": _hlist(write_addresses), + "write_count": len(write_addresses), + "confidence": "medium" if write_addresses else "low", + "caveat": ( + "This byte is staged before calls to loc_BA26; the analyzer does not infer " + "a stable field name beyond response position." + ), + } + ) + + return fields + + +def _rx_field_candidates( + ordered: list[JsonObject], + dispatch: JsonObject | None, +) -> list[JsonObject]: + read_map = { + address: [ + int(ins["address"]) + for ins in ordered + if address in _read_addresses_in_range(ins, RX_FRAME_START, RX_FRAME_END) + ] + for address in range(RX_FRAME_START, RX_FRAME_END + 1) + } + fields: list[JsonObject] = [] + for offset, address in enumerate(range(RX_FRAME_START, RX_FRAME_END + 1)): + if offset == 0 and dispatch: + name = "command_low3" + confidence = "candidate-medium" + mask = dispatch.get("mask") + evidence = _dedupe_ints(read_map[address] + dispatch.get("evidence_addresses", [])) + elif offset in {1, 2}: + name = "likely_id_or_index" + confidence = "candidate-low" + mask = None + evidence = read_map[address] + elif offset in {3, 4}: + name = "likely_value" + confidence = "candidate-low" + mask = None + evidence = read_map[address] + else: + name = "checksum" + confidence = "candidate-medium" + mask = None + evidence = read_map[address] + field = { + "offset": offset, + "field": f"byte{offset}", + "name": name, + "address": address, + "address_hex": _h16(address), + "confidence": confidence, + "caveat": "Field name is inferred from access pattern and remains a candidate.", + "evidence_addresses": _dedupe_ints(evidence), + "evidence_addresses_hex": _hlist(evidence), + } + if mask is not None: + field["mask"] = mask + field["mask_hex"] = _h16(int(mask), width=2) + fields.append(field) + return fields + + +def _find_command_dispatch(ordered: list[JsonObject]) -> JsonObject | None: + by_index = {int(ins["address"]): index for index, ins in enumerate(ordered) if "address" in ins} + best: JsonObject | None = None + + for index, ins in enumerate(ordered): + if _mnemonic_root(ins.get("mnemonic", "")) != "AND": + continue + if _immediate_source_value(str(ins.get("operands", ""))) != 0x07: + continue + _source, selector_reg = _source_destination_operands(str(ins.get("operands", ""))) + if not selector_reg: + continue + + read = _find_prior_read(ordered, index, RX_FRAME_START, selector_reg) + if read is None: + continue + + comparisons = _dispatch_comparisons(ordered, index + 1, selector_reg) + command_values = sorted({int(item["command_value"]) for item in comparisons}) + candidate = { + "kind": "command_dispatch_candidate", + "selector": "rx0_low3_bits", + "field": "command_low3", + "rx_offset": 0, + "rx_address": RX_FRAME_START, + "rx_address_hex": _h16(RX_FRAME_START), + "source_address": RX_FRAME_START, + "source_address_hex": _h16(RX_FRAME_START), + "source_field": "byte0", + "mask": 0x07, + "mask_hex": _h16(0x07), + "selector_register": selector_reg, + "read_address": int(read["address"]), + "read_address_hex": _h16(int(read["address"])), + "mask_address": int(ins["address"]), + "mask_address_hex": _h16(int(ins["address"])), + "command_values": command_values, + "command_values_hex": [_h16(value, width=2) for value in command_values], + "comparisons": comparisons, + "cases": [ + { + "value": int(item["command_value"]), + "value_hex": item["command_value_hex"], + "target": int(item["handler_start"]), + "target_hex": item["handler_start_hex"], + "compare_address": item["compare_address"], + "branch_address": item["branch_address"], + } + for item in comparisons + ], + "evidence_addresses": _dedupe_ints( + [int(read["address"]), int(ins["address"])] + + [addr for item in comparisons for addr in item["evidence_addresses"]] + ), + "confidence": "medium", + "caveat": ( + "Dispatch is inferred from a read of RX[0], an AND 0x07 mask, and nearby " + "compare/branch pairs. Gating state around the dispatch may affect reachability." + ), + } + candidate["evidence_addresses_hex"] = _hlist(candidate["evidence_addresses"]) + if best is None or len(comparisons) > len(best["comparisons"]): + best = candidate + + if best: + for item in best["comparisons"]: + target = item.get("handler_start") + if isinstance(target, int) and target in by_index: + item["handler_start_index"] = by_index[target] + return best + + +def _dispatch_comparisons( + ordered: list[JsonObject], + start_index: int, + selector_reg: str, +) -> list[JsonObject]: + comparisons: list[JsonObject] = [] + for index in range(start_index, min(len(ordered) - 1, start_index + 96)): + ins = ordered[index] + address = int(ins.get("address", -1)) + if address >= 0xBE70: + break + if _mnemonic_root(str(ins.get("mnemonic", ""))) not in {"CMP", "CMP:E", "CMP:G", "CMP:I"}: + continue + if _destination_operand(str(ins.get("operands", ""))).upper() != selector_reg.upper(): + continue + value = _immediate_source_value(str(ins.get("operands", ""))) + if value is None or not 0 <= value <= 7: + continue + branch = ordered[index + 1] + if str(branch.get("mnemonic", "")).upper() != "BEQ": + continue + targets = _targets(branch) + if not targets: + continue + branch_address = int(branch["address"]) + target = int(targets[0]) + comparisons.append( + { + "command_value": value, + "command_value_hex": _h16(value, width=2), + "compare_address": address, + "compare_address_hex": _h16(address), + "branch_address": branch_address, + "branch_address_hex": _h16(branch_address), + "handler_start": target, + "handler_start_hex": _h16(target), + "evidence_addresses": [address, branch_address], + "evidence_addresses_hex": _hlist([address, branch_address]), + } + ) + return comparisons + + +def _command_candidates( + ordered: list[JsonObject], + dispatch: JsonObject | None, + responses: list[JsonObject], +) -> list[JsonObject]: + if not dispatch: + return [] + + comparisons = [ + item for item in dispatch.get("comparisons", []) if isinstance(item, Mapping) + ] + starts = sorted({int(item["handler_start"]) for item in comparisons if "handler_start" in item}) + ranges = { + start: _handler_end(ordered, start, starts) + for start in starts + } + + by_value: dict[int, JsonObject] = {} + for comparison in comparisons: + value = int(comparison["command_value"]) + start = int(comparison["handler_start"]) + end = ranges.get(start) + command = by_value.setdefault( + value, + { + "kind": "command_candidate", + "command_value": value, + "command_value_hex": _h16(value, width=2), + "name_candidate": _command_name_candidate(value), + "summary": _command_summary(value), + "handler_alternatives": [], + "evidence_addresses": [], + "response_candidates": [], + "rx_reads": [], + "confidence": "medium", + "caveat": ( + "Command value and handler range are inferred from compare/BEQ dispatch. " + "No command name or intent is asserted." + ), + }, + ) + alternative = { + "handler_start": start, + "handler_start_hex": _h16(start), + "handler_end": end, + "handler_end_hex": _h16(end) if end is not None else None, + "dispatch_compare_address": comparison["compare_address"], + "dispatch_compare_address_hex": comparison["compare_address_hex"], + "dispatch_branch_address": comparison["branch_address"], + "dispatch_branch_address_hex": comparison["branch_address_hex"], + } + if alternative not in command["handler_alternatives"]: + command["handler_alternatives"].append(alternative) + command["evidence_addresses"].extend(dispatch.get("evidence_addresses", [])[:2]) + command["evidence_addresses"].extend(comparison.get("evidence_addresses", [])) + + for command in by_value.values(): + alternatives = command["handler_alternatives"] + starts_for_command = _dedupe_ints( + alt["handler_start"] for alt in alternatives if isinstance(alt["handler_start"], int) + ) + ends_for_command = _dedupe_ints( + alt["handler_end"] for alt in alternatives if isinstance(alt["handler_end"], int) + ) + command["handler_start"] = starts_for_command[0] if len(starts_for_command) == 1 else None + command["handler_start_hex"] = _h16(starts_for_command[0]) if len(starts_for_command) == 1 else None + command["handler_end"] = ends_for_command[0] if len(ends_for_command) == 1 else None + command["handler_end_hex"] = _h16(ends_for_command[0]) if len(ends_for_command) == 1 else None + + ranges_for_command = [ + (alt["handler_start"], alt["handler_end"]) + for alt in alternatives + if isinstance(alt["handler_end"], int) + ] + command["rx_reads"] = _rx_reads_in_ranges(ordered, ranges_for_command) + command["response_candidates"] = [ + response["id"] + for response in responses + if _response_in_ranges(response, ranges_for_command) + ] + response_evidence = [ + addr + for response in responses + if response["id"] in command["response_candidates"] + for addr in response.get("evidence_addresses", []) + ] + command["evidence_addresses"] = _dedupe_ints(command["evidence_addresses"] + response_evidence) + command["evidence_addresses_hex"] = _hlist(command["evidence_addresses"]) + + return [by_value[value] for value in sorted(by_value)] + + +def _byte_layout() -> list[JsonObject]: + return [ + { + "offset": 0, + "rx_address": RX_FRAME_START, + "tx_staging_address": TX_STAGING_START, + "name_candidate": "op_flags", + "semantic": "low three bits select a command; upper bits are preserved or gated in some paths", + "confidence": "medium-high", + }, + { + "offset": 1, + "rx_address": RX_FRAME_START + 1, + "tx_staging_address": TX_STAGING_START + 1, + "name_candidate": "addr_page_flags", + "semantic": "candidate high/page byte for logical point/index; bit 7 is tested as a control flag", + "confidence": "medium", + }, + { + "offset": 2, + "rx_address": RX_FRAME_START + 2, + "tx_staging_address": TX_STAGING_START + 2, + "name_candidate": "addr_offset", + "semantic": "candidate low/offset byte for logical point/index", + "confidence": "medium", + }, + { + "offset": 3, + "rx_address": RX_FRAME_START + 3, + "tx_staging_address": TX_STAGING_START + 3, + "name_candidate": "value_hi", + "semantic": "candidate high byte of a word value", + "confidence": "medium", + }, + { + "offset": 4, + "rx_address": RX_FRAME_START + 4, + "tx_staging_address": TX_STAGING_START + 4, + "name_candidate": "value_lo", + "semantic": "candidate low byte of a word value", + "confidence": "medium", + }, + { + "offset": 5, + "rx_address": RX_CHECKSUM_ADDRESS, + "tx_staging_address": None, + "name_candidate": "checksum", + "semantic": "0x5A-seeded XOR of bytes 0..4", + "confidence": "high", + }, + ] + + +def _command_name_candidate(value: int) -> str: + return { + 0x00: "set_value_acked", + 0x01: "read_value", + 0x02: "clear_or_abort", + 0x04: "set_value_no_immediate_reply", + 0x05: "ack_or_clear_pending", + 0x06: "set_secondary_value", + 0x07: "retransmit_or_error_reply", + }.get(value, f"command_{value:02X}") + + +def _command_summary(value: int) -> str: + return { + 0x00: "candidate write of RX[3:4] into primary/current tables, followed by a response", + 0x01: "candidate read from the primary table, followed by a response carrying the value", + 0x02: "candidate clear/abort path with no immediate response builder", + 0x04: "candidate write/update path that stores a value without an immediate serial response", + 0x05: "candidate pending/event acknowledgement path", + 0x06: "candidate secondary-table value write path", + 0x07: "candidate retransmit/NAK-style path; error handling also builds command 0x07 responses", + }.get(value, "candidate command semantics are unknown") + + +def _index_decoder_candidate(ordered: list[JsonObject]) -> JsonObject | None: + calls = [ + ins for ins in ordered + if _mnemonic_root(str(ins.get("mnemonic", ""))) in {"BSR", "JSR", "PJSR"} + and ( + INDEX_DECODER_ADDRESS in _targets(ins) + or INDEX_DECODER_LABEL.upper() in str(ins.get("operands", "")).upper() + ) + ] + if not calls: + return None + + evidence_addresses = [int(ins["address"]) for ins in calls] + return { + "kind": "logical_index_decoder_candidate", + "label": INDEX_DECODER_LABEL, + "address": INDEX_DECODER_ADDRESS, + "address_hex": _h16(INDEX_DECODER_ADDRESS), + "input_fields": ["addr_page_flags", "addr_offset"], + "output_register": "R5", + "post_scale_register": "R4", + "post_scale": "R4 = R5 << 1", + "mapping_candidate": [ + {"page": 0, "offset_range": "0x00-0x7F", "index_range": "0x000-0x07F"}, + {"page": 1, "offset_range": "0x00-0xFF", "index_range": "0x080-0x17F"}, + {"page": 2, "offset_range": "0x00-0x7F", "index_range": "0x180-0x1FF"}, + {"page": "other/overflow", "index": "0x1FF"}, + ], + "evidence_addresses": evidence_addresses, + "evidence_addresses_hex": _hlist(evidence_addresses), + "confidence": "medium", + "caveat": ( + "Mapping is inferred from loc_622B behavior and the nearby R4 = R5 << 1 table-index use." + ), + } + + +def _response_candidates(ordered: list[JsonObject]) -> list[JsonObject]: + responses: list[JsonObject] = [] + for index, ins in enumerate(ordered): + if not _is_send_builder_call(ins): + continue + window = _response_window(ordered, index) + writes = _staging_writes(window) + if not writes: + continue + reads = _rx_reads(window, RX_FRAME_START + 1, RX_FRAME_START + 4) + call_address = int(ins["address"]) + evidence_addresses = _dedupe_ints( + [write["instruction_address"] for write in writes] + + [read["instruction_address"] for read in reads] + + [call_address] + ) + response = { + "id": f"response_at_{call_address:04X}", + "kind": "response_staging_candidate", + "call_address": call_address, + "call_address_hex": _h16(call_address), + "send_builder": SEND_BUILDER_LABEL, + "send_builder_address": SEND_BUILDER_ADDRESS, + "send_builder_address_hex": _h16(SEND_BUILDER_ADDRESS), + "window_start": int(window[0]["address"]) if window else call_address, + "window_start_hex": _h16(int(window[0]["address"])) if window else _h16(call_address), + "writes": writes, + "rx_reads": reads, + "evidence_addresses": evidence_addresses, + "evidence_addresses_hex": _hlist(evidence_addresses), + "confidence": "medium", + "caveat": ( + "Response candidate means F850-F854 are written shortly before loc_BA26. " + "The analyzer does not prove every byte is meaningful for every path." + ), + } + responses.append(response) + return responses + + +def _rx_field_candidates( + ordered: list[JsonObject], + dispatch: JsonObject | None, +) -> list[JsonObject]: + fields: list[JsonObject] = [] + dispatch_evidence = [] + if isinstance(dispatch, Mapping): + dispatch_evidence = [ + value for value in dispatch.get("evidence_addresses", []) if isinstance(value, int) + ] + + for offset in range(RX_FRAME_LENGTH): + address = RX_FRAME_START + offset + read_evidence = [ + int(ins["address"]) for ins in ordered if _is_read_from_address(ins, address) + ] + name = "payload_byte" + confidence = "candidate-low" + caveat = "role is inferred only from frame position" + mask = None + + if offset == 0: + name = "command_low3" + confidence = "candidate-high" if dispatch else "candidate-medium" + caveat = "RX[0] is masked with 0x07 before command comparisons" + mask = 0x07 + read_evidence = _dedupe_ints(read_evidence + dispatch_evidence) + elif offset in {1, 2}: + name = "likely_id_or_index" + confidence = "candidate-medium" if read_evidence else "candidate-low" + caveat = "RX[1:2] are read near logical point/index and response-echo handling" + elif offset in {3, 4}: + name = "likely_value" + confidence = "candidate-medium" if read_evidence else "candidate-low" + caveat = "RX[3:4] are read near table-value write/read response handling" + elif offset == 5: + name = "checksum" + confidence = "candidate-high" + caveat = "RX[5] is validated by the serial reconstruction checksum evidence" + + field: JsonObject = { + "kind": "rx_field_semantic_candidate", + "offset": offset, + "name": name, + "address": address, + "address_hex": _h16(address), + "confidence": confidence, + "caveat": caveat, + "evidence_addresses": _dedupe_ints(read_evidence), + "evidence_addresses_hex": _hlist(read_evidence), + } + if mask is not None: + field["mask"] = mask + field["mask_hex"] = _h16(mask, width=2) + fields.append(field) + return fields + + +def _response_builder_aliases(responses: list[JsonObject]) -> list[JsonObject]: + builders: list[JsonObject] = [] + for response in responses: + writes: list[JsonObject] = [] + for write in response.get("writes", []): + if not isinstance(write, Mapping): + continue + for address in write.get("addresses", []): + if not isinstance(address, int): + continue + writes.append( + { + "address": address, + "address_hex": _h16(address), + "instruction_address": write.get("instruction_address"), + "instruction_address_hex": write.get("instruction_address_hex"), + "source": write.get("source"), + "instruction": write.get("instruction"), + } + ) + builders.append( + { + "kind": "response_builder_candidate", + "buffer_start": TX_STAGING_START, + "buffer_start_hex": _h16(TX_STAGING_START), + "buffer_end": TX_STAGING_END, + "buffer_end_hex": _h16(TX_STAGING_END), + "send_call_target": SEND_BUILDER_ADDRESS, + "send_call_target_hex": _h16(SEND_BUILDER_ADDRESS), + "call_address": response.get("call_address"), + "call_address_hex": response.get("call_address_hex"), + "writes": writes, + "evidence_addresses": response.get("evidence_addresses", []), + "evidence_addresses_hex": response.get("evidence_addresses_hex", []), + "confidence": response.get("confidence", "medium"), + "caveat": response.get("caveat"), + } + ) + return builders + + +def _send_builder_candidate( + ordered: list[JsonObject], + responses: list[JsonObject], + tx_candidate: Mapping[str, Any] | None, +) -> JsonObject: + copies = [] + builder_body = [ + ins + for ins in ordered + if SEND_BUILDER_ADDRESS <= int(ins.get("address", -1)) <= 0xBA83 + ] + for ins in builder_body: + source, destination = _source_destination_operands(str(ins.get("operands", ""))) + source_address = _first_address_in_range(ins, TX_STAGING_START, TX_STAGING_END, operand=source) + destination_address = _first_address_in_range(ins, TX_FRAME_START, TX_FRAME_END, operand=destination) + if source_address is None or destination_address is None: + continue + copies.append( + { + "instruction_address": int(ins["address"]), + "instruction_address_hex": _h16(int(ins["address"])), + "source_address": source_address, + "source_address_hex": _h16(source_address), + "destination_address": destination_address, + "destination_address_hex": _h16(destination_address), + "instruction": str(ins.get("text", "")), + } + ) + + call_addresses = [int(response["call_address"]) for response in responses] + evidence_addresses = _dedupe_ints( + [int(ins["address"]) for ins in builder_body if _has_ref_in_range(ins, TX_FRAME_START, TX_FRAME_END)] + + call_addresses + ) + return { + "kind": "tx_send_builder_candidate", + "label": SEND_BUILDER_LABEL, + "address": SEND_BUILDER_ADDRESS, + "address_hex": _h16(SEND_BUILDER_ADDRESS), + "staging_buffer_start": TX_STAGING_START, + "staging_buffer_start_hex": _h16(TX_STAGING_START), + "staging_buffer_end": TX_STAGING_END, + "staging_buffer_end_hex": _h16(TX_STAGING_END), + "tx_frame_start": TX_FRAME_START, + "tx_frame_start_hex": _h16(TX_FRAME_START), + "tx_frame_end": TX_FRAME_END, + "tx_frame_end_hex": _h16(TX_FRAME_END), + "checksum_address": TX_CHECKSUM_ADDRESS, + "checksum_address_hex": _h16(TX_CHECKSUM_ADDRESS), + "checksum_seed": CHECKSUM_SEED, + "checksum_seed_hex": _h16(CHECKSUM_SEED), + "staging_to_frame_copies": copies, + "response_call_addresses": call_addresses, + "response_call_addresses_hex": _hlist(call_addresses), + "serial_reconstruction_candidate_id": tx_candidate.get("id") if tx_candidate else None, + "evidence_addresses": evidence_addresses, + "evidence_addresses_hex": _hlist(evidence_addresses), + "confidence": "high" if copies and tx_candidate else "medium" if copies else "low", + "caveat": ( + "loc_BA26 is treated as a send builder because it copies F850-F854 into the " + "evidence-supported TX frame and then starts SCI1 transmission." + ), + } + + +def _top_level_evidence( + ordered: list[JsonObject], + dispatch: JsonObject | None, + responses: list[JsonObject], + rx_candidate: Mapping[str, Any] | None, + tx_candidate: Mapping[str, Any] | None, +) -> list[JsonObject]: + evidence: list[JsonObject] = [] + if rx_candidate: + evidence.append( + { + "kind": "rx_frame_reconstruction_present", + "summary": "serial_reconstruction contains an evidence-supported SCI1 RX frame candidate", + "candidate_id": rx_candidate.get("id"), + } + ) + if tx_candidate: + evidence.append( + { + "kind": "tx_frame_reconstruction_present", + "summary": "serial_reconstruction contains an evidence-supported SCI1 TX frame candidate", + "candidate_id": tx_candidate.get("id"), + } + ) + if dispatch: + evidence.append( + { + "kind": "rx0_masked_command_dispatch", + "summary": "RX[0] is read, masked with 0x07, and compared against command values", + "addresses": dispatch.get("evidence_addresses", []), + "addresses_hex": dispatch.get("evidence_addresses_hex", []), + } + ) + if responses: + addresses = _dedupe_ints( + [addr for response in responses for addr in response.get("evidence_addresses", [])] + ) + evidence.append( + { + "kind": "responses_stage_f850_f854_before_send", + "summary": "F850-F854 writes are observed before calls to loc_BA26", + "addresses": addresses, + "addresses_hex": _hlist(addresses), + "response_count": len(responses), + } + ) + rx_payload_reads = [ + int(ins["address"]) + for ins in ordered + if any(_is_read_from_address(ins, address) for address in range(RX_FRAME_START + 1, RX_FRAME_START + 5)) + ] + if rx_payload_reads: + evidence.append( + { + "kind": "rx_payload_bytes_read", + "summary": "RX[1..4] are read in the command-processing region", + "addresses": _dedupe_ints(rx_payload_reads), + "addresses_hex": _hlist(rx_payload_reads), + } + ) + return evidence + + +def _response_window(ordered: list[JsonObject], call_index: int) -> list[JsonObject]: + start = call_index + for index in range(call_index - 1, max(-1, call_index - 48), -1): + candidate = ordered[index] + mnemonic = str(candidate.get("mnemonic", "")).upper() + if mnemonic in {"RTS", "RTE"}: + break + if candidate.get("kind") == "branch" and mnemonic != "BSR": + break + start = index + return ordered[start:call_index] + + +def _staging_writes(window: list[JsonObject]) -> list[JsonObject]: + writes: list[JsonObject] = [] + for index, ins in enumerate(window): + touched = _written_addresses_in_range(ins, TX_STAGING_START, TX_STAGING_END) + if not touched: + continue + source, _destination = _source_destination_operands(str(ins.get("operands", ""))) + source_info = _source_info(window, index, source) + writes.append( + { + "instruction_address": int(ins["address"]), + "instruction_address_hex": _h16(int(ins["address"])), + "addresses": touched, + "addresses_hex": _hlist(touched), + "source_operand": source, + "source": source_info, + "instruction": str(ins.get("text", "")), + } + ) + return writes + + +def _source_info(window: list[JsonObject], index: int, source: str) -> JsonObject: + immediate = _parse_immediate(source) + if immediate is not None: + return { + "kind": "immediate", + "value": immediate, + "value_hex": _h16(immediate, width=2 if immediate <= 0xFF else 4), + } + source_upper = source.upper() + for prior in reversed(window[max(0, index - 4) : index]): + prior_source, prior_destination = _source_destination_operands(str(prior.get("operands", ""))) + if prior_destination.upper() != source_upper: + continue + rx_address = _first_address_in_range(prior, RX_FRAME_START, RX_FRAME_END, operand=prior_source) + if rx_address is not None and _is_read_from_address(prior, rx_address): + return { + "kind": "rx_frame_byte", + "rx_offset": rx_address - RX_FRAME_START, + "rx_address": rx_address, + "rx_address_hex": _h16(rx_address), + "evidence_address": int(prior["address"]), + "evidence_address_hex": _h16(int(prior["address"])), + "instruction": str(prior.get("text", "")), + } + return {"kind": "register_or_computed", "operand": source} + + +def _rx_reads(window: list[JsonObject], start: int, end: int) -> list[JsonObject]: + reads: list[JsonObject] = [] + for ins in window: + for address in range(start, end + 1): + if not _is_read_from_address(ins, address): + continue + reads.append( + { + "instruction_address": int(ins["address"]), + "instruction_address_hex": _h16(int(ins["address"])), + "rx_offset": address - RX_FRAME_START, + "rx_address": address, + "rx_address_hex": _h16(address), + "instruction": str(ins.get("text", "")), + } + ) + return reads + + +def _rx_reads_in_ranges( + ordered: list[JsonObject], + ranges: list[tuple[int, int]], +) -> list[JsonObject]: + reads: list[JsonObject] = [] + for ins in ordered: + address = int(ins.get("address", -1)) + if not any(start <= address <= end for start, end in ranges): + continue + reads.extend(_rx_reads([ins], RX_FRAME_START + 1, RX_FRAME_START + 4)) + seen: set[tuple[int, int]] = set() + output: list[JsonObject] = [] + for read in reads: + key = (int(read["instruction_address"]), int(read["rx_address"])) + if key in seen: + continue + seen.add(key) + output.append(read) + return output + + +def _response_in_ranges(response: Mapping[str, Any], ranges: list[tuple[int, int]]) -> bool: + call_address = int(response.get("call_address", -1)) + return any(start <= call_address <= end for start, end in ranges) + + +def _handler_end( + ordered: list[JsonObject], + start: int, + handler_starts: list[int], +) -> int | None: + addresses = [int(ins["address"]) for ins in ordered] + try: + start_index = addresses.index(start) + except ValueError: + return None + + later_starts = [candidate for candidate in handler_starts if candidate > start] + if later_starts: + next_start = min(later_starts) + previous = [address for address in addresses if start <= address < next_start] + return previous[-1] if previous else None + + for ins in ordered[start_index:]: + mnemonic = str(ins.get("mnemonic", "")).upper() + if mnemonic in {"RTS", "RTE"}: + return int(ins["address"]) + if mnemonic == "BRA" and SEND_BUILDER_ADDRESS not in _targets(ins): + targets = _targets(ins) + if targets and targets[0] >= 0xBE6D: + return int(ins["address"]) + return None + + +def _find_prior_read( + ordered: list[JsonObject], + index: int, + address: int, + destination_register: str, +) -> JsonObject | None: + for candidate in reversed(ordered[max(0, index - 6) : index]): + if not _is_read_from_address(candidate, address): + continue + if _destination_operand(str(candidate.get("operands", ""))).upper() == destination_register.upper(): + return candidate + return None + + +def _is_send_builder_call(ins: Mapping[str, Any]) -> bool: + mnemonic = str(ins.get("mnemonic", "")).upper() + if mnemonic not in {"BSR", "JSR", "PJSR"}: + return False + if SEND_BUILDER_ADDRESS in _targets(ins): + return True + return SEND_BUILDER_LABEL.upper() in str(ins.get("operands", "")).upper() + + +def _written_addresses_in_range(ins: Mapping[str, Any], start: int, end: int) -> list[int]: + if not _has_ref_in_range(ins, start, end): + return [] + source, destination = _source_destination_operands(str(ins.get("operands", ""))) + del source + base = _first_address_in_range(ins, start, end, operand=destination) + if base is None or not _is_write_to_address(ins, base): + return [] + width = _access_width(str(ins.get("mnemonic", ""))) + return [address for address in range(base, min(base + width - 1, end) + 1)] + + +def _read_addresses_in_range(ins: Mapping[str, Any], start: int, end: int) -> list[int]: + if not _has_ref_in_range(ins, start, end): + return [] + source, _destination = _source_destination_operands(str(ins.get("operands", ""))) + base = _first_address_in_range(ins, start, end, operand=source) + if base is None or not _is_read_from_address(ins, base): + return [] + width = _access_width(str(ins.get("mnemonic", ""))) + return [address for address in range(base, min(base + width - 1, end) + 1)] + + +def _is_read_from_address(ins: Mapping[str, Any], address: int) -> bool: + source, destination = _source_destination_operands(str(ins.get("operands", ""))) + if _operand_mentions_address(source, address): + return True + if address not in _references(ins): + return False + if source.startswith("@") and not _operand_mentions_any_reference(destination, _references(ins)): + return True + return _access_direction(ins, address) == "read" + + +def _is_write_to_address(ins: Mapping[str, Any], address: int) -> bool: + _source, destination = _source_destination_operands(str(ins.get("operands", ""))) + if _operand_mentions_address(destination, address): + return _access_direction(ins, address) == "write" + if address not in _references(ins): + return False + return _access_direction(ins, address) == "write" + + +def _access_direction(ins: Mapping[str, Any], address: int) -> str | None: + root = _mnemonic_root(str(ins.get("mnemonic", ""))) + if root in {"BTST", "CMP", "CMP:E", "CMP:G", "CMP:I", "MOVFPE", "TST"}: + return "read" + if root in {"BCLR", "BNOT", "BSET", "CLR", "INC", "INC:G", "NEG", "NOT"}: + return "write" + if root in {"ADD:Q", "ADD:G", "ADDS", "ADDX", "AND", "OR", "SUB", "SUBS", "SUBX", "XOR"}: + return "write" + if root in {"MOV:G", "MOV:S", "MOVTPE"}: + source, destination = _source_destination_operands(str(ins.get("operands", ""))) + if _operand_mentions_address(destination, address): + return "write" + if _operand_mentions_address(source, address): + return "read" + if address in _references(ins): + if destination.startswith("@") and not _operand_mentions_any_reference(source, _references(ins)): + return "write" + if source.startswith("@") and not _operand_mentions_any_reference(destination, _references(ins)): + return "read" + if root in {"MOV:L", "MOV:F"}: + return "read" + if root == "STC": + return "write" + if root == "LDC": + return "read" + return None + + +def _first_address_in_range( + ins: Mapping[str, Any], + start: int, + end: int, + *, + operand: str = "", +) -> int | None: + if operand: + for address in range(start, end + 1): + if _operand_mentions_address(operand, address): + return address + for address in _references(ins): + if start <= address <= end: + return address + return None + + +def _has_ref_in_range(ins: Mapping[str, Any], start: int, end: int) -> bool: + return any(start <= address <= end for address in _references(ins)) + + +def _references(ins: Mapping[str, Any]) -> list[int]: + references = ins.get("references", []) + output: list[int] = [] + if not isinstance(references, list): + return output + for reference in references: + if isinstance(reference, Mapping) and isinstance(reference.get("address"), int): + output.append(int(reference["address"])) + elif isinstance(reference, int): + output.append(reference) + return output + + +def _targets(ins: Mapping[str, Any]) -> list[int]: + targets = ins.get("targets", []) + if not isinstance(targets, list): + return [] + return [int(target) for target in targets if isinstance(target, int)] + + +def _instruction_sequence(value: object) -> list[JsonObject]: + if isinstance(value, Mapping): + values: Iterable[Any] = value.values() + elif isinstance(value, list): + values = value + else: + values = [] + return sorted( + [item for item in values if isinstance(item, dict) and isinstance(item.get("address"), int)], + key=lambda item: int(item["address"]), + ) + + +def _serial_reconstruction(payload: Mapping[str, Any]) -> Mapping[str, Any]: + serial = payload.get("serial_reconstruction") + return serial if isinstance(serial, Mapping) else {} + + +def _candidate_by_kind(serial: Mapping[str, Any], kind: str) -> Mapping[str, Any] | None: + candidates = serial.get("candidates") + if not isinstance(candidates, list): + return None + for candidate in candidates: + if isinstance(candidate, Mapping) and candidate.get("kind") == kind: + return candidate + return None + + +def _source_destination_operands(operands: str) -> tuple[str, str]: + depth = 0 + split_at: int | None = None + for index, char in enumerate(operands): + if char in "({": + depth += 1 + elif char in ")}" and depth: + depth -= 1 + elif char == "," and depth == 0: + split_at = index + if split_at is None: + operand = operands.strip() + return "", operand + return operands[:split_at].strip(), operands[split_at + 1 :].strip() + + +def _destination_operand(operands: str) -> str: + return _source_destination_operands(operands)[1] + + +def _immediate_source_value(operands: str) -> int | None: + source, _destination = _source_destination_operands(operands) + if not source.startswith("#"): + return None + return _parse_immediate(source) + + +def _parse_immediate(operand: str) -> int | None: + text = operand.strip() + if text.startswith("#"): + text = text[1:].strip() + try: + if text.upper().startswith("H'"): + return int(text[2:], 16) & 0xFFFF + if text.upper().startswith("0X"): + return int(text, 16) & 0xFFFF + if text.upper().startswith("$"): + return int(text[1:], 16) & 0xFFFF + return int(text, 10) & 0xFFFF + except ValueError: + return None + + +def _operand_mentions_any_reference(operand: str, references: list[int]) -> bool: + return any(_operand_mentions_address(operand, address) for address in references) + + +def _operand_mentions_address(operand: str, address: int) -> bool: + operand_upper = operand.upper().replace(" ", "") + names = { + TX_STAGING_START: ("TX_STAGING",), + TX_FRAME_START: ("TX_FRAME",), + TX_CHECKSUM_ADDRESS: ("TX_CHECKSUM",), + RX_FRAME_START: ("RX_FRAME",), + RX_CHECKSUM_ADDRESS: ("RX_CHECKSUM",), + } + if any(name in operand_upper for name in names.get(address, ())): + return True + negative = (0x10000 - address) & 0xFFFF + return ( + f"H'{address:04X}" in operand_upper + or f"0X{address:04X}" in operand_upper + or f"${address:04X}" in operand_upper + or f"-H'{negative:04X}" in operand_upper + or f"-0X{negative:04X}" in operand_upper + or f"-${negative:04X}" in operand_upper + ) + + +def _mnemonic_root(mnemonic: str) -> str: + return mnemonic.rsplit(".", 1)[0].upper() + + +def _access_width(mnemonic: str) -> int: + upper = mnemonic.upper() + if upper.endswith(".L"): + return 4 + if upper.endswith(".W"): + return 2 + return 1 + + +def _confidence_score( + frame_supported: bool, + dispatch: JsonObject | None, + responses: list[JsonObject], + commands: list[JsonObject], +) -> float: + score = 0.2 + if frame_supported: + score += 0.25 + if dispatch: + score += 0.2 + if responses: + score += min(0.2, 0.04 * len(responses)) + if commands: + score += min(0.15, 0.02 * len(commands)) + return round(min(score, 0.9), 2) + + +def _confidence_label(score: float) -> str: + if score >= 0.75: + return "medium-high" + if score >= 0.5: + return "medium" + return "low" + + +def _dedupe_ints(values: Iterable[int]) -> list[int]: + seen: set[int] = set() + output: list[int] = [] + for value in values: + if value in seen: + continue + seen.add(value) + output.append(value) + return output + + +def _hlist(values: Iterable[int]) -> list[str]: + return [_h16(value) for value in _dedupe_ints(values)] + + +def _h16(value: int, *, width: int = 4) -> str: + return f"H'{value & 0xFFFF:0{width}X}" + + +__all__ = ["analyze_serial_semantics"] diff --git a/tests/test_serial_pseudocode.py b/tests/test_serial_pseudocode.py index db50b1d..192c332 100644 --- a/tests/test_serial_pseudocode.py +++ b/tests/test_serial_pseudocode.py @@ -97,6 +97,23 @@ def candidate_payload() -> dict: } +def semantic_payload() -> dict: + payload = candidate_payload() + payload["instructions"] = [ + {"address": 0xBC08, "mnemonic": "MOV:G.B", "operands": "@H'F860, R0", "references": [{"address": 0xF860}], "targets": []}, + {"address": 0xBC0C, "mnemonic": "AND.B", "operands": "#H'07, R0", "references": [], "targets": []}, + {"address": 0xBC20, "mnemonic": "CMP:E.B", "operands": "#H'00, R0", "references": [], "targets": []}, + {"address": 0xBC22, "mnemonic": "BEQ", "operands": "loc_BC69", "references": [], "targets": [0xBC69]}, + {"address": 0xBC24, "mnemonic": "CMP:E.B", "operands": "#H'01, R0", "references": [], "targets": []}, + {"address": 0xBC26, "mnemonic": "BEQ", "operands": "loc_BCD7", "references": [], "targets": [0xBCD7]}, + {"address": 0xBCB0, "mnemonic": "MOV:G.B", "operands": "#H'04, @H'F850", "references": [{"address": 0xF850}], "targets": []}, + {"address": 0xBCB5, "mnemonic": "MOV:G.B", "operands": "@H'F861, R0", "references": [{"address": 0xF861}], "targets": []}, + {"address": 0xBCB9, "mnemonic": "MOV:G.B", "operands": "R0, @H'F851", "references": [{"address": 0xF851}], "targets": []}, + {"address": 0xBCCD, "mnemonic": "BSR", "operands": "loc_BA26", "references": [], "targets": [0xBA26]}, + ] + return payload + + class SerialPseudocodeTest(unittest.TestCase): def test_generates_focused_tx_and_rx_candidate_paths(self): text = generate_serial_pseudocode(candidate_payload(), source_name="rom.json") @@ -117,6 +134,17 @@ class SerialPseudocodeTest(unittest.TestCase): self.assertIn("return sci1_process_rx_candidate_frame();", text) self.assertIn("rx_xor_checksum_validation: H'BBD6, H'BBEC", text) + def test_generates_candidate_protocol_semantics_switch(self): + text = generate_serial_pseudocode(semantic_payload()) + + self.assertIn("Candidate Protocol Semantics", text) + self.assertIn("byte0: op_flags", text) + self.assertIn("dispatch: command_low3 = RX_FRAME(0) & 0x07", text) + self.assertIn("case 0x00u:", text) + self.assertIn("candidate_set_value_acked(logical_index, value);", text) + self.assertIn("case 0x01u:", text) + self.assertIn("candidate_read_value(logical_index, value);", text) + def test_tx_only_option_omits_rx_functions(self): text = generate_serial_pseudocode( candidate_payload(), diff --git a/tests/test_serial_semantics.py b/tests/test_serial_semantics.py new file mode 100644 index 0000000..aa447f6 --- /dev/null +++ b/tests/test_serial_semantics.py @@ -0,0 +1,160 @@ +import unittest + +from h8536.serial_semantics import analyze_serial_semantics + + +def reference(address: int) -> dict: + return {"address": address} + + +def instruction( + address: int, + mnemonic: str, + operands: str = "", + references: list[int] | None = None, + targets: list[int] | None = None, +) -> dict: + return { + "address": address, + "mnemonic": mnemonic, + "operands": operands, + "references": [reference(item) for item in (references or [])], + "targets": targets or [], + } + + +def base_payload(instructions: list[dict]) -> dict: + return { + "serial_reconstruction": { + "candidates": [ + { + "kind": "candidate_sci1_rx_frame", + "channel": "SCI1", + "frame_length": 6, + "validation_buffer_start": 0xF860, + "validation_buffer_end": 0xF865, + "checksum_address": 0xF865, + "checksum_seed": 0x5A, + "confidence": "high", + }, + { + "kind": "candidate_sci1_tx_frame", + "channel": "SCI1", + "frame_length": 6, + "buffer_start": 0xF850, + "buffer_end": 0xF855, + "checksum_address": 0xF855, + "checksum_seed": 0x5A, + "confidence": "high", + }, + ], + }, + "instructions": instructions, + } + + +def only_semantics(testcase: unittest.TestCase, payload: dict) -> dict: + analysis = analyze_serial_semantics(payload) + testcase.assertEqual(analysis["kind"], "serial_semantics") + testcase.assertEqual(len(analysis["protocol_semantics"]), 1) + return analysis["protocol_semantics"][0] + + +class SerialSemanticsTest(unittest.TestCase): + def test_detects_low_three_bit_command_dispatch(self): + payload = base_payload( + [ + instruction(0xBA80, "MOV:G.B", "@H'F860, R0", [0xF860]), + instruction(0xBA84, "AND.B", "#H'07, R0"), + instruction(0xBA88, "CMP:E.B", "#H'00, R0"), + instruction(0xBA8C, "BEQ", "loc_BAA0", targets=[0xBAA0]), + instruction(0xBA90, "CMP:E.B", "#H'02, R0"), + instruction(0xBA94, "BEQ", "loc_BAC0", targets=[0xBAC0]), + instruction(0xBA98, "CMP:E.B", "#H'07, R0"), + instruction(0xBA9C, "BEQ", "loc_BAE0", targets=[0xBAE0]), + ] + ) + + semantics = only_semantics(self, payload) + + dispatch = semantics["command_dispatch"] + self.assertEqual(dispatch["source_address"], 0xF860) + self.assertEqual(dispatch["source_field"], "byte0") + self.assertEqual(dispatch["mask"], 0x07) + self.assertEqual(dispatch["field"], "command_low3") + self.assertEqual( + {(case["value"], case["target"]) for case in dispatch["cases"]}, + {(0x00, 0xBAA0), (0x02, 0xBAC0), (0x07, 0xBAE0)}, + ) + self.assertIn(0xBA80, dispatch["evidence_addresses"]) + self.assertIn(0xBA84, dispatch["evidence_addresses"]) + + def test_labels_likely_rx_fields_from_validation_buffer_offsets(self): + payload = base_payload( + [ + instruction(0xBB00, "MOV:G.B", "@H'F860, R0", [0xF860]), + instruction(0xBB04, "AND.B", "#H'07, R0"), + instruction(0xBB08, "MOV:G.W", "@H'F861, R1", [0xF861]), + instruction(0xBB0C, "MOV:G.W", "@H'F863, R2", [0xF863]), + ] + ) + + semantics = only_semantics(self, payload) + + fields = {field["offset"]: field for field in semantics["rx_fields"]} + self.assertEqual(fields[0]["name"], "command_low3") + self.assertEqual(fields[0]["address"], 0xF860) + self.assertEqual(fields[0]["mask"], 0x07) + self.assertEqual(fields[1]["name"], "likely_id_or_index") + self.assertEqual(fields[2]["name"], "likely_id_or_index") + self.assertEqual(fields[3]["name"], "likely_value") + self.assertEqual(fields[4]["name"], "likely_value") + self.assertIn("candidate", fields[1]["confidence"]) + self.assertIn("candidate", fields[3]["confidence"]) + + def test_detects_response_builder_before_serial_send_call(self): + payload = base_payload( + [ + instruction(0xBC00, "MOV:G.B", "@H'F860, R0", [0xF860]), + instruction(0xBC04, "MOV:G.B", "R0, @H'F850", [0xF850]), + instruction(0xBC08, "MOV:G.B", "@H'F861, R1", [0xF861]), + instruction(0xBC0C, "MOV:G.B", "R1, @H'F851", [0xF851]), + instruction(0xBC10, "MOV:G.B", "@H'F862, R2", [0xF862]), + instruction(0xBC14, "MOV:G.B", "R2, @H'F852", [0xF852]), + instruction(0xBC18, "MOV:G.B", "#H'00, @H'F853", [0xF853]), + instruction(0xBC1C, "MOV:G.B", "#H'01, @H'F854", [0xF854]), + instruction(0xBC20, "BSR", "loc_BA26", targets=[0xBA26]), + ] + ) + + semantics = only_semantics(self, payload) + + response = semantics["response_builders"][0] + self.assertEqual(response["buffer_start"], 0xF850) + self.assertEqual(response["buffer_end"], 0xF854) + self.assertEqual(response["send_call_target"], 0xBA26) + self.assertEqual(response["call_address"], 0xBC20) + self.assertEqual( + [write["address"] for write in response["writes"]], + [0xF850, 0xF851, 0xF852, 0xF853, 0xF854], + ) + + def test_missing_serial_reconstruction_candidates_emit_no_protocol_semantics(self): + payload = { + "serial_reconstruction": {"candidates": []}, + "instructions": [ + instruction(0xBA80, "MOV:G.B", "@H'F860, R0", [0xF860]), + instruction(0xBA84, "AND.B", "#H'07, R0"), + instruction(0xBA88, "CMP:E.B", "#H'00, R0"), + instruction(0xBA8C, "BEQ", "loc_BAA0", targets=[0xBAA0]), + ], + } + + analysis = analyze_serial_semantics(payload) + + self.assertEqual(analysis["kind"], "serial_semantics") + self.assertEqual(analysis["protocol_semantics"], []) + + +if __name__ == "__main__": + unittest.main()