Skip to content

qemu-arm: Rework port to provide a proper REPL and run tests through a pty serial port #15624

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 6 commits into from
Aug 28, 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
2 changes: 1 addition & 1 deletion .github/workflows/ports_qemu-arm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ jobs:
run: source tools/ci.sh && ci_qemu_arm_build
- name: Print failures
if: failure()
run: grep --before-context=100 --text "FAIL" ports/qemu-arm/build/console.out
run: tests/run-tests.py --print-failures
46 changes: 37 additions & 9 deletions ports/qemu-arm/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
BOARD ?= mps2-an385

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

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

Expand All @@ -6,16 +11,19 @@ QSTR_DEFS = qstrdefsport.h

# MicroPython feature configurations
MICROPY_ROM_TEXT_COMPRESSION ?= 1
FROZEN_MANIFEST ?= "freeze('test-frzmpy')"

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

BOARD ?= mps2-an385
CFLAGS += -DMICROPY_HW_BOARD_NAME='"$(BOARD)"'
QEMU_ARGS += -machine $(BOARD) -nographic -monitor null -semihosting

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
Expand All @@ -24,15 +32,17 @@ endif
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_EXTRA = -global nrf51-soc.flash-size=1048576 -global nrf51-soc.sram-size=262144
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
Expand All @@ -41,11 +51,16 @@ endif
ifeq ($(BOARD),sabrelite)
CFLAGS += -mcpu=cortex-a9
CFLAGS += -DQEMU_SOC_IMX6
CFLAGS += -DMICROPY_HW_MCU_NAME='"Cortex-A9"'
LDSCRIPT = imx6.ld
QEMU_EXTRA = -m 128M
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'
endif

CROSS_COMPILE ?= arm-none-eabi-
Expand Down Expand Up @@ -78,16 +93,18 @@ LIBS = $(shell $(CC) $(CFLAGS) -print-libgcc-file-name)
SRC_COMMON_C = \
startup.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 \

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

LIB_SRC_C += $(SRC_LIB_LIBM_C)
LIB_SRC_C += $(SRC_LIB_LIBM_SQRT_SW_C)

Expand All @@ -113,10 +130,21 @@ 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
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 $<

.PHONY: run
run: $(BUILD)/firmware.elf
qemu-system-arm -machine $(BOARD) $(QEMU_EXTRA) -nographic -monitor null -semihosting -kernel $<
qemu-system-arm $(QEMU_ARGS) -serial pty -kernel $<

.PHONY: test
test: $(BUILD)/firmware.elf
$(eval DIRNAME=ports/$(notdir $(CURDIR)))
cd $(TOP)/tests && ./run-tests.py --target qemu-arm --device execpty:"qemu-system-arm $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(TESTS_EXCLUDE)

## `$(LD)` doesn't seem to like `--specs` for some reason, but we can just use `$(CC)` here.
$(BUILD)/firmware.elf: $(LDSCRIPT) $(ALL_OBJ_RUN)
Expand Down
33 changes: 0 additions & 33 deletions ports/qemu-arm/Makefile.test

This file was deleted.

59 changes: 50 additions & 9 deletions ports/qemu-arm/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
MicroPython port to qemu-arm
============================

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

Expand All @@ -15,14 +18,52 @@ 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.

This port will only work with the [GNU ARM Embedded Toolchain](
https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
and not with CodeSourcery toolchain. You will need to modify
`LDFLAGS` if you want to use CodeSourcery's version of `arm-none-eabi`.
The difference is that CodeSourcery needs `-T generic-m-hosted.ld` while
ARM's version requires `--specs=nano.specs --specs=rdimon.specs` to be
passed to the linker.
Build instructions
------------------

First make sure the MicroPython cross-compiler is built (run from this directory):

$ make -C ../../mpy-cross

Then build using:

$ make

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

Running
-------

When the firmware is run it will provide a REPL on the emulated hardware UART.
To access the REPL directly use:

$ make repl

This will start `qemu-system-arm` with the UART redirected to stdio. It's also
possible to redirect the UART to a pty device using:

$ make run

This will start the emulation and the name of the pty device will be printed to
stdout. This serial device then be accessed via a serial terminal program,
for example `mpremote`:

$ mpremote connect /dev/pts/1

You can disconnect and reconnect to the serial device multiple times. Once you
are finished, stop the `make run` command by pressing Ctrl-C where that command
was started (or execute `machine.reset()` at the REPL).

The test suite can be run against the firmware by using the UART redirection.
You can either do this automatically using the single command:

$ make test

To build and run image with builtin testsuite:
Or manually by first starting the emulation with `make run` and then running the
tests against the serial device, for example:

make -f Makefile.test test
$ cd ../../tests
$ ./run-tests.py --target qemu-arm --device /dev/pts/1
53 changes: 31 additions & 22 deletions ports/qemu-arm/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,48 +25,57 @@
*/

#include <stdlib.h>
#include <stdio.h>

#include "py/compile.h"
#include "py/runtime.h"
#include "py/stackctrl.h"
#include "py/gc.h"
#include "py/mperrno.h"
#include "shared/runtime/gchelper.h"
#include "shared/runtime/pyexec.h"

void do_str(const char *src, mp_parse_input_kind_t input_kind) {
nlr_buf_t nlr;
if (nlr_push(&nlr) == 0) {
mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0);
qstr source_name = lex->source_name;
mp_parse_tree_t parse_tree = mp_parse(lex, input_kind);
mp_obj_t module_fun = mp_compile(&parse_tree, source_name, true);
mp_call_function_0(module_fun);
nlr_pop();
} else {
// uncaught exception
mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
}
}
#define HEAP_SIZE (100 * 1024)

static uint32_t gc_heap[HEAP_SIZE / sizeof(uint32_t)];

int main(int argc, char **argv) {
mp_stack_ctrl_init();
mp_stack_set_limit(10240);
uint32_t heap[16 * 1024 / 4];
gc_init(heap, (char *)heap + 16 * 1024);
mp_init();
do_str("print('hello world!')", MP_PARSE_SINGLE_INPUT);
mp_deinit();
return 0;
gc_init(gc_heap, (char *)gc_heap + HEAP_SIZE);

for (;;) {
mp_init();

for (;;) {
if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) {
if (pyexec_raw_repl() != 0) {
break;
}
} else {
if (pyexec_friendly_repl() != 0) {
break;
}
}
}

mp_printf(&mp_plat_print, "MPY: soft reboot\n");

gc_sweep_all();
mp_deinit();
}
}

void gc_collect(void) {
gc_collect_start();
gc_helper_collect_regs_and_stack();
gc_collect_end();
}

mp_lexer_t *mp_lexer_new_from_file(qstr filename) {
mp_raise_OSError(MP_ENOENT);
}

void nlr_jump_fail(void *val) {
printf("uncaught NLR\n");
mp_printf(&mp_plat_print, "uncaught NLR\n");
exit(1);
}
16 changes: 16 additions & 0 deletions ports/qemu-arm/modmachine.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@
// This file is never compiled standalone, it's included directly from
// extmod/modmachine.c via MICROPY_PY_MACHINE_INCLUDEFILE.

#include <stdlib.h>

static void mp_machine_idle(void) {
// Do nothing.
}

#if MICROPY_PY_MACHINE_RESET

static void mp_machine_reset(void) {
// Exit qemu (via semihosting call).
exit(0);
}

static mp_int_t mp_machine_reset_cause(void) {
// Not implemented.
return 0;
}

#endif
13 changes: 3 additions & 10 deletions ports/qemu-arm/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,19 @@
#define MICROPY_MALLOC_USES_ALLOCATED_SIZE (1)
#define MICROPY_MEM_STATS (1)
#define MICROPY_ENABLE_GC (1)
#define MICROPY_KBD_EXCEPTION (0)
#define MICROPY_HELPER_REPL (0)
#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1)
#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ)
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
#define MICROPY_WARNINGS (1)
#define MICROPY_PY_BUILTINS_INPUT (0)
#define MICROPY_PY_BUILTINS_HELP (0)
#define MICROPY_PY_IO_IOBASE (0)
#define MICROPY_PY_SYS_PLATFORM "qemu-arm"
#define MICROPY_PY_SYS_STDFILES (0)
#define MICROPY_PY_SYS_STDIO_BUFFER (0)
#define MICROPY_PY_SELECT (0)
#define MICROPY_PY_TIME (0)
#define MICROPY_PY_ASYNCIO (0)
#define MICROPY_PY_MACHINE (1)
#define MICROPY_PY_MACHINE_INCLUDEFILE "ports/qemu-arm/modmachine.c"
#define MICROPY_PY_MACHINE_RESET (1)
#define MICROPY_PY_MACHINE_PIN_BASE (1)
#define MICROPY_VFS (1)

Expand All @@ -78,8 +75,4 @@ typedef long mp_off_t;
// We need an implementation of the log2 function which is not a macro.
#define MP_NEED_LOG2 (1)

#ifdef TEST
#include "shared/upytesthelper/upytesthelper.h"
#undef MP_PLAT_PRINT_STRN
#define MP_PLAT_PRINT_STRN(str, len) upytest_output(str, len)
#endif
#define MP_STATE_PORT MP_STATE_VM
Loading
Loading