"""PTIMER block (modeled virtual time; not a silicon timing claim). Evidence kind: **hardware register/field header** (offsets and field layout) plus a *modeled* time source. The firmware has not yet been observed reading this block in the 570.144 boot runs (the observed time-like reads were on falcon MAILBOX1); the device is therefore opt-in: it is registered only when a profile declares a ``timer`` section, and a section with ``"enabled": false`` is skipped. Its values are explicitly labelled *modeled*. Header facts used: * ``NV_PTIMER`` block base ``0x9000`` in the register aperture. * ``NV_PTIMER_TIME_0`` at aperture offset ``0x9400``: nanoseconds bits ``31:5``. * ``NV_PTIMER_TIME_1`` at aperture offset ``0x9410``: nanoseconds bits ``28:0``. * Reads of TIME_0 latch TIME_1 (paired read), as on the hardware register pair; the simulator derives nanoseconds from the machine's instruction count times the profile's ``ns_per_insn`` (default 1.0, i.e. 1 ns/insn -- a placeholder, not calibrated). Register offsets below are **relative to the block window base** the profile declares (``PRIV + 0x9000``), i.e. aperture offset minus ``PTIMER_BASE``: ``0x9400 - 0x9000 = 0x400``. Using the aperture-absolute numbers here would place the registers outside the declared window and serve nothing. """ from __future__ import annotations from .base import Device PTIMER_BASE = 0x9000 #: Block-relative offsets of NV_PTIMER_TIME_0/1 (aperture 0x9400/0x9410). TIME_0 = 0x400 TIME_1 = 0x410 class PtimerBlock(Device): """Two-register nanosecond timer with a modeled tick source.""" name = "timer" def __init__(self, *, base: int = 0, size: int = 0x1000, ns_per_insn: float = 1.0, clock=None): super().__init__(base=base, size=size) self.ns_per_insn = float(ns_per_insn) self.clock = clock # callable returning virtual instruction count self._latch = 0 self.reads = 0 def _now_ns(self) -> int: insns = self.clock() if self.clock is not None else 0 return int(insns * self.ns_per_insn) & 0xFFFFFFFFFFFFFFFF def read(self, offset: int, size: int) -> int | None: if size != 4: return None if offset == TIME_0: self._latch = self._now_ns() self.reads += 1 return (self._latch >> 5) & 0xFFFFFFFF # NSEC field 31:5 if offset == TIME_1: self.reads += 1 return self._latch & 0x1FFFFFFF # NSEC field 28:0 return None def peek(self, offset: int, size: int) -> int | None: if size != 4: return None if offset == TIME_0: return (self._now_ns() >> 5) & 0xFFFFFFFF if offset == TIME_1: return self._now_ns() & 0x1FFFFFFF return None def write(self, offset: int, size: int, value: int) -> bool: return False def state(self) -> dict: return {"ns_per_insn": self.ns_per_insn, "latch": self._latch, "reads": self.reads} def load_state(self, data: dict) -> None: self.ns_per_insn = float(data.get("ns_per_insn", self.ns_per_insn)) self._latch = int(data.get("latch", 0)) self.reads = int(data.get("reads", 0))