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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
|
#!/usr/bin/env python3
"""Shared plumbing for the risc-v-gsp-rm-debugger value-validation demos.
Every demo maps one *real* Open-IOV scenario (cited from the handoffs) onto a
runnable demonstration on the bundled demo firmware / native RV64 simulator,
prints the question, the risc-v-gsp-rm-debugger commands, the answer, the evidence artifact
and the measured wall-clock, and emits one machine-readable ``RESULT_JSON``
line so ``run_all.py`` can aggregate.
Stdlib only; no hardware; runs from the tool root
(``open-iov/debug/risc-v-gsp-rm-vm``).
"""
from __future__ import annotations
import hashlib
import json
import os
import shutil
import sys
import tempfile
import time
HERE = os.path.dirname(os.path.abspath(__file__))
TOOL_ROOT = os.path.dirname(os.path.dirname(HERE))
if TOOL_ROOT not in sys.path:
sys.path.insert(0, TOOL_ROOT)
ARTIFACT_ROOT = os.path.join(HERE, "artifacts")
DEMO_ELF = os.path.join(TOOL_ROOT, "firmware", "uproc_demo", "build", "uproc_demo.elf")
SCHEMA_JSON = os.path.join(TOOL_ROOT, "firmware", "uproc_demo", "gsp_ctrl.schema.json")
SDK_ELF = os.path.join(TOOL_ROOT, "sdk", "build", "injected_demo.elf")
CTRL_MAGIC = 0x47535031
CTRL_RPC_COUNT = 0x10
CTRL_LAST_RPC = 0x18
CTRL_MAILBOX = 0x20
CTRL_ARG0 = 0x28
CTRL_ARG1 = 0x30
CTRL_ARG2 = 0x38
CTRL_RESULT = 0x40
CTRL_BUF = 0x48
CTRL_SIZE = 0x190
PING_XOR = 0xBEEF
INJECT_CMD_BASE = 16
SERVICE_SLOT = 6
#: Per-demo wall-clock ceiling the runner enforces (subprocess timeout).
DEMO_TIMEOUT_S = 45.0
#: Runner ceiling so the whole suite stays well under ~3 min.
SUITE_BUDGET_S = 180.0
class DemoError(RuntimeError):
"""A demo precondition failed in a way the caller should report, not mask."""
def qemu_available() -> bool:
return bool(shutil.which("qemu-system-riscv64"))
def demo_available() -> bool:
return os.path.isfile(DEMO_ELF)
def choose_backend(preferred: str = "native") -> str:
"""Environment override wins; QEMU is used only when preferred and present."""
env = os.environ.get("VALUE_DEMO_BACKEND")
if env:
return env
if preferred == "qemu":
return "qemu" if qemu_available() else "native"
return "native"
def sha256_file(path: str) -> str:
digest = hashlib.sha256()
with open(path, "rb") as fh:
for chunk in iter(lambda: fh.read(1 << 20), b""):
digest.update(chunk)
return digest.hexdigest()
class Stopwatch:
def __init__(self) -> None:
self.t0 = time.monotonic()
self.elapsed = 0.0
def stop(self) -> float:
self.elapsed = time.monotonic() - self.t0
return self.elapsed
def artifact_dir(name: str) -> str:
path = os.path.join(ARTIFACT_ROOT, name)
os.makedirs(path, exist_ok=True)
return path
def session_dir(root: str, label: str = "session") -> str:
"""A unique recording dir under ``root``.
Demos share a fixed artifact root so their reports are discoverable, but
two concurrent demo runs (tests, agents) must never share one
``events.jsonl`` or a reader can see a half-written recording.
"""
os.makedirs(root, exist_ok=True)
return tempfile.mkdtemp(prefix=f"{label}-", dir=root)
def write_json(path: str, payload) -> str:
os.makedirs(os.path.dirname(path), exist_ok=True)
tmp = f"{path}.tmp-{os.getpid()}"
with open(tmp, "w", encoding="utf-8") as fh:
json.dump(payload, fh, indent=2, sort_keys=True)
fh.write("\n")
os.replace(tmp, path)
return path
def write_text(path: str, text: str) -> str:
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as fh:
fh.write(text)
return path
def load_schema(ge):
with open(SCHEMA_JSON, "r", encoding="utf-8") as fh:
return ge.schema_from_json(json.load(fh))
def decode_ctrl(ge, data: bytes, schema=None) -> dict:
schema = schema or load_schema(ge)
out = schema.decode(data, base=0)
if isinstance(out.get("buf"), (bytes, bytearray)):
out["buf"] = bytes(out["buf"]).split(b"\x00", 1)[0].decode("utf-8", "replace")
return out
def short_hex(value) -> str:
if value is None:
return "None"
if isinstance(value, str):
return value
return f"{value:#x}" if isinstance(value, int) else str(value)
def render_banner(demo: str, question: str, scenario: str, baseline: str, commands) -> None:
print(f"=== {demo} ===")
print(f"real scenario : {scenario}")
print(f"baseline : {baseline}")
print(f"question : {question}")
print("commands :")
for command in commands:
print(f" - {command}")
def finish(result: dict, artifact: str, elapsed: float) -> dict:
"""Persist the per-demo result and print the human + machine summary."""
result["elapsed_s"] = round(elapsed, 4)
result["artifact"] = os.path.relpath(artifact, TOOL_ROOT) if artifact else None
result.setdefault("success", True)
if artifact:
write_json(os.path.join(artifact, "result.json"), result)
print(f"answer : {result.get('answer')}")
print(f"evidence : {result.get('artifact')}")
print(f"analog time : {elapsed:.3f} s ({result.get('backend')} backend)")
if result.get("real_baseline"):
print(f"real baseline : {result['real_baseline']}")
if result.get("projected"):
print(f"projected : {result['projected']}")
if result.get("blocked_on_device_model"):
print("blocked : real case blocked on H2 device model; workflow shown on analog")
if not result.get("success", True):
print(f"status : FAILED - {result.get('failure', 'unknown')}")
print("RESULT_JSON: " + json.dumps(result, sort_keys=True))
sys.stdout.flush()
return result
def timed_demo(fn, artifact: str) -> dict:
watch = Stopwatch()
try:
result = fn()
except Exception as exc: # report, never hide
import traceback
result = {
"success": False,
"failure": f"{type(exc).__name__}: {exc}",
"traceback": traceback.format_exc()[-2000:],
}
return finish(result, artifact, watch.stop())
def scenario(
*,
ids,
baseline,
projection,
blocked=False,
device=None,
) -> dict:
return {
"citation_ids": ids,
"static_baseline": baseline,
"projection": projection,
"blocked_on_device_model": blocked,
"device_model_needed": device,
}
|