# 42 — GMMU/FBIF observability (in development) > **Status: in development (2026-10-03, G1 work package).** This page > documents the observe-only GMMU and FBIF/FBPA register windows added to the > device model, the `greeneye-gmmu-fbif-trace/1` document and the boot-runner > flags that produce it. Development-only: nothing here authorises GPU > hardware, kernel modules, mdev, guest access or reboots, and nothing here is > a silicon-accuracy claim. > > **See also:** `36-GSP-DEVICE-MODEL.md` (the device model this plugs into), > `06-ACCURACY.md` (fidelity levels), `24-DEVICE-MAP.md` (harvested access > maps), `35-GREENEYE-SIMULATOR-DESIGN.md` §4.1 (the Phase G inventory this > observability de-risks). ## 1. What this is The Phase G board model needs the GMMU and the FB interface eventually. Before modelling either, we need to know **what the firmware actually touches** in those register windows, with which width and direction, from which PC, and without pretending to know register behaviour we have not established. `sim/devices/observe.py` adds **observer windows**: device-model windows that record every access and then **decline** it, so execution follows the existing device-map / lenient / unknown path exactly as it did before the window existed. They never invent a read value. ## 2. What is observed The TU104 profile (`sim/profiles/tu104-570.144.json`) declares 23 observer windows, all `observe-only`. Bases are runtime addresses in the GSP RISC-V PRIV aperture (`0x2000_0000_0000_0000` + BAR0 offset); offsets/names come from the hardware register/field headers (evidence kind: **header**). The B0–B2 runs did not touch any of them; the B3 wave reaches the WPR2 read pair (`0x1FA824/0x1FA828`), which falls inside the `gmmu-wpr-lock` window and is recorded observe-only (2 accesses, 0 served; the fwsec device answers). | Window label | Runtime base | Size | Covers | |---|---|---|---| | `gmmu-pri-mmu` | `0x2000000000100C80` | `0x280` | `NV_PFB_PRI_MMU_*` cluster (`NV_PFB_PRI_MMU_CTRL` first) | | `gmmu-wpr-lock` | `0x20000000001FA7D8` | `0x5C` | MMU per-engine PRIV_LEVEL masks + VPR/WPR/lock address pair | | `gmmu-hsmmu` | `0x20000000001FAC00` | `0x400` | `NV_PFB_HSMMU_PRI_MMU_*` (secondary MMU block) | | `fbpa` | `0x20000000009A0000` | `0x4000` | main FBPA block (`NV_PFB_FBPA_*`) | | `fbpa-partition-0..15` | `0x2000000000900000` + i·`0x4000` | `0x4000` | per-partition FBPA instances | | `fbpa-mc-0..2` | `0x2000000000980000` + i·`0x4000` | `0x4000` | FBPA MC instances | Register names (and, for a few control/status registers, header field names with bit positions) are declared per window so traces are readable; offsets inside a window that the profile does not name are still recorded and land under `unclassified`. `gmmu-pri-mmu` spans the `NV_PFB_PRI_MMU_*` cluster `0x100C80–0x100EFC`; the few non-MMU PFB registers interleaved in that span (e.g. the PRI_FB clock/power-gating registers) are recorded as unclassified, never renamed. ## 3. Mechanism: record, then decline Each `ObservedWindow` is a normal device-model window and is consulted by the registry like any other. Its `read`/`write` always record: ```text addr, offset, size, rw, value-if-known, pc, vt, window, register, served ``` and then return `None`/`False` unless the window is declared `storage` **and** the accessed offset is a declared plain read/write register **and** (for reads) that register has been written before. Declining means the registry falls through to the next matching window and the machine's existing path applies unchanged: * lenient mode: the access is logged/counted as before and reads 0; * fault mode: the same `Fault(cause, tval)` as without the window; * a modelled window underneath (e.g. the PMC block) is consulted exactly as before and serves the access if it models it, and the observer records that it happened. (The `gmmu-wpr-lock` window overlaps the fwsec WPR2 window, so it exercises this fall-through; it asserts nothing about whether the underlying WPR2 path serves — that path is unchanged by the observer and currently serves nothing through the registry, a pre-existing condition.) **No-fake-read guarantee.** An observe-only read records `"value": null` and returns `None`; a storage read before any write is declined the same way; no reset value is synthesised. The only read value a storage window can return is one this same run wrote to that register. All profile windows for GMMU/FBIF are `observe-only`; the `storage` mode exists for surfaces whose header proves plain read/write behaviour and is exercised by tests. `pc` is the faulting instruction address (the registry forwards it only to devices that opt in). `vt` is the machine instruction counter at the access; it advances at `Machine.run` boundaries, so accesses inside one bounded run share the run-start value — a documented granularity, not a timing claim (fidelity L2 is not modelled). **Size limit.** Each window's access log is capped (`max_access_log`, default 65536 entries); accesses beyond the cap are counted, not written. The trace document reports `dropped_accesses` per window and in the summary and separates `accesses` (total observed) from `retained_accesses` (present in the document), so truncation is never silent. ## 4. Trace document: `greeneye-gmmu-fbif-trace/1` `--trace-out ` writes a trace file: a path that is an existing directory (or ends in `/`, which makes that directory) gets the canonical filename `gmmu-fbif-trace.json` inside; anything else is used as the file name. Accesses are grouped per window and per declared register; undeclared offsets go to `unclassified`. Shape: ```json { "schema": "greeneye-gmmu-fbif-trace/1", "image_sha256": "…", "profile": "tu104-570.144.json", "profile_name": "tu104-570.144", "symbols": "symbols.json", "summary": {"windows": 23, "windows_touched": 0, "accesses": 0, "retained_accesses": 0, "dropped_accesses": 0, "reads": 0, "writes": 0, "served": 0, "registers_touched": 0}, "windows": [ {"label": "gmmu-pri-mmu", "base": "0x2000000000100c80", "size": "0x280", "mode": "observe-only", "evidence": "…", "counts": {"r": 0, "w": 0, "served": 0, "declined": 0}, "retained_accesses": 0, "dropped_accesses": 0, "registers": [ {"offset": "0x0", "name": "NV_PFB_PRI_MMU_CTRL", "access": "rw", "counts": {"r": 0, "w": 1, "served": 0}, "accesses": [ {"addr": "0x2000000000100c80", "offset": "0x0", "size": 4, "rw": "w", "value": "0x1", "pc": "0x4001234", "pc_symbol": "rm_init+0x20", "vt": 0, "window": "gmmu-pri-mmu", "register": "NV_PFB_PRI_MMU_CTRL", "served": false, "decoded": {"VM_PG_SIZE": 1}} ]} ], "unclassified": {"counts": {"r": 0, "w": 0, "served": 0}, "accesses": []}} ] } ``` `pc_symbol` is `null` unless `--symbols ` is given; `decoded` is present only when a value is known and the register declares fields (bit positions from the header). `value`/`addr`/`offset`/`pc` are hex strings; `vt` is an integer. The document is deterministic for a fixed image, profile, flags and stimulus. The **count summary is always in the boot report** (and session metadata under `gmmu_fbif`) even when `--trace-out` is off; the trace blob is added to the recorded session only when the flag is on. ## 5. Commands ```bash cd open-iov/debug/risc-v-gsp-rm-vm # B2 run with the observer summary, trace written to a directory: python3 -m sim.gsp_boot --instructions 20000 --record dumps/gsp-boot-b2-observe \ --trace-out dumps/gsp-boot-b2-observe # Resolve recorded PCs through a symbol database: python3 -m sim.gsp_boot --trace-out /tmp/trace.json --symbols symbols.json # Ad-hoc window whose base is not established yet (repeatable, no guessing): python3 -m sim.gsp_boot --observe-window 0x20000000009A0000:0x4000:fbpa \ --observe-window 0x2000000000100C80:0x280:gmmu ``` `--observe-window BASE:SIZE[:LABEL[:MODE]]` is the escape hatch: the trace accepts a runtime window list, so an unknown surface can be observed before any profile entry exists. A runtime window is `observe-only` unless `MODE=storage` is given (storage without declared registers still records and declines). ## 6. What is NOT modelled * **No GMMU page walk, no PTE/PDE decode, no TLB, no fault handling, no ATS.** Window accesses are recorded; their semantics are not interpreted. * **No FBIF transaction semantics, compression, ECC or partition hashing.** FBPA register offsets/names are labels, not behaviour. * **No interrupt or timing (L2) claims.** `vt` is the modeled instruction counter, not cycles. * **No device-environment (L3) claims.** The windows exist because the simulator serves them; silicon behaviour is not asserted. * **Storage is not semantics.** A storage window only echoes values written in the same run; it is not evidence that the real register stores them. * **No product or hardware action.** Nothing in this workstream touches `vgsp`, a GPU, a module, mdev, or a guest. ## 7. Evidence, limits and tests * Evidence kind for every window/offset is the hardware register/field header (block ranges and register offsets); observed firmware use in B0–B2 was **zero accesses** (measured at the B2 stop: 14 682 instructions, 0 unknown MMIO, 1 device access — the PMC chip-ID read). The B3 wave's WPR2 reads are the first observed accesses (2 reads inside `gmmu-wpr-lock`, observe-only, 0 served). The mechanism is still tested end-to-end with runtime windows. * Boot numbers are unchanged by the observer windows (instructions, stop PC/reason, unknown MMIO, unhandled-in-window, served accesses); the only session difference is the added device-state entries for the observers. * Tests: `tests/test_gmmu_fbif_trace.py` — no-semantic-change (lenient and fault paths match a registry without the window), storage read-back, field decode, profile expansion, trace schema, access-log-cap reporting, WPR2-overlap pass-through, B2 invariance, symbol resolution, runtime windows. * Public artifacts carry only component register names/offsets and behaviour; the window/register tables come from the tracked analysis and recorded sessions.