1
0
Files
h8-536-decoder/tests/test_rx_branch_trace.py
2026-05-26 10:48:39 +10:00

94 lines
4.3 KiB
Python

import io
import json
import tempfile
import unittest
from pathlib import Path
from h8536.rx_branch_trace import analyze_rx_branch_trace, format_text_report, main
def ins(address: int, text: str | None = None) -> dict[str, object]:
return {
"address": address,
"text": text or f"INS_{address:04X}",
"mnemonic": (text or "NOP").split()[0],
"operands": "",
"kind": "normal",
"targets": [],
"references": [],
}
def fixture_payload() -> dict[str, object]:
addresses = {
0x3FEF, 0x3FF3, 0x3FF5, 0x3FF9, 0x3FFD, 0x4001, 0x4003, 0x4007,
0xBB57, 0xBB5B, 0xBB5F, 0xBB63, 0xBB67, 0xBB6D, 0xBB71, 0xBB75,
0xBB77, 0xBB7D, 0xBB82, 0xBB84, 0xBB88, 0xBB8A, 0xBB90, 0xBB96,
0xBB9A, 0xBB9C, 0xBB9E, 0xBBA3, 0xBBAB, 0xBBB0, 0xBBB3, 0xBBCB,
0xBBCF, 0xBBD3, 0xBBD6, 0xBBD8, 0xBBDC, 0xBBE0, 0xBBE4, 0xBBE8,
0xBBEC, 0xBBF0, 0xBBF3, 0xBBF7, 0xBBFD, 0xBC01, 0xBC08, 0xBC0C,
0xBC0F, 0xBC13, 0xBC15, 0xBC19, 0xBC1D, 0xBC20, 0xBC24, 0xBC29,
0xBC2E, 0xBC33, 0xBC37, 0xBC3A, 0xBC3C, 0xBC3E, 0xBC42, 0xBC45,
0xBC4A, 0xBC4F, 0xBC54, 0xBC5C, 0xBC60, 0xBC63, 0xBC67, 0xBC69,
0xBC75, 0xBC79, 0xBC82, 0xBC86, 0xBCB0, 0xBCCD, 0xBCD0, 0xBCD7,
0xBCE0, 0xBCE8, 0xBCEC, 0xBCF0, 0xBCF6, 0xBCFA, 0xBCFD, 0xBD04,
0xBD08, 0xBD0B, 0xBD0E, 0xBD1A, 0xBD1E, 0xBD22, 0xBD26, 0xBD35,
0xBD64, 0xBD67, 0xBD6D, 0xBD75, 0xBD79, 0xBD80, 0xBD85, 0xBD94,
0xBD9A, 0xBDB5, 0xBDBF, 0xBDC2, 0xBDC8, 0xBDD0, 0xBDD4, 0xBDDB,
0xBDE5, 0xBDE9, 0xBDED, 0xBDF3, 0xBDFB, 0xBDFF, 0xBE05, 0xBE09,
0xBE0D, 0xBE11, 0xBE15, 0xBE19, 0xBE1D, 0xBE22, 0xBE27, 0xBE29,
0xBE2D, 0xBE31, 0xBE33, 0xBE37, 0xBE3C, 0xBE3E, 0xBE43, 0xBE47,
0xBE4D, 0xBE52, 0xBE5A, 0xBE62, 0xBE6A, 0xBE70, 0xBE78, 0xBE80,
0xBE82, 0xBE84, 0xBE88, 0xBE91, 0xBE95, 0xBE99, 0xBE9D, 0xBE9E,
0xBEA5, 0xBEA9, 0xBEAD, 0xBEAF, 0xBEB5, 0xBEBB, 0xBEBF, 0xBEC1,
0xBEC5, 0xBECB, 0xBED1, 0xBED5, 0xBEE4,
}
return {"instructions": [ins(address) for address in sorted(addresses)]}
class RxBranchTraceTest(unittest.TestCase):
def test_analyzes_dispatch_split_and_commands(self):
analysis = analyze_rx_branch_trace(fixture_payload())
self.assertEqual(analysis["summary"]["confidence"], "high")
self.assertEqual(analysis["frame_model"]["checksum_seed"], 0x5A)
self.assertTrue(analysis["stages"][2]["present"])
self.assertIn("FAA2 != 0", analysis["stages"][2]["summary"])
commands = {command["command"]: command for command in analysis["commands"]}
self.assertIn("continuation path only", commands[0x04]["availability"])
self.assertIn("selector zero is special", "\n".join(commands[0x04]["side_effects"]))
self.assertIn("selectors 0x006C", "\n".join(commands[0x05]["side_effects"]))
self.assertIn("previous finalized TX frame", commands[0x07]["summary"])
def test_text_report_mentions_bench_implications(self):
text = format_text_report(analyze_rx_branch_trace(fixture_payload()))
self.assertIn("H8/536 SCI1 RX Branch Trace", text)
self.assertIn("cmd 0x04 continuation_set_value_candidate", text)
self.assertIn("standalone command 4 frame from idle should not hit BD0E", text)
self.assertIn("Command 5 is not a generic always-live ACK", text)
self.assertIn("Selector Decode", text)
self.assertIn("TXI/RXI race and continuation collapse", text)
self.assertIn("RX-to-TX Feedback Loops", text)
def test_cli_writes_json_output(self):
with tempfile.TemporaryDirectory() as tmp:
input_path = Path(tmp) / "rom.json"
output_path = Path(tmp) / "rx.json"
input_path.write_text(json.dumps(fixture_payload()), encoding="utf-8")
stdout = io.StringIO()
rc = main(["--json", "--out", str(output_path), str(input_path)], stdout=stdout)
self.assertEqual(rc, 0)
self.assertIn("wrote", stdout.getvalue())
payload = json.loads(output_path.read_text(encoding="utf-8"))
self.assertEqual(payload["kind"], "rx_branch_trace")
self.assertIn("downstream_traces", payload)
self.assertIn("feedback_loops", payload)
if __name__ == "__main__":
unittest.main()