Intial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.pyc
|
||||||
27057
Manual/0900766b802125d0.md
Normal file
27057
Manual/0900766b802125d0.md
Normal file
File diff suppressed because it is too large
Load Diff
57
README.md
Normal file
57
README.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# H8/536 ROM Decompiler
|
||||||
|
|
||||||
|
This repo now includes a standalone Python helper for the H8/536 ROM image:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python h8536_decompiler.py ROM\M27C512@DIP28_1.BIN --out build\rom_decompiled.asm --json build\rom_decompiled.json
|
||||||
|
```
|
||||||
|
|
||||||
|
On this machine the Windows `python.exe` entry is a Microsoft Store launcher stub, so validation was run with WSL:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
ubuntu.exe run python3 h8536_decompiler.py --out build/rom_decompiled.asm --json build/rom_decompiled.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## What It Does
|
||||||
|
|
||||||
|
- Decodes the H8/500 instruction set used by the H8/536.
|
||||||
|
- Reads the H8/536 minimum-mode vector table from the ROM.
|
||||||
|
- Recursively traces reachable code from reset, interrupt, and trap vectors.
|
||||||
|
- Emits labels for branch and call targets.
|
||||||
|
- Annotates H8/536 on-chip register accesses such as `P1DDR`, `SYSCR1`, `WCR`, and timer/SCI/A-D registers.
|
||||||
|
- Handles the E-clock transfer instructions `MOVFPE` and `MOVTPE`.
|
||||||
|
|
||||||
|
The generated listing is written to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
build/rom_decompiled.asm
|
||||||
|
```
|
||||||
|
|
||||||
|
The optional JSON output is useful for scripts or later analysis:
|
||||||
|
|
||||||
|
```text
|
||||||
|
build/rom_decompiled.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Useful Options
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python h8536_decompiler.py --help
|
||||||
|
```
|
||||||
|
|
||||||
|
- `--mode min|max`: vector format. This ROM appears to be minimum mode; `min` is the default.
|
||||||
|
- `--entry H'1234`: add an extra entry point to recursive tracing.
|
||||||
|
- `--linear`: linear-sweep the selected range instead of tracing from vectors.
|
||||||
|
- `--start H'1000 --end H'D100`: constrain the decode range.
|
||||||
|
- `--br H'FE`: resolve short absolute `@aa:8` operands through a known base-register value.
|
||||||
|
|
||||||
|
## Code Layout
|
||||||
|
|
||||||
|
- `h8536_decompiler.py`: compatibility wrapper for the CLI.
|
||||||
|
- `h8536/cli.py`: argument parsing and end-to-end orchestration.
|
||||||
|
- `h8536/decoder.py`: instruction and effective-address decoding.
|
||||||
|
- `h8536/tables.py`: manual-derived opcode/vector/register tables.
|
||||||
|
- `h8536/vectors.py`: exception vector parsing.
|
||||||
|
- `h8536/analysis.py`: recursive tracing, linear sweep, and labels.
|
||||||
|
- `h8536/render.py`: assembly and JSON output.
|
||||||
|
- `h8536/model.py`, `h8536/rom.py`, `h8536/formatting.py`: shared data structures and helpers.
|
||||||
BIN
ROM/M27C512@DIP28_1.BIN
Normal file
BIN
ROM/M27C512@DIP28_1.BIN
Normal file
Binary file not shown.
3451
build/rom_decompiled.asm
Normal file
3451
build/rom_decompiled.asm
Normal file
File diff suppressed because it is too large
Load Diff
29813
build/rom_decompiled.json
Normal file
29813
build/rom_decompiled.json
Normal file
File diff suppressed because it is too large
Load Diff
2
h8536/__init__.py
Normal file
2
h8536/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
"""H8/536 ROM decompiler package."""
|
||||||
|
|
||||||
61
h8536/analysis.py
Normal file
61
h8536/analysis.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Iterable
|
||||||
|
|
||||||
|
from .decoder import H8536Decoder
|
||||||
|
from .formatting import label_for
|
||||||
|
from .model import Instruction
|
||||||
|
from .tables import FLOW_STOP
|
||||||
|
|
||||||
|
|
||||||
|
def trace(decoder: H8536Decoder, starts: Iterable[int], start_limit: int, end_limit: int) -> dict[int, Instruction]:
|
||||||
|
decoded: dict[int, Instruction] = {}
|
||||||
|
queue = [(addr, decoder.br) for addr in starts if start_limit <= addr < end_limit]
|
||||||
|
seen_starts: set[tuple[int, int | None]] = set()
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
pc, br = queue.pop(0)
|
||||||
|
if (pc, br) in seen_starts:
|
||||||
|
continue
|
||||||
|
seen_starts.add((pc, br))
|
||||||
|
|
||||||
|
while start_limit <= pc < end_limit:
|
||||||
|
decoder.br = br
|
||||||
|
if pc in decoded:
|
||||||
|
break
|
||||||
|
ins = decoder.decode(pc)
|
||||||
|
decoded[pc] = ins
|
||||||
|
next_br = ins.br_value if ins.writes_br else br
|
||||||
|
for target in ins.targets:
|
||||||
|
if start_limit <= target < end_limit and (target, next_br) not in seen_starts:
|
||||||
|
queue.append((target, next_br))
|
||||||
|
if ins.kind in FLOW_STOP or not ins.fallthrough:
|
||||||
|
break
|
||||||
|
pc = (pc + max(ins.size, 1)) & 0xFFFF
|
||||||
|
br = next_br
|
||||||
|
|
||||||
|
return decoded
|
||||||
|
|
||||||
|
|
||||||
|
def linear_sweep(decoder: H8536Decoder, start: int, end: int) -> dict[int, Instruction]:
|
||||||
|
decoded: dict[int, Instruction] = {}
|
||||||
|
pc = start
|
||||||
|
br = decoder.br
|
||||||
|
while pc < end:
|
||||||
|
decoder.br = br
|
||||||
|
ins = decoder.decode(pc)
|
||||||
|
decoded[pc] = ins
|
||||||
|
if ins.writes_br:
|
||||||
|
br = ins.br_value
|
||||||
|
pc += max(ins.size, 1)
|
||||||
|
return decoded
|
||||||
|
|
||||||
|
|
||||||
|
def collect_labels(instructions: Iterable[Instruction], vectors: dict[int, tuple[str, int]]) -> dict[int, str]:
|
||||||
|
labels: dict[int, str] = {}
|
||||||
|
for _vector_addr, (name, target) in vectors.items():
|
||||||
|
labels.setdefault(target, f"vec_{name}_{target:04X}")
|
||||||
|
for ins in instructions:
|
||||||
|
for target in ins.targets:
|
||||||
|
labels.setdefault(target, label_for(target))
|
||||||
|
return labels
|
||||||
84
h8536/cli.py
Normal file
84
h8536/cli.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .analysis import collect_labels, linear_sweep, trace
|
||||||
|
from .decoder import H8536Decoder
|
||||||
|
from .formatting import parse_int
|
||||||
|
from .render import format_listing, write_json
|
||||||
|
from .rom import Rom
|
||||||
|
from .vectors import read_dtc_vectors_max, read_dtc_vectors_min, read_vectors_max, read_vectors_min
|
||||||
|
|
||||||
|
|
||||||
|
def default_end(data: bytes) -> int:
|
||||||
|
last = 0
|
||||||
|
for idx, value in enumerate(data):
|
||||||
|
if value != 0xFF:
|
||||||
|
last = idx
|
||||||
|
return min(len(data), ((last + 0x100) // 0x100) * 0x100)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Disassemble/decompile a Hitachi H8/536 ROM image.")
|
||||||
|
parser.add_argument("rom", nargs="?", default="ROM/M27C512@DIP28_1.BIN", type=Path, help="ROM binary to decode")
|
||||||
|
parser.add_argument("--out", type=Path, default=Path("build/rom_decompiled.asm"), help="assembly listing output")
|
||||||
|
parser.add_argument("--json", type=Path, default=None, help="optional structured JSON output")
|
||||||
|
parser.add_argument("--mode", choices=("min", "max"), default="min", help="CPU/vector mode; this ROM appears to be min")
|
||||||
|
parser.add_argument("--start", type=parse_int, default=0x0000, help="decode lower address limit")
|
||||||
|
parser.add_argument("--end", type=parse_int, default=None, help="decode upper address limit, exclusive")
|
||||||
|
parser.add_argument("--entry", type=parse_int, action="append", default=[], help="extra entry point to trace")
|
||||||
|
parser.add_argument("--br", type=parse_int, default=None, help="optional BR value for @aa:8 short absolute operands")
|
||||||
|
parser.add_argument("--linear", action="store_true", help="linear-sweep the selected range instead of tracing from vectors")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
data = args.rom.read_bytes()
|
||||||
|
rom = Rom(data)
|
||||||
|
end = args.end if args.end is not None else default_end(data)
|
||||||
|
|
||||||
|
vectors = read_vectors_min(rom) if args.mode == "min" else read_vectors_max(rom)
|
||||||
|
dtc_vectors = read_dtc_vectors_min(rom) if args.mode == "min" else read_dtc_vectors_max(rom)
|
||||||
|
starts = [target for _name, target in vectors.values()] + args.entry
|
||||||
|
|
||||||
|
labels: dict[int, str] = {}
|
||||||
|
decoder = H8536Decoder(rom, br=args.br, labels=labels)
|
||||||
|
decoder.br = args.br
|
||||||
|
if args.linear:
|
||||||
|
instructions = linear_sweep(decoder, args.start, end)
|
||||||
|
else:
|
||||||
|
instructions = trace(decoder, starts, args.start, end)
|
||||||
|
|
||||||
|
labels.update(collect_labels(instructions.values(), vectors))
|
||||||
|
decoder.labels = labels
|
||||||
|
|
||||||
|
# Re-decode now that labels are known, so operands can use stable symbols.
|
||||||
|
decoder.br = args.br
|
||||||
|
if args.linear:
|
||||||
|
instructions = linear_sweep(decoder, args.start, end)
|
||||||
|
else:
|
||||||
|
instructions = trace(decoder, starts, args.start, end)
|
||||||
|
labels.update(collect_labels(instructions.values(), vectors))
|
||||||
|
|
||||||
|
args.out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
args.out.write_text(
|
||||||
|
format_listing(
|
||||||
|
args.rom,
|
||||||
|
rom,
|
||||||
|
instructions,
|
||||||
|
vectors,
|
||||||
|
labels,
|
||||||
|
args.mode,
|
||||||
|
traced=not args.linear,
|
||||||
|
dtc_vectors=dtc_vectors,
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
if args.json:
|
||||||
|
args.json.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
write_json(args.json, instructions, vectors, labels, dtc_vectors=dtc_vectors)
|
||||||
|
|
||||||
|
invalid = sum(1 for ins in instructions.values() if not ins.valid)
|
||||||
|
print(f"wrote {args.out} ({len(instructions)} items, {invalid} invalid/data bytes)")
|
||||||
|
if args.json:
|
||||||
|
print(f"wrote {args.json}")
|
||||||
|
return 0
|
||||||
500
h8536/decoder.py
Normal file
500
h8536/decoder.py
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Iterable
|
||||||
|
|
||||||
|
from .formatting import (
|
||||||
|
bit_comment,
|
||||||
|
control_reg,
|
||||||
|
disp_text,
|
||||||
|
h8,
|
||||||
|
h16,
|
||||||
|
h24,
|
||||||
|
label_or_h,
|
||||||
|
reg_list,
|
||||||
|
s8,
|
||||||
|
s16,
|
||||||
|
write_comment,
|
||||||
|
)
|
||||||
|
from .model import EA, Instruction
|
||||||
|
from .rom import DecodeError, Rom
|
||||||
|
from .tables import BRANCH_NAMES, IO_REGISTERS
|
||||||
|
|
||||||
|
|
||||||
|
class H8536Decoder:
|
||||||
|
def __init__(self, rom: Rom, br: int | None = None, labels: dict[int, str] | None = None) -> None:
|
||||||
|
self.rom = rom
|
||||||
|
self.br = br
|
||||||
|
self.labels = labels if labels is not None else {}
|
||||||
|
|
||||||
|
def symbol(self, address: int) -> str:
|
||||||
|
if address in self.labels:
|
||||||
|
return self.labels[address]
|
||||||
|
if address in IO_REGISTERS:
|
||||||
|
return IO_REGISTERS[address]
|
||||||
|
return h16(address)
|
||||||
|
|
||||||
|
def mem(self, address: int) -> str:
|
||||||
|
return "@" + self.symbol(address)
|
||||||
|
|
||||||
|
def short_abs_address(self, low: int) -> int | None:
|
||||||
|
if self.br is None:
|
||||||
|
return None
|
||||||
|
return ((self.br & 0xFF) << 8) | (low & 0xFF)
|
||||||
|
|
||||||
|
def short_abs(self, low: int) -> str:
|
||||||
|
address = self.short_abs_address(low)
|
||||||
|
if address is None:
|
||||||
|
return f"@BR:{h8(low)}"
|
||||||
|
return self.mem(address)
|
||||||
|
|
||||||
|
def decode_ea(self, address: int) -> EA:
|
||||||
|
b = self.rom.u8(address)
|
||||||
|
if 0xA0 <= b <= 0xAF:
|
||||||
|
size = "W" if b & 0x08 else "B"
|
||||||
|
reg = b & 0x07
|
||||||
|
return EA(f"R{reg}", "reg", size, 1, reg=reg)
|
||||||
|
if 0xB0 <= b <= 0xBF:
|
||||||
|
size = "W" if b & 0x08 else "B"
|
||||||
|
reg = b & 0x07
|
||||||
|
return EA(f"@-R{reg}", "predec", size, 1, reg=reg)
|
||||||
|
if 0xC0 <= b <= 0xCF:
|
||||||
|
size = "W" if b & 0x08 else "B"
|
||||||
|
reg = b & 0x07
|
||||||
|
return EA(f"@R{reg}+", "postinc", size, 1, reg=reg)
|
||||||
|
if 0xD0 <= b <= 0xDF:
|
||||||
|
size = "W" if b & 0x08 else "B"
|
||||||
|
reg = b & 0x07
|
||||||
|
return EA(f"@R{reg}", "indirect", size, 1, reg=reg)
|
||||||
|
if 0xE0 <= b <= 0xEF:
|
||||||
|
size = "W" if b & 0x08 else "B"
|
||||||
|
reg = b & 0x07
|
||||||
|
disp = self.rom.u8(address + 1)
|
||||||
|
return EA(f"@({disp_text(s8(disp))},R{reg})", "disp8", size, 2, reg=reg, value=s8(disp))
|
||||||
|
if 0xF0 <= b <= 0xFF:
|
||||||
|
size = "W" if b & 0x08 else "B"
|
||||||
|
reg = b & 0x07
|
||||||
|
disp = self.rom.u16(address + 1)
|
||||||
|
return EA(f"@({disp_text(s16(disp))},R{reg})", "disp16", size, 3, reg=reg, value=s16(disp))
|
||||||
|
if b == 0x04:
|
||||||
|
value = self.rom.u8(address + 1)
|
||||||
|
return EA(f"#{h8(value)}", "imm", "B", 2, value=value)
|
||||||
|
if b == 0x0C:
|
||||||
|
value = self.rom.u16(address + 1)
|
||||||
|
return EA(f"#{h16(value)}", "imm", "W", 3, value=value)
|
||||||
|
if b in (0x05, 0x0D):
|
||||||
|
size = "W" if b == 0x0D else "B"
|
||||||
|
low = self.rom.u8(address + 1)
|
||||||
|
full = self.short_abs_address(low)
|
||||||
|
text = self.short_abs(low)
|
||||||
|
return EA(text, "abs8", size, 2, value=low, address=full)
|
||||||
|
if b in (0x15, 0x1D):
|
||||||
|
size = "W" if b == 0x1D else "B"
|
||||||
|
full = self.rom.u16(address + 1)
|
||||||
|
return EA(self.mem(full), "abs16", size, 3, address=full)
|
||||||
|
raise DecodeError(f"not an EA byte: {h8(b)}")
|
||||||
|
|
||||||
|
def decode(self, address: int) -> Instruction:
|
||||||
|
try:
|
||||||
|
b = self.rom.u8(address)
|
||||||
|
except DecodeError:
|
||||||
|
return Instruction(address, b"", ".eof", kind="invalid", fallthrough=False, valid=False)
|
||||||
|
|
||||||
|
if is_ea_byte(b):
|
||||||
|
try:
|
||||||
|
return self.decode_general(address)
|
||||||
|
except DecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.decode_direct(address)
|
||||||
|
except DecodeError:
|
||||||
|
raw = self.rom.slice(address, 1)
|
||||||
|
return Instruction(address, raw, ".db", h8(raw[0]), kind="invalid", fallthrough=False, valid=False)
|
||||||
|
|
||||||
|
def decode_general(self, address: int) -> Instruction:
|
||||||
|
ea = self.decode_ea(address)
|
||||||
|
op_addr = address + ea.length
|
||||||
|
op = self.rom.u8(op_addr)
|
||||||
|
end = op_addr + 1
|
||||||
|
mnemonic = ""
|
||||||
|
operands = ""
|
||||||
|
comment = ""
|
||||||
|
writes_br = False
|
||||||
|
br_value: int | None = None
|
||||||
|
|
||||||
|
source_to_reg = {
|
||||||
|
0x20: "ADD:G",
|
||||||
|
0x28: "ADDS",
|
||||||
|
0x30: "SUB",
|
||||||
|
0x38: "SUBS",
|
||||||
|
0x40: "OR",
|
||||||
|
0x50: "AND",
|
||||||
|
0x60: "XOR",
|
||||||
|
0x70: "CMP:G",
|
||||||
|
0x80: "MOV:G",
|
||||||
|
0xA0: "ADDX",
|
||||||
|
0xA8: "MULXU",
|
||||||
|
0xB0: "SUBX",
|
||||||
|
0xB8: "DIVXU",
|
||||||
|
}
|
||||||
|
|
||||||
|
base = op & 0xF8
|
||||||
|
rd = op & 0x07
|
||||||
|
|
||||||
|
if base in source_to_reg:
|
||||||
|
mnemonic = f"{source_to_reg[base]}.{ea.size}"
|
||||||
|
operands = f"{ea.text}, R{rd}"
|
||||||
|
elif base == 0x90:
|
||||||
|
rs = op & 0x07
|
||||||
|
if ea.mode == "reg":
|
||||||
|
mnemonic = "XCH.W"
|
||||||
|
operands = f"R{ea.reg}, R{rs}"
|
||||||
|
else:
|
||||||
|
mnemonic = f"MOV:G.{ea.size}"
|
||||||
|
operands = f"R{rs}, {ea.text}"
|
||||||
|
comment = write_comment(ea, None)
|
||||||
|
elif op in (0x10, 0x11, 0x12) and ea.mode == "reg":
|
||||||
|
names = {0x10: "SWAP.B", 0x11: "EXTS.B", 0x12: "EXTU.B"}
|
||||||
|
mnemonic = names[op]
|
||||||
|
operands = ea.text
|
||||||
|
elif op in (0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F):
|
||||||
|
unary = {
|
||||||
|
0x13: "CLR",
|
||||||
|
0x14: "NEG",
|
||||||
|
0x15: "NOT",
|
||||||
|
0x16: "TST",
|
||||||
|
0x17: "TAS",
|
||||||
|
0x18: "SHAL",
|
||||||
|
0x19: "SHAR",
|
||||||
|
0x1A: "SHLL",
|
||||||
|
0x1B: "SHLR",
|
||||||
|
0x1C: "ROTL",
|
||||||
|
0x1D: "ROTR",
|
||||||
|
0x1E: "ROTXL",
|
||||||
|
0x1F: "ROTXR",
|
||||||
|
}
|
||||||
|
size = "B" if op == 0x17 else ea.size
|
||||||
|
mnemonic = f"{unary[op]}.{size}"
|
||||||
|
operands = ea.text
|
||||||
|
elif op in (0x08, 0x09, 0x0C, 0x0D):
|
||||||
|
q = {0x08: 1, 0x09: 2, 0x0C: -1, 0x0D: -2}[op]
|
||||||
|
mnemonic = f"ADD:Q.{ea.size}"
|
||||||
|
operands = f"#{q}, {ea.text}"
|
||||||
|
comment = write_comment(ea, None)
|
||||||
|
elif op in (0x06, 0x07):
|
||||||
|
if op == 0x06:
|
||||||
|
imm = self.rom.u8(end)
|
||||||
|
end += 1
|
||||||
|
imm_text = h8(imm)
|
||||||
|
size = ea.size
|
||||||
|
else:
|
||||||
|
imm = self.rom.u16(end)
|
||||||
|
end += 2
|
||||||
|
imm_text = h16(imm)
|
||||||
|
size = "W"
|
||||||
|
mnemonic = f"MOV:G.{size}"
|
||||||
|
operands = f"#{imm_text}, {ea.text}"
|
||||||
|
comment = write_comment(ea, imm)
|
||||||
|
elif op in (0x04, 0x05):
|
||||||
|
if op == 0x04:
|
||||||
|
imm = self.rom.u8(end)
|
||||||
|
end += 1
|
||||||
|
imm_text = h8(imm)
|
||||||
|
size = "B"
|
||||||
|
else:
|
||||||
|
imm = self.rom.u16(end)
|
||||||
|
end += 2
|
||||||
|
imm_text = h16(imm)
|
||||||
|
size = "W"
|
||||||
|
mnemonic = f"CMP:G.{size}"
|
||||||
|
operands = f"#{imm_text}, {ea.text}"
|
||||||
|
elif op == 0x00:
|
||||||
|
ext = self.rom.u8(end)
|
||||||
|
end += 1
|
||||||
|
ext_base = ext & 0xF8
|
||||||
|
if ext_base == 0x80:
|
||||||
|
mnemonic = "MOVFPE.B"
|
||||||
|
operands = f"{ea.text}, R{ext & 7}"
|
||||||
|
elif ext_base == 0x90:
|
||||||
|
mnemonic = "MOVTPE.B"
|
||||||
|
operands = f"R{ext & 7}, {ea.text}"
|
||||||
|
comment = write_comment(ea, None)
|
||||||
|
else:
|
||||||
|
raise DecodeError(f"unknown extended general op {h8(ext)} after {h8(op)}")
|
||||||
|
elif (op & 0xF0) in (0xC0, 0xD0, 0xE0, 0xF0):
|
||||||
|
bit_ops = {0xC0: "BSET", 0xD0: "BCLR", 0xE0: "BNOT", 0xF0: "BTST"}
|
||||||
|
mnemonic = f"{bit_ops[op & 0xF0]}.{ea.size}"
|
||||||
|
operands = f"#{op & 0x0F}, {ea.text}"
|
||||||
|
if op & 0xF0 in (0xC0, 0xD0, 0xE0):
|
||||||
|
comment = bit_comment(mnemonic, ea, op & 0x0F)
|
||||||
|
elif base in (0x48, 0x58, 0x68, 0x78) and ea.mode != "imm":
|
||||||
|
bit_ops = {0x48: "BSET", 0x58: "BCLR", 0x68: "BNOT", 0x78: "BTST"}
|
||||||
|
mnemonic = f"{bit_ops[base]}.{ea.size}"
|
||||||
|
operands = f"R{rd}, {ea.text}"
|
||||||
|
elif base == 0x88:
|
||||||
|
mnemonic = f"LDC.{ea.size}"
|
||||||
|
operands = f"{ea.text}, {control_reg(rd, ea.size)}"
|
||||||
|
if rd == 1 and ea.size == "B":
|
||||||
|
writes_br = True
|
||||||
|
br_value = ea.value if ea.mode == "imm" else None
|
||||||
|
if br_value is not None:
|
||||||
|
comment = f"BR = {h8(br_value)}; @aa:8 upper address byte"
|
||||||
|
elif base == 0x98:
|
||||||
|
mnemonic = f"STC.{ea.size}"
|
||||||
|
operands = f"{control_reg(rd, ea.size)}, {ea.text}"
|
||||||
|
comment = write_comment(ea, None)
|
||||||
|
elif ea.mode == "imm" and base in (0x48, 0x58, 0x68):
|
||||||
|
ctrl_ops = {0x48: "ORC", 0x58: "ANDC", 0x68: "XORC"}
|
||||||
|
mnemonic = f"{ctrl_ops[base]}.{ea.size}"
|
||||||
|
operands = f"{ea.text}, {control_reg(rd, ea.size)}"
|
||||||
|
else:
|
||||||
|
raise DecodeError(f"unknown general op {h8(op)} after {h8(self.rom.u8(address))}")
|
||||||
|
|
||||||
|
raw = self.rom.slice(address, end - address)
|
||||||
|
references = [ea.address] if ea.address is not None else []
|
||||||
|
return Instruction(
|
||||||
|
address,
|
||||||
|
raw,
|
||||||
|
mnemonic,
|
||||||
|
operands,
|
||||||
|
comment=comment,
|
||||||
|
references=references,
|
||||||
|
writes_br=writes_br,
|
||||||
|
br_value=br_value,
|
||||||
|
)
|
||||||
|
|
||||||
|
def decode_direct(self, address: int) -> Instruction:
|
||||||
|
b = self.rom.u8(address)
|
||||||
|
|
||||||
|
if b == 0x00:
|
||||||
|
return self.ins(address, 1, "NOP")
|
||||||
|
if b == 0x02:
|
||||||
|
mask = self.rom.u8(address + 1)
|
||||||
|
return self.ins(address, 2, "LDM.W", f"@SP+, {reg_list(mask)}")
|
||||||
|
if b == 0x12:
|
||||||
|
mask = self.rom.u8(address + 1)
|
||||||
|
return self.ins(address, 2, "STM.W", f"{reg_list(mask)}, @-SP")
|
||||||
|
if b == 0x03:
|
||||||
|
page = self.rom.u8(address + 1)
|
||||||
|
pc = self.rom.u16(address + 2)
|
||||||
|
return self.ins(address, 4, "PJSR", f"@{h24((page << 16) | pc)}", "call", [pc])
|
||||||
|
if b == 0x13:
|
||||||
|
page = self.rom.u8(address + 1)
|
||||||
|
pc = self.rom.u16(address + 2)
|
||||||
|
return self.ins(address, 4, "PJMP", f"@{h24((page << 16) | pc)}", "jump", [pc], False)
|
||||||
|
if b in (0x01, 0x06, 0x07):
|
||||||
|
op2 = self.rom.u8(address + 1)
|
||||||
|
if 0xB8 <= op2 <= 0xBF:
|
||||||
|
disp = s8(self.rom.u8(address + 2))
|
||||||
|
target = (address + 3 + disp) & 0xFFFF
|
||||||
|
cond = {0x01: "F", 0x06: "NE", 0x07: "EQ"}[b]
|
||||||
|
return self.ins(
|
||||||
|
address,
|
||||||
|
3,
|
||||||
|
f"SCB/{cond}",
|
||||||
|
f"R{op2 & 7}, {label_or_h(target, self.labels)}",
|
||||||
|
"branch",
|
||||||
|
[target],
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
if b == 0x08:
|
||||||
|
op2 = self.rom.u8(address + 1)
|
||||||
|
if (op2 & 0xF0) == 0x10:
|
||||||
|
return self.ins(address, 2, "TRAPA", f"#{op2 & 0x0F}")
|
||||||
|
if b == 0x09:
|
||||||
|
return self.ins(address, 1, "TRAP/VS")
|
||||||
|
if b == 0x0A:
|
||||||
|
return self.ins(address, 1, "RTE", kind="rte", fallthrough=False)
|
||||||
|
if b == 0x0E:
|
||||||
|
disp = s8(self.rom.u8(address + 1))
|
||||||
|
target = (address + 2 + disp) & 0xFFFF
|
||||||
|
return self.ins(address, 2, "BSR", label_or_h(target, self.labels), "call", [target])
|
||||||
|
if b == 0x0F:
|
||||||
|
return self.ins(address, 1, "UNLK", "FP")
|
||||||
|
if b == 0x10:
|
||||||
|
target = self.rom.u16(address + 1)
|
||||||
|
return self.ins(address, 3, "JMP", f"@{label_or_h(target, self.labels)}", "jump", [target], False)
|
||||||
|
if b == 0x11:
|
||||||
|
return self.decode_11(address)
|
||||||
|
if b == 0x14:
|
||||||
|
value = self.rom.u8(address + 1)
|
||||||
|
return self.ins(address, 2, "RTD", f"#{h8(value)}", "return", fallthrough=False)
|
||||||
|
if b == 0x17:
|
||||||
|
value = self.rom.u8(address + 1)
|
||||||
|
return self.ins(address, 2, "LINK", f"FP, #{h8(value)}")
|
||||||
|
if b == 0x18:
|
||||||
|
target = self.rom.u16(address + 1)
|
||||||
|
return self.ins(address, 3, "JSR", f"@{label_or_h(target, self.labels)}", "call", [target])
|
||||||
|
if b == 0x19:
|
||||||
|
return self.ins(address, 1, "RTS", kind="return", fallthrough=False)
|
||||||
|
if b == 0x1A:
|
||||||
|
return self.ins(address, 1, "SLEEP", kind="sleep", fallthrough=False)
|
||||||
|
if b == 0x1C:
|
||||||
|
value = self.rom.u16(address + 1)
|
||||||
|
return self.ins(address, 3, "RTD", f"#{h16(value)}", "return", fallthrough=False)
|
||||||
|
if b == 0x1E:
|
||||||
|
disp = s16(self.rom.u16(address + 1))
|
||||||
|
target = (address + 3 + disp) & 0xFFFF
|
||||||
|
return self.ins(address, 3, "BSR", label_or_h(target, self.labels), "call", [target])
|
||||||
|
if b == 0x1F:
|
||||||
|
value = self.rom.u16(address + 1)
|
||||||
|
return self.ins(address, 3, "LINK", f"FP, #{h16(value)}")
|
||||||
|
if 0x20 <= b <= 0x2F:
|
||||||
|
cond = b & 0x0F
|
||||||
|
disp = s8(self.rom.u8(address + 1))
|
||||||
|
target = (address + 2 + disp) & 0xFFFF
|
||||||
|
fallthrough = BRANCH_NAMES[cond] not in ("BRA",)
|
||||||
|
kind = "jump" if BRANCH_NAMES[cond] == "BRA" else "branch"
|
||||||
|
return self.ins(address, 2, BRANCH_NAMES[cond], label_or_h(target, self.labels), kind, [target], fallthrough)
|
||||||
|
if 0x30 <= b <= 0x3F:
|
||||||
|
cond = b & 0x0F
|
||||||
|
disp = s16(self.rom.u16(address + 1))
|
||||||
|
target = (address + 3 + disp) & 0xFFFF
|
||||||
|
fallthrough = BRANCH_NAMES[cond] not in ("BRA",)
|
||||||
|
kind = "jump" if BRANCH_NAMES[cond] == "BRA" else "branch"
|
||||||
|
return self.ins(address, 3, BRANCH_NAMES[cond], label_or_h(target, self.labels), kind, [target], fallthrough)
|
||||||
|
if 0x40 <= b <= 0x47:
|
||||||
|
rd = b & 7
|
||||||
|
imm = self.rom.u8(address + 1)
|
||||||
|
return self.ins(address, 2, "CMP:E", f"#{h8(imm)}, R{rd}")
|
||||||
|
if 0x48 <= b <= 0x4F:
|
||||||
|
rd = b & 7
|
||||||
|
imm = self.rom.u16(address + 1)
|
||||||
|
return self.ins(address, 3, "CMP:I", f"#{h16(imm)}, R{rd}")
|
||||||
|
if 0x50 <= b <= 0x57:
|
||||||
|
rd = b & 7
|
||||||
|
imm = self.rom.u8(address + 1)
|
||||||
|
return self.ins(address, 2, "MOV:E.B", f"#{h8(imm)}, R{rd}")
|
||||||
|
if 0x58 <= b <= 0x5F:
|
||||||
|
rd = b & 7
|
||||||
|
imm = self.rom.u16(address + 1)
|
||||||
|
return self.ins(address, 3, "MOV:I.W", f"#{h16(imm)}, R{rd}")
|
||||||
|
if 0x60 <= b <= 0x67:
|
||||||
|
rd = b & 7
|
||||||
|
low = self.rom.u8(address + 1)
|
||||||
|
ref = self.short_abs_address(low)
|
||||||
|
return self.ins(
|
||||||
|
address,
|
||||||
|
2,
|
||||||
|
"MOV:L.B",
|
||||||
|
f"{self.short_abs(low)}, R{rd}",
|
||||||
|
references=[ref] if ref is not None else None,
|
||||||
|
)
|
||||||
|
if 0x68 <= b <= 0x6F:
|
||||||
|
rd = b & 7
|
||||||
|
low = self.rom.u8(address + 1)
|
||||||
|
ref = self.short_abs_address(low)
|
||||||
|
return self.ins(
|
||||||
|
address,
|
||||||
|
2,
|
||||||
|
"MOV:L.W",
|
||||||
|
f"{self.short_abs(low)}, R{rd}",
|
||||||
|
references=[ref] if ref is not None else None,
|
||||||
|
)
|
||||||
|
if 0x70 <= b <= 0x77:
|
||||||
|
rs = b & 7
|
||||||
|
low = self.rom.u8(address + 1)
|
||||||
|
ref = self.short_abs_address(low)
|
||||||
|
ea = EA(self.short_abs(low), "abs8", "B", 2, value=low, address=ref)
|
||||||
|
return self.ins(
|
||||||
|
address,
|
||||||
|
2,
|
||||||
|
"MOV:S.B",
|
||||||
|
f"R{rs}, {ea.text}",
|
||||||
|
references=[ref] if ref is not None else None,
|
||||||
|
comment=write_comment(ea, None),
|
||||||
|
)
|
||||||
|
if 0x78 <= b <= 0x7F:
|
||||||
|
rs = b & 7
|
||||||
|
low = self.rom.u8(address + 1)
|
||||||
|
ref = self.short_abs_address(low)
|
||||||
|
ea = EA(self.short_abs(low), "abs8", "W", 2, value=low, address=ref)
|
||||||
|
return self.ins(
|
||||||
|
address,
|
||||||
|
2,
|
||||||
|
"MOV:S.W",
|
||||||
|
f"R{rs}, {ea.text}",
|
||||||
|
references=[ref] if ref is not None else None,
|
||||||
|
comment=write_comment(ea, None),
|
||||||
|
)
|
||||||
|
if 0x80 <= b <= 0x87:
|
||||||
|
rd = b & 7
|
||||||
|
disp = s8(self.rom.u8(address + 1))
|
||||||
|
return self.ins(address, 2, "MOV:F.B", f"@({disp_text(disp)},R6), R{rd}")
|
||||||
|
if 0x88 <= b <= 0x8F:
|
||||||
|
rd = b & 7
|
||||||
|
disp = s8(self.rom.u8(address + 1))
|
||||||
|
return self.ins(address, 2, "MOV:F.W", f"@({disp_text(disp)},R6), R{rd}")
|
||||||
|
if 0x90 <= b <= 0x97:
|
||||||
|
rs = b & 7
|
||||||
|
disp = s8(self.rom.u8(address + 1))
|
||||||
|
return self.ins(address, 2, "MOV:F.B", f"R{rs}, @({disp_text(disp)},R6)")
|
||||||
|
if 0x98 <= b <= 0x9F:
|
||||||
|
rs = b & 7
|
||||||
|
disp = s8(self.rom.u8(address + 1))
|
||||||
|
return self.ins(address, 2, "MOV:F.W", f"R{rs}, @({disp_text(disp)},R6)")
|
||||||
|
raise DecodeError(f"unknown direct opcode {h8(b)}")
|
||||||
|
|
||||||
|
def decode_11(self, address: int) -> Instruction:
|
||||||
|
op = self.rom.u8(address + 1)
|
||||||
|
if op == 0x14:
|
||||||
|
value = self.rom.u8(address + 2)
|
||||||
|
return self.ins(address, 3, "PRTD", f"#{h8(value)}", "return", fallthrough=False)
|
||||||
|
if op == 0x19:
|
||||||
|
return self.ins(address, 2, "PRTS", kind="return", fallthrough=False)
|
||||||
|
if op == 0x1C:
|
||||||
|
value = self.rom.u16(address + 2)
|
||||||
|
return self.ins(address, 4, "PRTD", f"#{h16(value)}", "return", fallthrough=False)
|
||||||
|
if 0xC0 <= op <= 0xC7:
|
||||||
|
return self.ins(address, 2, "PJMP", f"@R{op & 7}", "jump", fallthrough=False)
|
||||||
|
if 0xC8 <= op <= 0xCF:
|
||||||
|
return self.ins(address, 2, "PJSR", f"@R{op & 7}", "call")
|
||||||
|
if 0xD0 <= op <= 0xD7:
|
||||||
|
return self.ins(address, 2, "JMP", f"@R{op & 7}", "jump", fallthrough=False)
|
||||||
|
if 0xD8 <= op <= 0xDF:
|
||||||
|
return self.ins(address, 2, "JSR", f"@R{op & 7}", "call")
|
||||||
|
if 0xE0 <= op <= 0xE7:
|
||||||
|
disp = s8(self.rom.u8(address + 2))
|
||||||
|
return self.ins(address, 3, "JMP", f"@({disp_text(disp)},R{op & 7})", "jump", fallthrough=False)
|
||||||
|
if 0xE8 <= op <= 0xEF:
|
||||||
|
disp = s8(self.rom.u8(address + 2))
|
||||||
|
return self.ins(address, 3, "JSR", f"@({disp_text(disp)},R{op & 7})", "call")
|
||||||
|
if 0xF0 <= op <= 0xF7:
|
||||||
|
disp = s16(self.rom.u16(address + 2))
|
||||||
|
return self.ins(address, 4, "JMP", f"@({disp_text(disp)},R{op & 7})", "jump", fallthrough=False)
|
||||||
|
if 0xF8 <= op <= 0xFF:
|
||||||
|
disp = s16(self.rom.u16(address + 2))
|
||||||
|
return self.ins(address, 4, "JSR", f"@({disp_text(disp)},R{op & 7})", "call")
|
||||||
|
raise DecodeError(f"unknown 0x11 opcode {h8(op)}")
|
||||||
|
|
||||||
|
def ins(
|
||||||
|
self,
|
||||||
|
address: int,
|
||||||
|
size: int,
|
||||||
|
mnemonic: str,
|
||||||
|
operands: str = "",
|
||||||
|
kind: str = "normal",
|
||||||
|
targets: Iterable[int] | None = None,
|
||||||
|
fallthrough: bool = True,
|
||||||
|
references: Iterable[int] | None = None,
|
||||||
|
comment: str = "",
|
||||||
|
writes_br: bool = False,
|
||||||
|
br_value: int | None = None,
|
||||||
|
) -> Instruction:
|
||||||
|
return Instruction(
|
||||||
|
address,
|
||||||
|
self.rom.slice(address, size),
|
||||||
|
mnemonic,
|
||||||
|
operands,
|
||||||
|
kind=kind,
|
||||||
|
targets=list(targets or []),
|
||||||
|
fallthrough=fallthrough,
|
||||||
|
comment=comment,
|
||||||
|
references=list(references or []),
|
||||||
|
writes_br=writes_br,
|
||||||
|
br_value=br_value,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_ea_byte(value: int) -> bool:
|
||||||
|
return value in (0x04, 0x05, 0x0C, 0x0D, 0x15, 0x1D) or 0xA0 <= value <= 0xFF
|
||||||
101
h8536/formatting.py
Normal file
101
h8536/formatting.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .model import EA
|
||||||
|
from .tables import IO_BITFIELDS, IO_REGISTERS
|
||||||
|
|
||||||
|
|
||||||
|
def h8(value: int) -> str:
|
||||||
|
return f"H'{value & 0xFF:02X}"
|
||||||
|
|
||||||
|
|
||||||
|
def h16(value: int) -> str:
|
||||||
|
return f"H'{value & 0xFFFF:04X}"
|
||||||
|
|
||||||
|
|
||||||
|
def h24(value: int) -> str:
|
||||||
|
return f"H'{value & 0xFFFFFF:06X}"
|
||||||
|
|
||||||
|
|
||||||
|
def s8(value: int) -> int:
|
||||||
|
value &= 0xFF
|
||||||
|
return value - 0x100 if value & 0x80 else value
|
||||||
|
|
||||||
|
|
||||||
|
def s16(value: int) -> int:
|
||||||
|
value &= 0xFFFF
|
||||||
|
return value - 0x10000 if value & 0x8000 else value
|
||||||
|
|
||||||
|
|
||||||
|
def parse_int(text: str) -> int:
|
||||||
|
return int(text.replace("H'", "0x").replace("$", "0x"), 0)
|
||||||
|
|
||||||
|
|
||||||
|
def reg_list(mask: int) -> str:
|
||||||
|
regs = [f"R{idx}" for idx in range(8) if mask & (1 << idx)]
|
||||||
|
return "{" + ",".join(regs) + "}" if regs else "{}"
|
||||||
|
|
||||||
|
|
||||||
|
def disp_text(value: int) -> str:
|
||||||
|
if value < 0:
|
||||||
|
return f"-{h16(-value) if value < -0xFF else h8(-value)}"
|
||||||
|
return h16(value) if value > 0xFF else h8(value)
|
||||||
|
|
||||||
|
|
||||||
|
def short_abs(low: int) -> str:
|
||||||
|
return f"@BR:{h8(low)}"
|
||||||
|
|
||||||
|
|
||||||
|
def control_reg(ccc: int, size: str) -> str:
|
||||||
|
if size == "W":
|
||||||
|
return "SR" if ccc == 0 else f"CR{ccc}?"
|
||||||
|
return {
|
||||||
|
0: "CCR",
|
||||||
|
1: "BR",
|
||||||
|
2: "EP",
|
||||||
|
3: "DP",
|
||||||
|
4: "TP",
|
||||||
|
}.get(ccc, f"CR{ccc}?")
|
||||||
|
|
||||||
|
|
||||||
|
def label_for(address: int) -> str:
|
||||||
|
return f"loc_{address:04X}"
|
||||||
|
|
||||||
|
|
||||||
|
def label_or_h(address: int, labels: dict[int, str]) -> str:
|
||||||
|
return labels.get(address, label_for(address))
|
||||||
|
|
||||||
|
|
||||||
|
def _bitfield_name(address: int, bit: int) -> str | None:
|
||||||
|
return IO_BITFIELDS.get(address, {}).get(bit)
|
||||||
|
|
||||||
|
|
||||||
|
def _bitfield_values(address: int, value: int) -> str:
|
||||||
|
fields = IO_BITFIELDS.get(address)
|
||||||
|
if not fields:
|
||||||
|
return ""
|
||||||
|
parts = [f"{name}={(value >> bit) & 1}" for bit, name in sorted(fields.items(), reverse=True)]
|
||||||
|
return " ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def write_comment(ea: EA, value: int | None) -> str:
|
||||||
|
if ea.address is None or ea.address not in IO_REGISTERS:
|
||||||
|
return ""
|
||||||
|
name = IO_REGISTERS[ea.address]
|
||||||
|
if value is None:
|
||||||
|
return name
|
||||||
|
text = f"{name} = {h16(value) if value > 0xFF else h8(value)}"
|
||||||
|
fields = _bitfield_values(ea.address, value)
|
||||||
|
return f"{text} ({fields})" if fields else text
|
||||||
|
|
||||||
|
|
||||||
|
def bit_comment(mnemonic: str, ea: EA, bit: int) -> str:
|
||||||
|
if ea.address is None or ea.address not in IO_REGISTERS:
|
||||||
|
return ""
|
||||||
|
action = {"BSET": "set", "BCLR": "clear", "BNOT": "toggle", "BTST": "test"}.get(
|
||||||
|
mnemonic.split(".")[0],
|
||||||
|
"bit",
|
||||||
|
)
|
||||||
|
bit_name = _bitfield_name(ea.address, bit)
|
||||||
|
if bit_name:
|
||||||
|
return f"{action} {bit_name} (bit {bit}) of {IO_REGISTERS[ea.address]}"
|
||||||
|
return f"{action} bit {bit} of {IO_REGISTERS[ea.address]}"
|
||||||
36
h8536/memory.py
Normal file
36
h8536/memory.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class MemoryRegion:
|
||||||
|
name: str
|
||||||
|
start: int
|
||||||
|
end: int
|
||||||
|
kind: str
|
||||||
|
manual: str
|
||||||
|
|
||||||
|
def contains(self, address: int) -> bool:
|
||||||
|
return self.start <= address <= self.end
|
||||||
|
|
||||||
|
|
||||||
|
# Manual references:
|
||||||
|
# - H8/536 address map: Manual/0900766b802125d0.md:26503-26505
|
||||||
|
# - Vector/DTC low address area: Manual/0900766b802125d0.md:2944-2946
|
||||||
|
# - Register field H'FE80-H'FFFF: Manual/0900766b802125d0.md:2961-2968
|
||||||
|
# - On-chip RAM H'F680-H'FE7F: Manual/0900766b802125d0.md:17884-17894
|
||||||
|
MEMORY_REGIONS: tuple[MemoryRegion, ...] = (
|
||||||
|
MemoryRegion("exception_vectors", 0x0000, 0x009F, "vectors", "section 2 address space"),
|
||||||
|
MemoryRegion("dtc_vectors", 0x00A0, 0x00FF, "dtc_vectors", "section 2 address space"),
|
||||||
|
MemoryRegion("on_chip_rom", 0x0100, 0xF67F, "rom", "section 17 ROM"),
|
||||||
|
MemoryRegion("on_chip_ram", 0xF680, 0xFE7F, "ram", "section 16 RAM"),
|
||||||
|
MemoryRegion("register_field", 0xFE80, 0xFFFF, "registers", "appendix B register map"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def region_for(address: int) -> MemoryRegion:
|
||||||
|
for region in MEMORY_REGIONS:
|
||||||
|
if region.contains(address):
|
||||||
|
return region
|
||||||
|
return MemoryRegion("external_or_unmapped", address, address, "external", "outside page-0 on-chip ranges")
|
||||||
40
h8536/model.py
Normal file
40
h8536/model.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EA:
|
||||||
|
text: str
|
||||||
|
mode: str
|
||||||
|
size: str
|
||||||
|
length: int
|
||||||
|
reg: int | None = None
|
||||||
|
value: int | None = None
|
||||||
|
address: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Instruction:
|
||||||
|
address: int
|
||||||
|
raw: bytes
|
||||||
|
mnemonic: str
|
||||||
|
operands: str = ""
|
||||||
|
kind: str = "normal"
|
||||||
|
targets: list[int] = field(default_factory=list)
|
||||||
|
fallthrough: bool = True
|
||||||
|
comment: str = ""
|
||||||
|
valid: bool = True
|
||||||
|
references: list[int] = field(default_factory=list)
|
||||||
|
writes_br: bool = False
|
||||||
|
br_value: int | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def size(self) -> int:
|
||||||
|
return len(self.raw)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self) -> str:
|
||||||
|
if self.operands:
|
||||||
|
return f"{self.mnemonic} {self.operands}"
|
||||||
|
return self.mnemonic
|
||||||
87
h8536/render.py
Normal file
87
h8536/render.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .formatting import h16, label_for
|
||||||
|
from .model import Instruction
|
||||||
|
from .rom import Rom
|
||||||
|
from .vectors import DtcVectorEntry
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
) -> 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("")
|
||||||
|
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("")
|
||||||
|
|
||||||
|
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 = f" ; {ins.comment}" if ins.comment 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,
|
||||||
|
) -> 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()),
|
||||||
|
"instructions": [
|
||||||
|
{
|
||||||
|
"address": ins.address,
|
||||||
|
"bytes": ins.raw.hex().upper(),
|
||||||
|
"text": ins.text,
|
||||||
|
"mnemonic": ins.mnemonic,
|
||||||
|
"operands": ins.operands,
|
||||||
|
"kind": ins.kind,
|
||||||
|
"targets": ins.targets,
|
||||||
|
"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")
|
||||||
42
h8536/rom.py
Normal file
42
h8536/rom.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .formatting import h16
|
||||||
|
|
||||||
|
|
||||||
|
class DecodeError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Rom:
|
||||||
|
def __init__(self, data: bytes, base: int = 0) -> None:
|
||||||
|
self.data = data
|
||||||
|
self.base = base
|
||||||
|
|
||||||
|
@property
|
||||||
|
def end(self) -> int:
|
||||||
|
return self.base + len(self.data)
|
||||||
|
|
||||||
|
def offset(self, address: int) -> int:
|
||||||
|
return address - self.base
|
||||||
|
|
||||||
|
def contains(self, address: int, size: int = 1) -> bool:
|
||||||
|
off = self.offset(address)
|
||||||
|
return 0 <= off and off + size <= len(self.data)
|
||||||
|
|
||||||
|
def u8(self, address: int) -> int:
|
||||||
|
if not self.contains(address):
|
||||||
|
raise DecodeError(f"address out of ROM: {h16(address)}")
|
||||||
|
return self.data[self.offset(address)]
|
||||||
|
|
||||||
|
def u16(self, address: int) -> int:
|
||||||
|
if not self.contains(address, 2):
|
||||||
|
raise DecodeError(f"word out of ROM: {h16(address)}")
|
||||||
|
off = self.offset(address)
|
||||||
|
return (self.data[off] << 8) | self.data[off + 1]
|
||||||
|
|
||||||
|
def slice(self, address: int, size: int) -> bytes:
|
||||||
|
if not self.contains(address, size):
|
||||||
|
raise DecodeError(f"range out of ROM: {h16(address)}+{size}")
|
||||||
|
off = self.offset(address)
|
||||||
|
return self.data[off : off + size]
|
||||||
|
|
||||||
308
h8536/tables.py
Normal file
308
h8536/tables.py
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
BRANCH_NAMES = [
|
||||||
|
"BRA",
|
||||||
|
"BRN",
|
||||||
|
"BHI",
|
||||||
|
"BLS",
|
||||||
|
"BCC",
|
||||||
|
"BCS",
|
||||||
|
"BNE",
|
||||||
|
"BEQ",
|
||||||
|
"BVC",
|
||||||
|
"BVS",
|
||||||
|
"BPL",
|
||||||
|
"BMI",
|
||||||
|
"BGE",
|
||||||
|
"BLT",
|
||||||
|
"BGT",
|
||||||
|
"BLE",
|
||||||
|
]
|
||||||
|
|
||||||
|
FLOW_STOP = {"jump", "return", "rte", "sleep", "invalid"}
|
||||||
|
|
||||||
|
|
||||||
|
IO_REGISTERS: dict[int, str] = {
|
||||||
|
0xFE80: "P1DDR",
|
||||||
|
0xFE81: "P2DDR",
|
||||||
|
0xFE82: "P1DR",
|
||||||
|
0xFE83: "P2DR",
|
||||||
|
0xFE84: "P3DDR",
|
||||||
|
0xFE85: "P4DDR",
|
||||||
|
0xFE86: "P3DR",
|
||||||
|
0xFE87: "P4DR",
|
||||||
|
0xFE88: "P5DDR",
|
||||||
|
0xFE89: "P6DDR",
|
||||||
|
0xFE8A: "P5DR",
|
||||||
|
0xFE8B: "P6DR",
|
||||||
|
0xFE8C: "P7DDR",
|
||||||
|
0xFE8E: "P7DR",
|
||||||
|
0xFE8F: "P8DR",
|
||||||
|
0xFE90: "FRT1_TCR",
|
||||||
|
0xFE91: "FRT1_TCSR",
|
||||||
|
0xFE92: "FRT1_FRC_H",
|
||||||
|
0xFE93: "FRT1_FRC_L",
|
||||||
|
0xFE94: "FRT1_OCRA_L",
|
||||||
|
0xFE95: "FRT1_OCRB_L",
|
||||||
|
0xFE96: "FRT1_ICR_H",
|
||||||
|
0xFE97: "FRT1_ICR_L",
|
||||||
|
0xFEA0: "FRT2_TCR",
|
||||||
|
0xFEA1: "FRT2_TCSR",
|
||||||
|
0xFEA2: "FRT2_FRC_H",
|
||||||
|
0xFEA3: "FRT2_FRC_L",
|
||||||
|
0xFEA4: "FRT2_OCRA_H",
|
||||||
|
0xFEA5: "FRT2_OCRA_L",
|
||||||
|
0xFEA6: "FRT2_OCRB_H",
|
||||||
|
0xFEA7: "FRT2_OCRB_L",
|
||||||
|
0xFEA8: "FRT2_ICR_H",
|
||||||
|
0xFEA9: "FRT2_ICR_L",
|
||||||
|
0xFEB0: "FRT3_TCR",
|
||||||
|
0xFEB1: "FRT3_TCSR",
|
||||||
|
0xFEB2: "FRT3_FRC_H",
|
||||||
|
0xFEB3: "FRT3_FRC_L",
|
||||||
|
0xFEB4: "FRT3_OCRA_H",
|
||||||
|
0xFEB5: "FRT3_OCRA_L",
|
||||||
|
0xFEB6: "FRT3_OCRB_H",
|
||||||
|
0xFEB7: "FRT3_OCRB_L",
|
||||||
|
0xFEB8: "FRT3_ICR_H",
|
||||||
|
0xFEB9: "FRT3_ICR_L",
|
||||||
|
0xFEC0: "PWM1_TCR",
|
||||||
|
0xFEC1: "PWM1_DTR",
|
||||||
|
0xFEC2: "PWM1_TCNT",
|
||||||
|
0xFEC4: "PWM2_TCR",
|
||||||
|
0xFEC5: "PWM2_DTR",
|
||||||
|
0xFEC6: "PWM2_TCNT",
|
||||||
|
0xFEC8: "PWM3_TCR",
|
||||||
|
0xFEC9: "PWM3_DTR",
|
||||||
|
0xFECA: "PWM3_TCNT",
|
||||||
|
0xFED0: "TMR_TCR",
|
||||||
|
0xFED1: "TMR_TCSR",
|
||||||
|
0xFED2: "TMR_TCORA",
|
||||||
|
0xFED3: "TMR_TCORB",
|
||||||
|
0xFED4: "TMR_TCNT",
|
||||||
|
0xFED8: "SCI1_SMR",
|
||||||
|
0xFED9: "SCI1_BRR",
|
||||||
|
0xFEDA: "SCI1_SCR",
|
||||||
|
0xFEDB: "SCI1_TDR",
|
||||||
|
0xFEDC: "SCI1_SSR",
|
||||||
|
0xFEDD: "SCI1_RDR",
|
||||||
|
0xFEE0: "ADDRA_H",
|
||||||
|
0xFEE1: "ADDRA_L",
|
||||||
|
0xFEE2: "ADDRB_H",
|
||||||
|
0xFEE3: "ADDRB_L",
|
||||||
|
0xFEE4: "ADDRC_H",
|
||||||
|
0xFEE5: "ADDRC_L",
|
||||||
|
0xFEE6: "ADDRD_H",
|
||||||
|
0xFEE7: "ADDRD_L",
|
||||||
|
0xFEE8: "ADCSR",
|
||||||
|
0xFEEC: "WDT_TCSR_R",
|
||||||
|
0xFEED: "WDT_TCNT_R",
|
||||||
|
0xFEF0: "SCI2_SMR",
|
||||||
|
0xFEF1: "SCI2_BRR",
|
||||||
|
0xFEF2: "SCI2_SCR",
|
||||||
|
0xFEF3: "SCI2_TDR",
|
||||||
|
0xFEF4: "SCI2_SSR",
|
||||||
|
0xFEF5: "SCI2_RDR",
|
||||||
|
0xFEFC: "SYSCR1",
|
||||||
|
0xFEFD: "SYSCR2",
|
||||||
|
0xFEFE: "P9DDR",
|
||||||
|
0xFEFF: "P9DR",
|
||||||
|
0xFF00: "IPRA",
|
||||||
|
0xFF01: "IPRB",
|
||||||
|
0xFF02: "IPRC",
|
||||||
|
0xFF03: "IPRD",
|
||||||
|
0xFF04: "IPRE",
|
||||||
|
0xFF05: "IPRF",
|
||||||
|
0xFF08: "DTEA",
|
||||||
|
0xFF09: "DTEB",
|
||||||
|
0xFF0A: "DTEC",
|
||||||
|
0xFF0B: "DTED",
|
||||||
|
0xFF0C: "DTEE",
|
||||||
|
0xFF0D: "DTEF",
|
||||||
|
0xFF10: "WCR",
|
||||||
|
0xFF11: "RAMCR",
|
||||||
|
0xFF12: "MDCR",
|
||||||
|
0xFF13: "SBYCR",
|
||||||
|
0xFF14: "RSTCSR_W",
|
||||||
|
0xFF15: "RSTCSR_R",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_FRT_TCR_BITS = {
|
||||||
|
7: "ICIE",
|
||||||
|
6: "OCIEB",
|
||||||
|
5: "OCIEA",
|
||||||
|
4: "OVIE",
|
||||||
|
3: "OEB",
|
||||||
|
2: "OEA",
|
||||||
|
1: "CKS1",
|
||||||
|
0: "CKS0",
|
||||||
|
}
|
||||||
|
|
||||||
|
_FRT_TCSR_BITS = {
|
||||||
|
7: "ICF",
|
||||||
|
6: "OCFB",
|
||||||
|
5: "OCFA",
|
||||||
|
4: "OVF",
|
||||||
|
3: "OLVLB",
|
||||||
|
2: "OLVLA",
|
||||||
|
1: "IEDG",
|
||||||
|
0: "CCLRA",
|
||||||
|
}
|
||||||
|
|
||||||
|
_PWM_TCR_BITS = {
|
||||||
|
7: "OE",
|
||||||
|
6: "OS",
|
||||||
|
2: "CKS2",
|
||||||
|
1: "CKS1",
|
||||||
|
0: "CKS0",
|
||||||
|
}
|
||||||
|
|
||||||
|
_SCI_SMR_BITS = {
|
||||||
|
7: "C/A",
|
||||||
|
6: "CHR",
|
||||||
|
5: "PE",
|
||||||
|
4: "O/E",
|
||||||
|
3: "STOP",
|
||||||
|
1: "CKS1",
|
||||||
|
0: "CKS0",
|
||||||
|
}
|
||||||
|
|
||||||
|
_SCI_SCR_BITS = {
|
||||||
|
7: "TIE",
|
||||||
|
6: "RIE",
|
||||||
|
5: "TE",
|
||||||
|
4: "RE",
|
||||||
|
1: "CKE1",
|
||||||
|
0: "CKE0",
|
||||||
|
}
|
||||||
|
|
||||||
|
_SCI_SSR_BITS = {
|
||||||
|
7: "TDRE",
|
||||||
|
6: "RDRF",
|
||||||
|
5: "ORER",
|
||||||
|
4: "FER",
|
||||||
|
3: "PER",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IO_BITFIELDS: dict[int, dict[int, str]] = {
|
||||||
|
0xFE90: _FRT_TCR_BITS,
|
||||||
|
0xFE91: _FRT_TCSR_BITS,
|
||||||
|
0xFEA0: _FRT_TCR_BITS,
|
||||||
|
0xFEA1: _FRT_TCSR_BITS,
|
||||||
|
0xFEB0: _FRT_TCR_BITS,
|
||||||
|
0xFEB1: _FRT_TCSR_BITS,
|
||||||
|
0xFEC0: _PWM_TCR_BITS,
|
||||||
|
0xFEC4: _PWM_TCR_BITS,
|
||||||
|
0xFEC8: _PWM_TCR_BITS,
|
||||||
|
0xFED0: {
|
||||||
|
7: "CMIEB",
|
||||||
|
6: "CMIEA",
|
||||||
|
5: "OVIE",
|
||||||
|
4: "CCLR1",
|
||||||
|
3: "CCLR0",
|
||||||
|
2: "CKS2",
|
||||||
|
1: "CKS1",
|
||||||
|
0: "CKS0",
|
||||||
|
},
|
||||||
|
0xFED1: {
|
||||||
|
7: "CMFB",
|
||||||
|
6: "CMFA",
|
||||||
|
5: "OVF",
|
||||||
|
3: "OS3",
|
||||||
|
2: "OS2",
|
||||||
|
1: "OS1",
|
||||||
|
0: "OS0",
|
||||||
|
},
|
||||||
|
0xFED8: _SCI_SMR_BITS,
|
||||||
|
0xFEDA: _SCI_SCR_BITS,
|
||||||
|
0xFEDC: _SCI_SSR_BITS,
|
||||||
|
0xFEE8: {
|
||||||
|
7: "ADF",
|
||||||
|
6: "ADIE",
|
||||||
|
5: "ADST",
|
||||||
|
4: "SCAN",
|
||||||
|
3: "CKS",
|
||||||
|
2: "CH2",
|
||||||
|
1: "CH1",
|
||||||
|
0: "CH0",
|
||||||
|
},
|
||||||
|
0xFEF0: _SCI_SMR_BITS,
|
||||||
|
0xFEF2: _SCI_SCR_BITS,
|
||||||
|
0xFEF4: _SCI_SSR_BITS,
|
||||||
|
0xFEFC: {
|
||||||
|
6: "IRQ1E",
|
||||||
|
5: "IRQ0E",
|
||||||
|
4: "NMIEG",
|
||||||
|
3: "BRLE",
|
||||||
|
},
|
||||||
|
0xFEFD: {
|
||||||
|
6: "IRQ5E",
|
||||||
|
5: "IRQ4E",
|
||||||
|
4: "IRQ3E",
|
||||||
|
3: "IRQ2E",
|
||||||
|
2: "P6PWME",
|
||||||
|
1: "P9PWME",
|
||||||
|
0: "P9SCI2E",
|
||||||
|
},
|
||||||
|
0xFF10: {
|
||||||
|
3: "WMS1",
|
||||||
|
2: "WMS0",
|
||||||
|
1: "WC1",
|
||||||
|
0: "WC0",
|
||||||
|
},
|
||||||
|
0xFF11: {
|
||||||
|
7: "RAME",
|
||||||
|
},
|
||||||
|
0xFF12: {
|
||||||
|
2: "MDS2",
|
||||||
|
1: "MDS1",
|
||||||
|
0: "MDS0",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
VECTOR_NAMES_MIN: dict[int, str] = {
|
||||||
|
0x0000: "reset",
|
||||||
|
0x0002: "reserved_0002",
|
||||||
|
0x0004: "invalid_instruction",
|
||||||
|
0x0006: "zero_divide",
|
||||||
|
0x0008: "trap_vs",
|
||||||
|
0x0010: "address_error",
|
||||||
|
0x0012: "trace",
|
||||||
|
0x0016: "nmi",
|
||||||
|
0x0040: "irq0",
|
||||||
|
0x0042: "interval_timer",
|
||||||
|
0x0048: "irq1",
|
||||||
|
0x0050: "irq2",
|
||||||
|
0x0052: "irq3",
|
||||||
|
0x0058: "irq4",
|
||||||
|
0x005A: "irq5",
|
||||||
|
0x0060: "frt1_ici",
|
||||||
|
0x0062: "frt1_ocia",
|
||||||
|
0x0064: "frt1_ocib",
|
||||||
|
0x0066: "frt1_fovi",
|
||||||
|
0x0068: "frt2_ici",
|
||||||
|
0x006A: "frt2_ocia",
|
||||||
|
0x006C: "frt2_ocib",
|
||||||
|
0x006E: "frt2_fovi",
|
||||||
|
0x0070: "frt3_ici",
|
||||||
|
0x0072: "frt3_ocia",
|
||||||
|
0x0074: "frt3_ocib",
|
||||||
|
0x0076: "frt3_fovi",
|
||||||
|
0x0078: "tmr_cmia",
|
||||||
|
0x007A: "tmr_cmib",
|
||||||
|
0x007C: "tmr_ovi",
|
||||||
|
0x0080: "sci1_eri",
|
||||||
|
0x0082: "sci1_rxi",
|
||||||
|
0x0084: "sci1_txi",
|
||||||
|
0x0088: "sci2_eri",
|
||||||
|
0x008A: "sci2_rxi",
|
||||||
|
0x008C: "sci2_txi",
|
||||||
|
0x0090: "ad_adi",
|
||||||
|
}
|
||||||
|
|
||||||
|
for trap in range(16):
|
||||||
|
VECTOR_NAMES_MIN[0x0020 + trap * 2] = f"trapa_{trap:x}"
|
||||||
104
h8536/vectors.py
Normal file
104
h8536/vectors.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from .rom import Rom
|
||||||
|
from .tables import VECTOR_NAMES_MIN
|
||||||
|
|
||||||
|
|
||||||
|
class DtcVectorEntry(TypedDict):
|
||||||
|
vector_address: int
|
||||||
|
source: str
|
||||||
|
register_info_address: int
|
||||||
|
target: int
|
||||||
|
|
||||||
|
|
||||||
|
DTC_VECTOR_NAMES_MIN: dict[int, str] = {
|
||||||
|
0x00C0: "irq0",
|
||||||
|
0x00C2: "interval_timer",
|
||||||
|
0x00C8: "irq1",
|
||||||
|
0x00D0: "irq2",
|
||||||
|
0x00D2: "irq3",
|
||||||
|
0x00D8: "irq4",
|
||||||
|
0x00DA: "irq5",
|
||||||
|
0x00E0: "frt1_ici",
|
||||||
|
0x00E2: "frt1_ocia",
|
||||||
|
0x00E4: "frt1_ocib",
|
||||||
|
0x00E8: "frt2_ici",
|
||||||
|
0x00EA: "frt2_ocia",
|
||||||
|
0x00EC: "frt2_ocib",
|
||||||
|
0x00F0: "frt3_ici",
|
||||||
|
0x00F2: "frt3_ocia",
|
||||||
|
0x00F4: "frt3_ocib",
|
||||||
|
0x00F8: "tmr_cmia",
|
||||||
|
0x00FA: "tmr_cmib",
|
||||||
|
0x00A2: "sci1_rxi",
|
||||||
|
0x00A4: "sci1_txi",
|
||||||
|
0x00AA: "sci2_rxi",
|
||||||
|
0x00AC: "sci2_txi",
|
||||||
|
0x00B0: "ad_adi",
|
||||||
|
}
|
||||||
|
|
||||||
|
DTC_VECTOR_NAMES_MAX: dict[int, str] = {
|
||||||
|
min_addr * 2: name for min_addr, name in DTC_VECTOR_NAMES_MIN.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def read_vectors_min(rom: Rom) -> dict[int, tuple[str, int]]:
|
||||||
|
vectors: dict[int, tuple[str, int]] = {}
|
||||||
|
for addr in range(0x0000, 0x009A, 2):
|
||||||
|
if not rom.contains(addr, 2):
|
||||||
|
break
|
||||||
|
target = rom.u16(addr)
|
||||||
|
if target in (0x0000, 0xFFFF):
|
||||||
|
continue
|
||||||
|
name = VECTOR_NAMES_MIN.get(addr, f"vector_{addr:04X}")
|
||||||
|
vectors[addr] = (name, target)
|
||||||
|
return vectors
|
||||||
|
|
||||||
|
|
||||||
|
def read_vectors_max(rom: Rom) -> dict[int, tuple[str, int]]:
|
||||||
|
vectors: dict[int, tuple[str, int]] = {}
|
||||||
|
for addr in range(0x0000, 0x0134, 4):
|
||||||
|
if not rom.contains(addr, 4):
|
||||||
|
break
|
||||||
|
page = rom.u8(addr + 1)
|
||||||
|
pc = rom.u16(addr + 2)
|
||||||
|
if pc in (0x0000, 0xFFFF) or page != 0:
|
||||||
|
continue
|
||||||
|
name = VECTOR_NAMES_MIN.get(addr // 2, f"vector_{addr:04X}")
|
||||||
|
vectors[addr] = (name, pc)
|
||||||
|
return vectors
|
||||||
|
|
||||||
|
|
||||||
|
def _dtc_entry(vector_address: int, source: str, target: int) -> DtcVectorEntry:
|
||||||
|
return {
|
||||||
|
"vector_address": vector_address,
|
||||||
|
"source": source,
|
||||||
|
"register_info_address": target,
|
||||||
|
"target": target,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def read_dtc_vectors_min(rom: Rom) -> dict[int, DtcVectorEntry]:
|
||||||
|
vectors: dict[int, DtcVectorEntry] = {}
|
||||||
|
for addr, source in sorted(DTC_VECTOR_NAMES_MIN.items()):
|
||||||
|
if not rom.contains(addr, 2):
|
||||||
|
continue
|
||||||
|
target = rom.u16(addr)
|
||||||
|
if target in (0x0000, 0xFFFF):
|
||||||
|
continue
|
||||||
|
vectors[addr] = _dtc_entry(addr, source, target)
|
||||||
|
return vectors
|
||||||
|
|
||||||
|
|
||||||
|
def read_dtc_vectors_max(rom: Rom) -> dict[int, DtcVectorEntry]:
|
||||||
|
vectors: dict[int, DtcVectorEntry] = {}
|
||||||
|
for addr, source in sorted(DTC_VECTOR_NAMES_MAX.items()):
|
||||||
|
if not rom.contains(addr, 4):
|
||||||
|
continue
|
||||||
|
target = rom.u16(addr + 2)
|
||||||
|
if target in (0x0000, 0xFFFF):
|
||||||
|
continue
|
||||||
|
vectors[addr] = _dtc_entry(addr, source, target)
|
||||||
|
return vectors
|
||||||
9
h8536_decompiler.py
Normal file
9
h8536_decompiler.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Compatibility wrapper for the H8/536 ROM decompiler CLI."""
|
||||||
|
|
||||||
|
from h8536.cli import main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|
||||||
103
tests/test_decoder_manual_examples.py
Normal file
103
tests/test_decoder_manual_examples.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from h8536.decoder import H8536Decoder
|
||||||
|
from h8536.analysis import trace
|
||||||
|
from h8536.rom import Rom
|
||||||
|
|
||||||
|
|
||||||
|
def decode(data: list[int]):
|
||||||
|
return H8536Decoder(Rom(bytes(data), base=0)).decode(0)
|
||||||
|
|
||||||
|
|
||||||
|
class ManualDecoderExamplesTest(unittest.TestCase):
|
||||||
|
def test_manual_add_general_register_indirect_byte_example(self):
|
||||||
|
instruction = decode([0xD0, 0x21])
|
||||||
|
|
||||||
|
self.assertEqual(instruction.text, "ADD:G.B @R0, R1")
|
||||||
|
self.assertEqual(instruction.size, 2)
|
||||||
|
self.assertEqual(instruction.targets, [])
|
||||||
|
|
||||||
|
def test_manual_add_general_absolute_8_word_example(self):
|
||||||
|
instruction = decode([0x0D, 0x11, 0x21])
|
||||||
|
|
||||||
|
self.assertEqual(instruction.text, "ADD:G.W @BR:H'11, R1")
|
||||||
|
self.assertEqual(instruction.size, 3)
|
||||||
|
self.assertEqual(instruction.targets, [])
|
||||||
|
|
||||||
|
def test_reset_sequence_style_ldc_word_immediate_to_sr(self):
|
||||||
|
instruction = decode([0x0C, 0x07, 0x00, 0x88])
|
||||||
|
|
||||||
|
self.assertEqual(instruction.text, "LDC.W #H'0700, SR")
|
||||||
|
self.assertEqual(instruction.size, 4)
|
||||||
|
self.assertEqual(instruction.targets, [])
|
||||||
|
|
||||||
|
def test_branch_targets_and_flow_metadata_for_bne_and_bra(self):
|
||||||
|
bne = decode([0x26, 0x02])
|
||||||
|
bra = decode([0x20, 0xFE])
|
||||||
|
|
||||||
|
self.assertEqual(bne.text, "BNE loc_0004")
|
||||||
|
self.assertEqual(bne.size, 2)
|
||||||
|
self.assertEqual(bne.kind, "branch")
|
||||||
|
self.assertEqual(bne.targets, [0x0004])
|
||||||
|
self.assertTrue(bne.fallthrough)
|
||||||
|
|
||||||
|
self.assertEqual(bra.text, "BRA loc_0000")
|
||||||
|
self.assertEqual(bra.size, 2)
|
||||||
|
self.assertEqual(bra.kind, "jump")
|
||||||
|
self.assertEqual(bra.targets, [0x0000])
|
||||||
|
self.assertFalse(bra.fallthrough)
|
||||||
|
|
||||||
|
def test_jsr_rts_and_bsr_basics(self):
|
||||||
|
jsr_abs = decode([0x18, 0x12, 0x34])
|
||||||
|
jsr_reg = decode([0x11, 0xD8])
|
||||||
|
rts = decode([0x19])
|
||||||
|
bsr = decode([0x0E, 0x02])
|
||||||
|
|
||||||
|
self.assertEqual(jsr_abs.text, "JSR @loc_1234")
|
||||||
|
self.assertEqual(jsr_abs.size, 3)
|
||||||
|
self.assertEqual(jsr_abs.kind, "call")
|
||||||
|
self.assertEqual(jsr_abs.targets, [0x1234])
|
||||||
|
|
||||||
|
self.assertEqual(jsr_reg.text, "JSR @R0")
|
||||||
|
self.assertEqual(jsr_reg.size, 2)
|
||||||
|
self.assertEqual(jsr_reg.kind, "call")
|
||||||
|
self.assertEqual(jsr_reg.targets, [])
|
||||||
|
|
||||||
|
self.assertEqual(rts.text, "RTS")
|
||||||
|
self.assertEqual(rts.size, 1)
|
||||||
|
self.assertEqual(rts.kind, "return")
|
||||||
|
self.assertEqual(rts.targets, [])
|
||||||
|
self.assertFalse(rts.fallthrough)
|
||||||
|
|
||||||
|
self.assertEqual(bsr.text, "BSR loc_0004")
|
||||||
|
self.assertEqual(bsr.size, 2)
|
||||||
|
self.assertEqual(bsr.kind, "call")
|
||||||
|
self.assertEqual(bsr.targets, [0x0004])
|
||||||
|
|
||||||
|
def test_movfpe_movtpe_extended_general_form(self):
|
||||||
|
movfpe = decode([0x05, 0x80, 0x00, 0x82])
|
||||||
|
movtpe = decode([0x05, 0x80, 0x00, 0x92])
|
||||||
|
|
||||||
|
self.assertEqual(movfpe.text, "MOVFPE.B @BR:H'80, R2")
|
||||||
|
self.assertEqual(movfpe.size, 4)
|
||||||
|
self.assertEqual(movfpe.targets, [])
|
||||||
|
|
||||||
|
self.assertEqual(movtpe.text, "MOVTPE.B R2, @BR:H'80")
|
||||||
|
self.assertEqual(movtpe.size, 4)
|
||||||
|
self.assertEqual(movtpe.targets, [])
|
||||||
|
|
||||||
|
def test_trace_tracks_ldc_immediate_to_br_for_short_absolute_operands(self):
|
||||||
|
rom = Rom(bytes([0x04, 0xFE, 0x89, 0x60, 0xFC]), base=0)
|
||||||
|
decoder = H8536Decoder(rom)
|
||||||
|
|
||||||
|
instructions = trace(decoder, [0], 0, len(rom.data))
|
||||||
|
|
||||||
|
self.assertEqual(instructions[0].text, "LDC.B #H'FE, BR")
|
||||||
|
self.assertTrue(instructions[0].writes_br)
|
||||||
|
self.assertEqual(instructions[0].br_value, 0xFE)
|
||||||
|
self.assertEqual(instructions[3].text, "MOV:L.B @SYSCR1, R0")
|
||||||
|
self.assertEqual(instructions[3].references, [0xFEFC])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user