From 9f9c283ef48950487bb566e9717819e8a2a29ac6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 12 Aug 2024 11:17:00 +1000 Subject: [PATCH 1/6] shared/runtime/semihosting_arm: Support semihosting on non-Thumb ARM. Signed-off-by: Damien George --- shared/runtime/semihosting_arm.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/shared/runtime/semihosting_arm.c b/shared/runtime/semihosting_arm.c index 3ca29d5d752c4..5cfaaa3c6b3c7 100644 --- a/shared/runtime/semihosting_arm.c +++ b/shared/runtime/semihosting_arm.c @@ -40,10 +40,6 @@ #define OPEN_MODE_READ (0) // mode "r" #define OPEN_MODE_WRITE (4) // mode "w" -#ifndef __thumb__ -#error Semihosting is only implemented for ARM microcontrollers. -#endif - static int mp_semihosting_stdout; static uint32_t mp_semihosting_call(uint32_t num, const void *arg) { @@ -61,7 +57,13 @@ static uint32_t mp_semihosting_call(uint32_t num, const void *arg) { register uint32_t num_reg __asm__ ("r0") = num; register const void *args_reg __asm__ ("r1") = arg; __asm__ __volatile__ ( + #if defined(__ARM_ARCH_ISA_ARM) + "svc 0x00123456\n" // invoke semihosting call + #elif defined(__ARM_ARCH_ISA_THUMB) "bkpt 0xAB\n" // invoke semihosting call + #else + #error Unknown architecture + #endif : "+r" (num_reg) // call number and result : "r" (args_reg) // arguments : "memory"); // make sure args aren't optimized away From 70a6791b09f9fad55b99e5996433888cee0a7a64 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 9 Aug 2024 13:54:14 +1000 Subject: [PATCH 2/6] shared/runtime/semihosting_arm: Add mp_semihosting_rx_chars. Signed-off-by: Damien George --- shared/runtime/semihosting_arm.c | 15 +++++++++++++++ shared/runtime/semihosting_arm.h | 3 +++ 2 files changed, 18 insertions(+) diff --git a/shared/runtime/semihosting_arm.c b/shared/runtime/semihosting_arm.c index 5cfaaa3c6b3c7..d44124faabadf 100644 --- a/shared/runtime/semihosting_arm.c +++ b/shared/runtime/semihosting_arm.c @@ -34,6 +34,7 @@ #define SYS_OPEN 0x01 #define SYS_WRITEC 0x03 #define SYS_WRITE 0x05 +#define SYS_READ 0x06 #define SYS_READC 0x07 // Constants: @@ -91,6 +92,20 @@ int mp_semihosting_rx_char() { return mp_semihosting_call(SYS_READC, NULL); } +// Returns 0 on success. +int mp_semihosting_rx_chars(char *str, size_t len) { + struct { + uint32_t fd; + const char *str; + uint32_t len; + } args = { + .fd = mp_semihosting_stdout, + .str = str, + .len = len, + }; + return mp_semihosting_call(SYS_READ, &args); +} + static void mp_semihosting_tx_char(char c) { mp_semihosting_call(SYS_WRITEC, &c); } diff --git a/shared/runtime/semihosting_arm.h b/shared/runtime/semihosting_arm.h index 7e90f25ac916c..1faaae7fec7d8 100644 --- a/shared/runtime/semihosting_arm.h +++ b/shared/runtime/semihosting_arm.h @@ -38,6 +38,8 @@ Then make sure the debugger is attached and enables semihosting. In OpenOCD thi done with ARM semihosting enable followed by reset. The terminal will need further configuration to work with MicroPython (bash: stty raw -echo). +If mp_semihosting_rx_char() doesn't work then try mp_semihosting_rx_chars(str, 1). + */ #include @@ -45,6 +47,7 @@ configuration to work with MicroPython (bash: stty raw -echo). void mp_semihosting_init(); int mp_semihosting_rx_char(); +int mp_semihosting_rx_chars(char *str, size_t len); uint32_t mp_semihosting_tx_strn(const char *str, size_t len); uint32_t mp_semihosting_tx_strn_cooked(const char *str, size_t len); From 1090f1a60c730cd589f65047e105a22a0445721d Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 12 Aug 2024 11:17:26 +1000 Subject: [PATCH 3/6] shared/runtime/semihosting_arm: Add mp_semihosting_exit. Signed-off-by: Damien George --- shared/runtime/semihosting_arm.c | 8 ++++++++ shared/runtime/semihosting_arm.h | 1 + 2 files changed, 9 insertions(+) diff --git a/shared/runtime/semihosting_arm.c b/shared/runtime/semihosting_arm.c index d44124faabadf..f4d168f79bc81 100644 --- a/shared/runtime/semihosting_arm.c +++ b/shared/runtime/semihosting_arm.c @@ -36,6 +36,7 @@ #define SYS_WRITE 0x05 #define SYS_READ 0x06 #define SYS_READC 0x07 +#define SYS_EXIT 0x18 // Constants: #define OPEN_MODE_READ (0) // mode "r" @@ -88,6 +89,13 @@ void mp_semihosting_init() { mp_semihosting_stdout = mp_semihosting_open_console(OPEN_MODE_WRITE); } +void mp_semihosting_exit(int status) { + if (status == 0) { + status = 0x20026; + } + mp_semihosting_call(SYS_EXIT, (void *)(uintptr_t)status); +} + int mp_semihosting_rx_char() { return mp_semihosting_call(SYS_READC, NULL); } diff --git a/shared/runtime/semihosting_arm.h b/shared/runtime/semihosting_arm.h index 1faaae7fec7d8..08fb66578ac14 100644 --- a/shared/runtime/semihosting_arm.h +++ b/shared/runtime/semihosting_arm.h @@ -46,6 +46,7 @@ If mp_semihosting_rx_char() doesn't work then try mp_semihosting_rx_chars(str, 1 #include void mp_semihosting_init(); +void mp_semihosting_exit(int status); int mp_semihosting_rx_char(); int mp_semihosting_rx_chars(char *str, size_t len); uint32_t mp_semihosting_tx_strn(const char *str, size_t len); From e8863e44e51bdd22a684f88779770845f0e7245b Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 12 Aug 2024 11:16:10 +1000 Subject: [PATCH 4/6] qemu-arm/Makefile: Make the build directory reflect the board. So multiple boards can be built at once. Signed-off-by: Damien George --- ports/qemu-arm/Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ports/qemu-arm/Makefile b/ports/qemu-arm/Makefile index f521a0c5ad82d..5ff63bf7daacd 100644 --- a/ports/qemu-arm/Makefile +++ b/ports/qemu-arm/Makefile @@ -1,3 +1,8 @@ +BOARD ?= mps2-an385 + +# Make the build directory reflect the board. +BUILD ?= build-$(BOARD) + include ../../py/mkenv.mk -include mpconfigport.mk @@ -11,8 +16,6 @@ MICROPY_ROM_TEXT_COMPRESSION ?= 1 include $(TOP)/py/py.mk include $(TOP)/extmod/extmod.mk -BOARD ?= mps2-an385 - ifeq ($(BOARD),netduino2) CFLAGS += -mthumb -mcpu=cortex-m3 -mfloat-abi=soft CFLAGS += -DQEMU_SOC_STM32 From 8a3842eba7db90af2b29035c5c1ecfd5b140f493 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 12 Aug 2024 11:20:30 +1000 Subject: [PATCH 5/6] qemu-arm/uart: Implement uart_rx_chr. Signed-off-by: Damien George --- ports/qemu-arm/uart.c | 57 +++++++++++++++++++++++++++++++++++++++---- ports/qemu-arm/uart.h | 4 +++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/ports/qemu-arm/uart.c b/ports/qemu-arm/uart.c index d7338f9a24230..5ace3d465f147 100644 --- a/ports/qemu-arm/uart.c +++ b/ports/qemu-arm/uart.c @@ -31,14 +31,29 @@ #if defined(QEMU_SOC_STM32) +#define UART_SR_RXNE (1 << 5) +#define UART_CR1_UE (1 << 13) +#define UART_CR1_TE (1 << 3) +#define UART_CR1_RE (1 << 2) + typedef struct _UART_t { volatile uint32_t SR; volatile uint32_t DR; + volatile uint32_t BRR; + volatile uint32_t CR1; } UART_t; #define UART0 ((UART_t *)(0x40011000)) void uart_init(void) { + UART0->CR1 = UART_CR1_UE | UART_CR1_TE | UART_CR1_RE; +} + +int uart_rx_chr(void) { + if (!(UART0->SR & UART_SR_RXNE)) { + return UART_RX_NO_CHAR; + } + return UART0->DR; } void uart_tx_strn(const char *buf, size_t len) { @@ -50,11 +65,15 @@ void uart_tx_strn(const char *buf, size_t len) { #elif defined(QEMU_SOC_NRF51) typedef struct _UART_t { - volatile uint32_t r0[2]; + volatile uint32_t STARTRX; // 0x000 + volatile uint32_t STOPRX; // 0x004 volatile uint32_t STARTTX; // 0x008 - volatile uint32_t r1[(0x500 - 0x008) / 4 - 1]; + volatile uint32_t r0[(0x108 - 0x008) / 4 - 1]; + volatile uint32_t RXDRDY; // 0x108 + volatile uint32_t r1[(0x500 - 0x108) / 4 - 1]; volatile uint32_t ENABLE; // 0x500 - volatile uint32_t r2[(0x51c - 0x500) / 4 - 1]; + volatile uint32_t r2[(0x518 - 0x500) / 4 - 1]; + volatile uint32_t RXD; // 0x518 volatile uint32_t TXD; // 0x51c } UART_t; @@ -62,9 +81,18 @@ typedef struct _UART_t { void uart_init(void) { UART0->ENABLE = 4; + UART0->STARTRX = 1; UART0->STARTTX = 1; } +int uart_rx_chr(void) { + if (!UART0->RXDRDY) { + return UART_RX_NO_CHAR; + } + UART0->RXDRDY = 0; + return UART0->RXD; +} + void uart_tx_strn(const char *buf, size_t len) { for (size_t i = 0; i < len; ++i) { UART0->TXD = buf[i]; @@ -74,6 +102,7 @@ void uart_tx_strn(const char *buf, size_t len) { #elif defined(QEMU_SOC_MPS2) #define UART_STATE_TXFULL (1 << 0) +#define UART_STATE_RXFULL (1 << 1) #define UART_CTRL_TX_EN (1 << 0) #define UART_CTRL_RX_EN (1 << 1) @@ -90,7 +119,14 @@ typedef struct _UART_t { void uart_init(void) { UART0->BAUDDIV = 16; - UART0->CTRL = UART_CTRL_TX_EN; + UART0->CTRL = UART_CTRL_TX_EN | UART_CTRL_RX_EN; +} + +int uart_rx_chr(void) { + if (!(UART0->STATE & UART_STATE_RXFULL)) { + return UART_RX_NO_CHAR; + } + return UART0->DATA; } void uart_tx_strn(const char *buf, size_t len) { @@ -104,7 +140,9 @@ void uart_tx_strn(const char *buf, size_t len) { #elif defined(QEMU_SOC_IMX6) #define UART_UCR1_UARTEN (1 << 0) +#define UART_UCR2_RXEN (1 << 1) #define UART_UCR2_TXEN (1 << 2) +#define UART_UTS1_RXEMPTY (1 << 5) typedef struct _UART_t { volatile uint32_t URXD; // 0x00 @@ -113,13 +151,22 @@ typedef struct _UART_t { volatile uint32_t r1[15]; volatile uint32_t UCR1; // 0x80 volatile uint32_t UCR2; // 0x84 + volatile uint32_t r2[11]; + volatile uint32_t UTS1; // 0xb4 } UART_t; #define UART1 ((UART_t *)(0x02020000)) void uart_init(void) { UART1->UCR1 = UART_UCR1_UARTEN; - UART1->UCR2 = UART_UCR2_TXEN; + UART1->UCR2 = UART_UCR2_TXEN | UART_UCR2_RXEN; +} + +int uart_rx_chr(void) { + if (UART1->UTS1 & UART_UTS1_RXEMPTY) { + return UART_RX_NO_CHAR; + } + return UART1->URXD & 0xff; } void uart_tx_strn(const char *buf, size_t len) { diff --git a/ports/qemu-arm/uart.h b/ports/qemu-arm/uart.h index 33eae05bd76dd..9c62a295d1d04 100644 --- a/ports/qemu-arm/uart.h +++ b/ports/qemu-arm/uart.h @@ -28,7 +28,11 @@ #include +// Returned from uart_rx_chr when there are no chars available. +#define UART_RX_NO_CHAR (-1) + void uart_init(void); +int uart_rx_chr(void); void uart_tx_strn(const char *buf, size_t len); #endif // MICROPY_INCLUDED_QEMU_ARM_UART_H From d9a0fdda9a7b0db55c1115b55bb1b83cd5ce739c Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 12 Aug 2024 11:32:28 +1000 Subject: [PATCH 6/6] 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 --- .github/workflows/ports_qemu-arm.yml | 2 +- ports/qemu-arm/Makefile | 41 +++++++++--- ports/qemu-arm/Makefile.test | 33 ---------- ports/qemu-arm/README.md | 59 ++++++++++++++--- ports/qemu-arm/main.c | 53 ++++++++------- ports/qemu-arm/modmachine.c | 16 +++++ ports/qemu-arm/mpconfigport.h | 13 +--- ports/qemu-arm/mphalport.c | 66 +++++++++++++++++++ ports/qemu-arm/mphalport.h | 5 +- ports/qemu-arm/startup.c | 29 ++------ ports/qemu-arm/tests_profile.txt | 16 ----- ports/qemu-riscv/Makefile | 2 +- .../test_main.c => qemu-riscv/main.c} | 38 ++++++----- tests/run-tests.py | 12 ++-- tools/ci.sh | 6 +- 15 files changed, 237 insertions(+), 154 deletions(-) delete mode 100644 ports/qemu-arm/Makefile.test create mode 100644 ports/qemu-arm/mphalport.c delete mode 100644 ports/qemu-arm/tests_profile.txt rename ports/{qemu-arm/test_main.c => qemu-riscv/main.c} (65%) diff --git a/.github/workflows/ports_qemu-arm.yml b/.github/workflows/ports_qemu-arm.yml index db3cd7871d591..99750b7535657 100644 --- a/.github/workflows/ports_qemu-arm.yml +++ b/.github/workflows/ports_qemu-arm.yml @@ -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 diff --git a/ports/qemu-arm/Makefile b/ports/qemu-arm/Makefile index 5ff63bf7daacd..cdfc39580b9d8 100644 --- a/ports/qemu-arm/Makefile +++ b/ports/qemu-arm/Makefile @@ -11,14 +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 +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 @@ -27,8 +32,9 @@ 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 @@ -36,6 +42,7 @@ 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 @@ -44,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- @@ -81,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) @@ -116,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) diff --git a/ports/qemu-arm/Makefile.test b/ports/qemu-arm/Makefile.test deleted file mode 100644 index cb5b0927c8c9a..0000000000000 --- a/ports/qemu-arm/Makefile.test +++ /dev/null @@ -1,33 +0,0 @@ -LIB_SRC_C = shared/upytesthelper/upytesthelper.c - -FROZEN_MANIFEST ?= "freeze('test-frzmpy')" - -include Makefile - -ifeq ($(BOARD),sabrelite) -# These don't work on Cortex-A9. -TESTS_EXCLUDE = inlineasm/asmdiv.py inlineasm/asmspecialregs.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-arm --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 30s qemu-system-arm -machine $(BOARD) $(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" diff --git a/ports/qemu-arm/README.md b/ports/qemu-arm/README.md index f821c4d1e28fb..34d73fd165756 100644 --- a/ports/qemu-arm/README.md +++ b/ports/qemu-arm/README.md @@ -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). @@ -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 diff --git a/ports/qemu-arm/main.c b/ports/qemu-arm/main.c index 025c1f17da04a..042106580407d 100644 --- a/ports/qemu-arm/main.c +++ b/ports/qemu-arm/main.c @@ -25,41 +25,50 @@ */ #include -#include #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) { @@ -67,6 +76,6 @@ mp_lexer_t *mp_lexer_new_from_file(qstr filename) { } void nlr_jump_fail(void *val) { - printf("uncaught NLR\n"); + mp_printf(&mp_plat_print, "uncaught NLR\n"); exit(1); } diff --git a/ports/qemu-arm/modmachine.c b/ports/qemu-arm/modmachine.c index a897c5670e49b..75872a22c1f40 100644 --- a/ports/qemu-arm/modmachine.c +++ b/ports/qemu-arm/modmachine.c @@ -27,6 +27,22 @@ // This file is never compiled standalone, it's included directly from // extmod/modmachine.c via MICROPY_PY_MACHINE_INCLUDEFILE. +#include + 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 diff --git a/ports/qemu-arm/mpconfigport.h b/ports/qemu-arm/mpconfigport.h index fce379e47ecf4..4059a5926d3cd 100644 --- a/ports/qemu-arm/mpconfigport.h +++ b/ports/qemu-arm/mpconfigport.h @@ -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) @@ -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 diff --git a/ports/qemu-arm/mphalport.c b/ports/qemu-arm/mphalport.c new file mode 100644 index 0000000000000..dbb87b48b801a --- /dev/null +++ b/ports/qemu-arm/mphalport.c @@ -0,0 +1,66 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 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. + */ + +#include "py/mphal.h" +#include "shared/runtime/semihosting_arm.h" +#include "uart.h" + +// UART is better behaved with redirection under qemu-system-arm, so prefer that for stdio. +#define USE_UART (1) +#define USE_SEMIHOSTING (0) + +uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { + // Not implemented. + return 0; +} + +int mp_hal_stdin_rx_chr(void) { + for (;;) { + #if USE_UART + int c = uart_rx_chr(); + if (c >= 0) { + return c; + } + #endif + #if USE_SEMIHOSTING + char str[1]; + int ret = mp_semihosting_rx_chars(str, 1); + if (ret == 0) { + return str[0]; + } + #endif + } +} + +mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) { + #if USE_UART + uart_tx_strn(str, len); + #endif + #if USE_SEMIHOSTING + mp_semihosting_tx_strn(str, len); + #endif + return len; +} diff --git a/ports/qemu-arm/mphalport.h b/ports/qemu-arm/mphalport.h index 8a40505ba6b3a..348b45701b89e 100644 --- a/ports/qemu-arm/mphalport.h +++ b/ports/qemu-arm/mphalport.h @@ -24,7 +24,4 @@ * THE SOFTWARE. */ -#include "uart.h" - -#define mp_hal_stdin_rx_chr() (0) -#define mp_hal_stdout_tx_strn_cooked(s, l) uart_tx_strn((s), (l)) +#include "shared/runtime/interrupt_char.h" diff --git a/ports/qemu-arm/startup.c b/ports/qemu-arm/startup.c index a1e89d111c007..118a5b8006f15 100644 --- a/ports/qemu-arm/startup.c +++ b/ports/qemu-arm/startup.c @@ -28,6 +28,7 @@ #include #include +#include "shared/runtime/semihosting_arm.h" #include "uart.h" extern uint32_t _estack, _sidata, _sdata, _edata, _sbss, _ebss; @@ -97,6 +98,8 @@ const uint32_t isr_vector[] __attribute__((section(".isr_vector"))) = { #endif void _start(void) { + mp_semihosting_init(); + // Enable the UART uart_init(); @@ -108,21 +111,9 @@ void _start(void) { exit(0); } -__attribute__((naked)) void exit(int status) { +void exit(int status) { // Force qemu to exit using ARM Semihosting - __asm volatile ( - "mov r1, r0\n" - "cmp r1, #0\n" - "bne .notclean\n" - "ldr r1, =0x20026\n" // ADP_Stopped_ApplicationExit, a clean exit - ".notclean:\n" - "movs r0, #0x18\n" // SYS_EXIT - #if defined(__ARM_ARCH_ISA_ARM) - "svc 0x00123456\n" - #elif defined(__ARM_ARCH_ISA_THUMB) - "bkpt 0xab\n" - #endif - ); + mp_semihosting_exit(status); for (;;) { } } @@ -134,13 +125,3 @@ void __assert_func(const char *file, int line, const char *func, const char *exp exit(1); } #endif - -// The following are needed for tinytest - -#include - -int setvbuf(FILE *stream, char *buf, int mode, size_t size) { - return 0; -} - -struct _reent *_impure_ptr; diff --git a/ports/qemu-arm/tests_profile.txt b/ports/qemu-arm/tests_profile.txt deleted file mode 100644 index 101943b7c6ab0..0000000000000 --- a/ports/qemu-arm/tests_profile.txt +++ /dev/null @@ -1,16 +0,0 @@ -# Port-specific test directories. - -test_dirs.update(("inlineasm", "ports/qemu-arm")) - -# Port-specific tests exclusion list. - -exclude_tests.update( - ( - # inline asm FP tests (require Cortex-M4) - "inlineasm/asmfpaddsub.py", - "inlineasm/asmfpcmp.py", - "inlineasm/asmfpldrstr.py", - "inlineasm/asmfpmuldiv.py", - "inlineasm/asmfpsqrt.py", - ) -) diff --git a/ports/qemu-riscv/Makefile b/ports/qemu-riscv/Makefile index 6f15ce52e73c7..473aec882d640 100644 --- a/ports/qemu-riscv/Makefile +++ b/ports/qemu-riscv/Makefile @@ -81,7 +81,7 @@ SRC_COMMON_C = \ shared/runtime/sys_stdio_mphal.c \ SRC_RUN_C = \ - ports/qemu-arm/main.c \ + main.c \ SRC_TEST_C = \ test_main.c \ diff --git a/ports/qemu-arm/test_main.c b/ports/qemu-riscv/main.c similarity index 65% rename from ports/qemu-arm/test_main.c rename to ports/qemu-riscv/main.c index 96984f7cd16ed..025c1f17da04a 100644 --- a/ports/qemu-arm/test_main.c +++ b/ports/qemu-riscv/main.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2014 Ilya Dmitrichenko + * Copyright (c) 2014-2023 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -32,28 +32,34 @@ #include "py/stackctrl.h" #include "py/gc.h" #include "py/mperrno.h" -#include "shared/runtime/gchelper.h" -#include "lib/tinytest/tinytest.h" -#include "lib/tinytest/tinytest_macros.h" -#define HEAP_SIZE (100 * 1024) - -#include "genhdr/tests.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); + } +} -int main() { +int main(int argc, char **argv) { mp_stack_ctrl_init(); mp_stack_set_limit(10240); - static uint32_t heap[HEAP_SIZE / sizeof(uint32_t)]; - upytest_set_heap(heap, (char *)heap + HEAP_SIZE); - int r = tinytest_main(0, NULL, groups); - printf("status: %d\n", r); - return r; + 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; } 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) { diff --git a/tests/run-tests.py b/tests/run-tests.py index d0c93f74b9d39..60bfc2599fb47 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -676,7 +676,11 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): "extmod/time_time_ns.py" ) # RA fsp rtc function doesn't support nano sec info elif args.target == "qemu-arm": - skip_tests.add("misc/print_exception.py") # requires sys stdfiles + skip_tests.add("inlineasm/asmfpaddsub.py") # requires Cortex-M4 + skip_tests.add("inlineasm/asmfpcmp.py") + skip_tests.add("inlineasm/asmfpldrstr.py") + skip_tests.add("inlineasm/asmfpmuldiv.py") + skip_tests.add("inlineasm/asmfpsqrt.py") elif args.target == "qemu-riscv": skip_tests.add("misc/print_exception.py") # requires sys stdfiles elif args.target == "webassembly": @@ -1043,7 +1047,6 @@ def main(): LOCAL_TARGETS = ( "unix", - "qemu-arm", "qemu-riscv", "webassembly", ) @@ -1054,6 +1057,7 @@ def main(): "esp32", "minimal", "nrf", + "qemu-arm", "renesas-ra", "rp2", ) @@ -1141,10 +1145,6 @@ def main(): "ports/unix", ) elif args.target == "qemu-arm": - if not args.write_exp: - raise ValueError("--target=qemu-arm must be used with --write-exp") - # Generate expected output files for qemu run. - # This list should match the test_dirs tuple in tinytest-codegen.py. test_dirs += ( "float", "inlineasm", diff --git a/tools/ci.sh b/tools/ci.sh index 7353a3ecdfb7f..175d7f57622fb 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -258,10 +258,8 @@ function ci_qemu_arm_build { make ${MAKEOPTS} -C ports/qemu-arm submodules make ${MAKEOPTS} -C ports/qemu-arm CFLAGS_EXTRA=-DMP_ENDIANNESS_BIG=1 make ${MAKEOPTS} -C ports/qemu-arm clean - make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test submodules - make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test test - make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test clean - make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test BOARD=sabrelite test + make ${MAKEOPTS} -C ports/qemu-arm test + make ${MAKEOPTS} -C ports/qemu-arm BOARD=sabrelite test } ########################################################################################