Skip to main content

Simantic

Test your firmware without a board.

PyPI Python versions MIT licence

Nothing to plug in, nothing to flash. Simantic boots your real ELF on a simulated microcontroller and hands you the whole machine from Python. Watch it print, press a button, read a variable straight out of RAM.

pip install simantic
from simantic import Sim

with Sim(elf="fw.elf", mcu="STM32F401RE", uart="usart2") as sim:
    sim.expect("ready")
    sim.inject_gpio("gpioc", 13, True)      # press the user button
    sim.expect("button pressed")
    assert sim.read_u32("press_count") == 1

That is a whole test. No probe, no breakpoint, no waiting on hardware.

Three things you get that a bench cannot give you:

  • See inside. Read any variable, register, or RTOS thread while the firmware runs, without halting it.
  • Poke it. Press buttons, send CAN frames, feed the radio, all from your script.
  • Repeat exactly. Time moves only when you ask, so a run comes out the same every time, on your laptop and in CI.

The simulator lives inside your Python process, so there is no server to start and no port to talk to.

Alpha, version 0.3.x. We are still moving things around, so the API can change without a deprecation period. Pin an exact version (simantic==0.3.0) if you depend on it, and please hold off on production pipelines for now. Tell us what breaks.

Setup

pip install is the whole setup. The first Sim(...) downloads the engine it needs into ~/.simantic/ and checks it against the published checksum. The wheel on PyPI holds only Python code; the simulators are never inside it.

Sign in once if you want to name MCUs by part number:

simantic auth

That opens a browser tab, much like gh auth login, and saves a token to ~/.sim_id. In CI, pipe one in instead: echo $TOKEN | simantic auth. If you bring your own platform file (repl="board.repl"), you need no account at all.

Already have the sim binary? Put it on PATH or point $SIMANTIC_SIM at it. simantic status shows what resolved.

Pick your engine

The same script runs on either engine. You choose per simulation:

Sim(elf="fw.elf", mcu="STM32F401RE", uart="usart2")                  # Renode engine, the default
Sim(elf="fw.elf", mcu="STM32F401RE", uart="usart2", backend="rust")  # our Rust engine

The Rust engine is a small extension module, runs one machine, and is considerably faster. Anything it cannot do yet, such as multi machine scenarios or CAN and radio injection, raises simantic.NotSupported and names the gap instead of quietly doing nothing. You can follow what each engine covers in simantic-core#183.

Testing with pytest

Take the sim fixture and write ordinary tests:

def test_timer_irq_fires(sim):
    s = sim(elf="fw.elf", mcu="STM32F401RE", uart="usart2")
    s.expect("fired=1", timeout=8)
    assert s.read_u32("fired") == 1

The engine starts once per worker rather than once per test, and every machine is closed for you. When a test fails, its UART transcript is attached to the report, because that is usually the evidence you want.

--sim-backend=renode|rust|both chooses the engine. With both, each test runs on each and the engine name appears in the test id. Anything an engine cannot do is reported as a skip with the reason, so one suite can target both and stay honest about what each covers.

One tip worth real time: on the Renode engine, every hand off between Python and the simulation costs a few hundred microseconds. Reading is free, pausing and resuming is not. Prefer expect(), which crosses once, over a loop that polls every millisecond. On the Rust engine, polling is essentially free.

If you keep test.yaml fixture manifests, installing the package also turns each one into its own pytest item, so you get -k filtering, --junitxml, and xdist for free. Multi machine manifests need the --scenario runner and report as skips for now.

For a single run with no assertions in the middle, there is run_firmware(...):

run = simantic.run_firmware("build/zephyr.elf", mcu="STM32F401RE",
                            expect=["RESULT: PASS"])
assert run.passed, run.failure_report()

Telemetry

When you are signed in, we count the shape of a pytest session (how many tests ran, passed, failed, skipped) and which SDK calls you make, by name only. It is one request per pytest run, buffered in ~/.simantic/usage.jsonl, and uploaded at most hourly, so nothing ever waits on the network.

We do not send file paths, project names, test names, firmware, or simulation output. Those are yours. Turn it off whenever you like:

export SIMANTIC_TELEMETRY=0     # or DO_NOT_TRACK=1

Questions

We would genuinely like to hear how this goes for you, especially if something is confusing or broken. Write to founder@simantic.dev, or open an issue.

License

MIT. The simulators it drives are separate software under their own terms.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

simantic-0.3.0.tar.gz (53.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

simantic-0.3.0-py3-none-any.whl (42.6 kB view details)

Uploaded Python 3

File details

Details for the file simantic-0.3.0.tar.gz.

File metadata

  • Download URL: simantic-0.3.0.tar.gz
  • Upload date:
  • Size: 53.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for simantic-0.3.0.tar.gz
Algorithm Hash digest
SHA256 92932a15e4fadb847ea7d91e99f6cf26a93358ebff03b799e1435ec8093ec0d2
MD5 07ab8de6238e34da252a5a7b40ced9dd
BLAKE2b-256 47206a80bcf79dd2de848ca73db4a0b823bd255ce91cfbe644f03e5b6dea65ad

See more details on using hashes here.

Provenance

The following attestation bundles were made for simantic-0.3.0.tar.gz:

Publisher: ci.yml on simantic-dev/pippy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file simantic-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: simantic-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 42.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for simantic-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3a9d18084f14f305de9dee47f86d7a27d46cfc7b07f4cd94f38eeb3db3d1325a
MD5 17c2bb3401cd9cbffdd68d56c126a343
BLAKE2b-256 8b01bb839294b8fdd6e88120d64d85faeda355e9f29fa8fbc7dbfe0ea7d5136e

See more details on using hashes here.

Provenance

The following attestation bundles were made for simantic-0.3.0-py3-none-any.whl:

Publisher: ci.yml on simantic-dev/pippy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.3.1

2 files

This release

0.3.0 This release

2 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page