from __future__ import annotations from dataclasses import dataclass, field from ..decoder import H8536Decoder from ..formatting import h16 from ..rom import DecodeError from ..vectors import read_vectors_min from .constants import ( FRT_TCR_OCIEA, FRT_TCSR_OCFA, FRT1_FRC_H, FRT1_OCRA_H, FRT1_TCR, FRT1_TCSR, FRT2_FRC_H, FRT2_OCRA_H, FRT2_TCR, FRT2_TCSR, IPRA, IPRC, IPRE, SCI_SCR_RE, SCI_SCR_RIE, SCI_SCR_TIE, SCI_SSR_FER, SCI_SSR_ORER, SCI_SSR_PER, SCI_SSR_RDRF, SCI_SSR_TDRE, VECTOR_FRT1_OCIA, VECTOR_FRT2_OCIA, VECTOR_INTERVAL_TIMER, VECTOR_SCI1_ERI, VECTOR_SCI1_RXI, VECTOR_SCI1_TXI, ) from .cpu import CPUState, mask, s8, s16, sign_bit from .errors import EmulatorError, UnsupportedInstruction from .fast_paths import P9FastPath, P9FastPathConfig from .memory import MemoryMap from .sci import SCI1 from .timers import FrtOciaScheduler, FrtRegisters from .uart import UartTiming @dataclass class RunReport: steps: int cycles: int pc: int stopped_reason: str tx_bytes: bytes tx_frames: list[bytes] heartbeat_seen: bool clock_hz: int frt1_ocia_period_ms: float | None = None frt2_ocia_period_ms: float | None = None unsupported: str | None = None trace: list[str] = field(default_factory=list) def summary_lines(self) -> list[str]: lines = [ f"steps={self.steps}", f"cycles={self.cycles}", f"pc={h16(self.pc)}", f"stopped={self.stopped_reason}", f"clock_hz={self.clock_hz}", "frt1_ocia_period_ms=" + _format_optional_ms(self.frt1_ocia_period_ms), "frt2_ocia_period_ms=" + _format_optional_ms(self.frt2_ocia_period_ms), "tx_bytes=" + self.tx_bytes.hex(" ").upper(), "tx_frames=" + ", ".join(frame.hex(" ").upper() for frame in self.tx_frames), f"heartbeat_seen={self.heartbeat_seen}", ] if self.unsupported: lines.append(f"unsupported={self.unsupported}") lines.append( "next_todo=implement the stopped opcode, then add interrupt scheduling for SCI1 TXI and interval/watchdog timer overflow" ) return lines class H8536Emulator: def __init__( self, rom_bytes: bytes, *, interval_steps: int = 2048, frt1_ocia_steps: int | None = None, frt2_ocia_steps: int | None = None, clock_hz: int = 10_000_000, p9_fast_path: P9FastPath | None = None, p9_fast_path_enabled: bool = False, p9_fast_default_input_byte: int = 0xFF, p9_fast_default_wrapper_success: bool = False, p7_input: int = 0xFF, eeprom_seed: str = "blank", sci1_tx_timing: UartTiming | None = None, ) -> None: if not rom_bytes: raise ValueError("ROM image is empty") if eeprom_seed not in {"blank", "factory"}: raise ValueError("eeprom_seed must be 'blank' or 'factory'") self.sci1 = SCI1() self.memory = MemoryMap(rom_bytes, self.sci1, p7_input=p7_input) if eeprom_seed == "factory": self.memory.seed_factory_eeprom_and_shadow() self.sci1.configure_tx_timing(sci1_tx_timing, clock_hz=clock_hz) self.memory.p9_bus.default_wrapper_success = bool(p9_fast_default_wrapper_success) self.p9_fast_path = p9_fast_path or P9FastPath( P9FastPathConfig(enabled=p9_fast_path_enabled, default_input_byte=p9_fast_default_input_byte) ) self.cpu = CPUState() self.vectors = read_vectors_min(self.memory.rom) self.interval_steps = max(1, interval_steps) self.frt1_ocia_steps = max(1, frt1_ocia_steps) if frt1_ocia_steps is not None else None self.frt2_ocia_steps = max(1, frt2_ocia_steps) if frt2_ocia_steps is not None else None self.clock_hz = max(1, clock_hz) self.frt1_ocia = FrtOciaScheduler(FrtRegisters("FRT1", FRT1_TCR, FRT1_TCSR, FRT1_FRC_H, FRT1_OCRA_H)) self.frt2_ocia = FrtOciaScheduler(FrtRegisters("FRT2", FRT2_TCR, FRT2_TCSR, FRT2_FRC_H, FRT2_OCRA_H)) self._interval_counter = 0 self._frt1_ocia_counter = 0 self._frt2_ocia_counter = 0 self.reset() def reset(self) -> None: self.cpu = CPUState(pc=self.reset_vector()) def reset_vector(self) -> int: if self.memory.rom.contains(0, 2): return self.memory.rom.u16(0) raise DecodeError("ROM does not contain a reset vector at H'0000") def inject_sci1_rx_byte(self, value: int) -> None: self.memory.inject_sci1_rx_byte(value) def step(self) -> str: pc = self.cpu.pc cycles_before = self.cpu.cycles self.memory.current_pc = pc try: if self.p9_fast_path.try_handle(self): self._tick_peripherals(self.cpu.cycles - cycles_before) return f"{h16(pc)}: {'':<17} P9 fast-path" decoder = H8536Decoder(self.memory.rom, br=self.cpu.br) ins = decoder.decode(pc) if not ins.valid: raise UnsupportedInstruction(pc, ins.raw, ins.text) next_pc = (pc + ins.size) & 0xFFFF raw = ins.raw text = ins.text if raw[0] == 0x00: pass elif raw[0] == 0x02 and len(raw) == 2: self._pop_register_mask(raw[1]) elif raw[0] == 0x11 and len(raw) >= 2: next_pc = self._indirect_jump_call(raw, pc, next_pc) elif raw[0] == 0x12 and len(raw) == 2: self._push_register_mask(raw[1]) elif raw[0] in (0x01, 0x06, 0x07) and len(raw) == 3 and 0xB8 <= raw[1] <= 0xBF: next_pc = self._scb(raw, pc, next_pc) elif raw[0] in (0x04, 0x0C): next_pc = self._execute_general(pc, next_pc) elif 0x40 <= raw[0] <= 0x47 and len(raw) == 2: reg = raw[0] & 0x07 self._cmp(self._reg_read(reg, 1), raw[1], 1) elif 0x48 <= raw[0] <= 0x4F and len(raw) == 3: reg = raw[0] & 0x07 self._cmp(self.cpu.regs[reg], int.from_bytes(raw[1:3], "big"), 2) elif 0x50 <= raw[0] <= 0x57 and len(raw) == 2: self._reg_write(raw[0] & 0x07, raw[1], 1) self._set_logic_flags(raw[1], 1) elif 0x58 <= raw[0] <= 0x5F and len(raw) == 3: self.cpu.regs[raw[0] & 0x07] = int.from_bytes(raw[1:3], "big") self._set_logic_flags(self.cpu.regs[raw[0] & 0x07], 2) elif raw[0] in (0x0E, 0x1E, 0x18): next_pc = self._direct_call(raw, next_pc) elif raw[0] == 0x19: next_pc = self._pop16() elif raw[0] == 0x0A: next_pc = self._return_from_interrupt() elif raw[0] in (0x15, 0x1D) and len(raw) >= 4: next_pc = self._execute_general(pc, next_pc) elif raw[0] in range(0xA0, 0x100): next_pc = self._execute_general(pc, next_pc) elif raw[0] in range(0x20, 0x30) and len(raw) == 2: next_pc = self._branch8(raw, pc, next_pc) elif raw[0] in range(0x30, 0x40) and len(raw) == 3: next_pc = self._branch16(raw, pc, next_pc) else: raise UnsupportedInstruction(pc, raw, text) self.cpu.pc = next_pc self.cpu.steps += 1 cycle_delta = self._rough_cycles(raw) self.cpu.cycles += cycle_delta self._tick_peripherals(cycle_delta) return f"{h16(pc)}: {' '.join(f'{byte:02X}' for byte in raw):<17} {text}" finally: self.memory.current_pc = None def run(self, max_steps: int, trace: bool = False, stop_on_heartbeat: bool = False) -> RunReport: trace_lines: list[str] = [] stopped_reason = "max_steps" unsupported: str | None = None for _ in range(max_steps): try: line = self.step() except UnsupportedInstruction as exc: stopped_reason = "unsupported_instruction" unsupported = str(exc) break if trace: trace_lines.append(line) if stop_on_heartbeat and self.sci1.saw_heartbeat(): stopped_reason = "heartbeat" break return RunReport( steps=self.cpu.steps, cycles=self.cpu.cycles, pc=self.cpu.pc, stopped_reason=stopped_reason, tx_bytes=bytes(self.sci1.tx_bytes), tx_frames=list(self.sci1.tx_frames), heartbeat_seen=self.sci1.saw_heartbeat(), clock_hz=self.clock_hz, frt1_ocia_period_ms=self.frt1_ocia.period_ms(self.memory, self.clock_hz), frt2_ocia_period_ms=self.frt2_ocia.period_ms(self.memory, self.clock_hz), unsupported=unsupported, trace=trace_lines, ) def _execute_general(self, pc: int, next_pc: int) -> int: ea = self._decode_ea(pc) op = self.memory.rom.u8(pc + int(ea["length"])) size = int(ea["size"]) raw = self.memory.rom.slice(pc, self._general_length(pc, ea, op)) if op == 0x00: ext = self.memory.rom.u8(pc + int(ea["length"]) + 1) ext_base = ext & 0xF8 reg = ext & 0x07 if ext_base == 0x80: value = self._read_ea(ea, 1) self._reg_write(reg, value, 1) self._set_logic_flags(value, 1) elif ext_base == 0x90: self._write_ea(ea, self._reg_read(reg, 1), 1) else: raise UnsupportedInstruction(pc, raw, H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text) return next_pc if op in (0x06, 0x07): value = raw[-1] if op == 0x06 else int.from_bytes(raw[-2:], "big") write_size = size if op == 0x06 else 2 self._write_ea(ea, value, write_size) self._set_logic_flags(value, write_size) return next_pc base = op & 0xF8 rd = op & 0x07 if ea["mode"] == "imm" and base in (0x48, 0x58, 0x68): value = self._read_ea(ea, size) if base == 0x48: self.cpu.sr |= value elif base == 0x58: self.cpu.sr &= value else: self.cpu.sr ^= value elif base == 0x80: value = self._read_ea(ea, size) self._reg_write(rd, value, size) self._set_logic_flags(value, size) elif base == 0x90: self._write_ea(ea, self._reg_read(rd, size), size) elif base == 0x20: lhs = self._reg_read(rd, size) rhs = self._read_ea(ea, size) result = lhs + rhs self._reg_write(rd, result, size) self._set_add_flags(lhs, rhs, result, size) elif base == 0x30: lhs = self._reg_read(rd, size) rhs = self._read_ea(ea, size) result = lhs - rhs self._reg_write(rd, result, size) self._set_sub_flags(lhs, rhs, result, size) elif base == 0x50: result = self._reg_read(rd, size) & self._read_ea(ea, size) self._reg_write(rd, result, size) self._set_logic_flags(result, size) elif base == 0x40: result = self._reg_read(rd, size) | self._read_ea(ea, size) self._reg_write(rd, result, size) self._set_logic_flags(result, size) elif base == 0x60: result = self._reg_read(rd, size) ^ self._read_ea(ea, size) self._reg_write(rd, result, size) self._set_logic_flags(result, size) elif base == 0x70: self._cmp(self._reg_read(rd, size), self._read_ea(ea, size), size) elif base == 0xA8: if size != 1: raise UnsupportedInstruction(pc, raw, H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text) result = (self._reg_read(rd, 1) * self._read_ea(ea, 1)) & 0xFFFF self.cpu.regs[rd] = result self._set_logic_flags(result, 2) self.cpu.c = False elif op in (0x08, 0x09, 0x0C, 0x0D): delta = {0x08: 1, 0x09: 2, 0x0C: -1, 0x0D: -2}[op] old = self._read_ea(ea, size) result = old + delta self._write_ea(ea, result, size) if delta >= 0: self._set_add_flags(old, delta, result, size) else: self._set_sub_flags(old, -delta, result, size) elif op == 0x13: self._write_ea(ea, 0, size) self._set_logic_flags(0, size) elif op == 0x15: result = (~self._read_ea(ea, size)) & mask(size) self._write_ea(ea, result, size) self._set_logic_flags(result, size) elif op == 0x16: self._set_logic_flags(self._read_ea(ea, size), size) elif op in (0x10, 0x11, 0x12) and ea["mode"] == "reg" and size == 1: reg = int(ea["reg"]) value = self.cpu.regs[reg] & 0xFFFF if op == 0x10: result = ((value & 0x00FF) << 8) | ((value >> 8) & 0x00FF) elif op == 0x11: result = s8(value & 0xFF) & 0xFFFF else: result = value & 0x00FF self.cpu.regs[reg] = result self._set_logic_flags(result, 2) elif op == 0x1A: value = self._read_ea(ea, size) result = (value << 1) & mask(size) self.cpu.c = bool(value & sign_bit(size)) self._write_ea(ea, result, size) self._set_logic_flags(result, size) elif op == 0x1B: value = self._read_ea(ea, size) result = value >> 1 self.cpu.c = bool(value & 1) self._write_ea(ea, result, size) self._set_logic_flags(result, size) elif 0xC0 <= op <= 0xFF: bit = op & 0x0F self._bit_operation(ea, size, op & 0xF0, bit) elif base in (0x48, 0x58, 0x68, 0x78): bit = self._reg_read(rd, 1) & 0x0F self._bit_operation(ea, size, base + 0x80, bit) elif base == 0x88: value = self._read_ea(ea, size) if size == 2 and rd == 0: self.cpu.sr = value & 0xFFFF elif size == 1 and rd == 1: self.cpu.br = value & 0xFF else: raise UnsupportedInstruction(pc, raw, H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text) elif base == 0x98: if size == 2 and rd == 0: self._write_ea(ea, self.cpu.sr, 2) elif size == 1 and rd == 1: self._write_ea(ea, self.cpu.br, 1) else: raise UnsupportedInstruction(pc, raw, H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text) elif op in (0x04, 0x05): compare_size = 1 if op == 0x04 else 2 immediate = raw[-1] if op == 0x04 else int.from_bytes(raw[-2:], "big") self._cmp(self._read_ea(ea, compare_size), immediate, compare_size) else: raise UnsupportedInstruction(pc, raw, H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text) return next_pc def _branch8(self, raw: bytes, pc: int, next_pc: int) -> int: cond = raw[0] & 0x0F disp = s8(raw[1]) target = (pc + 2 + disp) & 0xFFFF if self._branch_condition(cond): return target return next_pc def _branch16(self, raw: bytes, pc: int, next_pc: int) -> int: cond = raw[0] & 0x0F disp = s16(int.from_bytes(raw[1:3], "big")) target = (pc + 3 + disp) & 0xFFFF if self._branch_condition(cond): return target return next_pc def _rough_cycles(self, raw: bytes) -> int: if raw[0] in (0x15, 0x1D): return 9 if raw[0] in (0x0E, 0x1E, 0x18): return 14 if raw[0] in (0x19, 0x0A): return 12 if raw[0] in range(0x20, 0x40): return 8 if (raw[0] & 0x0F) == 0 else 4 return 3 def _direct_call(self, raw: bytes, next_pc: int) -> int: self._push16(next_pc) if raw[0] == 0x0E: return (next_pc + s8(raw[1])) & 0xFFFF if raw[0] == 0x1E: return (next_pc + s16(int.from_bytes(raw[1:3], "big"))) & 0xFFFF return int.from_bytes(raw[1:3], "big") def _indirect_jump_call(self, raw: bytes, pc: int, next_pc: int) -> int: op = raw[1] if 0xC0 <= op <= 0xDF: target = self.cpu.regs[op & 0x07] & 0xFFFF elif 0xE0 <= op <= 0xEF and len(raw) >= 3: target = (self.cpu.regs[op & 0x07] + s8(raw[2])) & 0xFFFF elif 0xF0 <= op <= 0xFF and len(raw) >= 4: target = (self.cpu.regs[op & 0x07] + s16(int.from_bytes(raw[2:4], "big"))) & 0xFFFF else: raise UnsupportedInstruction(pc, raw, H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text) if 0xC8 <= op <= 0xCF or 0xD8 <= op <= 0xDF or 0xE8 <= op <= 0xEF or 0xF8 <= op <= 0xFF: self._push16(next_pc) return target def _scb(self, raw: bytes, pc: int, next_pc: int) -> int: reg = raw[1] & 0x07 condition = {0x01: False, 0x06: not self.cpu.z, 0x07: self.cpu.z}[raw[0]] if condition: return next_pc value = (self.cpu.regs[reg] - 1) & 0xFFFF self.cpu.regs[reg] = value self.cpu.z = value == 0 if value != 0: return (pc + 3 + s8(raw[2])) & 0xFFFF return next_pc def _tick_peripherals(self, cycle_delta: int) -> None: self.sci1.tick(cycle_delta) self._interval_counter += 1 if self.frt1_ocia_steps is None: self.frt1_ocia.tick(self.memory, cycle_delta) else: self._frt1_ocia_counter += 1 if self.frt2_ocia_steps is None: self.frt2_ocia.tick(self.memory, cycle_delta) else: self._frt2_ocia_counter += 1 self._service_pending_interrupt() def _service_pending_interrupt(self) -> None: if self.cpu.interrupt_depth: return candidates: list[tuple[int, int, str]] = [] sci1_rx_interrupts_enabled = bool(self.sci1.scr & SCI_SCR_RIE and self.sci1.scr & SCI_SCR_RE) if sci1_rx_interrupts_enabled and self.sci1.ssr & (SCI_SSR_ORER | SCI_SSR_FER | SCI_SSR_PER): target = self._vector_target(VECTOR_SCI1_ERI) if target is not None: candidates.append((self._sci1_priority(), target, "sci1_eri")) if sci1_rx_interrupts_enabled and self.sci1.ssr & SCI_SSR_RDRF: target = self._vector_target(VECTOR_SCI1_RXI) if target is not None: candidates.append((self._sci1_priority(), target, "sci1_rxi")) if self.sci1.scr & SCI_SCR_TIE and self.sci1.ssr & SCI_SSR_TDRE: target = self._vector_target(VECTOR_SCI1_TXI) if target is not None: candidates.append((self._sci1_priority(), target, "sci1_txi")) if self._interval_counter >= self.interval_steps: target = self._vector_target(VECTOR_INTERVAL_TIMER) if target is not None: candidates.append((self._interval_priority(), target, "interval_timer")) if self._frt1_ocia_pending(): target = self._vector_target(VECTOR_FRT1_OCIA) if target is not None: candidates.append((self._frt1_priority(), target, "frt1_ocia")) if self._frt2_ocia_pending(): target = self._vector_target(VECTOR_FRT2_OCIA) if target is not None: candidates.append((self._frt2_priority(), target, "frt2_ocia")) if not candidates: return priority, target, source = max(candidates, key=lambda item: item[0]) if priority <= self._interrupt_mask(): return if source == "interval_timer": self._interval_counter = 0 elif source == "frt1_ocia": if self.frt1_ocia_steps is not None: self._frt1_ocia_counter = 0 self.memory.write8(FRT1_TCSR, self.memory.read8(FRT1_TCSR) | FRT_TCSR_OCFA) elif source == "frt2_ocia": if self.frt2_ocia_steps is not None: self._frt2_ocia_counter = 0 self.memory.write8(FRT2_TCSR, self.memory.read8(FRT2_TCSR) | FRT_TCSR_OCFA) self._enter_interrupt(target) def _enter_interrupt(self, target: int) -> None: self._push16(self.cpu.sr) self._push16(self.cpu.pc) self.cpu.pc = target & 0xFFFF self.cpu.interrupt_depth += 1 def _return_from_interrupt(self) -> int: pc = self._pop16() self.cpu.sr = self._pop16() if self.cpu.interrupt_depth: self.cpu.interrupt_depth -= 1 return pc def _vector_target(self, vector_address: int) -> int | None: entry = self.vectors.get(vector_address) return entry[1] if entry else None def _interrupt_mask(self) -> int: return (self.cpu.sr >> 8) & 0x07 def _sci1_priority(self) -> int: return (self.memory.read8(IPRE) >> 4) & 0x07 def _interval_priority(self) -> int: return (self.memory.read8(IPRA) >> 4) & 0x07 def _frt1_ocia_pending(self) -> bool: if self.frt1_ocia_steps is None: return self.frt1_ocia.pending(self.memory) if self._frt1_ocia_counter < self.frt1_ocia_steps: return False return bool(self.memory.read8(FRT1_TCR) & FRT_TCR_OCIEA) def _frt1_priority(self) -> int: return (self.memory.read8(IPRC) >> 4) & 0x07 def _frt2_ocia_pending(self) -> bool: if self.frt2_ocia_steps is None: return self.frt2_ocia.pending(self.memory) if self._frt2_ocia_counter < self.frt2_ocia_steps: return False return bool(self.memory.read8(FRT2_TCR) & FRT_TCR_OCIEA) def _frt2_priority(self) -> int: # H8/536 IPRC assigns bits 6..4 to FRT1 and bits 2..0 to FRT2; # the ROM's IPRC=H'66 therefore gives both timers priority 6. return self.memory.read8(IPRC) & 0x07 def _push16(self, value: int) -> None: sp = (self.cpu.regs[7] - 2) & 0xFFFF self.cpu.regs[7] = sp self.memory.write16(sp, value) def _pop16(self) -> int: sp = self.cpu.regs[7] & 0xFFFF value = self.memory.read16(sp) self.cpu.regs[7] = (sp + 2) & 0xFFFF return value def _push_register_mask(self, mask_value: int) -> None: for reg in range(8): if mask_value & (1 << reg): self._push16(self.cpu.regs[reg]) def _pop_register_mask(self, mask_value: int) -> None: for reg in reversed(range(8)): if mask_value & (1 << reg): self.cpu.regs[reg] = self._pop16() def _decode_ea(self, pc: int) -> dict[str, int | str | None]: first = self.memory.rom.u8(pc) if 0xA0 <= first <= 0xAF: return {"mode": "reg", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 1, "value": None} if 0xB0 <= first <= 0xBF: return {"mode": "predec", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 1, "value": None} if 0xC0 <= first <= 0xCF: return {"mode": "postinc", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 1, "value": None} if 0xD0 <= first <= 0xDF: return {"mode": "indirect", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 1, "value": None} if 0xE0 <= first <= 0xEF: return {"mode": "disp", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 2, "value": s8(self.memory.rom.u8(pc + 1))} if 0xF0 <= first <= 0xFF: return {"mode": "disp", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 3, "value": s16(self.memory.rom.u16(pc + 1))} if first in (0x04, 0x0C): size = 2 if first == 0x0C else 1 value = self.memory.rom.u16(pc + 1) if size == 2 else self.memory.rom.u8(pc + 1) return {"mode": "imm", "reg": None, "size": size, "length": 3 if size == 2 else 2, "value": value} if first in (0x15, 0x1D): return {"mode": "abs16", "reg": None, "size": 2 if first == 0x1D else 1, "length": 3, "value": self.memory.rom.u16(pc + 1)} raise UnsupportedInstruction(pc, self.memory.rom.slice(pc, 1), H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text) def _general_length(self, pc: int, ea: dict[str, int | str | None], op: int) -> int: length = int(ea["length"]) + 1 if op == 0x00: return length + 1 if op == 0x06: return length + 1 if op in (0x07, 0x05): return length + 2 if op == 0x04: return length + 1 return length def _ea_address(self, ea: dict[str, int | str | None], size: int, *, write: bool = False) -> int: mode = ea["mode"] reg = ea.get("reg") if mode == "abs16": return int(ea["value"]) & 0xFFFF if mode == "indirect": return self.cpu.regs[int(reg)] & 0xFFFF if mode == "disp": return (self.cpu.regs[int(reg)] + int(ea["value"])) & 0xFFFF if mode == "predec": self.cpu.regs[int(reg)] = (self.cpu.regs[int(reg)] - size) & 0xFFFF return self.cpu.regs[int(reg)] if mode == "postinc": address = self.cpu.regs[int(reg)] & 0xFFFF if not write: self.cpu.regs[int(reg)] = (self.cpu.regs[int(reg)] + size) & 0xFFFF return address raise EmulatorError(f"EA mode {mode} does not have an address") def _read_ea(self, ea: dict[str, int | str | None], size: int) -> int: mode = ea["mode"] if mode == "imm": return int(ea["value"]) & mask(size) if mode == "reg": return self._reg_read(int(ea["reg"]), size) address = self._ea_address(ea, size) return self.memory.read16(address) if size == 2 else self.memory.read8(address) def _write_ea(self, ea: dict[str, int | str | None], value: int, size: int) -> None: if ea["mode"] == "reg": self._reg_write(int(ea["reg"]), value, size) return address = self._ea_address(ea, size, write=True) if size == 2: self.memory.write16(address, value) else: self.memory.write8(address, value) def _reg_read(self, reg: int, size: int) -> int: value = self.cpu.regs[reg] & 0xFFFF return value if size == 2 else value & 0xFF def _reg_write(self, reg: int, value: int, size: int) -> None: if size == 2: self.cpu.regs[reg] = value & 0xFFFF else: self.cpu.regs[reg] = (self.cpu.regs[reg] & 0xFF00) | (value & 0xFF) def _cmp(self, lhs: int, rhs: int, size: int) -> None: self._set_sub_flags(lhs, rhs, lhs - rhs, size) def _set_logic_flags(self, value: int, size: int) -> None: value &= mask(size) self.cpu.z = value == 0 self.cpu.n = bool(value & sign_bit(size)) self.cpu.v = False def _set_add_flags(self, lhs: int, rhs: int, result: int, size: int) -> None: max_value = mask(size) sign = sign_bit(size) result &= max_value lhs &= max_value rhs &= max_value self.cpu.z = result == 0 self.cpu.n = bool(result & sign) self.cpu.c = lhs + rhs > max_value self.cpu.v = bool((~(lhs ^ rhs) & (lhs ^ result) & sign) != 0) def _set_sub_flags(self, lhs: int, rhs: int, result: int, size: int) -> None: max_value = mask(size) sign = sign_bit(size) result &= max_value lhs &= max_value rhs &= max_value self.cpu.z = result == 0 self.cpu.n = bool(result & sign) self.cpu.c = lhs < rhs self.cpu.v = bool(((lhs ^ rhs) & (lhs ^ result) & sign) != 0) def _bit_operation(self, ea: dict[str, int | str | None], size: int, op_base: int, bit: int) -> None: value = self._read_ea(ea, size) bit &= 15 if size == 2 else 7 bit_mask = 1 << bit self.cpu.z = not bool(value & bit_mask) if op_base == 0xC0: self._write_ea(ea, value | bit_mask, size) elif op_base == 0xD0: self._write_ea(ea, value & ~bit_mask, size) elif op_base == 0xE0: self._write_ea(ea, value ^ bit_mask, size) def _branch_condition(self, cond: int) -> bool: if cond == 0x0: return True if cond == 0x1: return False if cond == 0x2: return not self.cpu.c and not self.cpu.z if cond == 0x3: return self.cpu.c or self.cpu.z if cond == 0x4: return not self.cpu.c if cond == 0x5: return self.cpu.c if cond == 0x6: return not self.cpu.z if cond == 0x7: return self.cpu.z if cond == 0x8: return not self.cpu.v if cond == 0x9: return self.cpu.v if cond == 0xA: return not self.cpu.n if cond == 0xB: return self.cpu.n if cond == 0xC: return self.cpu.n == self.cpu.v if cond == 0xD: return self.cpu.n != self.cpu.v if cond == 0xE: return not self.cpu.z and self.cpu.n == self.cpu.v return self.cpu.z or self.cpu.n != self.cpu.v def _format_optional_ms(value: float | None) -> str: return "unavailable" if value is None else f"{value:.3f}"