: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 documented "in-code" using **Doxygen**. The documentation is automatically built and deployed to GitHub pages and is available online at https://stnolting.github.io/neorv32/sw/files.html. .Example Programs [TIP] A collection of annotated example programs, which show how to use certain CPU functions and peripheral/IO modules, can be found in `sw/example`. // #################################################################################################################### :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` * `RISCV_PREFIX=riscv32-unknown-elf-` These default configurations can be override at any times using <<_application_makefile>> variables. [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 pre-defined C libraries that allow an easy integration 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 HAL, core 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 intrinsics & custom 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_onewire.c` | `neorv32_onewire.h` | HW driver functions for the **ONEWIRE** | `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" | - | `legacy.h` | backwards compatibility wrappers and functions (do not use for new designs) |======================= .Core Library Documentation [TIP] The _doxygen_-based documentation of the software framework including all core libraries is available online at https://stnolting.github.io/neorv32/sw/files.html. .CMSIS System View Description File (SVD) [TIP] A CMSIS-SVD-compatible **System View Description (SVD)** file including all peripherals is available in `sw/svd`. <<< // #################################################################################################################### :sectnums: === Application Makefile Application compilation is based on a single, centralized **GNU makefile** (`sw/common/common.mk`). Each project in the `sw/example` folder provides a makefile that just includes this central makefile. [TIP] When creating a new project, copy an existing project folder or at least the makefile to the new project folder. It is recommended to create new projects also in `sw/example` to keep the file dependencies. However, these dependencies can be manually configured via makefile variables if 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 and the compiler's `bin` folder has 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/demo_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 asm - compile and generate assembly listing file for manual debugging elf - compile and generate ELF file bin - compile and generate RAW executable file (binary file, no header) hex - compile and generate RAW executable file (hex char file, no header) image - compile and generate VHDL IMEM boot image (for application, no header) in local folder install - compile, generate and install VHDL IMEM boot image (for application, no header) sim - in-console simulation using default/simple testbench and GHDL all - exe + install + hex + bin + asm elf_info - show ELF layout info clean - clean up project home folder clean_all - clean up whole project, core libraries and image generator bl_image - compile and generate VHDL BOOTROM boot image (for bootloader only, no header) in local folder bootloader - compile, generate and install VHDL BOOTROM boot image (for bootloader only, no header) === 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/demo_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. [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, the NEORV32-specific start-up code (`sw/common/crt0.S`) is used (pulled-in by the linker script). | `-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). | `-g` | Include debugging information/symbols in ELF. |======================= :sectnums: ==== Custom (Compiler) Flags Custom flags can be _appended_ to the `USER_FLAGS` variable. This allows to customize the entire software framework while calling `make` without the need to change the makefile(s) or the linker script. The following example will add debug symbols to the executable (`-g`) and will also define the linker script's `__neorv32_heap_size` setting the maximal heap size to 4096 bytes: .Example: using the `USER_FLAGS` variable for customization [source,bash] ---- $ make USER_FLAGS+="-g -Wl,--__neorv32_heap_size,__heap_size=4096" clean_all exe ---- <<< // #################################################################################################################### :sectnums: === Executable Image Format In order to generate an executable for th processors all source files have to be compiled, linked and packed into a _final executable_. :sectnums: ==== Linker Script After all the application sources have been compiled, they need to be _linked_. 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. In general, the linker script defines three memory sections: `rom`, `ram` and `iodev`. .Linker script - memory sections [cols="<2,<8"] [options="header",grid="rows"] |======================= | Memory section | Description | `ram` | Data memory address space (processor-internal/external DMEM) | `rom` | Instruction memory address space (processor-internal/external IMEM) _or_ internal bootloader ROM | `iodev` | Processor-internal memory-mapped IO/peripheral devices address space |======================= [NOTE] The `iodev` section is entirely defined by the processor hardware layout and should not be modified at all. [NOTE] The `rom` section is automatically re-mapped to the processor-internal <<_bootloader_rom_bootrom>> when (re-)compiling the bootloader Each section has two main attributes: `ORIGIN` and `LENGTH`. `ORIGIN` defines the base address of the according section while `LENGTH` defines its size in bytes. The attributes are configured indirectly via variables that provide default values. .Linker script - section configuration [source] ---- /* Default rom/ram (IMEM/DMEM) sizes */ __neorv32_rom_size = DEFINED(__neorv32_rom_size) ? __neorv32_rom_size : 2048M; __neorv32_ram_size = DEFINED(__neorv32_ram_size) ? __neorv32_ram_size : 8K; /* Default section base addresses - do not change this unless the hardware-defined address space layout is changed! */ __neorv32_rom_base = DEFINED(__neorv32_rom_base) ? __neorv32_rom_base : 0x00000000; /* = VHDL package's "ispace_base_c" */ __neorv32_ram_base = DEFINED(__neorv32_ram_base) ? __neorv32_ram_base : 0x80000000; /* = VHDL package's "dspace_base_c" */ ---- Only the region **sizes** should be modified by the user. The base addresses are defined by the processor's hardware (see section <<_address_space>>) and should not be altered at all. The size (and base) configuration can be edited by the user - either by explicitly changing the default values in the linker script or by overriding them when invoking `make`: .Overriding default rom size configuration (setting 4096 bytes) [source, bash] ---- $ make USER_FLAGS+="-Wl,--defsym,__neorv32_rom_size=4096" clean_all exe ---- [IMPORTANT] `neorv32_rom_base` (= `ORIGIN` of the `ram` section) has to be always identical to the processor's `dspace_base_c` hardware configuration. Also, `neorv32_ram_base` (= `ORIGIN` of the `rom` section) has to be always identical to the processor's `ispace_base_c` hardware configuration. [NOTE] The default configuration for the `rom` section assumes a maximum of 2GB _logical_ 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 a 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). 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 script - 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`. .Section Alignment [NOTE] The default NEORV32 linker script aligns _all_ regions so they start and end on a 32-bit (word) boundary. The default NEORV32 start-up code (crt0) makes use of this alignment by using word-level memory instructions to initialize the `.data` section and to clear the `.bss` section (faster!). :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). .Heap Size [IMPORTANT] The maximum size of the heap is defined by the linker script's `__neorv32_heap_size` variable. This variable has to be **explicitly defined** in order to define a heap size (and to use dynamic memory allocation at all) other than zero. The user can define the heap size while invoking the application makefile: `$ USER_FLAGS+="-Wl,--defsym,__neorv32_heap_size=4k" make clean_all exe` (defines a heap size of 4*1024 bytes). .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 The NEORV32 is a processor for _embedded_ applications, which is not capable of running desktop OSs like Linux (at least not without emulation). Hence, the default software framework relies on **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`). .Constructors and Destructors [NOTE] Constructors and destructors for plain C code or for C++ applications are supported by the software framework. See `sw/example/hellp_cpp` for a minimal example. .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. The image generator can generate several 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` (including header) for UART uploading via the bootloader. | `-app_img` | Generates an executable VHDL memory initialization image (no header) for the processor-internal IMEM. This option generates the `rtl/core/neorv32_application_image.vhd` file. | `-raw_hex` | Generates a plain ASCII hex-char file `neorv32_raw_exe.hex` (no header) for custom purpose. | `-raw_bin` | Generates a plain binary file `neorv32_raw_exe.bin` (no header) for custom purpose. | `-bld_img` | Generates an executable VHDL memory initialization image (no header) 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 application2 compilation flow will generate the `neorv32_exe.bin` executable for uploading via UART to the default NEORV32 bootloader. .Image Generator Compilation [NOTE] The sources of the image generator are automatically compiled when invoking the makefile (requiring a native GCC installation). .Executable Header [NOTE] 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. **Note that this executable format cannot be used for _direct_ execution (e.g. via XIP or direct memory access).** :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] . Clear <<_mstatus>>. . Clear <<_mie>> disabling all interrupt sources. . Install an early-boot dummy trap handler to <<_mtvec>>. . Initialize the global pointer `gp` and the stack pointer `sp` according to the <<_ram_layout>> provided by the linker script. . Initialize all integer register `x1 - x31` (only `x1 - x15` if the `E` CPU extension is enabled). . Setup `.data` section to configure initialized variables. . Clear the `.bss` section. . Call all _constructors_ (if there are any). . Call the application's `main` function (with no arguments: `argc` = `argv` = 0). . If `main` returns: ** Tnterrupts are disabled by clearing <<_mie>>. ** `mains`'s return value is copied to the <<_mscratch>> CSR to allow inspection by the debugger. ** Call all _destructors_ (if there are any). ** An optional <<_after_main_handler>> is called (if defined at all). ** The CPU enters sleep mode (using the `wfi` instruction) or remains in an endless loop (if `wfi` "returns"). .Bootloader Start-Up Code [NOTE] The bootloader uses the same start-up code as any "usual" application. However, certain parts are omitted when compiling `crt0` for the bootloader (like calling constructors and destructors). See the `crt0` source code for more information. :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 main function returned with exit code %i. \n", return_code); <1> } ---- <1> Use `` here to make clear this is a message comes from the runtime environment. [NOTE] The after-main handler is executed _after_ executing all destructor functions (if there are any at all). <<< // #################################################################################################################### include::software_bootloader.adoc[] <<< // #################################################################################################################### include::software_rte.adoc[]