summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/examples/scripts/sim_diff.py
blob: f79b822ced7afa5bcd077826676983570db752c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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())