A bare metal Hello world in assembly on RISC-V with QEMU
December 28, 2019 12 Comments
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.