diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 10:58:21 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 10:58:21 -0400 |
| commit | 002e3d7d13c414ea874abbee8d82803dd3a72fe5 (patch) | |
| tree | 6627c12fa07f6a1cf73e5aaeb7390e698dd3a896 /debug/risc-v-gsp-rm-vm/examples/scripts/sim_diff.py | |
| parent | 2f249817df327f5dcccf0464ad517fa60d6071d3 (diff) | |
| download | open-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/scripts/sim_diff.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/examples/scripts/sim_diff.py | 104 |
1 files changed, 104 insertions, 0 deletions
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()) |
