377 lines
12 KiB
Python
377 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
from .decoder import is_ea_byte
|
|
from .model import Instruction
|
|
|
|
|
|
CYCLE_SOURCE = "manual Appendix A.4, tables A-7/A-8"
|
|
CYCLE_ASSUMPTION = "on-chip instruction fetch/operand access, no external wait states"
|
|
|
|
|
|
def annotate_cycles(instructions: dict[int, Instruction], mode: str) -> None:
|
|
for instruction in instructions.values():
|
|
instruction.cycles = estimate_cycles(instruction, mode)
|
|
|
|
|
|
def cycle_comment(cycles: dict[str, object] | None) -> str:
|
|
if not cycles:
|
|
return ""
|
|
if "cycles" in cycles:
|
|
return f"cycles={cycles['cycles']}"
|
|
if "cycles_min" in cycles and "cycles_max" in cycles:
|
|
return f"cycles={cycles['cycles_min']}-{cycles['cycles_max']}"
|
|
if "not_taken" in cycles and "taken" in cycles:
|
|
return f"cycles={cycles['not_taken']}/{cycles['taken']} nt/t"
|
|
if "trap_not_taken" in cycles:
|
|
return f"cycles={cycles['trap_not_taken']}/{cycles['trap_taken']} no-trap/trap"
|
|
if "false" in cycles:
|
|
return f"cycles={cycles['false']}/{cycles['count_minus_1']}/{cycles['taken']} false/-1/t"
|
|
return ""
|
|
|
|
|
|
def estimate_cycles(instruction: Instruction, mode: str) -> dict[str, object] | None:
|
|
if not instruction.valid:
|
|
return None
|
|
|
|
if instruction.raw and is_ea_byte(instruction.raw[0]):
|
|
base = _estimate_general_cycles(instruction, mode)
|
|
else:
|
|
base = _estimate_direct_cycles(instruction, mode)
|
|
if base is None:
|
|
return None
|
|
|
|
base.setdefault("source", CYCLE_SOURCE)
|
|
base.setdefault("assumption", CYCLE_ASSUMPTION)
|
|
return base
|
|
|
|
|
|
def _info(
|
|
cycles: int,
|
|
base_cycles: int | None = None,
|
|
adjustment: int = 0,
|
|
stack_adjustment: int = 0,
|
|
note: str | None = None,
|
|
) -> dict[str, object]:
|
|
info: dict[str, object] = {"cycles": cycles}
|
|
if base_cycles is not None:
|
|
info["base_cycles"] = base_cycles
|
|
if adjustment:
|
|
info["alignment_adjustment"] = adjustment
|
|
if stack_adjustment:
|
|
info["stack_adjustment"] = stack_adjustment
|
|
if note:
|
|
info["note"] = note
|
|
return info
|
|
|
|
|
|
def _branch_info(not_taken: int, taken: int, instruction: Instruction) -> dict[str, object]:
|
|
adjusted_taken = taken + _branch_adjust(instruction.address)
|
|
info: dict[str, object] = {
|
|
"not_taken": not_taken,
|
|
"taken": adjusted_taken,
|
|
"base_taken": taken,
|
|
}
|
|
if adjusted_taken != taken:
|
|
info["alignment_adjustment_taken"] = adjusted_taken - taken
|
|
return info
|
|
|
|
|
|
def _mode_min_max(mode: str, minimum: int, maximum: int) -> int:
|
|
return maximum if mode == "max" else minimum
|
|
|
|
|
|
def _branch_adjust(address: int) -> int:
|
|
# Table A-8(a): branch/system transfer adjustment is 0 at even starts, 1 at odd starts.
|
|
return address & 1
|
|
|
|
|
|
def _ea_mode(raw: bytes) -> tuple[str, str] | None:
|
|
if not raw:
|
|
return None
|
|
b = raw[0]
|
|
if 0xA0 <= b <= 0xAF:
|
|
return "reg", "W" if b & 0x08 else "B"
|
|
if 0xB0 <= b <= 0xBF:
|
|
return "predec", "W" if b & 0x08 else "B"
|
|
if 0xC0 <= b <= 0xCF:
|
|
return "postinc", "W" if b & 0x08 else "B"
|
|
if 0xD0 <= b <= 0xDF:
|
|
return "indirect", "W" if b & 0x08 else "B"
|
|
if 0xE0 <= b <= 0xEF:
|
|
return "disp8", "W" if b & 0x08 else "B"
|
|
if 0xF0 <= b <= 0xFF:
|
|
return "disp16", "W" if b & 0x08 else "B"
|
|
if b == 0x04:
|
|
return "imm", "B"
|
|
if b == 0x0C:
|
|
return "imm", "W"
|
|
if b == 0x05:
|
|
return "abs8", "B"
|
|
if b == 0x0D:
|
|
return "abs8", "W"
|
|
if b == 0x15:
|
|
return "abs16", "B"
|
|
if b == 0x1D:
|
|
return "abs16", "W"
|
|
return None
|
|
|
|
|
|
def _a8_adjust(address: int, ea_mode: str, size: str, mov_imm_to_ea: bool = False) -> int:
|
|
# Table A-8(b), simplified for decoded modes used by this tool.
|
|
if mov_imm_to_ea:
|
|
if size == "B":
|
|
return 1
|
|
even = {
|
|
"reg": 2,
|
|
"predec": 0,
|
|
"postinc": 2,
|
|
"indirect": 2,
|
|
"disp8": 2,
|
|
"disp16": 0,
|
|
"abs8": 2,
|
|
"abs16": 2,
|
|
}
|
|
odd = {
|
|
"reg": 0,
|
|
"predec": 2,
|
|
"postinc": 0,
|
|
"indirect": 0,
|
|
"disp8": 0,
|
|
"disp16": 2,
|
|
"abs8": 0,
|
|
"abs16": 0,
|
|
}
|
|
return (odd if address & 1 else even).get(ea_mode, 0)
|
|
|
|
even_default = {
|
|
"reg": 0,
|
|
"predec": 1,
|
|
"postinc": 0,
|
|
"indirect": 1,
|
|
"disp8": 1,
|
|
"disp16": 1,
|
|
"abs8": 0,
|
|
"abs16": 1,
|
|
"imm": 0,
|
|
}
|
|
odd_default = {
|
|
"reg": 0,
|
|
"predec": 0,
|
|
"postinc": 1,
|
|
"indirect": 0,
|
|
"disp8": 0,
|
|
"disp16": 0,
|
|
"abs8": 1,
|
|
"abs16": 0,
|
|
"imm": 0,
|
|
}
|
|
return (odd_default if address & 1 else even_default).get(ea_mode, 0)
|
|
|
|
|
|
def _memory_read_base(ea_mode: str, size: str) -> int:
|
|
if ea_mode == "reg":
|
|
return 2 if size == "B" else 3
|
|
if ea_mode == "imm":
|
|
return 3 if size == "B" else 4
|
|
if ea_mode in {"disp8", "disp16", "abs16"}:
|
|
return 6
|
|
return 5
|
|
|
|
|
|
def _memory_write_base(ea_mode: str, size: str) -> int:
|
|
if ea_mode == "reg":
|
|
return 2 if size == "B" else 3
|
|
if ea_mode in {"disp8", "disp16", "abs16"}:
|
|
return 6
|
|
return 5
|
|
|
|
|
|
def _read_modify_write_base(ea_mode: str, size: str) -> int:
|
|
if ea_mode == "reg":
|
|
return 2 if size == "B" else 3
|
|
if ea_mode in {"disp8", "disp16", "abs16"}:
|
|
return 8
|
|
return 7
|
|
|
|
|
|
def _imm_to_ea_base(ea_mode: str, size: str) -> int:
|
|
if ea_mode == "reg":
|
|
return 2 if size == "B" else 3
|
|
if ea_mode in {"disp8", "disp16", "abs16"}:
|
|
return 8 if size == "B" else 9
|
|
return 7 if size == "B" else 8
|
|
|
|
|
|
def _estimate_general_cycles(instruction: Instruction, mode: str) -> dict[str, object] | None:
|
|
parsed = _ea_mode(instruction.raw)
|
|
if parsed is None or len(instruction.raw) < 2:
|
|
return None
|
|
ea_mode, ea_size = parsed
|
|
mnemonic = instruction.mnemonic
|
|
op = instruction.raw[1 + (1 if ea_mode in {"imm", "abs8"} else 2 if ea_mode == "abs16" else 0)]
|
|
if ea_mode in {"disp8"}:
|
|
op = instruction.raw[2]
|
|
elif ea_mode == "disp16":
|
|
op = instruction.raw[3]
|
|
|
|
mov_imm_to_ea = mnemonic.startswith("MOV:G") and instruction.operands.startswith("#")
|
|
adjustment = _a8_adjust(instruction.address, ea_mode, ea_size, mov_imm_to_ea)
|
|
|
|
if mnemonic.startswith(("ADD:G", "ADDS", "SUB", "SUBS", "ADDX", "SUBX", "AND.", "OR.", "XOR.", "CMP:G")):
|
|
base = _memory_read_base(ea_mode, ea_size)
|
|
elif mnemonic.startswith("MOV:G"):
|
|
base = _imm_to_ea_base(ea_mode, ea_size) if mov_imm_to_ea else _memory_write_base(ea_mode, ea_size)
|
|
elif mnemonic.startswith(("BTST", "TST")):
|
|
base = _memory_read_base(ea_mode, ea_size)
|
|
elif mnemonic.startswith(("BSET", "BCLR", "BNOT", "CLR", "NEG", "NOT", "SHAL", "SHAR", "SHLL", "SHLR", "ROTL", "ROTR", "ROTXL", "ROTXR", "TAS")):
|
|
base = _read_modify_write_base(ea_mode, ea_size)
|
|
elif mnemonic.startswith("ADD:Q"):
|
|
base = 4 if ea_mode == "reg" else _read_modify_write_base(ea_mode, ea_size)
|
|
elif mnemonic.startswith("MULXU.B"):
|
|
base = 18 if ea_mode == "reg" else 19
|
|
elif mnemonic.startswith("MULXU.W"):
|
|
base = 25
|
|
elif mnemonic.startswith("DIVXU.B"):
|
|
base = 21 if ea_mode == "reg" else 23
|
|
elif mnemonic.startswith("DIVXU.W"):
|
|
base = 28 if ea_mode == "reg" else 29
|
|
elif mnemonic.startswith(("LDC.B", "STC.B")):
|
|
base = 4 if ea_mode in {"reg", "imm"} else 6 if ea_mode in {"abs8", "indirect", "predec", "postinc"} else 7
|
|
elif mnemonic.startswith(("LDC.W", "STC.W")):
|
|
base = 6 if ea_mode in {"reg", "imm"} else 7 if ea_mode in {"abs8", "indirect", "predec", "postinc"} else 8
|
|
elif mnemonic.startswith(("ORC", "ANDC", "XORC")):
|
|
base = 4
|
|
adjustment = 0
|
|
elif mnemonic.startswith(("EXTS", "EXTU", "SWAP")):
|
|
base = 3
|
|
adjustment = 0
|
|
elif mnemonic.startswith(("MOVFPE", "MOVTPE")):
|
|
cycles = _mode_min_max(mode, 13, 20)
|
|
return _info(cycles, note="E-clock peripheral transfer")
|
|
else:
|
|
return None
|
|
|
|
return _info(base + adjustment, base, adjustment)
|
|
|
|
|
|
def _estimate_direct_cycles(instruction: Instruction, mode: str) -> dict[str, object] | None:
|
|
raw = instruction.raw
|
|
mnemonic = instruction.mnemonic
|
|
if not raw:
|
|
return None
|
|
op = raw[0]
|
|
|
|
if mnemonic.startswith("B") and 0x20 <= op <= 0x3F:
|
|
info = _branch_info(3, 7, instruction)
|
|
if mnemonic == "BRA":
|
|
info["cycles"] = info["taken"]
|
|
elif mnemonic == "BRN":
|
|
info["cycles"] = info["not_taken"]
|
|
return info
|
|
if mnemonic.startswith("SCB/"):
|
|
taken = 8 + _branch_adjust(instruction.address)
|
|
return {
|
|
"false": 3,
|
|
"count_minus_1": 4,
|
|
"taken": taken,
|
|
"base_taken": 8,
|
|
}
|
|
if mnemonic == "BSR":
|
|
base = 9
|
|
adjust = _branch_adjust(instruction.address)
|
|
stack = 4
|
|
return _info(base + stack + adjust, base, adjust, stack, "PC word push to stack")
|
|
if mnemonic == "JMP":
|
|
if op == 0x10:
|
|
base = 7
|
|
elif len(raw) >= 2 and raw[0] == 0x11 and 0xD0 <= raw[1] <= 0xD7:
|
|
base = 6
|
|
elif len(raw) >= 2 and raw[0] == 0x11 and 0xE0 <= raw[1] <= 0xE7:
|
|
base = 7
|
|
elif len(raw) >= 2 and raw[0] == 0x11 and 0xF0 <= raw[1] <= 0xF7:
|
|
base = 8
|
|
else:
|
|
return None
|
|
adjust = _branch_adjust(instruction.address)
|
|
return _info(base + adjust, base, adjust)
|
|
if mnemonic == "JSR":
|
|
if op == 0x18:
|
|
base = 9
|
|
elif len(raw) >= 2 and raw[0] == 0x11 and 0xD8 <= raw[1] <= 0xDF:
|
|
base = 9
|
|
elif len(raw) >= 2 and raw[0] == 0x11 and 0xE8 <= raw[1] <= 0xEF:
|
|
base = 9
|
|
elif len(raw) >= 2 and raw[0] == 0x11 and 0xF8 <= raw[1] <= 0xFF:
|
|
base = 10
|
|
else:
|
|
return None
|
|
adjust = _branch_adjust(instruction.address)
|
|
stack = 4
|
|
return _info(base + stack + adjust, base, adjust, stack, "PC word push to stack")
|
|
if mnemonic == "PJMP":
|
|
base = 9 if op == 0x13 else 8
|
|
adjust = _branch_adjust(instruction.address)
|
|
return _info(base + adjust, base, adjust)
|
|
if mnemonic == "PJSR":
|
|
base = 15 if op == 0x03 else 13
|
|
adjust = _branch_adjust(instruction.address)
|
|
stack = 6 if mode == "max" else 4
|
|
return _info(base + stack + adjust, base, adjust, stack, "return address push to stack")
|
|
if mnemonic == "TRAPA":
|
|
return _info(_mode_min_max(mode, 17, 22))
|
|
if mnemonic == "TRAP/VS":
|
|
return {
|
|
"trap_not_taken": 3,
|
|
"trap_taken": _mode_min_max(mode, 18, 23) + _branch_adjust(instruction.address),
|
|
"base_trap_taken": _mode_min_max(mode, 18, 23),
|
|
}
|
|
if mnemonic == "RTE":
|
|
base = _mode_min_max(mode, 13, 15)
|
|
adjust = _branch_adjust(instruction.address)
|
|
return _info(base + adjust, base, adjust)
|
|
if mnemonic == "RTS":
|
|
base = 8
|
|
adjust = _branch_adjust(instruction.address)
|
|
stack = 4
|
|
return _info(base + stack + adjust, base, adjust, stack, "PC word pop from stack")
|
|
if mnemonic == "RTD":
|
|
base = 9
|
|
adjust = _branch_adjust(instruction.address)
|
|
stack = 4
|
|
return _info(base + stack + adjust, base, adjust, stack, "PC word pop from stack")
|
|
if mnemonic == "PRTS":
|
|
base = 12
|
|
adjust = _branch_adjust(instruction.address)
|
|
stack = 6 if mode == "max" else 4
|
|
return _info(base + stack + adjust, base, adjust, stack, "return address pop from stack")
|
|
if mnemonic == "PRTD":
|
|
base = 13
|
|
adjust = _branch_adjust(instruction.address)
|
|
stack = 6 if mode == "max" else 4
|
|
return _info(base + stack + adjust, base, adjust, stack, "return address pop from stack")
|
|
if mnemonic == "UNLK":
|
|
return _info(5)
|
|
if mnemonic == "LINK":
|
|
return _info(6 if len(raw) == 2 else 7)
|
|
if mnemonic == "LDM.W":
|
|
n = raw[1].bit_count()
|
|
cycles = 6 + 4 * n
|
|
return _info(cycles, note=f"6+4n, n={n}")
|
|
if mnemonic == "STM.W":
|
|
n = raw[1].bit_count()
|
|
cycles = 6 + 3 * n
|
|
return _info(cycles, note=f"6+3n, n={n}")
|
|
if mnemonic in {"NOP", "SLEEP"}:
|
|
return _info(2)
|
|
if mnemonic.startswith("CMP:E"):
|
|
return _info(2)
|
|
if mnemonic.startswith("CMP:I"):
|
|
return _info(3)
|
|
if mnemonic.startswith("MOV:E"):
|
|
return _info(2)
|
|
if mnemonic.startswith("MOV:I"):
|
|
return _info(3)
|
|
if mnemonic.startswith(("MOV:L", "MOV:S", "MOV:F")):
|
|
return _info(5)
|
|
return None
|