# rust-embedded/riscv: the crate layer that makes Rust on a RISC-V microcontroller possible

> This repository holds the low level crates for writing Rust on RISC-V microcontrollers, from CPU register access and intrinsics through startup code, interrupt handling and semihosting, and it is where the Rust embedded team keeps the pieces that a board support package depends on. This looks at how the crates fit together and what they leave to you.

**rust-embedded/riscv** — Low level access to RISC-V processors

- Repository: https://github.com/rust-embedded/riscv
- Stars: 1,133 · Forks: 200
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/rust-embedded-riscv

## Six crates, one layer at a time

The repository contains various crates useful for writing Rust programs on RISC-V microcontrollers, and the readme lists what each one is for.

The `riscv` crate is for CPU registers access and intrinsics. The `riscv-peripheral` crate gives interfaces for standard RISC-V peripherals. The `riscv-rt` crate is startup code and interrupt handling. The `riscv-semihosting` crate provides semihosting for RISC-V processors. The `riscv-target-parser` crate is a utility crate for parsing RISC-V targets in build scripts. The `riscv-types` crate holds common traits to be implemented by RISC-V PACs.

That list is a stack, and reading it bottom up tells you what a firmware project actually needs from the ecosystem. `riscv-types` is the foundation because it defines the traits, `riscv-peripheral` provides the interfaces for the peripherals the ISA standardises, `riscv` is the layer that talks to the processor itself, and `riscv-rt` is what runs before your `main`.

The remaining two solve problems that are easy to underestimate. Semihosting is how firmware talks to a debugger or a test harness without a full operating system underneath, and the target parser is what lets a build script ask what the current target is, which matters because RISC-V is not one architecture but a base plus a set of extensions, and the code to compile can depend on which of them are present.

The project is developed and maintained by the RISC-V team of the rust-embedded working group, and the crates are published on crates.io.

## The processor layer and the peripheral layer are separate crates

The split between `riscv` and `riscv-peripheral` is the one that matters for how you structure a project.

`riscv` is about the processor. It gives you access to CPU registers and provides the intrinsics, which are the compiler builtins that map onto the RISC-V instruction set, things like reading the cycle counter, issuing a fence, or performing a supervisor call. None of that is specific to a chip.

`riscv-peripheral` is about the peripherals the ISA standardises, and it gives you interfaces for them, so the same code can drive the same standard peripheral on different silicon.

`riscv-types` is the piece that makes that possible, holding the common traits that RISC-V PACs, peripheral access crates, are expected to implement. A PAC, a peripheral access crate, is the vendor supplied crate for a specific chip, and it is not in this repository, because it cannot be: the traits here define the contract, and the chip specific crate implements it.

So a firmware project using this ecosystem ends up with three things in its dependency tree: this repository's crates for the processor and the runtime, a vendor PAC for the chip, and a HAL, if the vendor supplies one. The `riscv-peripheral` crate is what lets the code above the HAL stay the same across chips.

That layering is the reason the crates are separate rather than one large crate. It also means the repository's scope stops deliberately at the processor: it is not a board support package, it is not a HAL, and it does not know anything about your board's pins, clocks or peripherals.

## Startup and interrupts, and the cost of a bare metal Rust

The `riscv-rt` crate is startup code and interrupt handling, and it is the crate that makes the difference between a Rust program that runs on hardware and one that does not.

On a microcontroller there is no operating system to call `main` for you. Something has to set up the stack, copy the initialised data into RAM, set up the trap vector table, and call the Rust entry point. That is startup code, and `riscv-rt` provides it, along with the interrupt handling that sits on top of it.

This is also the crate that most affects what you can build. A bare metal Rust program has no heap by default, no `std` in many configurations, and no `println!` unless you have configured a defmt logger. That last point is why the workspace has a `defmt` dependency: defmt is the compact logging format designed for embedded targets, and it is what you format your diagnostics with instead of `std` formatting.

The practical implication for a team evaluating this is that the friction is not in the RISC-V instructions, it is in the runtime contract. You have to agree with the `riscv-rt` crate on the memory layout, the trap handling, the interrupt priorities, and where the panic handler goes, and every one of those is a decision you have to make and get right before the first line of your own logic runs.

The `riscv-semihosting` crate fits into the same world. Semihosting is the mechanism by which firmware running on bare metal can call out to a debugger or a test harness for things like writing output, and having a crate for it means your tests can print without a serial port and without `std`.

## The target parser exists because RISC-V is a base plus extensions

The `riscv-target-parser` crate is described as a utility crate for parsing RISC-V targets in build scripts, and it is worth a paragraph on its own because it explains a structural feature of the architecture.

RISC-V is not a single instruction set. It is a base integer instruction set plus optional extensions: the compressed instruction set, the floating point variants, the supervisor and user privilege modes, the vector extension, and so on. A single target name can therefore correspond to quite different hardware, and the code that is correct for one target may not assemble for another.

A build script that needs to know what it is targeting has to parse the target triple, and that is what this crate does. It gives the build script a way to branch on which extensions are enabled, for example to compile in a vector routine only when the target supports it, or to select the right instruction path.

The repository also has a `riscv-macros` directory, which is not in the readme's list of crates but is in the workspace members, and it is the natural home for procedural macros the ecosystem needs, such as generating register accessors.

The point for a reader is that a large part of what makes Rust on RISC-V pleasant is not the register access, it is the surrounding machinery for handling the variability of the target, and that machinery lives here rather than in every firmware project.

## The workspace and how the crates are tested

The `Cargo.toml` at the root is a workspace, and it says more about the project's shape than the readme does.

The members are the six published crates, `riscv`, `riscv-macros`, `riscv-peripheral`, `riscv-rt`, `riscv-semihosting`, `riscv-target-parser` and `riscv-types`, plus three test crates and a task runner: `tests-build`, `tests-qemu`, `tests-trybuild` and `xtask`. The default members are narrower, covering the crates you would actually depend on:

```toml
default-members = [
    "riscv",
    "riscv-macros",
    "riscv-peripheral",
    "riscv-rt",
    "riscv-semihosting",
    "riscv-types",
]
```

The three test crates tell you how this project is verified. `tests-qemu` is the important one: running the firmware under QEMU means the startup code, the trap handling and the interrupt paths are exercised without hardware, which is the only practical way to test that layer continuously. `tests-trybuild` is the standard Rust mechanism for compile-fail tests, where each case is a snippet of code that is expected not to compile, and the error output is compared. For a crate that generates code through macros and exposes registers through a type-safe API, compile-fail tests are how you check that the misuse cases still produce the errors you intend.

The rest of the tree supports that: a `ci/` directory for continuous integration, an `xtask` directory for the task runner that automates repository chores, a `typos.toml` for the spell checker, and a `CODE_OF_CONDUCT.md`.

The workspace also pins a single `defmt` version for every crate that uses it, through the workspace dependencies, so a firmware project does not end up with two incompatible logger versions in its tree.

## Licensing, and where this repository stops

The licence arrangement is the standard rust-embedded one. The repository carries `LICENSE-APACHE.md` and `LICENSE-MIT.md`, and the contribution clause states that unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 licence, shall be dual licensed as above without any additional terms or conditions.

So the project is Apache-2.0 or MIT at your choice, and that covers both the crates and any patch you send upstream.

On the boundary of the project, the honest answer is that this is a processor level library. It gives you registers, intrinsics, standard peripheral interfaces, startup and interrupts, semihosting, and build-time target parsing. It does not give you a HAL for a chip, a board support package, a driver for a specific peripheral, a linker script, or a way to flash anything.

For a team that means the dependency list does not end here. You need the vendor PAC for your silicon, and typically a HAL on top, and a linker script and a memory map for your board, and a way to get the binary onto the device. This repository is the layer that everything else in a Rust firmware stack is written against, which is why it has to be right, and why the RISC-V team rather than a board vendor maintains it.

It is also worth being clear about the class of hardware this suits. These are microcontroller class processors with a trap and interrupt model, running bare metal. It is not a tool for Linux-class RISC-V cores, and it is not a HAL.

On maintenance, the record is regular. v0.14.0 was published on 2025-06-10, riscv-v0.15.0 on 2025-09-09, and riscv-v0.16.0 on 2025-12-24, with the last push to the repository on 2026-10-05. The crates are still on a 0.x line, so expect to read the changelog when you bump a minor version.

## Conclusion

rust-embedded/riscv fits a team writing firmware for a RISC-V microcontroller in Rust, and it is a hard requirement rather than a convenience, because the runtime, the interrupt handling and the register layer all come from here. It does not fit a board without a vendor peripheral crate that implements the riscv-types traits, and it is the wrong starting point if you want a HAL for a specific chip, since the scope stops at the processor and its standard peripherals. Before you commit, check the crate versions you would pin, since the published line is still 0.x, and read the target parsing crate, since the build script configuration is where most of the setup decisions are made.

## FAQ

### What crates does the rust-embedded/riscv repository provide?

Six published crates: riscv for CPU register access and intrinsics, riscv-peripheral for standard RISC-V peripheral interfaces, riscv-rt for startup code and interrupt handling, riscv-semihosting, riscv-target-parser for parsing targets in build scripts, and riscv-types for the common traits that peripheral access crates implement.

### What is riscv-rt used for?

It provides the startup code and interrupt handling for bare metal RISC-V targets, meaning the stack setup, data initialisation and trap vector table that have to run before your own main function, along with the interrupt handling on top of it.

### Do I still need a vendor peripheral crate with these crates?

Yes. The riscv-types crate defines the traits that peripheral access crates implement, and those are supplied per chip by the vendor, so a firmware project needs this repository's crates for the processor and runtime plus a vendor PAC, and usually a HAL above it.

### How are the riscv crates tested?

The workspace includes three test crates: tests-qemu, which runs the firmware under QEMU so the startup and interrupt paths are exercised without hardware, tests-trybuild for compile-fail tests of the misuse cases, and tests-build, plus an xtask directory for automating repository chores.

### What licence applies to the riscv crates and to contributions?

The repository ships LICENSE-APACHE.md and LICENSE-MIT.md, and the contribution clause states that any contribution submitted for inclusion is dual licensed under those terms unless you explicitly state otherwise.

## Sources

- [Issues](https://github.com/rust-embedded/riscv/issues)
- [License: Apache-2.0](https://github.com/rust-embedded/riscv/blob/master/LICENSE)
- [README](https://github.com/rust-embedded/riscv/blob/master/README.md)
- [Releases](https://github.com/rust-embedded/riscv/releases)
- [rust-embedded/riscv on GitHub](https://github.com/rust-embedded/riscv)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rust-embedded-riscv
