neorv32/docs/datasheet/software_rte.adoc

257 lines
13 KiB
Plaintext
Raw Permalink Normal View History

2024-02-24 08:25:27 +00:00
:sectnums:
=== NEORV32 Runtime Environment
The NEORV32 software framework provides a minimal **runtime environment** (abbreviated "RTE") that takes care of a stable
and _safe_ execution environment by handling _all_ traps (exceptions & interrupts). The RTE simplifies trap handling
by wrapping the CPU's privileged architecture (i.e. trap-related CSRs) into a unified software API.
Once initialized, the RTE provides <<_default_rte_trap_handlers>> that catch all possible traps. These
default handlers just output a message via UART to inform the user when a certain trap has been triggered. The
default handlers can be overridden by the application code to install application-specific handler functions for each trap.
[IMPORTANT]
Using the RTE is **optional but highly recommended**. The RTE provides a simple and comfortable way of delegating
traps to application-specific handlers while making sure that all traps (even though they are not explicitly used
by the application) are handled correctly. Performance-optimized applications or embedded operating systems may
not use the RTE at all in order to increase response time.
==== RTE Operation
The RTE manages the trap-related CSRs of the CPU's privileged architecture (<<_machine_trap_handling_csrs>>).
It initializes the <<_mtvec>> CSR in DIRECT mode, which then provides the base entry point for _all_ traps. The address
stored to this register defines the address of the **first-level trap handler**, which is provided by the
NEORV32 RTE. Whenever an exception or interrupt is triggered this first-level trap handler is executed.
The first-level handler performs a complete context save, analyzes the source of the trap and
calls the according **second-level trap handler**, which takes care of the actual exception/interrupt
handling. The RTE manages a private look-up table to store the addresses of the according second-level trap handlers.
After the initial RTE setup, each entry in the RTE's trap handler look-up table is initialized with a
<<_default_rte_trap_handlers>>. These default handler do not execute any trap-related operations - they
just output a message via the *primary UART (UART0)* to inform the user that a trap has occurred, which is not (yet)
handled by the actual application. After sending this message, the RTE tries to continue executing the actual program
by resolving the trap cause.
==== Using the RTE
[IMPORTANT]
All provided RTE functions can be called only from machine-mode code.
The NEORV32 is part of the default NEORV32 software framework. However, it has to explicitly enabled by calling
the RTE's setup function:
.RTE Setup (Function Prototype)
[source,c]
----
void neorv32_rte_setup(void);
----
[NOTE]
The RTE should be enabled right at the beginning of the application's `main` function.
[IMPORTANT]
It is recommended to not use the <<_mscratch>> CSR when using the RTE as this register is used to provide services
for <<_application_context_handling>> (i.e. modifying the registers of application code that caused a trap).
As mentioned above, all traps will just trigger execution of the RTE's <<_default_rte_trap_handlers>> at first.
To use application-specific handlers, which actually "handle" a trap, the default handlers can be overridden
by installing user-defined ones:
.Installing an Application-Specific Trap Handler (Function Prototype)
[source,c]
----
int neorv32_rte_handler_install(uint8_t id, void (*handler)(void));
----
The first argument `id` defines the "trap ID" (for example a certain interrupt request) that shall be handled
by the user-defined handler. These IDs are defined in `sw/lib/include/neorv32_rte.h`:
.RTE Trap Identifiers (cut-out)
[source,c]
----
enum NEORV32_RTE_TRAP_enum {
RTE_TRAP_I_MISALIGNED = 0, /**< Instruction address misaligned */
RTE_TRAP_I_ACCESS = 1, /**< Instruction (bus) access fault */
RTE_TRAP_I_ILLEGAL = 2, /**< Illegal instruction */
RTE_TRAP_BREAKPOINT = 3, /**< Breakpoint (EBREAK instruction) */
RTE_TRAP_L_MISALIGNED = 4, /**< Load address misaligned */
RTE_TRAP_L_ACCESS = 5, /**< Load (bus) access fault */
RTE_TRAP_S_MISALIGNED = 6, /**< Store address misaligned */
RTE_TRAP_S_ACCESS = 7, /**< Store (bus) access fault */
RTE_TRAP_UENV_CALL = 8, /**< Environment call from user mode (ECALL instruction) */
RTE_TRAP_MENV_CALL = 9, /**< Environment call from machine mode (ECALL instruction) */
RTE_TRAP_MSI = 10, /**< Machine software interrupt */
RTE_TRAP_MTI = 11, /**< Machine timer interrupt */
RTE_TRAP_MEI = 12, /**< Machine external interrupt */
RTE_TRAP_FIRQ_0 = 13, /**< Fast interrupt channel 0 */
RTE_TRAP_FIRQ_1 = 14, /**< Fast interrupt channel 1 */
RTE_TRAP_FIRQ_2 = 15, /**< Fast interrupt channel 2 */
RTE_TRAP_FIRQ_3 = 16, /**< Fast interrupt channel 3 */
RTE_TRAP_FIRQ_4 = 17, /**< Fast interrupt channel 4 */
RTE_TRAP_FIRQ_5 = 18, /**< Fast interrupt channel 5 */
RTE_TRAP_FIRQ_6 = 19, /**< Fast interrupt channel 6 */
RTE_TRAP_FIRQ_7 = 20, /**< Fast interrupt channel 7 */
RTE_TRAP_FIRQ_8 = 21, /**< Fast interrupt channel 8 */
RTE_TRAP_FIRQ_9 = 22, /**< Fast interrupt channel 9 */
RTE_TRAP_FIRQ_10 = 23, /**< Fast interrupt channel 10 */
RTE_TRAP_FIRQ_11 = 24, /**< Fast interrupt channel 11 */
RTE_TRAP_FIRQ_12 = 25, /**< Fast interrupt channel 12 */
RTE_TRAP_FIRQ_13 = 26, /**< Fast interrupt channel 13 */
RTE_TRAP_FIRQ_14 = 27, /**< Fast interrupt channel 14 */
RTE_TRAP_FIRQ_15 = 28 /**< Fast interrupt channel 15 */
----
The second argument `*handler` is the actual function that implements the user-defined trap handler.
The custom handler functions need to have a specific format without any arguments and with no return value:
.Custom Trap Handler (Function Prototype)
[source,c]
----
void custom_trap_handler_xyz(void) {
// handle trap...
}
----
.Custom Trap Handler Attributes
[WARNING]
Do **NOT** use the `((interrupt))` attribute for the application trap handler functions! This
will place a `mret` instruction to the end of it making it impossible to return to the first-level
trap handler of the RTE core, which will cause stack corruption.
The following example shows how to install a custom handler (`custom_mtime_irq_handler`) for handling
the RISC-V machine timer (MTIME) interrupt:
.Installing a MTIME IRQ Handler
[source,c]
----
neorv32_rte_handler_install(RTE_TRAP_MTI, custom_mtime_irq_handler);
----
User-defined trap handlers can also be un-installed. This will remove the users trap handler from the RTE core
and will re-install the <<_default_rte_trap_handlers>> for the specific trap.
.Function Prototype: Installing an Application-Specific Trap Handler
[source,c]
----
int neorv32_rte_handler_uninstall(uint8_t id);
----
The argument `id` defines the identifier of the according trap that shall be un-installed.
The following example shows how to un-install the custom handler `custom_mtime_irq_handler` from the
RISC-V machine timer (MTIME) interrupt:
.Example: Removing the Custom MTIME IRQ Handler
[source,c]
----
neorv32_rte_handler_uninstall(RTE_TRAP_MTI);
----
[TIP]
The current RTE configuration can be printed via UART0 via the `neorv32_rte_info` function.
==== Default RTE Trap Handlers
The default RTE trap handlers are executed when a certain trap is triggered that is not (yet) handled by an
application-defined trap handler. The default handler will output a message giving additional debug information
via the <<_primary_universal_asynchronous_receiver_and_transmitter_uart0>> to inform the user and it will also
try to resume normal program execution. Some exemplary RTE outputs are shown below.
.Continuing Execution
[WARNING]
In most cases the RTE can successfully continue operation - for example if it catches an **interrupt** request
that is not handled by the actual application program. However, if the RTE catches an un-handled **trap** like
a bus access fault exception continuing execution will most likely fail making the CPU crash. Some exceptions
cannot be resolved by the default debug trap handlers and will halt the CPU (see example below).
.RTE Default Trap Handler Output Examples
[source]
----
<NEORV32-RTE> [M] Illegal instruction @ PC=0x000002d6, MTINST=0x000000FF, MTVAL=0x00000000 </NEORV32-RTE> <1>
<NEORV32-RTE> [U] Illegal instruction @ PC=0x00000302, MTINST=0x00000000, MTVAL=0x00000000 </NEORV32-RTE> <2>
<NEORV32-RTE> [U] Load address misaligned @ PC=0x00000440, MTINST=0x01052603, MTVAL=0x80000101 </NEORV32-RTE> <3>
<NEORV32-RTE> [M] Fast IRQ 0x00000003 @ PC=0x00000820, MTINST=0x00000000, MTVAL=0x00000000 </NEORV32-RTE> <4>
<NEORV32-RTE> [M] Instruction access fault @ PC=0x90000000, MTINST=0x42078b63, MTVAL=0x00000000 !!FATAL EXCEPTION!! Halting CPU. </NEORV32-RTE>\n <5>
----
<1> Illegal 32-bit instruction `MTINST=0x000000FF` at address `PC=0x000002d6` while the CPU was in machine-mode (`[M]`).
<2> Illegal 16-bit instruction `MTINST=0x00000000` at address `PC=0x00000302` while the CPU was in user-mode (`[U]`).
<3> Misaligned load access at address `PC=0x00000440` caused by instruction `MTINST=0x01052603` (trying to load a full 32-bit word from address `MTVAL=0x80000101`) while the CPU was in machine-mode (`[U]`).
<4> Fast interrupt request from channel 3 before executing instruction at address `PC=0x00000820` while the CPU was in machine-mode (`[M]`).
<5> Instruction bus access fault at address `PC=0x90000000` while executing instruction `MTINST=0x42078b63` - this is fatal for the default debug trap handler while the CPU was in machine-mode (`[M]`).
The specific message right at the beginning of the debug trap handler message corresponds to the trap code
obtained from the <<_mcause>> CSR (see <<_neorv32_trap_listing>>). A full list of all messages and the according
`mcause` trap codes is shown below.
.RTE Default Trap Handler Messages and According `mcause` Values
[cols="<5,^5"]
[options="header",grid="rows"]
|=======================
| Trap identifier | According `mcause` CSR value
| "Instruction address misaligned" | `0x00000000`
| "Instruction access fault" | `0x00000001`
| "Illegal instruction" | `0x00000002`
| "Breakpoint" | `0x00000003`
| "Load address misaligned" | `0x00000004`
| "Load access fault" | `0x00000005`
| "Store address misaligned" | `0x00000006`
| "Store access fault" | `0x00000007`
| "Environment call from U-mode" | `0x00000008`
| "Environment call from M-mode" | `0x0000000b`
| "Machine software IRQ" | `0x80000003`
| "Machine timer IRQ" | `0x80000007`
| "Machine external IRQ" | `0x8000000b`
| "Fast IRQ 0x00000000" | `0x80000010`
| "Fast IRQ 0x00000001" | `0x80000011`
| "Fast IRQ 0x00000002" | `0x80000012`
| "Fast IRQ 0x00000003" | `0x80000013`
| "Fast IRQ 0x00000004" | `0x80000014`
| "Fast IRQ 0x00000005" | `0x80000015`
| "Fast IRQ 0x00000006" | `0x80000016`
| "Fast IRQ 0x00000007" | `0x80000017`
| "Fast IRQ 0x00000008" | `0x80000018`
| "Fast IRQ 0x00000009" | `0x80000019`
| "Fast IRQ 0x0000000a" | `0x8000001a`
| "Fast IRQ 0x0000000b" | `0x8000001b`
| "Fast IRQ 0x0000000c" | `0x8000001c`
| "Fast IRQ 0x0000000d" | `0x8000001d`
| "Fast IRQ 0x0000000e" | `0x8000001e`
| "Fast IRQ 0x0000000f" | `0x8000001f`
| "Unknown trap cause" | undefined
|=======================
==== Application Context Handling
Upon trap entry the RTE backups the _entire_ application context (i.e. all `x` general purpose registers)
to the stack. The context is restored automatically after trap completion. The base address of the according
stack frame is copied to the <<_mscratch>> CSR. By having this information available, the RTE provides dedicated
functions for accessing and _altering_ the application context:
.Context Access Functions
[source,c]
----
// Prototypes
uint32_t neorv32_rte_context_get(int x); // read register x
void neorv32_rte_context_put(int x, uint32_t data); write data to register x
// Examples
uint32_t tmp = neorv32_rte_context_get(9); // read register 'x9'
neorv32_rte_context_put(28, tmp); // write 'tmp' to register 'x28'
----
.RISC-V `E` Extension
[NOTE]
Registers `x16..x31` are not available if the RISC-V <<_e_isa_extension>> is enabled.
The context access functions can be used by application-specific trap handlers to emulate unsupported
CPU / SoC features like unimplemented IO modules, unsupported instructions and even unaligned memory accesses.
.Demo Program: Emulate Unaligned Memory Access
[TIP]
A demo program, which showcases how to emulate unaligned memory accesses using the NEORV32 runtime environment
can be found in `sw/example/demo_emulate_unaligned`.