Board support for QEMU's RISC-V virt machine. Depend on it and a bare-metal
C++ project builds, runs and tests with no board knowledge of its own.
[dependencies]
mcpplibs.riscv-virt-rt = "0.3"
[targets.firmware]
kind = "bin"
main = "src/main.cpp"import mcpplibs.riscv_virt_rt;
extern "C" int main() {
board::println("hello from qemu virt");
board::printf("float %.4f\n", 3.14159);
return 0;
}mcpp new blinky --template riscv-virt-rt
cd blinky && mcpp runThe template ships with this package, so it cannot drift from it: mcpp new
writes the dependency line at the version it resolved.
mcpp build --target riscv64-none-elf # firmware + .bin + .map + a size summary
mcpp run --target riscv64-none-elf # boots in qemu
mcpp test --target riscv64-none-elf # each tests/*.cpp runs as its own image⭐ Nothing in that project names picolibc, compiler-rt, crt0, a linker script,
a load address, -nostdlib, -mcmodel — or an emulator. There is no
[target.*] section at all.
| C library | picolibc, per ISA profile, privately included and re-exported as a module |
| Runtime | compiler-rt builtins printf formats floats through ryu, which does 128-bit shifts, and rv64 has no instruction for them |
| Startup | picolibc's semihosting crt0, so an ordinary int main() works |
| Memory layout | picolibc's linker script for the virt map |
| Runner | qemu-system-riscv{32,64} by absolute path, with the machine model and firmware mode |
--target |
ISA profile |
|---|---|
riscv64-none-elf |
rv64gc / lp64d |
riscv32-none-elf |
rv32imac / ilp32 |
One package serves both: the profile is chosen from the target mcpp resolved, not from a second copy of this file.
-
mcpp ≥ 2026.8.19.4, which is where the target began supplying its own C library. Two earlier boundaries, worth keeping apart:
- 2026.8.19.2 is the hard floor:
build.mcppcallsmcpp::runner, which arrived there. - 2026.8.19.3 is the version to actually use. On 2026.8.19.2 this package
builds and
mcpp runworks, butmcpp buildfollowed bymcpp runexecutes the firmware on the host instead of the emulator — it prints nothing and exits 1.
Below the hard floor the build stops while compiling
build.mcpp:error: 'runner' is not a member of 'mcpp' The `mcpp` build module this engine bundles does not have that name. Either the package was written for a newer mcpp (try `mcpp self update`…⚠️ The floor is stated here in prose because a package cannot probe for it.if constexpr (requires { mcpp::runner("x"); })is a hard error when the name is absent, notfalse— a requires-expression over a qualified name that does not exist is ill-formed. There is no in-language feature test, so the version is documented and the diagnostic above is the fallback. - 2026.8.19.2 is the hard floor:
-
xim:qemu-riscv, declared in[xlings].depsand located throughmcpp::xpkg_dir.⚠️ The target's C library is not declared here and should not be. It is a property of the target, and mcpp resolves it from the target's own row the same way it resolves the compiler — its headers are on every compile line and its directory on the link search path before this package says anything. That is why the linker line below is bare names (-lc,-lcrt0-semihost): this package chooses which pieces, not where they are.
build.mcpp emits them as a package-private include directory, and mcpp
keeps them that way on purpose. They are riscv*-none-elf headers; a consumer
that could #include <stdio.h> would be getting a header for a target its own
translation unit may not be built for. What crosses the boundary is the module.
Apache-2.0. The payloads it links carry their own: picolibc is BSD-3-Clause / BSD-2-Clause, compiler-rt is Apache-2.0 WITH LLVM-exception.