212 lines
8.1 KiB
Python
212 lines
8.1 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
from .cpu import mask, sign_bit
|
|
|
|
|
|
LOC_BFE0_TRANSFER_WRAPPER = 0xBFE0
|
|
LOC_BFFE_TRANSFER_WRAPPER = 0xBFFE
|
|
LOC_C08B_P9_WRITE_BYTE = 0xC08B
|
|
LOC_C0DB_P9_READ_BYTE = 0xC0DB
|
|
LOC_C10C_P9_MASTER_ACK = 0xC10C
|
|
LOC_C10C_P9_MARKER = 0xC10C
|
|
LOC_C121_P9_START = 0xC121
|
|
LOC_C121_P9_MARKER = LOC_C121_P9_START
|
|
LOC_C142_P9_STOP = 0xC142
|
|
LOC_C142_P9_MARKER = LOC_C142_P9_STOP
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class P9FastPathConfig:
|
|
"""Configuration for optional ROM P9 transfer shortcuts.
|
|
|
|
The helper assumes the CPU PC is exactly at a known routine entry. It feeds
|
|
those byte/marker/wrapper operations into the modeled P9/X24164 bus and
|
|
returns via RTS. Integration should keep this disabled unless the runner
|
|
intentionally opts into skipping these ROM routines.
|
|
"""
|
|
|
|
enabled: bool = False
|
|
write_byte_pc: int = LOC_C08B_P9_WRITE_BYTE
|
|
read_byte_pc: int = LOC_C0DB_P9_READ_BYTE
|
|
marker_pcs: frozenset[int] = frozenset(
|
|
{
|
|
LOC_C10C_P9_MARKER,
|
|
LOC_C121_P9_MARKER,
|
|
LOC_C142_P9_MARKER,
|
|
}
|
|
)
|
|
wrapper_pcs: frozenset[int] = frozenset(
|
|
{
|
|
LOC_BFE0_TRANSFER_WRAPPER,
|
|
LOC_BFFE_TRANSFER_WRAPPER,
|
|
}
|
|
)
|
|
default_input_byte: int = 0xFF
|
|
account_step: bool = True
|
|
cycles_per_hit: int = 0
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class P9FastPathEvent:
|
|
kind: str
|
|
pc: int
|
|
value: int | None = None
|
|
source: str | None = None
|
|
queue_depth: int | None = None
|
|
success: bool | None = None
|
|
|
|
def line(self) -> str:
|
|
value = "" if self.value is None else f" value={self.value:02X}"
|
|
success = "" if self.success is None else f" success={int(self.success)}"
|
|
source = "" if self.source is None else f" source={self.source}"
|
|
queue_depth = "" if self.queue_depth is None else f" queued={self.queue_depth}"
|
|
return f"{self.kind} pc={self.pc:04X}{value}{success}{source}{queue_depth}"
|
|
|
|
|
|
@dataclass
|
|
class P9FastPath:
|
|
"""Optional fast-path scaffold for ROM P9 bit-transfer routines."""
|
|
|
|
config: P9FastPathConfig = field(default_factory=P9FastPathConfig)
|
|
input_bytes: list[int] = field(default_factory=list)
|
|
input_sources: list[str] = field(default_factory=list)
|
|
output_bytes: list[int] = field(default_factory=list)
|
|
events: list[P9FastPathEvent] = field(default_factory=list)
|
|
|
|
def __post_init__(self) -> None:
|
|
if len(self.input_sources) < len(self.input_bytes):
|
|
self.input_sources.extend(["initial"] * (len(self.input_bytes) - len(self.input_sources)))
|
|
elif len(self.input_sources) > len(self.input_bytes):
|
|
del self.input_sources[len(self.input_bytes) :]
|
|
|
|
def queue_input(self, *values: int, source: str = "queued") -> None:
|
|
self.input_bytes.extend(value & 0xFF for value in values)
|
|
self.input_sources.extend(source for _ in values)
|
|
|
|
def queue_input_script(self, name: str, values: list[int] | tuple[int, ...]) -> None:
|
|
self.queue_input(*values, source=f"script:{name}")
|
|
|
|
def trace_lines(self, limit: int | None = None) -> list[str]:
|
|
events = self.events if limit is None else self.events[-limit:]
|
|
return [event.line() for event in events]
|
|
|
|
def try_handle(self, emulator: Any) -> bool:
|
|
if not self.config.enabled:
|
|
return False
|
|
|
|
pc = emulator.cpu.pc & 0xFFFF
|
|
if pc == (self.config.write_byte_pc & 0xFFFF):
|
|
self._handle_write_byte(emulator)
|
|
elif pc == (self.config.read_byte_pc & 0xFFFF):
|
|
self._handle_read_byte(emulator)
|
|
elif pc in self.config.marker_pcs:
|
|
self._handle_marker(emulator, pc)
|
|
elif pc in self.config.wrapper_pcs:
|
|
self._handle_wrapper(emulator)
|
|
else:
|
|
return False
|
|
|
|
if self.config.account_step:
|
|
emulator.cpu.steps += 1
|
|
emulator.cpu.cycles += self.config.cycles_per_hit
|
|
return True
|
|
|
|
def _handle_write_byte(self, emulator: Any) -> None:
|
|
pc = emulator.cpu.pc & 0xFFFF
|
|
value = emulator.cpu.regs[0] & 0xFF
|
|
self.output_bytes.append(value)
|
|
success = emulator.memory.p9_bus.fast_write_byte(value)
|
|
self.events.append(P9FastPathEvent("write_byte", pc, value, source="x24164", success=success))
|
|
|
|
result = 1 if success else 0
|
|
emulator.cpu.regs[0] = result
|
|
self._set_logic_flags(emulator.cpu, result, 1)
|
|
self._return_from_subroutine(emulator)
|
|
|
|
def _handle_read_byte(self, emulator: Any) -> None:
|
|
pc = emulator.cpu.pc & 0xFFFF
|
|
if self.input_bytes:
|
|
value = self.input_bytes.pop(0)
|
|
source = self.input_sources.pop(0) if self.input_sources else "queued"
|
|
elif (x24164_value := emulator.memory.p9_bus.fast_read_byte()) is not None:
|
|
value = x24164_value
|
|
source = "x24164"
|
|
else:
|
|
value = self.config.default_input_byte
|
|
source = "default_input_byte"
|
|
value &= 0xFF
|
|
self.events.append(P9FastPathEvent("read_byte", pc, value, source, len(self.input_bytes)))
|
|
|
|
# The ROM-side read routine yields a byte in R5. Model that as a byte
|
|
# register write so the existing high byte is not accidentally clobbered.
|
|
emulator.cpu.regs[5] = (emulator.cpu.regs[5] & 0xFF00) | value
|
|
self._set_logic_flags(emulator.cpu, value, 1)
|
|
self._return_from_subroutine(emulator)
|
|
|
|
def _handle_wrapper(self, emulator: Any) -> None:
|
|
pc = emulator.cpu.pc & 0xFFFF
|
|
success: bool
|
|
source: str
|
|
queue_depth: int | None
|
|
if pc == (LOC_BFE0_TRANSFER_WRAPPER & 0xFFFF):
|
|
address = emulator.cpu.regs[4] & 0x0FFF
|
|
value = emulator.cpu.regs[5] & 0xFFFF
|
|
write_success = emulator.memory.p9_bus.fast_write_word(address, value)
|
|
read_success, readback = emulator.memory.p9_bus.fast_read_word(address)
|
|
success = write_success and read_success and readback == value
|
|
source = "x24164_write_verify"
|
|
queue_depth = None
|
|
elif pc == (LOC_BFFE_TRANSFER_WRAPPER & 0xFFFF):
|
|
address = emulator.cpu.regs[4] & 0x0FFF
|
|
read_success, value = emulator.memory.p9_bus.fast_read_word(address)
|
|
if read_success:
|
|
emulator.cpu.regs[5] = value & 0xFFFF
|
|
success = read_success
|
|
source = "x24164_read_word"
|
|
queue_depth = None
|
|
else:
|
|
success, source, queue_depth = emulator.memory.p9_bus.consume_wrapper_result()
|
|
value = 1 if success else 0
|
|
self.events.append(
|
|
P9FastPathEvent(
|
|
"wrapper_success" if success else "wrapper_timeout",
|
|
pc,
|
|
value=value,
|
|
source=source,
|
|
queue_depth=queue_depth,
|
|
success=success,
|
|
)
|
|
)
|
|
|
|
emulator.cpu.regs[0] = value
|
|
self._set_logic_flags(emulator.cpu, value, 1)
|
|
self._return_from_subroutine(emulator)
|
|
|
|
def _return_from_subroutine(self, emulator: Any) -> None:
|
|
sp = emulator.cpu.regs[7] & 0xFFFF
|
|
emulator.cpu.pc = emulator.memory.read16(sp) & 0xFFFF
|
|
emulator.cpu.regs[7] = (sp + 2) & 0xFFFF
|
|
|
|
def _handle_marker(self, emulator: Any, pc: int) -> None:
|
|
if pc == (LOC_C121_P9_START & 0xFFFF):
|
|
emulator.memory.p9_bus.fast_start()
|
|
self.events.append(P9FastPathEvent("start", pc, source="x24164"))
|
|
elif pc == (LOC_C10C_P9_MASTER_ACK & 0xFFFF):
|
|
emulator.memory.p9_bus.fast_master_ack(True)
|
|
self.events.append(P9FastPathEvent("master_ack", pc, source="x24164"))
|
|
elif pc == (LOC_C142_P9_STOP & 0xFFFF):
|
|
emulator.memory.p9_bus.fast_stop()
|
|
self.events.append(P9FastPathEvent("stop", pc, source="x24164"))
|
|
else:
|
|
self.events.append(P9FastPathEvent("marker", pc))
|
|
self._return_from_subroutine(emulator)
|
|
|
|
def _set_logic_flags(self, cpu: Any, value: int, size: int) -> None:
|
|
value &= mask(size)
|
|
cpu.z = value == 0
|
|
cpu.n = bool(value & sign_bit(size))
|
|
cpu.v = False
|