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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
|
# 36 — GSP device model (in development)
> **Status: in development (2026-10-03, S1 work package).** This page
> documents the `sim/devices/` device model, the versioned
> `sim/profiles/` documents and the real-firmware boot-ladder results. It is
> the companion to `35-GREENEYE-SIMULATOR-DESIGN.md` §3 (Phase S) and
> `06-ACCURACY.md`. Development-only: nothing here authorises GPU hardware,
> kernel modules, mdev, guest access or reboots.
>
> **See also:** `37-SIM-QEMU-LINE.md` (the B0–B3 device model on the frozen
> simulator-vs-QEMU checks) and `40-NEW-VERSION-TRIAGE.md` (one-command triage
> of a new image through this ladder).
## 1. What this is
The device model gives the native RV64 core (`sim/machine.py`) real MMIO
semantics for the windows the real GSP firmware touches while booting, so a
bounded run advances **past the QEMU first fault** with evidence instead of
lenient zero-reads. Modeled devices take precedence over the lenient
device-map/unknown fallback; an access inside a modeled window that the
device does not implement is still reported as unknown (never silently
zeroed). Every modeled register carries an **evidence kind**:
* **header** — a hardware register/field map (offset, width, access type,
field positions, reset/alignment);
* **firmware use** — the disassembled firmware code that reads/writes it;
* **observed access** — the address/width/direction/PC/value sequence seen in
a real run;
* **shim** — reset-time state the real SEC2 booter/host would provide; always
an explicit, session-recorded assumption, never a device claim.
If a window cannot be proven, none of it is modeled and it is listed as an
unknown (§6).
## 2. Architecture
```
sim/profiles/<name>.json versioned profile: windows, chip id, load layout,
│ DMA aperture windows, boot-shim values
▼
sim/devices/__init__.py registry_from_profile(profile, machine)
│
├─ base.py DeviceRegistry: range+size decode, access log,
│ JSON state/snapshot, peek() debugger surface
├─ pmc.py PMC/master block (NV_PMC_BOOT_42 chip ID)
├─ falcon.py falcon block: IRQ, MAILBOX0/1, DEBUGINFO, DMA,
│ FBIF control/transcfg
├─ priscv.py RISC-V-local block: IRQ mask, trace, error regs
├─ timer.py PTIMER (modeled virtual time; opt-in)
├─ csb.py CSB/TRANSCOM message window seam (B4 landing)
├─ fwsec.py WPR2 address pair (FWSEC state unmodeled)
├─ pmu.py PMU stub: observes, never fakes
├─ dma.py aperture → RAM translation seam (FBGPA/SYSGPA)
└─ irq.py falcon + RISC-V-local IRQ aggregation
▼
sim/machine.py Machine(devices=..., translator=..., reset_regs=...)
│ + sim/mpu.py: XMPU entry model (fetch/load/store)
│ + sim/libos.py: libos syscall service model
▼
sim/gsp_boot.py bounded real-firmware run + session + rung
```
`Machine` consults devices after RAM/UART and before the device map/lenient
fallback; the aperture translator (`dma.py`) is consulted first so
FBGPA/SYSGPA memory accesses resolve to RAM. Overlapping device windows
resolve last-registered-first, and a window that does not model an access
defers to the next matching window (safe fallback), so a narrow overlay can
serve some offsets without hiding the wider device. `Machine.device_state()`
and the `greeneye-sim-devices/1` snapshot expose all device state as
serialisable data (snapshot/restore support is additive and not yet wired to
moments).
## 3. Modeled inventory
Offsets are relative to each block base. `RW` semantics are storage unless
noted; every unlisted offset is unhandled and logged.
| Block | Window(s) (PRIV unless noted) | Modeled registers | Evidence |
|---|---|---|---|
| PMC | `+0x0` (size 0x1000) | `BOOT_42` @0xA00: ARCHITECTURE 28:24, IMPLEMENTATION 23:20, MAJOR 19:16, MINOR 15:12, MINOR_EXT 11:8, CHIP_ID 28:20 (TU104 = 0x164), read-only | header + firmware use + observed (first MMIO) |
| Falcon | `+0x110000`, alias `0x300000000` | `IRQSSET` 0x000 (`irq_status \|=`), `IRQSCLR` 0x004 (`&= ~`), `IRQSTAT` 0x008 (read), `MAILBOX0/1` 0x040/0x044, `OS` 0x080, `DEBUGINFO` 0x094, `CPUCTL` 0x100, `BOOTVEC` 0x104, `DMACTL` 0x10c, `DMATRFBASE` 0x110, `DMATRFMOFFS` 0x114, `DMATRFCMD` 0x118 (transfer on write), `DMATRFFBOFFS` 0x11c, `DMATRFBASE1` 0x128, `FBIF_TRANSCFG(0..7)` 0x600, `FBIF_CTL` 0x624 | header + firmware use + observed |
| RISC-V local | `+0x111000`, alias `0x300001000` | `IRQMSET` 0x2ac, `IRQMCLR` 0x2b0, `IRQMASK` 0x2b4 (read), `IRQDEST` 0x2b8, `IRQTYPE` 0x2bc, `TRACECTL` 0x344, `TRACE_RDIDX` 0x348, `TRACE_WTIDX` 0x34c, `PRIV_ERR_STAT` 0x360, `PRIV_ERR_INFO` 0x364 (read), `PRIV_ERR_ADDR` 0x368 (read), `HUB_ERR_STAT` 0x36c | header + observed |
| PTIMER | `+0x9000` | `TIME_0` aperture 0x9400 / block `+0x400` (NSEC 31:5), `TIME_1` aperture 0x9410 / block `+0x410` (NSEC 28:0); value = instruction count × `ns_per_insn` (**modeled**, not calibrated) | header + **modeled** time |
| WPR2 | `+0x1FA000` | `WPR2_ADDR_LO` 0x1FA824, `WPR2_ADDR_HI` 0x1FA828 (4 KiB alignment; HI points at the last 128 KiB block) | header + firmware use |
| CSB/TRANSCOM | profile-declared | none by default; a profile may declare individual registers with width/value and `rw`/`ro`/`wo`/`w1c`/`set`/`clear` semantics | none yet (B4 landing point) |
| PMU | profile-declared | none; the stub records accesses and returns unhandled | none yet |
| DMA seam | FBGPA/SYSGPA (profile) | GPU-physical → RAM windows for falcon DMA and aperture loads/stores; unmapped transfers are refused and recorded | address-map header + firmware use |
Falcon DMA follows the header field semantics: `size = 1 << (SIZE+2)`,
`IMEM`/`DMEM` aperture select, `WRITE` direction, address
`((BASE1<<32 | BASE) << 8) + FBOFFS`, `IDLE` set on success and `ERROR` on a
refused transfer. Which FB/SYS aperture a DMA address targets (the
`FBIF_TRANSCFG.TARGET` decode) is **not proven yet** and is listed in §6.
### 3.1 Boot shim (declared assumptions)
The firmware's `_start` consumes boot arguments the real SEC2 booter/host
writes; the simulator reconstructs them from the documented structure layout
and records each as an assumption in `session.json.boot_shim.assumptions`:
* `NV_RISCV_BOOT_PARAMS` at a RAM address (bootType `NVRM`, size 120,
version 1, core-dump buffer, one `WPRM` libos init arg);
* a `GspFwWprMeta` (magic/revision/verified and a self-consistent FB layout);
* a `libos_bootstrap_args_t` — six `u64` fields (IMEM and DMEM
`base`/`size`/`bs`) the real bootloader builds from its TCM configuration
and passes in `a0`; the M-mode TCM configuration is declared in the
profile (offsets 0, 64 KiB windows). This replaces the earlier
raw-image mirror at the DMEM aperture: with the XMPU model (§3.2) the
resident-section copy reads its real backing and no image mirror is needed;
* `a0`–`a5` register arguments (a0 = bootstrap-args pointer; the rest are
declared assumptions the boot path only uses where its own computation is
not self-sufficient).
The DEBUGINFO **host-consume** behaviour is also a shim: the firmware writes a
boot-stage marker and polls bits 22:20 until they clear, and no self-clearing
path exists in the firmware, so the host side is modelled as consuming the
stage marker on read. The device never changes a read value except for this
documented shim, and the shim is profile-enabled and session-recorded.
### 3.2 CPU protection unit (XMPU) — core model, `sim/mpu.py`
The GSP core boots in bare mode and then enables a small protection unit
through M-mode CSRs; the firmware programs entries for its code/data ranges
and, during init, for a scratch window it copies resident sections through.
Evidence kind: **hardware CSR/field header + firmware use + observed access
sequence** (the 570.144 bootstrap writes are visible in any run's device/CSR
trace). Modelled CSRs:
| CSR | value | semantics |
|---|---|---|
| `MMPUIDX` (0x7C8) | entry index | INDEX field 5:0 (64 entries) |
| `MMPUVA` (0x7C9) | virtual base | BASE 63:12, `VLD` bit 0 |
| `MMPUPA` (0x7CA) | physical base | BASE 63:12 |
| `MMPURNG` (0x7CB) | range (a size) | RANGE 63:12 |
| `MMPUATTR` (0x7CC) | attributes | recorded, not enforced |
Translation is active only when `mstatus.VM == NVMPU` (`0x1E`; field bits
28:24); lookup is lowest-index-first (the firmware's own bootstrap comment
documents the precedence). An entry maps `addr` to `pa + (addr - va_base)`
when `va_base <= addr < va_base + range`. The model applies to instruction
fetch, loads, stores and atomics; permissions/cache attributes are recorded
but not enforced, and **no match is treated as identity** (more lenient than
silicon) because the simulator keeps local memory flat and the boot path's
mappings are self-consistent. `read_mem`/`write_mem` (debugger surface)
remain raw and untranslated.
Evidence in the session: `session.json.mpu` carries the enabled flag, entry
table and `mstatus`. The model removes the B2 ceiling: with the correct
bootstrap args and the scratch mapping, the resident-section copy reads the
in-place image and the run continues (no DMEM image mirror, no faked copy).
### 3.3 libos syscall surface — service model, `sim/libos.py`
The application is a libos task that requests kernel services with `ecall`
(the syscall number in `t0`). The native machine stops on `ecall` unless a
handler is installed, so the boot runner attaches a deterministic service
model that:
* records every request (number, name, arguments, pc, instruction count) in
`session.json.libos`;
* acknowledges only no-ops and modelled services — task yield, cache
invalidation, external-interrupt arming, profiler enable/query, the
modeled timer read, DMA bounce-buffer registration (`TCM` region),
inverted-pagetable registration, and DMA copy/flush (an in-RAM copy in the
flat model: the real kernel drives the falcon DMA through TCM, the copy
effect is identical and is not faked — both sides must resolve or the run
stops);
* **never fakes waits**: `processor_suspend`, `task_port_send_recv_wait`,
`task_panic` and every unimplemented syscall stop the run with `R_ECALL`
and are reported in `session.json.libos.stopped`.
Task switching, interrupt delivery, pagination, partition switch and memory
queries are not modelled (force-stopped, not invented). The B3 result is
exactly this stop: the message-loop task blocks in `task_port_send_recv_wait`
with `armed_mask=0xFF`.
## 4. Boot ladder results (observed, 2026-10-03)
| Rung | Claim | Evidence |
|---|---|---|
| **B0** | flat image loads; first MMIO is the unmodelled PMC `BOOT_42` read at `0x2000_0000_0000_0A00` | `tests/test_sim_devices.py` (B0 classification) |
| **B1** | device model serves the whole boot path: **0 unknown MMIO, 0 unhandled in windows**, deterministic 2M-instruction run stopping at the boot-halt/report loop (`0x4010928`) | `run_boot(use_shim=False)`; sessions `dumps/gsp-boot-b1-run1`, `dumps/gsp-boot-b1-run2`; test `test_no_shim_b1_device_model_serves_boot` |
| **B2** | with the shim, the firmware passes the chip gate, XMPU setup, the bootstrap-args consumer and its **relocation copy** (`0x4007B7C`); the XMPU model resolves the copy's real backing and the run continues into libos init | sessions `dumps/gsp-boot-b2-run1`, `dumps/gsp-boot-b2-run2`; test `test_shim_reaches_relocation_plus_mmu` |
| **B3** | libos init completes (TCM registration `0x5B22000`/`0x1000`, pagetable registration, DMA flush, external-interrupt arming `0xFF`) and the message-loop task blocks in the port wait | sessions `dumps/gsp-boot-b3-run1`, `dumps/gsp-boot-b3-run2`; test `test_shim_reaches_message_loop_b3` |
Reproduction (read-only; no hardware):
```bash
cd open-iov/debug/risc-v-gsp-rm-vm
python3 -m sim.gsp_boot --instructions 2000000 --record dumps/gsp-boot-b3-run1
python3 -m unittest tests.test_sim_devices tests.test_mpu -v
```
Two-run determinism is asserted by `test_two_runs_identical` and was checked
at the recorded B3 sessions: identical instruction count (474 043), stop
reason/PC (`ecall @ 0x4018038`, syscall
`task_port_send_recv_wait`), device-state SHA-256 (`5b3cc7e4…`) and register
file. The B1 no-shim run is likewise identical across runs (2 000 000
instructions, budget stop at `0x4010928`, device-state SHA-256 `88eb07b7…`).
## 5. Session evidence
Each `run_boot` invocation writes a `greeneye-session/1` directory with:
* `session.json` — firmware SHA-256, profile name, load layout, rung,
milestones, stop reason/PC, unknown-MMIO counts, device-state SHA-256,
device-access counts, the full boot-shim assumption list, the XMPU state
(`mpu`: enabled, entries, mstatus) and the libos service log (`libos`:
calls, counts, TCM/pagetable registrations, the stopping request);
* `blobs/device-state.json` — the `greeneye-sim-devices/1` snapshot of every
device (registers, IRQ state, DMA windows/counters, shim flags);
* `events.jsonl` — the normal session event stream (`gsp_boot` note).
## 6. Unknowns (explicitly not modeled / not verified)
1. **CSB/TRANSCOM register semantics.** No message-window poll has been
observed yet, so no register is modeled. The 570.144 firmware blocks in
the message-loop port wait before any CSB access; B4 waits for the first
host-traffic-triggered poll; the `csb.py` seam and profile register
declarations are in place.
2. **Libos services.** Task switching, interrupt delivery, pagination,
partition switch and memory queries are not modelled: the service model
records and force-stops on them (never invents progress). DMA copies are
modelled as in-RAM copies in the flat memory model.
3. **PMU.** No PMU register access observed; stub only.
4. **FWSEC state machine.** Only the WPR2 address pair is modeled; FWSEC
execution is not.
5. **DMA aperture selection.** `FBIF_TRANSCFG.TARGET`/`MEM_TYPE` decode is
not proven; only the profile's DMA windows decide what an address means.
6. **Interrupt delivery.** Falcon/RISC-V IRQ state is aggregated and
serialisable, but mstatus/mie/mip/mtvec trap entry and interrupt injection
are not modelled (the firmware arms external interrupts with mask `0xFF`
and then waits forever in the flat model).
7. **PTIMER calibration.** Values are modeled from the instruction count with
a placeholder `ns_per_insn`; no firmware PTIMER read has been observed and
no timing claim is made.
8. **XMPU enforcement.** Translation, VLD gating, mstatus enable and
lowest-index precedence are modelled; permissions, cache/coherence
attributes and WPR IDs are recorded but not enforced, and an unmatched
access passes through as identity (lenient by design, listed here).
Nothing in this page is a silicon-accuracy claim; `06-ACCURACY.md` fidelity
levels apply (L0/L1 modelled, L2 modelled-not-calibrated, L3 partial and
listed above).
|