summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/sim/gsp_profile.py
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-03 14:05:38 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-03 14:05:38 -0400
commitcc79b478843f0230fe6aa8ecd822de7ba71c891e (patch)
tree5de760ddec4e45cc23d39ce7502635f5c0711217 /debug/risc-v-gsp-rm-vm/sim/gsp_profile.py
parent40351167dd209012ab485e6164ce94f0b8965422 (diff)
downloadopen-iov-cc79b478843f0230fe6aa8ecd822de7ba71c891e.tar.gz
open-iov-cc79b478843f0230fe6aa8ecd822de7ba71c891e.zip
vgsp: host CUDA bridge + sema release recovery; greeneye simulator wave
- kernel: gsp_cuda_bridge (QMD launch capture, debugfs job/out/done, param+output resolution cached from inline writes), sema drain fixes (never hold/starve a channel), GR ring autopin + counter autoreg, per-page push reads, counter-derive gate; module builds clean - tools/cuda-bridge: CUDA driver-API daemon executing mail/vadd kernels on the physical GPU, CUBB2/CBD2 v2 protocol, layout check + self-tests - tools/guest-diag + tools/cuda-ab: scanner/pin fixes, victory push-only mode - debug/risc-v-gsp-rm-vm: Phase S simulator wave (GSP device model, boot ladder, triage/re-demo harnesses, docs 30-41), GPGPU-sim scaffolding - STATE-CURRENT/docs updates
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/sim/gsp_profile.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/sim/gsp_profile.py317
1 files changed, 317 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/sim/gsp_profile.py b/debug/risc-v-gsp-rm-vm/sim/gsp_profile.py
new file mode 100644
index 0000000..6d87389
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/sim/gsp_profile.py
@@ -0,0 +1,317 @@
+"""GSP cycle-0 profile: bounded, deterministic real-firmware run with cycles.
+
+Loads the TU104 570.144 GSP flat image (read-only), maps it the same way the
+device-map/probe path does (``bias=0x80000000``, ``greeneye.elf`` PT_LOAD plan,
+lenient MMIO from a :class:`greeneye.device_map.DeviceMap`), executes a bounded
+instruction budget, and writes a normal ``greeneye-session/1`` directory whose
+metadata carries the modeled instruction/cycle totals and the stop reason.
+
+**Fidelity:** this is a bounded lenient first-touch profile, not a boot. The
+cycle figures come from :mod:`sim.cycles` - modeled defaults, not silicon
+timings (the simulator's L2 timing is *not* modelled; docs/06-ACCURACY.md,
+docs/32-GSP-CYCLE-MODEL.md). The run is deterministic: two identical
+invocations execute the same instructions and report the same cycle total.
+
+CLI::
+
+ python3 -m sim.gsp_profile --instructions 200000
+ python3 -m sim.gsp_profile --record dumps/gsp-cycle0 --json
+
+The image is loaded read-only; nothing is written back to it, and no hardware,
+GPU, module, guest or network is touched.
+"""
+from __future__ import annotations
+
+import argparse
+import json
+import os
+import sys
+from dataclasses import dataclass, field as _field
+
+from greeneye.device_map import DeviceMap, firmware_sha256
+from greeneye.elf import load_elf
+from greeneye.errors import GreenEyeError
+from sim.machine import (
+ R_BP,
+ R_BUDGET,
+ R_EBREAK,
+ R_ECALL,
+ R_FAULT,
+ R_STEP,
+ R_WATCH,
+)
+
+#: Tool root (the directory containing ``sim/`` and ``greeneye/``).
+TOOL_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+#: The TU104 570.144 GSP firmware, as laid out in the workspace tree.
+DEFAULT_IMAGE = os.path.normpath(
+ os.path.join(
+ TOOL_ROOT, "..", "..", "firmware", "57X-GSP", "570.144", "gsp-570.144.fwimage.bin"
+ )
+)
+
+#: GPU physical addresses are relocated into simulator RAM at this bias
+#: (same convention as the probe/device-map path).
+DEFAULT_BIAS = 0x80000000
+
+#: Small default budget: enough to pass the documented first-touch fault.
+DEFAULT_INSTRUCTIONS = 200_000
+
+#: Machine stop reasons -> stable session labels.
+STOP_REASONS = {
+ R_BUDGET: "budget",
+ R_BP: "breakpoint",
+ R_WATCH: "watch",
+ R_EBREAK: "ebreak",
+ R_FAULT: "fault",
+ R_STEP: "step",
+ R_ECALL: "ecall",
+}
+
+
+@dataclass
+class GspProfileResult:
+ """Outcome of one :func:`run_profile` invocation.
+
+ ``ran`` is False (with ``reason`` explaining why) when the image is absent
+ and the caller should skip cleanly; ``device_map`` is the in-memory map
+ used/learned during the run (never serialised by :meth:`to_dict`).
+ """
+
+ ran: bool
+ reason: str
+ image: str
+ image_sha256: str | None = None
+ session_dir: str | None = None
+ instructions: int = 0
+ cycles: int = 0
+ cycle_model: str | None = None
+ stop_reason: str | None = None
+ stop_pc: int | None = None
+ stop_pc_fw: int | None = None
+ first_unknown_mmio: dict | None = None
+ mmio_unknown: int = 0
+ device_map: DeviceMap | None = _field(default=None, repr=False)
+
+ def to_dict(self) -> dict:
+ data = {
+ "schema": "greeneye-gsp-cycle-profile/1",
+ "ran": self.ran,
+ "reason": self.reason,
+ "image": self.image,
+ "image_sha256": self.image_sha256,
+ "session": self.session_dir,
+ "instructions_executed": self.instructions,
+ "cycles": self.cycles,
+ "cycle_model": self.cycle_model,
+ "fidelity": "modeled cycles; L2 (timing) not modelled (docs/06)",
+ "stop": {"reason": self.stop_reason, "pc": self.stop_pc, "pc_fw": self.stop_pc_fw},
+ "first_unknown_mmio": self.first_unknown_mmio,
+ "mmio_unknown": self.mmio_unknown,
+ }
+ if self.device_map is not None:
+ data["device_map_entries"] = len(self.device_map)
+ return data
+
+
+def _fw_pc(pc: int, bias: int) -> int:
+ if not bias or pc == 0 or pc < bias:
+ return pc
+ return pc - bias
+
+
+def _memory_mb(image, bias: int, ram_base: int = DEFAULT_BIAS) -> int:
+ """RAM size that covers the relocated image, as the probe path computes it."""
+ end = image.max_vaddr + bias
+ need = end - ram_base + (1 << 20)
+ return max(256, (need + (1 << 20) - 1) // (1 << 20))
+
+
+def run_profile(
+ *,
+ image: str = DEFAULT_IMAGE,
+ instructions: int = DEFAULT_INSTRUCTIONS,
+ bias: int = DEFAULT_BIAS,
+ record=None,
+ device_map=None,
+ learn_mmio: bool = False,
+ name: str = "gsp-cycle-profile",
+ cycle_model=None,
+) -> GspProfileResult:
+ """Run the bounded cycle profile; skip cleanly when the image is absent.
+
+ ``record`` is a session directory (or True for an auto-named one under
+ ``dumps/``); the default ``None`` still records, so a session is always
+ written. ``device_map`` may be a :class:`~greeneye.device_map.DeviceMap`,
+ a path to one, or None for an empty lenient map.
+ """
+ if instructions <= 0:
+ raise GreenEyeError(f"instructions must be positive, got {instructions}")
+ if not os.path.isfile(image):
+ return GspProfileResult(ran=False, reason=f"image absent: {image}", image=image)
+
+ image_obj = load_elf(image)
+ if not image_obj.is_riscv:
+ raise GreenEyeError(f"{image}: not a RISC-V ELF image")
+ sha = firmware_sha256(image)
+
+ if isinstance(device_map, (str, os.PathLike)):
+ dm = DeviceMap.load(os.fspath(device_map))
+ elif device_map is None:
+ dm = DeviceMap(
+ meta={
+ "source": "gsp-profile",
+ "firmware": os.path.basename(image),
+ "firmware_sha256": sha,
+ "bias": bias,
+ }
+ )
+ else:
+ dm = device_map
+
+ from greeneye.sim_backend import launch_native # deferred: keeps sim/ standalone
+
+ session = launch_native(
+ kernel=image,
+ memory_mb=_memory_mb(image_obj, bias),
+ record=True if record is None else record,
+ name=name,
+ device_map=dm,
+ unknown_mmio="lenient",
+ learn_mmio=learn_mmio,
+ bias=bias,
+ cycle_model=cycle_model,
+ )
+ machine = session.target.machine
+ try:
+ reason, pc, _a, _b = machine.run(instructions)
+ first = machine.mmio_log[0] if machine.mmio_log else None
+ model = machine.cycle_model
+ stop_reason = STOP_REASONS.get(reason, str(reason))
+ stop_pc_fw = _fw_pc(pc, bias)
+ meta = {
+ "kind": "gsp-cycle-profile",
+ "firmware_sha256": sha,
+ "bias": bias,
+ "instructions_executed": machine.insns,
+ "cycles": machine.cycles,
+ "cycle_model": model.name if model is not None else None,
+ "cycle_fidelity": "modeled cycles, not measured (docs/32-GSP-CYCLE-MODEL.md)",
+ "stop_reason": stop_reason,
+ "stop_pc": pc,
+ "stop_pc_fw": stop_pc_fw,
+ "mmio_unknown": machine.mmio_unknown,
+ "first_unknown_mmio": first,
+ }
+ if model is not None:
+ meta["cycle_model_table"] = model.describe()
+ if getattr(session.recorder, "dir", None) is not None:
+ session.recorder.meta.update(meta)
+ session.note(
+ "gsp_cycle_profile",
+ instructions=machine.insns,
+ cycles=machine.cycles,
+ stop_reason=stop_reason,
+ stop_pc_fw=stop_pc_fw,
+ )
+ session_dir = getattr(session.recorder, "dir", None)
+ finally:
+ session.close()
+
+ return GspProfileResult(
+ ran=True,
+ reason="ok",
+ image=image,
+ image_sha256=sha,
+ session_dir=session_dir,
+ instructions=machine.insns,
+ cycles=machine.cycles,
+ cycle_model=model.name if model is not None else None,
+ stop_reason=stop_reason,
+ stop_pc=pc,
+ stop_pc_fw=stop_pc_fw,
+ first_unknown_mmio=first,
+ mmio_unknown=machine.mmio_unknown,
+ device_map=dm,
+ )
+
+
+def _print_result(result: GspProfileResult, out=sys.stdout) -> None:
+ print(f"image: {result.image}", file=out)
+ print(f"sha256: {result.image_sha256}", file=out)
+ print(f"session: {result.session_dir}", file=out)
+ print(f"instructions: {result.instructions}", file=out)
+ print(f"cycles (modeled): {result.cycles}", file=out)
+ print(f"cycle model: {result.cycle_model}", file=out)
+ if result.instructions:
+ print(f"cycles/instruction: {result.cycles / result.instructions:.3f}", file=out)
+ print(
+ f"stop: {result.stop_reason} pc={result.stop_pc:#x} (fw {result.stop_pc_fw:#x})",
+ file=out,
+ )
+ print(f"unknown MMIO accesses: {result.mmio_unknown}", file=out)
+ if result.first_unknown_mmio:
+ first = result.first_unknown_mmio
+ print(
+ "first unknown MMIO: "
+ f"pc={first['pc']:#x} addr={first['addr']:#x} "
+ f"size={first['size']} rw={first['rw']}",
+ file=out,
+ )
+ print(
+ "note: cycles are modeled (sim/cycles.py defaults); no silicon timing claim",
+ file=out,
+ )
+
+
+def main(argv: list[str] | None = None) -> int:
+ parser = argparse.ArgumentParser(
+ prog="python3 -m sim.gsp_profile",
+ description=(
+ "Bounded deterministic GSP cycle-0 profile (modeled cycles; no silicon claim). "
+ "Loads the real firmware read-only, runs under lenient MMIO and records a session."
+ ),
+ )
+ parser.add_argument("--image", default=DEFAULT_IMAGE, help="flat GSP firmware image")
+ parser.add_argument(
+ "--instructions", type=int, default=DEFAULT_INSTRUCTIONS, help="instruction budget"
+ )
+ parser.add_argument("--bias", type=lambda s: int(s, 0), default=DEFAULT_BIAS)
+ parser.add_argument("--record", help="session directory (default: auto under dumps/)")
+ parser.add_argument("--device-map", help="existing greeneye-device-map/1 JSON to serve MMIO")
+ parser.add_argument("--learn", action="store_true", help="learn observed MMIO into the map")
+ parser.add_argument("--learn-out", help="save the learned/used map here")
+ parser.add_argument("--name", default="gsp-cycle-profile")
+ parser.add_argument("--json", action="store_true", help="emit the result as JSON")
+ args = parser.parse_args(argv)
+
+ try:
+ result = run_profile(
+ image=args.image,
+ instructions=args.instructions,
+ bias=args.bias,
+ record=args.record,
+ device_map=args.device_map,
+ learn_mmio=args.learn,
+ name=args.name,
+ )
+ except GreenEyeError as exc:
+ print(f"[gsp-profile] error: {exc}", file=sys.stderr)
+ return 1
+
+ if not result.ran:
+ print(f"[gsp-profile] skipped: {result.reason}", file=sys.stderr)
+ return 2
+ if args.learn_out and result.device_map is not None:
+ result.device_map.save(args.learn_out)
+ print(f"[gsp-profile] device map ({len(result.device_map)} entries) -> {args.learn_out}")
+ if args.json:
+ print(json.dumps(result.to_dict(), indent=1, sort_keys=True))
+ else:
+ _print_result(result)
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())