1
0
Files
h8-536-decoder/h8536/render.py
2026-05-25 14:22:32 +10:00

252 lines
10 KiB
Python

from __future__ import annotations
import json
from pathlib import Path
from .cycles import cycle_comment
from .dtc import DtcEndpointInfo, DtcRegisterInfo
from .formatting import h16, label_for
from .memory import MEMORY_REGIONS, region_for
from .model import Instruction
from .peripheral_access import (
peripheral_comment_for_instruction,
peripheral_json_payload,
peripheral_metadata_for_instruction,
)
from .rom import Rom
from .sci import sci_comment_for_instruction, sci_json_payload, sci_metadata_for_instruction
from .tables import IO_REGISTERS
from .timing import format_timing_summary
from .vectors import DtcVectorEntry
def _dtc_endpoint_text(endpoint: DtcEndpointInfo) -> str:
address = endpoint["address"]
text = endpoint["text"]
return f"{text} ({h16(address)})" if text != h16(address) else text
def _dtc_register_lines(vector_addr: int, entry: DtcVectorEntry, info: DtcRegisterInfo) -> list[str]:
target = entry["register_info_address"]
if not info.get("valid"):
error = info.get("error", "register information unavailable")
return [f"; {h16(vector_addr)} {entry['source']:<24} {h16(target)} unavailable: {error}"]
mode = info["mode"]
source = info["source"]
destination = info["destination"]
count = info["count"]
lines = [
(
f"; {h16(vector_addr)} {entry['source']:<24} {h16(target)} "
f"{mode['size']} x{count['transfers']} ({count['bytes']} bytes): "
f"{_dtc_endpoint_text(source)} -> {_dtc_endpoint_text(destination)} "
f"[src+={mode['source_increment_step']}, dst+={mode['destination_increment_step']}]"
),
(
f"; DTMR={h16(info['dtmr'])} DTSR={h16(info['dtsr'])} "
f"DTDR={h16(info['dtdr'])} DTCR={h16(info['dtcr'])}"
),
]
if mode["reserved_set"]:
lines.append(f"; warning: DTMR reserved bits set ({h16(mode['reserved'])})")
if count["zero_means_65536"]:
lines.append("; DTCR raw zero means an initial transfer count of 65536")
return lines
def _reference_comment(ins: Instruction) -> str:
parts: list[str] = []
for address in ins.references:
region = region_for(address)
name = IO_REGISTERS.get(address, h16(address))
parts.append(f"{name} in {region.name}")
return "refs " + ", ".join(parts) if parts else ""
def format_listing(
rom_path: Path,
rom: Rom,
instructions: dict[int, Instruction],
vectors: dict[int, tuple[str, int]],
labels: dict[int, str],
mode: str,
traced: bool,
dtc_vectors: dict[int, DtcVectorEntry] | None = None,
data_candidates: dict[str, list[dict[str, object]]] | None = None,
timing_summary: dict[str, list[dict[str, object]]] | None = None,
show_cycles: bool = False,
sci_analysis: dict[str, object] | None = None,
peripheral_access: dict[str, object] | None = None,
) -> str:
lines: list[str] = []
lines.append("; H8/536 ROM disassembly")
lines.append(f"; input: {rom_path}")
lines.append(f"; bytes: {len(rom.data)}")
lines.append(f"; vector mode: {mode}")
lines.append(f"; analysis: {'recursive trace from vectors' if traced else 'linear sweep'}")
lines.append(";")
lines.append("; Notes from the manual:")
lines.append("; - H8/536 uses the H8/500 CPU instruction set.")
lines.append("; - In minimum mode the reset vector at H'0000-H'0001 is a 16-bit PC.")
lines.append("; - The register field is H'FE80-H'FFFF; names below come from appendix B.")
lines.append("; - @aa:8 short absolute operands use BR as the upper address byte.")
lines.append("; - SCI baud inference uses section 14.2.8 BRR formulas when SMR/BRR are known.")
if sci_analysis and sci_analysis.get("clock_hz") is None:
lines.append("; - Pass --clock-hz to convert SCI BRR settings into numeric baud rates.")
if show_cycles:
lines.append("; - Cycle counts use Appendix A tables A-7/A-8 for on-chip access with no external wait states.")
lines.append("")
lines.append("; Memory Map")
for region in MEMORY_REGIONS:
lines.append(f"; {h16(region.start)}-{h16(region.end)} {region.name:<18} {region.kind}")
lines.append("")
lines.append("; Vectors")
for vector_addr, (name, target) in sorted(vectors.items()):
target_name = labels.get(target, label_for(target))
lines.append(f"; {h16(vector_addr)} {name:<24} -> {target_name} ({h16(target)})")
lines.append("")
if dtc_vectors:
lines.append("; DTC Vectors")
for vector_addr, entry in sorted(dtc_vectors.items()):
target = entry["register_info_address"]
lines.append(f"; {h16(vector_addr)} {entry['source']:<24} -> {h16(target)}")
lines.append("")
lines.append("; DTC Register Information")
for vector_addr, entry in sorted(dtc_vectors.items()):
lines.extend(_dtc_register_lines(vector_addr, entry, entry["register_info"]))
lines.append("")
if data_candidates:
strings = data_candidates.get("strings", [])
pointer_tables = data_candidates.get("pointer_tables", [])
if strings or pointer_tables:
lines.append("; Unreached Data Candidates")
for item in strings[:40]:
lines.append(
f"; string {h16(int(item['address'])):<8} len={item['length']:<3} {item['text']!r}",
)
for item in pointer_tables[:40]:
targets = ", ".join(h16(int(target)) for target in item["targets"][:8])
suffix = " ..." if int(item["count"]) > 8 else ""
lines.append(
f"; ptrtbl {h16(int(item['address'])):<8} count={item['count']:<3} -> {targets}{suffix}",
)
lines.append("")
if timing_summary:
lines.extend(format_timing_summary(timing_summary))
for address in sorted(instructions):
ins = instructions[address]
if address in labels:
lines.append("")
lines.append(f"{labels[address]}:")
raw = " ".join(f"{byte:02X}" for byte in ins.raw)
padded_raw = raw.ljust(14)
comment_parts = [
part
for part in (
ins.comment,
sci_comment_for_instruction(sci_analysis, address),
peripheral_comment_for_instruction(peripheral_access, address),
_reference_comment(ins) if not ins.comment else "",
cycle_comment(ins.cycles) if show_cycles else "",
)
if part
]
comment = f" ; {'; '.join(comment_parts)}" if comment_parts else ""
lines.append(f"{address:04X}: {padded_raw} {ins.text}{comment}")
lines.append("")
return "\n".join(lines)
def write_json(
path: Path,
instructions: dict[int, Instruction],
vectors: dict[int, tuple[str, int]],
labels: dict[int, str],
dtc_vectors: dict[int, DtcVectorEntry] | None = None,
data_candidates: dict[str, list[dict[str, object]]] | None = None,
call_graph: dict[str, object] | None = None,
timing_summary: dict[str, list[dict[str, object]]] | None = None,
sci_analysis: dict[str, object] | None = None,
peripheral_access: dict[str, object] | None = None,
) -> None:
payload = {
"vectors": [
{"address": addr, "name": name, "target": target, "target_label": labels.get(target)}
for addr, (name, target) in sorted(vectors.items())
],
"dtc_vectors": list((dtc_vectors or {}).values()),
"memory_regions": [
{
"name": region.name,
"start": region.start,
"end": region.end,
"kind": region.kind,
"manual": region.manual,
}
for region in MEMORY_REGIONS
],
"data_candidates": data_candidates or {"strings": [], "pointer_tables": []},
"call_graph": call_graph or {"nodes": [], "edges": []},
"timing_summary": timing_summary or {"blocks": [], "loops": []},
"sci": sci_json_payload(sci_analysis),
"peripheral_access": peripheral_json_payload(peripheral_access),
"instructions": [
_instruction_payload(ins, sci_analysis, peripheral_access)
for ins in (instructions[addr] for addr in sorted(instructions))
],
}
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
def _instruction_payload(
ins: Instruction,
sci_analysis: dict[str, object] | None = None,
peripheral_access: dict[str, object] | None = None,
) -> dict[str, object]:
payload: dict[str, object] = {
"address": ins.address,
"address_region": region_for(ins.address).name,
"bytes": ins.raw.hex().upper(),
"text": ins.text,
"mnemonic": ins.mnemonic,
"operands": ins.operands,
"kind": ins.kind,
"targets": ins.targets,
"cycles": ins.cycles,
"references": [
{
"address": address,
"name": IO_REGISTERS.get(address),
"region": region_for(address).name,
"kind": region_for(address).kind,
}
for address in ins.references
],
"comment": ins.comment,
"valid": ins.valid,
}
sci_metadata = sci_metadata_for_instruction(sci_analysis, ins.address)
if sci_metadata:
payload["sci"] = sci_metadata
peripheral_metadata = peripheral_metadata_for_instruction(peripheral_access, ins.address)
if peripheral_metadata:
payload["peripheral_access"] = peripheral_metadata
return payload
def format_callgraph_dot(call_graph: dict[str, object]) -> str:
lines = ["digraph callgraph {"]
lines.append(' graph [rankdir="LR"];')
for node in call_graph.get("nodes", []):
label = node["label"]
lines.append(f' "{label}" [label="{label}\\n{h16(int(node["start"]))}"];')
for edge in call_graph.get("edges", []):
lines.append(f' "{edge["from_label"]}" -> "{edge["to_label"]}" [label="{h16(int(edge["call_site"]))}"];')
lines.append("}")
lines.append("")
return "\n".join(lines)