Program a Shakti RISC-V processor using Rust

Published on: 2020-11-16

Home | Archive

Shakti is an Open Source RISC-V processor development ecosystem created by researchers at IIT Madras. This post describes how you can get bare metal Rust running on a Shakti softcore.

Hardware setup

You will need an Artix-7 35T FPGA board to run a 32 bit, E-Class, Shakti processor core based SoC called Pinaka. This is a small microcontroller-class device with 128Kb of RAM.

Check out this video to find out how you can get Pinaka up and running on your Artix-35T. The video describes how to build the SoC from source (using the recently open-sourced Bluespec compiler and other tools) and get it programmed on the Artix-35T using vivado webpack. You can skip the build part by directly getting the compiled file (a .mcs file) from here.

Note 1: Vivado webpack is a multi-gigabyte download; it works on GNU/Linux, but needs specific (older) versions of Ubuntu. As I didn’t have the required version installed, I had to use Windows 10 to run Vivado.

Note 2: All the experiments described below were done on an Ubuntu (20.10) based distro called Pop_OS.

Installing the RISC-V toolchain

Check out the user manual for details on setting up the SDk and toolchain. A pre-compiled version of the toolchain is available, but I had to build it from source because of some shared library version incompatibilities. By the way, building from source is also a bit tricky and you may have to do some googling around to fix things (a suggestion: the “build.sh” script will build both 32 bit and 64 bit toolchains, you can edit this script to build only the 32 bit toolchain).

Hello, world!

Note: the Rust compiler version I am using is 1.49.0-nightly (ffa2e7ae8 2020-10-24).

Rust uses LLVM as the code generator, and LLVM can generate RISC-V code. There is a crate called riscv-rt which takes care of the startup/runtime support required to get a generic 32 bit RISC-V CPU up and running.

Getting the RISC-V target for Rust installed is as easy as:

rustup target add riscv32imac-unknown-none-elf
rustup default nightly
rustup target add riscv32imac-unknown-none-elf

Now clone this repo; it contains two small programs that I wrote as part of my Shakti/Rust experiments. Build the code by running (inside the rust-shakti folder):

cargo build --release

The binary for a blinking LED program will be available as:

target/riscv32imac-unknown-none-elf/release/blink

Now load and run the code using openocd and gdb as described in the Shakti user manual. You will see one of the RGB LED’s on the board blinking!

About the code

Check out the Cargo.toml file in the rust-shakti folder; you will note that we have added riscv-rt as a dependency.

Another interesting file is memory.x:

MEMORY
{
  RAM : ORIGIN = 0x80000000, LENGTH = 128K
}

REGION_ALIAS("REGION_TEXT", RAM);
REGION_ALIAS("REGION_RODATA", RAM);
REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);

We need not write a linker script ourselves; the above file basically customizes a built-in linker script with values that are specific to our processor. The user manual will tell you that the processor RAM is from 0x80000000 to 0x8001FFFF. We locate all the sections (text/data/bss/stack etc) in RAM.

Coming to the actual source of blinking LED code (src/bin/blink/main.rs) there is not much in it that is worthy of special mention, except perhaps the following.

We have a block of code (inside the delay routine):

unsafe {
            asm!("addi zero, zero, 0");
}

This asm block will prevent the LLVM optimizer from optimizing away our delay loop (note that we are building in release mode, ie, with optimizations enabled).

Random blink!

There is another fun program in our repo; it is:

src/bin/random/main.rs

It blinks two LED’s randomly; we generate a random bit stream using a linear feedback shift register algorithm.

Just to make things unnecessarily complicated, we do this using an iterator :) You will note that we create two iterator objects and zip them together:

let a = new_lfsr(0x1234);
let b = new_lfsr(0xabcd);
for bits in a.zip(b) {
    led_on_off(LED0_R, bits.0);
    led_on_off(LED1_G, bits.1);
    delay();
}

This is the kind of high-level code that we usually write in say Python; what about the performance impact?

You can disassemble the binary produced by this program using riscv32-unknown-elf-objdump and you will see that LLVM has aggressively optimized away all that iterator/zip etc stuff and all that you see is what you would have seen had you written low-level C-like code!

Moving ahead

My plan is to write some crates which will ensure that the Shakti 32 bit E-class processor has first class embedded Rust support.

Support Source Hut!

And now something off-topic!

You might have noticed that the rust-shakti repo is hosted neither on github nor on gitlab - it is on a relatively new service called Source Hut created by Drew DeVault.

More diversity is always welcome when it comes to critical infrastructure like source code hosting, please do check it out!