1
0
Files
h8-536-decoder/h8536/sci_protocol.py
2026-05-25 15:10:32 +10:00

447 lines
15 KiB
Python

from __future__ import annotations
from collections.abc import Iterable, Mapping
from dataclasses import dataclass
from .formatting import h16, parse_int
from .model import Instruction
MANUAL_REFERENCES = [
"Manual/0900766b802125d0.md:15748 SCI register map for RDR/TDR/SCR/SSR",
"Manual/0900766b802125d0.md:15794 RDR stores received data and is CPU-readable",
"Manual/0900766b802125d0.md:15823 TDR holds the next byte to transmit",
"Manual/0900766b802125d0.md:15976 SCR.TIE enables/disables TXI on TDRE",
"Manual/0900766b802125d0.md:15993 SCR.RIE enables RXI and ERI",
"Manual/0900766b802125d0.md:16008 SCR.TE enables the transmitter",
"Manual/0900766b802125d0.md:16028 SCR.RE enables the receiver",
"Manual/0900766b802125d0.md:16090 SSR flags are cleared by writing zero",
"Manual/0900766b802125d0.md:16100 SSR.TDRE means TDR can accept the next byte",
"Manual/0900766b802125d0.md:16116 SSR.RDRF means received data reached RDR",
"Manual/0900766b802125d0.md:16127 SSR.ORER reports receive overrun",
"Manual/0900766b802125d0.md:16140 SSR.FER reports framing errors",
"Manual/0900766b802125d0.md:16147 SSR.PER reports parity errors",
]
@dataclass(frozen=True)
class SciRegister:
channel: str
name: str
address: int
SCI_REGISTERS: tuple[SciRegister, ...] = (
SciRegister("SCI1", "SCR", 0xFEDA),
SciRegister("SCI1", "TDR", 0xFEDB),
SciRegister("SCI1", "SSR", 0xFEDC),
SciRegister("SCI1", "RDR", 0xFEDD),
SciRegister("SCI2", "SCR", 0xFEF2),
SciRegister("SCI2", "TDR", 0xFEF3),
SciRegister("SCI2", "SSR", 0xFEF4),
SciRegister("SCI2", "RDR", 0xFEF5),
)
SCI_REGISTER_BY_ADDRESS = {register.address: register for register in SCI_REGISTERS}
SCI_CHANNELS = ("SCI1", "SCI2")
SCR_CONTROL_BITS = {
7: ("TIE", "TX interrupt", "TXI"),
6: ("RIE", "RX/ERI interrupts", "RXI and ERI"),
5: ("TE", "transmitter", "TXD output"),
4: ("RE", "receiver", "RXD input"),
}
SSR_FLAGS = {
7: ("TDRE", "transmit data register empty"),
6: ("RDRF", "receive-data-full"),
5: ("ORER", "overrun error"),
4: ("FER", "framing error"),
3: ("PER", "parity error"),
}
SSR_CLEAR_ACTIONS = {
7: ("clear_tdre", "clear {channel} transmit data register empty flag (TDRE)"),
6: ("clear_rdrf", "clear {channel} receive-data-full flag (RDRF)"),
5: ("clear_orer", "clear {channel} overrun error flag (ORER)"),
4: ("clear_fer", "clear {channel} framing error flag (FER)"),
3: ("clear_per", "clear {channel} parity error flag (PER)"),
}
def analyze_sci_protocol(
instructions: Mapping[int, Instruction] | Iterable[Instruction],
) -> dict[str, object]:
"""Identify SCI protocol-level reads, writes, clears, and polling waits."""
ordered = _instruction_sequence(instructions)
annotations: dict[int, list[str]] = {}
instruction_metadata: dict[int, list[dict[str, object]]] = {}
channels: dict[str, dict[str, list[dict[str, object]]]] = {
channel: {"events": []} for channel in SCI_CHANNELS
}
events: list[dict[str, object]] = []
def record(event: dict[str, object]) -> None:
public = dict(event)
events.append(public)
channel = str(public["channel"])
channels[channel]["events"].append(public)
instruction_metadata.setdefault(int(public["address"]), []).append(public)
comment = public.get("comment")
if comment:
parts = annotations.setdefault(int(public["address"]), [])
text = str(comment)
if text not in parts:
parts.append(text)
tdre_wait_events = _tdre_wait_events_by_address(ordered)
for ins in ordered:
for event in _instruction_events(ins):
record(event)
for event in tdre_wait_events.get(ins.address, []):
record(event)
return {
"manual_references": MANUAL_REFERENCES,
"channels": channels,
"events": events,
"annotations": {
address: "; ".join(parts)
for address, parts in sorted(annotations.items())
},
"instructions": instruction_metadata,
}
def sci_protocol_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_protocol_metadata_for_instruction(
analysis: Mapping[str, object] | None,
address: int,
) -> list[dict[str, object]]:
if not analysis:
return []
instructions = analysis.get("instructions")
if not isinstance(instructions, Mapping):
return []
metadata = instructions.get(address)
return list(metadata) if isinstance(metadata, list) else []
def sci_protocol_json_payload(analysis: Mapping[str, object] | None) -> dict[str, object]:
if not analysis:
return {
"manual_references": MANUAL_REFERENCES,
"channels": {channel: {"events": []} for channel in SCI_CHANNELS},
"events": [],
}
return {
"manual_references": analysis.get("manual_references", MANUAL_REFERENCES),
"channels": analysis.get("channels", {}),
"events": analysis.get("events", []),
}
def _instruction_events(ins: Instruction) -> list[dict[str, object]]:
events: list[dict[str, object]] = []
for register in _referenced_sci_registers(ins):
if register.name == "TDR":
events.extend(_tdr_events(ins, register))
elif register.name == "RDR":
events.extend(_rdr_events(ins, register))
elif register.name == "SSR":
events.extend(_ssr_events(ins, register))
elif register.name == "SCR":
events.extend(_scr_events(ins, register))
return events
def _tdr_events(ins: Instruction, register: SciRegister) -> list[dict[str, object]]:
if _access_direction(ins, register.address) != "write":
return []
return [
_event(
ins,
register,
action="write_tdr",
comment=f"write RS232/SCI byte to {register.channel} TDR for transmission",
),
]
def _rdr_events(ins: Instruction, register: SciRegister) -> list[dict[str, object]]:
if _access_direction(ins, register.address) != "read":
return []
return [
_event(
ins,
register,
action="read_rdr",
comment=f"read {register.channel} received byte from RDR",
),
]
def _ssr_events(ins: Instruction, register: SciRegister) -> list[dict[str, object]]:
if _access_direction(ins, register.address) != "write":
return []
bit = _immediate_bit(ins.operands)
if _mnemonic_root(ins.mnemonic) == "BCLR" and bit in SSR_CLEAR_ACTIONS:
action, comment_template = SSR_CLEAR_ACTIONS[bit]
flag, description = SSR_FLAGS[bit]
return [
_event(
ins,
register,
action=action,
bit=bit,
flag=flag,
description=description,
comment=comment_template.format(channel=register.channel),
),
]
value = _written_immediate_value(ins)
if value is None:
return []
events: list[dict[str, object]] = []
for clear_bit, (action, comment_template) in SSR_CLEAR_ACTIONS.items():
if value & (1 << clear_bit):
continue
flag, description = SSR_FLAGS[clear_bit]
events.append(
_event(
ins,
register,
action=action,
bit=clear_bit,
flag=flag,
description=description,
value=value,
comment=comment_template.format(channel=register.channel),
),
)
return events
def _scr_events(ins: Instruction, register: SciRegister) -> list[dict[str, object]]:
if _access_direction(ins, register.address) != "write":
return []
bit = _immediate_bit(ins.operands)
root = _mnemonic_root(ins.mnemonic)
if root in {"BCLR", "BSET"} and bit in SCR_CONTROL_BITS:
return [_scr_bit_event(ins, register, bit, enabled=root == "BSET")]
value = _written_immediate_value(ins)
if value is None:
return []
return [
_scr_bit_event(ins, register, bit_number, enabled=bool(value & (1 << bit_number)), value=value)
for bit_number in SCR_CONTROL_BITS
]
def _scr_bit_event(
ins: Instruction,
register: SciRegister,
bit: int,
enabled: bool,
value: int | None = None,
) -> dict[str, object]:
bit_name, noun, interrupt_source = SCR_CONTROL_BITS[bit]
verb = "enable" if enabled else "disable"
action_noun = noun.replace("/", "_").replace(" ", "_").lower()
if bit == 7:
comment = f"{verb} {register.channel} TX interrupt (TIE)"
elif bit == 6:
comment = f"{verb} {register.channel} receive and receive-error interrupts (RIE)"
elif bit == 5:
comment = f"{verb} {register.channel} transmitter (TE)"
else:
comment = f"{verb} {register.channel} receiver (RE)"
return _event(
ins,
register,
action=f"{verb}_{action_noun}",
bit=bit,
bit_name=bit_name,
enabled=enabled,
interrupt_source=interrupt_source,
value=value,
comment=comment,
)
def _tdre_wait_events_by_address(ordered: list[Instruction]) -> dict[int, list[dict[str, object]]]:
events_by_address: dict[int, list[dict[str, object]]] = {}
for index, ins in enumerate(ordered):
register = _single_sci_register(ins)
if register is None or register.name != "SSR":
continue
if _mnemonic_root(ins.mnemonic) != "BTST" or _immediate_bit(ins.operands) != 7:
continue
branch = _next_beq_back_to(ordered, index, ins.address)
if branch is None:
continue
events_by_address.setdefault(ins.address, []).append(
_event(
ins,
register,
action="wait_for_tdre",
bit=7,
flag="TDRE",
branch_address=branch.address,
branch_target=ins.address,
comment=f"wait for {register.channel} transmit data register empty (TDRE=1)",
),
)
events_by_address.setdefault(branch.address, []).append(
_event(
branch,
register,
action="tdre_wait_branch",
bit=7,
flag="TDRE",
test_address=ins.address,
branch_target=ins.address,
comment=f"repeat {register.channel} transmit-empty wait while TDRE=0",
),
)
return events_by_address
def _next_beq_back_to(ordered: list[Instruction], index: int, target: int) -> Instruction | None:
for candidate in ordered[index + 1 : index + 5]:
if _mnemonic_root(candidate.mnemonic) != "BEQ":
continue
if target in candidate.targets:
return candidate
return None
def _event(
ins: Instruction,
register: SciRegister,
*,
action: str,
comment: str,
**extra: object,
) -> dict[str, object]:
event: dict[str, object] = {
"address": ins.address,
"instruction": ins.text,
"action": action,
"channel": register.channel,
"register": register.name,
"register_address": register.address,
"register_address_hex": h16(register.address),
"comment": comment,
"manual": MANUAL_REFERENCES,
}
event.update({key: value for key, value in extra.items() if value is not None})
return event
def _instruction_sequence(
instructions: Mapping[int, Instruction] | Iterable[Instruction],
) -> list[Instruction]:
values = instructions.values() if isinstance(instructions, Mapping) else instructions
return sorted(values, key=lambda ins: ins.address)
def _referenced_sci_registers(ins: Instruction) -> list[SciRegister]:
registers: list[SciRegister] = []
seen: set[int] = set()
for address in ins.references:
register = SCI_REGISTER_BY_ADDRESS.get(address)
if register is None or register.address in seen:
continue
seen.add(register.address)
registers.append(register)
return registers
def _single_sci_register(ins: Instruction) -> SciRegister | None:
registers = _referenced_sci_registers(ins)
return registers[0] if len(registers) == 1 else None
def _access_direction(ins: Instruction, address: int) -> str | None:
root = _mnemonic_root(ins.mnemonic)
if root in {"BTST", "CMP:E", "CMP:G", "CMP:I", "MOVFPE", "TST"}:
return "read"
if root in {"BCLR", "BNOT", "BSET", "CLR", "NEG", "NOT"}:
return "write"
if root in {"ADD:Q", "ADD:G", "ADDX", "AND", "OR", "SUB", "SUBS", "SUBX", "XOR"}:
return "write"
if root in {"MOV:G", "MOV:S", "MOVTPE"}:
source, destination = _source_destination_operands(ins.operands)
if _operand_mentions_address_or_register(destination, address):
return "write"
if _operand_mentions_address_or_register(source, address):
return "read"
if root in {"MOV:L", "MOV:F"}:
return "read"
if root == "STC":
return "write"
if root == "LDC":
return "read"
return None
def _source_destination_operands(operands: str) -> tuple[str, str]:
if "," not in operands:
operand = operands.strip()
return "", operand
source, destination = operands.rsplit(",", 1)
return source.strip(), destination.strip()
def _operand_mentions_address_or_register(operand: str, address: int) -> bool:
operand_upper = operand.upper()
register = SCI_REGISTER_BY_ADDRESS.get(address)
if register and f"{register.channel}_{register.name}" in operand_upper:
return True
return f"H'{address:04X}" in operand_upper or f"0X{address:04X}" in operand_upper
def _immediate_bit(operands: str) -> int | None:
source, _destination = _source_destination_operands(operands)
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 _written_immediate_value(ins: Instruction) -> int | None:
root = _mnemonic_root(ins.mnemonic)
if root == "CLR":
return 0
if root not in {"MOV:G", "MOV:S"}:
return None
source, _destination = _source_destination_operands(ins.operands)
if not source.startswith("#"):
return None
try:
return parse_int(source[1:]) & 0xFF
except ValueError:
return None
def _mnemonic_root(mnemonic: str) -> str:
return mnemonic.rsplit(".", 1)[0].upper()