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