QEMU Emulation for RISCV32E
Overview
The RISCV32E QEMU board configuration is used to emulate the RISCV32 (RV32E) architecture.
ELF Loading Convention
QEMU’s RISC-V virt machine mirrors the boot behavior of OpenSBI’s
fw_dynamic, fw_jump and fw_payload firmware, as well as the
Berkeley Boot Loader (BBL):
/*
* NB: Use low address not ELF entry point to ensure that the fw_dynamic
* behaviour when loading an ELF matches the fw_payload, fw_jump and BBL
* behaviour, as well as fw_dynamic with a raw binary, all of which jump to
* the (expected) load address load address. This allows kernels to have
* separate SBI and ELF entry points (used by FreeBSD, for example).
*/
In other words, when an ELF is passed to QEMU via -kernel, the vCPU’s
program counter is set to the lowest address the image is loaded at, not
to the address recorded in the ELF header’s e_entry field. This keeps
boot behavior consistent between raw binaries and ELF images and lets a
kernel expose an SBI entry point that differs from its ELF entry point.
This convention is normally invisible to Zephyr because the linker script
places the image’s entry point (CONFIG_KERNEL_ENTRY) at the very start
of the ROM region, so the load address and the entry point happen to
coincide. It becomes a problem the moment the two addresses diverge such
as if the ROM region reserves space in front of rom_start for a
header or padding, because QEMU will then jump to the beginning of that
reserved space instead of to CONFIG_KERNEL_ENTRY and Zephyr will never
run.
CONFIG_QEMU_DEVICE_LOADER works around this by
replacing the -kernel option with one -device loader,file=<elf>
entry per CPU (one for each of the CONFIG_MP_MAX_NUM_CPUS
cores configured). Unlike -kernel, QEMU’s generic loader device honors
the ELF’s actual entry point, so every vCPU starts execution at
CONFIG_KERNEL_ENTRY regardless of where it sits relative to the base of
ROM.
Programming and Debugging
The qemu_riscv32e board supports the runners and associated west commands listed below.
| flash | debug | debugserver | |
|---|---|---|---|
| qemu | ✅ (default) | ✅ (default) | ✅ |
Applications for the qemu_riscv32e board configuration can be built and run in
the usual way for emulated boards (see Building an Application and
Run an Application for more details).
Flashing
While this board is emulated and you can’t “flash” it, you can use this configuration to run basic Zephyr applications and kernel tests in the QEMU emulated environment. For example, with the Basic Synchronization sample:
# From the root of the zephyr repository
west build -b qemu_riscv32e samples/synchronization
west build -t run
This will build an image with the synchronization sample app, boot it using QEMU, and display the following console output:
*** Booting Zephyr OS build v3.1.0-rc1-59-g0d66cc1f6645 ***
thread_a: Hello World from cpu 0 on qemu_riscv32e!
thread_b: Hello World from cpu 0 on qemu_riscv32e!
thread_a: Hello World from cpu 0 on qemu_riscv32e!
thread_b: Hello World from cpu 0 on qemu_riscv32e!
thread_a: Hello World from cpu 0 on qemu_riscv32e!
thread_b: Hello World from cpu 0 on qemu_riscv32e!
thread_a: Hello World from cpu 0 on qemu_riscv32e!
thread_b: Hello World from cpu 0 on qemu_riscv32e!
thread_a: Hello World from cpu 0 on qemu_riscv32e!
thread_b: Hello World from cpu 0 on qemu_riscv32e!
thread_a: Hello World from cpu 0 on qemu_riscv32e!
thread_b: Hello World from cpu 0 on qemu_riscv32e!
Exit QEMU by pressing CTRL+A x.
Debugging
Refer to the detailed overview about Application Debugging.