1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
# 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/<probe>/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).
|