A bare metal Hello world in assembly on RISC-V with QEMU

I wanted to create a minimal, Hello world style, program, for RISC-V.

I wanted the program to run on Ubuntu, as well as on MacOS, and I wanted to use QEMU as RISC-V simulator.

QEMU – Ubuntu

For Ubuntu, I installed QEMU by following instructions on the QEMU site, which led to commands, as

git clone https://git.qemu.org/git/qemu.git
cd qemu
git submodule init
git submodule update --recursive
mkdir objdir
cd objdir
../configure --target-list=riscv32-softmmu --prefix=/home/$USER/prog/qemu_riscv

The configure step gave an error, as

ola@ola-VirtualBox:~/mw/qemu/objdir$ ../configure --target-list=riscv32-softmmu --prefix=/home/$USER/prog/qemu_riscv

ERROR: pkg-config binary 'pkg-config' not found

ola@ola-VirtualBox:~/mw/qemu/objdir$ 

I realized that this was due to lack of programs on my newly installed Ubuntu 18.04.3 on Virtualbox.

I installed pkg-config, by doing

sudo apt install pkg-config

after which I got more errors, which led to

sudo apt install libglib2.0-dev 
sudo apt install libpixman-1-dev

which in turn led to a successful configure command.

I could then build, using

make

and install, using

make install

The installation was then tested, as

ola@ola-VirtualBox:~/mw/qemu/objdir$ /home/$USER/prog/qemu_riscv/bin/qemu-system-riscv32 --help | head
QEMU emulator version 4.1.93 (v4.2.0-rc3-dirty)
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers
usage: qemu-system-riscv32 [options] [disk_image]

'disk_image' is a raw hard disk image for IDE hard disk 0

Standard options:
-h or -help     display this help and exit
-version        display version information and exit
-machine [type=]name[,prop[=value][,...]]
ola@ola-VirtualBox:~/mw/qemu/objdir$ 

QEMU – macOS Catalina

For macOS, I had already done an installation of QEMU on macOS Mojave.

I followed the steps from this experience, but now on macOS Catalina, and changing the configure command from

../configure --target-list=riscv32-softmmu --prefix=/Users/$USER/qemu_riscv

to

../configure --target-list=riscv32-softmmu --prefix=/Users/$USER/prog/qemu_riscv

I also noted that a parallel make

make -j

was successful.

I checked by my installation, by doing.

objdir oladahl$ /Users/$USER/prog/qemu_riscv/bin/qemu-system-riscv32 --help | head
QEMU emulator version 4.1.94 (v4.2.0-rc4-2-g02f9c885ed)
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers
usage: qemu-system-riscv32 [options] [disk_image]

'disk_image' is a raw hard disk image for IDE hard disk 0

Standard options:
-h or -help     display this help and exit
-version        display version information and exit
-machine [type=]name[,prop[=value][,...]]
objdir oladahl$ 

Toolchain

As the next step, I wanted to install a toolchain.

I decided to use this RISC-V toolchain.

Ubuntu

Following the instructions, I did, on Ubuntu

git clone https://github.com/riscv/riscv-gnu-toolchain
cd riscv-gnu-toolchain

followed by

git submodule update --init --recursive

which took some time.

I installed programs, as recommended, by doing

sudo apt-get install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev

A configure was then done, for a 32-bit instruction set, using information from the Github site, as

mkdir objdir
cd objdir/
../configure --prefix=/home/$USER/prog/gcc_riscv --with-arch=rv32gc --with-abi=ilp32d

I built the toolchain, at first trying with

make -j

which led to an out-of-memory situation, with prints such as

cc1plus: out of memory allocating 65536 bytes after a total of 2252800 bytes
virtual memory exhausted: Cannot allocate memory
virtual memory exhausted: Cannot allocate memory

cc1plus: out of memory allocating 240809 bytes after a total of 1343488 bytes
virtual memory exhausted: Cannot allocate memory
virtual memory exhausted: Cannot allocate memory
virtual memory exhausted: Cannot allocate memory

I changed to a sequential make

make

which was successful.

I checked that I could start gcc, using

ola@ola-VirtualBox:~/mw/riscv-gnu-toolchain/objdir$ /home/$USER/prog/gcc_riscv/bin/riscv32-unknown-elf-gcc -v
Using built-in specs.
COLLECT_GCC=/home/ola/prog/gcc_riscv/bin/riscv32-unknown-elf-gcc
COLLECT_LTO_WRAPPER=/home/ola/prog/gcc_riscv/libexec/gcc/riscv32-unknown-elf/9.2.0/lto-wrapper
Target: riscv32-unknown-elf
Configured with: /home/ola/mw/riscv-gnu-toolchain/objdir/../riscv-gcc/configure --target=riscv32-unknown-elf --prefix=/home/ola/prog/gcc_riscv --disable-shared --disable-threads --enable-languages=c,c++ --with-system-zlib --enable-tls --with-newlib --with-sysroot=/home/ola/prog/gcc_riscv/riscv32-unknown-elf --with-native-system-header-dir=/include --disable-libmudflap --disable-libssp --disable-libquadmath --disable-libgomp --disable-nls --disable-tm-clone-registry --src=../../riscv-gcc --disable-multilib --with-abi=ilp32d --with-arch=rv32gc --with-tune=rocket 'CFLAGS_FOR_TARGET=-Os   -mcmodel=medlow' 'CXXFLAGS_FOR_TARGET=-Os   -mcmodel=medlow'
Thread model: single
gcc version 9.2.0 (GCC) 
ola@ola-VirtualBox:~/mw/riscv-gnu-toolchain/objdir$ 
macOS Catalina

On macOS Catalina, I installed programs as

brew install gawk gnu-sed gmp mpfr libmpc isl zlib expat

Following the instructions, I cloned the repo, as

git clone https://github.com/riscv/riscv-gnu-toolchain
cd riscv-gnu-toolchain

followed by

git submodule update --init --recursive

which took some time.

A configure was done, for a 32-bit instruction set, using information from the Github site, as

mkdir objdir
cd objdir/
../configure --prefix=/Users/$USER/prog/gcc_riscv --with-arch=rv32gc --with-abi=ilp32d

I built the toolchain, using parallel make, as

make -j

which was successful.

I checked that I could start gcc, using

$ /Users/$USER/prog/gcc_riscv/bin/riscv32-unknown-elf-gcc -v
Using built-in specs.
COLLECT_GCC=/Users/oladahl/prog/gcc_riscv/bin/riscv32-unknown-elf-gcc
COLLECT_LTO_WRAPPER=/Users/oladahl/prog/gcc_riscv/libexec/gcc/riscv32-unknown-elf/9.2.0/lto-wrapper
Target: riscv32-unknown-elf
Configured with: /Users/oladahl/mw/riscv-gnu-toolchain/objdir/../riscv-gcc/configure --target=riscv32-unknown-elf --prefix=/Users/oladahl/prog/gcc_riscv --disable-shared --disable-threads --enable-languages=c,c++ --with-system-zlib --enable-tls --with-newlib --with-sysroot=/Users/oladahl/prog/gcc_riscv/riscv32-unknown-elf --with-native-system-header-dir=/include --disable-libmudflap --disable-libssp --disable-libquadmath --disable-libgomp --disable-nls --disable-tm-clone-registry --src=../../riscv-gcc --disable-multilib --with-abi=ilp32d --with-arch=rv32gc --with-tune=rocket 'CFLAGS_FOR_TARGET=-Os   -mcmodel=medlow' 'CXXFLAGS_FOR_TARGET=-Os   -mcmodel=medlow'
Thread model: single
gcc version 9.2.0 (GCC) 
$ 

A program

I decided to write an assembly program, and to use a simulated UART in QEMU as the means for printing.

Looking at the 32-bit instruction set, in the The RISC-V Instruction Set Manual for the Unprivileged ISA, available as one of the RISC-V Specifications, and also using information from this page for the rv8 simulator, I came up with a program as

    .global _start

_start:

    lui t0, 0x10010

    andi t1, t1, 0
    addi t1, t1, 72
    sw t1, 0(t0)

    andi t1, t1, 0
    addi t1, t1, 101
    sw t1, 0(t0)

    andi t1, t1, 0
    addi t1, t1, 108
    sw t1, 0(t0)

    andi t1, t1, 0
    addi t1, t1, 108
    sw t1, 0(t0)

    andi t1, t1, 0
    addi t1, t1, 111
    sw t1, 0(t0)

    andi t1, t1, 0
    addi t1, t1, 10
    sw t1, 0(t0)

finish:
    beq t1, t1, finish

Here, the UART address is indicated by the value 0x10010, which is used in the first instruction as

    lui t0, 0x10010

I found the UART address by first practising building of a hello world program from riscv-probe, where I also learned how to create a linker script.

After this, I looked in the QEMU source code, where I found the file hw/riscv/sifive_u.c, in which the UART address is found in a struct, defined (with values for entries not related to UART0 omitted) as

static const struct MemmapEntry {
     hwaddr base;
     hwaddr size;
} sifive_u_memmap[] = {
...
    [SIFIVE_U_UART0] =    { 0x10010000,     0x1000 },
...
};

Considering that the lui instruction loads its immediate value into the upper 20 bits of the register used in the instruction, we see that the value 0x10010000 can be loaded in to a register, in this case t0, by doing

    lui t0, 0x10010

Using a makefile, as

hello: hello.o link.lds
	riscv32-unknown-elf-ld -T link.lds -o hello hello.o

hello.o: hello.s
	riscv32-unknown-elf-as -o hello.o hello.s

clean:
	rm hello hello.o

where the file link.lds has the same content as the riscv-probe linker script, I could build and run the program, on Ubuntu, as

export PATH=/home/$USER/prog/gcc_riscv/bin/:$PATH
make
/home/$USER/prog/qemu_riscv/bin/qemu-system-riscv32 -machine sifive_u -nographic -kernel hello

with printouts as

$ /home/$USER/prog/qemu_riscv/bin/qemu-system-riscv32 -machine sifive_u -nographic -kernel hello
qemu-system-riscv32: warning: No -bios option specified. Not loading a firmware.
qemu-system-riscv32: warning: This default will change in a future QEMU release. Please use the -bios option to avoid breakages when this happens.
qemu-system-riscv32: warning: See QEMU's deprecation documentation for details.
Hello
Hello
QEMU: Terminated
$ 

On macOS Catalina, I did

export PATH=/Users/$USER/prog/gcc_riscv/bin/:$PATH
make
/Users/$USER/prog/qemu_riscv/bin/qemu-system-riscv32 -machine sifive_u -nographic -kernel hello

with printouts as

$ /Users/$USER/prog/qemu_riscv/bin/qemu-system-riscv32 -machine sifive_u -nographic -kernel hello
qemu-system-riscv32: warning: No -bios option specified. Not loading a firmware.
qemu-system-riscv32: warning: This default will change in a future QEMU release. Please use the -bios option to avoid breakages when this happens.
qemu-system-riscv32: warning: See QEMU's deprecation documentation for details.
Hello
Hello
QEMU: Terminated
$ 

In both cases (Ubuntu and macOS Catalina), I terminated QEMU using Ctrl-A x. Also, I don’t know why the Hello string was printed twice.

About Ola
Developer and writer -- creating Layered Books (http://layeredbooks.com) -- with books on programming and embedded systems - published a thriller at https://store.bookbaby.com/book/prevention

8 Responses to A bare metal Hello world in assembly on RISC-V with QEMU

  1. Fatih says:

    Hi Ola,

    Thanks for your example. I followed your steps and obtained the exactly same results. However when i try read and write memory instruction, qemu doesn’t give any response.

    I changed your assembly code as following;

    .global _start

    _start:

    lui t0, 0x10013
    lui a0, 0x80003

    andi a1, a1, 0
    andi a2, a2, 0
    addi a1, a1, 70
    sw a1, 0(a0)
    lw a2, 0(a0)

    sw a2, 0(t0)

    finish:
    beq a1, a1, finish

    when i run it on qemu it gives same warnings (qemu-system-riscv32: warning: No -bios option specified. Not loading a firmware. qemu-system-riscv32: warning: This default will change in a future QEMU release. Please use the -bios option to avoid breakages when this happens. qemu-system-riscv32: warning: See QEMU’s deprecation documentation for details) but doesn’t print anything. Did you try read and write from/into memory?

    Fatih

    • fatih says:

      Hi,

      I found my mistake. The lui instruction is sign extended. When i used it as above it gives the address 0xFFFFFFFF80003000 instead of 0x0000000080003000 for 64 bit cpu. Now i changed it with “auipc a0, 0x3” and it works well.

      Also, i didnt use this instruction to address UART “lui t0, 0x10013”, i used this one “lui t0, 0x10010”. I dont know how 3 went there 😀

      Fatih

      • Ola says:

        I recall seeing 0x10013 as well, and changing it to 0x10010 to make it work. I think it was when I was using this file to look up the UART address (which was not the correct one):

        sifive_e.c

  2. Ola says:

    Thanks for your comments, Fatih (and sorry for my *very* long response time)

  3. Ola says:

    As pointed out by a reader, the observation of prints happening twice:

    “In both cases (Ubuntu and macOS Catalina), I terminated QEMU using Ctrl-A x. Also, I don’t know why the Hello string was printed twice.”

    is most likely due to us running on a multicore system (with two cores), and the program running on both of these cores.

    Reading at

    https://forums.sifive.com/t/freedom-u-sdk-limiting-cpu-cores-to-1/2564

    I am thinking that perhaps starting qemu with -smp 1 will solve the problem.

    Alternatively, the program could be modified so that it only runs on one core.

    • Ola says:

      Starting with -smp 1 did not work. I got this error:

      $ qemu-system-riscv32 -machine sifive_u -nographic -bios none -smp 1 -kernel hello
      qemu-system-riscv32: Invalid SMP CPUs 1. The min CPUs supported by machine 'sifive_u' is 2
      $

      • Fatih says:

        Hi Ola,
        I was porting a Real Time Operating System to RISV in the days I was writing my previous comments. Your post took me back to the old days. Thanks again, your sharing was helpful in those days.

        As for this problem; as you said, qemu runs the code on 2 cores by default. I was initially reading the current core number and if it didn’t return 0, I was terminating that core.

  4. Pingback: RISC-V Hello on macOS Monterey and Ubuntu 20.04 | The Intobooks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: