Skip to content

Merge qemu-riscv port functionality into qemu-arm port #15743

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions .github/workflows/ports_qemu-arm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,26 @@ concurrency:
cancel-in-progress: true

jobs:
build_and_test:
build_and_test_arm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_qemu_arm_setup
run: source tools/ci.sh && ci_qemu_setup_arm
- name: Build and run test suite
run: source tools/ci.sh && ci_qemu_arm_build
run: source tools/ci.sh && ci_qemu_build_arm
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures

build_and_test_rv32:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_qemu_setup_rv32
- name: Build and run test suite
run: source tools/ci.sh && ci_qemu_build_rv32
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
33 changes: 0 additions & 33 deletions .github/workflows/ports_qemu-riscv.yml

This file was deleted.

182 changes: 103 additions & 79 deletions ports/qemu-arm/Makefile
Original file line number Diff line number Diff line change
@@ -1,78 +1,107 @@
BOARD ?= mps2-an385
################################################################################
# Initial setup of Makefile environment

BOARD ?= MPS2_AN385

# Make the build directory reflect the board.
BUILD ?= build-$(BOARD)

include ../../py/mkenv.mk
-include mpconfigport.mk

# Include board specific .mk file.
include boards/$(BOARD).mk

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

# MicroPython feature configurations
MICROPY_ROM_TEXT_COMPRESSION ?= 1

ifeq ($(QEMU_ARCH),arm)
FROZEN_MANIFEST ?= "freeze('test-frzmpy')"
endif
ifeq ($(QEMU_ARCH),riscv32)
FROZEN_MANIFEST ?= "freeze('test-frzmpy', ('frozen_const.py', 'frozen_viper.py', 'native_frozen_align.py'))"
endif

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

CFLAGS += -DMICROPY_HW_BOARD_NAME='"$(BOARD)"'
QEMU_ARGS += -machine $(BOARD) -nographic -monitor null -semihosting
################################################################################
# ARM specific settings

ifeq ($(BOARD),netduino2)
CFLAGS += -mthumb -mcpu=cortex-m3 -mfloat-abi=soft
CFLAGS += -DQEMU_SOC_STM32
CFLAGS += -DMICROPY_HW_MCU_NAME='"STM32"'
LDSCRIPT = stm32.ld
SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o
MPY_CROSS_FLAGS += -march=armv7m
endif
ifeq ($(QEMU_ARCH),arm)

CROSS_COMPILE ?= arm-none-eabi-

LDFLAGS += -nostdlib
LIBS = $(shell $(CC) $(CFLAGS) -print-libgcc-file-name)

SRC_C += \
mcu/arm/startup.c \
shared/runtime/semihosting_arm.c \

ifeq ($(BOARD),microbit)
CFLAGS += -mthumb -mcpu=cortex-m0 -mfloat-abi=soft
CFLAGS += -DQEMU_SOC_NRF51
CFLAGS += -DMICROPY_HW_MCU_NAME='"nRF51"'
LDSCRIPT = nrf51.ld
QEMU_ARGS += -global nrf51-soc.flash-size=1048576 -global nrf51-soc.sram-size=262144
SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb1.o
MPY_CROSS_FLAGS += -march=armv7m
endif

ifeq ($(BOARD),mps2-an385)
CFLAGS += -mthumb -mcpu=cortex-m3 -mfloat-abi=soft
CFLAGS += -DQEMU_SOC_MPS2
CFLAGS += -DMICROPY_HW_MCU_NAME='"Cortex-M3"'
LDSCRIPT = mps2.ld
SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o
MPY_CROSS_FLAGS += -march=armv7m
################################################################################
# RISC-V 32-bit specific settings

ifeq ($(QEMU_ARCH),riscv32)

CROSS_COMPILE ?= riscv64-unknown-elf-

GCC_VERSION = $(word 1, $(subst ., , $(shell $(CC) -dumpversion)))

RV32_ABI = ilp32

QEMU_ARGS += -bios none

# GCC 10 and lower do not recognise the Zicsr extension in the architecture name.
ifeq ($(shell test $(GCC_VERSION) -le 10; echo $$?),0)
RV32_ARCH ?= rv32imac
else
# Recent GCC versions explicitly require to declare extensions.
RV32_ARCH ?= rv32imac_zicsr
endif

ifeq ($(BOARD),sabrelite)
CFLAGS += -mcpu=cortex-a9
CFLAGS += -DQEMU_SOC_IMX6
CFLAGS += -DMICROPY_HW_MCU_NAME='"Cortex-A9"'
LDSCRIPT = imx6.ld
QEMU_ARGS += -m 128M
SRC_BOARD_O = shared/runtime/gchelper_generic.o
# It's really armv7a but closest supported value is armv6.
MPY_CROSS_FLAGS += -march=armv6
# Cortex-A9 should support unaligned-access, but qemu doesn't seem to.
CFLAGS += -mno-unaligned-access
# These don't work on Cortex-A9.
TESTS_EXCLUDE = --exclude '(asmdiv|asmspecialregs).py'
AFLAGS += -mabi=$(RV32_ABI) -march=$(RV32_ARCH)
CFLAGS += $(AFLAGS)
LDFLAGS += -mabi=$(RV32_ABI) -march=$(RV32_ARCH) -Wl,-EL

SRC_C += \
mcu/rv32/interrupts.c \
mcu/rv32/startup.c \

SRC_BOARD_O += mcu/rv32/entrypoint.o

endif

CROSS_COMPILE ?= arm-none-eabi-
################################################################################
# Project specific settings and compiler/linker flags

QEMU_SYSTEM = qemu-system-$(QEMU_ARCH)
QEMU_ARGS += -machine $(QEMU_MACHINE) -nographic -monitor null -semihosting
QEMU_ARGS += $(QEMU_EXTRA)

# Specifying QEMU_DEBUG=1 will block qemu until a debugger is connected.
ifeq ($(QEMU_DEBUG),1)
QEMU_DEBUG_ARGS ?= -s
QEMU_ARGS += -S $(QEMU_DEBUG_ARGS) $(QEMU_DEBUG_EXTRA)
endif

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

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

LDFLAGS += -T $(LDSCRIPT) -Wl,--gc-sections -Wl,-Map=$(@:.elf=.map)

# Debugging/Optimization
ifeq ($(DEBUG), 1)
CFLAGS += -g
Expand All @@ -81,74 +110,69 @@ else
COPT += -Os -DNDEBUG
endif

## With CoudeSourcery it's actually a little different, you just need `-T generic-m-hosted.ld`.
## Although for some reason `$(LD)` will not find that linker script, it works with `$(CC)`.
## It turns out that this is specific to CoudeSourcery, and ARM version of GCC ships something
## else instead and according to the following files, this is what we need to pass to `$(CC).
## - gcc-arm-none-eabi-4_8-2014q1/share/gcc-arm-none-eabi/samples/src/makefile.conf
## - gcc-arm-none-eabi-4_8-2014q1/share/gcc-arm-none-eabi/samples/src/qemu/Makefile
LDFLAGS= -T $(LDSCRIPT) --gc-sections -Map=$(@:.elf=.map)
LIBS = $(shell $(CC) $(CFLAGS) -print-libgcc-file-name)
# If Picolibc is available then select it explicitly. Ubuntu 22.04 ships its
# bare metal RISC-V toolchain with Picolibc rather than Newlib, and the default
# is "nosys" so a value must be provided. To avoid having per-distro
# workarounds, always select Picolibc if available.
PICOLIBC_SPECS = $(shell $(CC) --print-file-name=picolibc.specs)
ifeq ($(PICOLIBC_SPECS),picolibc.specs)
# Picolibc was not found.
else
$(info picolibc used $(PICOLIBC_SPECS))
SPECS_FRAGMENT = --specs=$(PICOLIBC_SPECS)
CFLAGS += $(SPECS_FRAGMENT)
LDFLAGS += $(SPECS_FRAGMENT)
endif

SRC_COMMON_C = \
startup.c \
################################################################################
# Source files and libraries

SRC_C += \
main.c \
uart.c \
mphalport.c \
shared/libc/string0.c \
shared/readline/readline.c \
shared/runtime/interrupt_char.c \
shared/runtime/pyexec.c \
shared/runtime/semihosting_arm.c \
shared/runtime/stdout_helpers.c \
shared/runtime/sys_stdio_mphal.c \

SRC_RUN_C = \
main.c \

LIB_SRC_C += $(SRC_LIB_LIBM_C)
LIB_SRC_C += $(SRC_LIB_LIBM_SQRT_SW_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)
OBJ += $(PY_O)
OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o))
OBJ += $(addprefix $(BUILD)/, $(SRC_BOARD_O))
OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o))

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

################################################################################
# Main targets

all: $(BUILD)/firmware.elf

.PHONY: repl
repl: $(BUILD)/firmware.elf
$(ECHO) "Use machine.reset() to exit"
qemu-system-arm $(QEMU_ARGS) -serial mon:stdio -kernel $<
$(QEMU_SYSTEM) $(QEMU_ARGS) -serial mon:stdio -kernel $<

.PHONY: run
run: $(BUILD)/firmware.elf
qemu-system-arm $(QEMU_ARGS) -serial pty -kernel $<
$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel $<

.PHONY: test
test: $(BUILD)/firmware.elf
$(eval DIRNAME=ports/$(notdir $(CURDIR)))
Copy link
Contributor

@agatti agatti Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add a testdebug target too. How would you be able to see things like #15589 in a debugger otherwise?

That issue was reproduced by running several tests in a row - with that debug target definition there would be no way to get that under gdb or lldb or anything else.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not possible to used both -S and the run-tests.py infrastructure, because the latter consumes stdio of qemu-system-X and so you won't be able to start the emulator because the monitor is not accessible.

That issue was reproduced by running several tests in a row -

I think we could make this debug target use -serial pty and then you can run the tests manually as many times as you like against the exposed serial device, eg:

$ make debug
<in a separate terminal>
$ gdb ...
<in a separate terminal>
$ ./run-tests.py --target qemu-arm --device /dev/pts/123

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tried the version I committed a few days ago as agatti@ce8a877, using -serial pty, and it seems to work when running make testdebug in the first terminal and when it's ready spawning another one and run gdb attaching to the qemu instance. Unless something else changed there's no need to use three terminals :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I will test.

But what I'm also saying above is that it's possible to rerun the test suite over and over, or whatever combination of tests you like. That requires 3 terminals.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But what I'm also saying above is that it's possible to rerun the test suite over and over, or whatever combination of tests you like. That requires 3 terminals.

Oh, never figured that was a possibility. Almost every time I've had the need for this it turned out to be a fatal crash so there was no point in rerunning the test without restarting qemu as well. In that case then yes, you're absolutely right.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, you can use -S -s with the make test target.

Instead of duplicating all targets with a debug version, I added an orthogonal option QEMU_DEBUG which can be enabled for any qemu target, eg:

$ make QEMU_DEBUG=1 test

That will run the test suite with -S -s.

I also added QEMU_DEBUG_EXTRA, and updated the README with descriptions of these options.

cd $(TOP)/tests && ./run-tests.py --target qemu-arm --device execpty:"qemu-system-arm $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(TESTS_EXCLUDE)
cd $(TOP)/tests && ./run-tests.py --target qemu-arm --device execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(RUN_TESTS_ARGS) $(RUN_TESTS_EXTRA)

## `$(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)
$(BUILD)/firmware.elf: $(LDSCRIPT) $(OBJ)
$(Q)$(CC) $(LDFLAGS) -o $@ $(OBJ) $(LIBS)
$(Q)$(SIZE) $@

################################################################################
# Remaining make rules

include $(TOP)/py/mkrules.mk
49 changes: 46 additions & 3 deletions ports/qemu-arm/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
MicroPython port to qemu-arm
============================

This is experimental, community-supported port for Cortex-M emulation as
provided by QEMU (http://qemu.org).
This is experimental, community-supported port for Cortex-M and RISC-V RV32IMC
emulation as provided by QEMU (http://qemu.org).

The purposes of this port are to enable:

Expand All @@ -18,6 +18,25 @@ The purposes of this port are to enable:
- no need to use OpenOCD or anything else that might slow down the
process in terms of plugging things together, pressing buttons, etc.

Dependencies
------------

### ARM

For ARM-based boards the build requires a bare-metal ARM toolchain, such as
`arm-none-eabi-gcc`.

### RISC-V

For RISC-V-based boards the build requires a bare metal RISC-V toolchain with GCC 10
or later, either with multilib support or 32 bits specific (M, C, and Zicsr
extensions must be supported, along with ilp32 ABI). Both newlib and picolibc are
supported, with the latter having precedence if found.

Most pre-built toolchains should work out of the box, either coming from your
Linux distribution's package manager, or independently packaged ones like
[xPack](https://xpack.github.io/dev-tools/riscv-none-elf-gcc/).

Build instructions
------------------

Expand All @@ -32,7 +51,17 @@ Then build using:
The default qemu-supported board is `mps2-an385`, a Cortex-M3 board. To select a
different board pass the `BOARD` argument to `make`, for example:

$ make BOARD=sabrelite
$ make BOARD=SABRELITE

Available boards are:

| Name for `BOARD=` | Architecture | Corresponding qemu board |
| ----------------- | ------------ | ------------------------ |
| `MICROBIT` | `arm` | `microbit` |
| `MPS2_AN385` | `arm` | `mps2-an385` |
| `NETDUINO2` | `arm` | `netduino2` |
| `SABRELITE` | `arm` | `sabrelite` |
| `VIRT_RV32` | `riscv32` | `virt` |

Running
-------
Expand Down Expand Up @@ -67,3 +96,17 @@ tests against the serial device, for example:

$ cd ../../tests
$ ./run-tests.py --target qemu-arm --device /dev/pts/1

Extra make options
------------------

The following options can be specified on the `make` command line:
- `CFLAGS_EXTRA`: pass in extra flags for the compiler.
- `RUN_TESTS_EXTRA`: pass in extra flags for `run-tests.py` when invoked via
`make test`.
- `QEMU_DEBUG=1`: when running qemu (via `repl`, `run` or `test` target), qemu
will block until a debugger is connected. By default it waits for a gdb connection
on TCP port 1234.
- `QEMU_DEBUG_ARGS`: defaults to `-s` (gdb on TCP port 1234), but can be overridden
with different qemu gdb arguments.
- `QEMU_DEBUG_EXTRA`: extra options to pass to qemu when `QEMU_DEBUG=1` is used.
Loading
Loading