added console options to configure/customize build-in bootloader

* updated according sections in data sheet and user guide
This commit is contained in:
stnolting 2021-06-26 20:03:10 +02:00
parent 2f15bf655c
commit 16cfbb63a0
5 changed files with 1265 additions and 1219 deletions

View file

@ -24,7 +24,7 @@ defined by the `hw_version_c` constant in the main VHDL package file [`rtl/core/
| Date (*dd.mm.yyyy*) | Version | Comment |
|:----------:|:-------:|:--------|
| 26.06.2021 | 1.5.7.3 | edit of v1.5.7.2: RISC-V spec claims to leave destination registers of trapping load operation unchanged (do _not_ set to zero); minor CPU control logic optimizations |
| 26.06.2021 | 1.5.7.3 | edit of v1.5.7.2: RISC-V spec claims to leave destination registers of trapping load operation unchanged (do _not_ set to zero); minor CPU control logic optimizations; :sparkles: reworked bootloader to provide several new configuration and customization options |
| 25.06.2021 | 1.5.7.2 | optimized instruction execution FSM: less hardware utilization, :lock: now _ensures_ to write ZERO to destination register if there is an exception during a load operation; made default bootloader even more HW configuration independent (GPIO, SPI and MTIME are optional; UART is optional but highly recommended); |
| 24.06.2021 | 1.5.7.1 | sparkles: added RISC-V `Zmmul` ISA extension (via `CPU_EXTENSION_RISCV_Zmmul` generic; default = _false_): implements only the integer multiplication instructions sub-set of the `M` extension; for size-constrained setups, requires ~50% less hardware ressources than the `M` extension |
| 23.06.2021 | [**:rocket:1.5.7**](https://github.com/stnolting/neorv32/releases/tag/v1.5.7) | **New release** |

View file

@ -381,30 +381,36 @@ int __neorv32_crt0_after_main(int32_t return_code) {
:sectnums:
=== Bootloader
The default bootloader (sw/bootloader/bootloader.c) of the NEORV32 processor allows to upload
new program executables at every time. If there is an external SPI flash connected to the processor (like the
FPGA's configuration memory), the bootloader can store the program executable to it. After reset, the
bootloader can directly boot from the flash without any user interaction.
[NOTE]
This section illustrated the **default** bootloader from the repository. The bootloader can be customized
to target application-specific scenarios. See User Guide section
https://stnolting.github.io/neorv32/ug/#_customizing_the_internal_bootloader[Customizing the Internal Bootloader]
for more information.
The default NEORV32 bootloader (source code `sw/bootloader/bootloader.c`) provides a build-in firmware that
allows to upload new application executables via UART at every time and to optionally store/boot them to/from
an external SPI flash. It features a simple "automatic boot" feature that will try to fetch an executable
from SPI flash if there is _no_ UART user interaction. This allows to build processor setup with
non-volatile application storage, which can be updated at any time.
The bootloader is only implemented if the <<_int_bootloader_en>> generic is _true_. This will
select the <<_indirect_boot>> boot configuration.
.Hardware requirements of the _default_ NEORV32 bootloader
[IMPORTANT]
**REQUIRED**: The bootloader requires the CSR access CPU extension (<<_cpu_extension_riscv_zicsr>> generic is _true_).
Furthermore, the bootloader requires at least 512 bytes of data memory (processor-internal DMEM or external DMEM). +
**REQUIRED**: The bootloader requires the CSR access CPU extension (<<_cpu_extension_riscv_zicsr>> generic is _true_)
and at least 512 bytes of data memory (processor-internal DMEM or external DMEM). +
+
_RECOMMENDED_: For user interaction via UART (like uploading executables) the primary UART (UART0) has to be
implemented (<<_io_uart0_en>> generic is _true_). Without UART the bootloader does not make much sense. However, auto-boot
via SPI is still supported but the bootloader should be customized (see User Guide) for this purpose. +
+
_OPTIONAL_: The bootloader uses bit 0 of the GPIO output port as "heart beat" and status LED if the
_OPTIONAL_: The default bootloader uses bit 0 of the GPIO output port as "heart beat" and status LED if the
GPIO controller is implemented (<<_io_gpio_en>> generic is _true_). +
+
_OPTIONAL_: The MTIME machine timer (<<_io_mtime_en>> generic is _true_) and the SPI controller
(<<_io_spi_en>> generic is _true_) are required in order to use the bootloader's auto-boot feature
(automatic boot from external SPI flash).
(automatic boot from external SPI flash if there is no user interaction via UART).
To interact with the bootloader, connect the primary UART (UART0) signals (`uart0_txd_o` and
`uart0_rxd_o`) of the processor's top entity via a serial port (-adapter) to your computer (hardware flow control is
@ -504,61 +510,18 @@ manually restarted via the `r` command.
The CPU is in machine level privilege mode after reset. When the bootloader boots an application,
this application is also started in machine level privilege mode.
:sectnums:
==== External SPI Flash for Booting
If you want the NEORV32 bootloader to automatically fetch and execute an application at system start, you
can store it to an external SPI flash. The advantage of the external memory is to have a non-volatile program
storage, which can be re-programmed at any time just by executing some bootloader commands. Thus, no
FPGA bitstream recompilation is required at all.
**SPI Flash Requirements**
The bootloader can access an SPI compatible flash via the processor top entity's SPI port and connected to
chip select `spi_csn_o(0)`. The flash must be capable of operating at least at 1/8 of the processor's main
clock. Only single read and write byte operations are used. The address has to be 24 bit long. Furthermore,
the SPI flash has to support at least the following commands:
* READ (`0x03`)
* READ STATUS (`0x05`)
* WRITE ENABLE (`0x06`)
* PAGE PROGRAM (`0x02`)
* SECTOR ERASE (`0xD8`)
* READ ID (`0x9E`)
Compatible (FGPA configuration) SPI flash memories are for example the "Winbond W25Q64FV2 or the "Micron N25Q032A".
**SPI Flash Configuration**
The base address `SPI_FLASH_BOOT_ADR` for the executable image inside the SPI flash is defined in the
"user configuration" section of the bootloader source code (`sw/bootloader/bootloader.c`). Most
FPGAs that use an external configuration flash, store the golden configuration bitstream at base address 0.
Make sure there is no address collision between the FPGA bitstream and the application image. You need to
change the default sector size if your flash has a sector size greater or less than 64kB:
[source,c]
----
/** SPI flash boot image base address */
#define SPI_FLASH_BOOT_ADR 0x00800000
/** SPI flash sector size in bytes */
#define SPI_FLASH_SECTOR_SIZE (64*1024)
----
[TIP]
More information regarding customization of the bootloader can be found in section
https://stnolting.github.io/neorv32/ug/#_customizing_the_internal_bootloader[Customizing the Internal Bootloader]
of the NEORV32 user guide. This guide also provides a tutorial how to program an external SPI flash
to automatically boot from it after reset (
https://stnolting.github.io/neorv32/ug/#_programming_an_external_spi_flash_via_the_bootloader[Programming an External SPI Flash via the Bootloader]).
For detailed information on using an SPI flash for application storage see User Guide section
https://stnolting.github.io/neorv32/ug/#_programming_an_external_spi_flash_via_the_bootloader[Programming an External SPI Flash via the Bootloader].
:sectnums:
==== Auto Boot Sequence
When you reset the NEORV32 processor, the bootloader waits 8 seconds for a user console input before it
When you reset the NEORV32 processor, the bootloader waits 8 seconds for a UART console input before it
starts the automatic boot sequence. This sequence tries to fetch a valid boot image from the external SPI
flash, connected to SPI chip select `spi_csn_o(0)`. If a valid boot image is found and can be successfully
transferred into the instruction memory, it is automatically started. If no SPI flash was detected or if there
was no valid boot image found, the bootloader stalls and the status LED is permanently activated.
flash, connected to SPI chip select `spi_csn_o(0)`. If a valid boot image is found that can be successfully
transferred into the instruction memory, it is automatically started. If no SPI flash is detected or if there
is no valid boot image found, and error code will be shown.
:sectnums:
@ -566,7 +529,7 @@ was no valid boot image found, the bootloader stalls and the status LED is perma
If something goes wrong during bootloader operation, an error code is shown. In this case the processor
stalls, a bell command and one of the following error codes are send to the terminal, the bootloader status
LED is permanently activated and the system must be reset manually.
LED is permanently activated and the system must be manually reset.
[cols="<2,<13"]
[grid="rows"]
@ -575,8 +538,6 @@ LED is permanently activated and the system must be reset manually.
| **`ERROR_1`** | Your program is way too big for the internal processors instructions memory. Increase the memory size or reduce (optimize!) your application code.
| **`ERROR_2`** | This indicates a checksum error. Something went wrong during the transfer of the program image (upload via UART or loading from the external SPI flash). If the error was caused by a UART upload, just try it again. When the error was generated during a flash access, the stored image might be corrupted.
| **`ERROR_3`** | This error occurs if the attached SPI flash cannot be accessed. Make sure you have the right type of flash and that it is properly connected to the NEORV32 SPI port using chip select #0.
| **`ERROR_4`** | This error pops up when an unexpected exception or interrupt was triggered. The cause of the trap (`mcause` CSR) is displayed for further investigation. This might be caused if an ISA extension is used that has not been synthesized.
| **`ERROR_?`** | Something really bad happened when there is no specific error code available :(
|=======================

View file

@ -363,7 +363,6 @@ automatic boot sequence and to start the actual bootloader user interface consol
BLDV: Mar 23 2021
HWV: 0x01050208
CLK: 0x05F5E100
USER: 0x10000DE0
MISA: 0x40901105
ZEXT: 0x00000023
PROC: 0x0EFF0037
@ -598,65 +597,101 @@ The RISC-V ISA string (for _MARCH_) follows a certain _canonical_ structure:
:sectnums:
== Customizing the Internal Bootloader
You can either customized the default bootloader via provided configuration options or you can write
a completely new bootloader that is specialized for your application.
The NEORV32 bootloader provides several options to configure and customize it for a certain application setup.
This configuration is done by passing _defines_ when compiling the bootloader. Of course you can also
modify to bootloader source code to provide a setup that perfectly fits your needs.
[IMPORTANT]
Keep in mind that the maximum size for the bootloader is limited to 32kB.
Each time the bootloader sources are modified, the bootloader has to be re-compiled (and re-installed to the
bootloader ROM) and the processor has to be re-synthesized.
The most important user-defined configuration options of the default bootloader are available as C-language
`#defines` right at the beginning of the bootloader source code (`sw/bootloader/bootloader.c`):
[NOTE]
Keep in mind that the maximum size for the bootloader is limited to 32kB and should be compiled using the
base ISA `rv32i` only to ensure it can work independently of the actual CPU configuration.
.Cut-out from the bootloader source code `bootloader.c`: configuration parameters
[source,c]
.Bootloader configuration parameters
[cols="<2,^1,^2,<6"]
[options="header", grid="rows"]
|=======================
| Parameter | Default | Legal values | Description
4+^| Serial console interface
| `UART_EN` | `1` | `0`, `1` | Set to `0` to disable UART0 (no serial console at all)
| `UART_BAUD` | `19200` | _any_ | Baud rate of UART0
4+^| Status LED
| `STATUS_LED_EN` | `1` | `0`, `1` | Enable bootloader status led ("heart beat") at `GPIO` output port pin #`STATUS_LED_PIN` when `1`
| `STATUS_LED_PIN` | `0` | `0` ... `31` | `GPIO` output pin used for the high-active status LED
4+^| Boot configuration
| `AUTO_BOOT_SPI_EN` | `0` | `0`, `1` | Set `1` to enable immediate boot from external SPI flash
| `AUTO_BOOT_OCD_EN` | `0` | `0`, `1` | Set `1` to enable boot via on-chip debugger (OCD)
| `AUTO_BOOT_TIMEOUT` | `8` | _any_ | Time in seconds after the auto-boot sequence starts (if there is no UART input by user); set to 0 to disabled auto-boot sequence
4+^| SPI configuration
| `SPI_FLASH_CS` | `0` | `0` ... `7` | SPI chip select output (`spi_csn_o`) for selecting flash
| `SPI_FLASH_SECTOR_SIZE` | `65536` | _any_ | SPI flash sector size in bytes
| `SPI_FLASH_CLK_PRSC` | `CLK_PRSC_8` | `CLK_PRSC_2` `CLK_PRSC_4` `CLK_PRSC_8` `CLK_PRSC_64` `CLK_PRSC_128` `CLK_PRSC_1024` `CLK_PRSC_2024` `CLK_PRSC_4096` | SPI clock pre-scaler (dividing main processor clock)
| `SPI_BOOT_BASE_ADDR` | `0x08000000` | _any_ 32-bit value | Defines the _base_ address of the executable in external flash
|=======================
Each configuration parameter is implemented as C-language `define` that can be manually overridden (_redefined_) when
invoking the bootloader's makefile. The according parameter and its new value has to be _appended_
(using `+=`) to the makefile's `USER_FLAGS` variable. Make sure to use the `-D` prefix here.
For example, to configure a UART Baud rate of 57600 and redirecting the status LED to output pin 20
use the following command (_in_ the bootloader's source folder `sw/bootloader`):
.Example: customizing, re-compiling and re-installing the bootloader
[source,console]
----
/** UART BAUD rate */
#define BAUD_RATE (19200)
/** Enable auto-boot sequence if != 0 */
#define AUTOBOOT_EN (1)
/** Time until the auto-boot sequence starts (in seconds) */
#define AUTOBOOT_TIMEOUT 8
/** Set to 0 to disable bootloader status LED */
#define STATUS_LED_EN (1)
/** SPI_DIRECT_BOOT_EN: Define/uncomment to enable SPI direct boot */
//#define SPI_DIRECT_BOOT_EN
/** Bootloader status LED at GPIO output port */
#define STATUS_LED (0)
/** SPI flash boot image base address (warning! address might wrap-around!) */
#define SPI_FLASH_BOOT_ADR (0x00800000)
/** SPI flash chip select line at spi_csn_o */
#define SPI_FLASH_CS (0)
/** Default SPI flash clock prescaler */
#define SPI_FLASH_CLK_PRSC (CLK_PRSC_8)
/** SPI flash sector size in bytes (default = 64kb) */
#define SPI_FLASH_SECTOR_SIZE (64*1024)
/** ASCII char to start fast executable upload process */
#define FAST_UPLOAD_CMD '#'
$ make USER_FLAGS+=-DUART_BAUD=57600 USER_FLAGS+=-DSTATUS_LED_PIN=20 clean_all bootloader
----
[NOTE]
The `clean_all` target ensure that all libraries are re-compiled. The `bootloader` target will automatically
compile and install the bootloader to the HDL boot ROM (updating `rtl/core/neorv32_bootloader_image.vhd`).
:sectnums:
=== Re-Compiling and Re-Installing the Bootloader
=== Bootloader Boot Configuration
Whenever you have modified the bootloader's C sources you need to recompile and re-install it and
re-synthesize your design afterwards.
The bootloader provides several _boot configurations_ that define where the actual application's executable
shall be fetched from. Note that the non-default boot configurations provide a smaller memory footprint
reducing boot ROM implementation costs.
[start=1]
.Compile and install the bootloader using the explicit `bootloader` makefile target.
[source,bash]
----
neorv32/sw/bootloader$ make clean_all bootloader
----
:sectnums!:
==== Default Boot Configuration
.Advanced
[NOTE]
You can also use the `bootloader` makefile target for any "normal" application. This will install that application
directly to the processor's internal boot ROM.
The _default_ bootloader configuration provides a UART-based user interface that allows to upload new executables
at any time. Optionally, the executable can also be programmed to an external SPI flash by the bootloader (see
section <<_programming_an_external_spi_flash_via_the_bootloader>>).
This configuration also provides an _automatic boot sequence_ (auto-boot) which will start fetching an executable
from external SPI flash using the default SPI configuration. By this, the default bootloader configuration
provides a "non volatile program storage" mechanism that automatically boot from external SPI flash
(after `AUTO_BOOT_TIMEOUT`) while still providing the option to re-program SPI flash at any time
via the UART interface.
:sectnums!:
==== `AUTO_BOOT_SPI_EN`
The automatic boot from SPI flash (enabled when `AUTO_BOOT_SPI_EN` is `1`) will fetch an executable from an external
SPI flash (using the according _SPI configuration_) right after reset. The bootloader will start fetching
the image at SPI flash base address `SPI_BOOT_BASE_ADDR`.
Note that there is _no_ UART console to interact with the bootloader. However, this boot configuration will
output minimal status messages via UART (if `UART_EN` is `1`).
:sectnums!:
==== `AUTO_BOOT_OCD_EN`
If `AUTO_BOOT_OCD_EN` is `1` the bootloader is implemented as minimal "halt loop" to be used with the on-chip debugger.
After initializing the hardware, the CPU waits in this endless loop until the on-chip debugger takes control over
the core (to upload and run the actual executable). See section <<_debugging_using_the_on_chip_debugger>>
for more information on how to use the on-chip debugger to upload and run executables.
[NOTE]
The bootloader is intended to work regardless of the actual NEORV32 hardware configuration
especially when it comes to CPU ISA extensions. Hence, the bootloader should be compiled using the
minimal `rv32i` ISA only.
All bootloader boot configuration support uploading new executables via the on-chip debugger.
[WARNING]
Note that this boot configuration does not load any executable at all! Hence,
this boot configuration is intended to be used with the on-chip debugger only.
@ -667,11 +702,34 @@ minimal `rv32i` ISA only.
The default processor-internal NEORV32 bootloader supports automatic booting from an external SPI flash.
This guide shows how to write an executable to the SPI flash via the bootloader so it can be automatically
fetched and executed after processor reset.
fetched and executed after processor reset. For example, you can use a section of the FPGA bitstream configuration
memory to store an application executable.
[TIP]
The SPI flash requirements are shown in section
https://stnolting.github.io/neorv32/#_external_spi_flash_for_booting[External SPI Flash for Booting] of the datasheet.
[NOTE]
This section assumes the _default_ configuration of the NEORV32 bootloader.
See section <<_customizing_the_internal_bootloader>> on how to customize the bootloader and its setting
(for example the SPI chip-select port, the SPI clock speed or the flash base address for storing the executable).
:sectnums:
=== SPI Flash
The bootloader can access an SPI compatible flash via the processor top entity's SPI port. By default, the flash
chip-select line is to `spi_csn_o(0)` and uses 1/8 of the processor's main clock as clock frequency.
The SPI flash has to support single-byte read and write, 24-bit addresses and at least the following standard commands:
* READ `0x03`
* READ STATUS `0x05`
* WRITE ENABLE `0x06`
* PAGE PROGRAM `0x02`
* SECTOR ERASE `0xD8`
* READ ID `0x9E`
Compatible (FGPA configuration) SPI flash memories are for example the "Winbond W25Q64FV2 or the "Micron N25Q032A".
:sectnums:
=== Programming an Executable
[start=1]
. At first, reset the NEORV32 processor and wait until the bootloader start screen appears in your terminal program.
@ -711,7 +769,7 @@ to specify the base address of the executable inside the SPI flash.
CMD:> u
Awaiting neorv32_exe.bin... OK
CMD:> p
Write 0x000013FC bytes to SPI flash @ 0x00800000? (y/n) y
Write 0x000013FC bytes to SPI flash @ 0x08000000? (y/n) y
Flashing... OK
CMD:>
----

File diff suppressed because it is too large Load diff

View file

@ -1,22 +1,6 @@
// #################################################################################################
// # << NEORV32 - Bootloader >> #
// # ********************************************************************************************* #
// # In order to run the bootloader on *any* CPU configuration, the bootloader should be compiled #
// # using the base ISA (rv32i/rv32e) only. #
// # ********************************************************************************************* #
// # Boot from (internal) instruction memory, UART or SPI Flash. #
// # Bootloader executables (neorv32_exe.bin) are LITTLE-ENDIAN! #
// # #
// # The bootloader uses the primary UART (UART0) for user console interface. #
// # #
// # UART configuration: 8 data bits, NO parity bit, 1 stop bit, 19200 baud (19200-8N1) #
// # Boot Flash: 8-bit SPI, 24-bit addresses (like Micron N25Q032A) @ neorv32.spi_csn_o(0) #
// # neorv32.gpio_o(0) is used as high-active status LED (can be disabled via #STATUS_LED_EN). #
// # #
// # Auto boot sequence (can be disabled via #AUTOBOOT_EN) after timeout (via #AUTOBOOT_TIMEOUT): #
// # -> Try booting from SPI flash at spi_csn_o(0). #
// # -> Permanently light up status led and stall CPU if SPI flash booting attempt fails. #
// # ********************************************************************************************* #
// # BSD 3-Clause License #
// # #
// # Copyright (c) 2021, Stephan Nolting. All rights reserved. #
@ -45,14 +29,14 @@
// # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED #
// # OF THE POSSIBILITY OF SUCH DAMAGE. #
// # ********************************************************************************************* #
// # The NEORV32 Processor - https://github.com/stnolting/neorv32 (c) Stephan Nolting #
// # The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 (c) Stephan Nolting #
// #################################################################################################
/**********************************************************************//**
* @file bootloader.c
* @author Stephan Nolting
* @brief Default NEORV32 bootloader.
* @brief NEORV32 bootloader.
**************************************************************************/
// Libraries
@ -61,29 +45,73 @@
/**********************************************************************//**
* @name User configuration
* @name Bootloader configuration (override via console to customize)
* default values are used if not explicitly customized
**************************************************************************/
/**@{*/
/** UART BAUD rate */
#define BAUD_RATE 19200
/** Enable auto-boot sequence if != 0 */
#define AUTOBOOT_EN 1
/** Time until the auto-boot sequence starts (in seconds) */
#define AUTOBOOT_TIMEOUT 8
/** Set to 0 to disable bootloader status LED */
#define STATUS_LED_EN 1
/** Set to 1 to enable SPI direct boot (disables the entire user console!) */
#define SPI_DIRECT_BOOT_EN 0
/** Bootloader status LED at GPIO output port */
#define STATUS_LED 0
/** SPI flash boot image base address (warning! address might wrap-around!) */
#define SPI_FLASH_BOOT_ADR 0x00800000
/** SPI flash chip select line at spi_csn_o */
#define SPI_FLASH_CS 0
/** Default SPI flash clock prescaler */
#define SPI_FLASH_CLK_PRSC CLK_PRSC_8
/** SPI flash sector size in bytes (default = 64kb) */
#define SPI_FLASH_SECTOR_SIZE 64*1024
/* ---- UART interface configuration ---- */
/** Set to 0 to disable UART interface */
#ifndef UART_EN
#define UART_EN 1
#endif
/** UART BAUD rate for serial interface */
#ifndef UART_BAUD
#define UART_BAUD 19200
#endif
/* ---- Status LED ---- */
/** Set to 0 to disable bootloader status LED (heart beat) at GPIO.gpio_o(STATUS_LED_PIN) */
#ifndef STATUS_LED_EN
#define STATUS_LED_EN 1
#endif
/** GPIO output pin for high-active bootloader status LED (heart beat) */
#ifndef STATUS_LED_PIN
#define STATUS_LED_PIN 0
#endif
/* ---- Boot configuration ---- */
/** Set to 1 to enable automatic (after reset) boot from external SPI flash at address SPI_BOOT_BASE_ADDR */
#ifndef AUTO_BOOT_SPI_EN
#define AUTO_BOOT_SPI_EN 0
#endif
/** Set to 1 to enable boot via on-chip debugger (keep CPU in halt loop until OCD takes over control) */
#ifndef AUTO_BOOT_OCD_EN
#define AUTO_BOOT_OCD_EN 0
#endif
/** Time until the auto-boot sequence starts (in seconds); 0 = disabled */
#ifndef AUTO_BOOT_TIMEOUT
#define AUTO_BOOT_TIMEOUT 8
#endif
/* ---- SPI configuration ---- */
/** SPI flash chip select (low-active) at SPI.spi_csn_o(SPI_FLASH_CS) */
#ifndef SPI_FLASH_CS
#define SPI_FLASH_CS 0
#endif
/** SPI flash sector size in bytes */
#ifndef SPI_FLASH_SECTOR_SIZE
#define SPI_FLASH_SECTOR_SIZE 65536 // default = 64kB
#endif
/** SPI flash clock pre-scaler; see #NEORV32_TWI_CT_enum */
#ifndef SPI_FLASH_CLK_PRSC
#define SPI_FLASH_CLK_PRSC CLK_PRSC_8
#endif
/** SPI flash boot base address */
#ifndef SPI_BOOT_BASE_ADDR
#define SPI_BOOT_BASE_ADDR 0x08000000
#endif
/**@}*/
@ -138,13 +166,25 @@ enum NEORV32_EXECUTABLE {
/**********************************************************************//**
* String output helper macros.
* Helper macros
**************************************************************************/
/**@{*/
/* Actual define-to-string helper */
/** Actual define-to-string helper */
#define xstr(a) str(a)
/* Internal helper macro */
/** Internal helper macro */
#define str(a) #a
/** Print to UART 0 */
#if (UART_EN != 0)
#define PRINT_TEXT(...) neorv32_uart0_print(__VA_ARGS__)
#define PRINT_XNUM(a) print_hex_word(a)
#define PRINT_GETC(a) neorv32_uart0_getc()
#define PRINT_PUTC(a) neorv32_uart0_putc(a)
#else
#define PRINT_TEXT(...)
#define PRINT_XNUM(a)
#define PRINT_GETC(a) 0
#define PRINT_PUTC(a)
#endif
/**@}*/
@ -183,10 +223,10 @@ void spi_flash_write_addr(uint32_t addr);
/**********************************************************************//**
* Sanity check: Do not compile with C extension
* Sanity check: Base ISA only!
**************************************************************************/
#if defined __riscv_compressed || defined __riscv_c
#error Bootloader has to be compiled without C ISA extension!
#if defined __riscv_atomic || defined __riscv_a || __riscv_b || __riscv_compressed || defined __riscv_c || defined __riscv_mul || defined __riscv_m
#warning In order to allow the bootloader to run on *any* CPU configuration it should be compiled using the base ISA only.
#endif
@ -195,126 +235,128 @@ void spi_flash_write_addr(uint32_t addr);
**************************************************************************/
int main(void) {
// check ISA
#if defined __riscv_atomic || defined __riscv_a || __riscv_b || __riscv_compressed || defined __riscv_c || defined __riscv_mul || defined __riscv_m
#warning In order to allow the bootloader to run on *ANY* CPU configuration it should be compiled using the base ISA (rv32i/e) only.
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// AUTO BOOT: OCD
// Stay in endless loop until the on-chip debugger
// takes over CPU control
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#if (AUTO_BOOT_OCD_EN != 0)
#warning Boot configuration: Boot via on-chip debugger.
while(1) {
asm volatile ("nop");
}
return 0; // should never be reached
#endif
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// AUTO BOOT: SPI flash
// Bootloader will directly boot and execute image from SPI flash
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#if (AUTO_BOOT_SPI_EN != 0)
#warning Boot configuration: Auto boot from external SPI flash.
PRINT_TEXT("\nNEORV32 bootloader\nLoading from SPI flash at ");
PRINT_XNUM((uint32_t)SPI_BOOT_BASE_ADDR);
PRINT_TEXT("...\n");
get_exe(EXE_STREAM_FLASH);
PRINT_TEXT("\n");
start_app();
return 0; // bootloader should never return
#endif
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// AUTO BOOT: Default
// User UART to upload new executable and optionally store it to SPI flash
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
exe_available = 0; // global variable for executable size; 0 means there is no exe available
getting_exe = 0; // we are not trying to get an executable yet
// ------------------------------------------------
// Minimal processor hardware initialization
// - all IO devices are reset and disabled by the crt0 code
// ------------------------------------------------
// get clock speed (in Hz)
uint32_t clock_speed = SYSINFO_CLK;
// init SPI for 8-bit, clock-mode 0
if (clock_speed < 40000000) {
neorv32_spi_setup(SPI_FLASH_CLK_PRSC, 0, 0);
}
else {
neorv32_spi_setup(CLK_PRSC_128, 0, 0);
}
#if (STATUS_LED_EN != 0)
if (neorv32_gpio_available()) {
// activate status LED, clear all others
neorv32_gpio_port_set(1 << STATUS_LED);
}
#endif
// init UART (no parity bit, no hardware flow control)
neorv32_uart_setup(BAUD_RATE, PARITY_NONE, FLOW_CONTROL_NONE);
// Configure machine system timer interrupt for ~2Hz
if (neorv32_mtime_available()) {
neorv32_mtime_set_timecmp(neorv32_mtime_get_time() + (clock_speed/4));
}
// configure trap handler (bare-metal, no neorv32 rte available)
neorv32_cpu_csr_write(CSR_MTVEC, (uint32_t)(&bootloader_trap_handler));
// active timer IRQ
neorv32_cpu_csr_write(CSR_MIE, 1 << CSR_MIE_MTIE); // activate MTIME IRQ source only!
neorv32_cpu_eint(); // enable global interrupts
// setup SPI for 8-bit, clock-mode 0
neorv32_spi_setup(SPI_FLASH_CLK_PRSC, 0, 0);
// ------------------------------------------------
// Fast boot mode: Direct SPI boot
// Bootloader will directly boot and execute image from SPI memory.
// No user UART console is available in this mode!
// ------------------------------------------------
#if (SPI_DIRECT_BOOT_EN != 0)
#warning Compiling bootloader in 'SPI direct boot mode'. Bootloader will directly boot from SPI memory. No user UART console will be available.
neorv32_uart_print("\nNEORV32 bootloader\nAccessing SPI flash at ");
print_hex_word((uint32_t)SPI_FLASH_BOOT_ADR);
neorv32_uart_print("\n");
get_exe(EXE_STREAM_FLASH);
neorv32_uart_print("\n");
start_app();
return 1; // bootloader should never return
#if (STATUS_LED_EN != 0)
if (neorv32_gpio_available()) {
// activate status LED, clear all others
neorv32_gpio_port_set(1 << STATUS_LED_PIN);
}
#endif
#if (UART_EN != 0)
// setup UART0 (primary UART, no parity bit, no hardware flow control)
neorv32_uart0_setup(UART_BAUD, PARITY_NONE, FLOW_CONTROL_NONE);
#endif
// Configure machine system timer interrupt for ~2Hz
if (neorv32_mtime_available()) {
neorv32_mtime_set_timecmp(neorv32_mtime_get_time() + (SYSINFO_CLK/4));
// active timer IRQ
neorv32_cpu_csr_write(CSR_MIE, 1 << CSR_MIE_MTIE); // activate MTIME IRQ source only!
neorv32_cpu_eint(); // enable global interrupts
}
// ------------------------------------------------
// Show bootloader intro and system info
// ------------------------------------------------
neorv32_uart_print("\n\n\n<< NEORV32 Bootloader >>\n\n"
PRINT_TEXT("\n\n\n<< NEORV32 Bootloader >>\n\n"
"BLDV: "__DATE__"\nHWV: ");
print_hex_word(neorv32_cpu_csr_read(CSR_MIMPID));
neorv32_uart_print("\nCLK: ");
print_hex_word(SYSINFO_CLK);
neorv32_uart_print("\nMISA: ");
print_hex_word(neorv32_cpu_csr_read(CSR_MISA));
neorv32_uart_print("\nZEXT: ");
print_hex_word(neorv32_cpu_csr_read(CSR_MZEXT));
neorv32_uart_print("\nPROC: ");
print_hex_word(SYSINFO_FEATURES);
neorv32_uart_print("\nIMEM: ");
print_hex_word(SYSINFO_IMEM_SIZE);
neorv32_uart_print(" bytes @");
print_hex_word(SYSINFO_ISPACE_BASE);
neorv32_uart_print("\nDMEM: ");
print_hex_word(SYSINFO_DMEM_SIZE);
neorv32_uart_print(" bytes @");
print_hex_word(SYSINFO_DSPACE_BASE);
PRINT_XNUM(neorv32_cpu_csr_read(CSR_MIMPID));
PRINT_TEXT("\nCLK: ");
PRINT_XNUM(SYSINFO_CLK);
PRINT_TEXT("\nMISA: ");
PRINT_XNUM(neorv32_cpu_csr_read(CSR_MISA));
PRINT_TEXT("\nZEXT: ");
PRINT_XNUM(neorv32_cpu_csr_read(CSR_MZEXT));
PRINT_TEXT("\nPROC: ");
PRINT_XNUM(SYSINFO_FEATURES);
PRINT_TEXT("\nIMEM: ");
PRINT_XNUM(SYSINFO_IMEM_SIZE);
PRINT_TEXT(" bytes @");
PRINT_XNUM(SYSINFO_ISPACE_BASE);
PRINT_TEXT("\nDMEM: ");
PRINT_XNUM(SYSINFO_DMEM_SIZE);
PRINT_TEXT(" bytes @");
PRINT_XNUM(SYSINFO_DSPACE_BASE);
// ------------------------------------------------
// Auto boot sequence
// ------------------------------------------------
#if (AUTOBOOT_EN != 0)
# if (AUTO_BOOT_TIMEOUT != 0)
if (neorv32_mtime_available()) {
neorv32_uart_print("\n\nAutoboot in "xstr(AUTOBOOT_TIMEOUT)"s. Press key to abort.\n");
uint64_t timeout_time = neorv32_mtime_get_time() + (uint64_t)(AUTOBOOT_TIMEOUT * clock_speed);
PRINT_TEXT("\n\nAutoboot in "xstr(AUTO_BOOT_TIMEOUT)"s. Press key to abort.\n");
uint64_t timeout_time = neorv32_mtime_get_time() + (uint64_t)(AUTO_BOOT_TIMEOUT * SYSINFO_CLK);
while(1){
if (neorv32_uart0_available()) { // wait for any key to be pressed
if (neorv32_uart_char_received()) {
if (neorv32_uart0_char_received()) {
break;
}
}
if (neorv32_mtime_get_time() >= timeout_time) { // timeout? start auto boot sequence
get_exe(EXE_STREAM_FLASH); // try booting from flash
neorv32_uart_print("\n");
PRINT_TEXT("\n");
start_app();
while(1);
}
}
neorv32_uart_print("Aborted.\n\n");
PRINT_TEXT("Aborted.\n\n");
}
#else
neorv32_uart_print("\n\n");
PRINT_TEXT("Aborted.\n\n");
#endif
print_help();
@ -325,10 +367,10 @@ int main(void) {
// ------------------------------------------------
while (1) {
neorv32_uart_print("\nCMD:> ");
char c = neorv32_uart_getc();
neorv32_uart_putc(c); // echo
neorv32_uart_print("\n");
PRINT_TEXT("\nCMD:> ");
char c = PRINT_GETC();
PRINT_PUTC(c); // echo
PRINT_TEXT("\n");
if (c == 'r') { // restart bootloader
asm volatile ("li t0, %[input_i]; jr t0" : : [input_i] "i" (BOOTLOADER_BASE_ADDRESS)); // jump to beginning of boot ROM
@ -345,11 +387,16 @@ int main(void) {
else if (c == 'l') { // get executable from flash
get_exe(EXE_STREAM_FLASH);
}
else if (c == 'e') { // start application program
start_app();
else if (c == 'e') { // start application program // executable available?
if (exe_available == 0) {
PRINT_TEXT("No executable available.");
}
else {
start_app();
}
}
else { // unknown command
neorv32_uart_print("Invalid CMD");
PRINT_TEXT("Invalid CMD");
}
}
@ -362,7 +409,7 @@ int main(void) {
**************************************************************************/
void print_help(void) {
neorv32_uart_print("Available CMDs:\n"
PRINT_TEXT("Available CMDs:\n"
" h: Help\n"
" r: Restart\n"
" u: Upload\n"
@ -377,23 +424,13 @@ void print_help(void) {
**************************************************************************/
void start_app(void) {
// executable available?
if (exe_available == 0) {
neorv32_uart_print("No executable available.");
return;
}
// no need to shut down/reset the used peripherals
// no need to disable interrupt sources
// -> crt0 will do a clean CPU/processor reset/setup
// deactivate global IRQs
neorv32_cpu_dint();
neorv32_uart_print("Booting...\n\n");
PRINT_TEXT("Booting...\n\n");
// wait for UART to finish transmitting
while (neorv32_uart_tx_busy());
while (neorv32_uart0_tx_busy());
// start app at instruction space base address
register uint32_t app_base = SYSINFO_ISPACE_BASE;
@ -417,7 +454,7 @@ void __attribute__((__interrupt__)) bootloader_trap_handler(void) {
if (cause == TRAP_CODE_MTI) { // raw exception code for MTI
#if (STATUS_LED_EN != 0)
if (neorv32_gpio_available()) {
neorv32_gpio_pin_toggle(STATUS_LED); // toggle status LED
neorv32_gpio_pin_toggle(STATUS_LED_PIN); // toggle status LED
}
#endif
// set time for next IRQ
@ -434,17 +471,17 @@ void __attribute__((__interrupt__)) bootloader_trap_handler(void) {
// Anything else (that was not expected); output exception notifier and try to resume
else {
uint32_t epc = neorv32_cpu_csr_read(CSR_MEPC);
#if (UART_EN != 0)
if (neorv32_uart0_available()) {
neorv32_uart_print("\n[EXC ");
print_hex_word(cause); // MCAUSE
neorv32_uart_putc(' ');
print_hex_word(epc); // MEPC
neorv32_uart_putc(' ');
print_hex_word(neorv32_cpu_csr_read(CSR_MTVAL)); // MTVAL
neorv32_uart_print("]\n");
PRINT_TEXT("\n[EXC ");
PRINT_XNUM(cause); // MCAUSE
PRINT_PUTC(' ');
PRINT_XNUM(epc); // MEPC
PRINT_PUTC(' ');
PRINT_XNUM(neorv32_cpu_csr_read(CSR_MTVAL)); // MTVAL
PRINT_TEXT("]\n");
}
#endif
neorv32_cpu_csr_write(CSR_MEPC, epc + 4); // advance to next instruction
}
}
@ -460,14 +497,14 @@ void get_exe(int src) {
getting_exe = 1; // to inform trap handler we were trying to get an executable
// flash image base address
uint32_t addr = SPI_FLASH_BOOT_ADR;
uint32_t addr = (uint32_t)SPI_BOOT_BASE_ADDR;
// get image from flash?
if (src == EXE_STREAM_UART) {
neorv32_uart_print("Awaiting neorv32_exe.bin... ");
PRINT_TEXT("Awaiting neorv32_exe.bin... ");
}
else {
neorv32_uart_print("Loading... ");
PRINT_TEXT("Loading... ");
// flash checks
if ((neorv32_spi_available() == 0) || // check if SPI is available at all
@ -503,7 +540,7 @@ void get_exe(int src) {
system_error(ERROR_CHECKSUM);
}
else {
neorv32_uart_print("OK");
PRINT_TEXT("OK");
exe_available = size; // store exe size
}
@ -520,21 +557,21 @@ void save_exe(void) {
uint32_t size = exe_available;
if (size == 0) {
neorv32_uart_print("No executable available.");
PRINT_TEXT("No executable available.");
return;
}
uint32_t addr = SPI_FLASH_BOOT_ADR;
uint32_t addr = (uint32_t)SPI_BOOT_BASE_ADDR;
// info and prompt
neorv32_uart_print("Write 0x");
print_hex_word(size);
neorv32_uart_print(" bytes to SPI flash @ 0x");
print_hex_word(addr);
neorv32_uart_print("? (y/n) ");
PRINT_TEXT("Write ");
PRINT_XNUM(size);
PRINT_TEXT(" bytes to SPI flash @ ");
PRINT_XNUM(addr);
PRINT_TEXT("? (y/n) ");
char c = neorv32_uart_getc();
neorv32_uart_putc(c);
char c = PRINT_GETC();
PRINT_PUTC(c);
if (c != 'y') {
return;
}
@ -544,11 +581,11 @@ void save_exe(void) {
system_error(ERROR_FLASH);
}
neorv32_uart_print("\nFlashing... ");
PRINT_TEXT("\nFlashing... ");
// clear memory before writing
uint32_t num_sectors = (size / (SPI_FLASH_SECTOR_SIZE)) + 1; // clear at least 1 sector
uint32_t sector = SPI_FLASH_BOOT_ADR;
uint32_t sector = (uint32_t)SPI_BOOT_BASE_ADDR;
while (num_sectors--) {
spi_flash_erase_sector(sector);
sector += SPI_FLASH_SECTOR_SIZE;
@ -575,9 +612,9 @@ void save_exe(void) {
// write checksum (sum complement)
checksum = (~checksum) + 1;
spi_flash_write_word(SPI_FLASH_BOOT_ADR + EXE_OFFSET_CHECKSUM, checksum);
spi_flash_write_word((uint32_t)SPI_BOOT_BASE_ADDR + EXE_OFFSET_CHECKSUM, checksum);
neorv32_uart_print("OK");
PRINT_TEXT("OK");
}
@ -598,7 +635,7 @@ uint32_t get_exe_word(int src, uint32_t addr) {
uint32_t i;
for (i=0; i<4; i++) {
if (src == EXE_STREAM_UART) {
data.uint8[i] = (uint8_t)neorv32_uart_getc();
data.uint8[i] = (uint8_t)PRINT_GETC();
}
else {
data.uint8[i] = spi_flash_read_byte(addr + i);
@ -616,13 +653,13 @@ uint32_t get_exe_word(int src, uint32_t addr) {
**************************************************************************/
void system_error(uint8_t err_code) {
neorv32_uart_print("\a\nERROR_"); // output error code with annoying bell sound
neorv32_uart_putc('0' + ((char)err_code));
PRINT_TEXT("\a\nERROR_"); // output error code with annoying bell sound
PRINT_PUTC('0' + ((char)err_code));
neorv32_cpu_dint(); // deactivate IRQs
#if (STATUS_LED_EN != 0)
if (neorv32_gpio_available()) {
neorv32_gpio_port_set(1 << STATUS_LED); // permanently light up status LED
neorv32_gpio_port_set(1 << STATUS_LED_PIN); // permanently light up status LED
}
#endif
@ -637,15 +674,17 @@ void system_error(uint8_t err_code) {
**************************************************************************/
void print_hex_word(uint32_t num) {
#if (UART_EN != 0)
static const char hex_symbols[16] = "0123456789abcdef";
neorv32_uart_print("0x");
PRINT_TEXT("0x");
int i;
for (i=0; i<8; i++) {
uint32_t index = (num >> (28 - 4*i)) & 0xF;
neorv32_uart_putc(hex_symbols[index]);
PRINT_PUTC(hex_symbols[index]);
}
#endif
}