Skip to content

Commit

Permalink
tests/run-tests, ports/qemu-riscv: Add QEMU RV32 port.
Browse files Browse the repository at this point in the history
This adds a QEMU-based bare metal RISC-V 32 bits port.  For the time
being only QEMU's "virt" 32 bits board is supported, using the ilp32 ABI
and the RV32IMC architecture.

Tests run and pass, and the test generator framework was modified to
remove hardcoded references to the "qemu-arm" port.

Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
  • Loading branch information
agatti committed Nov 7, 2023
1 parent 958c6d9 commit fce9833
Show file tree
Hide file tree
Showing 18 changed files with 1,072 additions and 0 deletions.
120 changes: 120 additions & 0 deletions ports/qemu-riscv/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
include ../../py/mkenv.mk
-include mpconfigport.mk

# qstr definitions (must come before including py.mk)
QSTR_DEFS = qstrdefsport.h

# MicroPython feature configurations
MICROPY_ROM_TEXT_COMPRESSION ?= 1

# include py core make definitions
include $(TOP)/py/py.mk
include $(TOP)/extmod/extmod.mk

CROSS_COMPILE ?= riscv64-unknown-elf-

BOARD ?= virt
LDFLAGS = --gc-sections -Map=$(@:.elf=.map)
LIBS = $(shell $(CC) $(CFLAGS) -print-libgcc-file-name)

ifeq ($(BOARD),virt)
ABI=ilp32
ARCH=rv32imc_zicsr
AFLAGS = -mabi=$(ABI) -march=$(ARCH)
CFLAGS += -mabi=$(ABI) -fPIC -mlittle-endian -march=$(ARCH) -mcmodel=medany
CFLAGS += -DQEMU_SOC_VIRT
LDSCRIPT = virt.ld
LDFLAGS += -T $(LDSCRIPT) -m elf32lriscv
SRC_BOARD_O = shared/runtime/gchelper_generic.o setjmp.o
SRC_BOARD_O += entry.o
endif

INC += -I.
INC += -I$(TOP)
INC += -I$(BUILD)

CFLAGS += $(INC) -Wall -Wpointer-arith -Wdouble-promotion -Wfloat-conversion -Werror -std=gnu99 $(COPT) \
-ffunction-sections -fdata-sections
CFLAGS += $(CFLAGS_EXTRA)

# Debugging/Optimization
ifeq ($(DEBUG), 1)
CFLAGS += -g
COPT = -O0
else
COPT += -Os -DNDEBUG
endif

SRC_COMMON_C = \
interrupts.c \
startup.c \
uart.c \
modmachine.c \
shared/libc/string0.c \
shared/runtime/sys_stdio_mphal.c \

SRC_RUN_C = \
main.c \

SRC_TEST_C = \
test_main.c \
lib/tinytest/tinytest.c \

LIB_SRC_C += $(addprefix lib/,\
libm/math.c \
libm/fmodf.c \
libm/nearbyintf.c \
libm/ef_sqrt.c \
libm/kf_rem_pio2.c \
libm/kf_sin.c \
libm/kf_cos.c \
libm/kf_tan.c \
libm/ef_rem_pio2.c \
libm/sf_sin.c \
libm/sf_cos.c \
libm/sf_tan.c \
libm/sf_frexp.c \
libm/sf_modf.c \
libm/sf_ldexp.c \
libm/asinfacosf.c \
libm/atanf.c \
libm/atan2f.c \
libm/roundf.c \
)

OBJ_COMMON =
OBJ_COMMON += $(PY_O)
OBJ_COMMON += $(addprefix $(BUILD)/, $(SRC_COMMON_C:.c=.o))
OBJ_COMMON += $(addprefix $(BUILD)/, $(SRC_BOARD_O))
OBJ_COMMON += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o))

OBJ_RUN =
OBJ_RUN += $(addprefix $(BUILD)/, $(SRC_RUN_C:.c=.o))

ALL_OBJ_RUN = $(OBJ_COMMON) $(OBJ_RUN)

OBJ_TEST =
OBJ_TEST += $(addprefix $(BUILD)/, $(SRC_TEST_C:.c=.o))

ALL_OBJ_TEST = $(OBJ_COMMON) $(OBJ_TEST)

# All object files, needed to get dependencies correct
OBJ = $(OBJ_COMMON) $(OBJ_RUN) $(OBJ_TEST)

# List of sources for qstr extraction
SRC_QSTR += $(SRC_COMMON_C) $(SRC_RUN_C) $(LIB_SRC_C)

all: run

debug: $(BUILD)/firmware.elf
qemu-system-riscv32 -machine $(BOARD) -bios none $(QEMU_EXTRA) -nographic -monitor null -semihosting -serial mon:stdio -S -s -kernel $<

run: $(BUILD)/firmware.elf
qemu-system-riscv32 -machine $(BOARD) -bios none $(QEMU_EXTRA) -nographic -monitor null -semihosting -kernel $<

## `$(LD)` doesn't seem to like `--specs` for some reason, but we can just use `$(CC)` here.
$(BUILD)/firmware.elf: $(LDSCRIPT) $(ALL_OBJ_RUN)
$(Q)$(LD) $(LDFLAGS) -o $@ $(ALL_OBJ_RUN) $(LIBS)
$(Q)$(SIZE) $@

include $(TOP)/py/mkrules.mk
34 changes: 34 additions & 0 deletions ports/qemu-riscv/Makefile.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
LIB_SRC_C = shared/upytesthelper/upytesthelper.c

include Makefile

ifeq ($(BOARD),virt)
TESTS_EXCLUDE = extmod/deflate_decompress.py
endif

CFLAGS += -DTEST

.PHONY: $(BUILD)/genhdr/tests.h

TESTS_PROFILE = $(dir $(abspath $(firstword $(MAKEFILE_LIST))))/tests_profile.txt

$(BUILD)/test_main.o: $(BUILD)/genhdr/tests.h
$(BUILD)/genhdr/tests.h:
(cd $(TOP)/tests; ./run-tests.py --target=qemu-riscv --write-exp)
$(Q)echo "Generating $@";(cd $(TOP)/tests; ../tools/tinytest-codegen.py --profile $(TESTS_PROFILE) $(addprefix --exclude ,$(TESTS_EXCLUDE))) > $@

$(BUILD)/lib/tinytest/tinytest.o: CFLAGS += -DNO_FORKING

$(BUILD)/firmware-test.elf: $(LDSCRIPT) $(ALL_OBJ_TEST)
$(Q)$(LD) $(LDFLAGS) -o $@ $(ALL_OBJ_TEST) $(LIBS)
$(Q)$(SIZE) $@

# Note: Using timeout(1) to handle cases where qemu hangs (e.g. this can happen with alignment errors).
test: $(BUILD)/firmware-test.elf
timeout --foreground -k 5s 60s qemu-system-riscv32 -machine $(BOARD) -bios none $(QEMU_EXTRA) -nographic -monitor null -semihosting -kernel $< > $(BUILD)/console.out
$(Q)tail -n2 $(BUILD)/console.out
$(Q)tail -n1 $(BUILD)/console.out | grep -q "status: 0"

debugtest: $(BUILD)/firmware-test.elf
qemu-system-riscv32 -machine $(BOARD) -bios none $(QEMU_EXTRA) -nographic -monitor null -semihosting -serial mon:stdio -S -s -kernel $<

35 changes: 35 additions & 0 deletions ports/qemu-riscv/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
This is experimental, community-supported port for RISC-V RV32IM emulation as
provided by QEMU (http://qemu.org).

The purposes of this port are to enable:

1. Continuous integration
- run tests against architecture-specific parts of code base
2. Experimentation
- simulation & prototyping of anything that has architecture-specific
code
- exploring instruction set in terms of optimising some part of
MicroPython or a module
3. Streamlined debugging
- no need for JTAG or even an MCU chip itself
- no need to use OpenOCD or anything else that might slow down the
process in terms of plugging things together, pressing buttons, etc.

This port requires a newlib based bare metal/ELF RISC-V toolchain, either
with multilib support or 32 bits specific (M, C, and Zicsr extensions must
be supported, along with ilp32 ABI). Either musl-based or glibc-based
toolchains are not tested or known to work.

If your toolchain does not support the above requirements, either download
a pre-built toolchain (and that's a hit or miss situation in its own right)
or follow the instructions on [RISC-V's reference toolchain Github repo](https://github.com/riscv-collab/riscv-gnu-toolchain)
to get a newlib-based multilib toolchain (the target will be
`riscv64-unknown-elf` but said toolchain will be able to generate 32 bits
code as well).

That said, when in doubt, build your own toolchain - it's the fastest way to
get things going for sure.

To build and run image with builtin testsuite:

make -f Makefile.test test
51 changes: 51 additions & 0 deletions ports/qemu-riscv/entry.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
Adapted from https://wiki.osdev.org/RISC-V_Bare_Bones#entry.S
According to the page history, this content was added after 20110703, and
following the text found at https://wiki.osdev.org/OSDev_Wiki:License the
overall licence for this code is as follows:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

.section .init
.option norvc
.type start, @function
.global start

start:
.cfi_startproc

.option push
.option norelax

la gp, global_pointer

.option pop

csrw satp, zero
la sp, stack_top
la t5, bss_start
la t6, bss_end

bss_clear:
sw zero, (t5)
addi t5, t5, 4
bltu t5, t6, bss_clear

la t0, _entry_point
csrw mepc, t0

tail _entry_point

.cfi_endproc
.end

Loading

0 comments on commit fce9833

Please sign in to comment.