from __future__ import annotations from collections.abc import Mapping from .formatting import parse_int from .model import Instruction SCI_REGISTERS: dict[str, dict[str, int]] = { "SCI1": { "SMR": 0xFED8, "BRR": 0xFED9, "SCR": 0xFEDA, "TDR": 0xFEDB, "SSR": 0xFEDC, "RDR": 0xFEDD, }, "SCI2": { "SMR": 0xFEF0, "BRR": 0xFEF1, "SCR": 0xFEF2, "TDR": 0xFEF3, "SSR": 0xFEF4, "RDR": 0xFEF5, }, } SCI_REGISTER_BY_ADDRESS = { address: (channel, register) for channel, registers in SCI_REGISTERS.items() for register, address in registers.items() } SYSCR2_ADDRESS = 0xFEFD SYSCR2_INITIAL = 0x80 P9SCI2E_BIT = 0 SCR_INITIAL = 0x0C MANUAL_REFERENCES = [ "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", ] BOARD_PROFILES = { "sony_rcp_tx7": { "name": "Sony RCP-TX7", "summary": "Board trace ties the H8/536 SCI1 pins to a MAX202 RS232 transceiver.", "manual_references": MANUAL_REFERENCES, "traces": [ { "channel": "SCI1", "signal": "TXD", "h8_pin": 66, "h8_pin_name": "P95/TXD", "h8_function": "TXD1", "max202_pin": 11, "evidence": "MAX202 pin 11 traces to H8 pin 66", }, { "channel": "SCI1", "signal": "RXD", "h8_pin": 67, "h8_pin_name": "P96/RXD", "h8_function": "RXD1", "max202_pin": 12, "evidence": "MAX202 pin 12 traces to H8 pin 67", }, ], }, } _READ_ONLY_ROOTS = {"BTST", "CMP", "CMP:E", "CMP:G", "CMP:I", "MOVFPE", "TST"} _BIT_WRITE_ROOTS = {"BCLR", "BNOT", "BSET"} def analyze_board_profile( instructions: Mapping[int, Instruction], board: str = "sony_rcp_tx7", ) -> dict[str, object]: """Annotate board-specific serial-path accesses without changing disassembly output.""" if board not in BOARD_PROFILES: raise ValueError(f"unsupported board profile: {board}") profile = BOARD_PROFILES[board] annotations: dict[int, str] = {} instruction_metadata: dict[int, dict[str, object]] = {} channels = _initial_channel_payload(profile) register_values: dict[int, int | None] = { SYSCR2_ADDRESS: SYSCR2_INITIAL, SCI_REGISTERS["SCI1"]["SCR"]: SCR_INITIAL, SCI_REGISTERS["SCI2"]["SCR"]: SCR_INITIAL, } for address in sorted(instructions): ins = instructions[address] access_kind = _access_kind(ins) access_records: list[dict[str, object]] = [] comments: list[str] = [] if SYSCR2_ADDRESS in _expanded_references(ins, access_kind): value = _write_value(ins, SYSCR2_ADDRESS, register_values.get(SYSCR2_ADDRESS), 1, 0) if access_kind == "write": register_values[SYSCR2_ADDRESS] = value record = _syscr2_access(ins, access_kind, register_values.get(SYSCR2_ADDRESS)) access_records.append(record) comments.append(str(record["comment"])) for target in _sci_targets(ins, access_kind): register_address = int(target["register_address"]) value = _write_value( ins, register_address, register_values.get(register_address), int(target["width"]), int(target["index"]), ) if access_kind == "write": register_values[register_address] = value channel = str(target["channel"]) register = str(target["register"]) p9sci2e = _bit_state(register_values.get(SYSCR2_ADDRESS), P9SCI2E_BIT) record = _sci_access( ins, channel, register, register_address, access_kind, value, register_values.get(register_address), p9sci2e, ) access_records.append(record) comments.append(str(record["comment"])) channel_payload = channels[channel] channel_accesses = channel_payload["accesses"] if isinstance(channel_accesses, list): channel_accesses.append(record) if register == "SCR": channel_payload["scr"] = _scr_metadata(register_values.get(register_address)) if access_records: comment = "; ".join(_dedupe(comments)) annotations[ins.address] = comment instruction_metadata[ins.address] = { "accesses": access_records, "comment": comment, } channels["SCI2"]["p9sci2e"] = _bit_state(register_values.get(SYSCR2_ADDRESS), P9SCI2E_BIT) return { "board": board, "name": profile["name"], "summary": profile["summary"], "manual_references": list(profile["manual_references"]), "traces": [dict(trace) for trace in profile["traces"]], "channels": channels, "annotations": annotations, "instructions": instruction_metadata, "state": { "SYSCR2": _register_state(register_values.get(SYSCR2_ADDRESS)), "P9SCI2E": _bit_state(register_values.get(SYSCR2_ADDRESS), P9SCI2E_BIT), }, } def board_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) if comment is None: comment = annotations.get(str(address)) return str(comment) if comment else "" def board_metadata_for_instruction( analysis: Mapping[str, object] | None, address: int, ) -> dict[str, object] | None: if not analysis: return None instructions = analysis.get("instructions") if not isinstance(instructions, Mapping): return None metadata = instructions.get(address) if metadata is None: metadata = instructions.get(str(address)) return metadata if isinstance(metadata, dict) else None def board_json_payload(analysis: Mapping[str, object] | None) -> dict[str, object]: if not analysis: return { "board": None, "name": None, "summary": None, "manual_references": [], "traces": [], "channels": {}, "instructions": {}, } return { "board": analysis.get("board"), "name": analysis.get("name"), "summary": analysis.get("summary"), "manual_references": analysis.get("manual_references", []), "traces": analysis.get("traces", []), "channels": analysis.get("channels", {}), "instructions": analysis.get("instructions", {}), "state": analysis.get("state", {}), } def _initial_channel_payload(profile: Mapping[str, object]) -> dict[str, dict[str, object]]: traces = [dict(trace) for trace in profile["traces"] if isinstance(trace, Mapping)] return { "SCI1": { "traced_to_max202": True, "path": "RS232/MAX202", "pins": traces, "scr": _scr_metadata(SCR_INITIAL), "accesses": [], }, "SCI2": { "traced_to_max202": False, "path": None, "note": "Sony RCP-TX7 MAX202 board traces are on SCI1 P95/P96, not SCI2 P92/P93.", "p9sci2e": False, "scr": _scr_metadata(SCR_INITIAL), "accesses": [], }, } def _syscr2_access(ins: Instruction, access: str, value: int | None) -> dict[str, object]: p9sci2e = _bit_state(value, P9SCI2E_BIT) comment = _syscr2_comment(access, p9sci2e) record: dict[str, object] = { "address": ins.address, "instruction": ins.text, "register": "SYSCR2", "register_address": SYSCR2_ADDRESS, "access": access, "p9sci2e": p9sci2e, "comment": comment, } if value is not None: record["value"] = value record["value_hex"] = _hex8(value) return record def _sci_access( ins: Instruction, channel: str, register: str, register_address: int, access: str, value: int | None, state_value: int | None, p9sci2e: bool | None, ) -> dict[str, object]: comment = _sci_comment(channel, register, access, value if value is not None else state_value, p9sci2e) record: dict[str, object] = { "address": ins.address, "instruction": ins.text, "channel": channel, "register": register, "register_address": register_address, "access": access, "traced_to_max202": channel == "SCI1", "comment": comment, } if value is not None: record["value"] = value record["value_hex"] = _hex8(value) if register == "SCR": record["scr"] = _scr_metadata(value if value is not None else state_value) if channel == "SCI2": record["p9sci2e"] = p9sci2e return record def _sci_comment( channel: str, register: str, access: str, value: int | None, p9sci2e: bool | None, ) -> str: if channel == "SCI1": return _sci1_comment(register, access, value) return _sci2_comment(register, access, p9sci2e) def _sci1_comment(register: str, access: str, value: int | None) -> str: if register in {"SMR", "BRR"}: return ( f"SCI1 {register} serial init for traced RS232/MAX202 path " "(H8 pin 66 P95/TXD to MAX202 pin 11; MAX202 pin 12 to H8 pin 67 P96/RXD)" ) if register == "SCR": bits = _scr_bits_text(value) suffix = f" {bits}" if bits else "" return ( f"SCI1 SCR {access}{suffix}; TE/RE select the traced RS232/MAX202 pins " "(P95/TXD pin 66 to MAX202 pin 11, P96/RXD pin 67 to MAX202 pin 12)" ) if register == "TDR": verb = "transmits" if access == "write" else "accesses transmit buffer" return ( f"SCI1 TDR {access} {verb} on traced RS232/MAX202 path: " "H8 pin 66 P95/TXD -> MAX202 pin 11" ) if register == "RDR": verb = "receives" if access == "read" else "accesses receive buffer" return ( f"SCI1 RDR {access} {verb} from traced RS232/MAX202 path: " "MAX202 pin 12 -> H8 pin 67 P96/RXD" ) if register == "SSR": return "SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use" return f"SCI1 {register} {access} on traced RS232/MAX202 path" def _sci2_comment(register: str, access: str, p9sci2e: bool | None) -> str: prefix = f"SCI2 {register} {access}; not the traced MAX202 path" if p9sci2e is False: return ( f"{prefix}; P9SCI2E=0 disables SCI2 pins P92/P93/P94, " "while the board trace is SCI1 P95/P96" ) if p9sci2e is True: return f"{prefix}; P9SCI2E=1 may route SCI2 pins, but the board trace is SCI1 P95/P96" return f"{prefix}; P9SCI2E state unknown, and the board trace is SCI1 P95/P96" def _syscr2_comment(access: str, p9sci2e: bool | None) -> str: if p9sci2e is False: return ( f"SYSCR2 {access} leaves P9SCI2E=0; SCI2 pins are disabled, " "so SCI2 is not the traced MAX202 path; traced RS232/MAX202 remains SCI1 P95/P96" ) if p9sci2e is True: return ( f"SYSCR2 {access} sets P9SCI2E=1; SCI2 pins may be enabled, " "but Sony RCP-TX7 MAX202 traces are SCI1 P95/P96" ) return f"SYSCR2 {access}; P9SCI2E unknown, board trace remains SCI1 P95/P96" def _sci_targets(ins: Instruction, access: str) -> list[dict[str, object]]: width = _mnemonic_width(ins.mnemonic) targets: list[dict[str, object]] = [] seen: set[int] = set() for base_address in ins.references: target_width = width if _operand_references_memory(ins, access) else 1 for index in range(target_width): register_address = base_address + index if register_address in seen: continue register_info = SCI_REGISTER_BY_ADDRESS.get(register_address) if register_info is None: continue seen.add(register_address) channel, register = register_info targets.append( { "channel": channel, "register": register, "register_address": register_address, "width": target_width, "index": index, }, ) return targets def _expanded_references(ins: Instruction, access: str) -> set[int]: width = _mnemonic_width(ins.mnemonic) target_width = width if _operand_references_memory(ins, access) else 1 addresses: set[int] = set() for base_address in ins.references: for index in range(target_width): addresses.add(base_address + index) return addresses def _operand_references_memory(ins: Instruction, access: str) -> bool: operand = _destination_operand(ins.operands) if access == "write" else _source_operand(ins.operands) return bool(operand and operand.startswith("@")) def _access_kind(ins: Instruction) -> str: root = _mnemonic_root(ins.mnemonic) if root in _READ_ONLY_ROOTS: return "read" destination = _destination_operand(ins.operands) if destination and destination.startswith("@"): return "write" return "read" def _write_value( ins: Instruction, register_address: int, current_value: int | None, width: int, index: int, ) -> int | None: if _access_kind(ins) != "write": return None root = _mnemonic_root(ins.mnemonic) if root == "CLR": return 0 if root in _BIT_WRITE_ROOTS: bit = _immediate_bit(ins.operands) if bit is None or current_value is None: return None if root == "BSET": return (current_value | (1 << bit)) & 0xFF if root == "BCLR": return (current_value & ~(1 << bit)) & 0xFF return (current_value ^ (1 << bit)) & 0xFF value = _immediate_source_value(ins.operands) if value is None: return None return _byte_for_target(value, width, index) def _destination_operand(operands: str) -> str | None: operands = operands.strip() if not operands: return None if "," not in operands: return operands return operands.rsplit(",", 1)[1].strip() def _source_operand(operands: str) -> str | None: operands = operands.strip() if not operands: return None if "," not in operands: return operands return operands.rsplit(",", 1)[0].strip() def _immediate_source_value(operands: str) -> int | None: source = _source_operand(operands) or "" if not source.startswith("#"): return None try: return parse_int(source[1:]) except ValueError: return None def _immediate_bit(operands: str) -> int | None: value = _immediate_source_value(operands) if value is None: return None return value if 0 <= value <= 7 else None def _mnemonic_root(mnemonic: str) -> str: return mnemonic.split(".", 1)[0] def _mnemonic_width(mnemonic: str) -> int: return 2 if mnemonic.endswith(".W") else 1 def _byte_for_target(value: int, width: int, index: int) -> int: shift = 8 * (width - index - 1) return (value >> shift) & 0xFF def _scr_metadata(value: int | None) -> dict[str, object]: metadata = _register_state(value) metadata.update( { "tie": None if value is None else bool(value & 0x80), "rie": None if value is None else bool(value & 0x40), "tx_enabled": None if value is None else bool(value & 0x20), "rx_enabled": None if value is None else bool(value & 0x10), }, ) return metadata def _register_state(value: int | None) -> dict[str, object]: return { "value": value, "value_hex": None if value is None else _hex8(value), } def _scr_bits_text(value: int | None) -> str: if value is None: return "" return f"TE={1 if value & 0x20 else 0} RE={1 if value & 0x10 else 0}" def _bit_state(value: int | None, bit: int) -> bool | None: if value is None: return None return bool(value & (1 << bit)) 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 _hex8(value: int) -> str: return f"H'{value & 0xFF:02X}"