376 lines
12 KiB
Python
376 lines
12 KiB
Python
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}"
|