import json import tempfile import unittest from pathlib import Path from h8536.model import Instruction from h8536.render import format_listing, write_json from h8536.rom import Rom from h8536.sci import analyze_sci, sci_comment_for_instruction def sci1_setup(scr: int = 0x3C) -> dict[int, Instruction]: return { 0x1000: Instruction( 0x1000, bytes.fromhex("15FED80624"), "MOV:G.B", "#H'24, @SCI1_SMR", references=[0xFED8], comment="SCI1_SMR = H'24", ), 0x1005: Instruction( 0x1005, bytes([0x15, 0xFE, 0xDA, 0x06, scr]), "MOV:G.B", f"#H'{scr:02X}, @SCI1_SCR", references=[0xFEDA], comment=f"SCI1_SCR = H'{scr:02X}", ), 0x100A: Instruction( 0x100A, bytes.fromhex("15FED90607"), "MOV:G.B", "#H'07, @SCI1_BRR", references=[0xFED9], comment="SCI1_BRR = H'07", ), } class SciInferenceTest(unittest.TestCase): def test_async_internal_baud_uses_manual_brr_formula(self): analysis = analyze_sci(sci1_setup(), clock_hz=16_000_000) config = analysis["channels"]["SCI1"]["configurations"][0] self.assertEqual(config["mode"], "async") self.assertEqual(config["cks_n"], 0) self.assertEqual(config["brr"], 7) self.assertEqual(config["baud_bps"], 31_250) self.assertEqual(config["formula"], "B = clock_hz / (64 * 2^(2n) * (N + 1))") self.assertIn("SCI1 async 8-bit even parity 1 stop baud 31250 bps", config["comment"]) def test_missing_clock_keeps_baud_partial(self): analysis = analyze_sci(sci1_setup(), clock_hz=None) comment = sci_comment_for_instruction(analysis, 0x100A) config = analysis["channels"]["SCI1"]["configurations"][0] self.assertIsNone(config["baud_bps"]) self.assertEqual(config["reason"], "clock_hz_missing") self.assertIn("baud needs --clock-hz", comment) self.assertNotIn("31250 bps", comment) def test_external_clock_selection_suppresses_internal_baud(self): analysis = analyze_sci(sci1_setup(scr=0x3E), clock_hz=16_000_000) config = analysis["channels"]["SCI1"]["configurations"][0] self.assertIsNone(config["baud_bps"]) self.assertEqual(config["clock_source"], "external") self.assertEqual(config["reason"], "external_clock_selected") self.assertIn("external clock selected", config["comment"]) def test_scr_bit_writes_are_tracked_without_repeating_same_baud(self): instructions = sci1_setup() instructions[0x1010] = Instruction( 0x1010, bytes.fromhex("15FEDAC7"), "BSET.B", "#7, @SCI1_SCR", references=[0xFEDA], comment="set TIE (bit 7) of SCI1_SCR", ) analysis = analyze_sci(instructions, clock_hz=16_000_000) writes = analysis["channels"]["SCI1"]["writes"] self.assertEqual([write["register"] for write in writes], ["SMR", "SCR", "BRR", "SCR"]) self.assertEqual(writes[-1]["value"], 0xBC) self.assertNotIn(0x1010, analysis["annotations"]) def test_listing_preserves_existing_comment_and_appends_sci_comment(self): instructions = sci1_setup() analysis = analyze_sci(instructions, clock_hz=16_000_000) listing = format_listing( Path("rom.bin"), Rom(b"\xFF" * 0x20), instructions, {}, {}, "min", traced=False, sci_analysis=analysis, ) self.assertIn("SCI1_BRR = H'07; SCI1 async 8-bit even parity 1 stop baud 31250 bps", listing) def test_json_includes_top_level_and_instruction_sci_metadata(self): instructions = sci1_setup() analysis = analyze_sci(instructions, clock_hz=16_000_000) with tempfile.TemporaryDirectory() as tmp: path = Path(tmp) / "out.json" write_json(path, instructions, {}, {}, sci_analysis=analysis) payload = json.loads(path.read_text(encoding="utf-8")) self.assertEqual(payload["sci"]["channels"]["SCI1"]["configurations"][0]["baud_bps"], 31_250) brr_instruction = next(item for item in payload["instructions"] if item["address"] == 0x100A) self.assertEqual(brr_instruction["sci"]["inferences"][0]["brr"], 7) if __name__ == "__main__": unittest.main()