from __future__ import annotations from collections.abc import Iterable, Mapping from dataclasses import dataclass from .formatting import h16, parse_int from .model import Instruction MANUAL_REFERENCES = [ "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", ] @dataclass(frozen=True) class SciRegister: channel: str name: str address: int SCI_REGISTERS: tuple[SciRegister, ...] = ( SciRegister("SCI1", "SCR", 0xFEDA), SciRegister("SCI1", "TDR", 0xFEDB), SciRegister("SCI1", "SSR", 0xFEDC), SciRegister("SCI1", "RDR", 0xFEDD), SciRegister("SCI2", "SCR", 0xFEF2), SciRegister("SCI2", "TDR", 0xFEF3), SciRegister("SCI2", "SSR", 0xFEF4), SciRegister("SCI2", "RDR", 0xFEF5), ) SCI_REGISTER_BY_ADDRESS = {register.address: register for register in SCI_REGISTERS} SCI_CHANNELS = ("SCI1", "SCI2") SCR_CONTROL_BITS = { 7: ("TIE", "TX interrupt", "TXI"), 6: ("RIE", "RX/ERI interrupts", "RXI and ERI"), 5: ("TE", "transmitter", "TXD output"), 4: ("RE", "receiver", "RXD input"), } SSR_FLAGS = { 7: ("TDRE", "transmit data register empty"), 6: ("RDRF", "receive-data-full"), 5: ("ORER", "overrun error"), 4: ("FER", "framing error"), 3: ("PER", "parity error"), } SSR_CLEAR_ACTIONS = { 7: ( "clear_tdre", "clear {channel} TDRE after TDR write; TXI can fire again when hardware reasserts TDRE", ), 6: ( "clear_rdrf", "clear {channel} RDRF with SSR R/(W)* semantics: write 0 clears latched hardware flag, write 1 preserves hardware-owned state", ), 5: ( "clear_orer", "clear {channel} ORER with SSR R/(W)* semantics: write 0 clears latched hardware flag, write 1 preserves hardware-owned state", ), 4: ( "clear_fer", "clear {channel} FER with SSR R/(W)* semantics: write 0 clears latched hardware flag, write 1 preserves hardware-owned state", ), 3: ( "clear_per", "clear {channel} PER with SSR R/(W)* semantics: write 0 clears latched hardware flag, write 1 preserves hardware-owned state", ), } def analyze_sci_protocol( instructions: Mapping[int, Instruction] | Iterable[Instruction], ) -> dict[str, object]: """Identify SCI protocol-level reads, writes, clears, and polling waits.""" ordered = _instruction_sequence(instructions) annotations: dict[int, list[str]] = {} instruction_metadata: dict[int, list[dict[str, object]]] = {} channels: dict[str, dict[str, list[dict[str, object]]]] = { channel: {"events": []} for channel in SCI_CHANNELS } events: list[dict[str, object]] = [] def record(event: dict[str, object]) -> None: public = dict(event) events.append(public) channel = str(public["channel"]) channels[channel]["events"].append(public) instruction_metadata.setdefault(int(public["address"]), []).append(public) comment = public.get("comment") if comment: parts = annotations.setdefault(int(public["address"]), []) text = str(comment) if text not in parts: parts.append(text) tdre_wait_events = _tdre_wait_events_by_address(ordered) for ins in ordered: for event in _instruction_events(ins): record(event) for event in tdre_wait_events.get(ins.address, []): record(event) return { "manual_references": MANUAL_REFERENCES, "channels": channels, "events": events, "annotations": { address: "; ".join(parts) for address, parts in sorted(annotations.items()) }, "instructions": instruction_metadata, } def sci_protocol_comment_for_instruction(analysis: Mapping[str, object] | None, address: int) -> str: if not analysis: return "" annotations = analysis.get("annotations") if not isinstance(annotations, Mapping): return "" comment = annotations.get(address) return str(comment) if comment else "" def sci_protocol_metadata_for_instruction( analysis: Mapping[str, object] | None, address: int, ) -> list[dict[str, object]]: if not analysis: return [] instructions = analysis.get("instructions") if not isinstance(instructions, Mapping): return [] metadata = instructions.get(address) return list(metadata) if isinstance(metadata, list) else [] def sci_protocol_json_payload(analysis: Mapping[str, object] | None) -> dict[str, object]: if not analysis: return { "manual_references": MANUAL_REFERENCES, "channels": {channel: {"events": []} for channel in SCI_CHANNELS}, "events": [], } return { "manual_references": analysis.get("manual_references", MANUAL_REFERENCES), "channels": analysis.get("channels", {}), "events": analysis.get("events", []), } def _instruction_events(ins: Instruction) -> list[dict[str, object]]: events: list[dict[str, object]] = [] for register in _referenced_sci_registers(ins): if register.name == "TDR": events.extend(_tdr_events(ins, register)) elif register.name == "RDR": events.extend(_rdr_events(ins, register)) elif register.name == "SSR": events.extend(_ssr_events(ins, register)) elif register.name == "SCR": events.extend(_scr_events(ins, register)) return events def _tdr_events(ins: Instruction, register: SciRegister) -> list[dict[str, object]]: if _access_direction(ins, register.address) != "write": return [] return [ _event( ins, register, action="write_tdr", comment=f"write RS232/SCI byte to {register.channel} TDR for transmission", ), ] def _rdr_events(ins: Instruction, register: SciRegister) -> list[dict[str, object]]: if _access_direction(ins, register.address) != "read": return [] return [ _event( ins, register, action="read_rdr", comment=f"read {register.channel} received byte from RDR", ), ] def _ssr_events(ins: Instruction, register: SciRegister) -> list[dict[str, object]]: if _access_direction(ins, register.address) != "write": return [] bit = _immediate_bit(ins.operands) if _mnemonic_root(ins.mnemonic) == "BCLR" and bit in SSR_CLEAR_ACTIONS: action, comment_template = SSR_CLEAR_ACTIONS[bit] flag, description = SSR_FLAGS[bit] return [ _event( ins, register, action=action, bit=bit, flag=flag, description=description, comment=comment_template.format(channel=register.channel), ), ] value = _written_immediate_value(ins) if value is None: return [] events: list[dict[str, object]] = [] for clear_bit, (action, comment_template) in SSR_CLEAR_ACTIONS.items(): if value & (1 << clear_bit): continue flag, description = SSR_FLAGS[clear_bit] events.append( _event( ins, register, action=action, bit=clear_bit, flag=flag, description=description, value=value, comment=comment_template.format(channel=register.channel), ), ) return events def _scr_events(ins: Instruction, register: SciRegister) -> list[dict[str, object]]: if _access_direction(ins, register.address) != "write": return [] bit = _immediate_bit(ins.operands) root = _mnemonic_root(ins.mnemonic) if root in {"BCLR", "BSET"} and bit in SCR_CONTROL_BITS: return [_scr_bit_event(ins, register, bit, enabled=root == "BSET")] value = _written_immediate_value(ins) if value is None: return [] return [ _scr_bit_event(ins, register, bit_number, enabled=bool(value & (1 << bit_number)), value=value) for bit_number in SCR_CONTROL_BITS ] def _scr_bit_event( ins: Instruction, register: SciRegister, bit: int, enabled: bool, value: int | None = None, ) -> dict[str, object]: bit_name, noun, interrupt_source = SCR_CONTROL_BITS[bit] verb = "enable" if enabled else "disable" action_noun = noun.replace("/", "_").replace(" ", "_").lower() if bit == 7: comment = ( f"{verb} {register.channel} TX interrupt (TIE); " "gates TXI when hardware sets TDRE" ) elif bit == 6: comment = f"{verb} {register.channel} receive and receive-error interrupts (RIE)" elif bit == 5: comment = f"{verb} {register.channel} transmitter (TE)" else: comment = f"{verb} {register.channel} receiver (RE)" return _event( ins, register, action=f"{verb}_{action_noun}", bit=bit, bit_name=bit_name, enabled=enabled, interrupt_source=interrupt_source, value=value, comment=comment, ) def _tdre_wait_events_by_address(ordered: list[Instruction]) -> dict[int, list[dict[str, object]]]: events_by_address: dict[int, list[dict[str, object]]] = {} for index, ins in enumerate(ordered): register = _single_sci_register(ins) if register is None or register.name != "SSR": continue if _mnemonic_root(ins.mnemonic) != "BTST" or _immediate_bit(ins.operands) != 7: continue branch = _next_beq_back_to(ordered, index, ins.address) if branch is None: continue events_by_address.setdefault(ins.address, []).append( _event( ins, register, action="wait_for_tdre", bit=7, flag="TDRE", branch_address=branch.address, branch_target=ins.address, comment=f"wait for {register.channel} transmit data register empty (TDRE=1)", ), ) events_by_address.setdefault(branch.address, []).append( _event( branch, register, action="tdre_wait_branch", bit=7, flag="TDRE", test_address=ins.address, branch_target=ins.address, comment=f"repeat {register.channel} transmit-empty wait while TDRE=0", ), ) return events_by_address def _next_beq_back_to(ordered: list[Instruction], index: int, target: int) -> Instruction | None: for candidate in ordered[index + 1 : index + 5]: if _mnemonic_root(candidate.mnemonic) != "BEQ": continue if target in candidate.targets: return candidate return None def _event( ins: Instruction, register: SciRegister, *, action: str, comment: str, **extra: object, ) -> dict[str, object]: event: dict[str, object] = { "address": ins.address, "instruction": ins.text, "action": action, "channel": register.channel, "register": register.name, "register_address": register.address, "register_address_hex": h16(register.address), "comment": comment, "manual": MANUAL_REFERENCES, } event.update({key: value for key, value in extra.items() if value is not None}) return event def _instruction_sequence( instructions: Mapping[int, Instruction] | Iterable[Instruction], ) -> list[Instruction]: values = instructions.values() if isinstance(instructions, Mapping) else instructions return sorted(values, key=lambda ins: ins.address) def _referenced_sci_registers(ins: Instruction) -> list[SciRegister]: registers: list[SciRegister] = [] seen: set[int] = set() for address in ins.references: register = SCI_REGISTER_BY_ADDRESS.get(address) if register is None or register.address in seen: continue seen.add(register.address) registers.append(register) return registers def _single_sci_register(ins: Instruction) -> SciRegister | None: registers = _referenced_sci_registers(ins) return registers[0] if len(registers) == 1 else None def _access_direction(ins: Instruction, address: int) -> str | None: root = _mnemonic_root(ins.mnemonic) if root in {"BTST", "CMP:E", "CMP:G", "CMP:I", "MOVFPE", "TST"}: return "read" if root in {"BCLR", "BNOT", "BSET", "CLR", "NEG", "NOT"}: return "write" if root in {"ADD:Q", "ADD:G", "ADDX", "AND", "OR", "SUB", "SUBS", "SUBX", "XOR"}: return "write" if root in {"MOV:G", "MOV:S", "MOVTPE"}: source, destination = _source_destination_operands(ins.operands) if _operand_mentions_address_or_register(destination, address): return "write" if _operand_mentions_address_or_register(source, address): return "read" if root in {"MOV:L", "MOV:F"}: return "read" if root == "STC": return "write" if root == "LDC": return "read" return None def _source_destination_operands(operands: str) -> tuple[str, str]: if "," not in operands: operand = operands.strip() return "", operand source, destination = operands.rsplit(",", 1) return source.strip(), destination.strip() def _operand_mentions_address_or_register(operand: str, address: int) -> bool: operand_upper = operand.upper() register = SCI_REGISTER_BY_ADDRESS.get(address) if register and f"{register.channel}_{register.name}" in operand_upper: return True return f"H'{address:04X}" in operand_upper or f"0X{address:04X}" in operand_upper def _immediate_bit(operands: str) -> int | None: source, _destination = _source_destination_operands(operands) if not source.startswith("#"): return None try: bit = parse_int(source[1:]) except ValueError: return None return bit if 0 <= bit <= 7 else None def _written_immediate_value(ins: Instruction) -> int | None: root = _mnemonic_root(ins.mnemonic) if root == "CLR": return 0 if root not in {"MOV:G", "MOV:S"}: return None source, _destination = _source_destination_operands(ins.operands) if not source.startswith("#"): return None try: return parse_int(source[1:]) & 0xFF except ValueError: return None def _mnemonic_root(mnemonic: str) -> str: return mnemonic.rsplit(".", 1)[0].upper()