Category: risc-v

Writing and Understanding RISC-V Assembly Code

For quite a while I have followed the RISC-V ISA with growing intereset. Now that RISC-V is becoming more and more popular and catching a lot of public attention, it is time to get my hands dirty with some low level RISC-V assembly coding.

An instruction set architecture (ISA) is a specification of the commands/instructions a specific processor or processor architecture family does understand and that it can execute. For this tutorial we will be looking at the RISC-V ISA.
Assembly code (often abbreviated asm) is textual low-level program code, which can be directly translated to machine code. We will be working with the RISC-V assembly language.
The connection between an ISA and assembly code is such that assembly language/code is used to write a program for a corresponding ISA. In theory there could be more than one assembly language for the same ISA (like there are multiple high level programming language for the same machine architecture), but this is seldom the case (having different “flavors” of the same assembly language is more common).

I think there is no need to reproduce any details about the RISC-V ISA at this point. There are plenty of good sources online, the official specification of the RISCV user-level ISA of course, especially the chapters “Instruction Set Listings” and “RISC-V Assembly Programmer’s Handbook”, or one of the RISCV reference cards.

Another good tutorial for beginners, which is a little slow paced imho, is the Western Digital RISC-V ASM tutorial on Youtube (the tutorial really gets started with part 6). For beginners it is nice to see how the base addresses for peripherals can be found using the datasheet and how to “talk to” those peripheral blocks using assembly code.

Note that all examples presented in the following are created/run on Ubuntu 18.04 (WSL) and platformio is used for convenience (no need to set up the toolchains manually). On Ubuntu platformio can be obtained with apt:

$> sudo apt install platformio

Now lets start by creating a new folder for our platformio project and initializing it.

$> mkdir riscv-asm-examples
$> cd riscv-asm-examples 
$> platformio init

I use the following platformio.ini file to define the project parameters, since I have a HiFive1 board lying around somewhere. Feel free to use any other supported RISC-V board or framework instead. If you do not own a RISC-V board you can still run the assembler and have a look at the machine code. But I really recommend to get a board, the Longan Nano costs only 5$.
The platformio.ini file I am using is very simple:

$> cat ./platformio.ini
platform = riscv
board = freedom-e300-hifive1
framework = freedom-e-sdk

To check that platformio is installed and the platformio.ini file is correct run a sanity check using an empty main function:

$> echo "int main(void) {return 13;}" > ./src/main.c
$> platformio run

Running platformio for the first time can take a while because all the required packages and toolchains will be downloaded, so do not get impatient if its takes a little while.
If everything went fine a [SUCCESS] message should show up after a while.

Okay, the main function has been compiled. To see the assembler code which was produced we can used objdump -d. The -d option stands for disassemble, which means that the given object file will be “translated” to human readable assembler code.
The object file of interest is hidden away by platformio in .pio/build/freedom-e300-hifive1/src/ (folder may vary if you used a different build framework).
To disassemble the almost empty main function from before do like so:

$> objdump -d .pio/build/freedom-e300-hifive1/src/main.o
.pio/build/freedom-e300-hifive1/src/main.o:     file format elf32-little
 objdump: can't disassemble for architecture UNKNOWN!

Uh oh! Why isn’t it working? The tutorial said that this should work. Liar!
Here is why:

$> ls -lha $(which objdump)
lrwxrwxrwx 1 root root 24 May  8  2019 /usr/bin/objdump -> x86_64-linux-gnu-objdump*

The objdump we called is for the x86 ISA, not for RISC-V. We need to call the correct objdump executable from our RISC-V toolchain. The toolchain is hidden away by platformio inside the user’s home folder under ~/.platformio.

~/.platformio/packages/toolchain-riscv/riscv64-unknown-elf/bin/objdump -d .pio/build/freedom-e300-hifive1/src/main.o
Disassembly of section .text.startup:
    0:   4535                    li      a0,13
    2:   8082                    ret

Nice. The general structure of the assembly code is:

<memory_address>: <opcode>    <instruction> [<parameter(s)>]

So the assembly code above tells us that an immediate value 13 is loaded into register a0, then we return. Register a0 is the register which holds the function return value according to the RISC-V calling convention. The value 13 was given as return value in the main function. So this is really the assembly code corresponding to our main function. Hurrah!

To make things easier I recommend to set a symbolic link to the correct objdump executable, e.g. inside the platformio project folder.

$> ln -s ~/.platformio/packages/toolchain-riscv/riscv64-unknown-elf/bin/objdump objdump
$> ./objdump --version
GNU objdump (SiFive Binutils 2.32.0-2019.08.0) 2.32

OK, time to start with some assembly coding examples. Most examples are very minimalistic and use the main function as entry point from which the assembly code is invoced. The main goal, to show that assembly code can achieve the same things as C code, is proven nontheless.

Oh and to upload an example to your board (e.g. HiFive1) run the following command inside the project root directory (folder where platformio.ini is located):

$> platformio run -t upload

On my Windows 10 machine this only worked with VisualStudioCode (with the platformio plugin intalled). WSL did not work (USB driver stuff). Using a Linux VM should not pose any problem though.
Also the HiFive1 requires a push of the red reset button to reset the board and load the new program after it was uploaded.

Example 1: Superblink

This example just reproduces the WesternDigital RISC-V assembler tutorial from Youtube. Some GPIO pins are initialized and LEDs are toggled between on and off.
Check out the code on github.

Cycling through the RGB LEDs with superblink.

Example 2: Superfade

This example is an extension to superblink and uses PWM signals to control the intensity of LED’s. Three PWM channels are used to control the intensity of the 3 RGB color LEDs. One-by-one the LEDs are ramped up to full intensity and then reverted back to 0 intensity, creating a (incomplete) rainbow pattern.
Check out the code on github.

Fading the RGB LEDs with PWM signals.

Example 3: Simple UART Echo

This example waits to receive data in the UART RX FIFO and subsequently sends the same data out to the UART TX FIFO, thus echoing back any characters that are received.
Check out the code on github.

uartecho in action.

That’s all for the moment. See you next time.



First Steps with the Longan Nano RISC-V Development Board

The Longan Nano is a new contestor in the area of affordable RISC-V development boards. The Longan Nano’s form factor and price puts it up against the Arduino Nano and all other varieties of STM32-based “nano boards”, which can be found abundantly on Ebay and AliExpress.

The Longan Nano features a GigaDevices GD32VF103CBT6 core running at 108 MHz with 32KB SRAM and 128KB flash.
The Longan Nano comes with a small 160×80 pixel LCD glued on top of the board and a practical plastic case. Really no reason to complain for just 5 $. More details in the datasheet.

The first requirement is to install PlatformIO. The command line tools of platformio are sufficient. I will assume the OS as Ubuntu 18.04 in the following.

$> pip3 install platformio 

To get a feeling for the versatility of PlatformIO let’s list the supported boards:

$> python3 -m platformio boards
... (lots of boards)

Now let’s only look at the boards of interest, to see if the Longan Nano is supported at all:

$> python3 -m platformio boards | grep GD32
gd32vf103v-eval     GD32VF103VBT6  108MHz 128KB 32KB GD32VF103V-EVAL
sipeed-longan-nano  GD32VF103CBT6  108MHz 128KB 32KB Sipeed Longan Nano
wio_lite_risc-v     GD32VF103CBT6  108MHz 128KB 32KB Wio Lite RISC-V

Next create a project directory for the Longan Nano and initialize a platformio project:

$> mkdir longan_nano
$> cd longan_nano
$> python3 -m platformio init
$> ls
include  lib  platformio.ini  src  test

Have a look at some information about our platform:

$> python3 -m platformio platform search gd32v
gd32v ~ GigaDevice GD32V
The GigaDevice GD32V device is a 32-bit general-purpose microcontroller based on the RISC-V core with an impressive balance of processing power, reduced power consumption and peripheral set.
Frameworks: arduino, gd32vf103-sdk
Packages: framework-gd32vf103-sdk, framework-arduino-gd32v, tool-openocd-gd32v, tool-gd32vflash, toolchain-gd32v

Before a build can be run some minimum viable source code is required. It is sufficient to put an empty main function into the src folder like so:

$> echo 'int main(int argc, char* argv[]) { return 0; }' > ./src/main.c

Now the project can be run for the first time to make platformio install requires packages and other dependency stuff:

$> python3 -m platformio run
Processing sipeed-longan-nano (platform: gd32v; framework: gd32v103f-sdk; board: sipeed-longan-nano)
PlatformManager: Installing gd32v
Error: This board doesn't support gd32v103f-sdk framework!

Oops! What’s going on here? The content of platformio.ini is copied from Sipeed’s example page, so why isn’t it working?
Well, as so often human error is the cause of this catastrophe. Fix the framework defined in platformio.ini like so:

 framework = gd32vf103-sdk 

Repeat the failing step from before:

$> python3 -m platformio run
Processing sipeed-longan-nano (platform: gd32v; framework: gd32vf103-sdk; board: sipeed-longan-nano)
Building .pio/build/sipeed-longan-nano/firmware.bin
 ================================ [SUCCESS] Took 2.50 seconds ==========================

Success! An empty project has been built, oh the accomplishment! Well at least the toolchain seems to work.
Just for fun the empty program can be uploaded to the Longan Nano board to see that the LED stops blinking (i.e. overwrite the preloaded example design).

To load a program to the Longan Nano it must be properly recognized as a USB device by Ubuntu. This is achieved by pressing and holding down the BOOT button and then pressing the RESET button (while still holding the BOOT button). After the RESET button has been pressed and released the BOOT button can also be released. This is the official way to do it, but may only yield the expected result after several attempts. I could be wrong, but for me it seemed to work quite nicely to unplug the Longan Nano, then hold down the BOOT button and plug the board back in (while still holding the BOOT button).
An unnamed USB device should appear in Linux:

$> lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 004: ID 28e9:0189  
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Next a USB-serial adapter (3.3V) is required. Check that platformio does recognize the adapter:

$> sudo python3 -m platformio device list
Hardware ID: USB VID:PID=0403:6001 SER=00000000 LOCATION=1-3
Description: FT232R USB UART

OK, one step back, it turns out the upload_protocol = serial part is not what I thought it was and only works with the Arduino framework.

$> sudo python3 -m platformio run --target upload
 Processing sipeed-longan-nano (platform: gd32v; framework: gd32vf103-sdk; board: sipeed-longan-nano)
CURRENT: upload_protocol = serial
 Looking for upload port…
 Auto-detected: /dev/ttyUSB0
 Uploading .pio/build/sipeed-longan-nano/firmware.bin
 Failed to init device.
 stm32flash Arduino_STM32_0.9
*** [upload] Error 1
 Using Parser : Raw BINARY
 Interface serial_posix: 115200 8E1
================================= [FAILED] Took 1.88 seconds ==========================

So that didn’t work.
The next alternative is to use dfu-util which is easily available via apt:

$> sudo apt install dfu-util

Subsequently change the upload_protocol in platformio.ini:

upload_protocol = dfu

Repeat the upload step from before:

$> sudo python3 -m platformio run --target upload
Processing sipeed-longan-nano (platform: gd32v; framework: gd32vf103-sdk; board: sipeed-longan-nano)
Download    [=========================] 100%         6584 bytes
 Download done.
 File downloaded successfully
 dfu-util: dfuse_download: libusb_control_transfer returned -9
 *** [upload] Error 74
================================= [FAILED] Took 2.71 seconds =========================

There is still an error showing at the end, but this one can be ignored (not sure why, but it does work despite of the error). After uploading a new program the Longan Nano has to be reset manually by pressing the RESET button. The Longan Nano should no longer blink it’s RGB LED, but instead remain dark and silent. Yippie?

Since this result is somewhat unsatisfying let’s load the official blinky example to bring the Longan Nano back to light. Get the sources from github (just clone the whole project) and descend into the example directory of interest, then build and upload the longan-nano-blink example.

$> git clone
$> cd ./platform-gd32v/examples/longan-nano-blink
$> sudo python3 -m platformio run --target upload
 Processing sipeed-longan-nano (platform: gd32v; framework: gd32vf103-sdk; board: sipeed-longan-nano)
Download    [=========================] 100%         6584 bytes
 Download done.
 File downloaded successfully
 dfu-util: dfuse_download: libusb_control_transfer returned -9
 *** [upload] Error 74
 ================================= [FAILED] Took 2.91 seconds ==========================

After pressing the RESET button the Longan Nano does start blinking it’s RGB LED again, using only the red color. Success!

Blinking like a real pro: the Longan Nano.

In case at any point the messagedfu-util: No DFU capable USB device available appears this means your host machine dropped the USB device corresponding to the Longan Nano for some reason. Try to connect it again with the BOOT and RESET button combo described above.

And that concludes the first steps with the Longan Nano. See you next time.



Anlogic TANG PriMER dev board

Recently I purchased a Sipeed TANG PriMER development board featuring an Anlogic EG4S20 FPGA (codenamed Eagle S20). The only reason I bought the board was to see what Anlogic FPGAs are capable of, since I had never heard of that FPGA vendor before. No need to think twice when the board costs less than 20$.

The TANG PriMER board is officially marketed as a RISC-V development board and comes with a Hummingbird E200 RISC-V softcore design preloaded into the onboard configuration flash. The Hummingbird is basically a slightly modified variation of the SiFive E2 core.
Setting up the tool chain was a bit of a hustle until I found this site which hosts both the TD IDE and the required license files. There are also some datasheets and schematics. Most of the official documentation is only available in Chinese, therefor I strongly recommend the inofficial english translation.

I have not done a lot with this board yet but verify the tool chain with a simple blink LED example. The design included a 32 bit counter which resulted in an estimated maximum frequency of 252 MHz. Not too bad. The TD IDE also comes with an IP wizard to generate IP cores, but it seems to just generate a wrapper for some primitive instantiations. It’s worth mentioning that the EG4S20 has an on-chip oscillator (250 or 266 MHz, documentation and IDE do not agree), on-chip SDRAM (64Mbit) and an 8-channel ADC (1MHz sample rate). Still need to figure out how to configure the board and which programmer can be used.

Since Anlogic FPGAs are not listed on or other distributor websites, it seems unlikely they will become widely available outside of China anytime soon.