summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/examples
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 10:58:21 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 10:58:21 -0400
commit002e3d7d13c414ea874abbee8d82803dd3a72fe5 (patch)
tree6627c12fa07f6a1cf73e5aaeb7390e698dd3a896 /debug/risc-v-gsp-rm-vm/examples
parent2f249817df327f5dcccf0464ad517fa60d6071d3 (diff)
downloadopen-iov-002e3d7d13c414ea874abbee8d82803dd3a72fe5.tar.gz
open-iov-002e3d7d13c414ea874abbee8d82803dd3a72fe5.zip
debug: green-eye wave 3 - RM harness, local simulator, device map, MCP plugin, value-validation demos, CI pipeline, toolchain docs; add host RM tools rmtool/rmtop
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/examples')
-rw-r--r--debug/risc-v-gsp-rm-vm/examples/scripts/sim_bench.py75
-rw-r--r--debug/risc-v-gsp-rm-vm/examples/scripts/sim_diff.py104
2 files changed, 179 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/examples/scripts/sim_bench.py b/debug/risc-v-gsp-rm-vm/examples/scripts/sim_bench.py
new file mode 100644
index 0000000..fa805ad
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/examples/scripts/sim_bench.py
@@ -0,0 +1,75 @@
+"""Measure native (M1) simulator throughput on the bundled demo firmware.
+
+Run standalone (from anywhere):
+
+ python3 examples/scripts/sim_bench.py # untraced baseline
+ python3 examples/scripts/sim_bench.py --trace # baseline + R4 trace overhead
+
+Reports MIPS for a fixed instruction budget -- the budget
+``SimClient.continue_(timeout=1.0)`` derives from SIM_IPS = 1e6. With
+``--trace`` a second run enables the exec/access trace and reports the
+per-instruction slowdown, so the non-trace path can be checked for regressions.
+"""
+from __future__ import annotations
+
+import argparse
+import sys
+import time
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[2]
+if str(ROOT) not in sys.path:
+ sys.path.insert(0, str(ROOT))
+
+import greeneye as ge
+from greeneye.errors import RspTimeout
+from sim.trace import Trace
+
+DEMO = ROOT / "firmware" / "uproc_demo" / "build" / "uproc_demo.elf"
+
+
+def measure(budget: int, *, trace: bool = False) -> tuple[int, float]:
+ """Run a fixed instruction budget; return (instructions, seconds)."""
+ session = ge.launch(kernel=str(DEMO), backend="native", name="sim-bench")
+ machine = session.client.machine
+ if trace:
+ session.client.trace = Trace() # recorder stays null: nothing is written
+ try:
+ machine.insns = 0
+ started = time.perf_counter()
+ try:
+ session.client.continue_(timeout=budget / 1_000_000.0)
+ except RspTimeout:
+ pass # budget exhausted: the benchmark workload completed
+ elapsed = time.perf_counter() - started
+ banner = session.target.read_uart().splitlines()
+ print(f"uart: {banner[0] if banner else '<empty>'!r}")
+ return machine.insns, elapsed
+ finally:
+ session.close()
+
+
+def main(argv: list[str] | None = None) -> int:
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
+ parser.add_argument("--budget", type=int, default=1_000_000)
+ parser.add_argument("--trace", action="store_true", help="also measure traced throughput")
+ parser.add_argument("--trace-budget", type=int, default=200_000)
+ args = parser.parse_args(argv)
+
+ insns, elapsed = measure(args.budget)
+ mips = insns / elapsed / 1e6 if elapsed else float("inf")
+ print(f"native M1: {insns} instructions in {elapsed:.3f}s = {mips:.2f} MIPS (untraced)")
+
+ if args.trace:
+ t_insns, t_elapsed = measure(args.trace_budget, trace=True)
+ t_mips = t_insns / t_elapsed / 1e6 if t_elapsed else float("inf")
+ overhead = (mips / t_mips - 1.0) if t_mips else float("inf")
+ print(
+ f"native M1 trace: {t_insns} instructions in {t_elapsed:.3f}s = {t_mips:.2f} MIPS "
+ f"({overhead * 100:+.1f}% per-instruction vs untraced)"
+ )
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/debug/risc-v-gsp-rm-vm/examples/scripts/sim_diff.py b/debug/risc-v-gsp-rm-vm/examples/scripts/sim_diff.py
new file mode 100644
index 0000000..f79b822
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/examples/scripts/sim_diff.py
@@ -0,0 +1,104 @@
+"""Differential harness: run one demo scenario on QEMU and native, then diff.
+
+ python3 examples/scripts/sim_diff.py [--out DIR] [--keep]
+
+Both backends execute the same scripted profile: enter/leave hooks on every
+uproc RPC handler, a write watchpoint on ``gsp_ctrl->rpc_count``, and a fixed
+0.3 s serviced run. The scenario is deterministic on both backends (the demo
+self-test finishes and call counts are fixed), so the hook/watch/breakpoint
+profiles must match exactly. Sessions are recorded under ``--out`` (or a
+temporary directory) and ``analyze.compare_sessions`` renders the delta report.
+
+Exit status: 0 when the profiles match (or QEMU is unavailable: skip),
+1 when they diverge.
+"""
+from __future__ import annotations
+
+import argparse
+import json
+import os
+import shutil
+import sys
+import tempfile
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[2]
+if str(ROOT) not in sys.path:
+ sys.path.insert(0, str(ROOT))
+
+import greeneye as ge
+from greeneye import analyze
+
+DEMO = ROOT / "firmware" / "uproc_demo" / "build" / "uproc_demo.elf"
+RPC_HANDLERS = ["rpc_ping", "rpc_echo", "rpc_fill", "rpc_sum", "rpc_state"]
+DURATION = 0.3
+
+
+def run_scenario(backend: str, session_dir: str) -> dict:
+ """Record one scripted run and return its canonical profile."""
+ session = ge.launch(kernel=str(DEMO), backend=backend, record=session_dir, name=f"diff-{backend}")
+ events: list = []
+ watch_hits: list = []
+ try:
+ for name in RPC_HANDLERS:
+ session.hook(
+ name,
+ on_enter=lambda hit, n=name: events.append(["enter", n, hit.arg(0), hit.arg(1)]),
+ on_leave=lambda hit, n=name: events.append(["leave", n, hit.retval]),
+ )
+ ctrl = session.symbols.lookup("gsp_ctrl")
+ session.watch(ctrl + 16, size=8, kind="w", callback=lambda hit: watch_hits.append(hit.addr))
+ session.run(duration=DURATION)
+ uart = session.target.read_uart()
+ bp_counts = [handle.hits for handle in session._bps.values()]
+ finally:
+ session.close()
+ return {
+ "hook_events": events,
+ "watch_hits": watch_hits,
+ "bp_counts": bp_counts,
+ "uart_first_line": uart.splitlines()[0] if uart else "",
+ }
+
+
+def main(argv: list[str] | None = None) -> int:
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
+ parser.add_argument("--out", help="directory for the two recorded sessions")
+ parser.add_argument("--keep", action="store_true", help="keep a temporary --out directory")
+ args = parser.parse_args(argv)
+
+ if not DEMO.exists():
+ print(f"skip: demo firmware not built ({DEMO}); run `make demo`")
+ return 0
+ if not shutil.which(os.environ.get("GREENEYE_QEMU", "qemu-system-riscv64")):
+ print("skip: qemu-system-riscv64 not found; native profile not compared")
+ return 0
+
+ out = args.out or tempfile.mkdtemp(prefix="greeneye-simdiff-")
+ os.makedirs(out, exist_ok=True)
+ qemu = run_scenario("qemu", os.path.join(out, "qemu"))
+ native = run_scenario("native", os.path.join(out, "native"))
+ report = analyze.compare_sessions(
+ os.path.join(out, "qemu"), os.path.join(out, "native")
+ )
+
+ if not args.out and not args.keep:
+ shutil.rmtree(out, ignore_errors=True)
+ location = "<temporary; use --out/--keep to keep>"
+ else:
+ location = out
+
+ print(f"# QEMU vs native differential profile ({location})")
+ print(json.dumps(qemu, sort_keys=True))
+ print(json.dumps(native, sort_keys=True))
+ print(report)
+
+ if qemu != native:
+ print("PARITY: FAIL")
+ return 1
+ print("PARITY: OK (hook/watch/bp profiles identical)")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())