Skip to main content

Siligent

An open-source framework that lets AI coding agents flash, test and measure real microcontroller boards, behind safety rules, so a firmware change is proven on the chip, not just compiled.

Try it in a minute (no hardware)

python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
nice -n 19 make -j3 -C firmware/l476     # needs arm-none-eabi-gcc
.venv/bin/python scripts/demo.py         # needs Renode, see "Virtual board"

Status: early alpha, working on two real boards

Board Chip Probe Energy
NUCLEO-L476RG STM32L476RG (Cortex-M4) ST-LINK, via pyOCD needs an external meter
MSP-EXP430FR5994 MSP430FR5994 (16-bit, FRAM) on-board eZ-FET, via TI's libmsp430 built in: TI EnergyTrace

Expect the interfaces to change.

  • Detects probes by USB ID and chips by their device ID, then loads that board's rules.
  • Flashes through a safety gate; refusals say which rule fired and why.
  • Runs on-device tests and reports pass/fail and CPU cycles per test, plus measured energy on the MSP430 LaunchPad.
  • Backs up program memory before every write; siligent restore puts a backup back.
  • Logs every action, allowed or refused.
  • MCP server so agents (Claude Code, Cursor, others) can use all of the above.
  • With several boards connected, pick one with --probe <serial> (agent tools take probe). A pinned probe that is missing is an error, never a reason to use another board.

Two external meter drivers are written and unit-tested but have not run on a real meter yet: the INA219 (active current only) and the Nordic PPK2 (down to sub-microamp, see plugins/siligent-ppk2, a separate GPL-2.0 package because its library is GPL).

Meters are plugins: a package registers a factory under the siligent.meters entry-point group. When a meter powers the board, the core checks the voltage against the board's supply range before any plugin runs.

Virtual board (Renode)

Add --virtual to any command to use a simulated board instead of real hardware (agent tools take virtual: true). The same safety rules apply. Both boards have a virtual version; Siligent picks the one the firmware was built for (or the board in bench.toml).

.venv/bin/siligent --virtual test firmware/l476/build/tests.elf
.venv/bin/siligent --virtual test firmware/msp430fr5994/prebuilt/tests.elf   # no TI compiler needed
  • Needs Renode (tested with 1.17.0). It is found on PATH, in ~/.local/opt/renode-*/, or via RENODE=.
  • Renode has no model of either chip, so src/siligent/boards/renode/ describes just enough of each to run test firmware: core, memory, device ID, and the cycle counter (DWT on the L476, Timer_A0/A1 on the MSP430). Other peripherals are not modelled; their limits are listed at the top of each .repl file.
  • The virtual MSP430 runs on Siligent's own version of Renode's MSP430X core (src/siligent/boards/renode/SiligentMSP430X.cs, compiled by Renode at start). It charges each instruction the cycles TI's manual gives for it, where Renode counts one per instruction, and it fixes the multi-bit rotates, which Renode 1.17 gets wrong and TI's software floating point relies on. Tested against the manual in tests/test_msp430x_core.py.
  • Cycle counts are simulated, not timed on the chip, and every virtual result says so in its note. No energy.
    • L476: within about 10-30% of the real chip for the example tests (the real chip is slower, mostly flash wait states).
    • MSP430: within 2% of the real chip for the example tests (1585, 15055, 395 and 272 cycles against 1557, 15052, 393 and 270 on the LaunchPad). A test longer than 65,535 cycles reports a wrong count: Renode's timer never signals the overflow the cycle counter relies on.

Quick start

python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pyocd pack install stm32l476rg
nice -n 19 make -j3 -C firmware/l476
cp bench.example.toml bench.toml   # then set flash_allowed = true when the bench is safe to flash

.venv/bin/siligent detect
.venv/bin/siligent test firmware/l476/build/tests.elf
.venv/bin/siligent flash firmware/l476/build/demo_refused.elf   # refused on purpose
.venv/bin/siligent log

Every command prints JSON. Other commands: rules, check (image checks without hardware; the board is worked out from the file), read, write, restore.

Agent access (MCP, stdio): run siligent-mcp. Tools: detect_hardware, board_rules, check_image, flash, run_tests, read_memory, write_memory, action_log.

What the safety gate enforces (NUCLEO-L476RG)

Rules live in src/siligent/boards/stm32l476rg.toml.

  • Flash only after a person confirms the bench is safe to flash: flash_allowed in bench.toml, either a list of probe serials or true. true is refused when several boards are connected, so allowing one board never allows another.
  • Flash only if the chip matches the rules (device ID 0x415) and read protection is level 0.
  • Firmware must sit inside flash, start with a valid vector table, and must not contain the option-byte unlock keys (heuristic: catches firmware that could lock the chip).
  • Host writes only to RAM, peripherals and core debug registers; never to the flash controller, option bytes or OTP.
  • SWD pins (PA13, PA14) cannot be switched out of debug mode by a host write.
  • The core is halted (with a reset) before erasing: erasing under running firmware locks the core up.

MSP430FR5994 LaunchPad

nice -n 19 make -j3 -C firmware/msp430fr5994    # TI's cl430, from Code Composer Studio
.venv/bin/siligent check firmware/msp430fr5994/build/demo_refused.elf   # refused: would lock the debug port
.venv/bin/siligent test firmware/msp430fr5994/build/tests.elf
  • Needs TI's debug library, libmsp430.so, which comes with Code Composer Studio. It is found under ~/ti/ccs*/ or via SILIGENT_LIBMSP430=.
  • Siligent never updates the debugger's firmware. If the library asks for an update, it stops and says so; update once with TI's tools.
  • The eZ-FET supplies about 3.3 V whatever is asked for (3.31 V measured); the measured supply is checked against the chip's range too.
  • siligent restore writes the whole of program memory back (FRAM is not erased first, so skipping bytes would leave newer code behind) and reads it back to confirm.
  • The firmware builds with TI's cl430 compiler from CCS (TI_CGT= to point elsewhere). CI does not build it yet.

What the gate enforces on this chip (src/siligent/boards/msp430fr5994.toml):

  • 0xFF80-0xFF8F hold the JTAG, BSL and IP-encapsulation signatures. Writing 0x5555 to the JTAG signature locks the debug port for good. Firmware may only contain 0xFF there, and those bytes must read 0xFF on the chip before any flash.
  • Firmware containing mov #0x5555, &0xFF80 (or 0xAAAA) is refused: code that would lock the chip at run time.
  • Host writes only to RAM, LEA RAM and peripherals; never to FRAM, the bootloader, the device descriptor (calibration data), the FRAM controller or the memory protection unit.
  • Firmware built for another architecture is refused (image.wrong_architecture).

Energy with TI EnergyTrace

On this LaunchPad the debugger measures energy itself, so siligent test reports it with no extra hardware ([meter] type = "auto", the default). An example run of firmware/msp430fr5994/build/tests.elf at 1 MHz:

Measured
Energy while the tests ran 14.2-15.1 uJ (14 runs)
Time 21-22 ms (the tests' own CPU time: 17.3 ms)
Average current about 200 uA at 3.29 V
Sleep current afterwards (LPM4) below EnergyTrace's resolution

What this is and is not:

  • It is the whole target side of the board, not the chip alone.
  • The chip runs with the debugger released: attached, the debugger keeps the clocks running and low-power modes never show.
  • The harness wakes EnergyTrace with 0.2 s of busy work, waits 0.3 s in LPM0, runs the tests, then sleeps in LPM4. The tests are found as the step up between the wait and the sleep, using the debugger's energy counter. If the step's length does not match the tests' CPU time, no energy is claimed.
  • Per-test figures (energy_uj_estimate) split the measured total by CPU time. They are estimates; measuring each test separately needs markers.

Writing on-device tests

The harness is in firmware/common/ and is shared by both boards; see firmware/common/tests.c:

#include "bp_test.h"
BP_TEST(my_test) { BP_ASSERT_EQ(add(2, 2), 4); }

Results go to a RAM mailbox (bp_results) that the host reads after the run. Each chip supplies a cycle counter in bp_port.c: the DWT counter on the STM32, Timer_A on the MSP430 (which has none).

Tests

.venv/bin/pytest -q

The virtual-board tests skip if Renode or the built firmware is missing. Set SILIGENT_REQUIRE_RENODE=1 to make them fail instead, as CI does. CI (.github/workflows/ci.yml) runs the unit tests on Python 3.11-3.13 and runs the example firmware on both virtual boards. CI builds the L476 firmware; for the MSP430 it runs the copies in firmware/msp430fr5994/prebuilt/, built with TI's compiler from Code Composer Studio. After changing the firmware, rebuild them with make -C firmware/msp430fr5994 prebuilt and commit them.

Licence

Apache-2.0 (see LICENSE). The PPK2 plugin in plugins/siligent-ppk2 is a separate package under GPL-2.0, because the library it uses is GPL-2.0.

Metadata

Release files for siligent 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for siligent 0.2.0
File Size Uploaded Provenance Actions
siligent-0.2.0.tar.gz 64.0 kB Without provenance Details for siligent-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for siligent 0.2.0
File Interpreter ABI Platform Actions
siligent-0.2.0-py3-none-any.whl Python 3 none any Details for siligent-0.2.0-py3-none-any.whl

Total release size: 122.9 kB

Release files / siligent-0.2.0.tar.gz

Download URL siligent-0.2.0.tar.gz
Size 64.0 kB
Tags Source
SHA-256 checksum
How to use checksums
2c719e7951dc56d0554fc2fd0cc446b527482ca6c5ed54c9e1613daae954060e
BLAKE2b-256 checksum
How to use checksums
26d57fb5c2b87525048cc25b1df3964c764129167bb75d487084841f1b0a0ca4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / siligent-0.2.0-py3-none-any.whl

Download URL siligent-0.2.0-py3-none-any.whl
Size 58.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6811a7030763ced51192cf7a2898ba3727de50ae9b71013f8635292ff65c57e3
BLAKE2b-256 checksum
How to use checksums
eb0685e51c1111ec91aaffeeb23a596abe6d819b5960c36ae697ede8f7766c09
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Security for siligent 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

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