Last updated on November 10, 2023.
I Got a Milk-V Duo (and It’s Running Rust)
I've always been interested in computer engineering, but previously, I had yet to learn how to get into it all. Luckily for me, Milk-V recently went off, creating several new RISC-V products!
These boards are perfect for me! They:
- aren't too expensive,
- use new and freaky technology, and
- can serve valuable purposes for me!
Since I was already looking for some development boards, these came at a perfect time! I went to their (only) distributor that ships to the United States and picked up a Milk-V Duo!
Well, I didn't pick it up - they shipped it! Remember that while the boards are $5, the shipping is $12! If you plan to pick one up, grab many! They're cheap enough not to be a worry there, but if one breaks, you won't have to agonize over more shipping!
I'd also grab some of these headers (affiliate link btw). I haven't had any trouble with them, and you get enough for any future boards you pick up!
Backstory
Anyway, it arrived after just over a week, arriving in one of the tiniest boxes I've ever received... or held!
As Milk-V's site says, the Duo is packing down there! It has two C906 cores (one for Linux and another for FreeRTOS). The board also has a balmy 64MB of memory and includes a CVITEK TPU - marketed as an "AI acceleration engine... shudders. I'm not sure if Google would appreciate their name either, but because it can run YOLO and even a limited OpenCV, I'm not particularly concerned!
Regarding its capabilities, we can use the Raspberry Pi as a mediator. Here, the Duo takes a comfy seat between the Pi Pico and Zero. Like the Pico, this board eschews desktop integration for a better development experience, relying on a USB-C connection for communication. As it turns out, you can plug and play on Linux, though Windows and macOS require some setup, as usual.
You'll also need to stick their Linux Buildroot image on a microSD card, which takes just a few minutes.
After it's all working, though, you can easily talk to it with an SSH command: ssh root@192.168.42.1
.
After that, you'll have a nice ash
shell at your disposal! Naturally, I immediately imagined how I could use the thing.
At my university, I work on some engineering competition teams, including a Formula SAE team and a URC robotics team.
Both utilize microcontrollers lavishly, and we've traditionally programmed them in either C or Python. However, everyone's been looking at other options, and Rust is something I've been interested in! So I bought one!
However, I had a lot of (very minor) difficulties. First, the microSD card didn't automatically expand, so I wrote an article about it! The default Buildroot image also reserves about HALF of the board's memory for the official Milk-V Camera, even if you don't have it. So... you need to remove the restriction by recompiling the Buildroot with different memory parameters. A nice forum post explains how in about two lines. (thanks!)
Even after those things, though, the biggest challenge is the C toolchain: Milk-V currently uses musl
over gnu
for their libc
. That's important for one reason - Rust gives riscv64gc-unknown-linux-gnu
a tier 2 status (with host tools) but leaves riscv64gc-unknown-linux-musl
in tier 3!
That means that gnu
targets can scoot by without compiling the kernel - they only need to run: rustup +nightly target add riscv64gc-unknown-linux-gnu
. They still have some configuration to do, but it's a little easier than what we need to do! Let's go over it!
Setup
There are a few stages to setting up cross-compilation for our little Duo. Before we can even touch Rust, though, we'll need a gcc
toolchain that works with riscv64gc-unknown-linux-musl
platforms.
Let's start with that!
The Toolchain
Depending on your machine, this step is either incredibly easy or mind-numbingly tedious.
I'll start with the Golden Girls of this blog post - if you have an x86_64 Linux machine, you can download this musl
archive and extract it to /opt/riscv-musl
(or some other location). Lucky!
Otherwise, you'll need to compile it yourself! I was on an Asahi Linux MacBook for this project, so I compiled everything for aarch64
. I could use my desktop, but it's nice to have everything nearby! Let's go on a journey...
First of all, there's a project on GitHub called the RISC-V GNU Compiler Toolchain. These folks created a cross-compiler for RISC-V targets.
The official Milk-V repo uses them, but we're beyond that. Another community member created a Buildroot you can run on your board and as a cross-compiler.
Compiling the Toolchain
Before you do anything, make sure to install all dependencies on your computer:
- Fedora:
sudo dnf install autoconf automake bc bison bzip2 cpio file flex gcc gcc-c++ openssl-devel ncurses-devel patchutils perl-core rsync tar unzip wget which -y
- Ubuntu/Debian:
sudo apt update && sudo apt install bc bison build-essential flex libssl-dev unzip
Now, you can grab the git
repo using git clone https://github.com/kinsamanka/milkv-buildroot.git
.
After it's done cloning, you can run:
barrett@canopy ~/D/milkv-buildroot (master)> bash -c "make O=$(pwd)/build milkv_duo_defconfig"
barrett@canopy ~/D/milkv-buildroot (master)> cd sdk
barrett@canopy ~/D/milkv-buildroot (master)> make sdk
...
Grab a cup of tea - this will take a while! On my MacBook Pro, compiling the buildroot and toolchain took around 40 minutes of HEAT! After it finishes compiling, though, we can start the Rust stuff!
Before we do anything else, please make sure your machine has the Nightly toolchain: rustup toolchain install nightly --allow-downgrade -c rustfmt clippy
I did sneak in Clippy. How could I not do that? It's so helpful! ahem
Cargo
Let's create a sample project to test our progress!
barrett@canopy ~/Downloads> cargo new farts-testing-farts
Created binary (application) `farts-testing-farts` package
barrett@canopy ~/Downloads>
We'll also add some complex dependencies to make sure all is well:
barrett@canopy ~/D/farts-testing-farts (main)> cargo add anyhow tokio tracing tracing-subscriber --features=tokio/rt,tokio/macros,tracing/async-await
Great, now we can make a small sample main.rs
file:
async
If we try to compile now, we'll end up with a LOT of errors. Give it a try if you want - this command will fix the "I don't have the std
library" errors: cargo +nightly build --target riscv64gc-unknown-linux-musl -Zbuild-std=core,std,panic_abort -Zbuild-std-features=panic_immediate_abort
Anyway, the reason the command isn't working yet is that Cargo still needs to learn how to use our shiny new toolchain! Let's teach it by writing a new configuration for Cargo. Make a new file at ~/.cargo/config.toml
! That's right - "config!"
Inside the file, add the following:
[]
= "/home/barrett/Downloads/milkv-buildroot/sdk/host/bin/riscv64-buildroot-linux-musl-gcc.br_real"
= [
"-C", "target-feature=-crt-static",
"-C", "link-arg=--sysroot=/home/barrett/Downloads/milkv-buildroot/sdk/host/riscv64-buildroot-linux-musl/sysroot",
# "-C", "target-feature=+crt-static", # Uncomment me to force static compilation
# "-C", "panic=abort", # Uncomment me to avoid compiling in panics
]
Of course, replace /home/barrett/Downloads
with wherever you cloned and compiled the SDK! Also, it's worth keeping in mind that we're using dynamic libraries here.
Let's run our build command. First, though, run a cargo clean
to make sure all the old build artifacts are gone. Sometimes, failed artifacts won't work with new, working ones!
barrett@canopy ~/D/farts-testing-farts (main)> cargo +nightly clean
Removed 1104 files, 694.1MiB total
barrett@canopy ~/D/farts-testing-farts (main)> cargo +nightly build --target riscv64gc-unknown-linux-musl -Zbuild-std --release
Compiling compiler_builtins v0.1.101
Compiling core v0.0.0 (/home/barrett/.rustup/toolchains/nightly-aarch64-unknown-linux-gnu/lib/rustlib/src/rust/library/core)
Compiling libc v0.2.149
(snip! there's a lot of stuff here...)
Compiling tracing v0.1.40
Compiling tokio v1.33.0
Compiling tracing-subscriber v0.3.17
Compiling farts-testing-farts v0.1.0 (/home/barrett/Downloads/farts-testing-farts)
Finished release [optimized] target(s) in 12.54s
barrett@canopy ~/D/farts-testing-farts (main)>
Great, that means our toolchain is working! Let's test it on the Milk-V Duo!
Testing
To move things to your Duo, you need a specific scp
command:
barrett@canopy ~/D/farts-testing-farts (main)> scp -O target/riscv64gc-unknown-linux-musl/release/farts-testing-farts root@192.168.42.1:/root/bin/farts-testing-farts
root@192.168.42.1's password:
farts-testing-farts 100% 367KB 4.0MB/s 00:00
barrett@canopy ~/D/farts-testing-farts (main)>
You use -O
as the Duo doesn't come bundled with FTP, which modern scp
uses as a backend.
Now for the moment of truth:
[root@milkv-duo]~/bin# ./farts
Hello, world!
1970-01-01T00:31:46.516325Z INFO farts: yo is this a different color or whhhhaaat
[root@milkv-duo]~/bin#
Ayooooo! It's working! However, you might get an error at first, so let's take a look at what that's all about...
Pitstop: Potential Weird Error
If you end up getting a weird error that kinda looks like:
[root@milkv-duo]~/bin# ./binary
-sh: binary: not found
...the computer is lying! No worries - it's not your fault, either! That said, we'll need to clean it up.
The message says there's "no file," but really, it just can't load the dynamic libraries! You'll need to look at the binary on your desktop computer and see where to make a symbolic link.
On your desktop computer, you can do as follows:
barrett@canopy ~/D/f/t/r/release (main)> readelf -l binary
Elf file type is DYN (Position-Independent Executable file) Entry point 0xa634 There are 10 program headers, starting at offset 64
Program Headers: (snip! a lot of bs here)
[Requesting program interpreter: /lib/ld-musl-riscv64.so.1]
(snip)
barrett@canopy ~/D/f/t/r/release (main)>
You're looking for that "requesting program interpreter" line! Let's grab the Duo and make its "real" version of that file pretend to be this ideal one. We'll find the real one and then use a symbolic link:
[root@milkv-duo]/tmp/bin# find /lib -name "**ld-musl**"
/lib/ld-musl-riscv64v0p7_xthead.so.1
[root@milkv-duo]/tmp/bin#
[root@milkv-duo]/tmp/bin# ln -sf /lib/ld-musl-riscv64v0p7_xthead.so.1 /lib/ld-musl-riscv64.so.1
Great! Now, our weirdly named library will work just fine for our program!
[root@milkv-duo]~/bin# ./binary
Hello, world!
It's possible to make some incredible things with boards like these! Since we have everything ready, I can't wait to see what comes next!
Thanks for your attention! Did you spot something wrong with the article? Please email me or make an issue on GitHub! It helps a lot! :)