# 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–B2 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/.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 (B3 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/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 (B3 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); * raw-image mirror at the DMEM aperture base so the app's own relocation copy is self-consistent (it is not: see §6); * `a0`–`a5` register arguments (bootargs PA/VA, ELF PA/size, load base, WPR id). 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. ## 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, MPU/vector setup, the bootargs consumer (`0x4007A30`) and reaches its **relocation copy** (`0x4007B7C`), then stops at a known unknown (DMEM runspace) | sessions `dumps/gsp-boot-b2-run1`, `dumps/gsp-boot-b2-run2`; test `test_shim_reaches_relocation_b2` | 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-b2-run1 python3 -m unittest tests.test_sim_devices -v ``` Two-run determinism is asserted by `test_two_runs_identical` and was checked at the recorded B2 sessions: identical instruction count (14 682), stop reason/PC (`fault @ 0x400EA0C`), device-state SHA-256 (`3fa264b9…`) 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 and the full boot-shim assumption list; * `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. **DMEM runspace content.** The app's relocation copy reads the DMEM aperture (`0x1_0000_0000+`) and writes it to the `0x400E000` runtime region; the bootloader-provided content is not reconstructed, so the copy writes zeros and the run stops at `0x400EA0C` (illegal instruction). This is the B2 ceiling today. 2. **CSB/TRANSCOM register semantics.** No message-window poll has been observed yet, so no register is modeled. B3 waits for the first observed poll; the `csb.py` seam and profile register declarations are in place. 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. 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. **MPU translation.** The firmware programs its MPU CSRs (observed), but the machine does not enforce translations; the libos private mapping is served by explicit device aliases and the FBGPA/SYSGPA seam, not by an MPU model. 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).