URL
https://opencores.org/ocsvn/neorv32/neorv32/trunk
Subversion Repositories neorv32
[/] [neorv32/] [trunk/] [docs/] [datasheet/] [software.adoc] - Rev 74
Compare with Previous | Blame | View Log
:sectnums:
== Software Framework
To make actual use of the NEORV32 processor, the project comes with a complete software ecosystem. This
ecosystem is based on the RISC-V port of the GCC GNU Compiler Collection and consists of the following elementary parts:
* <<_compiler_toolchain>>
* <<_core_libraries>>
* <<_application_makefile>>
* <<_executable_image_format>>
** <<_linker_script>>
** <<_ram_layout>>
** <<_c_standard_library>>
** <<_start_up_code_crt0>>
* <<_bootloader>>
* <<_neorv32_runtime_environment>>
A summarizing list of the most important elements of the software framework and their according
files and folders is shown below:
[cols="<6,<4"]
[grid="none"]
|=======================
| Application start-up code | `sw/common/crt0.S`
| Application linker script | `sw/common/neorv32.ld`
| Core hardware driver libraries ("HAL") | `sw/lib/include/` & `sw/lib/source/`
| Central application makefile | `sw/common/common.mk`
| Tool for generating NEORV32 executables | `sw/image_gen/`
| Default bootloader | `sw/bootloader/bootloader.c`
| Example programs | `sw/example`
|=======================
.Software Documentation
[TIP]
All core libraries and example programs are highly documented using **Doxygen**.
See section <<Building the Software Framework Documentation>>.
The documentation is automatically built and deployed to GitHub pages and is available online
at https://stnolting.github.io/neorv32/sw/files.html .
// ####################################################################################################################
:sectnums:
=== Compiler Toolchain
The toolchain for this project is based on the free RISC-V GCC-port. You can find the compiler sources and
build instructions on the official RISC-V GNU toolchain GitHub page: https://github.com/riscv/riscv-gnutoolchain.
The NEORV32 implements a 32-bit RISC-V architecture and uses a 32-bit integer and soft-float ABI by default.
Make sure the toolchain / toolchain build is configured accordingly.
* MARCH = `rv32i`
* MABI = `ilp32`
Alternatively, you can download my prebuilt `rv32i/e` toolchains for 64-bit x86 Linux from: https://github.com/stnolting/riscv-gcc-prebuilt
The default toolchain prefix used by the project's makefiles is **`riscv32-unknown-elf`**, which can be changes
using makefile flags at any time.
[TIP]
More information regarding the toolchain (building from scratch or downloading the prebuilt ones)
can be found in the user guides' section https://stnolting.github.io/neorv32/ug/#_software_toolchain_setup[Software Toolchain Setup].
<<<
// ####################################################################################################################
:sectnums:
=== Core Libraries
The NEORV32 project provides a set of C libraries that allows an easy usage of the processor/CPU features
(also called "HAL" - hardware abstraction layer). All driver and runtime-related files are located in
`sw/lib`. These are automatically included and linked by adding the following _include statement_:
[source,c]
----
#include <neorv32.h> // add NEORV32 HAL and runtime libraries
----
[cols="<3,<4,<8"]
[options="header",grid="rows"]
|=======================
| C source file | C header file | Description
| - | `neorv32.h` | main NEORV32 definitions and library file
| `neorv32_cfs.c` | `neorv32_cfs.h` | HW driver (stubs) functions for the custom functions subsystem
footnote:[This driver file only represents a stub, since the real CFS drivers are defined by the actual CFS implementation.]
| `neorv32_cpu.c` | `neorv32_cpu.h` | HW driver functions for the NEORV32 **CPU**
| `neorv32_cpu_cfu.c` | `neorv32_cpu_cfu.h` | HW driver functions for the NEORV32 **CFU** (custom instructions)
| `neorv32_gpio.c` | `neorv32_gpio.h` | HW driver functions for the **GPIO**
| `neorv32_gptmr.c` | `neorv32_gptmr.h` | HW driver functions for the **GPTRM**
| - | `neorv32_intrinsics.h` | macros for custom intrinsics & instructions
| `neorv32_mtime.c` | `neorv32_mtime.h` | HW driver functions for the **MTIME**
| `neorv32_neoled.c` | `neorv32_neoled.h` | HW driver functions for the **NEOLED**
| `neorv32_pwm.c` | `neorv32_pwm.h` | HW driver functions for the **PWM**
| `neorv32_rte.c` | `neorv32_rte.h` | NEORV32 **runtime environment** and helper functions
| `neorv32_slink.c` | `neorv32_slink.h` | HW driver functions for the **SLINK**
| `neorv32_spi.c` | `neorv32_spi.h` | HW driver functions for the **SPI**
| `neorv32_trng.c` | `neorv32_trng.h` | HW driver functions for the **TRNG**
| `neorv32_twi.c` | `neorv32_twi.h` | HW driver functions for the **TWI**
| `neorv32_uart.c` | `neorv32_uart.h` | HW driver functions for the **UART0** and **UART1**
| `neorv32_wdt.c` | `neorv32_wdt.h` | HW driver functions for the **WDT**
| `neorv32_xip.c` | `neorv32_xip.h` | HW driver functions for the **XIP**
| `neorv32_xirq.c` | `neorv32_xirq.h` | HW driver functions for the **XIRQ**
| `syscalls.c` | - | newlib system calls
|=======================
[TIP]
A CMSIS-SVD-compatible **System View Description (SVD)** file including all peripherals is available in `sw/svd`.
`sw/lib/include`. Currently, the following library files are available:
<<<
// ####################################################################################################################
:sectnums:
=== Application Makefile
Application compilation is based on a single, centralized **GNU makefiles** `sw/common/common.mk`. Each project in the
`sw/example` folder features a makefile that just includes this central makefile. When creating a new project copy an
existing project folder or at least the makefile to the new project folder. It is suggested to create new projects also
in `sw/example` to keep the file dependencies. However, these dependencies can be manually configured via makefiles
variables when the new project is located somewhere else.
[NOTE]
Before the makefile can be used to compile applications, the RISC-V GCC toolchain needs to be installed. Furthermore,
the `bin` folder of the compiler needs to be added to the system's `PATH` variable. More information can be found in
https://stnolting.github.io/neorv32/ug/#_software_toolchain_setup[User Guide: Software Toolchain Setup].
The makefile is invoked by simply executing `make` in the console. For example:
[source,bash]
----
neorv32/sw/example/blink_led$ make
----
:sectnums:
==== Targets
Just executing `make` (or executing `make help`) will show the help menu listing all available targets.
[source,makefile]
----
$ make
<<< NEORV32 SW Application Makefile >>>
Make sure to add the bin folder of RISC-V GCC to your PATH variable.
== Targets ==
help - show this text
check - check toolchain
info - show makefile/toolchain configuration
exe - compile and generate <neorv32_exe.bin> executable for upload via bootloader
hex - compile and generate <neorv32_exe.hex> executable raw file
image - compile and generate VHDL IMEM boot image (for application) in local folder
install - compile, generate and install VHDL IMEM boot image (for application)
sim - in-console simulation using default/simple testbench and GHDL
all - exe + hex + install
elf_info - show ELF layout info
clean - clean up project
clean_all - clean up project, core libraries and image generator
bl_image - compile and generate VHDL BOOTROM boot image (for bootloader only!) in local folder
bootloader - compile, generate and install VHDL BOOTROM boot image (for bootloader only!)
== Variables ==
USER_FLAGS - Custom toolchain flags [append only], default ""
EFFORT - Optimization level, default "-Os"
MARCH - Machine architecture, default "rv32i"
MABI - Machine binary interface, default "ilp32"
APP_INC - C include folder(s) [append only], default "-I ."
ASM_INC - ASM include folder(s) [append only], default "-I ."
RISCV_PREFIX - Toolchain prefix, default "riscv32-unknown-elf-"
NEORV32_HOME - NEORV32 home folder, default "../../.."
----
:sectnums:
==== Configuration
The compilation flow is configured via variables right at the beginning of the central
makefile (`sw/common/common.mk`):
[TIP]
The makefile configuration variables can be overridden or extended directly when invoking the makefile. For
example `$ make MARCH=rv32ic clean_all exe` overrides the default `MARCH` variable definitions.
Permanent modifications/definitions can be made in the project-local makefile
(e.g., `sw/example/blink_led/makefile`).
.Default Makefile Configuration
[source,makefile]
----
# *****************************************************************************
# USER CONFIGURATION
# *****************************************************************************
# User's application sources (*.c, *.cpp, *.s, *.S); add additional files here
APP_SRC ?= $(wildcard ./*.c) $(wildcard ./*.s) $(wildcard ./*.cpp) $(wildcard ./*.S)
# User's application include folders (don't forget the '-I' before each entry)
APP_INC ?= -I .
# User's application include folders - for assembly files only (don't forget the '-I' before each
entry)
ASM_INC ?= -I .
# Optimization
EFFORT ?= -Os
# Compiler toolchain
RISCV_PREFIX ?= riscv32-unknown-elf-
# CPU architecture and ABI
MARCH ?= rv32i
MABI ?= ilp32
# User flags for additional configuration (will be added to compiler flags)
USER_FLAGS ?=
# Relative or absolute path to the NEORV32 home folder
NEORV32_HOME ?= ../../..
# *****************************************************************************
----
.Variables Description
[cols="<3,<10"]
[grid="none"]
|=======================
| `APP_SRC` | The source files of the application (`*.c`, `*.cpp`, `*.S` and `*.s` files are allowed;
files of these types in the project folder are automatically added via wild cards). Additional files can be added separated by white spaces
| `APP_INC` | Include file folders; separated by white spaces; must be defined with `-I` prefix
| `ASM_INC` | Include file folders that are used only for the assembly source files (`*.S`/`*.s`).
| `EFFORT` | Optimization level, optimize for size (`-Os`) is default; legal values: `-O0`, `-O1`, `-O2`, `-O3`, `-Os`, `-Ofast`, ...
| `RISCV_PREFIX` | The toolchain prefix to be used; follows the triplet naming convention `[architecture]-[host_system]-[output]-...`
| `MARCH` | The targeted RISC-V architecture/ISA; enable compiler support of optional CPU extension by adding the according extension
name (e.g. `rv32im` for `M` CPU extension; see https://stnolting.github.io/neorv32/ug/#_enabling_risc_v_cpu_extensions[User Guide: Enabling RISC-V CPU Extensions]
for more information
| `MABI` | Application binary interface (default: 32-bit integer ABI `ilp32`)
| `USER_FLAGS` | Additional flags that will be forwarded to the compiler tools
| `NEORV32_HOME` | Relative or absolute path to the NEORV32 project home folder; adapt this if the makefile/project is not in the project's
default `sw/example` folder
|=======================
:sectnums:
==== Default Compiler Flags
The following default compiler flags are used for compiling an application. These flags are defined via the
`CC_OPTS` variable. Custom flags can be _appended_ to it using the `USER_FLAGS` variable.
[cols="<3,<9"]
[grid="none"]
|=======================
| `-Wall` | Enable all compiler warnings.
| `-ffunction-sections` | Put functions and data segment in independent sections. This allows a code optimization as dead code and unused data can be easily removed.
| `-nostartfiles` | Do not use the default start code. Instead, use the NEORV32-specific start-up code (`sw/common/crt0.S`).
| `-Wl,--gc-sections` | Make the linker perform dead code elimination.
| `-lm` | Include/link with `math.h`.
| `-lc` | Search for the standard C library when linking.
| `-lgcc` | Make sure we have no unresolved references to internal GCC library subroutines.
| `-mno-fdiv` | Use built-in software functions for floating-point divisions and square roots (since the according instructions are not supported yet).
| `-falign-functions=4` .4+| Force a 32-bit alignment of functions and labels (branch/jump/call targets). This increases performance as it simplifies instruction fetch when using the C extension. As a drawback this will also slightly increase the program code.
| `-falign-labels=4`
| `-falign-loops=4`
| `-falign-jumps=4`
|=======================
<<<
// ####################################################################################################################
:sectnums:
=== Executable Image Format
In order to generate a file, which can be executed by the processor, all source files have to be compiler, linked
and packed into a final _executable_.
:sectnums:
==== Linker Script
When all the application sources have been compiled, they need to be _linked_ in order to generate a unified
program file. For this purpose the makefile uses the NEORV32-specific linker script `sw/common/neorv32.ld` for
linking all object files that were generated during compilation.
The linker script defines three memory _sections_: `rom`, `ram` and `iodev`. Each section provides specific
access _attributes_: read access (`r`), write access (`w`) and executable (`x`).
.Linker memory sections - general
[cols="<2,^1,<7"]
[options="header",grid="rows"]
|=======================
| Memory section | Attributes | Description
| `ram` | `rwx` | Data memory address space (processor-internal/external DMEM)
| `rom` | `rx` | Instruction memory address space (processor-internal/external IMEM) _or_ internal bootloader ROM
| `iodev` | `rw` | Processor-internal memory-mapped IO/peripheral devices address space
|=======================
These sections are defined right at the beginning of the linker script:
.Linker memory sections - cut-out from linker script `neorv32.ld`
[source,c]
----
MEMORY
{
ram (rwx) : ORIGIN = 0x80000000, LENGTH = DEFINED(make_bootloader) ? 512 : 8*1024
rom (rx) : ORIGIN = DEFINED(make_bootloader) ? 0xFFFF0000 : 0x00000000, LENGTH = DEFINED(make_bootloader) ? 32K : 2048M
iodev (rw) : ORIGIN = 0xFFFFFE00, LENGTH = 512
}
----
Each memory section provides a _base address_ `ORIGIN` and a _size_ `LENGTH`. The base address and size of the `iodev` section is
fixed and should not be altered. The base addresses and sizes of the `ram` and `rom` regions correspond to the total available instruction
and data memory address space (see section <<_address_space_layout>>) as defined in `rtl/core/neorv32_package.vhd`.
[IMPORTANT]
`ORIGIN` of the `ram` section has to be always identical to the processor's `dspace_base_c` hardware configuration. +
+
`ORIGIN` of the `rom` section has to be always identical to the processor's `ispace_base_c` hardware configuration.
The sizes of `rom` section is a little bit more complicated. The default linker script configuration assumes a _maximum_ of 2GB _logical_
memory space, which is also the default configuration of the processor's hardware instruction memory address space. This size does not have
to reflect the _actual_ physical size of the instruction memory (internal IMEM and/or processor-external memory). It just provides a maximum
limit. When uploading new executable via the bootloader, the bootloader itself checks if sufficient _physical_ instruction memory is available.
If a new executable is embedded right into the internal-IMEM the synthesis tool will check, if the configured instruction memory size
is sufficient (e.g., via the <<_mem_int_imem_size>> generic).
[IMPORTANT]
The `rom` region uses a conditional assignment (via the `make_bootloader` symbol) for `ORIGIN` and `LENGTH` that is used to place
"normal executable" (i.e. for the IMEM) or "the bootloader image" to their according memories. +
+
The `ram` region also uses a conditional assignment (via the `make_bootloader` symbol) for `LENGTH`. When compiling the bootloader
(`make_bootloader` symbol is set) the generated bootloader will only use the _first_ 512 bytes of the data address space. This is
a fall-back to ensure the bootloader can operate independently of the actual _physical_ data memory size.
The linker maps all the regions from the compiled object files into five final sections: `.text`, `.rodata`, `.data`, `.bss` and `.heap`.
These regions contain everything required for the application to run:
.Linker memory regions
[cols="<1,<9"]
[options="header",grid="rows"]
|=======================
| Region | Description
| `.text` | Executable instructions generated from the start-up code and all application sources.
| `.rodata` | Constants (like strings) from the application; also the initial data for initialized variables.
| `.data` | This section is required for the address generation of fixed (= global) variables only.
| `.bss` | This section is required for the address generation of dynamic memory constructs only.
| `.heap` | This section is required for the address generation of dynamic memory constructs only.
|=======================
The `.text` and `.rodata` sections are mapped to processor's instruction memory space and the `.data`,
`.bss` and `heap` sections are mapped to the processor's data memory space. Finally, the `.text`, `.rodata` and `.data`
sections are extracted and concatenated into a single file `main.bin`.
:sectnums:
==== RAM Layout
The default NEORV32 linker script uses all of the defined RAM (linker script memory section `ram`) to create four areas.
Note that depending on the application some areas might not be existent at all.
.Default RAM Layout
image::ram_layout.png[400]
[start=1]
. **Constant data (`.data`)**: The constant data section is placed right at the beginning of the RAM. For example, this section
contains _explicitly initialized_ global variables. This section is initialized by the executable.
. **Dynamic data (`.bss`)**: The constant data section is followed by the dynamic data section, which contains _uninitialized_ data
like global variables without explicit initialization. This section is cleared by the start-up code `crt0.S`.
. **Heap (`.heap`)**: The heap is used for dynamic memory that is managed by functions like `malloc()` and `free()`. The heap
grows upwards. This section is not initialized at all.
. **Stack**: The stack starts at the very end of the RAM at address `ORIGIN(ram) + LENGTH(ram) - 4`. The stack grows downwards.
There is _no explicit limit_ for the maximum stack size as this is hard to check. However, a physical memory protection rule could
be used to configure a maximum size by adding a "protection area" between stack and heap (a PMP region without any access rights).
The maximum size of the heap is defined by the linker script's `__heap_size` symbol. This symbol can be overridden at any time.
By default, the maximum heap size is 1/4 of the total RAM size.
.Heap-Stack Collisions
[WARNING]
Take care when using dynamic memory to avoid collision of the heap and stack memory areas. There is no compile-time protection
mechanism available as the actual heap and stack size are defined by _runtime_ data. Also beware of fragmentation when
using dynamic memory allocation.
:sectnums:
==== C Standard Library
.Constructors and Deconstructors
[IMPORTANT]
The NEORV32 processor is an embedded system intended for running bare-metal or RTOS applications. To simplify this setup
explicit constructors and deconstructors are not supported by default. However, a minimal "deconstructor-alike" support is
provided by the <<_after_main_handler>>.
The NEORV32 is a processor for _embedded_ applications. Hence, it is not capable of running desktop OSs like Linux
(at least not without emulation). Hence, the software framework relies on a "bare-metal" setup that uses **newlib**
as default C standard library.
.RTOS Support
[NOTE]
The NEORV32 CPU and processor **do support** embedded RTOS like FreeRTOS and Zephyr. See the User guide section
https://stnolting.github.io/neorv32/ug/#_zephyr_rtos_support[Zephyr RTOS Support] and
https://stnolting.github.io/neorv32/ug/#_freertos_support[FreeRTOS Support]
for more information.
Newlib provides stubs for common "system calls" (like file handling and standard input/output) that are used by other
C libraries like `stdio`. These stubs are available in `sw/source/syscalls.c` and were adapted for the NEORV32 processor.
.Standard Console(s)
[NOTE]
<<_primary_universal_asynchronous_receiver_and_transmitter_uart0, UART0>>
is used to implement all the standard input, output and error consoles (`STDIN`, `STDOUT` and `STDERR`).
.Newlib Test/Demo Program
[TIP]
A simple test and demo program, which uses some of newlib's core functions (like `malloc`/`free` and `read`/`write`)
is available in `sw/example/demo_newlib`
:sectnums:
==== Executable Image Generator
The `main.bin` file is packed by the NEORV32 image generator (`sw/image_gen`) to generate the final executable file.
[NOTE]
The sources of the image generator are automatically compiled when invoking the makefile.
The image generator can generate three types of executables, selected by a flag when calling the generator:
[cols="<1,<9"]
[grid="none"]
|=======================
| `-app_bin` | Generates an executable binary file `neorv32_exe.bin` (for UART uploading via the bootloader).
| `-app_hex` | Generates a plain ASCII hex-char file `neorv32_exe.hex` that can be used to initialize custom (instruction-) memories (in synthesis/simulation).
| `-app_img` | Generates an executable VHDL memory initialization image for the processor-internal IMEM. This option generates the `rtl/core/neorv32_application_image.vhd` file.
| `-bld_img` | Generates an executable VHDL memory initialization image for the processor-internal BOOT ROM. This option generates the `rtl/core/neorv32_bootloader_image.vhd` file.
|=======================
All these options are managed by the makefile. The _normal application_ compilation flow will generate the `neorv32_exe.bin`
executable to be upload via UART to the NEORV32 bootloader.
The image generator add a small header to the `neorv32_exe.bin` executable, which consists of three 32-bit words located right at the
beginning of the file. The first word of the executable is the signature word and is always `0x4788cafe`. Based on this word the bootloader
can identify a valid image file. The next word represents the size in bytes of the actual program
image in bytes. A simple "complement" checksum of the actual program image is given by the third word. This
provides a simple protection against data transmission or storage errors.
:sectnums:
==== Start-Up Code (crt0)
The CPU and also the processor require a minimal start-up and initialization code to bring the CPU (and the SoC)
into a stable and initialized state and to initialize the C runtime environment before the actual application can be executed.
This start-up code is located in `sw/common/crt0.S` and is automatically linked _every_ application program
and placed right before the actual application code so it gets executed right after reset.
The `crt0.S` start-up performs the following operations:
[start=1]
. Disable interrupts globally by clearing <<_mstatus>>`.mie`.
. Initialize all integer registers `x1 - x31` (or just `x1 - x15` when using the `E` CPU extension) to a defined value.
. Initialize all CPU core CSRs and also install a default "dummy" trap handler for _all_ traps. This handler catches all traps
** All interrupt sources are disabled and all pending interrupts are cleared.
. Initialize the global pointer `gp` and the stack pointer `sp` according to the <<_ram_layout>> provided by the linker script.
during the early boot phase.
. Clear all counter CSRs and stop auto-increment.
. Clear IO area: Write zero to all memory-mapped registers within the IO region (`iodev` section). If certain devices have not
been implemented, a bus access fault exception will occur. This exception is captured by the dummy trap handler.
. Clear the `.bss` section defined by the linker script.
. Copy read-only data from the `.text` section to the `.data` section to set initialized variables.
. Call the application's `main` function (with _no_ arguments: `argc` = `argv` = 0).
. If the main function returns...
** the return value is copied to the <<_mscratch>> CSR to allow inspection by the on-chip debugger.
** an optional <<_after_main_handler>> is called (if defined at all).
** the last step the CPU does is entering endless sleep mode (using the `wfi` instruction).
:sectnums:
===== After-Main Handler
If the application's `main()` function actually returns, an _after main handler_ can be executed. This handler is a "normal" function
as the C runtime is still available when executed. If this handler uses any kind of peripheral/IO modules make sure these are
already initialized within the application. Otherwise you have to initialize them _inside_ the handler.
.After-main handler - function prototype
[source,c]
----
void __neorv32_crt0_after_main(int32_t return_code);
----
The function has exactly one argument (`return_code`) that provides the _return value_ of the application's main function.
For instance, this variable contains `-1` if the main function returned with `return -1;`. The after-main handler itself does
not provide a return value.
A simple UART output can be used to inform the user when the application's main function returns
(this example assumes that UART0 has been already properly configured in the actual application):
.After-main handler - simple example
[source,c]
----
void __neorv32_crt0_after_main(int32_t return_code) {
neorv32_uart0_printf("\n<RTE> main function returned with exit code %i. </RTE>\n", return_code); <1>
}
----
<1> Use `<RTE>` here to make clear this is a message comes from the runtime environment.
<<<
// ####################################################################################################################
include::software_bootloader.adoc[]
<<<
// ####################################################################################################################
include::software_rte.adoc[]