1
0
Files
h8-536-decoder/tests/test_serial_reconstruction.py

207 lines
10 KiB
Python

import json
import unittest
from h8536.model import Instruction
from h8536.serial_reconstruction import (
analyze_serial_reconstruction,
serial_reconstruction_comment_for_instruction,
serial_reconstruction_json_payload,
serial_reconstruction_metadata_for_instruction,
)
def ins(
address: int,
mnemonic: str,
operands: str = "",
references: list[int] | None = None,
kind: str = "normal",
targets: list[int] | None = None,
) -> Instruction:
return Instruction(
address,
b"",
mnemonic,
operands,
kind=kind,
targets=targets or [],
references=references or [],
)
class SerialReconstructionTest(unittest.TestCase):
def test_candidate_sci1_tx_frame_length_and_checksum_pattern(self):
instructions = {
0x3000: ins(0x3000, "MOV:G.B", "#H'02, @H'F858", [0xF858]),
0x3004: ins(0x3004, "MOV:G.B", "@H'F858, R0", [0xF858]),
0x3008: ins(0x3008, "MOV:G.B", "R0, @SCI1_TDR", [0xFEDB]),
0x300C: ins(0x300C, "MOV:G.B", "#1, @H'F9C2", [0xF9C2]),
0x301C: ins(0x301C, "MOV:E.B", "#H'5A, R1", []),
0x3020: ins(0x3020, "XOR.B", "@H'F858, R1", [0xF858]),
0x3024: ins(0x3024, "XOR.B", "@H'F859, R1", [0xF859]),
0x3028: ins(0x3028, "XOR.B", "@H'F85A, R1", [0xF85A]),
0x302C: ins(0x302C, "XOR.B", "@H'F85B, R1", [0xF85B]),
0x3030: ins(0x3030, "XOR.B", "@H'F85C, R1", [0xF85C]),
0x3034: ins(0x3034, "MOV:G.B", "R1, @H'F85D", [0xF85D]),
0x3100: ins(0x3100, "MOV:G.B", "@H'F9C2, R2", [0xF9C2]),
0x3104: ins(0x3104, "MOV:G.B", "@(-H'07A8,R2), R0", []),
0x3108: ins(0x3108, "MOV:G.B", "R0, @SCI1_TDR", [0xFEDB]),
0x310C: ins(0x310C, "ADD:Q.B", "#1, @H'F9C2", [0xF9C2]),
0x3110: ins(0x3110, "CMP:G.B", "#6, @H'F9C2", [0xF9C2]),
}
analysis = analyze_serial_reconstruction(instructions)
self.assertEqual(len(analysis["candidates"]), 1)
candidate = analysis["candidates"][0]
self.assertEqual(candidate["kind"], "candidate_sci1_tx_frame")
self.assertEqual(candidate["channel"], "SCI1")
self.assertEqual(candidate["frame_length"], 6)
self.assertEqual(candidate["buffer_start"], 0xF858)
self.assertEqual(candidate["checksum_address"], 0xF85D)
self.assertEqual(candidate["tx_index_address"], 0xF9C2)
self.assertEqual(candidate["checksum_seed"], 0x5A)
self.assertEqual(
[role["name"] for role in candidate["roles"]],
["tx_frame", "tx_checksum", "tx_index"],
)
self.assertEqual(candidate["roles"][0]["address"], 0xF858)
self.assertEqual(candidate["roles"][1]["address"], 0xF85D)
self.assertEqual(candidate["roles"][2]["address"], 0xF9C2)
self.assertEqual(candidate["tx_path"]["kind"], "interrupt_driven_txi")
self.assertEqual(candidate["tx_path"]["initial_tdr_write_address"], 0x3008)
self.assertEqual(candidate["tx_path"]["txi_indexed_tdr_write_address"], 0x3108)
self.assertIn("TDRE", candidate["tx_path"]["summary"])
self.assertEqual(candidate["confidence"], "high")
self.assertEqual(candidate["confidence_score"], 0.95)
self.assertEqual(candidate["missing_evidence"], [])
evidence_addresses = candidate["evidence_addresses"]
self.assertIn(0x3034, evidence_addresses["checksum_byte"])
self.assertIn(0x3034, evidence_addresses["xor_checksum_chain"])
self.assertIn(0x3108, evidence_addresses["tx_isr_indexed_send"])
self.assertEqual(evidence_addresses["tx_index_compare_frame_length"], [0x3110])
self.assertEqual(evidence_addresses["tx_checksum_seed"], [0x301C])
checksum_comment = serial_reconstruction_comment_for_instruction(analysis, 0x3034)
self.assertIn("candidate/evidence-supported SCI1 6-byte TX frame", checksum_comment)
self.assertIn("checksum H'F85D", checksum_comment)
self.assertIn("confidence high", checksum_comment)
metadata = serial_reconstruction_metadata_for_instruction(analysis, 0x3108)
self.assertEqual(metadata[0]["candidate_id"], "sci1_tx_frame_f858_len6_candidate")
self.assertEqual(metadata[0]["evidence"], "tx_isr_indexed_send")
self.assertIn(0x3108, metadata[0]["evidence_addresses"])
payload = serial_reconstruction_json_payload(analysis)
self.assertEqual(payload["candidates"][0]["frame_length"], 6)
self.assertEqual(payload["candidates"][0]["roles"][1]["name"], "tx_checksum")
json.dumps(payload)
def test_candidate_timer_ram_roles_from_frt1_ocia_tick(self):
instructions = {
0xBEEA: ins(0xBEEA, "BCLR.B", "#5, @FRT1_TCSR", [0xFE91]),
0xBEEE: ins(0xBEEE, "TST.B", "@H'F9C0", [0xF9C0]),
0xBEF4: ins(0xBEF4, "ADD:Q.B", "#-1, @H'F9C0", [0xF9C0]),
0xBEF8: ins(0xBEF8, "TST.B", "@H'F9C1", [0xF9C1]),
0xBEFE: ins(0xBEFE, "ADD:Q.B", "#-1, @H'F9C1", [0xF9C1]),
0xBF02: ins(0xBF02, "TST.W", "@H'F9C6", [0xF9C6]),
0xBF08: ins(0xBF08, "ADD:Q.W", "#-1, @H'F9C6", [0xF9C6]),
}
analysis = analyze_serial_reconstruction(instructions)
self.assertEqual(analysis["candidates"], [])
roles = {role["name"]: role for role in analysis["ram_roles"]}
self.assertEqual(set(roles), {"post_tx_report_delay", "secondary_tx_report_delay", "periodic_report_countdown"})
self.assertEqual(roles["post_tx_report_delay"]["address"], 0xF9C0)
self.assertEqual(roles["secondary_tx_report_delay"]["address"], 0xF9C1)
self.assertEqual(roles["periodic_report_countdown"]["address"], 0xF9C6)
self.assertEqual(roles["periodic_report_countdown"]["width_bits"], 16)
self.assertIn("candidate/evidence-supported", roles["post_tx_report_delay"]["confidence"])
self.assertIn("emulator-guided timer behavior", roles["post_tx_report_delay"]["caveat"])
payload = serial_reconstruction_json_payload(analysis)
self.assertEqual(payload["ram_roles"][0]["name"], "post_tx_report_delay")
json.dumps(payload)
decrement_comment = serial_reconstruction_comment_for_instruction(analysis, 0xBEF4)
self.assertIn("RAM role post_tx_report_delay", decrement_comment)
self.assertIn("FRT1 OCIA periodic tick ISR", decrement_comment)
metadata = serial_reconstruction_metadata_for_instruction(analysis, 0xBF08)
self.assertEqual(metadata[0]["action"], "serial_reconstruction_ram_role")
self.assertEqual(metadata[0]["role_name"], "periodic_report_countdown")
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", []),
0x500C: ins(0x500C, "MOV:G.B", "R1, @H'F9C3", [0xF9C3]),
0x5010: ins(0x5010, "CMP:E", "#H'06, R1", []),
0x5014: ins(0x5014, "MOV:G.B", "#H'14, @H'F9C5", [0xF9C5]),
0x5100: ins(0x5100, "CMP:G.B", "#H'06, @H'F9C3", [0xF9C3]),
0x5104: ins(0x5104, "MOV:G.W", "@H'F868, R0", [0xF868]),
0x5108: ins(0x5108, "MOV:G.W", "R0, @H'F860", [0xF860]),
0x510C: ins(0x510C, "MOV:G.W", "@H'F86A, R0", [0xF86A]),
0x5110: ins(0x5110, "MOV:G.W", "R0, @H'F862", [0xF862]),
0x5114: ins(0x5114, "MOV:G.W", "@H'F86C, R0", [0xF86C]),
0x5118: ins(0x5118, "MOV:G.W", "R0, @H'F864", [0xF864]),
0x5120: ins(0x5120, "MOV:E.B", "#H'5A, R0", []),
0x5124: ins(0x5124, "XOR.B", "@H'F860, R0", [0xF860]),
0x5128: ins(0x5128, "XOR.B", "@H'F861, R0", [0xF861]),
0x512C: ins(0x512C, "XOR.B", "@H'F862, R0", [0xF862]),
0x5130: ins(0x5130, "XOR.B", "@H'F863, R0", [0xF863]),
0x5134: ins(0x5134, "XOR.B", "@H'F864, R0", [0xF864]),
0x5138: ins(0x5138, "CMP:G.B", "@H'F865, R0", [0xF865]),
}
analysis = analyze_serial_reconstruction(instructions)
candidate = next(item for item in analysis["candidates"] if item["kind"] == "candidate_sci1_rx_frame")
self.assertEqual(candidate["frame_length"], 6)
self.assertEqual(candidate["capture_buffer_start"], 0xF868)
self.assertEqual(candidate["validation_buffer_start"], 0xF860)
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 = {
0x4000: ins(0x4000, "MOV:G.B", "R0, @SCI1_TDR", [0xFEDB]),
}
analysis = analyze_serial_reconstruction(instructions)
self.assertEqual(analysis["candidates"], [])
self.assertEqual(serial_reconstruction_comment_for_instruction(analysis, 0x4000), "")
self.assertEqual(serial_reconstruction_metadata_for_instruction(analysis, 0x4000), [])
self.assertEqual(serial_reconstruction_json_payload(analysis)["candidates"], [])
if __name__ == "__main__":
unittest.main()