timing adjustments
This commit is contained in:
@@ -4,9 +4,18 @@ from .cli import build_arg_parser, discover_rom_path, load_rom, main
|
||||
from .constants import (
|
||||
HEARTBEAT_FRAME,
|
||||
FRT_TCR_OCIEA,
|
||||
FRT_TCSR_CCLRA,
|
||||
FRT_TCSR_OCFA,
|
||||
FRT1_FRC_H,
|
||||
FRT1_FRC_L,
|
||||
FRT1_OCRA_H,
|
||||
FRT1_OCRA_L,
|
||||
FRT1_TCR,
|
||||
FRT1_TCSR,
|
||||
FRT2_FRC_H,
|
||||
FRT2_FRC_L,
|
||||
FRT2_OCRA_H,
|
||||
FRT2_OCRA_L,
|
||||
FRT2_TCR,
|
||||
FRT2_TCSR,
|
||||
IPRA,
|
||||
@@ -55,9 +64,18 @@ __all__ = [
|
||||
"EmulatorError",
|
||||
"FRT1_TCR",
|
||||
"FRT1_TCSR",
|
||||
"FRT1_FRC_H",
|
||||
"FRT1_FRC_L",
|
||||
"FRT1_OCRA_H",
|
||||
"FRT1_OCRA_L",
|
||||
"FRT2_TCR",
|
||||
"FRT2_TCSR",
|
||||
"FRT2_FRC_H",
|
||||
"FRT2_FRC_L",
|
||||
"FRT2_OCRA_H",
|
||||
"FRT2_OCRA_L",
|
||||
"FRT_TCR_OCIEA",
|
||||
"FRT_TCSR_CCLRA",
|
||||
"FRT_TCSR_OCFA",
|
||||
"HEARTBEAT_FRAME",
|
||||
"H8536Emulator",
|
||||
|
||||
@@ -166,11 +166,11 @@ class BenchReplayResult:
|
||||
class ReplayConfig:
|
||||
boot_steps: int = 250_000
|
||||
per_byte_steps: int = 5_000
|
||||
steps_per_second: int = 65_000
|
||||
post_log_steps: int = 50_000
|
||||
interval_steps: int = 512
|
||||
frt1_ocia_steps: int = 512
|
||||
frt2_ocia_steps: int = 512
|
||||
frt1_ocia_steps: int | None = None
|
||||
frt2_ocia_steps: int | None = None
|
||||
clock_hz: int = 10_000_000
|
||||
p9_fast_path: bool = True
|
||||
p9_fast_input: int = 0xFF
|
||||
|
||||
@@ -232,6 +232,7 @@ def run_bench_replay(log_path: Path, *, rom_path: Path | None = None, config: Re
|
||||
interval_steps=config.interval_steps,
|
||||
frt1_ocia_steps=config.frt1_ocia_steps,
|
||||
frt2_ocia_steps=config.frt2_ocia_steps,
|
||||
clock_hz=config.clock_hz,
|
||||
p9_fast_path_enabled=config.p9_fast_path,
|
||||
p9_fast_default_input_byte=config.p9_fast_input,
|
||||
)
|
||||
@@ -243,6 +244,7 @@ def run_bench_replay(log_path: Path, *, rom_path: Path | None = None, config: Re
|
||||
f"SCR={emulator.sci1.scr:02X} SSR={emulator.sci1.ssr:02X} "
|
||||
f"rx_serviceable={int(_rx_ready(emulator))} "
|
||||
f"sci1_priority={_sci1_priority(emulator)} interrupt_mask={_interrupt_mask(emulator)} "
|
||||
f"clock_hz={emulator.clock_hz} "
|
||||
f"lcd_display={emulator.memory.lcd.display_text(lines=4)!r}"
|
||||
)
|
||||
|
||||
@@ -251,7 +253,7 @@ def run_bench_replay(log_path: Path, *, rom_path: Path | None = None, config: Re
|
||||
for host in bench_log.tx_frames:
|
||||
delta_ms = 0 if previous_tx_ms is None else max(0, host.timestamp_ms - previous_tx_ms)
|
||||
tx_frame_start_before_delay = len(emulator.sci1.tx_frames)
|
||||
steps_before = _run_steps_for_ms(emulator, delta_ms, config.steps_per_second, context)
|
||||
steps_before = _run_cycles_for_ms(emulator, delta_ms, config.clock_hz, context)
|
||||
gap_frames = tuple(emulator.sci1.tx_frames[tx_frame_start_before_delay:])
|
||||
tx_frame_start = len(emulator.sci1.tx_frames)
|
||||
steps_during_rx = _inject_host_frame(emulator, host.frame, config.per_byte_steps, context)
|
||||
@@ -335,9 +337,9 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--rom", type=Path, help="ROM image path; defaults to ROM/M27C512@DIP28_1.BIN")
|
||||
parser.add_argument("--boot-steps", type=int, default=ReplayConfig.boot_steps)
|
||||
parser.add_argument("--per-byte-steps", type=int, default=ReplayConfig.per_byte_steps)
|
||||
parser.add_argument("--steps-per-second", type=int, default=ReplayConfig.steps_per_second)
|
||||
parser.add_argument("--post-log-steps", type=int, default=ReplayConfig.post_log_steps)
|
||||
parser.add_argument("--interval-steps", type=int, default=ReplayConfig.interval_steps)
|
||||
parser.add_argument("--clock-hz", type=lambda text: int(text, 0), default=ReplayConfig.clock_hz)
|
||||
parser.add_argument("--frt1-ocia-steps", type=int, default=ReplayConfig.frt1_ocia_steps)
|
||||
parser.add_argument("--frt2-ocia-steps", type=int, default=ReplayConfig.frt2_ocia_steps)
|
||||
parser.add_argument("--no-p9-fast-path", action="store_true", help="disable shortcut handling for known P9 routines")
|
||||
@@ -355,11 +357,11 @@ def main(argv: list[str] | None = None) -> int:
|
||||
config=ReplayConfig(
|
||||
boot_steps=args.boot_steps,
|
||||
per_byte_steps=args.per_byte_steps,
|
||||
steps_per_second=args.steps_per_second,
|
||||
post_log_steps=args.post_log_steps,
|
||||
interval_steps=args.interval_steps,
|
||||
frt1_ocia_steps=args.frt1_ocia_steps,
|
||||
frt2_ocia_steps=args.frt2_ocia_steps,
|
||||
clock_hz=args.clock_hz,
|
||||
p9_fast_path=not args.no_p9_fast_path,
|
||||
p9_fast_input=args.p9_fast_input,
|
||||
),
|
||||
@@ -383,9 +385,19 @@ def _inject_host_frame(emulator: H8536Emulator, frame: bytes, per_byte_steps: in
|
||||
return steps_total
|
||||
|
||||
|
||||
def _run_steps_for_ms(emulator: H8536Emulator, delta_ms: int, steps_per_second: int, context: RunContext) -> int:
|
||||
steps = int((max(0, delta_ms) * max(1, steps_per_second)) / 1000)
|
||||
return _run_steps(emulator, steps, context)
|
||||
def _run_cycles_for_ms(emulator: H8536Emulator, delta_ms: int, clock_hz: int, context: RunContext) -> int:
|
||||
target_delta_cycles = int((max(0, delta_ms) * max(1, clock_hz)) / 1000)
|
||||
target_cycles = emulator.cpu.cycles + target_delta_cycles
|
||||
completed = 0
|
||||
while emulator.cpu.cycles < target_cycles:
|
||||
context.record_pc(emulator.cpu.pc)
|
||||
try:
|
||||
emulator.step()
|
||||
except UnsupportedInstruction as exc:
|
||||
context.unsupported = str(exc)
|
||||
break
|
||||
completed += 1
|
||||
return completed
|
||||
|
||||
|
||||
def _run_steps(emulator: H8536Emulator, steps: int, context: RunContext) -> int:
|
||||
|
||||
@@ -39,8 +39,9 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--stop-on-heartbeat", action="store_true", help="stop only when 00 00 00 00 80 DA is emitted through SCI1 TDR")
|
||||
parser.add_argument("--memory-map", action="store_true", help="print the scaffolded memory map before running")
|
||||
parser.add_argument("--interval-steps", type=int, default=2048, help="rough step period for the scaffolded timer interrupt")
|
||||
parser.add_argument("--frt1-ocia-steps", type=int, default=1024, help="rough step period for the scaffolded FRT1 OCIA interrupt")
|
||||
parser.add_argument("--frt2-ocia-steps", type=int, default=1024, help="rough step period for the scaffolded FRT2 OCIA interrupt")
|
||||
parser.add_argument("--clock-hz", type=parse_int, default=10_000_000, help="CPU/phi clock in Hz for calibrated FRT timing")
|
||||
parser.add_argument("--frt1-ocia-steps", type=int, default=None, help="legacy step-period override for FRT1 OCIA")
|
||||
parser.add_argument("--frt2-ocia-steps", type=int, default=None, help="legacy step-period override for FRT2 OCIA")
|
||||
parser.add_argument("--p9-fast-path", action="store_true", help="shortcut known P9 bit-banged transfer routines for exploration")
|
||||
parser.add_argument("--p9-fast-input", type=parse_int, default=0xFF, help="default byte returned by the P9 fast-path read routine")
|
||||
return parser
|
||||
@@ -59,6 +60,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
interval_steps=args.interval_steps,
|
||||
frt1_ocia_steps=args.frt1_ocia_steps,
|
||||
frt2_ocia_steps=args.frt2_ocia_steps,
|
||||
clock_hz=args.clock_hz,
|
||||
p9_fast_path_enabled=args.p9_fast_path,
|
||||
p9_fast_default_input_byte=args.p9_fast_input,
|
||||
)
|
||||
|
||||
@@ -18,8 +18,16 @@ WDT_TCSR_R = 0xFEEC
|
||||
|
||||
FRT1_TCR = 0xFE90
|
||||
FRT1_TCSR = 0xFE91
|
||||
FRT1_FRC_H = 0xFE92
|
||||
FRT1_FRC_L = 0xFE93
|
||||
FRT1_OCRA_H = 0xFE94
|
||||
FRT1_OCRA_L = 0xFE95
|
||||
FRT2_TCR = 0xFEA0
|
||||
FRT2_TCSR = 0xFEA1
|
||||
FRT2_FRC_H = 0xFEA2
|
||||
FRT2_FRC_L = 0xFEA3
|
||||
FRT2_OCRA_H = 0xFEA4
|
||||
FRT2_OCRA_L = 0xFEA5
|
||||
|
||||
SCI_SCR_TIE = 0x80
|
||||
SCI_SCR_RIE = 0x40
|
||||
@@ -32,6 +40,7 @@ SCI_SSR_FER = 0x10
|
||||
SCI_SSR_PER = 0x08
|
||||
FRT_TCR_OCIEA = 0x20
|
||||
FRT_TCSR_OCFA = 0x20
|
||||
FRT_TCSR_CCLRA = 0x01
|
||||
|
||||
ON_CHIP_RAM_START = 0xF680
|
||||
ON_CHIP_RAM_END = 0xFE7F
|
||||
|
||||
@@ -125,6 +125,21 @@ class MemoryMap:
|
||||
self._set_register(SCI1_RDR, self.sci1.read(SCI1_RDR))
|
||||
self._set_register(SCI1_SSR, self.sci1.read(SCI1_SSR))
|
||||
|
||||
def register8(self, address: int) -> int:
|
||||
return self.registers[(address & 0xFFFF) - REGISTER_FIELD_START]
|
||||
|
||||
def register16(self, address: int) -> int:
|
||||
address &= 0xFFFF
|
||||
return (self.register8(address) << 8) | self.register8((address + 1) & 0xFFFF)
|
||||
|
||||
def set_register8(self, address: int, value: int) -> None:
|
||||
self._set_register(address & 0xFFFF, value)
|
||||
|
||||
def set_register16(self, address: int, value: int) -> None:
|
||||
address &= 0xFFFF
|
||||
self._set_register(address, (value >> 8) & 0xFF)
|
||||
self._set_register((address + 1) & 0xFFFF, value & 0xFF)
|
||||
|
||||
def _set_register(self, address: int, value: int) -> None:
|
||||
self.registers[address - REGISTER_FIELD_START] = value & 0xFF
|
||||
|
||||
|
||||
@@ -900,8 +900,9 @@ def run_probe(
|
||||
interval_steps: int,
|
||||
stop_on_tx: bool,
|
||||
p9_log_limit: int,
|
||||
frt1_ocia_steps: int = 1024,
|
||||
frt2_ocia_steps: int = 1024,
|
||||
frt1_ocia_steps: int | None = None,
|
||||
frt2_ocia_steps: int | None = None,
|
||||
clock_hz: int = 10_000_000,
|
||||
p9_fast_path: bool = False,
|
||||
p9_fast_input: int = 0xFF,
|
||||
sci_log_limit: int = 32,
|
||||
@@ -929,6 +930,7 @@ def run_probe(
|
||||
interval_steps=interval_steps,
|
||||
frt1_ocia_steps=frt1_ocia_steps,
|
||||
frt2_ocia_steps=frt2_ocia_steps,
|
||||
clock_hz=clock_hz,
|
||||
p9_fast_path_enabled=p9_fast_path,
|
||||
p9_fast_default_input_byte=p9_fast_input,
|
||||
)
|
||||
@@ -1150,8 +1152,9 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--rom", type=Path, help="ROM image path; defaults to the repo ROM image")
|
||||
parser.add_argument("--max-steps", type=int, default=250_000)
|
||||
parser.add_argument("--interval-steps", type=int, default=512)
|
||||
parser.add_argument("--frt1-ocia-steps", type=int, default=1024)
|
||||
parser.add_argument("--frt2-ocia-steps", type=int, default=1024)
|
||||
parser.add_argument("--clock-hz", type=parse_int, default=10_000_000)
|
||||
parser.add_argument("--frt1-ocia-steps", type=int, default=None, help="legacy step-period override for FRT1 OCIA")
|
||||
parser.add_argument("--frt2-ocia-steps", type=int, default=None, help="legacy step-period override for FRT2 OCIA")
|
||||
parser.add_argument("--stop-on-tx", action="store_true", help="stop when SCI1 TDR emits the first byte")
|
||||
parser.add_argument("--p9-fast-path", action="store_true", help="shortcut known P9 bit-banged transfer routines for exploration")
|
||||
parser.add_argument("--p9-fast-input", type=parse_int, default=0xFF)
|
||||
@@ -1236,6 +1239,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
interval_steps=args.interval_steps,
|
||||
frt1_ocia_steps=args.frt1_ocia_steps,
|
||||
frt2_ocia_steps=args.frt2_ocia_steps,
|
||||
clock_hz=args.clock_hz,
|
||||
stop_on_tx=args.stop_on_tx,
|
||||
p9_log_limit=args.p9_log_limit,
|
||||
p9_fast_path=args.p9_fast_path,
|
||||
|
||||
@@ -9,8 +9,12 @@ 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,
|
||||
@@ -36,6 +40,7 @@ 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
|
||||
@@ -47,6 +52,9 @@ class RunReport:
|
||||
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)
|
||||
|
||||
@@ -56,6 +64,9 @@ class RunReport:
|
||||
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}",
|
||||
@@ -74,8 +85,9 @@ class H8536Emulator:
|
||||
rom_bytes: bytes,
|
||||
*,
|
||||
interval_steps: int = 2048,
|
||||
frt1_ocia_steps: int = 1024,
|
||||
frt2_ocia_steps: int = 1024,
|
||||
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,
|
||||
@@ -90,8 +102,11 @@ class H8536Emulator:
|
||||
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)
|
||||
self.frt2_ocia_steps = max(1, frt2_ocia_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
|
||||
@@ -110,8 +125,9 @@ class H8536Emulator:
|
||||
|
||||
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._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)
|
||||
@@ -166,8 +182,9 @@ class H8536Emulator:
|
||||
|
||||
self.cpu.pc = next_pc
|
||||
self.cpu.steps += 1
|
||||
self.cpu.cycles += self._rough_cycles(raw)
|
||||
self._tick_peripherals()
|
||||
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:
|
||||
@@ -194,6 +211,9 @@ class H8536Emulator:
|
||||
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,
|
||||
)
|
||||
@@ -405,11 +425,17 @@ class H8536Emulator:
|
||||
return (pc + 3 + s8(raw[2])) & 0xFFFF
|
||||
return next_pc
|
||||
|
||||
def _tick_peripherals(self) -> None:
|
||||
def _tick_peripherals(self, cycle_delta: int) -> None:
|
||||
self.sci1.tick()
|
||||
self._interval_counter += 1
|
||||
self._frt1_ocia_counter += 1
|
||||
self._frt2_ocia_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:
|
||||
@@ -450,11 +476,13 @@ class H8536Emulator:
|
||||
if source == "interval_timer":
|
||||
self._interval_counter = 0
|
||||
elif source == "frt1_ocia":
|
||||
self._frt1_ocia_counter = 0
|
||||
self.memory.write8(FRT1_TCSR, self.memory.read8(FRT1_TCSR) | FRT_TCSR_OCFA)
|
||||
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":
|
||||
self._frt2_ocia_counter = 0
|
||||
self.memory.write8(FRT2_TCSR, self.memory.read8(FRT2_TCSR) | FRT_TCSR_OCFA)
|
||||
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:
|
||||
@@ -484,6 +512,8 @@ class H8536Emulator:
|
||||
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)
|
||||
@@ -492,6 +522,8 @@ class H8536Emulator:
|
||||
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)
|
||||
@@ -679,3 +711,7 @@ class H8536Emulator:
|
||||
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}"
|
||||
|
||||
@@ -202,8 +202,9 @@ def run_rx_probe(
|
||||
per_byte_steps: int = 5_000,
|
||||
post_frame_steps: int = 80_000,
|
||||
interval_steps: int = 512,
|
||||
frt1_ocia_steps: int = 512,
|
||||
frt2_ocia_steps: int = 512,
|
||||
frt1_ocia_steps: int | None = None,
|
||||
frt2_ocia_steps: int | None = None,
|
||||
clock_hz: int = 10_000_000,
|
||||
p9_fast_path: bool = True,
|
||||
p9_fast_input: int = 0xFF,
|
||||
stop_after_tx_frame: bool = True,
|
||||
@@ -214,6 +215,7 @@ def run_rx_probe(
|
||||
interval_steps=interval_steps,
|
||||
frt1_ocia_steps=frt1_ocia_steps,
|
||||
frt2_ocia_steps=frt2_ocia_steps,
|
||||
clock_hz=clock_hz,
|
||||
p9_fast_path_enabled=p9_fast_path,
|
||||
p9_fast_default_input_byte=p9_fast_input,
|
||||
)
|
||||
@@ -224,6 +226,7 @@ def run_rx_probe(
|
||||
f"boot={boot_reason} steps={boot_steps_used} pc={h16(emulator.cpu.pc)} "
|
||||
f"SCR={emulator.sci1.scr:02X} SSR={emulator.sci1.ssr:02X} "
|
||||
f"rx_serviceable={int(_rx_ready(emulator))} "
|
||||
f"clock_hz={emulator.clock_hz} "
|
||||
f"lcd_display={emulator.memory.lcd.display_text(lines=4)!r}"
|
||||
)
|
||||
|
||||
@@ -250,8 +253,9 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--post-frame-steps", type=int, default=80_000, help="maximum steps after a full injected frame")
|
||||
parser.add_argument("--keep-listening", action="store_true", help="use all post-frame steps instead of stopping at the first new TX frame")
|
||||
parser.add_argument("--interval-steps", type=int, default=512, help="rough step period for the scaffolded interval timer interrupt")
|
||||
parser.add_argument("--frt1-ocia-steps", type=int, default=512, help="rough step period for the scaffolded FRT1 OCIA interrupt")
|
||||
parser.add_argument("--frt2-ocia-steps", type=int, default=512, help="rough step period for the scaffolded FRT2 OCIA interrupt")
|
||||
parser.add_argument("--clock-hz", type=parse_int, default=10_000_000, help="CPU/phi clock in Hz for calibrated FRT timing")
|
||||
parser.add_argument("--frt1-ocia-steps", type=int, default=None, help="legacy step-period override for FRT1 OCIA")
|
||||
parser.add_argument("--frt2-ocia-steps", type=int, default=None, help="legacy step-period override for FRT2 OCIA")
|
||||
parser.add_argument("--no-p9-fast-path", action="store_true", help="disable shortcut handling for known P9 routines")
|
||||
parser.add_argument("--p9-fast-input", type=parse_int, default=0xFF, help="default byte returned by the P9 fast-path read routine")
|
||||
return parser
|
||||
@@ -274,6 +278,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
interval_steps=args.interval_steps,
|
||||
frt1_ocia_steps=args.frt1_ocia_steps,
|
||||
frt2_ocia_steps=args.frt2_ocia_steps,
|
||||
clock_hz=args.clock_hz,
|
||||
p9_fast_path=not args.no_p9_fast_path,
|
||||
p9_fast_input=args.p9_fast_input,
|
||||
stop_after_tx_frame=not args.keep_listening,
|
||||
|
||||
109
h8536/emulator/timers.py
Normal file
109
h8536/emulator/timers.py
Normal file
@@ -0,0 +1,109 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .constants import FRT_TCR_OCIEA, FRT_TCSR_CCLRA, FRT_TCSR_OCFA
|
||||
from .memory import MemoryMap
|
||||
|
||||
|
||||
FRT_INTERNAL_PRESCALERS = {
|
||||
0b00: 4,
|
||||
0b01: 8,
|
||||
0b10: 32,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FrtRegisters:
|
||||
name: str
|
||||
tcr: int
|
||||
tcsr: int
|
||||
frc_h: int
|
||||
ocra_h: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class FrtOciaScheduler:
|
||||
registers: FrtRegisters
|
||||
cycle_accumulator: int = 0
|
||||
last_config: tuple[int, int] | None = None
|
||||
compare_matches: int = 0
|
||||
|
||||
def tick(self, memory: MemoryMap, cycle_delta: int) -> None:
|
||||
cycle_delta = max(0, cycle_delta)
|
||||
tcr = memory.register8(self.registers.tcr)
|
||||
prescaler = frt_internal_prescaler(tcr)
|
||||
if prescaler is None:
|
||||
self.cycle_accumulator = 0
|
||||
self.last_config = None
|
||||
return
|
||||
|
||||
config = self._config(memory)
|
||||
if config != self.last_config:
|
||||
self.cycle_accumulator = 0
|
||||
self.last_config = config
|
||||
|
||||
self.cycle_accumulator += cycle_delta
|
||||
ticks = self.cycle_accumulator // prescaler
|
||||
self.cycle_accumulator %= prescaler
|
||||
if ticks <= 0:
|
||||
return
|
||||
|
||||
frc = memory.register16(self.registers.frc_h)
|
||||
ocra = memory.register16(self.registers.ocra_h)
|
||||
tcsr = memory.register8(self.registers.tcsr)
|
||||
clear_on_a = bool(tcsr & FRT_TCSR_CCLRA)
|
||||
matched = False
|
||||
|
||||
for _ in range(ticks):
|
||||
frc = (frc + 1) & 0xFFFF
|
||||
if frc == ocra:
|
||||
matched = True
|
||||
self.compare_matches += 1
|
||||
if clear_on_a:
|
||||
frc = 0
|
||||
|
||||
memory.set_register16(self.registers.frc_h, frc)
|
||||
if matched:
|
||||
memory.set_register8(self.registers.tcsr, tcsr | FRT_TCSR_OCFA)
|
||||
|
||||
def period_cycles(self, memory: MemoryMap) -> int | None:
|
||||
tcr = memory.register8(self.registers.tcr)
|
||||
prescaler = frt_internal_prescaler(tcr)
|
||||
if prescaler is None:
|
||||
return None
|
||||
ocra = memory.register16(self.registers.ocra_h)
|
||||
return frt_ocia_period_cycles(tcr, ocra)
|
||||
|
||||
def pending(self, memory: MemoryMap) -> bool:
|
||||
return bool(memory.register8(self.registers.tcr) & FRT_TCR_OCIEA and memory.register8(self.registers.tcsr) & FRT_TCSR_OCFA)
|
||||
|
||||
def period_ms(self, memory: MemoryMap, clock_hz: int) -> float | None:
|
||||
period = self.period_cycles(memory)
|
||||
if period is None or clock_hz <= 0:
|
||||
return None
|
||||
return 1000.0 * period / clock_hz
|
||||
|
||||
def _config(self, memory: MemoryMap) -> tuple[int, int]:
|
||||
return memory.register8(self.registers.tcr) & 0x03, memory.register16(self.registers.ocra_h)
|
||||
|
||||
|
||||
def frt_internal_prescaler(tcr: int) -> int | None:
|
||||
return FRT_INTERNAL_PRESCALERS.get(tcr & 0x03)
|
||||
|
||||
|
||||
def frt_ocia_period_cycles(tcr: int, ocra: int) -> int | None:
|
||||
prescaler = frt_internal_prescaler(tcr)
|
||||
if prescaler is None:
|
||||
return None
|
||||
compare_ticks = (ocra & 0xFFFF) or 0x10000
|
||||
return compare_ticks * prescaler
|
||||
|
||||
|
||||
__all__ = [
|
||||
"FRT_INTERNAL_PRESCALERS",
|
||||
"FrtOciaScheduler",
|
||||
"FrtRegisters",
|
||||
"frt_internal_prescaler",
|
||||
"frt_ocia_period_cycles",
|
||||
]
|
||||
Reference in New Issue
Block a user