DTC and SCI improvements
This commit is contained in:
375
h8536/sci.py
Normal file
375
h8536/sci.py
Normal file
@@ -0,0 +1,375 @@
|
||||
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}"
|
||||
Reference in New Issue
Block a user