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}, "SCI2": {"SMR": 0xFEF0, "BRR": 0xFEF1, "SCR": 0xFEF2}, } SCI_REGISTER_BY_ADDRESS = { address: (channel, register) for channel, registers in SCI_REGISTERS.items() for register, address in registers.items() } FORMULAS = { "async": "B = clock_hz / (64 * 2^(2n) * (N + 1))", "sync": "B = clock_hz / (8 * 2^(2n) * (N + 1))", } MANUAL_REFERENCES = [ "Manual/0900766b802125d0.md:15837 SMR selects SCI mode and CKS1/CKS0 internal clock source", "Manual/0900766b802125d0.md:16027 SCR.CKE1 selects internal or external clock source", "Manual/0900766b802125d0.md:16177 BRR and SMR.CKS determine the baud-rate generator", "Manual/0900766b802125d0.md:16303 asynchronous BRR formula", "Manual/0900766b802125d0.md:16379 synchronous BRR formula", "Manual/0900766b802125d0.md:16410 SCI clock source selection tables", ] _READ_ONLY_ROOTS = {"BTST", "CMP:E", "CMP:G", "CMP:I", "MOVFPE", "TST"} def analyze_sci( instructions: Mapping[int, Instruction], clock_hz: int | None = None, ) -> dict[str, object]: """Track SCI setup writes in listing order and infer conservative baud metadata.""" state: dict[str, dict[str, int | None]] = { channel: {"SMR": None, "BRR": None, "SCR": None} for channel in SCI_REGISTERS } channels: dict[str, dict[str, list[dict[str, object]]]] = { channel: {"writes": [], "configurations": []} for channel in SCI_REGISTERS } annotations: dict[int, str] = {} instruction_metadata: dict[int, dict[str, object]] = {} seen_configurations: set[tuple[object, ...]] = set() for address in sorted(instructions): ins = instructions[address] writes = _apply_instruction(ins, state) if not writes: continue affected_channels = sorted({str(write["channel"]) for write in writes}) instruction_entry: dict[str, object] = {"writes": writes} for write in writes: channel = str(write["channel"]) channels[channel]["writes"].append(write) emitted: list[dict[str, object]] = [] for channel in affected_channels: inference = _infer_channel(channel, state[channel], clock_hz) if inference is None: continue key = _configuration_key(channel, inference, clock_hz) if key in seen_configurations: continue seen_configurations.add(key) inference = {**inference, "address": ins.address, "instruction": ins.text} channels[channel]["configurations"].append(inference) emitted.append(inference) if emitted: annotation = "; ".join(str(item["comment"]) for item in emitted if item.get("comment")) if annotation: annotations[ins.address] = annotation instruction_entry["inferences"] = emitted instruction_metadata[ins.address] = instruction_entry return { "clock_hz": clock_hz, "formulas": FORMULAS, "manual_references": MANUAL_REFERENCES, "channels": channels, "annotations": annotations, "instructions": instruction_metadata, } def sci_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_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) return metadata if isinstance(metadata, dict) else None def sci_json_payload(analysis: Mapping[str, object] | None) -> dict[str, object]: if not analysis: return { "clock_hz": None, "formulas": FORMULAS, "manual_references": MANUAL_REFERENCES, "channels": {channel: {"writes": [], "configurations": []} for channel in SCI_REGISTERS}, } return { "clock_hz": analysis.get("clock_hz"), "formulas": analysis.get("formulas", FORMULAS), "manual_references": analysis.get("manual_references", MANUAL_REFERENCES), "channels": analysis.get("channels", {}), } def _apply_instruction(ins: Instruction, state: dict[str, dict[str, int | None]]) -> list[dict[str, object]]: root = _mnemonic_root(ins.mnemonic) if root in _READ_ONLY_ROOTS: return [] size = _mnemonic_size(ins.mnemonic) base_address = _destination_base_address(ins, size) if base_address is None: return [] operation = root value: int | None if root in {"BCLR", "BNOT", "BSET"}: bit = _immediate_bit(ins.operands) value = None targets = list(_target_registers(base_address, "B")) if bit is not None and len(targets) == 1: channel, register, _address = targets[0] current = state[channel][register] if current is not None: if root == "BSET": value = current | (1 << bit) elif root == "BCLR": value = current & ~(1 << bit) else: value = current ^ (1 << bit) value &= 0xFF return _record_writes(ins, targets, value, operation, state) if root == "CLR": value = 0 else: value = _immediate_source_value(ins.operands) if value is None and root == "NOT": targets = list(_target_registers(base_address, size)) if len(targets) == 1: channel, register, _address = targets[0] current = state[channel][register] value = None if current is None else (~current) & 0xFF targets = list(_target_registers(base_address, size)) if not targets: return [] if root in {"CMP", "CMP:E", "CMP:G", "CMP:I", "TST"}: return [] return _record_writes(ins, targets, value, operation, state) def _record_writes( ins: Instruction, targets: list[tuple[str, str, int]], value: int | None, operation: str, state: dict[str, dict[str, int | None]], ) -> list[dict[str, object]]: width = max(len(targets), 1) writes: list[dict[str, object]] = [] for index, (channel, register, register_address) in enumerate(targets): byte_value = _byte_for_target(value, width, index) state[channel][register] = byte_value event: dict[str, object] = { "address": ins.address, "instruction": ins.text, "channel": channel, "register": register, "register_address": register_address, "operation": operation, "value": byte_value, } if byte_value is not None: event["value_hex"] = _hex8(byte_value) writes.append(event) return writes def _target_registers(base_address: int, size: str) -> list[tuple[str, str, int]]: width = 2 if size == "W" else 1 targets: list[tuple[str, str, int]] = [] for offset in range(width): address = base_address + offset register = SCI_REGISTER_BY_ADDRESS.get(address) if register is None: continue channel, name = register targets.append((channel, name, address)) return targets def _destination_base_address(ins: Instruction, size: str) -> int | None: destination = _destination_operand(ins.operands) if destination is None or not destination.startswith("@"): return None width = 2 if size == "W" else 1 for address in ins.references: if any((address + offset) in SCI_REGISTER_BY_ADDRESS for offset in range(width)): return address return None 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 _immediate_source_value(operands: str) -> int | None: source = operands.rsplit(",", 1)[0].strip() if "," in operands else "" if not source.startswith("#"): return None try: return parse_int(source[1:]) except ValueError: return None def _immediate_bit(operands: str) -> int | None: source = operands.rsplit(",", 1)[0].strip() if "," in operands else "" 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 _mnemonic_root(mnemonic: str) -> str: return mnemonic.split(".", 1)[0] def _mnemonic_size(mnemonic: str) -> str: if mnemonic.endswith(".W"): return "W" return "B" def _byte_for_target(value: int | None, width: int, index: int) -> int | None: if value is None: return None shift = 8 * (width - index - 1) return (value >> shift) & 0xFF def _infer_channel( channel: str, registers: Mapping[str, int | None], clock_hz: int | None, ) -> dict[str, object] | None: smr = registers.get("SMR") brr = registers.get("BRR") scr = registers.get("SCR") if smr is None or brr is None: return None mode = "sync" if smr & 0x80 else "async" n = smr & 0x03 denominator = _baud_denominator(mode, n, brr) clock_source = "unknown" if scr is None else ("external" if scr & 0x02 else "internal") inference: dict[str, object] = { "channel": channel, "mode": mode, "mode_summary": _mode_summary(smr), "smr": smr, "smr_hex": _hex8(smr), "brr": brr, "brr_hex": _hex8(brr), "scr": scr, "scr_hex": _hex8(scr) if scr is not None else None, "cks_n": n, "cks_divisor": 1 << (2 * n), "denominator": denominator, "clock_source": clock_source, "formula": FORMULAS[mode], } if clock_hz is None: inference["baud_bps"] = None inference["confidence"] = "partial" inference["reason"] = "clock_hz_missing" inference["comment"] = ( f"{channel} {_mode_summary(smr)} BRR N={brr} CKS n={n}; baud needs --clock-hz" ) return inference if clock_source == "external": inference["baud_bps"] = None inference["confidence"] = "partial" inference["reason"] = "external_clock_selected" inference["comment"] = ( f"{channel} {_mode_summary(smr)} external clock selected; " f"BRR N={brr} CKS n={n}, internal baud not inferred" ) return inference baud = clock_hz / denominator inference["baud_bps"] = baud inference["confidence"] = "high" if clock_source == "internal" else "partial" inference["reason"] = None if clock_source == "internal" else "scr_clock_source_unknown" noun = "baud" if clock_source == "internal" else "baud generator" source_note = "; SCR clock source unknown" if clock_source == "unknown" else "" inference["comment"] = ( f"{channel} {_mode_summary(smr)} {noun} {_format_bps(baud)} " f"(BRR N={brr}, CKS n={n}, clock={clock_hz} Hz{source_note})" ) return inference def _configuration_key(channel: str, inference: Mapping[str, object], clock_hz: int | None) -> tuple[object, ...]: return ( channel, inference.get("smr"), inference.get("brr"), inference.get("clock_source"), clock_hz, ) def _baud_denominator(mode: str, n: int, brr: int) -> int: base = 8 if mode == "sync" else 64 return base * (1 << (2 * n)) * (brr + 1) def _mode_summary(smr: int) -> str: if smr & 0x80: return "sync" char_len = "7-bit" if smr & 0x40 else "8-bit" if smr & 0x20: parity = "odd parity" if smr & 0x10 else "even parity" else: parity = "no parity" stop = "2 stop" if smr & 0x08 else "1 stop" return f"async {char_len} {parity} {stop}" def _format_bps(baud: float) -> str: rounded = round(baud) if abs(baud - rounded) < 0.001: return f"{rounded} bps" return f"{baud:.3f}".rstrip("0").rstrip(".") + " bps" def _hex8(value: int) -> str: return f"H'{value & 0xFF:02X}"