Skip to content

Commit d9a0fdd

Browse files
committed
qemu-arm: Rework to provide a REPL and run tests via a pty serial port.
Currently, the qemu-arm (and qemu-riscv) port has two build modes: - a simple test that executes a Python string; and - a full test that uses tinytest to embed all tests within the firmware, then executes that and captures the output. This is very different to all the other ports. A difficulty with using tinytest is that with the large number of tests the firmware overflows its virtual flash size. It's also hard to run tests via .mpy files and with the native emitter. Being different to the other ports also means an extra burden on maintenance. This commit reworks the qemu-arm port so that it has a single build target that creates a standard firmware which has a REPL. When run under qemu-system-arm, the REPL acts like any other bare-metal port, complete with soft reset (use machine.reset() to turn it off and exit qemu-system-arm). This approach gives many benefits: - allows playing with a REPL without hardware; - allows running the test suite as it would on a bare-metal board, by making qemu-system-arm redirect the UART serial of the virtual device to a /dev/pts/xx file, and then running run-tests.py against that serial device; - skipping tests is now done via the logic in `run-tests.py` and no longer needs multiple places to define which tests to skip (`tools/tinytest-codegen.py`, `ports/qemu-arm/tests_profile.txt` and also `tests/run-tests.py`); - allows testing/using mpremote with the qemu-arm port. Eventually the qemu-riscv port would have a similar change. Prior to this commit the test results were: 743 tests ok. (121 skipped) With this commit the test results are: 753 tests performed (22673 individual testcases) 753 tests passed 138 tests skipped More tests are skipped because more are included in the run. But overall more tests pass. Signed-off-by: Damien George <damien@micropython.org>
1 parent 8a3842e commit d9a0fdd

File tree

15 files changed

+237
-154
lines changed

15 files changed

+237
-154
lines changed

.github/workflows/ports_qemu-arm.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ jobs:
2929
run: source tools/ci.sh && ci_qemu_arm_build
3030
- name: Print failures
3131
if: failure()
32-
run: grep --before-context=100 --text "FAIL" ports/qemu-arm/build/console.out
32+
run: tests/run-tests.py --print-failures

ports/qemu-arm/Makefile

+33-8
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,19 @@ QSTR_DEFS = qstrdefsport.h
1111

1212
# MicroPython feature configurations
1313
MICROPY_ROM_TEXT_COMPRESSION ?= 1
14+
FROZEN_MANIFEST ?= "freeze('test-frzmpy')"
1415

1516
# include py core make definitions
1617
include $(TOP)/py/py.mk
1718
include $(TOP)/extmod/extmod.mk
1819

20+
CFLAGS += -DMICROPY_HW_BOARD_NAME='"$(BOARD)"'
21+
QEMU_ARGS += -machine $(BOARD) -nographic -monitor null -semihosting
22+
1923
ifeq ($(BOARD),netduino2)
2024
CFLAGS += -mthumb -mcpu=cortex-m3 -mfloat-abi=soft
2125
CFLAGS += -DQEMU_SOC_STM32
26+
CFLAGS += -DMICROPY_HW_MCU_NAME='"STM32"'
2227
LDSCRIPT = stm32.ld
2328
SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o
2429
MPY_CROSS_FLAGS += -march=armv7m
@@ -27,15 +32,17 @@ endif
2732
ifeq ($(BOARD),microbit)
2833
CFLAGS += -mthumb -mcpu=cortex-m0 -mfloat-abi=soft
2934
CFLAGS += -DQEMU_SOC_NRF51
35+
CFLAGS += -DMICROPY_HW_MCU_NAME='"nRF51"'
3036
LDSCRIPT = nrf51.ld
31-
QEMU_EXTRA = -global nrf51-soc.flash-size=1048576 -global nrf51-soc.sram-size=262144
37+
QEMU_ARGS += -global nrf51-soc.flash-size=1048576 -global nrf51-soc.sram-size=262144
3238
SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb1.o
3339
MPY_CROSS_FLAGS += -march=armv7m
3440
endif
3541

3642
ifeq ($(BOARD),mps2-an385)
3743
CFLAGS += -mthumb -mcpu=cortex-m3 -mfloat-abi=soft
3844
CFLAGS += -DQEMU_SOC_MPS2
45+
CFLAGS += -DMICROPY_HW_MCU_NAME='"Cortex-M3"'
3946
LDSCRIPT = mps2.ld
4047
SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o
4148
MPY_CROSS_FLAGS += -march=armv7m
@@ -44,11 +51,16 @@ endif
4451
ifeq ($(BOARD),sabrelite)
4552
CFLAGS += -mcpu=cortex-a9
4653
CFLAGS += -DQEMU_SOC_IMX6
54+
CFLAGS += -DMICROPY_HW_MCU_NAME='"Cortex-A9"'
4755
LDSCRIPT = imx6.ld
48-
QEMU_EXTRA = -m 128M
56+
QEMU_ARGS += -m 128M
4957
SRC_BOARD_O = shared/runtime/gchelper_generic.o
5058
# It's really armv7a but closest supported value is armv6.
5159
MPY_CROSS_FLAGS += -march=armv6
60+
# Cortex-A9 should support unaligned-access, but qemu doesn't seem to.
61+
CFLAGS += -mno-unaligned-access
62+
# These don't work on Cortex-A9.
63+
TESTS_EXCLUDE = --exclude '(asmdiv|asmspecialregs).py'
5264
endif
5365

5466
CROSS_COMPILE ?= arm-none-eabi-
@@ -81,16 +93,18 @@ LIBS = $(shell $(CC) $(CFLAGS) -print-libgcc-file-name)
8193
SRC_COMMON_C = \
8294
startup.c \
8395
uart.c \
96+
mphalport.c \
8497
shared/libc/string0.c \
98+
shared/readline/readline.c \
99+
shared/runtime/interrupt_char.c \
100+
shared/runtime/pyexec.c \
101+
shared/runtime/semihosting_arm.c \
102+
shared/runtime/stdout_helpers.c \
85103
shared/runtime/sys_stdio_mphal.c \
86104

87105
SRC_RUN_C = \
88106
main.c \
89107

90-
SRC_TEST_C = \
91-
test_main.c \
92-
lib/tinytest/tinytest.c \
93-
94108
LIB_SRC_C += $(SRC_LIB_LIBM_C)
95109
LIB_SRC_C += $(SRC_LIB_LIBM_SQRT_SW_C)
96110

@@ -116,10 +130,21 @@ OBJ = $(OBJ_COMMON) $(OBJ_RUN) $(OBJ_TEST)
116130
# List of sources for qstr extraction
117131
SRC_QSTR += $(SRC_COMMON_C) $(SRC_RUN_C) $(LIB_SRC_C)
118132

119-
all: run
133+
all: $(BUILD)/firmware.elf
120134

135+
.PHONY: repl
136+
repl: $(BUILD)/firmware.elf
137+
$(ECHO) "Use machine.reset() to exit"
138+
qemu-system-arm $(QEMU_ARGS) -serial mon:stdio -kernel $<
139+
140+
.PHONY: run
121141
run: $(BUILD)/firmware.elf
122-
qemu-system-arm -machine $(BOARD) $(QEMU_EXTRA) -nographic -monitor null -semihosting -kernel $<
142+
qemu-system-arm $(QEMU_ARGS) -serial pty -kernel $<
143+
144+
.PHONY: test
145+
test: $(BUILD)/firmware.elf
146+
$(eval DIRNAME=ports/$(notdir $(CURDIR)))
147+
cd $(TOP)/tests && ./run-tests.py --target qemu-arm --device execpty:"qemu-system-arm $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(TESTS_EXCLUDE)
123148

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

ports/qemu-arm/Makefile.test

-33
This file was deleted.

ports/qemu-arm/README.md

+50-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
MicroPython port to qemu-arm
2+
============================
3+
14
This is experimental, community-supported port for Cortex-M emulation as
25
provided by QEMU (http://qemu.org).
36

@@ -15,14 +18,52 @@ The purposes of this port are to enable:
1518
- no need to use OpenOCD or anything else that might slow down the
1619
process in terms of plugging things together, pressing buttons, etc.
1720

18-
This port will only work with the [GNU ARM Embedded Toolchain](
19-
https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
20-
and not with CodeSourcery toolchain. You will need to modify
21-
`LDFLAGS` if you want to use CodeSourcery's version of `arm-none-eabi`.
22-
The difference is that CodeSourcery needs `-T generic-m-hosted.ld` while
23-
ARM's version requires `--specs=nano.specs --specs=rdimon.specs` to be
24-
passed to the linker.
21+
Build instructions
22+
------------------
23+
24+
First make sure the MicroPython cross-compiler is built (run from this directory):
25+
26+
$ make -C ../../mpy-cross
27+
28+
Then build using:
29+
30+
$ make
31+
32+
The default qemu-supported board is `mps2-an385`, a Cortex-M3 board. To select a
33+
different board pass the `BOARD` argument to `make`, for example:
34+
35+
$ make BOARD=sabrelite
36+
37+
Running
38+
-------
39+
40+
When the firmware is run it will provide a REPL on the emulated hardware UART.
41+
To access the REPL directly use:
42+
43+
$ make repl
44+
45+
This will start `qemu-system-arm` with the UART redirected to stdio. It's also
46+
possible to redirect the UART to a pty device using:
47+
48+
$ make run
49+
50+
This will start the emulation and the name of the pty device will be printed to
51+
stdout. This serial device then be accessed via a serial terminal program,
52+
for example `mpremote`:
53+
54+
$ mpremote connect /dev/pts/1
55+
56+
You can disconnect and reconnect to the serial device multiple times. Once you
57+
are finished, stop the `make run` command by pressing Ctrl-C where that command
58+
was started (or execute `machine.reset()` at the REPL).
59+
60+
The test suite can be run against the firmware by using the UART redirection.
61+
You can either do this automatically using the single command:
62+
63+
$ make test
2564

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

28-
make -f Makefile.test test
68+
$ cd ../../tests
69+
$ ./run-tests.py --target qemu-arm --device /dev/pts/1

ports/qemu-arm/main.c

+31-22
Original file line numberDiff line numberDiff line change
@@ -25,48 +25,57 @@
2525
*/
2626

2727
#include <stdlib.h>
28-
#include <stdio.h>
2928

3029
#include "py/compile.h"
3130
#include "py/runtime.h"
3231
#include "py/stackctrl.h"
3332
#include "py/gc.h"
3433
#include "py/mperrno.h"
34+
#include "shared/runtime/gchelper.h"
35+
#include "shared/runtime/pyexec.h"
3536

36-
void do_str(const char *src, mp_parse_input_kind_t input_kind) {
37-
nlr_buf_t nlr;
38-
if (nlr_push(&nlr) == 0) {
39-
mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0);
40-
qstr source_name = lex->source_name;
41-
mp_parse_tree_t parse_tree = mp_parse(lex, input_kind);
42-
mp_obj_t module_fun = mp_compile(&parse_tree, source_name, true);
43-
mp_call_function_0(module_fun);
44-
nlr_pop();
45-
} else {
46-
// uncaught exception
47-
mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
48-
}
49-
}
37+
#define HEAP_SIZE (100 * 1024)
38+
39+
static uint32_t gc_heap[HEAP_SIZE / sizeof(uint32_t)];
5040

5141
int main(int argc, char **argv) {
5242
mp_stack_ctrl_init();
5343
mp_stack_set_limit(10240);
54-
uint32_t heap[16 * 1024 / 4];
55-
gc_init(heap, (char *)heap + 16 * 1024);
56-
mp_init();
57-
do_str("print('hello world!')", MP_PARSE_SINGLE_INPUT);
58-
mp_deinit();
59-
return 0;
44+
gc_init(gc_heap, (char *)gc_heap + HEAP_SIZE);
45+
46+
for (;;) {
47+
mp_init();
48+
49+
for (;;) {
50+
if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) {
51+
if (pyexec_raw_repl() != 0) {
52+
break;
53+
}
54+
} else {
55+
if (pyexec_friendly_repl() != 0) {
56+
break;
57+
}
58+
}
59+
}
60+
61+
mp_printf(&mp_plat_print, "MPY: soft reboot\n");
62+
63+
gc_sweep_all();
64+
mp_deinit();
65+
}
6066
}
6167

6268
void gc_collect(void) {
69+
gc_collect_start();
70+
gc_helper_collect_regs_and_stack();
71+
gc_collect_end();
6372
}
6473

6574
mp_lexer_t *mp_lexer_new_from_file(qstr filename) {
6675
mp_raise_OSError(MP_ENOENT);
6776
}
6877

6978
void nlr_jump_fail(void *val) {
70-
printf("uncaught NLR\n");
79+
mp_printf(&mp_plat_print, "uncaught NLR\n");
7180
exit(1);
7281
}

ports/qemu-arm/modmachine.c

+16
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@
2727
// This file is never compiled standalone, it's included directly from
2828
// extmod/modmachine.c via MICROPY_PY_MACHINE_INCLUDEFILE.
2929

30+
#include <stdlib.h>
31+
3032
static void mp_machine_idle(void) {
3133
// Do nothing.
3234
}
35+
36+
#if MICROPY_PY_MACHINE_RESET
37+
38+
static void mp_machine_reset(void) {
39+
// Exit qemu (via semihosting call).
40+
exit(0);
41+
}
42+
43+
static mp_int_t mp_machine_reset_cause(void) {
44+
// Not implemented.
45+
return 0;
46+
}
47+
48+
#endif

ports/qemu-arm/mpconfigport.h

+3-10
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,19 @@
4242
#define MICROPY_MALLOC_USES_ALLOCATED_SIZE (1)
4343
#define MICROPY_MEM_STATS (1)
4444
#define MICROPY_ENABLE_GC (1)
45-
#define MICROPY_KBD_EXCEPTION (0)
46-
#define MICROPY_HELPER_REPL (0)
45+
#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1)
4746
#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ)
4847
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
4948
#define MICROPY_WARNINGS (1)
50-
#define MICROPY_PY_BUILTINS_INPUT (0)
51-
#define MICROPY_PY_BUILTINS_HELP (0)
5249
#define MICROPY_PY_IO_IOBASE (0)
5350
#define MICROPY_PY_SYS_PLATFORM "qemu-arm"
54-
#define MICROPY_PY_SYS_STDFILES (0)
5551
#define MICROPY_PY_SYS_STDIO_BUFFER (0)
5652
#define MICROPY_PY_SELECT (0)
5753
#define MICROPY_PY_TIME (0)
5854
#define MICROPY_PY_ASYNCIO (0)
5955
#define MICROPY_PY_MACHINE (1)
6056
#define MICROPY_PY_MACHINE_INCLUDEFILE "ports/qemu-arm/modmachine.c"
57+
#define MICROPY_PY_MACHINE_RESET (1)
6158
#define MICROPY_PY_MACHINE_PIN_BASE (1)
6259
#define MICROPY_VFS (1)
6360

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

81-
#ifdef TEST
82-
#include "shared/upytesthelper/upytesthelper.h"
83-
#undef MP_PLAT_PRINT_STRN
84-
#define MP_PLAT_PRINT_STRN(str, len) upytest_output(str, len)
85-
#endif
78+
#define MP_STATE_PORT MP_STATE_VM

0 commit comments

Comments
 (0)