diff --git a/sw/example/demo_dual_core_rte/Makefile b/sw/example/demo_dual_core_rte/Makefile new file mode 100644 index 000000000..7715e365b --- /dev/null +++ b/sw/example/demo_dual_core_rte/Makefile @@ -0,0 +1,33 @@ +# Application makefile. +# Use this makefile to configure all relevant CPU / compiler options. + +# Override the default CPU ISA +MARCH = rv32ia_zicsr_zifencei + +# Override the default RISC-V GCC prefix +#RISCV_PREFIX ?= riscv-none-elf- + +# Override default optimization goal +EFFORT = -Os + +# Add extended debug symbols +USER_FLAGS += -ggdb -gdwarf-3 + +# Adjust processor IMEM size +USER_FLAGS += -Wl,--defsym,__neorv32_rom_size=16k + +# Adjust processor DMEM size +USER_FLAGS += -Wl,--defsym,__neorv32_ram_size=8k + +# Adjust maximum heap size +#USER_FLAGS += -Wl,--defsym,__neorv32_heap_size=3k + +# Additional sources +#APP_SRC += $(wildcard ./*.c) +#APP_INC += -I . + +# Set path to NEORV32 root directory +NEORV32_HOME ?= ../../.. + +# Include the main NEORV32 makefile +include $(NEORV32_HOME)/sw/common/common.mk diff --git a/sw/example/demo_dual_core_rte/main.c b/sw/example/demo_dual_core_rte/main.c new file mode 100644 index 000000000..8ffec5419 --- /dev/null +++ b/sw/example/demo_dual_core_rte/main.c @@ -0,0 +1,203 @@ +// ================================================================================ // +// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 // +// Copyright (c) NEORV32 contributors. // +// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. // +// Licensed under the BSD-3-Clause license, see LICENSE for details. // +// SPDX-License-Identifier: BSD-3-Clause // +// ================================================================================ // + +/**********************************************************************//** + * @file demo_dual_core_rte/main.c + * @brief SMP dual-core program to show how to use the RTE on two cores. + * This example runs the same code on both cores and triggers the timer + * and software interrupts to showcase dual-core trap handling using the + * NEORV32 runtime environment (RTE). + **************************************************************************/ +#include +#include "spinlock.h" + +/** User configuration */ +#define BAUD_RATE 19200 + +/** Global variables */ +volatile uint8_t __attribute__ ((aligned (16))) core1_stack[2048]; // stack memory for core1 + + +/**********************************************************************//** + * Machine timer (CLINT) interrupt handler for BOTH cores. + **************************************************************************/ +void trap_handler_mtmi(void) { + + // find out which core is currently executing this + uint32_t core_id = neorv32_smp_whoami(); + + spin_lock(); + neorv32_uart0_printf("[core %u] MTIMER interrupt.\n", core_id); + spin_unlock(); + + // compute next interrupt time + uint64_t next_irq_time = neorv32_clint_time_get(); // current system time from CLINT.MTIME + if (core_id == 0) { + next_irq_time += 1 * neorv32_sysinfo_get_clk(); // 1 second for core 0 + } + else { + next_irq_time += 2 * neorv32_sysinfo_get_clk(); // 2 seconds for core 0 + } + + // this is automatically mapped to the current core's MTIMECMP register + neorv32_clint_mtimecmp_set(next_irq_time); + + // trigger software interrupt of the other core + if (core_id == 0) { + neorv32_clint_msi_set(1); // trigger core 1 + } + else { + neorv32_clint_msi_set(0); // trigger core 0 + } +} + + +/**********************************************************************//** + * Machine software (CLINT) interrupt handler for BOTH cores. + **************************************************************************/ +void trap_handler_mswi(void) { + + // find out which core is currently executing this + uint32_t core_id = neorv32_smp_whoami(); + + spin_lock(); + neorv32_uart0_printf("[core %u] Software interrupt.\n", core_id); + spin_unlock(); + + // clear software interrupt of current core + neorv32_clint_msi_clr(core_id); +} + + +/**********************************************************************//** + * Machine environment call trap handler for BOTH cores. + **************************************************************************/ +void trap_handler_ecall(void) { + + // find out which core is currently executing this + uint32_t core_id = neorv32_smp_whoami(); + + spin_lock(); + neorv32_uart0_printf("[core %u] Environment call.\n", core_id); + spin_unlock(); +} + + +/**********************************************************************//** + * "Application code" executed by BOTH cores. + * + * @return Irrelevant (but can be inspected by the debugger). + **************************************************************************/ +int app_main(void) { + + // (re-)setup NEORV32 runtime-environment (RTE) for the core that is executing this code + neorv32_rte_setup(); + + + // print message; use spinlock to have exclusive access to UART0 + uint32_t core_id = neorv32_smp_whoami(); // find out which core is currently executing this + spin_lock(); + neorv32_uart0_printf("[core %u] Hello world! This is core %u starting 'app_main()'.\n", core_id, core_id); + spin_unlock(); + + + // The NEORV32 Runtime Environment (RTE) provides an internal trap vector table. Each entry + // corresponds to a specific trap (exception or interrupt). Application software can install + // specific trap handler function to take care of each type of trap. + + // However, there is only a single trap vector table. Hence, both cores will execute the SAME + // handler function if they encounter the same trap. + + // setup machine timer interrupt for ALL cores + neorv32_clint_mtimecmp_set(0); // initialize core-specific MTIMECMP + neorv32_rte_handler_install(RTE_TRAP_MTI, trap_handler_mtmi); // install trap handler + neorv32_cpu_csr_set(CSR_MIE, 1 << CSR_MIE_MTIE); // enable interrupt source + + // setup machine software interrupt for ALL cores + neorv32_rte_handler_install(RTE_TRAP_MSI, trap_handler_mswi); // install trap handler + neorv32_cpu_csr_set(CSR_MIE, 1 << CSR_MIE_MSIE); // enable interrupt source + + // setup machine environment call trap for ALL cores + neorv32_rte_handler_install(RTE_TRAP_MENV_CALL, trap_handler_ecall); // install trap handler + + + // trigger environment call exception (just to test the according handler) + asm volatile ("ecall"); + + // enable machine-level interrupts and wait in sleep mode + neorv32_cpu_csr_set(CSR_MSTATUS, 1 << CSR_MSTATUS_MIE); + while (1) { + neorv32_cpu_sleep(); + } + + return 0; +} + + +/**********************************************************************//** + * Main function for core 0 (primary core). + * + * @warning This program requires the dual-core configuration, the CLINT, UART0 + * and the A/Zaamo ISA extension. + * + * @return Irrelevant (but can be inspected by the debugger). + **************************************************************************/ +int main(void) { + + // setup NEORV32 runtime-environment (RTE) for _this_ core (core 0) + // this is not required but keeps us safe + neorv32_rte_setup(); + + + // setup UART0 at default baud rate, no interrupts + if (neorv32_uart0_available() == 0) { // UART0 available? + return -1; + } + neorv32_uart0_setup(BAUD_RATE, 0); + neorv32_uart0_printf("\n<< NEORV32 SMP Dual-Core RTE Demo >>\n\n"); + + + // check hardware/software configuration + if (neorv32_sysinfo_get_numcores() < 2) { // two cores available? + neorv32_uart0_printf("[ERROR] dual-core option not enabled!\n"); + return -1; + } + if (neorv32_clint_available() == 0) { // CLINT available? + neorv32_uart0_printf("[ERROR] CLINT module not available!\n"); + return -1; + } + if ((neorv32_cpu_csr_read(CSR_MXISA) & (1< + +/**********************************************************************//** + * Private spinlock locked variable. + **************************************************************************/ +static volatile uint32_t __spin_locked = 0; + + +/**********************************************************************//** + * Spinlock: set lock. + * + * @warning This function is blocking until the lock is acquired and set. + **************************************************************************/ +void spin_lock(void) { + + while(__sync_lock_test_and_set(&__spin_locked, -1)); // -> amoswap.w +} + + +/**********************************************************************//** + * Spinlock: remove lock. + **************************************************************************/ +void spin_unlock(void) { + + //__sync_lock_release(&__spin_locked); // uses fence that is not required here + __sync_lock_test_and_set(&__spin_locked, 0); // -> amoswap.w +} diff --git a/sw/example/demo_dual_core_rte/spinlock.h b/sw/example/demo_dual_core_rte/spinlock.h new file mode 100644 index 000000000..fb07b1841 --- /dev/null +++ b/sw/example/demo_dual_core_rte/spinlock.h @@ -0,0 +1,12 @@ +/** + * @file spinlock.h + * @brief Single simple spin-lock based on atomic memory operations. + */ + +#ifndef spinlock_h +#define spinlock_h + +void spin_lock(void); +void spin_unlock(void); + +#endif // spinlock_h