from __future__ import annotations import json from pathlib import Path from .cycles import cycle_comment 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, show_cycles: bool = False, ) -> 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.") 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("") 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 "", 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, ) -> 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, "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, } 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)