718 lines
28 KiB
Python
718 lines
28 KiB
Python
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
|
|
|
|
|
|
@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,
|
|
) -> None:
|
|
if not rom_bytes:
|
|
raise ValueError("ROM image is empty")
|
|
self.sci1 = SCI1()
|
|
self.memory = MemoryMap(rom_bytes, self.sci1)
|
|
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
|
|
if self.p9_fast_path.try_handle(self):
|
|
self._tick_peripherals(self.cpu.cycles - cycles_before)
|
|
return f"{h16(pc)}: {'<p9-fast-path>':<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}"
|
|
|
|
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()
|
|
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}"
|