Compare commits
3 Commits
e872030675
...
56829b6e0b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56829b6e0b | ||
|
|
6ceed81765 | ||
|
|
c80ea695dc |
24
README.md
24
README.md
@@ -20,6 +20,12 @@ To turn the structured decompile output into conservative C-like pseudocode:
|
||||
.\.venv\Scripts\python.exe h8536_pseudocode.py build\rom_decompiled.json --out build\rom_pseudocode.c --cycles
|
||||
```
|
||||
|
||||
To generate a focused RX/TX serial-path pseudocode view from the reconstruction metadata:
|
||||
|
||||
```powershell
|
||||
.\.venv\Scripts\python.exe h8536_serial_pseudocode.py build\rom_decompiled.json --out build\rom_serial_pseudocode.c
|
||||
```
|
||||
|
||||
## What It Does
|
||||
|
||||
- Decodes the H8/500 instruction set used by the H8/536.
|
||||
@@ -35,6 +41,8 @@ To turn the structured decompile output into conservative C-like pseudocode:
|
||||
- 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`.
|
||||
- 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.
|
||||
@@ -90,6 +98,19 @@ python h8536_pseudocode.py --help
|
||||
- `--no-structure`: preserve label/goto output instead of simple structured `if`/loop output.
|
||||
- `--max-functions N`: emit only the first `N` functions for focused review.
|
||||
|
||||
For focused serial pseudocode:
|
||||
|
||||
```powershell
|
||||
python h8536_serial_pseudocode.py --help
|
||||
```
|
||||
|
||||
- `--tx-only`: emit only the candidate transmit path.
|
||||
- `--rx-only`: emit only the candidate receive path.
|
||||
- `--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
|
||||
|
||||
- `h8536_decompiler.py`: compatibility wrapper for the CLI.
|
||||
@@ -111,9 +132,12 @@ python h8536_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.
|
||||
- `h8536/pseudocode.py`: JSON-to-C-like pseudocode generation.
|
||||
- `h8536/render.py`: assembly and JSON output.
|
||||
- `h8536/model.py`, `h8536/rom.py`, `h8536/formatting.py`: shared data structures and helpers.
|
||||
- `h8536_pseudocode.py`: pseudocode CLI wrapper.
|
||||
- `h8536_serial_pseudocode.py`: focused serial pseudocode CLI wrapper.
|
||||
|
||||
@@ -3130,15 +3130,15 @@ loc_BB56:
|
||||
BB56: 19 RTS ; cycles=12
|
||||
|
||||
vec_sci1_eri_BB57:
|
||||
BB57: 15 FA A4 C7 BSET.B #7, @H'FAA4 ; refs ram_FAA4 in on_chip_ram; cycles=8
|
||||
BB5B: 15 FE DC D5 BCLR.B #5, @SCI1_SSR ; clear ORER (bit 5) of SCI1_SSR; clear SCI1 overrun error flag (ORER); SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; cycles=8
|
||||
BB5F: 15 FE DC D4 BCLR.B #4, @SCI1_SSR ; clear FER (bit 4) of SCI1_SSR; clear SCI1 framing error flag (FER); SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; cycles=8
|
||||
BB63: 15 FE DC D3 BCLR.B #3, @SCI1_SSR ; clear PER (bit 3) of SCI1_SSR; clear SCI1 parity error flag (PER); SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; cycles=8
|
||||
BB57: 15 FA A4 C7 BSET.B #7, @H'FAA4 ; candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 ERI latches FAA4.bit7, clears ORER/FER/PER, then falls through into the same RXI byte-capture path; confidence high; refs ram_FAA4 in on_chip_ram; cycles=8
|
||||
BB5B: 15 FE DC D5 BCLR.B #5, @SCI1_SSR ; clear ORER (bit 5) of SCI1_SSR; clear SCI1 overrun error flag (ORER); candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 ERI latches FAA4.bit7, clears ORER/FER/PER, then falls through into the same RXI byte-capture path; confidence high; SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; cycles=8
|
||||
BB5F: 15 FE DC D4 BCLR.B #4, @SCI1_SSR ; clear FER (bit 4) of SCI1_SSR; clear SCI1 framing error flag (FER); candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 ERI latches FAA4.bit7, clears ORER/FER/PER, then falls through into the same RXI byte-capture path; confidence high; SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; cycles=8
|
||||
BB63: 15 FE DC D3 BCLR.B #3, @SCI1_SSR ; clear PER (bit 3) of SCI1_SSR; clear SCI1 parity error flag (PER); candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 ERI latches FAA4.bit7, clears ORER/FER/PER, then falls through into the same RXI byte-capture path; confidence high; SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; cycles=8
|
||||
|
||||
vec_sci1_rxi_BB67:
|
||||
BB67: 12 03 STM.W {R0,R1}, @-SP ; cycles=12
|
||||
BB69: 15 FE DC D6 BCLR.B #6, @SCI1_SSR ; clear RDRF (bit 6) of SCI1_SSR; clear SCI1 receive-data-full flag (RDRF); SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; cycles=8
|
||||
BB6D: 15 FE DD 80 MOV:G.B @SCI1_RDR, R0 ; read SCI1 received byte from RDR; candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 RX ISR reads a byte from SCI1_RDR; confidence high; SCI1 RDR read receives from traced RS232/MAX202 path: MAX202 pin 12 -> H8 pin 67 P96/RXD; refs SCI1_RDR in register_field; cycles=6
|
||||
BB69: 15 FE DC D6 BCLR.B #6, @SCI1_SSR ; clear RDRF (bit 6) of SCI1_SSR; clear SCI1 receive-data-full flag (RDRF); candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: ROM clears SCI1 SSR.RDRF before reading SCI1_RDR; preserve this observed ordering even though the manual describes the canonical RDR-read then RDRF-clear sequence; confidence high; candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 ERI latches FAA4.bit7, clears ORER/FER/PER, then falls through into the same RXI byte-capture path; confidence high; SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; cycles=8
|
||||
BB6D: 15 FE DD 80 MOV:G.B @SCI1_RDR, R0 ; read SCI1 received byte from RDR; candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 RX ISR reads a byte from SCI1_RDR; confidence high; candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: ROM clears SCI1 SSR.RDRF before reading SCI1_RDR; preserve this observed ordering even though the manual describes the canonical RDR-read then RDRF-clear sequence; confidence high; candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 ERI latches FAA4.bit7, clears ORER/FER/PER, then falls through into the same RXI byte-capture path; confidence high; SCI1 RDR read receives from traced RS232/MAX202 path: MAX202 pin 12 -> H8 pin 67 P96/RXD; refs SCI1_RDR in register_field; cycles=6
|
||||
BB71: 15 F9 C1 16 TST.B @H'F9C1 ; refs ram_F9C1 in on_chip_ram; cycles=6
|
||||
BB75: 26 06 BNE loc_BB7D ; cycles=3/8 nt/t
|
||||
BB77: 15 F9 C3 13 CLR.B @H'F9C3 ; refs ram_F9C3 in on_chip_ram; cycles=8
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2944,18 +2944,18 @@ loc_BB56:
|
||||
void vec_sci1_eri_BB57(void)
|
||||
{
|
||||
/* vector sources: sci1_eri */
|
||||
MEM8[0xFAA4] |= BIT(7); /* BB57; BSET.B #7, @H'FAA4; refs ram_FAA4; cycles=8 */
|
||||
SCI1_SSR &= ~BIT(5); /* BB5B; BCLR.B #5, @SCI1_SSR; clear ORER (bit 5) of SCI1_SSR; clear SCI1 overrun error flag (ORER); SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; refs SCI1_SSR; cycles=8 */
|
||||
SCI1_SSR &= ~BIT(4); /* BB5F; BCLR.B #4, @SCI1_SSR; clear FER (bit 4) of SCI1_SSR; clear SCI1 framing error flag (FER); SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; refs SCI1_SSR; cycles=8 */
|
||||
SCI1_SSR &= ~BIT(3); /* BB63; BCLR.B #3, @SCI1_SSR; clear PER (bit 3) of SCI1_SSR; clear SCI1 parity error flag (PER); SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; refs SCI1_SSR; cycles=8 */
|
||||
MEM8[0xFAA4] |= BIT(7); /* BB57; BSET.B #7, @H'FAA4; candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 ERI latches FAA4.bit7, clears ORER/FER/PER, then falls through into the same RXI byte-capture path; confidence high; refs ram_FAA4; cycles=8 */
|
||||
SCI1_SSR &= ~BIT(5); /* BB5B; BCLR.B #5, @SCI1_SSR; clear ORER (bit 5) of SCI1_SSR; clear SCI1 overrun error flag (ORER); candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 ERI latches FAA4.bit7, clears ORER/FER/PER, then falls through into the same RXI byte-capture path; confidence high; SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; refs SCI1_SSR; cycles=8 */
|
||||
SCI1_SSR &= ~BIT(4); /* BB5F; BCLR.B #4, @SCI1_SSR; clear FER (bit 4) of SCI1_SSR; clear SCI1 framing error flag (FER); candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 ERI latches FAA4.bit7, clears ORER/FER/PER, then falls through into the same RXI byte-capture path; confidence high; SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; refs SCI1_SSR; cycles=8 */
|
||||
SCI1_SSR &= ~BIT(3); /* BB63; BCLR.B #3, @SCI1_SSR; clear PER (bit 3) of SCI1_SSR; clear SCI1 parity error flag (PER); candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 ERI latches FAA4.bit7, clears ORER/FER/PER, then falls through into the same RXI byte-capture path; confidence high; SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; refs SCI1_SSR; cycles=8 */
|
||||
}
|
||||
|
||||
void vec_sci1_rxi_BB67(void)
|
||||
{
|
||||
/* vector sources: sci1_rxi */
|
||||
push_registers(R0, R1); /* BB67; STM.W {R0,R1}, @-SP; cycles=12 */
|
||||
SCI1_SSR &= ~BIT(6); /* BB69; BCLR.B #6, @SCI1_SSR; clear RDRF (bit 6) of SCI1_SSR; clear SCI1 receive-data-full flag (RDRF); SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; refs SCI1_SSR; cycles=8 */
|
||||
R0 = (uint8_t)(SCI1_RDR); /* BB6D; MOV:G.B @SCI1_RDR, R0; read SCI1 received byte from RDR; candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 RX ISR reads a byte from SCI1_RDR; confidence high; SCI1 RDR read receives from traced RS232/MAX202 path: MAX202 pin 12 -> H8 pin 67 P96/RXD; refs SCI1_RDR; cycles=6 */
|
||||
SCI1_SSR &= ~BIT(6); /* BB69; BCLR.B #6, @SCI1_SSR; clear RDRF (bit 6) of SCI1_SSR; clear SCI1 receive-data-full flag (RDRF); candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: ROM clears SCI1 SSR.RDRF before reading SCI1_RDR; preserve this observed ordering even though the manual describes the canonical RDR-read then RDRF-clear sequence; confidence high; candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 ERI latches FAA4.bit7, clears ORER/FER/PER, then falls through into the same RXI byte-capture path; confidence high; SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use; refs SCI1_SSR; cycles=8 */
|
||||
R0 = (uint8_t)(SCI1_RDR); /* BB6D; MOV:G.B @SCI1_RDR, R0; read SCI1 received byte from RDR; candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 RX ISR reads a byte from SCI1_RDR; confidence high; candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: ROM clears SCI1 SSR.RDRF before reading SCI1_RDR; preserve this observed ordering even though the manual describes the canonical RDR-read then RDRF-clear sequence; confidence high; candidate/evidence-supported SCI1 6-byte RX frame; capture H'F868-H'F86D, validate H'F860-H'F865, checksum H'F865 seeded by H'005A; evidence: SCI1 ERI latches FAA4.bit7, clears ORER/FER/PER, then falls through into the same RXI byte-capture path; confidence high; SCI1 RDR read receives from traced RS232/MAX202 path: MAX202 pin 12 -> H8 pin 67 P96/RXD; refs SCI1_RDR; cycles=6 */
|
||||
set_flags_tst8(MEM8[0xF9C1]); /* BB71; TST.B @H'F9C1; refs ram_F9C1; cycles=6 */
|
||||
if (!Z) goto loc_BB7D; /* BB75; BNE loc_BB7D; cycles=3/8 nt/t */
|
||||
MEM8[0xF9C3] = 0; /* BB77; CLR.B @H'F9C3; refs ram_F9C3; cycles=8 */
|
||||
|
||||
417
build/rom_serial_pseudocode.c
Normal file
417
build/rom_serial_pseudocode.c
Normal file
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
* H8/536 focused SCI RX/TX pseudocode from build\rom_decompiled.json
|
||||
*
|
||||
* This is a protocol-oriented reconstruction from decompiler JSON metadata.
|
||||
* It is intentionally phrased as candidate behavior: it summarizes evidence
|
||||
* from the ROM without claiming source-level intent or a proven packet format.
|
||||
*/
|
||||
|
||||
/* Candidate summary:
|
||||
* - SCI1 TX 6-byte frame at H'F858-H'F85D: confidence high (0.95)
|
||||
* reason: all required independent evidence groups were observed
|
||||
* - SCI1 RX 6-byte frame captured at H'F868-H'F86D: confidence high (0.9)
|
||||
* reason: RX count, copy, and checksum-validation evidence were observed; no explicit header/sync byte was identified
|
||||
* caveat: candidate frame means six consecutive bytes within the observed RX timing/state machine, not a proven delimited packet
|
||||
*/
|
||||
|
||||
/* Board path:
|
||||
* Board trace ties the H8/536 SCI1 pins to a MAX202 RS232 transceiver.
|
||||
* - TXD: H8 pin 66 P95/TXD <-> MAX202 pin 11
|
||||
* evidence: MAX202 pin 11 traces to H8 pin 66
|
||||
* - RXD: H8 pin 67 P96/RXD <-> MAX202 pin 12
|
||||
* evidence: MAX202 pin 12 traces to H8 pin 67
|
||||
*/
|
||||
|
||||
/* Manual anchors used by the decompiler metadata:
|
||||
* - Manual/0900766b802125d0.md:15748 SCI register map for RDR/TDR/SCR/SSR
|
||||
* - Manual/0900766b802125d0.md:15794 RDR stores received data and is CPU-readable
|
||||
* - Manual/0900766b802125d0.md:15823 TDR holds the next byte to transmit
|
||||
* - Manual/0900766b802125d0.md:15976 SCR.TIE enables/disables TXI on TDRE
|
||||
* - Manual/0900766b802125d0.md:15993 SCR.RIE enables RXI and ERI
|
||||
* - Manual/0900766b802125d0.md:16008 SCR.TE enables the transmitter
|
||||
* - Manual/0900766b802125d0.md:16028 SCR.RE enables the receiver
|
||||
* - Manual/0900766b802125d0.md:16090 SSR flags are cleared by writing zero
|
||||
* - Manual/0900766b802125d0.md:16100 SSR.TDRE means TDR can accept the next byte
|
||||
* - Manual/0900766b802125d0.md:16116 SSR.RDRF means received data reached RDR
|
||||
* - Manual/0900766b802125d0.md:16127 SSR.ORER reports receive overrun
|
||||
* - Manual/0900766b802125d0.md:16140 SSR.FER reports framing errors
|
||||
* - Manual/0900766b802125d0.md:16147 SSR.PER reports parity errors
|
||||
* - Manual/0900766b802125d0.md:2417 FP-80 H8/536 pin 66 is P95/TXD
|
||||
* - Manual/0900766b802125d0.md:2418 FP-80 H8/536 pin 67 is P96/RXD
|
||||
* - Manual/0900766b802125d0.md:11192 Port 9 carries SCI1 and SCI2 serial signals
|
||||
* - Manual/0900766b802125d0.md:11201 P96 is RXD1 input
|
||||
* - Manual/0900766b802125d0.md:11202 P95 is TXD1 output
|
||||
* - Manual/0900766b802125d0.md:15725 SCI1 RXD input pin
|
||||
* - Manual/0900766b802125d0.md:15726 SCI1 TXD output pin
|
||||
* - Manual/0900766b802125d0.md:15750 SCI register table starts with SCI1 RDR/TDR/SMR/SCR/SSR/BRR
|
||||
* - Manual/0900766b802125d0.md:15758 SCI register table lists SCI2 RDR/TDR/SMR/SCR/SSR/BRR
|
||||
* - Manual/0900766b802125d0.md:15794 RDR receive data register
|
||||
* - Manual/0900766b802125d0.md:15823 TDR transmit data register
|
||||
* - Manual/0900766b802125d0.md:15969 SCR enables and disables SCI functions
|
||||
* - Manual/0900766b802125d0.md:16009 SCR.TE makes the TXD pin output
|
||||
* - Manual/0900766b802125d0.md:16029 SCR.RE makes the RXD pin input
|
||||
* - Manual/0900766b802125d0.md:16090 SSR contains transmit/receive status flags
|
||||
* - Manual/0900766b802125d0.md:10560 SYSCR2 controls port 9 pin functions
|
||||
* - Manual/0900766b802125d0.md:10631 SYSCR2.P9SCI2E controls the SCI2 functions of P92-P94
|
||||
*/
|
||||
|
||||
/* SCI1 link layer:
|
||||
* - 8E1 SCI characters carry the 6 protocol bytes; async 8-bit even parity 1 stop, clock internal, SMR=H'24, BRR=H'07, SCR=H'3C
|
||||
* - baud is clock-dependent; pass --clock-hz on the decompiler for bps.
|
||||
* - No SCI1 RXI/TXI DTC vector entries are present in JSON; RX/TX are modeled as CPU ISR paths.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
|
||||
extern volatile u8 MEM8[0x10000];
|
||||
|
||||
#define SCI1_SCR MEM8[0xFEDAu]
|
||||
#define SCI1_TDR MEM8[0xFEDBu]
|
||||
#define SCI1_SSR MEM8[0xFEDCu]
|
||||
#define SCI1_RDR MEM8[0xFEDDu]
|
||||
|
||||
#define SCI_SCR_TIE 0x80u
|
||||
#define SCI_SCR_RIE 0x40u
|
||||
#define SCI_SCR_TE 0x20u
|
||||
#define SCI_SCR_RE 0x10u
|
||||
#define SCI_SSR_TDRE 0x80u
|
||||
#define SCI_SSR_RDRF 0x40u
|
||||
#define SCI_SSR_ORER 0x20u
|
||||
#define SCI_SSR_FER 0x10u
|
||||
#define SCI_SSR_PER 0x08u
|
||||
|
||||
#define TX_FRAME_LENGTH 6u
|
||||
#define TX_FRAME(n) MEM8[(u16)(0xF858u + (n))]
|
||||
#define TX_INDEX MEM8[0xF9C2u]
|
||||
|
||||
#define RX_FRAME_LENGTH 6u
|
||||
#define RX_CAPTURE(n) MEM8[(u16)(0xF868u + (n))]
|
||||
#define RX_FRAME(n) MEM8[(u16)(0xF860u + (n))]
|
||||
#define RX_INDEX MEM8[0xF9C3u]
|
||||
#define RX_INTERBYTE_TIMEOUT MEM8[0xF9C1u]
|
||||
#define RX_COMPLETE_TIMER MEM8[0xF9C5u]
|
||||
#define RX_ERROR_LATCH MEM8[0xFAA4u]
|
||||
#define RX_ERROR_LATCH_PHYSICAL_ERROR 0x80u
|
||||
|
||||
/* 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
|
||||
* command effects:
|
||||
* - H'00 set_value_acked: Candidate acknowledged set: writes value bytes to primary/current tables, flags the index, and stages an echo-style response.
|
||||
* effect: table_write_candidate; target primary_value_table_candidate; source RX[3:4] value bytes, with an observed 0x80 fallback when decoded index is zero; table H'E000
|
||||
* effect: table_write_candidate; target current_value_table_candidate; source same candidate value written to the primary table; table H'E800
|
||||
* effect: flag_update_candidate; target per_index_flag_table_candidate; set bit 7; table H'EC00
|
||||
* 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
|
||||
* - H'01 read_value: Candidate read: reads the primary table and stages a value response.
|
||||
* effect: table_read_candidate; target primary_value_table_candidate; table H'E000
|
||||
* effect: response_staging_candidate; stage F850-F854 and call loc_BA26
|
||||
* 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
|
||||
* - H'02 clear_or_abort: Candidate clear/abort: clears serial session state without an observed immediate response.
|
||||
* effect: state_clear_candidate; target serial_session_flags_candidate; clear bit 7; state H'FAA2
|
||||
* evidence: H'BC08, H'BC0C, H'BC29, H'BC2B
|
||||
* - H'04 set_value_no_immediate_reply: Candidate deferred set: writes value bytes and flags the index without an observed immediate response.
|
||||
* effect: table_write_candidate; target primary_value_table_candidate; source RX[3:4] value bytes, with an observed 0x80 fallback when decoded index is zero; table H'E000
|
||||
* effect: flag_update_candidate; target per_index_flag_table_candidate; set bit 7; table H'EC00
|
||||
* evidence: H'BC08, H'BC0C, H'BC45, H'BC47
|
||||
* - H'05 ack_or_clear_pending: Candidate acknowledgement/clear: updates pending/event state without an observed immediate response.
|
||||
* effect: pending_acknowledgement_candidate; target selected event/pending state; clear selected pending flags and then clear serial session state
|
||||
* evidence: H'BC08, H'BC0C, H'BC4A, H'BC4C
|
||||
* - H'06 set_secondary_value: Candidate secondary set: writes value bytes to the secondary table and flags the index.
|
||||
* effect: table_write_candidate; target secondary_value_table_candidate; source RX[3:4] value bytes; table H'E400
|
||||
* effect: flag_update_candidate; target per_index_flag_table_candidate; set bit 6; table H'EC00
|
||||
* evidence: H'BC08, H'BC0C, H'BC4F, H'BC51
|
||||
* - H'07 retransmit_or_error_reply: Candidate retransmit/error reply: reuses prior TX bytes or builds an explicit 0x07 retry/error response.
|
||||
* effect: retransmit_candidate; target TX staging bytes H'F850-H'F854 before loc_BA26; source previous TX frame bytes H'F858-H'F85C
|
||||
* effect: response_staging_candidate; stage F850-F854 and call loc_BA26
|
||||
* evidence: H'BC08, H'BC0C, H'BC2E, H'BC30, H'BC54, H'BC56, H'BE09, H'BE11, H'BE19, H'BE22
|
||||
* response schemas:
|
||||
* - response_at_BB43: byte0=computed; byte1=computed; byte2=computed; byte3=current_value_table_candidate; byte4=current_value_table_candidate
|
||||
* evidence: H'BB1C, H'BB2B, H'BB20, H'BB3F, H'BB39
|
||||
* - response_at_BCCD: byte0=0x04; byte1=rx[1]; byte2=rx[2]; byte3=rx[3]; byte4=rx[4]
|
||||
* evidence: H'BCB0, H'BCB9, H'BCC1, H'BCC9
|
||||
* - response_at_BCFA: byte0=0x04; byte1=rx[2]; byte2=rx[2]; byte3=primary_value_table_candidate; byte4=primary_value_table_candidate
|
||||
* evidence: H'BCD7, H'BCE8, H'BCC1, H'BCF6, H'BCF0
|
||||
* - response_at_BE22: byte0=tx[0]; byte1=tx[1]; byte2=tx[2]; byte3=tx[3]; byte4=tx[4]
|
||||
* evidence: H'BE09, H'BE11, H'BE19
|
||||
* - ... 1 more candidate response schemas
|
||||
* table map candidates:
|
||||
* - primary_value_table_candidate at H'E000 (word_value); observed read, write
|
||||
* evidence: H'19E3, H'1A03, H'1A3D, H'1A6B, H'3F8C, H'4077, H'BC75, H'BC95, H'BCEC, H'BD1A, H'BD35
|
||||
* - secondary_value_table_candidate at H'E400 (word_value); observed read, write
|
||||
* evidence: H'19AA, H'1A4B, H'1A5B, H'1A81, H'1AB4, H'1AC1, H'407B, H'BDE5
|
||||
* - current_value_table_candidate at H'E800 (word_value); observed read, write
|
||||
* evidence: H'1A09, H'1A71, H'3F90, H'407F, H'BB35, H'BC79, H'BC99, H'BD1E
|
||||
* - flag_table_candidate at H'EC00 (bit_flags); observed write
|
||||
* evidence: H'4088, H'BC82, H'BC9D, H'BD22, H'BD39, H'BDE9
|
||||
* state variable candidates:
|
||||
* - event_queue_read_cursor_candidate H'F9B4: reads 1, writes 2; bits 5
|
||||
* evidence: H'BE78, H'BE95, H'BE99
|
||||
* - event_queue_write_or_pending_cursor_candidate H'F9B5: reads 1, writes 6; bits 7
|
||||
* evidence: H'BAF2, H'BD6D, H'BD71, H'BDC8, H'BDCC, H'BDF3, H'BDF7
|
||||
* - event_queue_base_or_current_slot_candidate H'F9B9: reads 1, writes 0
|
||||
* evidence: H'BE70
|
||||
* - serial_tx_busy_timer_candidate H'F9C0: reads 2, writes 8
|
||||
* evidence: H'BA26, H'BA2C, H'BAA2, H'BADA, H'BAE1, H'BAE8, H'BE1D, H'BE3E, H'BEEE, H'BEF4
|
||||
* - serial_session_flags_candidate H'FAA2: reads 5, writes 13; bits 3, 7
|
||||
* evidence: H'BA84, H'BA96, H'BB00, H'BC0F, H'BC15, H'BC33, H'BC5C, H'BCD0, H'BCFD, H'BD04, H'BD67, H'BD79, H'BDC2, H'BDD4, H'BDED, H'BDFF, H'BE47, H'BEAF
|
||||
* - serial_pending_mask_candidate H'FAA3: reads 1, writes 9; bits 7
|
||||
* evidence: H'BA9A, H'BB51, H'BC63, H'BD75, H'BDD0, H'BDFB, H'BE43, H'BEA5, H'BEA9, H'BECB
|
||||
* - ... 3 more state-variable candidates
|
||||
* retry/error model candidate:
|
||||
* - checksum path: 0x5A-seeded XOR over RX[0..4] differs from RX[5] -> loc_BE29
|
||||
* - retry path: counter H'FAA6, threshold 2; Candidate retry path clears/consults serial flags, increments FAA6, compares it with 2, and when still below the apparent limit stages a command 0x07 response.
|
||||
* - command 0x07 path: Candidate retransmit/explicit command 0x07 path either copies previous TX frame bytes back to F850-F854 or stages an observed 0x07 response before loc_BA26.
|
||||
* - evidence: H'BBD8, H'BBDC, H'BBE0, H'BBE4, H'BBE8, H'BBEC, H'BBF0, H'BE4D, H'BE56, H'BE5E, H'BE66, H'BE52, H'BE5A, H'BE62, H'BE6A, H'BE29, H'BE2D, H'BE33, H'BE37, H'BE43, H'BE47, H'BE05, H'BE0D, H'BE15, H'BE09, H'BE11, H'BE19, H'BE22
|
||||
*/
|
||||
|
||||
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
|
||||
* candidate effect: table_write_candidate; target primary_value_table_candidate; source RX[3:4] value bytes, with an observed 0x80 fallback when decoded index is zero; table H'E000
|
||||
* candidate effect: table_write_candidate; target current_value_table_candidate; source same candidate value written to the primary table; table H'E800
|
||||
* candidate effect: flag_update_candidate; target per_index_flag_table_candidate; set bit 7; table H'EC00
|
||||
* 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
|
||||
* candidate effect: table_read_candidate; target primary_value_table_candidate; table H'E000
|
||||
* candidate effect: response_staging_candidate; stage F850-F854 and call loc_BA26
|
||||
* 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
|
||||
* candidate effect: state_clear_candidate; target serial_session_flags_candidate; clear bit 7; state H'FAA2
|
||||
* 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
|
||||
* candidate effect: table_write_candidate; target primary_value_table_candidate; source RX[3:4] value bytes, with an observed 0x80 fallback when decoded index is zero; table H'E000
|
||||
* candidate effect: flag_update_candidate; target per_index_flag_table_candidate; set bit 7; table H'EC00
|
||||
* 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
|
||||
* candidate effect: pending_acknowledgement_candidate; target selected event/pending state; clear selected pending flags and then clear serial session state
|
||||
* 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
|
||||
* candidate effect: table_write_candidate; target secondary_value_table_candidate; source RX[3:4] value bytes; table H'E400
|
||||
* candidate effect: flag_update_candidate; target per_index_flag_table_candidate; set bit 6; table H'EC00
|
||||
* 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
|
||||
* candidate effect: retransmit_candidate; target TX staging bytes H'F850-H'F854 before loc_BA26; source previous TX frame bytes H'F858-H'F85C
|
||||
* candidate effect: response_staging_candidate; stage F850-F854 and call loc_BA26
|
||||
* 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
|
||||
* checksum formula: checksum = 0x5A ^ byte0 ^ byte1 ^ byte2 ^ byte3 ^ byte4
|
||||
* evidence addresses:
|
||||
* - checksum_byte: H'BA64
|
||||
* - initial_send_from_buffer_start: H'BA6E, H'BA72
|
||||
* - tx_buffer_region: H'BA3A, H'BA42, H'BA4A, H'BA50, H'BA54, H'BA58, H'BA5C, H'BA60, H'BA64, H'BA6E, H'BE05, H'BE0D, H'BE15
|
||||
* - tx_checksum_seed: H'BA4E
|
||||
* - tx_index_compare_frame_length: H'BAC3
|
||||
* - tx_index_increment: H'BABF
|
||||
* - tx_index_initialized_to_one: H'BA76
|
||||
* - tx_isr_indexed_send: H'BAAB, H'BAB1, H'BAB5
|
||||
* - xor_checksum_chain: H'BA50, H'BA54, H'BA58, H'BA5C, H'BA60, H'BA64
|
||||
*/
|
||||
static u8 sci1_tx_candidate_checksum(void)
|
||||
{
|
||||
u8 checksum = 0x5Au;
|
||||
checksum ^= TX_FRAME(0);
|
||||
checksum ^= TX_FRAME(1);
|
||||
checksum ^= TX_FRAME(2);
|
||||
checksum ^= TX_FRAME(3);
|
||||
checksum ^= TX_FRAME(4);
|
||||
return checksum;
|
||||
}
|
||||
|
||||
void sci1_tx_start_candidate_frame(void)
|
||||
{
|
||||
/* The ROM appears to have populated TX_FRAME(0..4) before this point. */
|
||||
TX_FRAME(5) = sci1_tx_candidate_checksum();
|
||||
|
||||
while ((SCI1_SSR & SCI_SSR_TDRE) == 0u) {
|
||||
/* wait for transmit data register empty */
|
||||
}
|
||||
|
||||
SCI1_TDR = TX_FRAME(0);
|
||||
TX_INDEX = 1u;
|
||||
SCI1_SSR &= (u8)~SCI_SSR_TDRE;
|
||||
SCI1_SCR |= SCI_SCR_TIE;
|
||||
}
|
||||
|
||||
void sci1_txi_candidate_isr(void)
|
||||
{
|
||||
if (TX_INDEX < TX_FRAME_LENGTH) {
|
||||
SCI1_TDR = TX_FRAME(TX_INDEX);
|
||||
TX_INDEX = (u8)(TX_INDEX + 1u);
|
||||
SCI1_SSR &= (u8)~SCI_SSR_TDRE;
|
||||
}
|
||||
|
||||
if (TX_INDEX >= TX_FRAME_LENGTH) {
|
||||
SCI1_SCR &= (u8)~SCI_SCR_TIE;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* RX reconstruction evidence
|
||||
* candidate/evidence-supported SCI1 6-byte RX frame hypothesis using capture buffer H'F868-H'F86D; checksum byte H'F865 is validated against XOR seeded by H'005A
|
||||
* checksum formula: checksum = 0x5A ^ byte0 ^ byte1 ^ byte2 ^ byte3 ^ byte4
|
||||
* RX error handling: SCI1 ERI appears to mark a physical receive error and continue into the RXI byte-capture path; the RXI path clears RDRF before reading RDR in the ROM order.
|
||||
* RX error caveat: Manual text distinguishes ORER from FER/PER data transfer into RDR and describes the normal RDR-read then RDRF-clear ordering; this output preserves the observed ROM order.
|
||||
* evidence addresses:
|
||||
* - rx_checksum_seed: H'BBD6
|
||||
* - rx_complete_timer: H'BB9E
|
||||
* - rx_copy_capture_to_frame_buffer: H'BBB3, H'BBBB, H'BBC3, H'BBB7, H'BBBF, H'BBC7
|
||||
* - rx_eri_falls_through_to_rxi: H'BB57, H'BB5B, H'BB5F, H'BB63, H'BB69, H'BB6D
|
||||
* - rx_index_increment_store: H'BB94, H'BB96
|
||||
* - rx_indexed_store: H'BB90
|
||||
* - rx_isr_compare_frame_length: H'BB9A
|
||||
* - rx_processor_requires_six_bytes: H'BBAB
|
||||
* - rx_rdr_read: H'BB6D
|
||||
* - rx_rdrf_clear_before_rdr_read: H'BB69, H'BB6D
|
||||
* - rx_xor_checksum_validation: H'BBD6, H'BBD8, H'BBDC, H'BBE0, H'BBE4, H'BBE8, H'BBEC
|
||||
*/
|
||||
static u8 sci1_rx_candidate_checksum(void)
|
||||
{
|
||||
u8 checksum = 0x5Au;
|
||||
checksum ^= RX_FRAME(0);
|
||||
checksum ^= RX_FRAME(1);
|
||||
checksum ^= RX_FRAME(2);
|
||||
checksum ^= RX_FRAME(3);
|
||||
checksum ^= RX_FRAME(4);
|
||||
return checksum;
|
||||
}
|
||||
|
||||
bool sci1_process_rx_candidate_frame(void)
|
||||
{
|
||||
u8 i;
|
||||
|
||||
if (RX_INDEX != RX_FRAME_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0u; i < RX_FRAME_LENGTH; i++) {
|
||||
RX_FRAME(i) = RX_CAPTURE(i);
|
||||
}
|
||||
|
||||
if (sci1_rx_candidate_checksum() != RX_FRAME(5)) {
|
||||
RX_INDEX = 0u;
|
||||
return false;
|
||||
}
|
||||
|
||||
RX_INDEX = 0u;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sci1_rx_byte_received_candidate_isr(void)
|
||||
{
|
||||
u8 byte;
|
||||
|
||||
SCI1_SSR &= (u8)~SCI_SSR_RDRF;
|
||||
byte = SCI1_RDR;
|
||||
|
||||
if (RX_INTERBYTE_TIMEOUT == 0u) {
|
||||
RX_INDEX = 0u;
|
||||
}
|
||||
|
||||
RX_INTERBYTE_TIMEOUT = 5u;
|
||||
RX_CAPTURE(RX_INDEX) = byte;
|
||||
RX_INDEX = (u8)(RX_INDEX + 1u);
|
||||
|
||||
if (RX_INDEX == RX_FRAME_LENGTH) {
|
||||
RX_COMPLETE_TIMER = 0x14u;
|
||||
return sci1_process_rx_candidate_frame();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void sci1_rx_error_candidate_isr(void)
|
||||
{
|
||||
RX_ERROR_LATCH |= RX_ERROR_LATCH_PHYSICAL_ERROR;
|
||||
SCI1_SSR &= (u8)~(SCI_SSR_ORER | SCI_SSR_FER | SCI_SSR_PER);
|
||||
sci1_rx_byte_received_candidate_isr();
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
|
||||
|
||||
923
h8536/serial_pseudocode.py
Normal file
923
h8536/serial_pseudocode.py
Normal file
@@ -0,0 +1,923 @@
|
||||
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]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SerialPseudocodeOptions:
|
||||
include_tx: bool = True
|
||||
include_rx: bool = True
|
||||
include_evidence: bool = True
|
||||
include_manual: bool = True
|
||||
include_board: bool = True
|
||||
include_semantics: bool = True
|
||||
|
||||
|
||||
def generate_serial_pseudocode(
|
||||
payload: JsonObject,
|
||||
*,
|
||||
source_name: str = "",
|
||||
options: SerialPseudocodeOptions | None = None,
|
||||
) -> str:
|
||||
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))
|
||||
if opts.include_board:
|
||||
lines.extend(_board_comment_lines(payload))
|
||||
if opts.include_manual:
|
||||
lines.extend(_manual_reference_lines(payload))
|
||||
lines.extend(_sci_link_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:
|
||||
lines.extend(_tx_functions(tx_candidate, opts))
|
||||
emitted = True
|
||||
if opts.include_rx and rx_candidate:
|
||||
lines.extend(_rx_functions(rx_candidate, opts))
|
||||
emitted = True
|
||||
|
||||
if not emitted:
|
||||
lines.append("/* No requested SCI serial reconstruction candidates were present in the JSON input. */")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def load_serial_pseudocode_input(path: Path) -> JsonObject:
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
payload = json.load(handle)
|
||||
if not isinstance(payload, dict) or "instructions" not in payload:
|
||||
raise ValueError(f"{path} does not look like h8536_decompiler JSON output")
|
||||
return payload
|
||||
|
||||
|
||||
def write_serial_pseudocode(
|
||||
input_path: Path,
|
||||
output_path: Path,
|
||||
options: SerialPseudocodeOptions,
|
||||
) -> None:
|
||||
payload = load_serial_pseudocode_input(input_path)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
output_path.write_text(
|
||||
generate_serial_pseudocode(payload, source_name=str(input_path), options=options),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate focused C-like SCI RX/TX pseudocode from h8536_decompiler JSON output.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"input",
|
||||
nargs="?",
|
||||
type=Path,
|
||||
default=Path("build/rom_decompiled.json"),
|
||||
help="structured JSON emitted by h8536_decompiler.py",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out",
|
||||
type=Path,
|
||||
default=Path("build/rom_serial_pseudocode.c"),
|
||||
help="focused serial pseudocode output path",
|
||||
)
|
||||
mode = parser.add_mutually_exclusive_group()
|
||||
mode.add_argument("--tx-only", action="store_true", help="emit only the candidate TX path")
|
||||
mode.add_argument("--rx-only", action="store_true", help="emit only the candidate RX path")
|
||||
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(
|
||||
include_tx=not args.rx_only,
|
||||
include_rx=not args.tx_only,
|
||||
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}")
|
||||
return 0
|
||||
|
||||
|
||||
def _file_header(
|
||||
source_name: str,
|
||||
tx_candidate: JsonObject | None,
|
||||
rx_candidate: JsonObject | None,
|
||||
) -> list[str]:
|
||||
source = f" from {source_name}" if source_name else ""
|
||||
lines = [
|
||||
"/*",
|
||||
f" * H8/536 focused SCI RX/TX pseudocode{source}",
|
||||
" *",
|
||||
" * This is a protocol-oriented reconstruction from decompiler JSON metadata.",
|
||||
" * It is intentionally phrased as candidate behavior: it summarizes evidence",
|
||||
" * from the ROM without claiming source-level intent or a proven packet format.",
|
||||
" */",
|
||||
"",
|
||||
]
|
||||
if tx_candidate or rx_candidate:
|
||||
lines.append("/* Candidate summary:")
|
||||
for candidate in (tx_candidate, rx_candidate):
|
||||
if not candidate:
|
||||
continue
|
||||
lines.append(
|
||||
" * - "
|
||||
+ _candidate_label(candidate)
|
||||
+ f": confidence {candidate.get('confidence', 'unknown')} "
|
||||
+ f"({candidate.get('confidence_score', 'n/a')})"
|
||||
)
|
||||
reason = str(candidate.get("confidence_reason") or "").strip()
|
||||
if reason:
|
||||
lines.append(f" * reason: {_comment_text(reason)}")
|
||||
caveat = str(candidate.get("caveat") or "").strip()
|
||||
if caveat:
|
||||
lines.append(f" * caveat: {_comment_text(caveat)}")
|
||||
lines.append(" */")
|
||||
lines.append("")
|
||||
return lines
|
||||
|
||||
|
||||
def _board_comment_lines(payload: JsonObject) -> list[str]:
|
||||
board = payload.get("board_profile")
|
||||
if not isinstance(board, dict):
|
||||
return []
|
||||
lines = ["/* Board path:"]
|
||||
summary = str(board.get("summary") or "").strip()
|
||||
if summary:
|
||||
lines.append(f" * {summary}")
|
||||
for trace in _mapping_items(board.get("traces")):
|
||||
signal = trace.get("signal", "?")
|
||||
h8_pin = trace.get("h8_pin", "?")
|
||||
h8_name = trace.get("h8_pin_name", "?")
|
||||
max202_pin = trace.get("max202_pin", "?")
|
||||
evidence = str(trace.get("evidence") or "").strip()
|
||||
lines.append(f" * - {signal}: H8 pin {h8_pin} {h8_name} <-> MAX202 pin {max202_pin}")
|
||||
if evidence:
|
||||
lines.append(f" * evidence: {_comment_text(evidence)}")
|
||||
lines.append(" */")
|
||||
lines.append("")
|
||||
return lines
|
||||
|
||||
|
||||
def _manual_reference_lines(payload: JsonObject) -> list[str]:
|
||||
refs: list[str] = []
|
||||
for section in ("sci_protocol", "board_profile"):
|
||||
data = payload.get(section)
|
||||
if isinstance(data, dict):
|
||||
refs.extend(str(ref) for ref in data.get("manual_references", []) if ref)
|
||||
refs = _dedupe(refs)
|
||||
if not refs:
|
||||
return []
|
||||
lines = ["/* Manual anchors used by the decompiler metadata:"]
|
||||
for ref in refs:
|
||||
lines.append(f" * - {_comment_text(ref)}")
|
||||
lines.append(" */")
|
||||
lines.append("")
|
||||
return lines
|
||||
|
||||
|
||||
def _sci_link_lines(payload: JsonObject) -> list[str]:
|
||||
config = _first_sci1_configuration(payload)
|
||||
dtc_note = _dtc_sci_note(payload)
|
||||
if not config and not dtc_note:
|
||||
return []
|
||||
|
||||
lines = ["/* SCI1 link layer:"]
|
||||
if config:
|
||||
mode_summary = str(config.get("mode_summary") or config.get("mode") or "SCI mode")
|
||||
char_format = "8E1" if "8-bit even parity 1 stop" in mode_summary else mode_summary
|
||||
smr = config.get("smr_hex") or _optional_hex(config.get("smr"), width=2)
|
||||
brr = config.get("brr_hex") or _optional_hex(config.get("brr"), width=2)
|
||||
scr = config.get("scr_hex") or _optional_hex(config.get("scr"), width=2)
|
||||
clock_source = config.get("clock_source") or "clock source unknown"
|
||||
lines.append(
|
||||
" * - "
|
||||
+ _comment_text(
|
||||
f"{char_format} SCI characters carry the 6 protocol bytes; "
|
||||
f"{mode_summary}, clock {clock_source}, SMR={smr}, BRR={brr}, SCR={scr}",
|
||||
),
|
||||
)
|
||||
baud = config.get("baud_bps")
|
||||
if isinstance(baud, (int, float)):
|
||||
lines.append(f" * - candidate baud: {_comment_text(str(baud))} bps")
|
||||
else:
|
||||
lines.append(" * - baud is clock-dependent; pass --clock-hz on the decompiler for bps.")
|
||||
if dtc_note:
|
||||
lines.append(f" * - {_comment_text(dtc_note)}")
|
||||
lines.append(" */")
|
||||
lines.append("")
|
||||
return lines
|
||||
|
||||
|
||||
def _first_sci1_configuration(payload: JsonObject) -> JsonObject | None:
|
||||
sci = payload.get("sci")
|
||||
if not isinstance(sci, dict):
|
||||
return None
|
||||
channels = sci.get("channels")
|
||||
if not isinstance(channels, dict):
|
||||
return None
|
||||
sci1 = channels.get("SCI1")
|
||||
if not isinstance(sci1, dict):
|
||||
return None
|
||||
configurations = sci1.get("configurations")
|
||||
if not isinstance(configurations, list):
|
||||
return None
|
||||
for item in configurations:
|
||||
if isinstance(item, dict):
|
||||
return item
|
||||
return None
|
||||
|
||||
|
||||
def _dtc_sci_note(payload: JsonObject) -> str:
|
||||
vectors = payload.get("dtc_vectors")
|
||||
if not isinstance(vectors, list):
|
||||
return ""
|
||||
sources = {
|
||||
str(item.get("source", "")).lower()
|
||||
for item in vectors
|
||||
if isinstance(item, dict)
|
||||
}
|
||||
if "sci1_rxi" in sources or "sci1_txi" in sources:
|
||||
return "SCI1 DTC vector entries are present; review DTC metadata before treating RX/TX as CPU-only."
|
||||
return "No SCI1 RXI/TXI DTC vector entries are present in JSON; RX/TX are modeled as CPU ISR paths."
|
||||
|
||||
|
||||
def _declarations(tx_candidate: JsonObject | None, rx_candidate: JsonObject | None) -> list[str]:
|
||||
candidate = tx_candidate or rx_candidate or {}
|
||||
channel = str(candidate.get("channel") or "SCI1")
|
||||
tdr = _int_field(tx_candidate, "tdr_address", 0xFEDB)
|
||||
rdr = _int_field(rx_candidate, "rdr_address", 0xFEDD)
|
||||
scr = tdr - 1 if tdr else 0xFEDA
|
||||
ssr = rdr - 1 if rdr else 0xFEDC
|
||||
|
||||
lines = [
|
||||
"#include <stdbool.h>",
|
||||
"#include <stdint.h>",
|
||||
"",
|
||||
"typedef uint8_t u8;",
|
||||
"typedef uint16_t u16;",
|
||||
"",
|
||||
"extern volatile u8 MEM8[0x10000];",
|
||||
"",
|
||||
f"#define {channel}_SCR MEM8[{_c_hex(scr)}]",
|
||||
f"#define {channel}_TDR MEM8[{_c_hex(tdr)}]",
|
||||
f"#define {channel}_SSR MEM8[{_c_hex(ssr)}]",
|
||||
f"#define {channel}_RDR MEM8[{_c_hex(rdr)}]",
|
||||
"",
|
||||
"#define SCI_SCR_TIE 0x80u",
|
||||
"#define SCI_SCR_RIE 0x40u",
|
||||
"#define SCI_SCR_TE 0x20u",
|
||||
"#define SCI_SCR_RE 0x10u",
|
||||
"#define SCI_SSR_TDRE 0x80u",
|
||||
"#define SCI_SSR_RDRF 0x40u",
|
||||
"#define SCI_SSR_ORER 0x20u",
|
||||
"#define SCI_SSR_FER 0x10u",
|
||||
"#define SCI_SSR_PER 0x08u",
|
||||
"",
|
||||
]
|
||||
|
||||
if tx_candidate:
|
||||
tx_start = _int_field(tx_candidate, "buffer_start", 0xF858)
|
||||
tx_index = _int_field(tx_candidate, "tx_index_address", 0xF9C2)
|
||||
length = _int_field(tx_candidate, "frame_length", 6)
|
||||
lines.extend(
|
||||
[
|
||||
f"#define TX_FRAME_LENGTH {length}u",
|
||||
f"#define TX_FRAME(n) MEM8[(u16)({_c_hex(tx_start)} + (n))]",
|
||||
f"#define TX_INDEX MEM8[{_c_hex(tx_index)}]",
|
||||
"",
|
||||
],
|
||||
)
|
||||
|
||||
if rx_candidate:
|
||||
capture_start = _int_field(rx_candidate, "capture_buffer_start", 0xF868)
|
||||
frame_start = _int_field(rx_candidate, "validation_buffer_start", 0xF860)
|
||||
rx_index = _int_field(rx_candidate, "rx_index_address", 0xF9C3)
|
||||
timeout = _int_field(rx_candidate, "interbyte_timeout_address", 0xF9C1)
|
||||
complete = _int_field(rx_candidate, "complete_timer_address", 0xF9C5)
|
||||
length = _int_field(rx_candidate, "frame_length", 6)
|
||||
error_handling = rx_candidate.get("rx_error_handling")
|
||||
error_latch = 0xFAA4
|
||||
if isinstance(error_handling, dict):
|
||||
error_latch = _int_field(error_handling, "error_latch_address", 0xFAA4)
|
||||
lines.extend(
|
||||
[
|
||||
f"#define RX_FRAME_LENGTH {length}u",
|
||||
f"#define RX_CAPTURE(n) MEM8[(u16)({_c_hex(capture_start)} + (n))]",
|
||||
f"#define RX_FRAME(n) MEM8[(u16)({_c_hex(frame_start)} + (n))]",
|
||||
f"#define RX_INDEX MEM8[{_c_hex(rx_index)}]",
|
||||
f"#define RX_INTERBYTE_TIMEOUT MEM8[{_c_hex(timeout)}]",
|
||||
f"#define RX_COMPLETE_TIMER MEM8[{_c_hex(complete)}]",
|
||||
f"#define RX_ERROR_LATCH MEM8[{_c_hex(error_latch)}]",
|
||||
"#define RX_ERROR_LATCH_PHYSICAL_ERROR 0x80u",
|
||||
"",
|
||||
],
|
||||
)
|
||||
|
||||
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.extend(_command_effect_comment_lines(protocol.get("command_effects"), opts, prefix=" * "))
|
||||
lines.extend(_response_schema_comment_lines(_schema_list(protocol), opts, prefix=" * "))
|
||||
lines.extend(_table_map_comment_lines(_table_map_list(protocol), opts, prefix=" * "))
|
||||
lines.extend(_state_variable_comment_lines(protocol.get("state_variable_candidates"), opts, prefix=" * "))
|
||||
lines.extend(_retry_error_comment_lines(protocol.get("retry_error_model"), opts, prefix=" * "))
|
||||
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}")
|
||||
for effect_line in _command_effect_switch_lines(command):
|
||||
lines.append(f" * {effect_line}")
|
||||
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 _command_effect_comment_lines(
|
||||
value: object,
|
||||
opts: SerialPseudocodeOptions,
|
||||
*,
|
||||
prefix: str,
|
||||
) -> list[str]:
|
||||
effects = [item for item in _object_list(value) if item.get("effects") or item.get("summary")]
|
||||
if not effects:
|
||||
return []
|
||||
lines = [f"{prefix}command effects:"]
|
||||
for item in effects:
|
||||
command = item.get("command_value_hex") or _command_hex(item.get("command_value"))
|
||||
name = item.get("name_candidate") or "unknown_command"
|
||||
summary = _comment_text(str(item.get("summary") or "candidate effects"))
|
||||
lines.append(f"{prefix}- {command} {name}: {summary}")
|
||||
for effect in _object_list(item.get("effects"))[:3]:
|
||||
effect_text = _effect_summary(effect)
|
||||
if effect_text:
|
||||
lines.append(f"{prefix} effect: {effect_text}")
|
||||
evidence = _hex_join(item.get("evidence_addresses_hex"))
|
||||
if opts.include_evidence and evidence:
|
||||
lines.append(f"{prefix} evidence: {evidence}")
|
||||
return lines
|
||||
|
||||
|
||||
def _response_schema_comment_lines(
|
||||
schemas: list[JsonObject],
|
||||
opts: SerialPseudocodeOptions,
|
||||
*,
|
||||
prefix: str,
|
||||
) -> list[str]:
|
||||
if not schemas:
|
||||
return []
|
||||
lines = [f"{prefix}response schemas:"]
|
||||
for schema in schemas[:4]:
|
||||
response_id = schema.get("response_id") or schema.get("call_address_hex") or "candidate_response"
|
||||
byte_text = _response_schema_summary(schema)
|
||||
lines.append(f"{prefix}- {response_id}: {byte_text}")
|
||||
evidence = _hex_join(schema.get("evidence_addresses_hex"))
|
||||
if opts.include_evidence and evidence:
|
||||
lines.append(f"{prefix} evidence: {evidence}")
|
||||
if len(schemas) > 4:
|
||||
lines.append(f"{prefix}- ... {len(schemas) - 4} more candidate response schemas")
|
||||
return lines
|
||||
|
||||
|
||||
def _table_map_comment_lines(
|
||||
tables: list[JsonObject],
|
||||
opts: SerialPseudocodeOptions,
|
||||
*,
|
||||
prefix: str,
|
||||
) -> list[str]:
|
||||
if not tables:
|
||||
return []
|
||||
lines = [f"{prefix}table map candidates:"]
|
||||
for table in tables[:6]:
|
||||
name = table.get("name_candidate") or "unnamed_table_candidate"
|
||||
address = table.get("logical_base_address_hex") or table.get("address_hex") or "?"
|
||||
element = table.get("element_candidate")
|
||||
accesses = ", ".join(str(item) for item in table.get("observed_accesses", []) if item) or "access?"
|
||||
detail = f"{name} at {address}"
|
||||
if element:
|
||||
detail += f" ({element})"
|
||||
lines.append(f"{prefix}- {_comment_text(detail)}; observed {accesses}")
|
||||
evidence = _hex_join(table.get("evidence_addresses_hex"))
|
||||
if opts.include_evidence and evidence:
|
||||
lines.append(f"{prefix} evidence: {evidence}")
|
||||
if len(tables) > 6:
|
||||
lines.append(f"{prefix}- ... {len(tables) - 6} more table candidates")
|
||||
return lines
|
||||
|
||||
|
||||
def _state_variable_comment_lines(
|
||||
value: object,
|
||||
opts: SerialPseudocodeOptions,
|
||||
*,
|
||||
prefix: str,
|
||||
) -> list[str]:
|
||||
states = sorted(
|
||||
_object_list(value),
|
||||
key=lambda item: item.get("address") if isinstance(item.get("address"), int) else 0x10000,
|
||||
)
|
||||
if not states:
|
||||
return []
|
||||
lines = [f"{prefix}state variable candidates:"]
|
||||
for state in states[:6]:
|
||||
name = state.get("name_candidate") or "unnamed_state_candidate"
|
||||
address = state.get("address_hex") or _command_hex(state.get("address"))
|
||||
reads = state.get("read_count", "?")
|
||||
writes = state.get("write_count", "?")
|
||||
bits = ", ".join(str(item) for item in state.get("bit_candidates", []))
|
||||
suffix = f"; bits {bits}" if bits else ""
|
||||
lines.append(f"{prefix}- {_comment_text(str(name))} {address}: reads {reads}, writes {writes}{suffix}")
|
||||
evidence = _hex_join(state.get("evidence_addresses_hex"))
|
||||
if opts.include_evidence and evidence:
|
||||
lines.append(f"{prefix} evidence: {evidence}")
|
||||
if len(states) > 6:
|
||||
lines.append(f"{prefix}- ... {len(states) - 6} more state-variable candidates")
|
||||
return lines
|
||||
|
||||
|
||||
def _retry_error_comment_lines(
|
||||
value: object,
|
||||
opts: SerialPseudocodeOptions,
|
||||
*,
|
||||
prefix: str,
|
||||
) -> list[str]:
|
||||
if not isinstance(value, dict):
|
||||
return []
|
||||
checksum = value.get("checksum_failure_path")
|
||||
retry = value.get("retry_path")
|
||||
command_07 = value.get("command_0x07_path")
|
||||
lines = [f"{prefix}retry/error model candidate:"]
|
||||
if isinstance(checksum, dict):
|
||||
condition = _comment_text(str(checksum.get("condition_candidate") or "checksum failure"))
|
||||
target = checksum.get("error_target") or checksum.get("error_target_address_hex") or "error target"
|
||||
lines.append(f"{prefix}- checksum path: {condition} -> {target}")
|
||||
if isinstance(retry, dict):
|
||||
counter = retry.get("counter_address_hex") or "counter?"
|
||||
threshold = retry.get("threshold_candidate", "?")
|
||||
summary = _comment_text(str(retry.get("summary") or "candidate retry path"))
|
||||
lines.append(f"{prefix}- retry path: counter {counter}, threshold {threshold}; {summary}")
|
||||
if isinstance(command_07, dict):
|
||||
summary = _comment_text(str(command_07.get("summary") or "candidate command 0x07 path"))
|
||||
lines.append(f"{prefix}- command 0x07 path: {summary}")
|
||||
evidence = _hex_join(value.get("evidence_addresses_hex"))
|
||||
if opts.include_evidence and evidence:
|
||||
lines.append(f"{prefix}- evidence: {evidence}")
|
||||
return lines
|
||||
|
||||
|
||||
def _command_effect_switch_lines(command: JsonObject) -> list[str]:
|
||||
effects = _object_list(command.get("effects"))[:3]
|
||||
lines = []
|
||||
for effect in effects:
|
||||
text = _effect_summary(effect)
|
||||
if text:
|
||||
lines.append(f"candidate effect: {text}")
|
||||
return lines
|
||||
|
||||
|
||||
def _effect_summary(effect: JsonObject) -> str:
|
||||
kind = str(effect.get("kind") or "effect_candidate")
|
||||
parts = [kind]
|
||||
target = effect.get("target_candidate") or effect.get("destination_candidate")
|
||||
source = effect.get("source_candidate")
|
||||
operation = effect.get("operation_candidate")
|
||||
table = effect.get("table_base_hex")
|
||||
state = effect.get("state_address_hex")
|
||||
if target:
|
||||
parts.append(f"target {target}")
|
||||
if source:
|
||||
parts.append(f"source {source}")
|
||||
if operation:
|
||||
parts.append(str(operation))
|
||||
if table:
|
||||
parts.append(f"table {table}")
|
||||
if state:
|
||||
parts.append(f"state {state}")
|
||||
return _comment_text("; ".join(parts))
|
||||
|
||||
|
||||
def _schema_list(protocol: JsonObject) -> list[JsonObject]:
|
||||
schemas = _object_list(protocol.get("response_schemas"))
|
||||
if schemas:
|
||||
return schemas
|
||||
return _object_list(protocol.get("response_schema"))
|
||||
|
||||
|
||||
def _table_map_list(protocol: JsonObject) -> list[JsonObject]:
|
||||
tables = _object_list(protocol.get("logical_table_map_candidates"))
|
||||
if tables:
|
||||
return tables
|
||||
return _object_list(protocol.get("table_map_candidates"))
|
||||
|
||||
|
||||
def _response_schema_summary(schema: JsonObject) -> str:
|
||||
parts = []
|
||||
for item in _object_list(schema.get("bytes")):
|
||||
label = item.get("byte") or f"byte{item.get('offset', '?')}"
|
||||
source = item.get("source_expression") or item.get("source_kind") or "unknown"
|
||||
parts.append(f"{label}={source}")
|
||||
return _comment_text("; ".join(parts) if parts else "candidate byte sources unknown")
|
||||
|
||||
|
||||
def _object_list(value: object) -> list[JsonObject]:
|
||||
if not isinstance(value, list):
|
||||
return []
|
||||
return [item for item in value if isinstance(item, dict)]
|
||||
|
||||
|
||||
def _command_hex(value: object) -> str:
|
||||
if isinstance(value, int):
|
||||
return f"0x{value:02X}"
|
||||
return "?"
|
||||
|
||||
|
||||
def _tx_functions(candidate: JsonObject, opts: SerialPseudocodeOptions) -> list[str]:
|
||||
length = _int_field(candidate, "frame_length", 6)
|
||||
seed = _int_field(candidate, "checksum_seed", 0x5A)
|
||||
data_length = max(length - 1, 0)
|
||||
lines = _candidate_comment_block("TX reconstruction evidence", candidate, opts)
|
||||
lines.extend(
|
||||
[
|
||||
"static u8 sci1_tx_candidate_checksum(void)",
|
||||
"{",
|
||||
f" u8 checksum = {_c_hex(seed, width=2)};",
|
||||
],
|
||||
)
|
||||
for index in range(data_length):
|
||||
lines.append(f" checksum ^= TX_FRAME({index});")
|
||||
lines.extend(
|
||||
[
|
||||
" return checksum;",
|
||||
"}",
|
||||
"",
|
||||
"void sci1_tx_start_candidate_frame(void)",
|
||||
"{",
|
||||
" /* The ROM appears to have populated TX_FRAME(0..4) before this point. */",
|
||||
f" TX_FRAME({data_length}) = sci1_tx_candidate_checksum();",
|
||||
"",
|
||||
f" while ((SCI1_SSR & SCI_SSR_TDRE) == 0u) {{",
|
||||
" /* wait for transmit data register empty */",
|
||||
" }",
|
||||
"",
|
||||
" SCI1_TDR = TX_FRAME(0);",
|
||||
" TX_INDEX = 1u;",
|
||||
" SCI1_SSR &= (u8)~SCI_SSR_TDRE;",
|
||||
" SCI1_SCR |= SCI_SCR_TIE;",
|
||||
"}",
|
||||
"",
|
||||
"void sci1_txi_candidate_isr(void)",
|
||||
"{",
|
||||
" if (TX_INDEX < TX_FRAME_LENGTH) {",
|
||||
" SCI1_TDR = TX_FRAME(TX_INDEX);",
|
||||
" TX_INDEX = (u8)(TX_INDEX + 1u);",
|
||||
" SCI1_SSR &= (u8)~SCI_SSR_TDRE;",
|
||||
" }",
|
||||
"",
|
||||
" if (TX_INDEX >= TX_FRAME_LENGTH) {",
|
||||
" SCI1_SCR &= (u8)~SCI_SCR_TIE;",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
],
|
||||
)
|
||||
return lines
|
||||
|
||||
|
||||
def _rx_functions(candidate: JsonObject, opts: SerialPseudocodeOptions) -> list[str]:
|
||||
length = _int_field(candidate, "frame_length", 6)
|
||||
seed = _int_field(candidate, "checksum_seed", 0x5A)
|
||||
data_length = max(length - 1, 0)
|
||||
lines = _candidate_comment_block("RX reconstruction evidence", candidate, opts)
|
||||
lines.extend(
|
||||
[
|
||||
"static u8 sci1_rx_candidate_checksum(void)",
|
||||
"{",
|
||||
f" u8 checksum = {_c_hex(seed, width=2)};",
|
||||
],
|
||||
)
|
||||
for index in range(data_length):
|
||||
lines.append(f" checksum ^= RX_FRAME({index});")
|
||||
lines.extend(
|
||||
[
|
||||
" return checksum;",
|
||||
"}",
|
||||
"",
|
||||
"bool sci1_process_rx_candidate_frame(void)",
|
||||
"{",
|
||||
" u8 i;",
|
||||
"",
|
||||
" if (RX_INDEX != RX_FRAME_LENGTH) {",
|
||||
" return false;",
|
||||
" }",
|
||||
"",
|
||||
" for (i = 0u; i < RX_FRAME_LENGTH; i++) {",
|
||||
" RX_FRAME(i) = RX_CAPTURE(i);",
|
||||
" }",
|
||||
"",
|
||||
f" if (sci1_rx_candidate_checksum() != RX_FRAME({data_length})) {{",
|
||||
" RX_INDEX = 0u;",
|
||||
" return false;",
|
||||
" }",
|
||||
"",
|
||||
" RX_INDEX = 0u;",
|
||||
" return true;",
|
||||
"}",
|
||||
"",
|
||||
"bool sci1_rx_byte_received_candidate_isr(void)",
|
||||
"{",
|
||||
" u8 byte;",
|
||||
"",
|
||||
" SCI1_SSR &= (u8)~SCI_SSR_RDRF;",
|
||||
" byte = SCI1_RDR;",
|
||||
"",
|
||||
" if (RX_INTERBYTE_TIMEOUT == 0u) {",
|
||||
" RX_INDEX = 0u;",
|
||||
" }",
|
||||
"",
|
||||
" RX_INTERBYTE_TIMEOUT = 5u;",
|
||||
" RX_CAPTURE(RX_INDEX) = byte;",
|
||||
" RX_INDEX = (u8)(RX_INDEX + 1u);",
|
||||
"",
|
||||
" if (RX_INDEX == RX_FRAME_LENGTH) {",
|
||||
" RX_COMPLETE_TIMER = 0x14u;",
|
||||
" return sci1_process_rx_candidate_frame();",
|
||||
" }",
|
||||
"",
|
||||
" return false;",
|
||||
"}",
|
||||
"",
|
||||
"void sci1_rx_error_candidate_isr(void)",
|
||||
"{",
|
||||
" RX_ERROR_LATCH |= RX_ERROR_LATCH_PHYSICAL_ERROR;",
|
||||
" SCI1_SSR &= (u8)~(SCI_SSR_ORER | SCI_SSR_FER | SCI_SSR_PER);",
|
||||
" sci1_rx_byte_received_candidate_isr();",
|
||||
"}",
|
||||
"",
|
||||
],
|
||||
)
|
||||
return lines
|
||||
|
||||
|
||||
def _candidate_comment_block(
|
||||
title: str,
|
||||
candidate: JsonObject,
|
||||
opts: SerialPseudocodeOptions,
|
||||
) -> list[str]:
|
||||
lines = ["/*", f" * {title}"]
|
||||
comment = str(candidate.get("comment") or candidate.get("short_comment") or "").strip()
|
||||
if comment:
|
||||
lines.append(f" * {_comment_text(comment)}")
|
||||
formula = str(candidate.get("checksum_formula") or "").strip()
|
||||
if formula:
|
||||
lines.append(f" * checksum formula: {_comment_text(formula)}")
|
||||
error_handling = candidate.get("rx_error_handling")
|
||||
if isinstance(error_handling, dict):
|
||||
summary = str(error_handling.get("summary") or "").strip()
|
||||
if summary:
|
||||
lines.append(f" * RX error handling: {_comment_text(summary)}")
|
||||
caveat = str(error_handling.get("manual_caveat") or "").strip()
|
||||
if caveat:
|
||||
lines.append(f" * RX error caveat: {_comment_text(caveat)}")
|
||||
if opts.include_evidence:
|
||||
evidence = candidate.get("evidence_addresses_hex")
|
||||
if isinstance(evidence, dict):
|
||||
lines.append(" * evidence addresses:")
|
||||
for key in sorted(evidence):
|
||||
addresses = ", ".join(str(item) for item in evidence.get(key, []))
|
||||
lines.append(f" * - {key}: {addresses}")
|
||||
lines.append(" */")
|
||||
return lines
|
||||
|
||||
|
||||
def _find_candidate(payload: JsonObject, kind: str) -> JsonObject | None:
|
||||
serial = payload.get("serial_reconstruction")
|
||||
if not isinstance(serial, dict):
|
||||
return None
|
||||
candidates = serial.get("candidates")
|
||||
if not isinstance(candidates, list):
|
||||
return None
|
||||
for candidate in candidates:
|
||||
if isinstance(candidate, dict) and candidate.get("kind") == kind:
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def _candidate_label(candidate: JsonObject) -> str:
|
||||
kind = str(candidate.get("kind") or "candidate")
|
||||
channel = str(candidate.get("channel") or "SCI")
|
||||
length = candidate.get("frame_length", "?")
|
||||
if kind.endswith("_tx_frame"):
|
||||
start = candidate.get("buffer_start_hex") or _h(_int_field(candidate, "buffer_start", 0))
|
||||
end = candidate.get("buffer_end_hex") or _h(_int_field(candidate, "buffer_end", 0))
|
||||
return f"{channel} TX {length}-byte frame at {start}-{end}"
|
||||
if kind.endswith("_rx_frame"):
|
||||
capture_start = candidate.get("capture_buffer_start_hex") or _h(
|
||||
_int_field(candidate, "capture_buffer_start", 0),
|
||||
)
|
||||
capture_end = candidate.get("capture_buffer_end_hex") or _h(
|
||||
_int_field(candidate, "capture_buffer_end", 0),
|
||||
)
|
||||
return f"{channel} RX {length}-byte frame captured at {capture_start}-{capture_end}"
|
||||
return f"{channel} {kind}"
|
||||
|
||||
|
||||
def _mapping_items(value: object) -> list[JsonObject]:
|
||||
if not isinstance(value, list):
|
||||
return []
|
||||
return [item for item in value if isinstance(item, dict)]
|
||||
|
||||
|
||||
def _int_field(candidate: JsonObject | None, key: str, default: int) -> int:
|
||||
if not candidate:
|
||||
return default
|
||||
value = candidate.get(key)
|
||||
if isinstance(value, bool):
|
||||
return int(value)
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
return default
|
||||
|
||||
|
||||
def _c_hex(value: int, *, width: int = 4) -> str:
|
||||
return f"0x{value & 0xFFFF:0{width}X}u"
|
||||
|
||||
|
||||
def _optional_hex(value: object, *, width: int = 4) -> str:
|
||||
if isinstance(value, int):
|
||||
return f"H'{value & 0xFFFF:0{width}X}"
|
||||
return "unknown"
|
||||
|
||||
|
||||
def _h(value: int) -> str:
|
||||
return f"H'{value & 0xFFFF:04X}"
|
||||
|
||||
|
||||
def _dedupe(items: list[str]) -> list[str]:
|
||||
seen: set[str] = set()
|
||||
output: list[str] = []
|
||||
for item in items:
|
||||
if item in seen:
|
||||
continue
|
||||
seen.add(item)
|
||||
output.append(item)
|
||||
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", " ")
|
||||
@@ -7,6 +7,7 @@ from .model import Instruction
|
||||
|
||||
|
||||
SCI1_TDR_ADDRESS = 0xFEDB
|
||||
SCI1_SSR_ADDRESS = 0xFEDC
|
||||
SCI1_RDR_ADDRESS = 0xFEDD
|
||||
TX_BUFFER_START = 0xF858
|
||||
TX_CHECKSUM_ADDRESS = 0xF85D
|
||||
@@ -24,6 +25,7 @@ RX_INDEX_ADDRESS = 0xF9C3
|
||||
RX_INTERBYTE_TIMEOUT_ADDRESS = 0xF9C1
|
||||
RX_COMPLETE_TIMER_ADDRESS = 0xF9C5
|
||||
RX_FRAME_LENGTH = 6
|
||||
RX_ERROR_LATCH_ADDRESS = 0xFAA4
|
||||
|
||||
_BUFFER_DATA_END = TX_CHECKSUM_ADDRESS - 1
|
||||
_MIN_BUFFER_REFERENCES = 3
|
||||
@@ -265,6 +267,41 @@ def _collect_rx_evidence(ordered: list[Instruction]) -> list[dict[str, object]]:
|
||||
),
|
||||
)
|
||||
|
||||
rdrf_before_rdr = _rx_rdrf_clear_before_rdr_read(ordered)
|
||||
if rdrf_before_rdr:
|
||||
evidence.append(
|
||||
_evidence(
|
||||
"rx_rdrf_clear_before_rdr_read",
|
||||
rdrf_before_rdr,
|
||||
summary=(
|
||||
"ROM clears SCI1 SSR.RDRF before reading SCI1_RDR; preserve this observed "
|
||||
"ordering even though the manual describes the canonical RDR-read then "
|
||||
"RDRF-clear sequence"
|
||||
),
|
||||
manual_references=[
|
||||
"Manual/0900766b802125d0.md:16652 RDRF clear sequence reads RDR before clearing RDRF",
|
||||
"Manual/0900766b802125d0.md:16926 canonical receive flag clear sequence",
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
eri_fallthrough = _rx_eri_fallthrough_sequence(ordered)
|
||||
if eri_fallthrough:
|
||||
evidence.append(
|
||||
_evidence(
|
||||
"rx_eri_falls_through_to_rxi",
|
||||
eri_fallthrough,
|
||||
summary=(
|
||||
"SCI1 ERI latches FAA4.bit7, clears ORER/FER/PER, then falls through into "
|
||||
"the same RXI byte-capture path"
|
||||
),
|
||||
manual_references=[
|
||||
"Manual/0900766b802125d0.md:16703 FER/PER transfer errored data to RDR; ORER does not",
|
||||
"Manual/0900766b802125d0.md:16936 ERI is requested on ORER, FER, or PER",
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
indexed_stores = [ins for ins in ordered if _is_indexed_capture_store(ins)]
|
||||
if indexed_stores:
|
||||
evidence.append(
|
||||
@@ -435,6 +472,17 @@ def _rx_candidate_from_evidence(evidence: list[dict[str, object]]) -> dict[str,
|
||||
key: list(evidence_by_key[key]["addresses"])
|
||||
for key in _RX_REQUIRED_EVIDENCE
|
||||
}
|
||||
optional_evidence_keys = [
|
||||
key
|
||||
for key in (
|
||||
"rx_rdrf_clear_before_rdr_read",
|
||||
"rx_eri_falls_through_to_rxi",
|
||||
)
|
||||
if key in evidence_by_key
|
||||
]
|
||||
for key in optional_evidence_keys:
|
||||
evidence_addresses[key] = list(evidence_by_key[key]["addresses"])
|
||||
|
||||
return {
|
||||
"id": "sci1_rx_frame_f868_len6_candidate",
|
||||
"kind": "candidate_sci1_rx_frame",
|
||||
@@ -469,13 +517,18 @@ def _rx_candidate_from_evidence(evidence: list[dict[str, object]]) -> dict[str,
|
||||
"caveat": "candidate frame means six consecutive bytes within the observed RX timing/state machine, not a proven delimited packet",
|
||||
"required_evidence_count": len(_RX_REQUIRED_EVIDENCE),
|
||||
"observed_evidence_count": len(_RX_REQUIRED_EVIDENCE),
|
||||
"optional_evidence_count": len(optional_evidence_keys),
|
||||
"missing_evidence": [],
|
||||
"evidence_addresses": evidence_addresses,
|
||||
"evidence_addresses_hex": {
|
||||
key: [h16(address) for address in addresses]
|
||||
for key, addresses in evidence_addresses.items()
|
||||
},
|
||||
"evidence": [evidence_by_key[key] for key in _RX_REQUIRED_EVIDENCE],
|
||||
"evidence": [
|
||||
evidence_by_key[key]
|
||||
for key in [*_RX_REQUIRED_EVIDENCE, *optional_evidence_keys]
|
||||
],
|
||||
"rx_error_handling": _rx_error_handling_candidate(evidence_by_key),
|
||||
"short_comment": (
|
||||
f"candidate/evidence-supported SCI1 {RX_FRAME_LENGTH}-byte RX frame; "
|
||||
f"capture {h16(RX_CAPTURE_START)}-{h16(RX_CAPTURE_END)}, validate "
|
||||
@@ -708,6 +761,100 @@ def _rx_xor_checksum_validation(ordered: list[Instruction]) -> list[Instruction]
|
||||
return []
|
||||
|
||||
|
||||
def _rx_rdrf_clear_before_rdr_read(ordered: list[Instruction]) -> list[Instruction]:
|
||||
for index, ins in enumerate(ordered):
|
||||
if not _is_bclr_bit(ins, SCI1_SSR_ADDRESS, 6):
|
||||
continue
|
||||
window = ordered[index + 1:index + 5]
|
||||
for candidate in window:
|
||||
if _mnemonic_root(candidate.mnemonic) in {"RTE", "RTS"}:
|
||||
break
|
||||
if _is_read_from_address(candidate, SCI1_RDR_ADDRESS):
|
||||
return [ins, candidate]
|
||||
return []
|
||||
|
||||
|
||||
def _rx_eri_fallthrough_sequence(ordered: list[Instruction]) -> list[Instruction]:
|
||||
for index, ins in enumerate(ordered):
|
||||
if not _is_bset_bit(ins, RX_ERROR_LATCH_ADDRESS, 7):
|
||||
continue
|
||||
window = ordered[index:index + 18]
|
||||
if any(_mnemonic_root(candidate.mnemonic) in {"RTE", "RTS"} for candidate in window[:6]):
|
||||
continue
|
||||
error_clears: list[Instruction] = []
|
||||
for bit in (5, 4, 3):
|
||||
clear = next(
|
||||
(
|
||||
candidate for candidate in window
|
||||
if _is_bclr_bit(candidate, SCI1_SSR_ADDRESS, bit)
|
||||
),
|
||||
None,
|
||||
)
|
||||
if clear is not None:
|
||||
error_clears.append(clear)
|
||||
if len(error_clears) != 3:
|
||||
continue
|
||||
after_error = [
|
||||
candidate for candidate in window
|
||||
if candidate.address > max(clear.address for clear in error_clears)
|
||||
]
|
||||
byte_path = _rx_rdrf_clear_before_rdr_read(after_error)
|
||||
if byte_path:
|
||||
return _dedupe_instructions([ins, *error_clears, *byte_path])
|
||||
return []
|
||||
|
||||
|
||||
def _rx_error_handling_candidate(evidence_by_key: Mapping[str, dict[str, object]]) -> dict[str, object] | None:
|
||||
fallthrough = evidence_by_key.get("rx_eri_falls_through_to_rxi")
|
||||
clear_order = evidence_by_key.get("rx_rdrf_clear_before_rdr_read")
|
||||
if fallthrough is None and clear_order is None:
|
||||
return None
|
||||
evidence_items = [
|
||||
item for item in (fallthrough, clear_order) if isinstance(item, Mapping)
|
||||
]
|
||||
evidence_addresses = _dedupe_ints(
|
||||
int(address)
|
||||
for item in evidence_items
|
||||
for address in item.get("addresses", [])
|
||||
if isinstance(address, int)
|
||||
)
|
||||
return {
|
||||
"kind": "sci1_rx_error_handling_candidate",
|
||||
"error_latch_address": RX_ERROR_LATCH_ADDRESS,
|
||||
"error_latch_address_hex": h16(RX_ERROR_LATCH_ADDRESS),
|
||||
"error_latch_bit": 7,
|
||||
"fallthrough_to_rx_byte_path": fallthrough is not None,
|
||||
"rdrf_clear_before_rdr_read": clear_order is not None,
|
||||
"summary": (
|
||||
"SCI1 ERI appears to mark a physical receive error and continue into the RXI "
|
||||
"byte-capture path; the RXI path clears RDRF before reading RDR in the ROM order."
|
||||
),
|
||||
"manual_caveat": (
|
||||
"Manual text distinguishes ORER from FER/PER data transfer into RDR and describes "
|
||||
"the normal RDR-read then RDRF-clear ordering; this output preserves the observed ROM order."
|
||||
),
|
||||
"evidence_addresses": evidence_addresses,
|
||||
"evidence_addresses_hex": [h16(address) for address in evidence_addresses],
|
||||
"confidence": "candidate-medium" if fallthrough else "candidate-low",
|
||||
}
|
||||
|
||||
|
||||
def _is_bclr_bit(ins: Instruction, address: int, bit: int) -> bool:
|
||||
return (
|
||||
_mnemonic_root(ins.mnemonic) == "BCLR"
|
||||
and _is_write_to_address(ins, address)
|
||||
and _immediate_source_value(ins.operands) == bit
|
||||
)
|
||||
|
||||
|
||||
def _is_bset_bit(ins: Instruction, address: int, bit: int) -> bool:
|
||||
return (
|
||||
_mnemonic_root(ins.mnemonic) == "BSET"
|
||||
and _is_write_to_address(ins, address)
|
||||
and _immediate_source_value(ins.operands) == bit
|
||||
)
|
||||
|
||||
|
||||
def _is_read_from_address(ins: Instruction, address: int) -> bool:
|
||||
source, destination = _source_destination_operands(ins.operands)
|
||||
if _operand_mentions_address(source, address):
|
||||
@@ -792,6 +939,17 @@ def _dedupe_instructions(instructions: list[Instruction]) -> list[Instruction]:
|
||||
return output
|
||||
|
||||
|
||||
def _dedupe_ints(values: Iterable[int]) -> list[int]:
|
||||
output: list[int] = []
|
||||
seen: set[int] = set()
|
||||
for value in values:
|
||||
if value in seen:
|
||||
continue
|
||||
seen.add(value)
|
||||
output.append(value)
|
||||
return output
|
||||
|
||||
|
||||
def _instruction_sequence(
|
||||
instructions: Mapping[int, Instruction] | Iterable[Instruction],
|
||||
) -> list[Instruction]:
|
||||
@@ -837,9 +995,12 @@ def _operand_mentions_address(operand: str, address: int) -> bool:
|
||||
operand_upper = operand.upper().replace(" ", "")
|
||||
names = {
|
||||
SCI1_TDR_ADDRESS: ("SCI1_TDR",),
|
||||
SCI1_SSR_ADDRESS: ("SCI1_SSR",),
|
||||
SCI1_RDR_ADDRESS: ("SCI1_RDR",),
|
||||
TX_BUFFER_START: ("TX_BUFFER",),
|
||||
TX_CHECKSUM_ADDRESS: ("TX_CHECKSUM",),
|
||||
TX_INDEX_ADDRESS: ("TX_INDEX",),
|
||||
RX_ERROR_LATCH_ADDRESS: ("RX_ERROR_LATCH",),
|
||||
}
|
||||
if any(name in operand_upper for name in names.get(address, ())):
|
||||
return True
|
||||
|
||||
2389
h8536/serial_semantics.py
Normal file
2389
h8536/serial_semantics.py
Normal file
File diff suppressed because it is too large
Load Diff
5
h8536_serial_pseudocode.py
Normal file
5
h8536_serial_pseudocode.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from h8536.serial_pseudocode import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
316
tests/test_serial_pseudocode.py
Normal file
316
tests/test_serial_pseudocode.py
Normal file
@@ -0,0 +1,316 @@
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from h8536.serial_pseudocode import (
|
||||
SerialPseudocodeOptions,
|
||||
generate_serial_pseudocode,
|
||||
write_serial_pseudocode,
|
||||
)
|
||||
|
||||
|
||||
def candidate_payload() -> dict:
|
||||
return {
|
||||
"instructions": [],
|
||||
"dtc_vectors": [],
|
||||
"sci": {
|
||||
"channels": {
|
||||
"SCI1": {
|
||||
"configurations": [
|
||||
{
|
||||
"mode": "async",
|
||||
"mode_summary": "async 8-bit even parity 1 stop",
|
||||
"smr": 0x24,
|
||||
"smr_hex": "H'24",
|
||||
"brr": 0x07,
|
||||
"brr_hex": "H'07",
|
||||
"scr": 0x3C,
|
||||
"scr_hex": "H'3C",
|
||||
"clock_source": "internal",
|
||||
"baud_bps": None,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"sci_protocol": {
|
||||
"manual_references": [
|
||||
"Manual/0900766b802125d0.md:15794 RDR receive data register",
|
||||
"Manual/0900766b802125d0.md:15823 TDR transmit data register",
|
||||
],
|
||||
},
|
||||
"board_profile": {
|
||||
"summary": "Board trace ties the H8/536 SCI1 pins to a MAX202 RS232 transceiver.",
|
||||
"manual_references": [
|
||||
"Manual/0900766b802125d0.md:2417 FP-80 H8/536 pin 66 is P95/TXD",
|
||||
],
|
||||
"traces": [
|
||||
{
|
||||
"signal": "TXD",
|
||||
"h8_pin": 66,
|
||||
"h8_pin_name": "P95/TXD",
|
||||
"max202_pin": 11,
|
||||
"evidence": "MAX202 pin 11 traces to H8 pin 66",
|
||||
},
|
||||
{
|
||||
"signal": "RXD",
|
||||
"h8_pin": 67,
|
||||
"h8_pin_name": "P96/RXD",
|
||||
"max202_pin": 12,
|
||||
"evidence": "MAX202 pin 12 traces to H8 pin 67",
|
||||
},
|
||||
],
|
||||
},
|
||||
"serial_reconstruction": {
|
||||
"candidates": [
|
||||
{
|
||||
"kind": "candidate_sci1_tx_frame",
|
||||
"channel": "SCI1",
|
||||
"frame_length": 6,
|
||||
"buffer_start": 0xF858,
|
||||
"buffer_start_hex": "H'F858",
|
||||
"buffer_end": 0xF85D,
|
||||
"buffer_end_hex": "H'F85D",
|
||||
"checksum_address": 0xF85D,
|
||||
"tx_index_address": 0xF9C2,
|
||||
"tdr_address": 0xFEDB,
|
||||
"checksum_seed": 0x5A,
|
||||
"checksum_formula": "checksum = 0x5A ^ byte0 ^ byte1 ^ byte2 ^ byte3 ^ byte4",
|
||||
"confidence": "high",
|
||||
"confidence_score": 0.95,
|
||||
"confidence_reason": "all required independent evidence groups were observed",
|
||||
"comment": "candidate/evidence-supported SCI1 6-byte TX frame hypothesis",
|
||||
"evidence_addresses_hex": {
|
||||
"tx_checksum_seed": ["H'BA4E"],
|
||||
"xor_checksum_chain": ["H'BA50", "H'BA54"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"kind": "candidate_sci1_rx_frame",
|
||||
"channel": "SCI1",
|
||||
"frame_length": 6,
|
||||
"capture_buffer_start": 0xF868,
|
||||
"capture_buffer_start_hex": "H'F868",
|
||||
"capture_buffer_end": 0xF86D,
|
||||
"capture_buffer_end_hex": "H'F86D",
|
||||
"validation_buffer_start": 0xF860,
|
||||
"validation_buffer_end": 0xF865,
|
||||
"checksum_address": 0xF865,
|
||||
"rx_index_address": 0xF9C3,
|
||||
"rdr_address": 0xFEDD,
|
||||
"interbyte_timeout_address": 0xF9C1,
|
||||
"complete_timer_address": 0xF9C5,
|
||||
"checksum_seed": 0x5A,
|
||||
"checksum_formula": "checksum = 0x5A ^ byte0 ^ byte1 ^ byte2 ^ byte3 ^ byte4",
|
||||
"confidence": "high",
|
||||
"confidence_score": 0.9,
|
||||
"confidence_reason": "RX count, copy, and checksum-validation evidence were observed",
|
||||
"caveat": "candidate frame means six consecutive bytes, not a proven delimited packet",
|
||||
"comment": "candidate/evidence-supported SCI1 6-byte RX frame hypothesis",
|
||||
"rx_error_handling": {
|
||||
"error_latch_address": 0xFAA4,
|
||||
"error_latch_address_hex": "H'FAA4",
|
||||
"error_latch_bit": 7,
|
||||
"fallthrough_to_rx_byte_path": True,
|
||||
"rdrf_clear_before_rdr_read": True,
|
||||
"summary": "SCI1 ERI marks a physical receive error and continues into RXI byte capture.",
|
||||
"manual_caveat": "The ROM clears RDRF before reading RDR; preserve that observed order.",
|
||||
"evidence_addresses_hex": ["H'BB57", "H'BB69", "H'BB6D"],
|
||||
},
|
||||
"evidence_addresses_hex": {
|
||||
"rx_rdr_read": ["H'BB6D"],
|
||||
"rx_rdrf_clear_before_rdr_read": ["H'BB69", "H'BB6D"],
|
||||
"rx_eri_falls_through_to_rxi": ["H'BB57", "H'BB5B", "H'BB5F", "H'BB63", "H'BB69", "H'BB6D"],
|
||||
"rx_xor_checksum_validation": ["H'BBD6", "H'BBEC"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
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")
|
||||
|
||||
self.assertIn("focused SCI RX/TX pseudocode from rom.json", text)
|
||||
self.assertIn("SCI1 TX 6-byte frame at H'F858-H'F85D", text)
|
||||
self.assertIn("SCI1 RX 6-byte frame captured at H'F868-H'F86D", text)
|
||||
self.assertIn("8E1 SCI characters carry the 6 protocol bytes", text)
|
||||
self.assertIn("SMR=H'24, BRR=H'07, SCR=H'3C", text)
|
||||
self.assertIn("No SCI1 RXI/TXI DTC vector entries are present", text)
|
||||
self.assertIn("MAX202 pin 11 traces to H8 pin 66", text)
|
||||
self.assertIn("Manual/0900766b802125d0.md:15823 TDR transmit data register", text)
|
||||
self.assertIn("#define TX_FRAME(n) MEM8[(u16)(0xF858u + (n))]", text)
|
||||
self.assertIn("#define RX_CAPTURE(n) MEM8[(u16)(0xF868u + (n))]", text)
|
||||
self.assertIn("checksum ^= TX_FRAME(4);", text)
|
||||
self.assertIn("TX_FRAME(5) = sci1_tx_candidate_checksum();", text)
|
||||
self.assertIn("SCI1_TDR = TX_FRAME(0);", text)
|
||||
self.assertIn("void sci1_txi_candidate_isr(void)", text)
|
||||
self.assertIn("SCI1_SSR &= (u8)~SCI_SSR_RDRF;\n byte = SCI1_RDR;", text)
|
||||
self.assertIn("RX_CAPTURE(RX_INDEX) = byte;", text)
|
||||
self.assertIn("return sci1_process_rx_candidate_frame();", text)
|
||||
self.assertIn("RX_ERROR_LATCH |= RX_ERROR_LATCH_PHYSICAL_ERROR;", text)
|
||||
self.assertIn("sci1_rx_byte_received_candidate_isr();", text)
|
||||
self.assertIn("RX error handling: SCI1 ERI marks a physical receive error", 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_surfaces_refined_semantic_candidates(self):
|
||||
analysis = {
|
||||
"protocol_semantics": [
|
||||
{
|
||||
"confidence": "medium",
|
||||
"confidence_score": 0.7,
|
||||
"commands": [
|
||||
{
|
||||
"command_value": 0,
|
||||
"command_value_hex": "0x00",
|
||||
"name_candidate": "set_value_acked",
|
||||
"summary": "candidate acknowledged set",
|
||||
"effects": [
|
||||
{
|
||||
"kind": "table_write_candidate",
|
||||
"target_candidate": "primary_value_table_candidate",
|
||||
"source_candidate": "RX[3:4] value bytes",
|
||||
"table_base_hex": "H'E000",
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
"command_effects": [
|
||||
{
|
||||
"command_value": 0,
|
||||
"command_value_hex": "0x00",
|
||||
"name_candidate": "set_value_acked",
|
||||
"summary": "Candidate acknowledged set writes value bytes.",
|
||||
"effects": [
|
||||
{
|
||||
"kind": "table_write_candidate",
|
||||
"target_candidate": "primary_value_table_candidate",
|
||||
"source_candidate": "RX[3:4] value bytes",
|
||||
"table_base_hex": "H'E000",
|
||||
"evidence_addresses_hex": ["H'C010"],
|
||||
}
|
||||
],
|
||||
"evidence_addresses_hex": ["H'C000"],
|
||||
}
|
||||
],
|
||||
"response_schemas": [
|
||||
{
|
||||
"response_id": "response_at_C030",
|
||||
"bytes": [
|
||||
{"byte": "byte0", "source_expression": "0x04"},
|
||||
{"byte": "byte1", "source_expression": "RX[1]"},
|
||||
],
|
||||
"evidence_addresses_hex": ["H'C01C", "H'C024"],
|
||||
}
|
||||
],
|
||||
"logical_table_map_candidates": [
|
||||
{
|
||||
"name_candidate": "primary_value_table_candidate",
|
||||
"logical_base_address_hex": "H'E000",
|
||||
"element_candidate": "word_value",
|
||||
"observed_accesses": ["write"],
|
||||
"evidence_addresses_hex": ["H'C010"],
|
||||
}
|
||||
],
|
||||
"state_variable_candidates": [
|
||||
{
|
||||
"name_candidate": "serial_session_flags_candidate",
|
||||
"address": 0xFAA2,
|
||||
"address_hex": "H'FAA2",
|
||||
"read_count": 1,
|
||||
"write_count": 2,
|
||||
"bit_candidates": [7],
|
||||
"evidence_addresses_hex": ["H'C018"],
|
||||
}
|
||||
],
|
||||
"retry_error_model": {
|
||||
"checksum_failure_path": {
|
||||
"condition_candidate": "0x5A-seeded XOR over RX[0..4] differs from RX[5]",
|
||||
"error_target": "loc_BE29",
|
||||
},
|
||||
"retry_path": {
|
||||
"counter_address_hex": "H'FAA6",
|
||||
"threshold_candidate": 2,
|
||||
"summary": "Candidate retry path stages a command 0x07 response.",
|
||||
},
|
||||
"command_0x07_path": {
|
||||
"summary": "Candidate explicit command 0x07 path copies previous TX frame bytes.",
|
||||
},
|
||||
"evidence_addresses_hex": ["H'BBD6", "H'BE29"],
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
with patch("h8536.serial_pseudocode.analyze_serial_semantics", return_value=analysis):
|
||||
text = generate_serial_pseudocode(candidate_payload())
|
||||
|
||||
self.assertIn("command effects:", text)
|
||||
self.assertIn("effect: table_write_candidate; target primary_value_table_candidate", text)
|
||||
self.assertIn("response schemas:", text)
|
||||
self.assertIn("response_at_C030: byte0=0x04; byte1=RX[1]", text)
|
||||
self.assertIn("table map candidates:", text)
|
||||
self.assertIn("primary_value_table_candidate at H'E000", text)
|
||||
self.assertIn("state variable candidates:", text)
|
||||
self.assertIn("serial_session_flags_candidate H'FAA2: reads 1, writes 2; bits 7", text)
|
||||
self.assertIn("retry/error model candidate:", text)
|
||||
self.assertIn("checksum path: 0x5A-seeded XOR over RX[0..4] differs from RX[5] -> loc_BE29", text)
|
||||
self.assertIn("candidate effect: table_write_candidate; target primary_value_table_candidate", text)
|
||||
|
||||
def test_tx_only_option_omits_rx_functions(self):
|
||||
text = generate_serial_pseudocode(
|
||||
candidate_payload(),
|
||||
options=SerialPseudocodeOptions(include_rx=False),
|
||||
)
|
||||
|
||||
self.assertIn("void sci1_tx_start_candidate_frame(void)", text)
|
||||
self.assertNotIn("sci1_rx_byte_received_candidate_isr", text)
|
||||
|
||||
def test_write_serial_pseudocode_writes_output_file(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
input_path = Path(tmp) / "rom.json"
|
||||
output_path = Path(tmp) / "serial.c"
|
||||
input_path.write_text(
|
||||
json.dumps(candidate_payload()),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
write_serial_pseudocode(input_path, output_path, SerialPseudocodeOptions())
|
||||
|
||||
self.assertIn("sci1_process_rx_candidate_frame", output_path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -88,6 +88,11 @@ class SerialReconstructionTest(unittest.TestCase):
|
||||
|
||||
def test_candidate_sci1_rx_frame_length_and_checksum_validation_pattern(self):
|
||||
instructions = {
|
||||
0x4FF0: ins(0x4FF0, "BSET.B", "#7, @H'FAA4", [0xFAA4]),
|
||||
0x4FF4: ins(0x4FF4, "BCLR.B", "#5, @SCI1_SSR", [0xFEDC]),
|
||||
0x4FF8: ins(0x4FF8, "BCLR.B", "#4, @SCI1_SSR", [0xFEDC]),
|
||||
0x4FFC: ins(0x4FFC, "BCLR.B", "#3, @SCI1_SSR", [0xFEDC]),
|
||||
0x4FFE: ins(0x4FFE, "BCLR.B", "#6, @SCI1_SSR", [0xFEDC]),
|
||||
0x5000: ins(0x5000, "MOV:G.B", "@SCI1_RDR, R0", [0xFEDD]),
|
||||
0x5004: ins(0x5004, "MOV:G.B", "R0, @(-H'0798,R1)", []),
|
||||
0x5008: ins(0x5008, "ADD:Q.B", "#1, R1", []),
|
||||
@@ -119,11 +124,24 @@ class SerialReconstructionTest(unittest.TestCase):
|
||||
self.assertEqual(candidate["checksum_address"], 0xF865)
|
||||
self.assertEqual(candidate["checksum_seed"], 0x5A)
|
||||
self.assertIn("no explicit header", candidate["confidence_reason"])
|
||||
self.assertIn("rx_rdrf_clear_before_rdr_read", candidate["evidence_addresses"])
|
||||
self.assertIn("rx_eri_falls_through_to_rxi", candidate["evidence_addresses"])
|
||||
self.assertTrue(candidate["rx_error_handling"]["fallthrough_to_rx_byte_path"])
|
||||
self.assertTrue(candidate["rx_error_handling"]["rdrf_clear_before_rdr_read"])
|
||||
self.assertEqual(candidate["rx_error_handling"]["error_latch_address"], 0xFAA4)
|
||||
|
||||
comment = serial_reconstruction_comment_for_instruction(analysis, 0x5138)
|
||||
self.assertIn("candidate/evidence-supported SCI1 6-byte RX frame", comment)
|
||||
self.assertIn("checksum H'F865", comment)
|
||||
self.assertIn("confidence high", comment)
|
||||
self.assertIn(
|
||||
"ROM clears SCI1 SSR.RDRF before reading SCI1_RDR",
|
||||
serial_reconstruction_comment_for_instruction(analysis, 0x4FFE),
|
||||
)
|
||||
self.assertIn(
|
||||
"SCI1 ERI latches FAA4.bit7",
|
||||
serial_reconstruction_comment_for_instruction(analysis, 0x4FF0),
|
||||
)
|
||||
|
||||
def test_lone_tdr_write_does_not_emit_reconstruction(self):
|
||||
instructions = {
|
||||
|
||||
317
tests/test_serial_semantics.py
Normal file
317
tests/test_serial_semantics.py
Normal file
@@ -0,0 +1,317 @@
|
||||
import unittest
|
||||
from typing import Any
|
||||
|
||||
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]
|
||||
|
||||
|
||||
def semantic_items(value: Any) -> list[Any]:
|
||||
if isinstance(value, list):
|
||||
return value
|
||||
if isinstance(value, dict):
|
||||
return list(value.values())
|
||||
return []
|
||||
|
||||
|
||||
def command_item(items: list[Any], command: int) -> Any:
|
||||
for item in items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
command_value = item.get("command_value", item.get("command"))
|
||||
if command_value == command:
|
||||
return item
|
||||
if isinstance(command_value, str) and command_value.lower() == f"0x{command:02x}":
|
||||
return item
|
||||
return None
|
||||
|
||||
|
||||
def semantic_text(value: Any) -> str:
|
||||
if isinstance(value, dict):
|
||||
return " ".join(
|
||||
f"{key} {semantic_text(item)}"
|
||||
for key, item in value.items()
|
||||
).lower()
|
||||
if isinstance(value, list):
|
||||
return " ".join(semantic_text(item) for item in value).lower()
|
||||
return str(value).lower()
|
||||
|
||||
|
||||
def planned_semantics_payload() -> dict:
|
||||
return base_payload(
|
||||
[
|
||||
instruction(0xBF00, "MOV:G.B", "@H'F860, R0", [0xF860]),
|
||||
instruction(0xBF04, "AND.B", "#H'07, R0"),
|
||||
instruction(0xBF08, "CMP:E.B", "#H'00, R0"),
|
||||
instruction(0xBF0C, "BEQ", "loc_C000", targets=[0xC000]),
|
||||
instruction(0xBF10, "CMP:E.B", "#H'01, R0"),
|
||||
instruction(0xBF14, "BEQ", "loc_C100", targets=[0xC100]),
|
||||
instruction(0xBF18, "CMP:E.B", "#H'06, R0"),
|
||||
instruction(0xBF1C, "BEQ", "loc_C600", targets=[0xC600]),
|
||||
instruction(0xBF20, "CMP:E.B", "#H'07, R0"),
|
||||
instruction(0xBF24, "BEQ", "loc_C700", targets=[0xC700]),
|
||||
instruction(0xC000, "MOV:G.B", "@H'F861, R1", [0xF861]),
|
||||
instruction(0xC004, "MOV:G.B", "@H'F862, R2", [0xF862]),
|
||||
instruction(0xC008, "BSR", "loc_622B", targets=[0x622B]),
|
||||
instruction(0xC00C, "MOV:G.W", "@H'F863, R3", [0xF863]),
|
||||
instruction(0xC010, "MOV:G.W", "R3, @H'F900", [0xF900]),
|
||||
instruction(0xC014, "MOV:G.W", "R3, @H'F920", [0xF920]),
|
||||
instruction(0xC018, "MOV:G.B", "#H'01, @H'FAA2", [0xFAA2]),
|
||||
instruction(0xC01C, "MOV:G.B", "#H'04, @H'F850", [0xF850]),
|
||||
instruction(0xC020, "MOV:G.B", "@H'F861, R4", [0xF861]),
|
||||
instruction(0xC024, "MOV:G.B", "R4, @H'F851", [0xF851]),
|
||||
instruction(0xC028, "MOV:G.B", "@H'F862, R5", [0xF862]),
|
||||
instruction(0xC02C, "MOV:G.B", "R5, @H'F852", [0xF852]),
|
||||
instruction(0xC030, "BSR", "loc_BA26", targets=[0xBA26]),
|
||||
instruction(0xC100, "MOV:G.B", "@H'F861, R1", [0xF861]),
|
||||
instruction(0xC104, "MOV:G.B", "@H'F862, R2", [0xF862]),
|
||||
instruction(0xC108, "BSR", "loc_622B", targets=[0x622B]),
|
||||
instruction(0xC10C, "MOV:G.W", "@H'F900, R3", [0xF900]),
|
||||
instruction(0xC110, "MOV:G.B", "#H'04, @H'F850", [0xF850]),
|
||||
instruction(0xC114, "MOV:G.B", "@H'F861, R4", [0xF861]),
|
||||
instruction(0xC118, "MOV:G.B", "R4, @H'F851", [0xF851]),
|
||||
instruction(0xC11C, "MOV:G.B", "@H'F862, R5", [0xF862]),
|
||||
instruction(0xC120, "MOV:G.B", "R5, @H'F852", [0xF852]),
|
||||
instruction(0xC124, "MOV:G.W", "R3, @H'F853", [0xF853]),
|
||||
instruction(0xC128, "MOV:G.B", "@H'F9B5, R6", [0xF9B5]),
|
||||
instruction(0xC12C, "BSR", "loc_BA26", targets=[0xBA26]),
|
||||
instruction(0xC600, "MOV:G.B", "@H'F861, R1", [0xF861]),
|
||||
instruction(0xC604, "MOV:G.B", "@H'F862, R2", [0xF862]),
|
||||
instruction(0xC608, "BSR", "loc_622B", targets=[0x622B]),
|
||||
instruction(0xC60C, "MOV:G.W", "@H'F863, R3", [0xF863]),
|
||||
instruction(0xC610, "MOV:G.W", "R3, @H'F940", [0xF940]),
|
||||
instruction(0xC614, "MOV:G.B", "#H'01, @H'F980", [0xF980]),
|
||||
instruction(0xC618, "MOV:G.B", "#H'01, @H'F9C0", [0xF9C0]),
|
||||
instruction(0xC700, "MOV:G.B", "#H'07, @H'F850", [0xF850]),
|
||||
instruction(0xC704, "MOV:G.B", "@H'F861, R1", [0xF861]),
|
||||
instruction(0xC708, "MOV:G.B", "R1, @H'F851", [0xF851]),
|
||||
instruction(0xC70C, "BSR", "loc_BA26", targets=[0xBA26]),
|
||||
instruction(0xC800, "CMP:E.B", "@H'F865, R7", [0xF865]),
|
||||
instruction(0xC804, "BNE", "loc_C700", targets=[0xC700]),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
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_planned_command_effects_include_core_command_behaviors(self):
|
||||
semantics = only_semantics(self, planned_semantics_payload())
|
||||
|
||||
self.assertIn("command_effects", semantics)
|
||||
effects = semantic_items(semantics["command_effects"])
|
||||
|
||||
command_0 = command_item(effects, 0x00)
|
||||
self.assertIsNotNone(command_0)
|
||||
command_0_text = semantic_text(command_0)
|
||||
self.assertIn("write", command_0_text)
|
||||
self.assertIn("primary_value_table", command_0_text)
|
||||
self.assertIn("current_value_table", command_0_text)
|
||||
|
||||
command_1 = command_item(effects, 0x01)
|
||||
self.assertIsNotNone(command_1)
|
||||
command_1_text = semantic_text(command_1)
|
||||
self.assertIn("read", command_1_text)
|
||||
self.assertIn("response", command_1_text)
|
||||
|
||||
command_6 = command_item(effects, 0x06)
|
||||
self.assertIsNotNone(command_6)
|
||||
command_6_text = semantic_text(command_6)
|
||||
self.assertIn("write", command_6_text)
|
||||
self.assertIn("secondary_value_table", command_6_text)
|
||||
|
||||
def test_planned_response_schema_tracks_immediates_and_rx_copies(self):
|
||||
semantics = only_semantics(self, planned_semantics_payload())
|
||||
|
||||
self.assertIn("response_schema", semantics)
|
||||
schema_text = semantic_text(semantics["response_schema"])
|
||||
|
||||
self.assertIn("tx", schema_text)
|
||||
self.assertIn("byte0", schema_text)
|
||||
self.assertIn("0x04", schema_text)
|
||||
self.assertIn("byte1", schema_text)
|
||||
self.assertIn("rx[1]", schema_text)
|
||||
self.assertIn("byte2", schema_text)
|
||||
self.assertIn("rx[2]", schema_text)
|
||||
|
||||
def test_planned_table_map_candidates_name_known_tables(self):
|
||||
semantics = only_semantics(self, planned_semantics_payload())
|
||||
|
||||
self.assertIn("table_map_candidates", semantics)
|
||||
table_text = semantic_text(semantics["table_map_candidates"])
|
||||
|
||||
self.assertIn("primary_value_table", table_text)
|
||||
self.assertIn("current_value_table", table_text)
|
||||
self.assertIn("secondary_value_table", table_text)
|
||||
self.assertIn("flag_table", table_text)
|
||||
|
||||
def test_planned_state_variable_candidates_include_known_addresses(self):
|
||||
semantics = only_semantics(self, planned_semantics_payload())
|
||||
|
||||
self.assertIn("state_variable_candidates", semantics)
|
||||
state_text = semantic_text(semantics["state_variable_candidates"])
|
||||
|
||||
self.assertIn("faa2", state_text)
|
||||
self.assertIn("f9b5", state_text)
|
||||
self.assertIn("f9c0", state_text)
|
||||
|
||||
def test_planned_retry_error_model_identifies_retransmit_and_checksum_error(self):
|
||||
semantics = only_semantics(self, planned_semantics_payload())
|
||||
|
||||
self.assertIn("retry_error_model", semantics)
|
||||
retry_text = semantic_text(semantics["retry_error_model"])
|
||||
|
||||
self.assertIn("retransmit", retry_text)
|
||||
self.assertIn("0x07", retry_text)
|
||||
self.assertIn("checksum_error_response", retry_text)
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user