1
0

DTC and SCI improvements

This commit is contained in:
Aiden
2026-05-25 14:22:32 +10:00
parent 62d1c3c876
commit 80819448cf
21 changed files with 13823 additions and 86 deletions

375
h8536/sci.py Normal file
View 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}"