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

256
h8536/peripheral_access.py Normal file
View File

@@ -0,0 +1,256 @@
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass
from .formatting import h16
from .model import Instruction
MANUAL_REFERENCES = [
"Manual/0900766b802125d0.md:12185 FRT FRC/OCRA/OCRB/ICR use TEMP for 16-bit CPU access",
"Manual/0900766b802125d0.md:12193 FRT byte access order is upper byte then lower byte",
"Manual/0900766b802125d0.md:12212 OCRA/OCRB reads are direct; writes still use TEMP",
"Manual/0900766b802125d0.md:17546 A/D ADDRA-ADDRD lower byte is accessed through TEMP",
"Manual/0900766b802125d0.md:17556 A/D full-result byte reads must be upper byte then lower byte",
]
@dataclass(frozen=True)
class TempRegisterPair:
name: str
high: int
low: int
module: str
read_only: bool = False
high_only_read_ok: bool = False
direct_read_without_temp: bool = False
TEMP_REGISTER_PAIRS: tuple[TempRegisterPair, ...] = (
TempRegisterPair("FRT1_FRC", 0xFE92, 0xFE93, "FRT TEMP"),
TempRegisterPair("FRT1_OCRA", 0xFE94, 0xFE95, "FRT TEMP", direct_read_without_temp=True),
TempRegisterPair("FRT1_OCRB", 0xFE96, 0xFE97, "FRT TEMP", direct_read_without_temp=True),
TempRegisterPair("FRT1_ICR", 0xFE98, 0xFE99, "FRT TEMP", read_only=True),
TempRegisterPair("FRT2_FRC", 0xFEA2, 0xFEA3, "FRT TEMP"),
TempRegisterPair("FRT2_OCRA", 0xFEA4, 0xFEA5, "FRT TEMP", direct_read_without_temp=True),
TempRegisterPair("FRT2_OCRB", 0xFEA6, 0xFEA7, "FRT TEMP", direct_read_without_temp=True),
TempRegisterPair("FRT2_ICR", 0xFEA8, 0xFEA9, "FRT TEMP", read_only=True),
TempRegisterPair("FRT3_FRC", 0xFEB2, 0xFEB3, "FRT TEMP"),
TempRegisterPair("FRT3_OCRA", 0xFEB4, 0xFEB5, "FRT TEMP", direct_read_without_temp=True),
TempRegisterPair("FRT3_OCRB", 0xFEB6, 0xFEB7, "FRT TEMP", direct_read_without_temp=True),
TempRegisterPair("FRT3_ICR", 0xFEB8, 0xFEB9, "FRT TEMP", read_only=True),
TempRegisterPair("ADDRA", 0xFEE0, 0xFEE1, "A/D TEMP", read_only=True, high_only_read_ok=True),
TempRegisterPair("ADDRB", 0xFEE2, 0xFEE3, "A/D TEMP", read_only=True, high_only_read_ok=True),
TempRegisterPair("ADDRC", 0xFEE4, 0xFEE5, "A/D TEMP", read_only=True, high_only_read_ok=True),
TempRegisterPair("ADDRD", 0xFEE6, 0xFEE7, "A/D TEMP", read_only=True, high_only_read_ok=True),
)
TEMP_PAIR_BY_ADDRESS = {
address: (pair, byte_name)
for pair in TEMP_REGISTER_PAIRS
for address, byte_name in ((pair.high, "high"), (pair.low, "low"))
}
READ_ONLY_ROOTS = {"BTST", "CMP:E", "CMP:G", "CMP:I", "MOVFPE", "TST"}
WRITE_ROOTS = {"BCLR", "BNOT", "BSET", "CLR", "MOV:G", "MOV:S", "MOVTPE", "NEG", "NOT"}
def analyze_peripheral_access(instructions: Mapping[int, Instruction]) -> dict[str, object]:
annotations: dict[int, list[str]] = {}
warnings: list[dict[str, object]] = []
instruction_metadata: dict[int, list[dict[str, object]]] = {}
pending: dict[tuple[str, str], dict[str, object]] = {}
for address in sorted(instructions):
ins = instructions[address]
accesses = _instruction_accesses(ins)
if not accesses:
continue
instruction_metadata[address] = [_public_access(access) for access in accesses]
for access in accesses:
note, warning = _analyze_access(access, pending)
if note:
annotations.setdefault(address, []).append(note)
if warning:
warnings.append(warning)
for item in pending.values():
pair = item["pair"]
assert isinstance(pair, TempRegisterPair)
if _pending_high_byte_is_suspicious(pair, str(item["direction"])):
warnings.append(
{
"address": item["address"],
"register": pair.name,
"severity": "warning",
"message": (
f"{pair.name} high byte access was not followed by low byte; "
"manual recommends word access or high-byte then low-byte"
),
"manual": MANUAL_REFERENCES,
},
)
return {
"manual_references": MANUAL_REFERENCES,
"warnings": warnings,
"annotations": {
address: "; ".join(parts)
for address, parts in sorted(annotations.items())
},
"instructions": instruction_metadata,
}
def peripheral_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 peripheral_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 metadata if isinstance(metadata, list) else []
def peripheral_json_payload(analysis: Mapping[str, object] | None) -> dict[str, object]:
if not analysis:
return {"manual_references": MANUAL_REFERENCES, "warnings": []}
return {
"manual_references": analysis.get("manual_references", MANUAL_REFERENCES),
"warnings": analysis.get("warnings", []),
}
def _analyze_access(
access: dict[str, object],
pending: dict[tuple[str, str], dict[str, object]],
) -> tuple[str, dict[str, object] | None]:
pair = access["pair"]
assert isinstance(pair, TempRegisterPair)
direction = str(access["direction"])
byte = str(access["byte"])
size = str(access["size"])
key = (pair.name, direction)
if size == "W" and byte == "high":
pending.pop(key, None)
return f"{pair.name} word {direction}; TEMP byte-order hazard avoided", None
if byte == "high":
pending[key] = access
if direction == "read" and pair.high_only_read_ok:
return f"{pair.name} high-byte read; valid 8-bit result, read low byte next for full 10-bit value", None
if direction == "read" and pair.direct_read_without_temp:
return f"{pair.name} high-byte read; OCRA/OCRB reads do not use TEMP", None
return f"{pair.name} high-byte {direction}; next low-byte access completes TEMP transfer", None
previous = pending.pop(key, None)
if previous is not None:
return f"{pair.name} low-byte {direction}; completes high->low TEMP transfer", None
warning = {
"address": access["address"],
"register": pair.name,
"severity": "warning",
"message": (
f"{pair.name} low byte {direction} before matching high byte; "
"TEMP may contain stale or unrelated data"
),
"manual": MANUAL_REFERENCES,
}
return f"{pair.name} low-byte {direction} before high byte; check TEMP ordering", warning
def _pending_high_byte_is_suspicious(pair: TempRegisterPair, direction: str) -> bool:
if direction == "read" and pair.high_only_read_ok:
return False
if direction == "read" and pair.direct_read_without_temp:
return False
return True
def _instruction_accesses(ins: Instruction) -> list[dict[str, object]]:
size = _mnemonic_size(ins.mnemonic)
direction = _access_direction(ins)
if direction is None:
return []
accesses: list[dict[str, object]] = []
for ref in ins.references:
pair_info = TEMP_PAIR_BY_ADDRESS.get(ref)
if pair_info is None:
continue
pair, byte = pair_info
if size == "W" and ref == pair.low:
byte = "low_unaligned"
accesses.append(
{
"address": ins.address,
"instruction": ins.text,
"register": pair.name,
"pair": pair,
"high_address": pair.high,
"low_address": pair.low,
"referenced_address": ref,
"referenced_address_hex": h16(ref),
"byte": byte,
"size": size,
"direction": direction,
},
)
return accesses
def _public_access(access: dict[str, object]) -> dict[str, object]:
return {
key: value
for key, value in access.items()
if key != "pair"
}
def _access_direction(ins: Instruction) -> str | None:
root = _mnemonic_root(ins.mnemonic)
if root in READ_ONLY_ROOTS:
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"}:
return "write" if _destination_operand(ins.operands).startswith("@") else "read"
if root in {"MOV:L", "MOV:F", "MOVFPE"}:
return "read"
if root == "STC":
return "write"
if root == "LDC":
return "read"
return None
def _destination_operand(operands: str) -> str:
if "," not in operands:
return operands.strip()
return operands.rsplit(",", 1)[1].strip()
def _mnemonic_root(mnemonic: str) -> str:
return mnemonic.rsplit(".", 1)[0]
def _mnemonic_size(mnemonic: str) -> str:
return "W" if mnemonic.endswith(".W") else "B"