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 restoreputs 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 takeprobe). 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 viaRENODE=. - 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.replfile. - 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 intests/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_allowedinbench.toml, either a list of probe serials ortrue.trueis 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 viaSILIGENT_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 restorewrites 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
cl430compiler 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.