Serial TX/RX builder
This commit is contained in:
21
README.md
21
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,7 @@ 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`.
|
||||
- Generates a focused RX/TX serial-path pseudocode view from those serial reconstruction 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 +97,18 @@ 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.
|
||||
|
||||
## Code Layout
|
||||
|
||||
- `h8536_decompiler.py`: compatibility wrapper for the CLI.
|
||||
@@ -111,9 +130,11 @@ 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_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.
|
||||
|
||||
220
build/rom_serial_pseudocode.c
Normal file
220
build/rom_serial_pseudocode.c
Normal file
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#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]
|
||||
|
||||
/*
|
||||
* 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
|
||||
* 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_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_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)
|
||||
{
|
||||
SCI1_SSR &= (u8)~(SCI_SSR_ORER | SCI_SSR_FER | SCI_SSR_PER);
|
||||
RX_INDEX = 0u;
|
||||
}
|
||||
477
h8536/serial_pseudocode.py
Normal file
477
h8536/serial_pseudocode.py
Normal file
@@ -0,0 +1,477 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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")
|
||||
|
||||
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(_declarations(tx_candidate, rx_candidate))
|
||||
|
||||
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")
|
||||
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,
|
||||
)
|
||||
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 _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)
|
||||
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)}]",
|
||||
"",
|
||||
],
|
||||
)
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def _tx_functions(candidate: JsonObject, opts: SerialPseudocodeOptions) -> list[str]:
|
||||
length = _int_field(candidate, "frame_length", 6)
|
||||
seed = _int_field(candidate, "checksum_seed", 0x5A)
|
||||
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)",
|
||||
"{",
|
||||
" SCI1_SSR &= (u8)~(SCI_SSR_ORER | SCI_SSR_FER | SCI_SSR_PER);",
|
||||
" RX_INDEX = 0u;",
|
||||
"}",
|
||||
"",
|
||||
],
|
||||
)
|
||||
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)}")
|
||||
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 _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 _comment_text(text: str) -> str:
|
||||
return text.replace("*/", "* /").replace("\r", " ").replace("\n", " ")
|
||||
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())
|
||||
144
tests/test_serial_pseudocode.py
Normal file
144
tests/test_serial_pseudocode.py
Normal file
@@ -0,0 +1,144 @@
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from h8536.serial_pseudocode import (
|
||||
SerialPseudocodeOptions,
|
||||
generate_serial_pseudocode,
|
||||
write_serial_pseudocode,
|
||||
)
|
||||
|
||||
|
||||
def candidate_payload() -> dict:
|
||||
return {
|
||||
"instructions": [],
|
||||
"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",
|
||||
"evidence_addresses_hex": {
|
||||
"rx_rdr_read": ["H'BB6D"],
|
||||
"rx_xor_checksum_validation": ["H'BBD6", "H'BBEC"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
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("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_xor_checksum_validation: H'BBD6, H'BBEC", 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()
|
||||
Reference in New Issue
Block a user