# 32 — GSP cycle model (increment 0, in development) > **Status: in development (Phase 1.5, work package WP1.5.1 increment 0).** > This page describes a table-driven **modeled** cycle count added to the > native reference core (`sim/`) and a bounded, deterministic "cycle-0" > profile of the real 570.144 GSP firmware. It makes **no silicon timing, > performance, latency or accuracy claim**: the numbers are modeled defaults > under an explicit table, not measurements. The simulator's fidelity > statement is unchanged — L0 (instruction semantics) and L1 (memory > behaviour) are modelled; **L2 (time/concurrency) is not** (`06-ACCURACY.md`). > Development-only: nothing here authorises GPU hardware, kernel modules, > mdev or guest access. ## 1. Purpose The native simulator reports virtual time as an instruction count (`insns`). That is enough for functional debugging but not for comparing instruction mixes, for calibration-table work, or for a first, honest cycle-shaped artifact from a real firmware run. Increment 0 adds: * `sim/cycles.py` — an isolated, per-instruction-class latency table with a deterministic cost function; * a `cycles` counter on `sim.machine.Machine`, advanced by that table alongside the existing instruction count; * `sim/gsp_profile.py` — a bounded, read-only run of the real `firmware/57X-GSP/570.144/gsp-570.144.fwimage.bin` that records a normal `greeneye-session/1` directory with the modeled cycle totals; * `tests/test_sim_cycles.py`. Everything is stdlib-only Python 3.10+, offline, and read-only with respect to the firmware image. ## 2. The model `sim/cycles.CycleModel` is a plain Python table. All defaults are **modeled** from public, generic in-order RISC-V pipeline assumptions — one cycle for plain ALU work, conservative multi-cycle M-extension costs, a fixed extra latency for a completed memory access — and are deliberately *not* derived from the GSP core, from silicon or from vendor data. | Class | Default | Notes | |---|---|---| | ALU / shift / `lui` / `auipc` / op-imm | 1 | add, sub, logical, compare | | branch | 1 | taken/not-taken penalty deliberately not modelled | | `jal` / `jalr` | 1 | | | `mul` / `mulh` / `mulhsu` / `mulhu` | 3 | conservative | | `div` / `divu` / `rem` / `remu` | 20 | conservative, data-independent | | load / store | 1 | plus a completed-access latency (below) | | AMO (`lr`/`sc`/`amo*`) | 2 | plus one completed-access latency | | CSR | 2 | | | trap (`ecall`/`ebreak`/`mret`) | 3 | | | `fence` / `fence.i` | 1 | no ordering model (single hart) | | illegal | 1 | execution stops immediately | Memory latency is a **parameter**, not a cache model: | Parameter | Default | Meaning | |---|---|---| | `memory_latency` | 3 | extra cycles when a RAM access completes | | `mmio_latency` | 12 | extra cycles when an unmodelled-MMIO access completes | The cost of one instruction is `instruction_cost(kind, sub)`; a completed load/store/AMO adds `memory_cost(is_mmio)` on top. Cost depends only on the decoded instruction class (`sim/decode.py` kind/sub) and the access region: no host clock, no entropy, no caches, no hazards. `CycleModel.describe()` emits the full table for session metadata. ## 3. Machine integration `sim.machine.Machine` keeps its previous behaviour exactly; cycles are additive: ```python from sim.machine import Machine m = Machine() # DEFAULT_MODEL active m.run(10_000) m.insns # virtual time (unchanged semantics) m.cycles # modeled cycles, advanced by the table m.cycle_model.name # "rv64imac-modeled/1" off = Machine(cycle_model=False) # cycles stays 0; disabling path for A/B slow = Machine(cycle_model=CycleModel(memory_latency=9)) ``` `greeneye.sim_backend.launch_native(..., cycle_model=...)` threads the choice through the Session API (`None` = default table, `False` = off, or an instance). The counter increments for the instruction the machine stops on under a watchpoint/breakpoint, mirroring how `insns` counts it; the memory latency is only added when the access actually completes. Determinism is by construction: two identical runs produce identical instruction and cycle totals (tested). Measured overhead on this host (CPython 3.12, 200k instructions of the real image, best of three): ~0.073 s with accounting disabled vs ~0.082 s enabled (~12%); disabled runs are byte-for-byte the pre-increment behaviour. ## 4. The cycle-0 GSP profile ```bash # bounded, deterministic, read-only; writes a session under dumps/ python3 -m sim.gsp_profile --instructions 200000 # explicit session directory and machine-readable result python3 -m sim.gsp_profile --record dumps/gsp-cycle0 --json # start from a harvested first-touch map and keep what the run learned python3 -m sim.gsp_profile --device-map dumps//device-map.json --learn --learn-out /tmp/map.json # image absent (e.g. LFS not fetched): exits 2 with a skip reason, no session python3 -m sim.gsp_profile --image /nonexistent.bin ``` The profile mirrors the established device-map/probe path: the ELF's PT_LOAD plan is relocated by `bias=0x80000000` (`greeneye/elf.py`), RAM is sized to cover the image as `greeneye/device_map.py` computes it, and all unmodelled accesses are served leniently (zero or the mapped value) through a `greeneye-device-map/1`. The instruction budget is exact, not wall-clock: `Machine.run(budget)` is called directly, so the run is deterministic. Session metadata (`session.json`) and a `gsp_cycle_profile` event carry: `firmware_sha256`, `bias`, `instructions_executed`, `cycles`, `cycle_model`, `cycle_model_table`, `cycle_fidelity`, `stop_reason`/`stop_pc` (both relocated and firmware-space), `mmio_unknown` and the first unknown MMIO access. **Observed output** (this tree, 2026-10-03; sha256 `2d1bb5c6…c3ceea`, budget 200 000): ``` instructions: 200000 cycles (modeled): 200012 stop: budget pc=0x84009070 (fw 0x4009070) unknown MMIO accesses: 1 first unknown MMIO: pc=0x84009014 addr=0x2000000000000a00 size=4 rw=r ``` This is a bounded lenient first-touch profile, **not a boot**: it reproduces the documented QEMU probe first-touch address and continues under the modeled lenient policy for the budget. The stop PC is a budget sample inside the firmware's early path, not a milestone. ## 5. What is not claimed * No silicon timing, latency, throughput, frequency or pipeline claim; the table is a modeled default and says so in code, metadata and reports (`sim/cycles.py::NOTE`, `CYCLE_FIDELITY`). * No cache/memory-hierarchy model: `memory_latency`/`mmio_latency` are fixed parameters, not simulated caches or DRAM. * No device model: L3 remains absent (`06-ACCURACY.md`); the profile runs under lenient MMIO, so its control flow after the first unknown access is conditioned on that policy, not on device behaviour. * No accuracy metric may be computed from this increment alone. Calibration (`RISC-V-GSP-RM-DEBUGGER-ROADMAP.md`, `16-CUSTOM-SIMULATOR.md`) still requires sourced latency tables and, for any product-facing claim, supervised physical confirmation. ## 6. Tests `tests/test_sim_cycles.py` (21 tests, all offline): * table sanity (every decoded kind has a positive cost; ALU/branch/jump unit cost; M-extension ordering; memory latency is a parameter; labeling); * counter arithmetic on hand-assembled programs (RAM vs MMIO latency); * disabling the model leaves `cycles` at 0 and changes no execution state; * determinism double-runs at machine and profile level; * the bundled demo executes identically with and without the model; * real-image bounded profile: first touch matches the documented probe address, session metadata carries the cycle totals, two runs are identical; * absent image skips cleanly. ## 7. Next steps 1. Device model (WP1.5.2): falcon/SEC2 windows, CSB/TRANSCOM, WPR2/FWSEC, PTIMER, IRQ routing — the run then stops modelling MMIO leniently. 2. Boot shims with recorded assumptions (WP1.5.3) so the profile can report a real milestone instead of a budget sample. 3. Sourced instruction/access latency tables and a documented calibration metric (`06-ACCURACY.md`, `16-CUSTOM-SIMULATOR.md`) before any timing-shaped statement; per-MCU cycle domains come with WP1.5.4/WP1.5.5. 4. Additive `cycle` event class in the session schema (roadmap V2, "formats and versioning") once the event vocabulary is reviewed. The increment is deliberately a table and a counter: every later step can replace defaults without touching the execution loop (the model is passed in) or the session format (metadata is additive).