Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions docs/session-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,20 @@ Sim(scenario={...}, machine="c6", uart="uart0", cwd=fixture_dir)
```

Scenario dicts use the `sim --scenario` schema verbatim: `machines`
(`repl`/`mcu` + `overlay` + `elf`), `media` (BLE/CAN/Ethernet/UART buses),
`networkServices` (scripted peers), `quantum`. Relative paths resolve against
`cwd=` (default: the process cwd).

`trace_symbols=[...]`, `trace_interrupts=True` and `show_logs=True` turn on the
non-halting instrumentation; read it back with `symbol_trace()`, `interrupts()`
and `logs()`.
(`repl`/`mcu` + `overlay` + `elf` + `symbolsElfPath`), `media` (BLE/CAN/Ethernet/UART
buses), `networkServices` (scripted peers), `quantum`. Relative paths resolve
against `cwd=` (default: the process cwd).

`symbols_elf=` (single-machine form) / `symbolsElfPath` (scenario machine
entries — same key as the CLI's scenario YAML, so a scenario dict is
copy-pasteable) attaches a companion ELF that carries debug symbols for a
stripped image, e.g. a PlatformIO/IDF `firmware.elf` alongside a stripped
flash container passed as `elf=`. It only changes what `symbol()` and RTOS
introspection can resolve — the image that actually runs is still `elf=`.

`trace_symbols=[...]`, `trace_memory=[...]`, `trace_interrupts=True` and
`show_logs=True` turn on the non-halting instrumentation; read it back with
`symbol_trace()`, `interrupts()` and `logs()`.

## Drive

Expand Down
8 changes: 5 additions & 3 deletions src/simantic/_rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ class NotSupported(SimError):

class RustBackend:
def __init__(self, machines: list[dict], *, base: Path, media, services, quantum,
trace_symbols, trace_interrupts, engine_dir):
trace_symbols, trace_memory, trace_interrupts, engine_dir):
if len(machines) != 1:
raise NotSupported("backend='rust' runs one machine; multi-machine scenarios need backend='renode'")
if media or services:
raise NotSupported("backend='rust' has no media or network services yet (simantic-core#183)")
if trace_symbols or trace_interrupts:
raise NotSupported("backend='rust' has no symbol/interrupt tracing yet (simantic-core#183)")
if trace_symbols or trace_memory or trace_interrupts:
raise NotSupported("backend='rust' has no symbol/memory/interrupt tracing yet (simantic-core#183)")
m = machines[0]
if m.get("symbolsElfPath"):
raise NotSupported("backend='rust' has no symbols_elf support yet (simantic-core#183)")
self.machines = [m["name"]]
self._elf = (base / m["elf"]).read_bytes()
repl = base / m["repl"] if m.get("repl") else None
Expand Down
34 changes: 21 additions & 13 deletions src/simantic/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ def __init__(
scenario: dict[str, Any] | None = None,
machine: str | None = None,
uart: str = "uart0",
symbols_elf: str | os.PathLike[str] | None = None,
trace_symbols: list[str] = (),
trace_memory: list[str] = (),
trace_interrupts: bool = False,
show_logs: bool = False,
cwd: str | os.PathLike[str] | None = None,
Expand Down Expand Up @@ -176,7 +178,8 @@ def __init__(
if "elf" not in m or ("repl" in m) == ("mcu" in m):
raise ValueError(f"machine {m['name']!r} needs elf and exactly one of repl/mcu")
else:
machines = [{"name": "machine", "elf": elf, "repl": repl, "mcu": mcu, "overlay": overlay}]
machines = [{"name": "machine", "elf": elf, "repl": repl, "mcu": mcu, "overlay": overlay,
"symbolsElfPath": symbols_elf}]
scenario = scenario or {}

telemetry.record(f"sdk.session.{backend}")
Expand All @@ -186,13 +189,14 @@ def __init__(
self._b = RustBackend(
machines, base=self._base, media=scenario.get("media"),
services=scenario.get("networkServices"), quantum=scenario.get("quantum"),
trace_symbols=trace_symbols, trace_interrupts=trace_interrupts, engine_dir=engine_dir,
trace_symbols=trace_symbols, trace_memory=trace_memory, trace_interrupts=trace_interrupts,
engine_dir=engine_dir,
)
else:
self._b = _RenodeBackend(
machines, base=self._base, work=self._work, media=scenario.get("media"),
services=scenario.get("networkServices"), quantum=scenario.get("quantum"),
trace_symbols=trace_symbols, trace_interrupts=trace_interrupts,
trace_symbols=trace_symbols, trace_memory=trace_memory, trace_interrupts=trace_interrupts,
show_logs=show_logs, engine_dir=engine_dir,
)
self.machines: list[str] = list(self._b.machines)
Expand Down Expand Up @@ -377,16 +381,19 @@ class _RenodeBackend:
"interrupts": ("ReadInterrupts", _interrupt), "symbol_trace": ("ReadSymbolTrace", _symbol_trace)}

def __init__(self, machines: list[dict], *, base: Path, work: Path, media, services, quantum,
trace_symbols, trace_interrupts, show_logs, engine_dir):
trace_symbols, trace_memory, trace_interrupts, show_logs, engine_dir):
self._base, self._work = base, work
ns = load(engine_dir)
spec = ns.SessionSpec()
spec.TraceInterrupts = trace_interrupts
spec.ShowBackendLogs = show_logs
for sym in trace_symbols:
spec.TraceSymbols.Add(sym)
for region in trace_memory:
spec.TraceMemory.Add(region)
for m in machines:
self._add_machine(spec, m["name"], m.get("repl"), m.get("mcu"), m.get("overlay"), m["elf"])
self._add_machine(spec, m["name"], m.get("repl"), m.get("mcu"), m.get("overlay"), m["elf"],
m.get("symbolsElfPath"))
for med in media or []:
sm = spec.AddMedium(med["type"], list(med.get("connect") or []))
sm.Strict = bool(med.get("strict", False))
Expand All @@ -407,22 +414,23 @@ def __init__(self, machines: list[dict], *, base: Path, work: Path, media, servi
raise SimError(f"could not start the simulation: {exc}") from exc
self.machines = list(self._session.Machines)

def _add_machine(self, spec, name: str, repl, mcu, overlay, elf) -> None:
def _add_machine(self, spec, name: str, repl, mcu, overlay, elf, symbols_elf) -> None:
"""Platform file → AddMachine; model name → the local model library when
$SIMANTIC_MCU_LIB is set (development), else the engine's own resolver
(~/.sim_cache, then the backend with stored credentials — like `sim --mcu`)."""
elf_path = str(self._base / elf)
if repl is not None:
if overlay is not None:
raise ValueError("overlay= applies to mcu=, not repl=")
spec.AddMachine(name, str(self._base / repl), elf_path)
return
if os.environ.get(MCU_LIB_ENV):
sm = spec.AddMachine(name, str(self._base / repl), elf_path)
elif os.environ.get(MCU_LIB_ENV):
platform = platform_path(mcu, self._base / overlay if overlay else None, self._work)
spec.AddMachine(name, str(platform), elf_path)
return
fragment = (self._base / overlay).read_text() if overlay else None
spec.AddModel(name, mcu, elf_path, fragment)
sm = spec.AddMachine(name, str(platform), elf_path)
else:
fragment = (self._base / overlay).read_text() if overlay else None
sm = spec.AddModel(name, mcu, elf_path, fragment)
if symbols_elf:
sm.SymbolsElfPath = str(self._base / symbols_elf)

def _service_args(self, args: str) -> str:
# A script path is the common case; make it absolute against cwd=.
Expand Down
24 changes: 24 additions & 0 deletions tests/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,27 @@ def test_expect_timeout_is_assertion():
with pytest.raises(ExpectTimeout) as exc:
sim.expect("never printed by anything", timeout=2)
assert isinstance(exc.value, AssertionError)


@needs_engine
def test_symbols_elf_single_machine():
"""symbols_elf= reaches SessionMachine.SymbolsElfPath (simantic-core#307)
without upsetting the run. No stripped-image fixture is wired up here, so
the companion ELF is the image itself — a smoke test of the plumbing
(loading the same ELF's symbols twice isn't a real symbol-resolution
check), not a claim that a stripped image + a genuinely separate
companion ELF resolves symbols (verified manually against
sim-fixtures/build/zephyr/zephyr.elf stripped with arm-none-eabi-strip)."""
with Sim(elf=ELF, repl=REPL, uart=UART, symbols_elf=ELF) as sim:
m = sim.expect(r"RESULT: (PASS|FAIL)", timeout=120)
assert "PASS" in m


@needs_engine
def test_symbols_elf_path_in_scenario():
"""symbolsElfPath in a scenario machine dict — same key as the CLI's
scenario YAML (simantic-cli#184), so a scenario dict stays copy-pasteable."""
scenario = {"machines": {"machine": {"repl": REPL, "elf": ELF, "symbolsElfPath": ELF}}}
with Sim(scenario=scenario, uart=UART) as sim:
m = sim.expect(r"RESULT: (PASS|FAIL)", timeout=120)
assert "PASS" in m
Loading