rigless 0.0.5

Virtual hardware-in-the-loop runtime: run unmodified firmware on an emulated MCU with every peripheral, chip model, and unit of virtual time in Rust; a fork of QEMU is built in and executes the instructions
Documentation
rigless-0.0.5 has been yanked.

rigless

A hardware-in-the-loop runtime for embedded firmware, in Rust, and the open half of Rigless. An unmodified firmware image runs on an emulated MCU: every peripheral, every chip on the board and every unit of virtual time is a Rust model, and a fork of QEMU built into the crate executes the instructions, calling those models from its own thread for every access.

It is written for any part and any board. An SoC is its vendor's SVD, the register tables generated from it, and the models written against those tables; a board is a struct that has its MCUs, its crystals and the chips on its buses. Each of those is a public trait, and implementing one is how a part or a board is added. The parts and boards this build carries are behind Cargo features, so wherever one is named it is an example and not the subject.

Documentation

rigless-labs.gitlab.io/rigless is the API documentation of main, and the guide is its front page: what you implement, how a peripheral is modeled, how a machine is composed, and how to add an SoC, a model, a chip, a machine or a core family. Every seam is a trait with its contract in its own module.

Start there for anything beyond the setup and the two commands below.

Setup

The emulator is built in and is not optional, so building this needs a C compiler, python3 with its venv and wheel packages, meson, ninja, pkg-config and the glib-2.0 headers, as well as a recent stable Rust. rust-toolchain.toml pins the Rust version and rustup picks it up.

From crates.io:

cargo install rigless

From a checkout, which is what you want if you are changing the emulator:

git submodule update --init --depth 1 qemu
git -C qemu submodule update --init --depth 1 subprojects/dtc subprojects/keycodemapdb
cargo install --path .

Those are the fork and the two subprojects its build reads; --recursive would also fetch the fifteen ROM submodules nothing here compiles. The two ways of installing differ only in where the emulator's source comes from. A checkout builds the submodule at qemu/ and never reaches the network. A crate from the registry cannot carry 169 MB of emulator, so it fetches the source archive named in emulator.pin and checks it against the digest there before unpacking it; RIGLESS_EMULATOR_TARBALL points that at a local copy for an offline or air-gapped build. Either way the emulator is pinned to one commit of rigless-labs/qemu, so which emulator goes with which crate needs no table.

Usage

rigless run executes a firmware image on one MCU of a machine and prints every peripheral access, then its stop, the addresses no register covers, and the interrupts it has no line for:

rigless run --machine <name> --until 2ms firmware.elf
read  t=20 n=5 pc=0x080001c0 at=0x40023830 w=32 v=0x00100000
write t=28 n=7 pc=0x080001c6 at=0x40023830 w=32 v=0x00100001
stop t=2000000 n=500000 reason=deadline
unmodeled at=0x40023800 pc=0x080001e0
Flag What it does
--machine NAME The machine to run on. A build carries the machines machine::all() returns and no others
--target MCU Which MCU to run, on a machine that has several. A process runs one, since the emulator has one set of state
--until TIME How much virtual time to run, with a unit: ns, us, ms, s. Without it the run ends when you interrupt it
--gdb PORT Serve GDB on that port while the target runs. --wait-for-debugger holds the core before its first instruction instead
--coverage PATH Write what the run reached: every instance a model is bound to, and every register of one that was not touched
--actuators PERIOD Print the board's outputs whenever one changes, read every period of virtual time
--host WIRE=PORT Listen for a host on that wire at that port instead of the one the board names; 0 takes any free port
--link Be the target of an engine on stdin and stdout, a step at a time, and print nothing else there

Virtual time is not wall-clock time, so any deadline you can name passes faster than you can type. A wire the board says a host may sit on gets a TCP socket on the loopback interface, and the run says so before it starts; whatever connects there is the far end of that serial port.

rigless svd generate turns a vendor's CMSIS-SVD file into the register tables of an SoC. The directory must hold exactly one .svd file, and the tables land beside it:

rigless svd generate src/soc/<name>

The trace and benchmark commands follow.

Building and testing

.gitlab-ci.yml is the source of truth. Every job's script is a command you can run yourself, so this page does not copy them and cannot tell you something the pipeline has stopped doing. Your merge request runs all of them anyway.

The emulator has one set of global state and so runs once per process, which is why the suite runs under cargo nextest rather than cargo test. The tests that boot a firmware need one built under firmware/, and fail rather than skip without it, saying so.

Contributing

CONTRIBUTING.md covers how to get set up, how we work, and what a change is expected to carry. Models come with a conformance check, and determinism is the product: no wall-clock time, no unseeded randomness, and no iteration over unordered collections in the emulation path.

What it is not

The open runtime boots firmware, runs unit tests in CI, serves GDB, writes traces, and carries the seam a plant is stepped against. Stepping a plant in lockstep, injecting faults, and replaying runs bit-for-bit is the Rigless engine, rigless-hil, which is a separate, commercial binary, and the Plant trait it steps is rigless-plant in rigless-labs/hil. Nor is it an emulator of one board: a part and a board are modules behind a feature, and the runtime knows neither by name.

License

GPL-2.0-or-later. The emulator is built in and is not optional, so what you receive is a combined work with a program derived from QEMU and carries that licence.

The Rust sources here are themselves offered under MIT or Apache-2.0, at your option; see LICENSE-MIT, LICENSE-APACHE and NOTICE. That dual offer is what makes the combination lawful rather than a courtesy: Apache-2.0 adds a patent-retaliation condition that GPL-2.0 does not permit, so the GPL takes these sources under MIT instead. By submitting a contribution you license it under both, as section 5 of the Apache license states; no sign-off or paperwork is needed.

Rigless is a trademark of Rigless Labs.