Skip to content

Running on QEMU

Installing QEMU

Detailed instructions for building on Linux are on QEMU's Wiki. First you need to install required additional packages, which depend on the Linux distribution used on the host.

Install necessary packages for AlmaLinux 8:

sudo dnf group install "Development Tools"
sudo dnf \
    install git glib2-devel libfdt-devel pixman-devel \
    zlib-devel bzip2 ninja-build python3 python3-tomli \
    python38

Install necessary packages for Ubuntu 22.04:

sudo apt install \
    git cmake ninja-build gperf ccache \
    dfu-util device-tree-compiler wget \
    python3-pip python3-setuptools python3-wheel \
    xz-utils file make gcc gcc-multilib pkg-config \
    libglib2.0-dev libpixman-1-dev zlib1g-dev

After installation of prerequisites you can download sources, configure and build in the following way (use your own --prefix value for installation path):

# Clone v9.0 from GitHub repository
$ git clone --depth 1 -b stable-9.0 https://github.com/qemu/qemu.git
$ cd qemu

# Configure QEMU for 32-bit and 64-bit RISC-V targets
$ ./configure \
        --target-list=riscv32-softmmu,riscv64-softmmu \
        --prefix=$HOME/qemu

# Build and install QEMU
$ make
$ make install

After the build is complete qemu-system-riscv32 and qemu-system-riscv64 are placed in $HOME/qemu/bin$ folder, which you can add to the PATH environment variable for convenience, and use the short binary name instead of the full path.

Code example

Consider this code example which prints all passed command line arguments:

#include <stdio.h>

int main(int argc, char *argv[]) {
    for (int i = 0; i < argc; i++) {
        printf("%d: %s\n", i, argv[i]);
    }
    return 0;
}

Build and run for RMX-100

Build with Picolibc-based toolchain:

$ riscv64-snps-elf-gcc \
        -march=rv32imac_zcb_zba_zbb_zbs \
        -mabi=ilp32 \
        -mtune=arc-v-rmx-100-series \
        -Wl,-defsym=__flash=0x80000000 \
        -Wl,-defsym=__flash_size=1M \
        -Wl,-defsym=__ram=0x80200000 \
        -Wl,-defsym=__ram_size=1M \
        -specs=picolibc.specs \
        --oslib=semihost \
        --crt0=semihost \
        args.c -o args.elf

Build with Newlib-based toolchain:

$ riscv64-snps-elf-gcc \
        -march=rv32imac_zcb_zba_zbb_zbs \
        -mabi=ilp32 \
        -mtune=arc-v-rmx-100-series \
        -Wl,-defsym=txtmem_addr=0x80000000 \
        -Wl,-defsym=txtmem_len=1M \
        -Wl,-defsym=datamem_addr=0x80200000 \
        -Wl,-defsym=datamem_len=1M \
        -specs=semihost.specs \
        -specs=arcv.specs \
        --crt0=no-csr \
        -T arcv.ld \
        args.c -o args.elf

Run on QEMU:

$ qemu-system-riscv32 \
        -semihosting \
        -nographic \
        -machine virt \
        -cpu rv32,f=off,zfa=off,d=off,zce=on,zba=on,zbb=on,zbs=on \
        -bios none \
        -kernel args.elf \
        -append "one two three"
0: args.elf
1: one
2: two
3: three

Build and Run for Base RMX-500

Build with Picolibc-based toolchain:

$ riscv64-snps-elf-gcc \
        -march=rv32imac_zcb_zba_zbb_zbs \
        -mabi=ilp32 \
        -mtune=arc-v-rmx-500-series \
        -Wl,-defsym=__flash=0x80000000 \
        -Wl,-defsym=__flash_size=1M \
        -Wl,-defsym=__ram=0x80200000 \
        -Wl,-defsym=__ram_size=1M \
        -specs=picolibc.specs \
        --oslib=semihost \
        --crt0=semihost \
        args.c -o args.elf

Build with Newlib-based toolchain:

$ riscv64-snps-elf-gcc \
        -march=rv32imac_zcb_zba_zbb_zbs \
        -mabi=ilp32 \
        -mtune=arc-v-rmx-500-series \
        -Wl,-defsym=txtmem_addr=0x80000000 \
        -Wl,-defsym=txtmem_len=1M \
        -Wl,-defsym=datamem_addr=0x80200000 \
        -Wl,-defsym=datamem_len=1M \
        -specs=semihost.specs \
        -specs=arcv.specs \
        --crt0=no-csr \
        -T arcv.ld \
        args.c -o args.elf

Run on QEMU:

$ qemu-system-riscv32 \
        -semihosting \
        -nographic \
        -machine virt \
        -cpu rv32,f=off,zfa=off,d=off,zce=on,zba=on,zbb=on,zbs=on \
        -bios none \
        -kernel args.elf \
        -append "one two three"
0: args.elf
1: one
2: two
3: three

Build and Run for Base RHX-100

Build with Picolibc-based toolchain:

$ riscv64-snps-elf-gcc \
        -march=rv32imafc_zcb_zba_zbb_zbs \
        -mabi=ilp32f \
        -mtune=arc-v-rhx-100-series \
        -Wl,-defsym=__flash=0x80000000 \
        -Wl,-defsym=__flash_size=1M \
        -Wl,-defsym=__ram=0x80200000 \
        -Wl,-defsym=__ram_size=1M \
        -specs=picolibc.specs \
        --oslib=semihost \
        --crt0=semihost \
        args.c -o args.elf

Build with Newlib-based toolchain:

$ riscv64-snps-elf-gcc \
        -march=rv32imafc_zcb_zba_zbb_zbs \
        -mabi=ilp32f \
        -mtune=arc-v-rhx-100-series \
        -Wl,-defsym=txtmem_addr=0x80000000 \
        -Wl,-defsym=txtmem_len=1M \
        -Wl,-defsym=datamem_addr=0x80200000 \
        -Wl,-defsym=datamem_len=1M \
        -specs=semihost.specs \
        -specs=arcv.specs \
        --crt0=no-csr \
        -T arcv.ld \
        args.c -o args.elf

Run on QEMU:

$ qemu-system-riscv32 \
        -semihosting \
        -nographic \
        -machine virt \
        -cpu rv32,f=on,zfa=off,d=off,m=on,a=on,zca=on,zcf=on,zcb=on,zcmp=on,zba=on,zbb=on,zbs=on \
        -bios none \
        -kernel args.elf \
        -append "one two three"
0: args.elf
1: one
2: two
3: three

Build and Run for Base RPX-100

Build with Picolibc-based toolchain:

$ riscv64-snps-elf-gcc \
        -march=rv64imafdc_zcb_zba_zbb_zbs \
        -mabi=lp64d \
        -mtune=arc-v-rpx-100-series \
        -mcmodel=medany \
        -Wl,-defsym=__flash=0x80000000 \
        -Wl,-defsym=__flash_size=1M \
        -Wl,-defsym=__ram=0x80200000 \
        -Wl,-defsym=__ram_size=1M \
        -specs=picolibc.specs \
        --oslib=semihost \
        --crt0=semihost \
        args.c -o args.elf

Build with Newlib-based toolchain:

$ riscv64-snps-elf-gcc \
        -march=rv64imafdc_zcb_zba_zbb_zbs \
        -mabi=lp64d \
        -mtune=arc-v-rpx-100-series \
        -mcmodel=medany \
        -Wl,-defsym=txtmem_addr=0x80000000 \
        -Wl,-defsym=txtmem_len=1M \
        -Wl,-defsym=datamem_addr=0x80200000 \
        -Wl,-defsym=datamem_len=1M \
        -specs=semihost.specs \
        -specs=arcv.specs \
        --crt0=no-csr \
        -T arcv.ld \
        args.c -o args.elf

Run on QEMU:

$ qemu-system-riscv64 \
        -semihosting \
        -nographic \
        -machine virt \
        -cpu rv64,f=on,zfa=off,d=on,m=on,a=on,zca=on,zcd=on,zcb=on,zba=on,zbb=on,zbs=on \
        -bios none \
        -kernel args.elf \
        -append "one two three"
0: args.elf
1: one
2: two
3: three

Debugging Using GDB

You can debug an application that is being run on QEMU with help of the built-in GDB server of QEMU. On the QEMU side, you need to enable the GDB sever and optionally instruct the QEMU not to start the application execution automatically, but instead wait for user input. For that you need to pass -s -S commands to qemu-system-riscv32 or qemu-system-riscv64.

Build with Picolibc-based toolchain:

$ riscv64-snps-elf-gcc \
        -march=rv32imac_zcb_zba_zbb_zbs \
        -mabi=ilp32 \
        -mtune=arc-v-rmx-100-series \
        -Wl,-defsym=__flash=0x80000000 \
        -Wl,-defsym=__flash_size=1M \
        -Wl,-defsym=__ram=0x80200000 \
        -Wl,-defsym=__ram_size=1M \
        -specs=picolibc.specs \
        --oslib=semihost \
        --crt0=semihost \
        -g \
        args.c -o args.elf

Build with Newlib-based toolchain:

$ riscv64-snps-elf-gcc \
        -march=rv32imac_zcb_zba_zbb_zbs \
        -mabi=ilp32 \
        -mtune=arc-v-rmx-100-series \
        -Wl,-defsym=txtmem_addr=0x80000000 \
        -Wl,-defsym=txtmem_len=1M \
        -Wl,-defsym=datamem_addr=0x80200000 \
        -Wl,-defsym=datamem_len=1M \
        -specs=semihost.specs \
        -specs=arcv.specs \
        --crt0=no-csr \
        -T arcv.ld \
        -g \
        args.c -o args.elf

Start GDB server (we use -gdb tcp::12345 instead of -s to set the custom GDB port number instead of the default 1234 port number):

$ qemu-system-riscv32 \
        -semihosting \
        -nographic \
        -machine virt \
        -cpu rv32,f=off,zfa=off,d=off,zce=on,zba=on,zbb=on,zbs=on \
        -bios none \
        -kernel args.elf \
        -append "one two three" \
        -gdb tcp::12345 -S

Start GDB session:

$ riscv64-snps-elf-gdb -q args.elf
Reading symbols from args.elf...
(gdb) target remote :12345
Remote debugging using :12345
0x00001000 in ?? ()
(gdb) b main
Breakpoint 1 at 0x800000e0: file args.c, line 4.
(gdb) c
Continuing.

Breakpoint 1, main (argc=4, argv=0x8020007c <argv>) at args.c:4
4       for (int i = 0; i < argc; i++) {
(gdb) step
5           printf("%d: %s\n", i, argv[i]);
(gdb) c
Continuing.
[Inferior 1 (process 1) exited normally]

Note that the first location where the CPU is waiting for commands is 0x00001000. It’s a virt board reset vector location. It is not possible to suppress execution of that reset vector code as it’s an essential part of the QEMU board (think of it as a boot-ROM). You can only bypass it if you load the target executable from the GDB client with the load command. Then execution starts from the loaded application's entry point.