522 lines
17 KiB
Python
522 lines
17 KiB
Python
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,
|
|
"TDR": 0xFEDB,
|
|
"SSR": 0xFEDC,
|
|
"RDR": 0xFEDD,
|
|
},
|
|
"SCI2": {
|
|
"SMR": 0xFEF0,
|
|
"BRR": 0xFEF1,
|
|
"SCR": 0xFEF2,
|
|
"TDR": 0xFEF3,
|
|
"SSR": 0xFEF4,
|
|
"RDR": 0xFEF5,
|
|
},
|
|
}
|
|
|
|
SCI_REGISTER_BY_ADDRESS = {
|
|
address: (channel, register)
|
|
for channel, registers in SCI_REGISTERS.items()
|
|
for register, address in registers.items()
|
|
}
|
|
|
|
SYSCR2_ADDRESS = 0xFEFD
|
|
SYSCR2_INITIAL = 0x80
|
|
P9SCI2E_BIT = 0
|
|
SCR_INITIAL = 0x0C
|
|
|
|
MANUAL_REFERENCES = [
|
|
"Manual/0900766b802125d0.md:2417 FP-80 H8/536 pin 66 is P95/TXD",
|
|
"Manual/0900766b802125d0.md:2418 FP-80 H8/536 pin 67 is P96/RXD",
|
|
"Manual/0900766b802125d0.md:11192 Port 9 carries SCI1 and SCI2 serial signals",
|
|
"Manual/0900766b802125d0.md:11201 P96 is RXD1 input",
|
|
"Manual/0900766b802125d0.md:11202 P95 is TXD1 output",
|
|
"Manual/0900766b802125d0.md:15725 SCI1 RXD input pin",
|
|
"Manual/0900766b802125d0.md:15726 SCI1 TXD output pin",
|
|
"Manual/0900766b802125d0.md:15750 SCI register table starts with SCI1 RDR/TDR/SMR/SCR/SSR/BRR",
|
|
"Manual/0900766b802125d0.md:15758 SCI register table lists SCI2 RDR/TDR/SMR/SCR/SSR/BRR",
|
|
"Manual/0900766b802125d0.md:15794 RDR receive data register",
|
|
"Manual/0900766b802125d0.md:15823 TDR transmit data register",
|
|
"Manual/0900766b802125d0.md:15969 SCR enables and disables SCI functions",
|
|
"Manual/0900766b802125d0.md:16009 SCR.TE makes the TXD pin output",
|
|
"Manual/0900766b802125d0.md:16029 SCR.RE makes the RXD pin input",
|
|
"Manual/0900766b802125d0.md:16090 SSR contains transmit/receive status flags",
|
|
"Manual/0900766b802125d0.md:10560 SYSCR2 controls port 9 pin functions",
|
|
"Manual/0900766b802125d0.md:10631 SYSCR2.P9SCI2E controls the SCI2 functions of P92-P94",
|
|
]
|
|
|
|
BOARD_PROFILES = {
|
|
"sony_rcp_tx7": {
|
|
"name": "Sony RCP-TX7",
|
|
"summary": "Board trace ties the H8/536 SCI1 pins to a MAX202 RS232 transceiver.",
|
|
"manual_references": MANUAL_REFERENCES,
|
|
"traces": [
|
|
{
|
|
"channel": "SCI1",
|
|
"signal": "TXD",
|
|
"h8_pin": 66,
|
|
"h8_pin_name": "P95/TXD",
|
|
"h8_function": "TXD1",
|
|
"max202_pin": 11,
|
|
"evidence": "MAX202 pin 11 traces to H8 pin 66",
|
|
},
|
|
{
|
|
"channel": "SCI1",
|
|
"signal": "RXD",
|
|
"h8_pin": 67,
|
|
"h8_pin_name": "P96/RXD",
|
|
"h8_function": "RXD1",
|
|
"max202_pin": 12,
|
|
"evidence": "MAX202 pin 12 traces to H8 pin 67",
|
|
},
|
|
],
|
|
},
|
|
}
|
|
|
|
_READ_ONLY_ROOTS = {"BTST", "CMP", "CMP:E", "CMP:G", "CMP:I", "MOVFPE", "TST"}
|
|
_BIT_WRITE_ROOTS = {"BCLR", "BNOT", "BSET"}
|
|
|
|
|
|
def analyze_board_profile(
|
|
instructions: Mapping[int, Instruction],
|
|
board: str = "sony_rcp_tx7",
|
|
) -> dict[str, object]:
|
|
"""Annotate board-specific serial-path accesses without changing disassembly output."""
|
|
if board not in BOARD_PROFILES:
|
|
raise ValueError(f"unsupported board profile: {board}")
|
|
|
|
profile = BOARD_PROFILES[board]
|
|
annotations: dict[int, str] = {}
|
|
instruction_metadata: dict[int, dict[str, object]] = {}
|
|
channels = _initial_channel_payload(profile)
|
|
register_values: dict[int, int | None] = {
|
|
SYSCR2_ADDRESS: SYSCR2_INITIAL,
|
|
SCI_REGISTERS["SCI1"]["SCR"]: SCR_INITIAL,
|
|
SCI_REGISTERS["SCI2"]["SCR"]: SCR_INITIAL,
|
|
}
|
|
|
|
for address in sorted(instructions):
|
|
ins = instructions[address]
|
|
access_kind = _access_kind(ins)
|
|
access_records: list[dict[str, object]] = []
|
|
comments: list[str] = []
|
|
|
|
if SYSCR2_ADDRESS in _expanded_references(ins, access_kind):
|
|
value = _write_value(ins, SYSCR2_ADDRESS, register_values.get(SYSCR2_ADDRESS), 1, 0)
|
|
if access_kind == "write":
|
|
register_values[SYSCR2_ADDRESS] = value
|
|
record = _syscr2_access(ins, access_kind, register_values.get(SYSCR2_ADDRESS))
|
|
access_records.append(record)
|
|
comments.append(str(record["comment"]))
|
|
|
|
for target in _sci_targets(ins, access_kind):
|
|
register_address = int(target["register_address"])
|
|
value = _write_value(
|
|
ins,
|
|
register_address,
|
|
register_values.get(register_address),
|
|
int(target["width"]),
|
|
int(target["index"]),
|
|
)
|
|
if access_kind == "write":
|
|
register_values[register_address] = value
|
|
|
|
channel = str(target["channel"])
|
|
register = str(target["register"])
|
|
p9sci2e = _bit_state(register_values.get(SYSCR2_ADDRESS), P9SCI2E_BIT)
|
|
record = _sci_access(
|
|
ins,
|
|
channel,
|
|
register,
|
|
register_address,
|
|
access_kind,
|
|
value,
|
|
register_values.get(register_address),
|
|
p9sci2e,
|
|
)
|
|
access_records.append(record)
|
|
comments.append(str(record["comment"]))
|
|
channel_payload = channels[channel]
|
|
channel_accesses = channel_payload["accesses"]
|
|
if isinstance(channel_accesses, list):
|
|
channel_accesses.append(record)
|
|
if register == "SCR":
|
|
channel_payload["scr"] = _scr_metadata(register_values.get(register_address))
|
|
|
|
if access_records:
|
|
comment = "; ".join(_dedupe(comments))
|
|
annotations[ins.address] = comment
|
|
instruction_metadata[ins.address] = {
|
|
"accesses": access_records,
|
|
"comment": comment,
|
|
}
|
|
|
|
channels["SCI2"]["p9sci2e"] = _bit_state(register_values.get(SYSCR2_ADDRESS), P9SCI2E_BIT)
|
|
|
|
return {
|
|
"board": board,
|
|
"name": profile["name"],
|
|
"summary": profile["summary"],
|
|
"manual_references": list(profile["manual_references"]),
|
|
"traces": [dict(trace) for trace in profile["traces"]],
|
|
"channels": channels,
|
|
"annotations": annotations,
|
|
"instructions": instruction_metadata,
|
|
"state": {
|
|
"SYSCR2": _register_state(register_values.get(SYSCR2_ADDRESS)),
|
|
"P9SCI2E": _bit_state(register_values.get(SYSCR2_ADDRESS), P9SCI2E_BIT),
|
|
},
|
|
}
|
|
|
|
|
|
def board_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)
|
|
if comment is None:
|
|
comment = annotations.get(str(address))
|
|
return str(comment) if comment else ""
|
|
|
|
|
|
def board_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)
|
|
if metadata is None:
|
|
metadata = instructions.get(str(address))
|
|
return metadata if isinstance(metadata, dict) else None
|
|
|
|
|
|
def _initial_channel_payload(profile: Mapping[str, object]) -> dict[str, dict[str, object]]:
|
|
traces = [dict(trace) for trace in profile["traces"] if isinstance(trace, Mapping)]
|
|
return {
|
|
"SCI1": {
|
|
"traced_to_max202": True,
|
|
"path": "RS232/MAX202",
|
|
"pins": traces,
|
|
"scr": _scr_metadata(SCR_INITIAL),
|
|
"accesses": [],
|
|
},
|
|
"SCI2": {
|
|
"traced_to_max202": False,
|
|
"path": None,
|
|
"note": "Sony RCP-TX7 MAX202 board traces are on SCI1 P95/P96, not SCI2 P92/P93.",
|
|
"p9sci2e": False,
|
|
"scr": _scr_metadata(SCR_INITIAL),
|
|
"accesses": [],
|
|
},
|
|
}
|
|
|
|
|
|
def _syscr2_access(ins: Instruction, access: str, value: int | None) -> dict[str, object]:
|
|
p9sci2e = _bit_state(value, P9SCI2E_BIT)
|
|
comment = _syscr2_comment(access, p9sci2e)
|
|
record: dict[str, object] = {
|
|
"address": ins.address,
|
|
"instruction": ins.text,
|
|
"register": "SYSCR2",
|
|
"register_address": SYSCR2_ADDRESS,
|
|
"access": access,
|
|
"p9sci2e": p9sci2e,
|
|
"comment": comment,
|
|
}
|
|
if value is not None:
|
|
record["value"] = value
|
|
record["value_hex"] = _hex8(value)
|
|
return record
|
|
|
|
|
|
def _sci_access(
|
|
ins: Instruction,
|
|
channel: str,
|
|
register: str,
|
|
register_address: int,
|
|
access: str,
|
|
value: int | None,
|
|
state_value: int | None,
|
|
p9sci2e: bool | None,
|
|
) -> dict[str, object]:
|
|
comment = _sci_comment(channel, register, access, value if value is not None else state_value, p9sci2e)
|
|
record: dict[str, object] = {
|
|
"address": ins.address,
|
|
"instruction": ins.text,
|
|
"channel": channel,
|
|
"register": register,
|
|
"register_address": register_address,
|
|
"access": access,
|
|
"traced_to_max202": channel == "SCI1",
|
|
"comment": comment,
|
|
}
|
|
if value is not None:
|
|
record["value"] = value
|
|
record["value_hex"] = _hex8(value)
|
|
if register == "SCR":
|
|
record["scr"] = _scr_metadata(value if value is not None else state_value)
|
|
if channel == "SCI2":
|
|
record["p9sci2e"] = p9sci2e
|
|
return record
|
|
|
|
|
|
def _sci_comment(
|
|
channel: str,
|
|
register: str,
|
|
access: str,
|
|
value: int | None,
|
|
p9sci2e: bool | None,
|
|
) -> str:
|
|
if channel == "SCI1":
|
|
return _sci1_comment(register, access, value)
|
|
return _sci2_comment(register, access, p9sci2e)
|
|
|
|
|
|
def _sci1_comment(register: str, access: str, value: int | None) -> str:
|
|
if register in {"SMR", "BRR"}:
|
|
return (
|
|
f"SCI1 {register} serial init for traced RS232/MAX202 path "
|
|
"(H8 pin 66 P95/TXD to MAX202 pin 11; MAX202 pin 12 to H8 pin 67 P96/RXD)"
|
|
)
|
|
if register == "SCR":
|
|
bits = _scr_bits_text(value)
|
|
suffix = f" {bits}" if bits else ""
|
|
return (
|
|
f"SCI1 SCR {access}{suffix}; TE/RE select the traced RS232/MAX202 pins "
|
|
"(P95/TXD pin 66 to MAX202 pin 11, P96/RXD pin 67 to MAX202 pin 12)"
|
|
)
|
|
if register == "TDR":
|
|
verb = "transmits" if access == "write" else "accesses transmit buffer"
|
|
return (
|
|
f"SCI1 TDR {access} {verb} on traced RS232/MAX202 path: "
|
|
"H8 pin 66 P95/TXD -> MAX202 pin 11"
|
|
)
|
|
if register == "RDR":
|
|
verb = "receives" if access == "read" else "accesses receive buffer"
|
|
return (
|
|
f"SCI1 RDR {access} {verb} from traced RS232/MAX202 path: "
|
|
"MAX202 pin 12 -> H8 pin 67 P96/RXD"
|
|
)
|
|
if register == "SSR":
|
|
return "SCI1 SSR status for traced RS232/MAX202 path; TDRE/RDRF/error flags gate TDR/RDR use"
|
|
return f"SCI1 {register} {access} on traced RS232/MAX202 path"
|
|
|
|
|
|
def _sci2_comment(register: str, access: str, p9sci2e: bool | None) -> str:
|
|
prefix = f"SCI2 {register} {access}; not the traced MAX202 path"
|
|
if p9sci2e is False:
|
|
return (
|
|
f"{prefix}; P9SCI2E=0 disables SCI2 pins P92/P93/P94, "
|
|
"while the board trace is SCI1 P95/P96"
|
|
)
|
|
if p9sci2e is True:
|
|
return f"{prefix}; P9SCI2E=1 may route SCI2 pins, but the board trace is SCI1 P95/P96"
|
|
return f"{prefix}; P9SCI2E state unknown, and the board trace is SCI1 P95/P96"
|
|
|
|
|
|
def _syscr2_comment(access: str, p9sci2e: bool | None) -> str:
|
|
if p9sci2e is False:
|
|
return (
|
|
f"SYSCR2 {access} leaves P9SCI2E=0; SCI2 pins are disabled, "
|
|
"so SCI2 is not the traced MAX202 path; traced RS232/MAX202 remains SCI1 P95/P96"
|
|
)
|
|
if p9sci2e is True:
|
|
return (
|
|
f"SYSCR2 {access} sets P9SCI2E=1; SCI2 pins may be enabled, "
|
|
"but Sony RCP-TX7 MAX202 traces are SCI1 P95/P96"
|
|
)
|
|
return f"SYSCR2 {access}; P9SCI2E unknown, board trace remains SCI1 P95/P96"
|
|
|
|
|
|
def _sci_targets(ins: Instruction, access: str) -> list[dict[str, object]]:
|
|
width = _mnemonic_width(ins.mnemonic)
|
|
targets: list[dict[str, object]] = []
|
|
seen: set[int] = set()
|
|
for base_address in ins.references:
|
|
target_width = width if _operand_references_memory(ins, access) else 1
|
|
for index in range(target_width):
|
|
register_address = base_address + index
|
|
if register_address in seen:
|
|
continue
|
|
register_info = SCI_REGISTER_BY_ADDRESS.get(register_address)
|
|
if register_info is None:
|
|
continue
|
|
seen.add(register_address)
|
|
channel, register = register_info
|
|
targets.append(
|
|
{
|
|
"channel": channel,
|
|
"register": register,
|
|
"register_address": register_address,
|
|
"width": target_width,
|
|
"index": index,
|
|
},
|
|
)
|
|
return targets
|
|
|
|
|
|
def _expanded_references(ins: Instruction, access: str) -> set[int]:
|
|
width = _mnemonic_width(ins.mnemonic)
|
|
target_width = width if _operand_references_memory(ins, access) else 1
|
|
addresses: set[int] = set()
|
|
for base_address in ins.references:
|
|
for index in range(target_width):
|
|
addresses.add(base_address + index)
|
|
return addresses
|
|
|
|
|
|
def _operand_references_memory(ins: Instruction, access: str) -> bool:
|
|
operand = _destination_operand(ins.operands) if access == "write" else _source_operand(ins.operands)
|
|
return bool(operand and operand.startswith("@"))
|
|
|
|
|
|
def _access_kind(ins: Instruction) -> str:
|
|
root = _mnemonic_root(ins.mnemonic)
|
|
if root in _READ_ONLY_ROOTS:
|
|
return "read"
|
|
destination = _destination_operand(ins.operands)
|
|
if destination and destination.startswith("@"):
|
|
return "write"
|
|
return "read"
|
|
|
|
|
|
def _write_value(
|
|
ins: Instruction,
|
|
register_address: int,
|
|
current_value: int | None,
|
|
width: int,
|
|
index: int,
|
|
) -> int | None:
|
|
if _access_kind(ins) != "write":
|
|
return None
|
|
|
|
root = _mnemonic_root(ins.mnemonic)
|
|
if root == "CLR":
|
|
return 0
|
|
if root in _BIT_WRITE_ROOTS:
|
|
bit = _immediate_bit(ins.operands)
|
|
if bit is None or current_value is None:
|
|
return None
|
|
if root == "BSET":
|
|
return (current_value | (1 << bit)) & 0xFF
|
|
if root == "BCLR":
|
|
return (current_value & ~(1 << bit)) & 0xFF
|
|
return (current_value ^ (1 << bit)) & 0xFF
|
|
|
|
value = _immediate_source_value(ins.operands)
|
|
if value is None:
|
|
return None
|
|
return _byte_for_target(value, width, index)
|
|
|
|
|
|
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 _source_operand(operands: str) -> str | None:
|
|
operands = operands.strip()
|
|
if not operands:
|
|
return None
|
|
if "," not in operands:
|
|
return operands
|
|
return operands.rsplit(",", 1)[0].strip()
|
|
|
|
|
|
def _immediate_source_value(operands: str) -> int | None:
|
|
source = _source_operand(operands) or ""
|
|
if not source.startswith("#"):
|
|
return None
|
|
try:
|
|
return parse_int(source[1:])
|
|
except ValueError:
|
|
return None
|
|
|
|
|
|
def _immediate_bit(operands: str) -> int | None:
|
|
value = _immediate_source_value(operands)
|
|
if value is None:
|
|
return None
|
|
return value if 0 <= value <= 7 else None
|
|
|
|
|
|
def _mnemonic_root(mnemonic: str) -> str:
|
|
return mnemonic.split(".", 1)[0]
|
|
|
|
|
|
def _mnemonic_width(mnemonic: str) -> int:
|
|
return 2 if mnemonic.endswith(".W") else 1
|
|
|
|
|
|
def _byte_for_target(value: int, width: int, index: int) -> int:
|
|
shift = 8 * (width - index - 1)
|
|
return (value >> shift) & 0xFF
|
|
|
|
|
|
def _scr_metadata(value: int | None) -> dict[str, object]:
|
|
metadata = _register_state(value)
|
|
metadata.update(
|
|
{
|
|
"tie": None if value is None else bool(value & 0x80),
|
|
"rie": None if value is None else bool(value & 0x40),
|
|
"tx_enabled": None if value is None else bool(value & 0x20),
|
|
"rx_enabled": None if value is None else bool(value & 0x10),
|
|
},
|
|
)
|
|
return metadata
|
|
|
|
|
|
def _register_state(value: int | None) -> dict[str, object]:
|
|
return {
|
|
"value": value,
|
|
"value_hex": None if value is None else _hex8(value),
|
|
}
|
|
|
|
|
|
def _scr_bits_text(value: int | None) -> str:
|
|
if value is None:
|
|
return ""
|
|
return f"TE={1 if value & 0x20 else 0} RE={1 if value & 0x10 else 0}"
|
|
|
|
|
|
def _bit_state(value: int | None, bit: int) -> bool | None:
|
|
if value is None:
|
|
return None
|
|
return bool(value & (1 << bit))
|
|
|
|
|
|
def _dedupe(items: list[str]) -> list[str]:
|
|
seen: set[str] = set()
|
|
output: list[str] = []
|
|
for item in items:
|
|
if item in seen:
|
|
continue
|
|
seen.add(item)
|
|
output.append(item)
|
|
return output
|
|
|
|
|
|
def _hex8(value: int) -> str:
|
|
return f"H'{value & 0xFF:02X}"
|