DTC and SCI improvements
This commit is contained in:
256
h8536/peripheral_access.py
Normal file
256
h8536/peripheral_access.py
Normal 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"
|
||||
Reference in New Issue
Block a user