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.

Building ghdl from source for Ubuntu 16.04 – mcode version

Based on input from Patrick Lehmann, I decided to re-install ghdl, now using the official GitHub site.

I learned from the README file how to build the mcode variant. I tried, and it worked fine (and it was much simpler than the gcc-based approach I tried earlier)!

Here are the commands that I used.

I downloaded and installed an Ada compiler, as instructed, and I proceeded with the build and install of ghdl, by first setting the PATH to include my newly installed Ada compiler, as

export PATH=/usr/gnat/bin:$PATH

I cloned the repo, using

git clone https://github.com/ghdl/ghdl.git

I configured and built ghdl, using

cd ghdl
./configure --prefix=/usr/local/ghdl_mcode
make

The installation was then done, as

ghdl:-$ sudo -i
# cd 
# PATH=/usr/gnat/bin/:$PATH make install
... 
...
# exit

That was all!

I could now build and run my hello world example, from my work-in-progress book about building a computer, as

vhdl:-$ export PATH=/usr/local/ghdl_mcode/bin/:$PATH
vhdl:-$ ghdl -a hello.vhdl
vhdl:-$ ghdl -e hello_world
vhdl:-$ ghdl -r hello_world
Hello, world
vhdl:-$ 

Installing ghdl from source on Ubuntu 16.04



NOTE – this page is outdated – please use updated page instead.



I wanted to reinstall ghdl on my new Ubuntu 16.04. My first idea was to use the method I used for the previous Ubuntu version, where I downloaded a ghdl package from this page with Debian packages.

But when I looked at the page now, I did not find any package that I could get to work on Ubuntu 16.04.

I decided to try an installation from source.

I found out that the source code could be downloaded from this ghdl-updates sourceforge page.

From the information about branches, I could see the version numbers for stable versions. I chose to download the 0.33 version.

The source code download was done using git clone, as

git clone git://git.code.sf.net/p/ghdl-updates/ghdl-updates.git ghdl-updates-ghdl-updates.git

and the 0.33 branch was checked out as

cd ghdl-updates-ghdl-updates.git
git branch
git checkout ghdl-0.33

I looked in the README file and learned that it was possible to build ghdl with gcc as backend. I decided to try this alternative. Another alternative was called mcode, but it was only available for 32-bit Ubuntu, and a third alternative was to use llvm.

The first step was to obtain an Ada compiler.

I installed gnat by doing

sudo apt install gnat

From the README file I then learned that I should download the gcc 4.9 source. I did this by doing

wget ftp://ftp.gnu.org/gnu/gcc/gcc-4.9.4/gcc-4.9.4.tar.bz2
tar xvjf gcc-4.9.4.tar.bz2

I then configured gcc, as instructed in the ghdl source code README file, via the ghdl source configure script, using the path to my downloaded gcc source as argument, by doing

cd ghdl-updates-ghdl-updates.git/
./configure --with-gcc=/home/ola/ghdl/gcc/gcc-4.9.4/
make copy-sources
cd ..

It was now required to download and build some auxiliary packages. I did this, for the package gmp, by doing

wget ftp://gcc.gnu.org/pub/gcc/infrastructure/gmp-4.3.2.tar.bz2
tar xvjf gmp-4.3.2.tar.bz2
mkdir gmp-4.3.2/gmp-objs/
cd gmp-4.3.2/gmp-objs/
../configure --prefix=/usr/local --disable-shared
make
sudo make install
cd ../..

followed by the package mpfr, as

wget ftp://gcc.gnu.org/pub/gcc/infrastructure/mpfr-2.4.2.tar.bz2
tar xvjf mpfr-2.4.2.tar.bz2
mkdir mpfr-2.4.2/mpfr-objs/
cd mpfr-2.4.2/mpfr-objs/
../configure --prefix=/usr/local --disable-shared --with-gmp=/usr/local
make
sudo make install
cd ../..

and finally, the third package mpc, as

wget ftp://gcc.gnu.org/pub/gcc/infrastructure/mpc-0.8.1.tar.gz
tar xvzf mpc-0.8.1.tar.gz
mkdir mpc-0.8.1/mpc-objs/
cd mpc-0.8.1/mpc-objs/
../configure --prefix=/usr/local --disable-shared --with-gmp=/usr/local
make
sudo make install
cd ../..

It was now time to build the actual ghdl. Since we had decided to use gcc as backend, we accomplish the task of building ghdl by building gcc. So we go back to our directory where we unpacked the gcc sources.

In that directory, we do configuration as

cd gcc-4.9.4
mkdir gcc-objs
cd gcc-objs/
../configure --prefix=/opt/ghdl-updates --enable-languages=c,vhdl --disable-bootstrap --with-gmp=/usr/local --disable-lto --disable-multilib

In the above configuration step, we have selected the prefix as /opt/ghdl-updates. The reason for this is to avoid a collision with an already existing gcc compiler (which we already have on our system – and which we need for the build of ghdl).

For the actual build step, we need to do make. Here, I ran into a problem since the program gnat1 was assumed to be used (instead of gnat). The error was seen when doing a plain

make

and it showed up, after a while, as

gnatbind -Lgrt_ -o run-bind.adb -n ghdl_main.ali
gcc -c -O2 -g -gnatec../../../gcc/vhdl/grt/grt.adc -gnat05 -o run-bind.o run-bind.adb
gcc: error trying to exec 'gnat1': execvp: No such file or directory
Makefile:586: recipe for target 'run-bind.o' failed

I found this searchcode page, where the problem, and a solution, were described.

Using this newfound piece of information, I located gnat1 by doing

$ find /usr -name gnat1
/usr/lib/gcc/x86_64-linux-gnu/4.9/gnat1

I then used this directory as a part of the PATH when doing make, as

PATH=/usr/lib/gcc/x86_64-linux-gnu/4.9:$PATH make

This resulted in a successful build.

Assuming that the PATH used for make was needed also when doing make install, I started a root shell as

sudo -i

and in that shell, after having done cd to the gcc-objs directory, I did

PATH=/usr/lib/gcc/x86_64-linux-gnu/4.9:$PATH make install MAKEINFO=true

This completed the installation of ghdl!

I could use ghdl, on the first example from the book Into Computers, after having set the PATH as

export PATH=/opt/ghdl-updates/bin/:$PATH

as described in the book, by doing

$ ghdl -a hello.vhdl
$ ghdl -e hello_world
$ ghdl -r hello_world
Hello, world
$