From ecd4b759d2f5c697470d343076acd75f7c3ebc75 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 31 Oct 2023 21:55:07 +0100 Subject: [PATCH] tests/run-tests, ports/qemu-riscv: Add QEMU RV32 port. 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. Signed-off-by: Alessandro Gatti --- ports/qemu-riscv/Makefile | 140 ++++++++++++++ ports/qemu-riscv/Makefile.test | 31 +++ ports/qemu-riscv/README.md | 73 ++++++++ ports/qemu-riscv/entrypoint.s | 70 +++++++ ports/qemu-riscv/interrupts.c | 292 +++++++++++++++++++++++++++++ ports/qemu-riscv/main.c | 51 +++++ ports/qemu-riscv/modmachine.c | 51 +++++ ports/qemu-riscv/mpconfigport.h | 69 +++++++ ports/qemu-riscv/mphalport.h | 5 + ports/qemu-riscv/qstrdefsport.h | 2 + ports/qemu-riscv/setjmp.s | 75 ++++++++ ports/qemu-riscv/startup.c | 95 ++++++++++ ports/qemu-riscv/test_main.c | 44 +++++ ports/qemu-riscv/tests_profile.txt | 52 +++++ ports/qemu-riscv/uart.c | 45 +++++ ports/qemu-riscv/uart.h | 2 + ports/qemu-riscv/virt.ld | 99 ++++++++++ tests/run-tests.py | 9 + 18 files changed, 1205 insertions(+) create mode 100644 ports/qemu-riscv/Makefile create mode 100644 ports/qemu-riscv/Makefile.test create mode 100644 ports/qemu-riscv/README.md create mode 100644 ports/qemu-riscv/entrypoint.s create mode 100644 ports/qemu-riscv/interrupts.c create mode 100644 ports/qemu-riscv/main.c create mode 100644 ports/qemu-riscv/modmachine.c create mode 100644 ports/qemu-riscv/mpconfigport.h create mode 100644 ports/qemu-riscv/mphalport.h create mode 100644 ports/qemu-riscv/qstrdefsport.h create mode 100644 ports/qemu-riscv/setjmp.s create mode 100644 ports/qemu-riscv/startup.c create mode 100644 ports/qemu-riscv/test_main.c create mode 100644 ports/qemu-riscv/tests_profile.txt create mode 100644 ports/qemu-riscv/uart.c create mode 100644 ports/qemu-riscv/uart.h create mode 100644 ports/qemu-riscv/virt.ld diff --git a/ports/qemu-riscv/Makefile b/ports/qemu-riscv/Makefile new file mode 100644 index 0000000000..8e254a0536 --- /dev/null +++ b/ports/qemu-riscv/Makefile @@ -0,0 +1,140 @@ +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 + +BOARD ?= virt + +CROSS_COMPILE ?= riscv64-unknown-elf- + +# The GCC version string starts like this: +# -gcc () .. +GCC_VERSION = $(subst ., , $(word 3, $(shell $(CC) --version))) +GCC_MAJOR = $(word 1, $(GCC_VERSION)) +GCC_MINOR = $(word 2, $(GCC_VERSION)) +GCC_PATCH = $(word 3, $(GCC_VERSION)) + +ifeq ($(BOARD),virt) +ABI=ilp32 +# GCC 10 and lower do not recognise the Zicsr extension in the +# architecture name. "Make" unfortunately does not provide any simple way +# to perform numeric comparisons, so to keep things simple we assume that +# GCC is at least version 10 for the time being. +ifeq ($(GCC_MAJOR),10) +ARCH=rv32imac +else +# Recent GCC versions explicitly require to declare extensions. +ARCH=rv32imac_zicsr +endif +AFLAGS = -mabi=$(ABI) -march=$(ARCH) +CFLAGS += -mabi=$(ABI) -march=$(ARCH) -mcmodel=medany +CFLAGS += -DQEMU_SOC_VIRT +ifeq ($(PICOLIBC),1) +CFLAGS += -DUSE_PICOLIBC -D__TMP_FLT_EVAL_METHOD +endif +LDSCRIPT = virt.ld +LDFLAGS += -T $(LDSCRIPT) -EL +SRC_BOARD_O = shared/runtime/gchelper_generic.o setjmp.o +SRC_BOARD_O += entrypoint.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 + +LDFLAGS += --gc-sections -Map=$(@:.elf=.map) +LIBS = $(shell $(CC) $(CFLAGS) -print-libgcc-file-name) + +SRC_COMMON_C = \ + interrupts.c \ + startup.c \ + uart.c \ + modmachine.c \ + shared/libc/string0.c \ + shared/runtime/sys_stdio_mphal.c \ + +SRC_RUN_C = \ + ../qemu-arm/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 + +# `make debug` will block QEMU until a debugger is connected to port 1234. +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 $< + +$(BUILD)/firmware.elf: $(LDSCRIPT) $(ALL_OBJ_RUN) + $(Q)$(LD) $(LDFLAGS) -o $@ $(ALL_OBJ_RUN) $(LIBS) + $(Q)$(SIZE) $@ + +include $(TOP)/py/mkrules.mk diff --git a/ports/qemu-riscv/Makefile.test b/ports/qemu-riscv/Makefile.test new file mode 100644 index 0000000000..df64a8d7cc --- /dev/null +++ b/ports/qemu-riscv/Makefile.test @@ -0,0 +1,31 @@ +LIB_SRC_C = shared/upytesthelper/upytesthelper.c + +include Makefile + +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" + +# `make debugtest` will block QEMU until a debugger is connected to port 1234. + +debugtest: $(BUILD)/firmware-test.elf + qemu-system-riscv32 -machine $(BOARD) -bios none $(QEMU_EXTRA) -nographic -monitor null -semihosting -serial mon:stdio -S -s -kernel $< diff --git a/ports/qemu-riscv/README.md b/ports/qemu-riscv/README.md new file mode 100644 index 0000000000..a92f8e8e57 --- /dev/null +++ b/ports/qemu-riscv/README.md @@ -0,0 +1,73 @@ +This is an experimental, community-supported port for RISC-V RV32IMC 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). + +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 toolchain that will work with this port +on a recent Linux installation after the necessary +[prerequisites](https://github.com/riscv-collab/riscv-gnu-toolchain#prerequisites) +are installed: + +```bash +# Check out the bootstrap source code for the toolchain. +git clone -b 2023.10.18 https://github.com/riscv-collab/riscv-gnu-toolchain +cd riscv-gnu-toolchain +# Configure the toolchain. +# NOTE 1: The toolchain that will be built will only generate code for one +# single ABI/Architecture combination, which is the one that this +# port supports. If you want a generic RISC-V toolchain, omit the +# `--with-multilib-generator` parameter. +# NOTE 2: Pick a prefix that is writable by the user that is building the +# toolchain, or installation will fail. +./configure \ + --with-multilib-generator="rv32imc_zicsr-ilp32--" \ + --prefix=$TARGETDIR \ + --enable-multilib +# Build the toolchain using all available CPU cores. +make -j `nproc` +# The toolchain is going to be available in $TARGETDIR, just remember to +# add $TARGETDIR/bin to the $PATH environment variable, or it won't be made +# available to the system. +``` + +To build and run image with builtin testsuite: + + make -f Makefile.test test + +However, if your toolchain comes with picolibc rather than newlib (like the +one available on Ubuntu 22.04, where the libc headers are not shipped with +the toolchain), you must pass `PICOLIBC=1` to make and make sure the picolibc +headers are visible to the compiler. The headers location can also be +provided via a compiler argument in the CFLAGS environment variable. So, for +example, if you want to run the test suite on Ubuntu 22.04 where picolibc must +be explicitly installed and the headers are not visible to the compiler, your +command line should look like this: + +```bash +CFLAGS="-I/usr/lib/picolibc/riscv64-unknown-elf/include" \ + make -f Makefile.test test PICOLIBC=1 +``` diff --git a/ports/qemu-riscv/entrypoint.s b/ports/qemu-riscv/entrypoint.s new file mode 100644 index 0000000000..68d0f12613 --- /dev/null +++ b/ports/qemu-riscv/entrypoint.s @@ -0,0 +1,70 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Alessandro Gatti + * + * 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. + */ + + .section .start + .option norvc /* Do not emit compressed instructions. */ + .type start, @function + .global start + +start: + .cfi_startproc + + .option push + .option norelax + la gp, _global_pointer /* Load global pointer register. */ + .option pop + + csrw satp, zero /* Disable supervisor mode. */ + + /* Fill stack with a canary value. */ + + li t0, 0x12345678 /* Load upper canary value. */ + la t1, _sstack /* Load stack area start address. */ + la t2, _estack /* Load stack area end address. */ +1: + sw t0, (t1) /* Write canary. */ + addi t1, t1, 4 /* Next word. */ + bltu t1, t2, 1b /* Loop until stack is filled. */ + + la sp, _estack /* Load stack pointer. */ + + /* Clear BSS area. */ + + la t1, _sbss /* Load BSS area start address. */ + la t2, _ebss /* Load BSS area end address. */ +1: + sw zero, (t1) /* Clear word. */ + addi t1, t1, 4 /* Next word. */ + bltu t1, t2, 1b /* Loop until BSS is cleared. */ + + /* Set program counter. */ + + la t0, _entry_point /* Load program counter address. */ + csrw mepc, t0 /* Store it into machine exception PC. */ + tail _entry_point /* Jump to entry point. */ + + .cfi_endproc + .end diff --git a/ports/qemu-riscv/interrupts.c b/ports/qemu-riscv/interrupts.c new file mode 100644 index 0000000000..8b9b86b7eb --- /dev/null +++ b/ports/qemu-riscv/interrupts.c @@ -0,0 +1,292 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Alessandro Gatti + * + * 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 +#include +#include + +// Vector table + +void mtvec_table(void) __attribute__((naked, section(".text.mtvec"), aligned(256))); + +// Default interrupts + +#define ASSIGN_EMPTY_MACHINE_INTERRUPT(interrupt_name) \ + void interrupt_name(void) __attribute__((alias("mtvec_nop"))) + +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_ssi); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_msi); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_sti); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_mti); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_sei); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_mei); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq0); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq1); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq2); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq3); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq4); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq5); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq6); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq7); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq8); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq9); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq10); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq11); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq12); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq13); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq14); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq15); + +void mtvec_table(void) { + __asm volatile ( + ".org mtvec_table + 0 \n" + "jal zero, mtvec_exception \n" // Exception Handler + ".org mtvec_table + 4 \n" + "jal zero, mtvec_ssi \n" // Supervisor Software Interrupt + ".org mtvec_table + 12 \n" + "jal zero, mtvec_msi \n" // Machine Software Interrupt + ".org mtvec_table + 20 \n" + "jal zero, mtvec_sti \n" // Supervisor Timer Interrupt + ".org mtvec_table + 28 \n" + "jal zero, mtvec_mti \n" // Machine Timer Interrupt + ".org mtvec_table + 36 \n" + "jal zero, mtvec_sei \n" // Supervisor External Interrupt + ".org mtvec_table + 44 \n" + "jal zero, mtvec_mei \n" // Machine External Interrupt + // Not sure how many platform interrupts QEMU handles... + ".org mtvec_table + 48 \n" + "jal mtvec_plat_irq0 \n" // Platform Interrupt #0 + "jal mtvec_plat_irq1 \n" // Platform Interrupt #1 + "jal mtvec_plat_irq2 \n" // Platform Interrupt #2 + "jal mtvec_plat_irq3 \n" // Platform Interrupt #3 + "jal mtvec_plat_irq4 \n" // Platform Interrupt #4 + "jal mtvec_plat_irq5 \n" // Platform Interrupt #5 + "jal mtvec_plat_irq6 \n" // Platform Interrupt #6 + "jal mtvec_plat_irq7 \n" // Platform Interrupt #7 + "jal mtvec_plat_irq8 \n" // Platform Interrupt #8 + "jal mtvec_plat_irq9 \n" // Platform Interrupt #9 + "jal mtvec_plat_irq10 \n" // Platform Interrupt #10 + "jal mtvec_plat_irq11 \n" // Platform Interrupt #11 + "jal mtvec_plat_irq12 \n" // Platform Interrupt #12 + "jal mtvec_plat_irq13 \n" // Platform Interrupt #13 + "jal mtvec_plat_irq14 \n" // Platform Interrupt #14 + "jal mtvec_plat_irq15 \n" // Platform Interrupt #15 + : + : + : "memory" + ); +} + +volatile uint32_t registers_copy[35] = { 0 }; + +const char *exception_causes[] = { + "Reserved", // 0 + "Supervisor software interrupt", // 1 + "Machine software interrupt", // 2 + "Supervisor timer interrupt", // 3 + "Machine timer interrupt", // 4 + "Supervisor external interrupt", // 5 + "Machine external interrupt", // 6 + "Designated for platform use", // 7 + "Instruction address misaligned", // 8 + "Instruction address fault", // 9 + "Illegal instruction", // 10 + "Breakpoint", // 11 + "Load address misaligned", // 12 + "Load address fault", // 13 + "Store/AMO address misaligned", // 14 + "Store/AMO access fault", // 15 + "Environment call from U-mode", // 16 + "Environment call from S-mode", // 17 + "Environment call from M-mode", // 18 + "Instruction page fault", // 19 + "Load page fault", // 20 + "Store/AMO page fault", // 21 + "Designated for custom use" // 22 +}; + +const char *lookup_cause(uint32_t mcause) { + if (mcause & 0x80000000) { + switch (mcause & 0x7FFFFFFF) { + case 1: + return exception_causes[1]; + case 3: + return exception_causes[2]; + case 5: + return exception_causes[3]; + case 7: + return exception_causes[4]; + case 9: + return exception_causes[5]; + case 11: + return exception_causes[6]; + default: + return (mcause >= 16) ? + exception_causes[7] : + exception_causes[0]; + } + } + + switch (mcause) { + case 0: + return exception_causes[8]; + case 1: + return exception_causes[9]; + case 2: + return exception_causes[10]; + case 3: + return exception_causes[11]; + case 4: + return exception_causes[12]; + case 5: + return exception_causes[13]; + case 6: + return exception_causes[14]; + case 7: + return exception_causes[15]; + case 8: + return exception_causes[16]; + case 9: + return exception_causes[17]; + case 11: + return exception_causes[18]; + case 12: + return exception_causes[19]; + case 13: + return exception_causes[20]; + case 15: + return exception_causes[21]; + default: { + if ((mcause >= 24 && mcause <= 31) || + (mcause >= 48 && mcause <= 63)) { + return exception_causes[22]; + } + + return exception_causes[0]; + } + } +} + +#pragma GCC push_options +#pragma GCC optimize ("align-functions=4") + +__attribute__((interrupt("machine"), weak)) void mtvec_nop(void) { +} + +__attribute__((interrupt("machine"), weak)) void mtvec_exception(void) { + __asm volatile ( + "csrrw x31, mscratch, x31 \n" // Save X31 + "la x31, registers_copy \n" // Load target address + "sw x1, 0(x31) \n" // Save X1 + "sw x2, 4(x31) \n" // Save X2 + "sw x3, 8(x31) \n" // Save X3 + "sw x4, 12(x31) \n" // Save X4 + "sw x5, 16(x31) \n" // Save X5 + "sw x6, 20(x31) \n" // Save X6 + "sw x7, 24(x31) \n" // Save X7 + "sw x8, 28(x31) \n" // Save X8 + "sw x9, 32(x31) \n" // Save X9 + "sw x10, 36(x31) \n" // Save X10 + "sw x11, 40(x31) \n" // Save X11 + "sw x12, 44(x31) \n" // Save X12 + "sw x13, 48(x31) \n" // Save X13 + "sw x14, 52(x31) \n" // Save X14 + "sw x15, 56(x31) \n" // Save X15 + "sw x16, 60(x31) \n" // Save X16 + "sw x17, 64(x31) \n" // Save X17 + "sw x18, 68(x31) \n" // Save X18 + "sw x19, 72(x31) \n" // Save X19 + "sw x20, 76(x31) \n" // Save X20 + "sw x21, 80(x31) \n" // Save X21 + "sw x22, 84(x31) \n" // Save X22 + "sw x23, 88(x31) \n" // Save X23 + "sw x24, 92(x31) \n" // Save X24 + "sw x25, 98(x31) \n" // Save X25 + "sw x26, 100(x31) \n" // Save X26 + "sw x27, 104(x31) \n" // Save X27 + "sw x28, 108(x31) \n" // Save X28 + "sw x29, 112(x31) \n" // Save X29 + "sw x30, 116(x31) \n" // Save X30 + "csrr x30, mscratch \n" // Retrieve X31 + "sw x30, 120(x31) \n" // Save X31 + "csrr x30, mepc \n" // Load MEPC + "sw x30, 124(x31) \n" // Save MEPC + "csrr x30, mcause \n" // Load MCAUSE + "sw x30, 128(x31) \n" // Save MCAUSE + "csrr x30, mtval \n" // Load MTVAL + "sw x30, 132(x31) \n" // Save MTVAL + "csrr x30, mstatus \n" // Load MSTATUS + "sw x30, 138(x31) \n" // Save MSTATUS + "lw x30, 116(x31) \n" // Restore X30 + "lw x31, 120(x31) \n" // Restore X31 + : + : + : "memory" + ); + + printf("\nMACHINE EXCEPTION CAUGHT:\n\n"); + + printf(" RA=%08lX SP=%08lX GP=%08lX TP=%08lX T0=%08lX T1=%08lX\n", + registers_copy[0], registers_copy[1], registers_copy[2], + registers_copy[3], registers_copy[4], registers_copy[5]); + printf(" T2=%08lX S0=%08lX S1=%08lX A0=%08lX A1=%08lX A2=%08lX\n", + registers_copy[6], registers_copy[7], registers_copy[8], + registers_copy[9], registers_copy[10], registers_copy[11]); + printf(" A3=%08lX A4=%08lX A5=%08lX A6=%08lX A7=%08lX S2=%08lX\n", + registers_copy[12], registers_copy[13], registers_copy[14], + registers_copy[15], registers_copy[16], registers_copy[17]); + printf(" S3=%08lX S4=%08lX S5=%08lX S6=%08lX S7=%08lX S8=%08lX\n", + registers_copy[18], registers_copy[19], registers_copy[20], + registers_copy[21], registers_copy[22], registers_copy[23]); + printf(" S9=%08lX S10=%08lX S11=%08lX T3=%08lX T4=%08lX T5=%08lX\n", + registers_copy[24], registers_copy[25], registers_copy[26], + registers_copy[27], registers_copy[28], registers_copy[29]); + printf(" T6=%08lX\n\n", registers_copy[30]); + + printf(" MEPC=%08lX MTVAL=%08lX MSTATUS=%08lx MCAUSE=%08lx (%s)\n", + registers_copy[31], registers_copy[33], registers_copy[34], + registers_copy[32], lookup_cause(registers_copy[32])); + + exit(-1); +} + +#pragma GCC pop_options + +void set_interrupt_table(void) { + __asm volatile ( + "csrrci s0, mstatus, 8 \n" // S0 = MSTATUS & ~MIE + "csrw mstatus, s0 \n" // Global machine interrupts are disabled + "csrw mie, zero \n" // Disable machine interrupts + "csrw mip, zero \n" // Clear pending machine interrupts + "addi s0, %0, 1 \n" // Vectored machine interrupts enabled + "csrw mtvec, s0 \n" // Set new machine vector table + "csrrsi s0, mstatus, 8 \n" // S0 = MSTATUS | MIE + "csrw mstatus, s0 \n" // Global machine interrupts are enabled + : + : "r" (mtvec_table) + : "memory" + ); +} diff --git a/ports/qemu-riscv/main.c b/ports/qemu-riscv/main.c new file mode 100644 index 0000000000..f57c03fb9e --- /dev/null +++ b/ports/qemu-riscv/main.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include + +#include "py/obj.h" +#include "py/compile.h" +#include "py/runtime.h" +#include "py/stackctrl.h" +#include "py/gc.h" +#include "py/repl.h" +#include "py/mperrno.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 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; +} + +void gc_collect(void) { +} + +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"); + exit(1); +} diff --git a/ports/qemu-riscv/modmachine.c b/ports/qemu-riscv/modmachine.c new file mode 100644 index 0000000000..4323557b6c --- /dev/null +++ b/ports/qemu-riscv/modmachine.c @@ -0,0 +1,51 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 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 "extmod/modmachine.h" + +#if MICROPY_PY_MACHINE + +static const mp_rom_map_elem_t machine_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_machine) }, + + { MP_ROM_QSTR(MP_QSTR_mem8), MP_ROM_PTR(&machine_mem8_obj) }, + { MP_ROM_QSTR(MP_QSTR_mem16), MP_ROM_PTR(&machine_mem16_obj) }, + { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, + + { MP_ROM_QSTR(MP_QSTR_PinBase), MP_ROM_PTR(&machine_pinbase_type) }, + { MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, +}; + +static MP_DEFINE_CONST_DICT(machine_module_globals, machine_module_globals_table); + +const mp_obj_module_t mp_module_machine = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&machine_module_globals, +}; + +MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_machine, mp_module_machine); + +#endif // MICROPY_PY_MACHINE diff --git a/ports/qemu-riscv/mpconfigport.h b/ports/qemu-riscv/mpconfigport.h new file mode 100644 index 0000000000..7a49687aad --- /dev/null +++ b/ports/qemu-riscv/mpconfigport.h @@ -0,0 +1,69 @@ +#include + +// options to control how MicroPython is built + +#define MICROPY_ALLOC_PATH_MAX (512) +#define MICROPY_MALLOC_USES_ALLOCATED_SIZE (1) +#define MICROPY_MEM_STATS (1) +#define MICROPY_DEBUG_PRINTERS (0) +#define MICROPY_ENABLE_GC (1) +#define MICROPY_STACK_CHECK (1) +#define MICROPY_HELPER_REPL (0) +#define MICROPY_HELPER_LEXER_UNIX (0) +#define MICROPY_ENABLE_SOURCE_LINE (1) +#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) +#define MICROPY_CAN_OVERRIDE_BUILTINS (1) +#define MICROPY_WARNINGS (1) +#define MICROPY_PY_ALL_SPECIAL_METHODS (1) +#define MICROPY_PY_REVERSE_SPECIAL_METHODS (1) +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) +#define MICROPY_PY_BUILTINS_BYTES_HEX (1) +#define MICROPY_PY_BUILTINS_FROZENSET (1) +#define MICROPY_PY_BUILTINS_MEMORYVIEW (1) +#define MICROPY_PY_BUILTINS_POW3 (1) +#define MICROPY_PY_IO (1) +#define MICROPY_PY_SYS_EXIT (1) +#define MICROPY_PY_SYS_MAXSIZE (1) +#define MICROPY_PY_SYS_PLATFORM "qemu-riscv32" +#define MICROPY_PY_ERRNO (1) +#define MICROPY_PY_BINASCII (1) +#define MICROPY_PY_RANDOM (1) +#define MICROPY_PY_UCTYPES (1) +#define MICROPY_PY_DEFLATE (1) +#define MICROPY_PY_JSON (1) +#define MICROPY_PY_OS (1) +#define MICROPY_PY_RE (1) +#define MICROPY_PY_HEAPQ (1) +#define MICROPY_PY_HASHLIB (1) +#define MICROPY_PY_MACHINE (1) +#define MICROPY_PY_MICROPYTHON_MEM_INFO (1) +#define MICROPY_USE_INTERNAL_PRINTF (1) +#define MICROPY_VFS (1) +#define MICROPY_GCREGS_SETJMP (1) +#define MICROPY_NLR_SETJMP (1) + +// type definitions for the specific machine + +#define MP_SSIZE_MAX (0x7fffffff) + +#define UINT_FMT "%lu" +#define INT_FMT "%ld" + +typedef int32_t mp_int_t; // must be pointer size +typedef uint32_t mp_uint_t; // must be pointer size +typedef long mp_off_t; + +// We need to provide a declaration/definition of alloca() +#if (USE_PICOLIBC) +#include +void *alloca(size_t size); +#else +#include +#endif + +#ifdef TEST +#include "shared/upytesthelper/upytesthelper.h" +#undef MP_PLAT_PRINT_STRN +#define MP_PLAT_PRINT_STRN(str, len) upytest_output(str, len) +#endif diff --git a/ports/qemu-riscv/mphalport.h b/ports/qemu-riscv/mphalport.h new file mode 100644 index 0000000000..29f080805f --- /dev/null +++ b/ports/qemu-riscv/mphalport.h @@ -0,0 +1,5 @@ +#include +#include "uart.h" + +#define mp_hal_stdin_rx_chr() (0) +#define mp_hal_stdout_tx_strn_cooked(s, l) uart_tx_strn((s), (l)) diff --git a/ports/qemu-riscv/qstrdefsport.h b/ports/qemu-riscv/qstrdefsport.h new file mode 100644 index 0000000000..00d3e2ae3c --- /dev/null +++ b/ports/qemu-riscv/qstrdefsport.h @@ -0,0 +1,2 @@ +// qstrs specific to this port +// *FORMAT-OFF* diff --git a/ports/qemu-riscv/setjmp.s b/ports/qemu-riscv/setjmp.s new file mode 100644 index 0000000000..55969318d3 --- /dev/null +++ b/ports/qemu-riscv/setjmp.s @@ -0,0 +1,75 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Alessandro Gatti + * + * 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. + */ + +/* Provide a base setjmp/longjmp implementation to not link libc in. */ + + .globl setjmp + .type setjmp, @function + +setjmp: + sw ra, 0(a0) + sw s0, 4(a0) + sw s1, 8(a0) + sw s2, 12(a0) + sw s3, 16(a0) + sw s4, 20(a0) + sw s5, 24(a0) + sw s6, 28(a0) + sw s7, 32(a0) + sw s8, 36(a0) + sw s9, 40(a0) + sw s10, 44(a0) + sw s11, 48(a0) + sw sp, 52(a0) + addi a0, zero, 0 + jalr zero, ra, 0 + + .size setjmp, .-setjmp + + .globl longjmp + .type longjmp, @function + +longjmp: + lw ra, 0(a0) + lw s0, 4(a0) + lw s1, 8(a0) + lw s2, 12(a0) + lw s3, 16(a0) + lw s4, 20(a0) + lw s5, 24(a0) + lw s6, 28(a0) + lw s7, 32(a0) + lw s8, 36(a0) + lw s9, 40(a0) + lw s10, 44(a0) + lw s11, 48(a0) + lw sp, 52(a0) + sltiu a0, a1, 1 + add a0, a0, a1 + jalr zero, ra, 0 + + .size longjmp, .-longjmp + diff --git a/ports/qemu-riscv/startup.c b/ports/qemu-riscv/startup.c new file mode 100644 index 0000000000..4e54c71a01 --- /dev/null +++ b/ports/qemu-riscv/startup.c @@ -0,0 +1,95 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Alessandro Gatti + * + * 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 +#include +#include + +#include "uart.h" + +extern void set_interrupt_table(void); +extern int main(int argc, char **argv); + +void _entry_point(void) { + // Set interrupt table + set_interrupt_table(); + // Enable UART + uart_init(); + // Now that we have a basic system up and running we can call main + main(0, 0); + // Finished + exit(0); +} + +void exit(int status) { + uint32_t semihosting_arguments[2] = { 0 }; + + // Exit via QEMU's RISC-V semihosting support. + __asm volatile ( + ".option push \n" // Transient options + ".option norvc \n" // Do not emit compressed instructions + ".align 4 \n" // 16 bytes alignment + "mv a1, %0 \n" // Load buffer + "li t0, 0x20026 \n" // ADP_Stopped_ApplicationExit + "sw t0, 0(a1) \n" // ADP_Stopped_ApplicationExit + "sw %1, 4(a1) \n" // Exit code + "addi a0, zero, 0x20 \n" // TARGET_SYS_EXIT_EXTENDED + "slli zero, zero, 0x1F \n" // Entry NOP + "ebreak \n" // Give control to the debugger + "srai zero, zero, 7 \n" // Semihosting call + ".option pop \n" // Restore previous options set + : + : "r" (semihosting_arguments), "r" (status) + : "memory" + ); + + // Should never reach here. + for (;;) { + } +} + +#ifndef NDEBUG +void __assert_func(const char *file, int line, const char *func, const char *expr) { + (void)func; + printf("Assertion '%s' failed, at file %s:%d\n", expr, file, line); + exit(1); +} +#endif + +// The following are needed for tinytest + +#include + +int setvbuf(FILE *stream, char *buf, int mode, size_t size) { + return 0; +} + +#if (USE_PICOLIBC) +FILE *const stdout; +#else +struct _reent _port_impure_data; +struct _reent *_impure_ptr = &_port_impure_data; +#endif diff --git a/ports/qemu-riscv/test_main.c b/ports/qemu-riscv/test_main.c new file mode 100644 index 0000000000..525200303b --- /dev/null +++ b/ports/qemu-riscv/test_main.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#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 "lib/tinytest/tinytest.h" +#include "lib/tinytest/tinytest_macros.h" + +#define HEAP_SIZE (200 * 1024) + +#include "genhdr/tests.h" + +int main() { + 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; +} + +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"); + exit(1); +} diff --git a/ports/qemu-riscv/tests_profile.txt b/ports/qemu-riscv/tests_profile.txt new file mode 100644 index 0000000000..1cfac949c6 --- /dev/null +++ b/ports/qemu-riscv/tests_profile.txt @@ -0,0 +1,52 @@ +# Port-specific tests exclusion list. + +exclude_tests = exclude_tests.union( + ( + # Native code generation is not yet supported. + "micropython/native_closure.py", + "micropython/native_const.py", + "micropython/native_const_intbig.py", + "micropython/native_for.py", + "micropython/native_fun_attrs.py", + "micropython/native_gen.py", + "micropython/native_misc.py", + "micropython/native_try.py", + "micropython/native_try_deep.py", + "micropython/native_while.py", + "micropython/native_with.py", + + # Viper code generator is not yet supported. + "micropython/viper_addr.py", + "micropython/viper_args.py", + "micropython/viper_binop_arith.py", + "micropython/viper_binop_arith_uint.py", + "micropython/viper_binop_bitwise_uint.py", + "micropython/viper_binop_comp.py", + "micropython/viper_binop_comp_imm.py", + "micropython/viper_binop_comp_uint.py", + "micropython/viper_binop_divmod.py", + "micropython/viper_binop_multi_comp.py", + "micropython/viper_cond.py", + "micropython/viper_const.py", + "micropython/viper_const_intbig.py", + "micropython/viper_error.py", + "micropython/viper_globals.py", + "micropython/viper_import.py", + "micropython/viper_misc.py", + "micropython/viper_misc2.py", + "micropython/viper_misc3.py", + "micropython/viper_misc_intbig.py", + "micropython/viper_ptr16_load.py", + "micropython/viper_ptr16_store.py", + "micropython/viper_ptr32_load.py", + "micropython/viper_ptr32_store.py", + "micropython/viper_ptr8_load.py", + "micropython/viper_ptr8_store.py", + "micropython/viper_storeattr.py", + "micropython/viper_subscr.py", + "micropython/viper_subscr_multi.py", + "micropython/viper_try.py", + "micropython/viper_types.py", + "micropython/viper_with.py", + ) +) diff --git a/ports/qemu-riscv/uart.c b/ports/qemu-riscv/uart.c new file mode 100644 index 0000000000..a266d6e6fd --- /dev/null +++ b/ports/qemu-riscv/uart.c @@ -0,0 +1,45 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Alessandro Gatti + * + * 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 +#include + +#include "uart.h" + +#if defined(QEMU_SOC_VIRT) + +volatile unsigned char *uart_buffer = (volatile unsigned char *)0x10000000; + +void uart_init(void) { +} + +void uart_tx_strn(const char *buffer, size_t length) { + for (size_t index = 0; index < length; index++) { + *uart_buffer = buffer[index]; + } +} + +#endif // QEMU_SOC_VIRT diff --git a/ports/qemu-riscv/uart.h b/ports/qemu-riscv/uart.h new file mode 100644 index 0000000000..a89e3f26e2 --- /dev/null +++ b/ports/qemu-riscv/uart.h @@ -0,0 +1,2 @@ +void uart_init(void); +void uart_tx_strn(const char *buf, size_t len); diff --git a/ports/qemu-riscv/virt.ld b/ports/qemu-riscv/virt.ld new file mode 100644 index 0000000000..062ca4e067 --- /dev/null +++ b/ports/qemu-riscv/virt.ld @@ -0,0 +1,99 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Alessandro Gatti + * + * 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. + */ + +/* Output format is 32 bits little endian. */ +OUTPUT_FORMAT("elf32-littleriscv", "elf32-littleriscv", "elf32-littleriscv"); + +/* + * Memory layout: + * + * 0x8000_0000: .text + * .........: .rodata + * 0x8040_0000: .data + * .........: _global_pointer + * .........: .bss + * 0x8060_0000: .stack + * 0x8060_0000: _sstack + * 0x8060_FFFF: _estack + */ +MEMORY +{ + ROM (xr) : ORIGIN = 0x80000000, LENGTH = 4M + RAM (xrw) : ORIGIN = ORIGIN(ROM) + LENGTH(ROM), LENGTH = 2M + STACK (rw) : ORIGIN = ORIGIN(RAM) + LENGTH(RAM), LENGTH = 64K +} + +SECTIONS +{ + /* Code + Read-Only data segment */ + + .text : ALIGN (4K) + { + *(.start) + *(.text) + . = ALIGN (4K); + *(.rodata) + _sirodata = .; + } > ROM + + .rodata : AT (_sirodata) ALIGN (4K) + { + *(.rodata) + } > ROM + + /* Data + BSS segment */ + + .data : ALIGN (4K) + { + *(.data) + _sibss = .; + } > RAM + + .bss : AT (_sibss) ALIGN (4K) + { + /* Mark global pointer address. */ + _global_pointer = .; + + /* Mark BSS start. */ + . = . + 4; + _sbss = .; + *(.bss) + /* Mark BSS end. */ + _ebss = .; + } > RAM + + /* Isolated stack segment. */ + + .stack : ALIGN(4K) + { + /* Mark stack start. */ + _sstack = .; + . = LENGTH(STACK); + /* Mark stack end. */ + _estack = .; + } > STACK +} + diff --git a/tests/run-tests.py b/tests/run-tests.py index 4f55cdd398..1914329cca 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -692,6 +692,8 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("micropython/extreme_exc.py") skip_tests.add("micropython/heapalloc_exc_compressed_emg_exc.py") skip_tests.add("micropython/import_mpy_invalid.py") + elif args.target == "qemu-riscv": + skip_tests.add("misc/print_exception.py") # requires sys stdfiles # Some tests are known to fail on 64-bit machines if pyb is None and platform.architecture()[0] == "64bit": @@ -1039,6 +1041,7 @@ the last matching regex is used: "unix", "qemu-arm", "webassembly", + "qemu-riscv", ) EXTERNAL_TARGETS = ( "pyboard", @@ -1143,6 +1146,12 @@ the last matching regex is used: ) elif args.target == "webassembly": test_dirs += ("float", "ports/webassembly") + elif args.target == "qemu-riscv": + if not args.write_exp: + raise ValueError("--target=qemu-riscv 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",) else: # run tests from these directories test_dirs = args.test_dirs