1
0
Files
h8-536-decoder/h8536/render.py
2026-05-25 13:47:13 +10:00

160 lines
6.2 KiB
Python

from __future__ import annotations
import json
from pathlib import Path
from .formatting import h16, label_for
from .memory import MEMORY_REGIONS, region_for
from .model import Instruction
from .rom import Rom
from .tables import IO_REGISTERS
from .vectors import DtcVectorEntry
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,
) -> 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("")
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("")
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("")
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, _reference_comment(ins) if not ins.comment 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,
) -> 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": []},
"instructions": [
{
"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,
"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,
}
for ins in (instructions[addr] for addr in sorted(instructions))
],
}
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
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)