# Contributing to Rigless
Thanks for your interest in Rigless. Whether you are fixing a typo, modeling a peripheral, or bringing up a whole board, we are glad you are here. Rigless is one crate in one tree: the runtime, the SoCs, the machines, the generator, and the command line all live here, and one merge request can change any of them together. This guide covers how to get set up, how we work, and where to ask when something is unclear.
## Ways to help
- **Report a bug.** Open an issue with what you ran, what you expected, and what happened. The firmware image and the `rigless run` command line that reproduce it are gold.
- **Improve the docs.** If something confused you, it will confuse the next person too. Small doc fixes are some of the most valuable merge requests we get.
- **Model a peripheral or a chip.** Every MCU block and sensor chip that gets modeled lets more firmware run unmodified. Each model's module opens with a table of what it covers, and an instance the MCU has no model for is register-only. A new MCU peripheral becomes a field of its SoC's MCU type, one per instance of its layout, and comes with a check in that SoC's conformance firmware — see below.
- **Add an SoC or a machine.** An SoC is a vendor's SVD, the tables generated from it, and an MCU type holding a model per instance as a field; a machine is a board struct that **has** one or more of those MCUs plus the crystals, rail and chips the board fits. They live in `src/soc` and `src/machine`, and the README walks through adding one.
- **Ask questions.** Open an issue with the question label. There are no dumb questions about emulators; they are full of surprises.
## Getting set up
You need a recent stable Rust; `rust-toolchain.toml` pins the exact version and rustup picks it up automatically. A few cargo tools make the checks faster to run locally:
```sh
cargo install cargo-binstall
cargo binstall cargo-nextest cargo-deny cargo-machete
```
Then build and run the tests:
```sh
cargo build
cargo nextest run --all-features
```
Running firmware needs the QEMU sidecar, which is built from our branch of the QEMU repository; the README says where to get a prebuilt one and which release pairs with which. The tests that run firmware fail rather than skip without it.
## Finding something to work on
Issues labeled `good first issue` are scoped so you can finish one in an afternoon. If you want to take on something larger, say so on the issue first so we can talk through the approach together and nobody duplicates work. For anything that touches the sidecar protocol, virtual time, or the shape of a machine, an issue discussion before code saves everyone a rewrite.
## Making a change
1. Fork the repository and create a branch for your change.
2. Keep the change focused. One fix or one feature per merge request is much easier to review, and gets merged faster.
3. Add a test for behavior you add or fix. If you are not sure what to test, ask on the issue and we will help.
4. Run the same checks CI runs, so there are no surprises:
```sh
cargo fmt --all
cargo clippy --all-targets --all-features -- -D warnings
cargo nextest run --all-features
cargo test --doc --all-features
cargo deny check
```
The tests that run firmware need `rigless-sidecar` on `PATH`, and they fail rather than skip when it is missing, naming what to install. CI runs those four and more: the licence headers, a build with the default features off so the feature gates cannot rot, the docs with warnings denied, `cargo machete`, the loom model and Miri over the shared-memory code, a publish dry run, and the conformance job below.
5. Open a merge request. Describe what changed and why, and mention the issue it relates to. If it is not ready yet, mark it as a draft; early feedback is welcome.
We aim to respond to every merge request within a few days. Reviews are conversations: if a comment is unclear, ask, and if you disagree, say so. We are all trying to get to the best version of the change.
## Models come with a conformance check
A unit test proves what somebody thought of. Beside them, each SoC has a **conformance firmware** in `firmware/<soc>/conformance` that runs on a bench machine under a real core and drives the part through the manual's own numbered sequences, and a gate in `tests/conformance.rs` that holds it to two things:
- **Breadth.** Every register of every instance the MCU has a model for is reached by the run. The report names the ones that were not, and a non-empty list fails. `rigless run --machine <soc>-bench --coverage PATH` writes it.
- **Depth.** Every check passes, and every check asserts *what came back* — a value, or an instant against the clock the firmware just configured — rather than that a flag moved.
So a merge request that adds or changes a model adds or changes a check, and the check has to be shown to die when the behaviour behind it is broken. Break the model by hand, watch the check go red, put it back. A check nobody can break is a finding, not a check.
Two rules keep the job inside its budget, which is ten minutes for everything: every wait in the firmware is bounded and reports a timeout for its own check, so one broken model costs one red check rather than a hung job; and a check picks the fastest configuration that still proves the behaviour, because in virtual time a check is free and waiting is not.
For a bus — SPI, I2C, a UART — there are seven things a check has to cover before that model is done, listed in `firmware/<soc>/conformance/README.md` with where the SPI stands against them. The seventh is the one people skip: assert the peer's whole recorded sequence, byte for byte and in order, and give it a peer that answers something known and **different** from what it was sent. A loopback that echoes hides a reversed bit order.
One thing in that firmware looks like duplication and is not: its register addresses and field positions are hand-written from the manual even though the generated tables hold the same numbers. The two are separate witnesses, a test holds them against each other, and generating both from one table would quietly destroy that. The exception is written where the addresses are.
## A few things about the code
- **Determinism is the product.** The same firmware, plant, and seed must produce the same trace every time. That means no wall-clock time, no unseeded randomness, and no iteration over unordered collections in the emulation path; a machine with several MCUs steps them in the machine's order every round, so that is fixed too. Clippy is configured to catch the common cases, and reviewers will point out the rest kindly.
- **Comments explain why.** The code says what it does; a comment earns its place by saying why, citing a manual's section, or noting a deliberate shortcut. Public items get a one-line doc comment, private ones a plain `//` where they need one at all.
- **Small and boring wins.** If the standard library or a crate we already depend on does the job, use it. New dependencies are welcome when they carry their weight; mention them in the merge request so we can check the license together.
- **Every source file starts with the two-line license header** you will see at the top of any file here. CI checks for it, so copy it into new files.
## Commit messages
A short imperative sentence that says what the commit does, such as `Add the SPI status flag model`, is all we need. The merge request description is the place for the why and the details.
## License
Rigless is licensed under the Apache License 2.0. By submitting a contribution you agree that it is licensed under the same terms, as section 5 of the license describes. There is no paperwork to sign.
## Security
If you find a security problem, please email wezley.sherman@riglesslabs.com instead of opening a public issue, and we will work with you on a fix and credit.
## Community
Be kind, assume good intent, and remember there is a person on the other side of every comment. We want Rigless to be a place where someone modeling their first peripheral feels as welcome as someone who has shipped flight software for a decade.