mirror of
https://github.com/stnolting/neorv32.git
synced 2025-04-19 11:55:05 -04:00
Merge branch 'main' into fix/pmp
This commit is contained in:
commit
108688460b
17 changed files with 2193 additions and 1982 deletions
|
@ -2,241 +2,179 @@
|
|||
:sectnums:
|
||||
=== Bootloader
|
||||
|
||||
The NEORV32 bootloader (`sw/bootloader/bootloader.c`) provides an optional built-in firmware that allows to upload
|
||||
new application firmware at any time without the need to re-synthesize the FPGA's bitstream. A UART connection
|
||||
is used to provide an interactive user interface that allows to upload executables. Furthermore, the bootloader
|
||||
provides a programmer to write an executable to a processor-external serial flash. The bootloader's auto-boot feature
|
||||
can optionally load and boot this executable right after reset. The NEORV32 bootloader is enabled via the
|
||||
<<_boot_configuration>> `BOOT_MODE_SELECT` generic.
|
||||
|
||||
.Pre-Built Bootloader Image
|
||||
[IMPORTANT]
|
||||
This section refers to the **default** NEORV32 bootloader. A pre-compiled memory image for the processor-internal
|
||||
<<_bootloader_rom_bootrom>> is available in the project's +rtl+ folder: `rtl/core/neorv32_bootloader_image.vhd`.
|
||||
This image is automatically inserted into the boot ROM when synthesizing the processor with the bootloader being
|
||||
enabled.
|
||||
|
||||
.Minimal RISC-V ISA and Memory Configuration
|
||||
[NOTE]
|
||||
The default bootloader image was compiled for a minimal `rv32e_zicsr_zifencei` ISA configuration and only requires a
|
||||
RAM size of at least 256 bytes. Both constraints ensure that the bootloader can be executed by any actual CPU/processor
|
||||
configuration. However, the bootloader can recompiled with different capabilities. See the User Guide
|
||||
https://stnolting.github.io/neorv32/ug/#_customizing_the_internal_bootloader for more information.
|
||||
This section refers to the **default** NEORV32 bootloader. The <<_bootloader_rom_bootrom>> image file
|
||||
(`rtl/core/neorv32_bootloader_image.vhd`) already contains the pre-compiled bootloader in its default configuration.
|
||||
|
||||
.SMP Dual-Core Configuration
|
||||
.Minimal Hardware Requirements
|
||||
[NOTE]
|
||||
For the SMP <<_dual_core_configuration>> only the primary core (core 0) will boot and execute the bootloader
|
||||
while the secondary core (core 1) will be halted in sleep mode.
|
||||
The default bootloader image was compiled for a minimal `rv32e_zicsr_zifencei` ISA configuration and requires a
|
||||
RAM (DMEM) size of at least 256 bytes. These constraints ensure that the bootloader can be executed on any
|
||||
CPU/processor configuration. It is recommended to enable at least
|
||||
<<_primary_universal_asynchronous_receiver_and_transmitter_uart0, UART0>>,
|
||||
the <<_core_local_interruptor_clint, CLINT>> and the <<_general_purpose_input_and_output_port_gpio, GPIO>> controller.
|
||||
|
||||
The NEORV32 bootloader (`sw/bootloader/bootloader.c`) provides an optional built-in firmware that
|
||||
allows to upload new application executables at _any time_ without the need to re-synthesize the FPGA's bitstream.
|
||||
A UART connection is used to provide a simple text-based user interface that allows to upload executables.
|
||||
|
||||
Furthermore, the bootloader provides options to store an executable to a processor-external SPI flash.
|
||||
An "auto boot" feature can optionally fetch this executable right after reset if there is no user interaction
|
||||
via UART. This allows to build processor setups with _non-volatile application storage_ while maintaining the option
|
||||
to update the application software at any timer.
|
||||
:sectnums:
|
||||
==== Bootloader Console
|
||||
|
||||
The default bootloader provides a serial console via UART0 for user interaction using the following terminal
|
||||
settings (`19200-8-N-1`):
|
||||
|
||||
* 19200 Baud
|
||||
* 8 data bits
|
||||
* no parity bit
|
||||
* 1 stop bit
|
||||
* newlines are `\r\n`
|
||||
* no transfer/control protocol
|
||||
|
||||
.Terminal Program
|
||||
[TIP]
|
||||
Any terminal program that can connect to a serial port should work. However, make sure the program can transfer data
|
||||
in _raw_ byte mode without any protocol overhead (e.g. XMODEM). Some terminal programs struggle with transmitting files
|
||||
larger than 4kB (see https://github.com/stnolting/neorv32/pull/215). Try a different terminal program if uploading of
|
||||
a binary does not work.
|
||||
|
||||
The bootloader uses the LSB of the top entity's GPIO output port (`gpio_o(0)`) for an high-active status LED. All other
|
||||
output pins are set to low. After reset, the status LED will start blinking at 2Hz and the splash screen shows up and
|
||||
<<_auto_boot_sequence>> is started. This auto-boot sequence can be skipped within 10s by pressing any key (i.e. sending
|
||||
any char).
|
||||
|
||||
.Bootloader Console (with annotations)
|
||||
[source]
|
||||
----
|
||||
NEORV32 Bootloader
|
||||
|
||||
BLDV: Mar 30 2025 <1>
|
||||
HWV: 0x01110202 <2>
|
||||
CLK: 0x017d7840 <3>
|
||||
MISA: 0x40801104 <4>
|
||||
XISA: 0x00000083 <5>
|
||||
SOC: 0x000f800d <6>
|
||||
IMEM: 0x00004000 <7>
|
||||
DMEM: 0x00002000 <8>
|
||||
|
||||
Autoboot in 10s. Press any key to abort. <9>
|
||||
Aborted. <10>
|
||||
|
||||
Available CMDs: <11>
|
||||
h: Help <12>
|
||||
r: Restart <13>
|
||||
u: Upload via UART <14>
|
||||
s: Store to SPI flash <15>
|
||||
l: Load from SPI flash <16>
|
||||
e: Start executable <17>
|
||||
CMD:> <18>
|
||||
----
|
||||
<1> Bootloader version (built date).
|
||||
<2> Processor hardware version in BCD format (<<_mimpid>> CSR).
|
||||
<3> Processor clock speed in Hz (`CLK` register of <<_system_configuration_information_memory_sysinfo>>.
|
||||
<4> RISC-V CPU extensions (<<_misa>> CSR).
|
||||
<5> NEORV32-specific CPU extensions (<<_mxisa>> CSR).
|
||||
<6> Processor configuration (`SOC` register of <<_system_configuration_information_memory_sysinfo>>.
|
||||
<7> Internal IMEM size in byte (`MEM` register of <<_system_configuration_information_memory_sysinfo>>.
|
||||
<8> Internal DMEM size in byte (`MEM` register of <<_system_configuration_information_memory_sysinfo>>.
|
||||
<9> Start of <<_auto_boot_sequence>>.
|
||||
<10> Auto-boot sequence aborted due to user console input.
|
||||
<11> List of all commands.
|
||||
<12> Show "Available CMDs" help text again.
|
||||
<13> Restart bootloader and auto-boot sequence.
|
||||
<14> Upload new program executable (`neorv32_exe.bin`) via UART.
|
||||
<15> Store previously-uploaded executable to SPI flash.
|
||||
<16> Load executable from SPI flash.
|
||||
<17> Start the (up)loaded executable.
|
||||
<18> Command prompt.
|
||||
|
||||
If the auto-boot countdown is stopped the interactive user console starts. A new executable (`neorv32_exe.bin`) can be
|
||||
uploaded via UART by executing the `u` command. After that the executable can be booted via the `e` command. To program
|
||||
the recently uploaded executable to an attached SPI flash press `s`. To load an executable from the SPI flash press `l`.
|
||||
The bootloader including the auto-boot sequence can be manually restarted at any time via the `r` command.
|
||||
|
||||
|
||||
:sectnums:
|
||||
==== Auto Boot Sequence
|
||||
|
||||
After a reset, the bootloader waits 10 seconds for a UART console input before it starts the automatic boot sequence:
|
||||
|
||||
[start=1]
|
||||
. Try to load an executable from an external SPI flash using chip select `spi_csn_o(0)`.
|
||||
. If 1 fails, try to load an executable from an external TWI flash at device address `0x50`. Note that this
|
||||
auto-boot option is disabled by default.
|
||||
. IF 2 fails, start user console.
|
||||
|
||||
If a valid boot image is loaded it will be immediately started.
|
||||
|
||||
|
||||
:sectnums:
|
||||
==== Customizing the Internal Bootloader
|
||||
|
||||
The NEORV32 bootloader provides several options to configure it for a custom setup.
|
||||
It configured via set of C-language `defines` in `sw/bootloader/config.h`. All defines
|
||||
provide default value that can be altered or overridden by Makefile directives.
|
||||
|
||||
.Software Documentation
|
||||
[TIP]
|
||||
The Doxygen-based documentation of the bootloader's software is available online:
|
||||
https://stnolting.github.io/neorv32/sw/bootloader_8c.html
|
||||
|
||||
|
||||
:sectnums:
|
||||
==== Bootloader SoC/CPU Requirements
|
||||
|
||||
The bootloader requires certain CPU and SoC extensions and modules to be enabled in order to operate correctly.
|
||||
|
||||
[cols="^2,<8"]
|
||||
[grid="none"]
|
||||
.Bootloader configuration parameters
|
||||
[cols="<2,^1,^2,<6"]
|
||||
[options="header", grid="rows"]
|
||||
|=======================
|
||||
| **REQUIRED** | The <<_boot_configuration>> (`BOOT_MODE_SELECT` generic) has to be set to "bootloader" mode.
|
||||
| **REQUIRED** | The bootloader requires the privileged architecture CPU extension (<<_zicsr_isa_extension>>) to be enabled.
|
||||
| **REQUIRED** | At least 512 bytes of data memory (processor-internal DMEM or processor-external DMEM) are required for the bootloader's stack and global variables.
|
||||
| _RECOMMENDED_ | For user interaction via the <<_bootloader_console>> (like uploading executables) the primary UART (<<_primary_universal_asynchronous_receiver_and_transmitter_uart0>>) is required.
|
||||
| _RECOMMENDED_ | The default bootloader uses bit 0 of the <<_general_purpose_input_and_output_port_gpio>> output port to drive a high-active "heart beat" status LED.
|
||||
| _RECOMMENDED_ | The machine timer of the <<_core_local_interruptor_clint>> is used to control blinking of the status LED and also to automatically trigger the <<_auto_boot_sequence>>.
|
||||
| OPTIONAL | The SPI controller (<<_serial_peripheral_interface_controller_spi>>) is needed to store/load executable from external flash using the <<_auto_boot_sequence>>.
|
||||
| OPTIONAL | The TWI controller (<<_two_wire_serial_interface_controller_twi>>) is needed to boot/execute code directly from pre-programmed TWI memory.
|
||||
| Parameter | Default | Legal values | Description
|
||||
4+^| Memory layout
|
||||
| `EXE_BASE_ADDR` | `0x00000000` | _any_ | Memory base address for the executable; also the boot address for the application.
|
||||
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.
|
||||
| `UART_HW_HANDSHAKE_EN` | `0` | `0,1` | Set to `1` to enable UART0 hardware flow control.
|
||||
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+^| Auto-boot configuration
|
||||
| `AUTO_BOOT_EN` | `1` | `0,1` | Auto-boot enabled when `1`.
|
||||
| `AUTO_BOOT_TIMEOUT` | `10` | _any_ | Time in seconds after the auto-boot sequence starts (if there is no UART input by the user).
|
||||
4+^| SPI configuration
|
||||
| `SPI_EN` | `1` | `0,1` | Set `1` to enable usage of the SPI module.
|
||||
| `SPI_FLASH_CS` | `0` | `0..7` | SPI chip select line (port `spi_csn_o`) for selecting flash.
|
||||
| `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.
|
||||
| `SPI_FLASH_BASE_ADDR` | `0x00400000` | _any_ | Defines the SPI flash base address for the executable.
|
||||
| `SPI_FLASH_ADDR_BYTES` | `3` | `1,2,3,4` | SPI flash address size in number of bytes.
|
||||
| `SPI_FLASH_SECTOR_SIZE` | `65536` | _any_ | SPI flash sector size in bytes.
|
||||
4+^| TWI configuration
|
||||
| `TWI_EN` | `0` | `0,1` | Set `1` to enable usage of the TWI module.
|
||||
| `TWI_CLK_PRSC` | `CLK_PRSC_1024` | `CLK_PRSC_2` `CLK_PRSC_4` `CLK_PRSC_8` `CLK_PRSC_64` `CLK_PRSC_128` `CLK_PRSC_1024` `CLK_PRSC_2024` `CLK_PRSC_4096` | TWI clock pre-scaler.
|
||||
| `TWI_CLK_DIV` | `1` | `1..31` | TWI clock divider.
|
||||
| `TWI_DEVICE_ID` | `0xA0` | _any_ | TWI flash I2C address ("write address" with R/W bit cleared).
|
||||
| `TWI_FLASH_BASE_ADDR` | `0x00000000` | _any_ | Defines the TWI flash base address for the executable.
|
||||
| `TWI_FLASH_ADDR_BYTES` | `2` | `1,2,3,4` | TWI flash address size in number of bytes.
|
||||
|=======================
|
||||
|
||||
|
||||
:sectnums:
|
||||
==== Bootloader Flash Requirements
|
||||
|
||||
The bootloader can access an SPI-compatible flash via the processor's top entity SPI port. By default, the flash
|
||||
chip-select line is driven by `spi_csn_o(0)` and the SPI clock uses 1/8 of the processor's main clock as clock frequency.
|
||||
The SPI flash has to support single-byte read and write operations, 24-bit addresses and at least the following standard commands:
|
||||
|
||||
* `0x02`: Program page (write byte)
|
||||
* `0x03`: Read data (byte)
|
||||
* `0x04`: Write disable (for volatile status register)
|
||||
* `0x05`: Read (first) status register
|
||||
* `0x06`: Write enable (for volatile status register)
|
||||
* `0xAB`: Wake-up from sleep mode (optional)
|
||||
* `0xD8`: Block erase (64kB)
|
||||
|
||||
.Custom Configuration
|
||||
[TIP]
|
||||
Most properties (like chip select line, flash address width, SPI clock frequency, ...) of the default bootloader can be reconfigured
|
||||
without the need to change the source code. Custom configuration can be made using command line switches (defines) when recompiling
|
||||
the bootloader. See the User Guide https://stnolting.github.io/neorv32/ug/#_customizing_the_internal_bootloader for more information.
|
||||
|
||||
:sectnums:
|
||||
==== Bootloader TWI memory Requirements
|
||||
|
||||
The bootloader can access an TWI-compatible memory via the processor's top entity TWI port. Single- and dual address memory is supported, and reading is done in the following pattern
|
||||
`Device Address + Enabled Read | Memory Address Byte 0 | Memory Address 1 (optional) | Read Byte 0 | Read Byte 1 | Read Byte 2 | Read Byte 3`.
|
||||
The addresses are incremented until the end of the program binary is reached.
|
||||
|
||||
A python upload script for uploading is provided in the `sw/eeprom_upload` folder. Currently only for the https://www.robot-electronics.co.uk/htm/usb_iss_tech.htm[USB-ISS] module.
|
||||
|
||||
|
||||
Clock speed information can be read here: <<_two_wire_serial_interface_controller_twi>>.
|
||||
|
||||
:sectnums:
|
||||
==== Bootloader Console
|
||||
|
||||
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 not used so the according interface signals can be
|
||||
ignored), configure your terminal program using the following settings and perform a reset of the processor.
|
||||
|
||||
Terminal console settings (`19200-8-N-1`):
|
||||
|
||||
* 19200 Baud
|
||||
* 8 data bits
|
||||
* no parity bit
|
||||
* 1 stop bit
|
||||
* newline on `\r\n` (carriage return, newline)
|
||||
* no transfer protocol / control flow protocol - just raw bytes
|
||||
|
||||
.Terminal Program
|
||||
[IMPORTANT]
|
||||
Any terminal program that can connect to a serial port should work. However, make sure the program
|
||||
can transfer data in _raw_ byte mode without any protocol overhead (e.g. XMODEM). Some terminal programs struggle with
|
||||
transmitting files larger than 4kB (see https://github.com/stnolting/neorv32/pull/215). Try a different terminal program
|
||||
if uploading of a binary does not work.
|
||||
|
||||
The bootloader uses the LSB of the top entity's `gpio_o` output port as high-active status LED. All other
|
||||
output pins are set to low level and won't be altered. After reset, the status LED will start blinking at 2Hz and the
|
||||
following intro screen shows up:
|
||||
|
||||
[source]
|
||||
----
|
||||
<< NEORV32 Bootloader >>
|
||||
|
||||
BLDV: Mar 7 2023
|
||||
HWV: 0x01080107
|
||||
CLK: 0x05f5e100
|
||||
MISA: 0x40901106
|
||||
XISA: 0xc0000fab
|
||||
SOC: 0xffff402f
|
||||
IMEM: 0x00008000
|
||||
DMEM: 0x00002000
|
||||
|
||||
Autoboot in 10s. Press any key to abort.
|
||||
----
|
||||
|
||||
The start-up screen gives some brief information about the bootloader and several system configuration parameters:
|
||||
|
||||
[cols="<2,<15"]
|
||||
[grid="none"]
|
||||
|=======================
|
||||
| `BLDV` | Bootloader version (built date).
|
||||
| `HWV` | Processor hardware version (the <<_mimpid>> CSR); in BCD format; example: `0x01040606` = v1.4.6.6).
|
||||
| `CLK` | Processor clock speed in Hz (via the `CLK` register from the <<_system_configuration_information_memory_sysinfo>>.
|
||||
| `MISA` | RISC-V CPU extensions (<<_misa>> CSR).
|
||||
| `XISA` | NEORV32-specific CPU extensions (<<_mxisa>> CSR).
|
||||
| `SOC` | Processor configuration (via the `SOC` register from the <<_system_configuration_information_memory_sysinfo>>.
|
||||
| `IMEM` | Internal IMEM size in byte (via the `MEM` register from the <<_system_configuration_information_memory_sysinfo>>.
|
||||
| `DMEM` | Internal DMEM size in byte (via the `MEM` register from the <<_system_configuration_information_memory_sysinfo>>.
|
||||
|=======================
|
||||
|
||||
Now you have 10 seconds to press _any_ key. Otherwise, the bootloader starts the <<_auto_boot_sequence>>. When
|
||||
you press any key within the 10 seconds, the actual bootloader user console starts:
|
||||
|
||||
[source]
|
||||
----
|
||||
<< NEORV32 Bootloader >>
|
||||
|
||||
BLDV: Mar 7 2023
|
||||
HWV: 0x01080107
|
||||
CLK: 0x05f5e100
|
||||
MISA: 0x40901106
|
||||
XISA: 0xc0000fab
|
||||
SOC: 0xffff402f
|
||||
IMEM: 0x00008000
|
||||
DMEM: 0x00002000
|
||||
|
||||
Autoboot in 10s. Press any key to abort. <1>
|
||||
Aborted.
|
||||
|
||||
Available CMDs:
|
||||
h: Help
|
||||
r: Restart
|
||||
u: Upload
|
||||
s: Store to flash
|
||||
l: Load from flash
|
||||
t: Load from TWI Device
|
||||
e: Execute
|
||||
CMD:>
|
||||
----
|
||||
<1> Auto boot sequence aborted due to user console input.
|
||||
|
||||
The auto boot countdown is stopped and the bootloader's user console is ready to receive one of the following commands:
|
||||
|
||||
* `h`: Show the help text (again)
|
||||
* `r`: Restart the bootloader and the auto-boot sequence
|
||||
* `u`: Upload new program executable (`neorv32_exe.bin`) via UART into the instruction memory
|
||||
* `s`: Store executable to SPI flash at `spi_csn_o(0)` (little-endian byte order)
|
||||
* `l`: Load executable from SPI flash at `spi_csn_o(0)` (little-endian byte order)
|
||||
* `t`: Load executable from TWI memory at `0x50` (little-endian byte order) (disabled by default)
|
||||
* `e`: Start the application, which is currently stored in the instruction memory (IMEM)
|
||||
|
||||
A new executable can be uploaded via UART by executing the `u` command. After that, the executable can be directly
|
||||
executed via the `e` command. To store the recently uploaded executable to an attached SPI flash press `s`. To
|
||||
directly load an executable from the SPI flash press `l`. The bootloader and the auto-boot sequence can be
|
||||
manually restarted via the `r` command.
|
||||
|
||||
.Executable Upload
|
||||
[IMPORTANT]
|
||||
Make sure to upload the NEORV32 executable `neorv32_exe.bin`. Uploading any other file (like `main.bin`)
|
||||
will cause an `ERR_EXE` bootloader error (see <<_bootloader_error_codes>>).
|
||||
|
||||
.SPI Flash Power Down Mode
|
||||
[NOTE]
|
||||
The bootloader will issue a "wake-up" command prior to using the SPI flash to ensure it is not
|
||||
in sleep mode / power-down mode (see https://github.com/stnolting/neorv32/pull/552).
|
||||
|
||||
.SPI Flash Programming
|
||||
[TIP]
|
||||
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 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)` or from external TWI memory. If both are enabled, the bootloader
|
||||
will select SPI. 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:
|
||||
==== Bootloader Error Codes
|
||||
|
||||
If something goes wrong during bootloader operation an error code and a short message is shown. In this case the processor
|
||||
is halted (entering <<_sleep_mode>>), the bootloader status LED is permanently activated and the processor has to be reset manually.
|
||||
|
||||
.Debugging Information
|
||||
[TIP]
|
||||
If an unexpected exception has been raised, the bootloader prints hexadecimal debug information showing
|
||||
the <<_mcause>>, <<_mepc>> and <<_mtval>> CSR values.
|
||||
If something goes wrong an error code is shown. In this case the bootloader status LED stops blinking, an error code is printed
|
||||
to the console and the processor is halted by entering <<_sleep_mode>>.
|
||||
|
||||
[cols="<2,<8"]
|
||||
[grid="rows"]
|
||||
|=======================
|
||||
| **`ERR_EXE`** | If you try to transfer an invalid executable (via UART or from the external SPI flash), this error message shows up. There might be a transfer protocol configuration error in the terminal program or maybe just the wrong file was selected. Also, if no SPI flash was found during an auto-boot attempt, this message will be displayed.
|
||||
| **`ERR_SIZE`** | Your program is way too big for the internal processor’s instructions memory. Increase the memory size or reduce your application code.
|
||||
| **`ERR_CHKS`** | 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.
|
||||
| **`ERR_FLSH`** | 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.
|
||||
| **`ERR_EXC`** | The bootloader encountered an unexpected exception during operation. This might be caused when it tries to access peripherals that were not implemented during synthesis. Example: executing commands `l` or `s` (SPI flash operations) without the SPI module being implemented.
|
||||
| **`ERR_TWI`** | The TWI received an unexpected NACK while reading the external memory. Are the address and speed settings correct?
|
||||
| **`ERROR_DEVICE`** | A device/flash-accessing function returned an error code. Make sure that the device is properly
|
||||
connected and that all required processor modules are actually enabled (by the according <<_processor_top_entity_generics>>).
|
||||
| **`ERROR_SIGNATURE`** | The signature that indicates a valid NEORV32 executable of the loaded executable is incorrect.
|
||||
This can be caused by a temporary transmission error or by an invalid or corrupted executable.
|
||||
| **`ERROR_CHECKSUM`** | The checksum of the loaded executable is incorrect. This can be caused by a temporary transmission
|
||||
error or by an invalid or corrupted executable.
|
||||
| **`ERROR_EXCEPTION`** | An unexpected trap (synchronous exception or interrupt) has occurred. This can be caused by an
|
||||
invalid bootloader configuration (actually available processor modules, memory layout, ...). For debugging purpose the error
|
||||
message will also display the
|
||||
|=======================
|
||||
|
|
|
@ -20,8 +20,8 @@ which provides **SoC setups** for various FPGAs, boards and toolchains.
|
|||
<<_installing_an_executable_directly_into_memory, persistent>> in internal memory
|
||||
* setup a new <<_setup_of_a_new_application_program_project, application project>>
|
||||
* <<_application_specific_processor_configuration, optimizing>> the core for your application
|
||||
* add <<_adding_custom_hardware_modules, custom hardware extensions>> and <<_customizing_the_internal_bootloader, customizing the bootloader>>
|
||||
* <<_programming_an_external_spi_flash_via_the_bootloader, program>> an external SPI flash for persistent application storage
|
||||
* add <<_adding_custom_hardware_modules, custom hardware extensions>>
|
||||
* <<_using_the_neorv32_bootloader>>
|
||||
* generate an AMD Vivado <<_packaging_the_processor_as_vivado_ip_block, IP block>>
|
||||
* <<_simulating_the_processor, simulate>> the processor and <<_building_the_documentation, build the documentation>>
|
||||
* RTOS support for <<_zephyr_rtos_support, Zephyr>> and <<_freertos_support, FreeRTOS>>
|
||||
|
@ -49,9 +49,7 @@ include::application_specific_configuration.adoc[]
|
|||
|
||||
include::adding_custom_hw_modules.adoc[]
|
||||
|
||||
include::customizing_the_bootloader.adoc[]
|
||||
|
||||
include::programming_an_external_spi_flash_via_bootloader.adoc[]
|
||||
include::using_the_neorv32_bootloader.adoc[]
|
||||
|
||||
include::packaging_vivado.adoc[]
|
||||
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
<<<
|
||||
:sectnums:
|
||||
== Customizing the Internal Bootloader
|
||||
|
||||
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]
|
||||
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.
|
||||
|
||||
[NOTE]
|
||||
Keep in mind that the maximum size for the bootloader is limited to 8kB and it should be compiled using the
|
||||
minimal base & privileged ISA `rv32e_zicsr_zifencei` only to ensure it can work with any actual CPU configuration.
|
||||
|
||||
.Bootloader configuration parameters
|
||||
[cols="<2,^1,^2,<6"]
|
||||
[options="header", grid="rows"]
|
||||
|=======================
|
||||
| Parameter | Default | Legal values | Description
|
||||
4+^| Memory layout
|
||||
| `EXE_BASE_ADDR` | `0x00000000` | _any_ | Base address / boot address for the executable (see section "Address Space" in the NEORV32 data sheet)
|
||||
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
|
||||
| `UART_HW_HANDSHAKE_EN` | `0` | `0`, `1` | Set to `1` to enable UART0 hardware flow control
|
||||
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+^| Auto-boot configuration
|
||||
| `AUTO_BOOT_TIMEOUT` | `10` | _any_ | Time in seconds after the auto-boot sequence starts (if there is no UART input by the user); set to 0 to disabled auto-boot sequence
|
||||
4+^| SPI configuration
|
||||
| `SPI_EN` | `1` | `0`, `1` | Set `1` to enable the usage of the SPI module (including load/store executables from/to SPI flash options)
|
||||
| `SPI_FLASH_CS` | `0` | `0` ... `7` | SPI chip select output (`spi_csn_o`) for selecting flash
|
||||
| `SPI_FLASH_ADDR_BYTES` | `3` | `2`, `3`, `4` | SPI flash address size in number of bytes (2=16-bit, 3=24-bit, 4=32-bit)
|
||||
| `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` | `0x00400000` | _any_ 32-bit value | Defines the _base_ address of the executable in external flash
|
||||
4+^| TWI configuration
|
||||
| `TWI_EN` | `0` | `0`, `1` | Set `1` to enable the usage of the TWI module (including load executables from TWI device option)
|
||||
| `TWI_CLK_PRSC` | `CLK_PRSC_64` | `CLK_PRSC_2` `CLK_PRSC_4` `CLK_PRSC_8` `CLK_PRSC_64` `CLK_PRSC_128` `CLK_PRSC_1024` `CLK_PRSC_2024` `CLK_PRSC_4096` | TWI clock pre-scaler (dividing main processor clock)
|
||||
| `TWI_CLK_DIV` | `3` | `0` ... `15` | TWI clock divider (dividing twi clock)
|
||||
| `TWI_DEVICE_ID` | `0x50` | `0x00` ... `0x7F` | First TWI device ID to start. Is incremented until the end of the program is reached, when `TWI_ADDR_BYTES` is `1`.
|
||||
| `TWI_ADDR_BYTES` | `1` | `1`, `2` | TWI memory address size in number of bytes. When `TWI_ADDR_BYTES` is `1`, `TWI_DEVICE_ID` the gets incremented as well.
|
||||
|=======================
|
||||
|
||||
[IMPORTANT]
|
||||
Enabling all features while sticking to the minimal RISC-V ISA will result in a too-large binary!
|
||||
|
||||
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 `USER_FLAGS` variable. Make sure to use the `-D` prefix here. The configuration is also listed in the makefile of the bootloader.
|
||||
|
||||
For example, to configure a UART Baud rate of 57600 and redirecting the status LED to GPIO output pin 20
|
||||
use the following command:
|
||||
|
||||
.Example: customizing, re-compiling and re-installing the bootloader
|
||||
[source,console]
|
||||
----
|
||||
sw/bootloader$ 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:
|
||||
=== Auto-Boot Configuration
|
||||
|
||||
The default bootloader 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>>).
|
||||
|
||||
The bootloader also provides an _automatic boot sequence_ (auto-boot) which will start copying an executable
|
||||
from external SPI flash to IMEM using the default SPI configuration. By this, the default bootloader
|
||||
provides a "non-volatile program storage" mechanism that automatically boots from external SPI flash
|
||||
(after `AUTO_BOOT_TIMEOUT`) while still providing the option to re-program the SPI flash at any time
|
||||
via the UART console.
|
|
@ -1,69 +0,0 @@
|
|||
<<<
|
||||
:sectnums:
|
||||
== Programming an External SPI Flash via the Bootloader
|
||||
|
||||
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. For example, you can use a section of the FPGA bitstream configuration
|
||||
memory to store an application executable.
|
||||
|
||||
.Customization
|
||||
[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:
|
||||
=== Programming an Executable
|
||||
|
||||
[start=1]
|
||||
. At first, reset the NEORV32 processor and wait until the bootloader start screen appears in your terminal program.
|
||||
. Abort the auto boot sequence and start the user console by pressing any key.
|
||||
. Press u to upload the executable that you want to store to the external flash:
|
||||
|
||||
[source]
|
||||
----
|
||||
CMD:> u
|
||||
Awaiting neorv32_exe.bin...
|
||||
----
|
||||
|
||||
[start=4]
|
||||
. Send the binary in raw binary via your terminal program. When the upload is completed and "OK"
|
||||
appears, press `s` to trigger the programming of the flash (do not execute the image via the `e`
|
||||
command as this might corrupt the image):
|
||||
|
||||
[source]
|
||||
----
|
||||
CMD:> u
|
||||
Awaiting neorv32_exe.bin... OK
|
||||
CMD:> s
|
||||
Write 0x000013FC bytes to SPI flash @ 0x02000000? (y/n)
|
||||
----
|
||||
|
||||
[start=5]
|
||||
. The bootloader shows the size of the executable and the base address inside the SPI flash where the
|
||||
executable is going to be stored. A prompt appears: Type `y` to start the programming or type `n` to
|
||||
abort.
|
||||
|
||||
[TIP]
|
||||
Section <<_customizing_the_internal_bootloader>> show the according C-language `define` that can be modified
|
||||
to specify the base address of the executable inside the SPI flash.
|
||||
|
||||
[source]
|
||||
----
|
||||
CMD:> u
|
||||
Awaiting neorv32_exe.bin... OK
|
||||
CMD:> s
|
||||
Write 0x000013FC bytes to SPI flash @ 0x02000000? (y/n) y
|
||||
Flashing... OK
|
||||
CMD:>
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
The bootloader stores the executable in **little-endian** byte-order to the flash.
|
||||
|
||||
[start=6]
|
||||
. If "OK" appears in the terminal line, the programming process was successful. Now you can use the
|
||||
auto boot sequence to automatically boot your application from the flash at system start-up without
|
||||
any user interaction.
|
80
docs/userguide/using_the_neorv32_bootloader.adoc
Normal file
80
docs/userguide/using_the_neorv32_bootloader.adoc
Normal file
|
@ -0,0 +1,80 @@
|
|||
<<<
|
||||
:sectnums:
|
||||
== Using the NEORV32 Bootloader
|
||||
|
||||
.Customization
|
||||
[NOTE]
|
||||
This section assumes the _default_ configuration of the NEORV32 bootloader.
|
||||
See the NEORV32 data sheet's bootloader section for more information.
|
||||
|
||||
|
||||
:sectnums:
|
||||
=== Bootloader SPI Flash Requirements
|
||||
|
||||
The bootloader can access an SPI-compatible flash via the processor's top entity SPI port. By default, the flash
|
||||
chip-select line is driven by `spi_csn_o(0)` and the SPI clock uses 1/8 of the processor's main clock as clock frequency.
|
||||
The SPI flash has to support single-byte read and write operations, 24-bit addresses and at least the following standard commands:
|
||||
|
||||
* `0x02`: Program page (write byte)
|
||||
* `0x03`: Read data (byte)
|
||||
* `0x04`: Write disable (for volatile status register)
|
||||
* `0x05`: Read (first) status register
|
||||
* `0x06`: Write enable (for volatile status register)
|
||||
* `0xAB`: Wake-up from sleep mode (optional)
|
||||
* `0xD8`: Block erase (64kB)
|
||||
|
||||
.SPI Flash Power Down Mode
|
||||
[NOTE]
|
||||
The bootloader will issue a "wake-up" command prior to using the SPI flash to ensure it is not
|
||||
in sleep mode / power-down mode (see https://github.com/stnolting/neorv32/pull/552).
|
||||
|
||||
|
||||
:sectnums:
|
||||
=== Programming an External SPI Flash via the Bootloader
|
||||
|
||||
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. For example, you can use a section of the FPGA bitstream
|
||||
configuration memory to store an application executable.
|
||||
|
||||
[start=1]
|
||||
. At first, reset the NEORV32 processor and wait until the bootloader start screen appears in your terminal program.
|
||||
. Abort the auto boot sequence and start the user console by pressing any key.
|
||||
. Press `u` to upload the executable that you want to store to the external flash:
|
||||
|
||||
[source]
|
||||
----
|
||||
CMD:> u
|
||||
Awaiting neorv32_exe.bin...
|
||||
----
|
||||
|
||||
[start=4]
|
||||
. Send the binary in raw binary via your terminal program. When the upload is completed and "OK"
|
||||
appears, press `s` to trigger the programming of the flash::
|
||||
|
||||
[source]
|
||||
----
|
||||
CMD:> u
|
||||
Awaiting neorv32_exe.bin... OK
|
||||
CMD:> s
|
||||
Write 0x00001614 bytes to SPI flash @0x00400000 (y/n)?
|
||||
----
|
||||
|
||||
[start=5]
|
||||
. The bootloader shows the size of the executable and the base address of the SPI flash where the
|
||||
executable will be stored. A prompt appears: type `y` to start the programming or type `n` to abort.
|
||||
|
||||
[source]
|
||||
----
|
||||
CMD:> u
|
||||
Awaiting neorv32_exe.bin... OK
|
||||
CMD:> s
|
||||
Write 0x00001614 bytes to SPI flash @0x00400000 (y/n)?
|
||||
Flashing... OK
|
||||
CMD:>
|
||||
----
|
||||
|
||||
[start=6]
|
||||
. If "OK" appears in the terminal line, the programming process was successful. Now you can use the
|
||||
auto boot sequence to automatically boot your application from the flash at system start-up without
|
||||
any user interaction.
|
File diff suppressed because it is too large
Load diff
10
sw/bootloader/README.md
Normal file
10
sw/bootloader/README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
## NEORV32 Bootloader
|
||||
|
||||
Use the `config.h` file to customize the bootloader configuration.
|
||||
Recompile and re-install the bootloader ROM image via `make bootloader`.
|
||||
|
||||
#### Documentation
|
||||
|
||||
* [Data Sheet: NEORV32 Bootloader](https://stnolting.github.io/neorv32/#_bootloader)
|
||||
* [User Guide: Customizing the internal bootloader](https://stnolting.github.io/neorv32/ug/#_customizing_the_internal_bootloader)
|
||||
* [User Guide: Programming an external SPI flash via the bootloader](https://stnolting.github.io/neorv32/ug/#_programming_an_external_spi_flash_via_the_bootloader)
|
File diff suppressed because it is too large
Load diff
156
sw/bootloader/config.h
Normal file
156
sw/bootloader/config.h
Normal file
|
@ -0,0 +1,156 @@
|
|||
// ================================================================================ //
|
||||
// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 //
|
||||
// Copyright (c) NEORV32 contributors. //
|
||||
// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. //
|
||||
// Licensed under the BSD-3-Clause license, see LICENSE for details. //
|
||||
// SPDX-License-Identifier: BSD-3-Clause //
|
||||
// ================================================================================ //
|
||||
|
||||
/**
|
||||
* @file config.h
|
||||
* @brief Default NEORV32 bootloader configuration.
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
/**********************************************************************
|
||||
* Processor memory layout
|
||||
**********************************************************************/
|
||||
|
||||
// Memory base address for the executable
|
||||
#ifndef EXE_BASE_ADDR
|
||||
#define EXE_BASE_ADDR 0x00000000U
|
||||
#endif
|
||||
|
||||
/**********************************************************************
|
||||
* UART configuration
|
||||
**********************************************************************/
|
||||
|
||||
// Set to 0 to disable UART
|
||||
#ifndef UART_EN
|
||||
#define UART_EN 1
|
||||
#endif
|
||||
|
||||
// UART BAUD rate for serial
|
||||
#ifndef UART_BAUD
|
||||
#define UART_BAUD 19200
|
||||
#endif
|
||||
|
||||
// Set to 1 to enable UART RTS/CTS hardware flow control
|
||||
#ifndef UART_HW_HANDSHAKE_EN
|
||||
#define UART_HW_HANDSHAKE_EN 0
|
||||
#endif
|
||||
|
||||
// Print splash screen
|
||||
#ifndef UART_PRINT_SPLASH_EN
|
||||
#define UART_PRINT_SPLASH_EN 1
|
||||
#endif
|
||||
|
||||
/**********************************************************************
|
||||
* Status LED
|
||||
**********************************************************************/
|
||||
|
||||
// Set to 0 to disable bootloader status LED 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
|
||||
#ifndef STATUS_LED_PIN
|
||||
#define STATUS_LED_PIN 0
|
||||
#endif
|
||||
|
||||
/**********************************************************************
|
||||
* Auto-boot configuration
|
||||
**********************************************************************/
|
||||
|
||||
// Enable auto-boot
|
||||
#ifndef AUTO_BOOT_EN
|
||||
#define AUTO_BOOT_EN 1
|
||||
#endif
|
||||
|
||||
// Time until the auto-boot sequence starts (in seconds)
|
||||
#ifndef AUTO_BOOT_TIMEOUT
|
||||
#define AUTO_BOOT_TIMEOUT 10
|
||||
#endif
|
||||
|
||||
/**********************************************************************
|
||||
* SPI configuration
|
||||
**********************************************************************/
|
||||
|
||||
// Enable SPI (default) including SPI flash boot options
|
||||
#ifndef SPI_EN
|
||||
#define SPI_EN 1
|
||||
#endif
|
||||
|
||||
// SPI flash chip select
|
||||
#ifndef SPI_FLASH_CS
|
||||
#define SPI_FLASH_CS 0
|
||||
#endif
|
||||
|
||||
// SPI flash clock prescaler, see #NEORV32_CLOCK_PRSC_enum
|
||||
#ifndef SPI_FLASH_CLK_PRSC
|
||||
#define SPI_FLASH_CLK_PRSC CLK_PRSC_64
|
||||
#endif
|
||||
|
||||
// SPI flash base address (hast to be aligned to the sector size)
|
||||
#ifndef SPI_FLASH_BASE_ADDR
|
||||
#define SPI_FLASH_BASE_ADDR 0x00400000U
|
||||
#endif
|
||||
|
||||
// SPI flash address bytes (1,2,3,4)
|
||||
#ifndef SPI_FLASH_ADDR_BYTES
|
||||
#define SPI_FLASH_ADDR_BYTES 3
|
||||
#endif
|
||||
|
||||
// SPI flash sector size in bytes
|
||||
#ifndef SPI_FLASH_SECTOR_SIZE
|
||||
#define SPI_FLASH_SECTOR_SIZE 65536
|
||||
#endif
|
||||
|
||||
/**********************************************************************
|
||||
* TWI configuration
|
||||
**********************************************************************/
|
||||
|
||||
// Enable TWI for copying to RAM
|
||||
#ifndef TWI_EN
|
||||
#define TWI_EN 0
|
||||
#endif
|
||||
|
||||
// TWI clock prescaler, see #NEORV32_CLOCK_PRSC_enum
|
||||
#ifndef TWI_CLK_PRSC
|
||||
#define TWI_CLK_PRSC CLK_PRSC_1024
|
||||
#endif
|
||||
|
||||
// TWI clock divider
|
||||
#ifndef TWI_CLK_DIV
|
||||
#define TWI_CLK_DIV 3
|
||||
#endif
|
||||
|
||||
// TWI allow clock stretching
|
||||
#ifndef TWI_CLK_STRECH_EN
|
||||
#define TWI_CLK_STRECH_EN 0
|
||||
#endif
|
||||
|
||||
// TWI device ID (write address; R/W cleared)
|
||||
#ifndef TWI_DEVICE_ID
|
||||
#define TWI_DEVICE_ID 0xA0
|
||||
#endif
|
||||
|
||||
// TWI flash base address (has to 4-byte aligned)
|
||||
#ifndef TWI_FLASH_BASE_ADDR
|
||||
#define TWI_FLASH_BASE_ADDR 0x00000000U
|
||||
#endif
|
||||
|
||||
// TWI flash address bytes (1,2,3,4)
|
||||
#ifndef TWI_FLASH_ADDR_BYTES
|
||||
#define TWI_FLASH_ADDR_BYTES 2
|
||||
#endif
|
||||
|
||||
// TWI flash bulk write enable
|
||||
#ifndef TWI_FLASH_BULK_WRITE_EN
|
||||
#define TWI_FLASH_BULK_WRITE_EN 0
|
||||
#endif
|
||||
|
||||
#endif // CONFIG_H
|
24
sw/bootloader/hal/include/spi_flash.h
Normal file
24
sw/bootloader/hal/include/spi_flash.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
// ================================================================================ //
|
||||
// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 //
|
||||
// Copyright (c) NEORV32 contributors. //
|
||||
// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. //
|
||||
// Licensed under the BSD-3-Clause license, see LICENSE for details. //
|
||||
// SPDX-License-Identifier: BSD-3-Clause //
|
||||
// ================================================================================ //
|
||||
|
||||
/**
|
||||
* @file spi_flash.h
|
||||
* @brief SPI flash driver.
|
||||
*/
|
||||
|
||||
#ifndef SPI_FLASH_H
|
||||
#define SPI_FLASH_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int spi_flash_check(void) ;
|
||||
int spi_flash_read_word(uint32_t addr, uint32_t* rdata) ;
|
||||
int spi_flash_write_word(uint32_t addr, uint32_t wdata);
|
||||
int spi_flash_erase_sector(uint32_t addr);
|
||||
|
||||
#endif // SPI_FLASH_H
|
23
sw/bootloader/hal/include/twi_flash.h
Normal file
23
sw/bootloader/hal/include/twi_flash.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
// ================================================================================ //
|
||||
// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 //
|
||||
// Copyright (c) NEORV32 contributors. //
|
||||
// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. //
|
||||
// Licensed under the BSD-3-Clause license, see LICENSE for details. //
|
||||
// SPDX-License-Identifier: BSD-3-Clause //
|
||||
// ================================================================================ //
|
||||
|
||||
/**
|
||||
* @file twi_flash.h
|
||||
* @brief TWI flash driver.
|
||||
*/
|
||||
|
||||
#ifndef TWI_FLASH_H
|
||||
#define TWI_FLASH_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int twi_flash_read_word(uint32_t addr, uint32_t* rdata);
|
||||
int twi_flash_write_word(uint32_t addr, uint32_t wdata);
|
||||
void twi_flash_delay_twi_tick(int tick_count);
|
||||
|
||||
#endif // TWI_FLASH_H
|
25
sw/bootloader/hal/include/uart.h
Normal file
25
sw/bootloader/hal/include/uart.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
// ================================================================================ //
|
||||
// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 //
|
||||
// Copyright (c) NEORV32 contributors. //
|
||||
// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. //
|
||||
// Licensed under the BSD-3-Clause license, see LICENSE for details. //
|
||||
// SPDX-License-Identifier: BSD-3-Clause //
|
||||
// ================================================================================ //
|
||||
|
||||
/**
|
||||
* @file uart.h
|
||||
* @brief Minimal UART0 driver.
|
||||
*/
|
||||
|
||||
#ifndef UART_H
|
||||
#define UART_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
char uart_getc(void);
|
||||
void uart_putc(char c);
|
||||
void uart_puts(const char *s);
|
||||
void uart_puth(uint32_t num);
|
||||
int uart_getw(uint32_t* rdata);
|
||||
|
||||
#endif // UART_H
|
246
sw/bootloader/hal/source/spi_flash.c
Normal file
246
sw/bootloader/hal/source/spi_flash.c
Normal file
|
@ -0,0 +1,246 @@
|
|||
// ================================================================================ //
|
||||
// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 //
|
||||
// Copyright (c) NEORV32 contributors. //
|
||||
// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. //
|
||||
// Licensed under the BSD-3-Clause license, see LICENSE for details. //
|
||||
// SPDX-License-Identifier: BSD-3-Clause //
|
||||
// ================================================================================ //
|
||||
|
||||
/**
|
||||
* @file spi_flash.c
|
||||
* @brief SPI flash driver.
|
||||
*/
|
||||
|
||||
#include <neorv32.h>
|
||||
#include <config.h>
|
||||
#include <spi_flash.h>
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* SPI flash commands
|
||||
**************************************************************************/
|
||||
enum SPI_FLASH_CMD_enum {
|
||||
SPI_FLASH_CMD_PAGE_PROGRAM = 0x02, /**< Program page */
|
||||
SPI_FLASH_CMD_READ = 0x03, /**< Read data */
|
||||
SPI_FLASH_CMD_WRITE_DISABLE = 0x04, /**< Disallow write access */
|
||||
SPI_FLASH_CMD_READ_STATUS = 0x05, /**< Get status register */
|
||||
SPI_FLASH_CMD_WRITE_ENABLE = 0x06, /**< Allow write access */
|
||||
SPI_FLASH_CMD_WAKE = 0xAB, /**< Wake up from sleep mode */
|
||||
SPI_FLASH_CMD_SECTOR_ERASE = 0xD8 /**< Erase complete sector */
|
||||
};
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* SPI flash status register bits
|
||||
**************************************************************************/
|
||||
enum SPI_FLASH_SREG_enum {
|
||||
FLASH_SREG_BUSY = 0, /**< Busy, write/erase in progress when set, read-only */
|
||||
FLASH_SREG_WEL = 1 /**< Write access enabled when set, read-only */
|
||||
};
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Send single command to SPI flash.
|
||||
*
|
||||
* @param[in] cmd Command byte.
|
||||
**************************************************************************/
|
||||
void spi_flash_cmd(uint8_t cmd) {
|
||||
|
||||
neorv32_spi_cs_en(SPI_FLASH_CS);
|
||||
neorv32_spi_transfer(cmd);
|
||||
neorv32_spi_cs_dis();
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Read flash status register.
|
||||
*
|
||||
* @return SPI flash status register.
|
||||
**************************************************************************/
|
||||
uint8_t spi_flash_read_status(void) {
|
||||
|
||||
neorv32_spi_cs_en(SPI_FLASH_CS);
|
||||
|
||||
neorv32_spi_transfer(SPI_FLASH_CMD_READ_STATUS);
|
||||
uint8_t res = neorv32_spi_transfer(0);
|
||||
|
||||
neorv32_spi_cs_dis();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Send address to flash.
|
||||
*
|
||||
* @param[in] addr Byte address.
|
||||
**************************************************************************/
|
||||
void spi_flash_send_addr(uint32_t addr) {
|
||||
|
||||
subwords32_t address;
|
||||
address.uint32 = addr;
|
||||
|
||||
#if (SPI_FLASH_ADDR_BYTES == 1)
|
||||
neorv32_spi_transfer(address.uint8[0]);
|
||||
#elif (SPI_FLASH_ADDR_BYTES == 2)
|
||||
neorv32_spi_transfer(address.uint8[1]);
|
||||
neorv32_spi_transfer(address.uint8[0]);
|
||||
#elif (SPI_FLASH_ADDR_BYTES == 3)
|
||||
neorv32_spi_transfer(address.uint8[2]);
|
||||
neorv32_spi_transfer(address.uint8[1]);
|
||||
neorv32_spi_transfer(address.uint8[0]);
|
||||
#elif (SPI_FLASH_ADDR_BYTES == 4)
|
||||
neorv32_spi_transfer(address.uint8[3]);
|
||||
neorv32_spi_transfer(address.uint8[2]);
|
||||
neorv32_spi_transfer(address.uint8[1]);
|
||||
neorv32_spi_transfer(address.uint8[0]);
|
||||
#else
|
||||
#error "Invalid SPI_FLASH_ADDR_BYTES configuration!"
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Write byte to SPI flash.
|
||||
*
|
||||
* @param[in] addr SPI flash read address.
|
||||
* @param[in] wdata SPI flash read data.
|
||||
**************************************************************************/
|
||||
void spi_flash_write_byte(uint32_t addr, uint8_t wdata) {
|
||||
|
||||
spi_flash_cmd(SPI_FLASH_CMD_WRITE_ENABLE); // allow write-access
|
||||
|
||||
neorv32_spi_cs_en(SPI_FLASH_CS);
|
||||
|
||||
neorv32_spi_transfer(SPI_FLASH_CMD_PAGE_PROGRAM);
|
||||
spi_flash_send_addr(addr);
|
||||
neorv32_spi_transfer(wdata);
|
||||
|
||||
neorv32_spi_cs_dis();
|
||||
|
||||
while(1) {
|
||||
if ((spi_flash_read_status() & (1 << FLASH_SREG_BUSY)) == 0) { // write in progress flag cleared?
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Check if SPI and flash are available/working by making sure the WEL
|
||||
* flag of the flash status register can be set and cleared again.
|
||||
*
|
||||
* @return 0 if success, !=0 if error
|
||||
**************************************************************************/
|
||||
int spi_flash_check(void) {
|
||||
|
||||
#if (SPI_EN != 0)
|
||||
if (neorv32_spi_available()) {
|
||||
// the flash may have been set to sleep prior to reaching this point. Make sure it's alive
|
||||
spi_flash_cmd(SPI_FLASH_CMD_WAKE);
|
||||
|
||||
// set WEL
|
||||
spi_flash_cmd(SPI_FLASH_CMD_WRITE_ENABLE);
|
||||
if ((spi_flash_read_status() & (1 << FLASH_SREG_WEL)) == 0) { // fail if WEL is cleared
|
||||
return -1;
|
||||
}
|
||||
|
||||
// clear WEL
|
||||
spi_flash_cmd(SPI_FLASH_CMD_WRITE_DISABLE);
|
||||
if ((spi_flash_read_status() & (1 << FLASH_SREG_WEL)) != 0) { // fail if WEL is set
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Read byte from SPI flash.
|
||||
*
|
||||
* @param[in] addr Word-aligned address.
|
||||
* @param[in,out] rdata Pointer for returned data (uint32_t).
|
||||
* @return 0 if success, !=0 if error
|
||||
**************************************************************************/
|
||||
int spi_flash_read_word(uint32_t addr, uint32_t* rdata) {
|
||||
|
||||
#if (SPI_EN != 0)
|
||||
neorv32_spi_cs_en(SPI_FLASH_CS);
|
||||
|
||||
neorv32_spi_transfer(SPI_FLASH_CMD_READ);
|
||||
spi_flash_send_addr(addr);
|
||||
|
||||
subwords32_t tmp;
|
||||
tmp.uint8[0] = neorv32_spi_transfer(0);
|
||||
tmp.uint8[1] = neorv32_spi_transfer(1);
|
||||
tmp.uint8[2] = neorv32_spi_transfer(2);
|
||||
tmp.uint8[3] = neorv32_spi_transfer(3);
|
||||
|
||||
neorv32_spi_cs_dis();
|
||||
|
||||
*rdata = tmp.uint32;
|
||||
return 0;
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Write word to SPI flash.
|
||||
*
|
||||
* @param addr SPI flash write address.
|
||||
* @param wdata SPI flash write data.
|
||||
* @return 0 if success, !=0 if error
|
||||
**************************************************************************/
|
||||
int spi_flash_write_word(uint32_t addr, uint32_t wdata) {
|
||||
|
||||
#if (SPI_EN != 0)
|
||||
subwords32_t data;
|
||||
data.uint32 = wdata;
|
||||
|
||||
// little-endian byte order
|
||||
int i;
|
||||
for (i=0; i<4; i++) {
|
||||
spi_flash_write_byte(addr + i, data.uint8[i]);
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Erase sector (64kB) at base address.
|
||||
*
|
||||
* @param[in] addr Base address of sector to erase.
|
||||
* @return 0 if success, !=0 if error
|
||||
**************************************************************************/
|
||||
int spi_flash_erase_sector(uint32_t addr) {
|
||||
|
||||
#if (SPI_EN != 0)
|
||||
spi_flash_cmd(SPI_FLASH_CMD_WRITE_ENABLE); // allow write-access
|
||||
|
||||
neorv32_spi_cs_en(SPI_FLASH_CS);
|
||||
|
||||
neorv32_spi_transfer(SPI_FLASH_CMD_SECTOR_ERASE);
|
||||
spi_flash_send_addr(addr);
|
||||
|
||||
neorv32_spi_cs_dis();
|
||||
|
||||
while(1) {
|
||||
if ((spi_flash_read_status() & (1 << FLASH_SREG_BUSY)) == 0) { // write-in-progress flag cleared?
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
244
sw/bootloader/hal/source/twi_flash.c
Normal file
244
sw/bootloader/hal/source/twi_flash.c
Normal file
|
@ -0,0 +1,244 @@
|
|||
// ================================================================================ //
|
||||
// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 //
|
||||
// Copyright (c) NEORV32 contributors. //
|
||||
// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. //
|
||||
// Licensed under the BSD-3-Clause license, see LICENSE for details. //
|
||||
// SPDX-License-Identifier: BSD-3-Clause //
|
||||
// ================================================================================ //
|
||||
|
||||
/**
|
||||
* @file twi_flash.c
|
||||
* @brief TWI flash driver.
|
||||
*/
|
||||
|
||||
#include <neorv32.h>
|
||||
#include <config.h>
|
||||
#include <twi_flash.h>
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Read 32-bit word from word-aligned TWI flash address.
|
||||
*
|
||||
* @param[in] addr Word-aligned address.
|
||||
* @param[in,out] rdata Pointer for returned data (uint32_t).
|
||||
* @return 0 if success, != 0 if error
|
||||
**************************************************************************/
|
||||
int twi_flash_read_word(uint32_t addr, uint32_t* rdata) {
|
||||
|
||||
#if (TWI_EN != 0)
|
||||
int i;
|
||||
int device_nack = 0;
|
||||
uint8_t transfer;
|
||||
subwords32_t data, address;
|
||||
|
||||
// TWI module available?
|
||||
if (neorv32_twi_available() == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// start condition
|
||||
neorv32_twi_generate_start();
|
||||
|
||||
// send device address
|
||||
transfer = TWI_DEVICE_ID | 0; // TWI WRITE
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
|
||||
// send access address
|
||||
address.uint32 = addr;
|
||||
#if (TWI_FLASH_ADDR_BYTES == 1)
|
||||
transfer = address.uint8[0];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
#elif (TWI_FLASH_ADDR_BYTES == 2)
|
||||
transfer = address.uint8[1];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
transfer = address.uint8[0];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
#elif (TWI_FLASH_ADDR_BYTES == 3)
|
||||
transfer = address.uint8[2];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
transfer = address.uint8[1];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
transfer = address.uint8[0];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
#elif (TWI_FLASH_ADDR_BYTES == 4)
|
||||
transfer = address.uint8[3];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
transfer = address.uint8[2];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
transfer = address.uint8[1];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
transfer = address.uint8[0];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
#else
|
||||
#error "Invalid TWI_FLASH_ADDR_BYTES configuration!"
|
||||
#endif
|
||||
|
||||
// repeated-start condition
|
||||
neorv32_twi_generate_start();
|
||||
|
||||
// send device address
|
||||
transfer = TWI_DEVICE_ID | 1; // TWI READ
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
|
||||
// read four bytes
|
||||
for (i = 0; i < 4; i++) {
|
||||
transfer = 0xFF;
|
||||
if (i == 3) {
|
||||
neorv32_twi_transfer(&transfer, 0); // NACK by host
|
||||
}
|
||||
else {
|
||||
neorv32_twi_transfer(&transfer, 1); // ACK by Host
|
||||
}
|
||||
data.uint8[i] = transfer;
|
||||
}
|
||||
*rdata = data.uint32;
|
||||
|
||||
// send stop condition
|
||||
neorv32_twi_generate_stop();
|
||||
|
||||
// delay next read
|
||||
twi_flash_delay_twi_tick(1000);
|
||||
|
||||
return device_nack;
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Write single byte to TWI flash.
|
||||
*
|
||||
* @param addr TWI flash write address.
|
||||
* @param wdata TWI flash write data.
|
||||
* @param stop Send TWI stop command at end of transmission
|
||||
* @return 0 if success, !=0 if error
|
||||
**************************************************************************/
|
||||
int twi_flash_write_byte(uint32_t addr, uint8_t wdata, int stop) {
|
||||
|
||||
int device_nack = 0;
|
||||
uint8_t transfer;
|
||||
subwords32_t address;
|
||||
|
||||
// start condition
|
||||
neorv32_twi_generate_start();
|
||||
|
||||
// send device address
|
||||
transfer = TWI_DEVICE_ID | 0; // TWI WRITE
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
|
||||
// send read access address
|
||||
address.uint32 = addr;
|
||||
#if (TWI_FLASH_ADDR_BYTES == 1)
|
||||
transfer = address.uint8[0];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
#elif (TWI_FLASH_ADDR_BYTES == 2)
|
||||
transfer = address.uint8[1];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
transfer = address.uint8[0];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
#elif (TWI_FLASH_ADDR_BYTES == 3)
|
||||
transfer = address.uint8[2];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
transfer = address.uint8[1];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
transfer = address.uint8[0];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
#elif (TWI_FLASH_ADDR_BYTES == 4)
|
||||
transfer = address.uint8[3];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
transfer = address.uint8[2];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
transfer = address.uint8[1];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
transfer = address.uint8[0];
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
#else
|
||||
#error "Invalid TWI_FLASH_ADDR_BYTES configuration!"
|
||||
#endif
|
||||
|
||||
// send write data
|
||||
transfer = wdata;
|
||||
device_nack |= neorv32_twi_transfer(&transfer, 0);
|
||||
|
||||
// send stop condition
|
||||
if (stop) {
|
||||
neorv32_twi_generate_stop();
|
||||
|
||||
// delay next send for EEPROM write cycle
|
||||
neorv32_aux_delay_ms(NEORV32_SYSINFO->CLK,5); // t_wr(max) = 5ms
|
||||
}
|
||||
|
||||
|
||||
return device_nack;
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Write word to TWI flash.
|
||||
*
|
||||
* @param addr TWI flash write address.
|
||||
* @param wdata TWI flash write data.
|
||||
* @return 0 if success, !=0 if error
|
||||
**************************************************************************/
|
||||
int twi_flash_write_word(uint32_t addr, uint32_t wdata) {
|
||||
|
||||
#if (TWI_EN != 0)
|
||||
int device_nack = 0;
|
||||
subwords32_t data;
|
||||
|
||||
// TWI module available?
|
||||
if (neorv32_twi_available() == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// write four bytes
|
||||
data.uint32 = wdata;
|
||||
#if(TWI_FLASH_BULK_WRITE_EN != 0)
|
||||
// send data
|
||||
uint8_t transfer = 0;
|
||||
device_nack += twi_flash_write_byte(addr, data.uint8[0], 0); // Start + addr + first byte
|
||||
transfer = data.uint8[1];
|
||||
device_nack += neorv32_twi_transfer(&transfer, 0);
|
||||
transfer = data.uint8[2];
|
||||
device_nack += neorv32_twi_transfer(&transfer, 0);
|
||||
transfer = data.uint8[3];
|
||||
device_nack += neorv32_twi_transfer(&transfer, 0);
|
||||
|
||||
// send stop condition
|
||||
neorv32_twi_generate_stop();
|
||||
|
||||
// delay next send for EEPROM write cycle
|
||||
neorv32_aux_delay_ms(NEORV32_SYSINFO->CLK,5); // t_wr(max) = 5ms
|
||||
#else
|
||||
device_nack += twi_flash_write_byte(addr+0, data.uint8[0], 1);
|
||||
device_nack += twi_flash_write_byte(addr+1, data.uint8[1], 1);
|
||||
device_nack += twi_flash_write_byte(addr+2, data.uint8[2], 1);
|
||||
device_nack += twi_flash_write_byte(addr+3, data.uint8[3], 1);
|
||||
#endif
|
||||
if (device_nack) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Keeps TWI Peripheral in IDLE for 'tick_count' TWI clock ticks
|
||||
*
|
||||
* @param tick_count Amount of TWI NOP ticks to wait
|
||||
*
|
||||
*/
|
||||
void twi_flash_delay_twi_tick(int tick_count){
|
||||
|
||||
for(int i = 0; i < tick_count; i++)
|
||||
{
|
||||
while (NEORV32_TWI->CTRL & (1<<TWI_CTRL_TX_FULL)); // wait for free TX entry
|
||||
NEORV32_TWI->DCMD = (uint32_t)(TWI_CMD_NOP << TWI_DCMD_CMD_LO); // IDLE for 1 twi tick
|
||||
}
|
||||
while (NEORV32_TWI->CTRL & (1 << TWI_CTRL_BUSY)); // wait until FIFO empty
|
||||
}
|
111
sw/bootloader/hal/source/uart.c
Normal file
111
sw/bootloader/hal/source/uart.c
Normal file
|
@ -0,0 +1,111 @@
|
|||
// ================================================================================ //
|
||||
// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 //
|
||||
// Copyright (c) NEORV32 contributors. //
|
||||
// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. //
|
||||
// Licensed under the BSD-3-Clause license, see LICENSE for details. //
|
||||
// SPDX-License-Identifier: BSD-3-Clause //
|
||||
// ================================================================================ //
|
||||
|
||||
/**
|
||||
* @file uart.c
|
||||
* @brief Minimal UART0 driver.
|
||||
*/
|
||||
|
||||
#include <neorv32.h>
|
||||
#include <config.h>
|
||||
#include <uart.h>
|
||||
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Read single char from UART0.
|
||||
*
|
||||
* @return Received char.
|
||||
**************************************************************************/
|
||||
char uart_getc(void) {
|
||||
|
||||
#if (UART_EN != 0)
|
||||
if (neorv32_uart_available(NEORV32_UART0)) {
|
||||
return neorv32_uart_getc(NEORV32_UART0);
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Print single char via UART0.
|
||||
*
|
||||
* @note Converts LF ("\n") to CR+LF ("\r\n").
|
||||
*
|
||||
* @param[in] c Character to print.
|
||||
**************************************************************************/
|
||||
void uart_putc(char c) {
|
||||
|
||||
#if (UART_EN != 0)
|
||||
if (neorv32_uart_available(NEORV32_UART0)) {
|
||||
if (c == '\n') {
|
||||
neorv32_uart_putc(NEORV32_UART0, '\r');
|
||||
}
|
||||
neorv32_uart_putc(NEORV32_UART0, c);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Print zero-terminated string via UART0.
|
||||
*
|
||||
* @param[in] s Pointer to string.
|
||||
**************************************************************************/
|
||||
void uart_puts(const char *s) {
|
||||
|
||||
#if (UART_EN != 0)
|
||||
char c = 0;
|
||||
while ((c = *s++)) {
|
||||
uart_putc(c);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Print 32-bit number as 8-digit hexadecimal value with "0x" suffix via UART0.
|
||||
*
|
||||
* @param[in] num Number to print as hexadecimal.
|
||||
**************************************************************************/
|
||||
void uart_puth(uint32_t num) {
|
||||
|
||||
#if (UART_EN != 0)
|
||||
static const char hex_symbols[16] = "0123456789abcdef";
|
||||
uart_putc('0');
|
||||
uart_putc('x');
|
||||
|
||||
int i;
|
||||
for (i=28; i>=0; i-=4) {
|
||||
uart_putc(hex_symbols[(num >> i) & 0xf]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Read 32-bit binary word from UART0.
|
||||
*
|
||||
* @param[in,out] rdata Pointer for returned data (uint32_t).
|
||||
* @return 0 if success, != 0 if error
|
||||
**************************************************************************/
|
||||
int uart_getw(uint32_t* rdata) {
|
||||
|
||||
#if (UART_EN != 0)
|
||||
int i;
|
||||
subwords32_t tmp;
|
||||
for (i=0; i<4; i++) {
|
||||
tmp.uint8[i] = (uint8_t)uart_getc();
|
||||
}
|
||||
*rdata = tmp.uint32;
|
||||
return 0;
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
569
sw/bootloader/main.c
Normal file
569
sw/bootloader/main.c
Normal file
|
@ -0,0 +1,569 @@
|
|||
// ================================================================================ //
|
||||
// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 //
|
||||
// Copyright (c) NEORV32 contributors. //
|
||||
// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. //
|
||||
// Licensed under the BSD-3-Clause license, see LICENSE for details. //
|
||||
// SPDX-License-Identifier: BSD-3-Clause //
|
||||
// ================================================================================ //
|
||||
|
||||
/**
|
||||
* @file main.c
|
||||
* @brief NEORV32 bootloader.
|
||||
*/
|
||||
|
||||
// libraries
|
||||
#include <stdint.h>
|
||||
#include <neorv32.h>
|
||||
#include <config.h>
|
||||
#include <spi_flash.h>
|
||||
#include <twi_flash.h>
|
||||
#include <uart.h>
|
||||
|
||||
// Executable source select
|
||||
#define EXE_STREAM_UART 0 // Get executable via UART
|
||||
#define EXE_STREAM_SPI 1 // Get executable from SPI flash
|
||||
#define EXE_STREAM_TWI 2 // Get executable from TWI device
|
||||
|
||||
// NEORV32 executable
|
||||
#define EXE_OFFSET_SIGNATURE (0) // Offset in bytes from start to signature (32-bit)
|
||||
#define EXE_OFFSET_SIZE (4) // Offset in bytes from start to size (32-bit)
|
||||
#define EXE_OFFSET_CHECKSUM (8) // Offset in bytes from start to checksum (32-bit)
|
||||
#define EXE_OFFSET_DATA (12) // Offset in bytes from start to data (32-bit)
|
||||
#define EXE_SIGNATURE 0x4788CAFEU // valid executable identifier
|
||||
|
||||
// Helper macros
|
||||
#define xstr(a) str(a)
|
||||
#define str(a) #a
|
||||
|
||||
// Global variables
|
||||
uint32_t exe_available = 0; // size of the loaded executable; 0 if no executable available
|
||||
|
||||
// Function prototypes
|
||||
void __attribute__((interrupt("machine"),aligned(4))) bootloader_trap_handler(void);
|
||||
void print_help(void);
|
||||
void start_app(void);
|
||||
int load_exe(int src);
|
||||
void save_exe(int dst);
|
||||
int get_exe_word(int src, uint32_t addr, uint32_t *rdata);
|
||||
int put_exe_word(int dst, uint32_t addr, uint32_t wdata);
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Bootloader main.
|
||||
**************************************************************************/
|
||||
int main(void) {
|
||||
|
||||
// ------------------------------------------------
|
||||
// Hardware setup
|
||||
// ------------------------------------------------
|
||||
|
||||
// configure trap handler (bare-metal, no neorv32 rte available)
|
||||
neorv32_cpu_csr_write(CSR_MTVEC, (uint32_t)(&bootloader_trap_handler));
|
||||
|
||||
// setup SPI, clock mode 0
|
||||
#if (SPI_EN != 0)
|
||||
if (neorv32_spi_available()) {
|
||||
neorv32_spi_setup(SPI_FLASH_CLK_PRSC, 0, 0, 0, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
// activate status GPIO LED, clear all others
|
||||
#if (STATUS_LED_EN != 0)
|
||||
if (neorv32_gpio_available()) {
|
||||
neorv32_gpio_port_set(1 << STATUS_LED_PIN);
|
||||
}
|
||||
#endif
|
||||
|
||||
// setup UART0
|
||||
#if (UART_EN != 0)
|
||||
if (neorv32_uart0_available()) {
|
||||
neorv32_uart0_setup(UART_BAUD, 0);
|
||||
#if (UART_HW_HANDSHAKE_EN != 0)
|
||||
neorv32_uart0_rtscts_enable();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// setup TWI
|
||||
#if (TWI_EN != 0)
|
||||
if (neorv32_twi_available()) {
|
||||
neorv32_twi_setup(TWI_CLK_PRSC, TWI_CLK_DIV, TWI_CLK_STRECH_EN);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Configure CLINT timer interrupt
|
||||
if (neorv32_clint_available()) {
|
||||
NEORV32_CLINT->MTIME.uint32[0] = 0;
|
||||
NEORV32_CLINT->MTIME.uint32[0] = 0;
|
||||
NEORV32_CLINT->MTIMECMP[0].uint32[0] = NEORV32_SYSINFO->CLK/4;
|
||||
NEORV32_CLINT->MTIMECMP[0].uint32[1] = 0;
|
||||
neorv32_cpu_csr_write(CSR_MIE, 1 << CSR_MIE_MTIE); // activate timer IRQ source
|
||||
neorv32_cpu_csr_set(CSR_MSTATUS, 1 << CSR_MSTATUS_MIE); // enable machine-mode interrupts
|
||||
}
|
||||
|
||||
// ------------------------------------------------
|
||||
// Splash screen
|
||||
// ------------------------------------------------
|
||||
#if (UART_PRINT_SPLASH_EN != 0)
|
||||
uart_puts("\n\n\nNEORV32 Bootloader\n\n"
|
||||
"BLDV: "
|
||||
__DATE__
|
||||
"\nHWV: ");
|
||||
uart_puth(neorv32_cpu_csr_read(CSR_MIMPID));
|
||||
uart_puts("\nCLK: ");
|
||||
uart_puth(NEORV32_SYSINFO->CLK);
|
||||
uart_puts("\nMISA: ");
|
||||
uart_puth(neorv32_cpu_csr_read(CSR_MISA));
|
||||
uart_puts("\nXISA: ");
|
||||
uart_puth(neorv32_cpu_csr_read(CSR_MXISA));
|
||||
uart_puts("\nSOC: ");
|
||||
uart_puth(NEORV32_SYSINFO->SOC);
|
||||
uart_puts("\nIMEM: ");
|
||||
uart_puth((uint32_t)(1 << NEORV32_SYSINFO->MISC[SYSINFO_MISC_IMEM]) & 0xFFFFFFFCU);
|
||||
uart_puts("\nDMEM: ");
|
||||
uart_puth((uint32_t)(1 << NEORV32_SYSINFO->MISC[SYSINFO_MISC_DMEM]) & 0xFFFFFFFCU);
|
||||
uart_puts("\n\n");
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------
|
||||
// Auto boot sequence
|
||||
// ------------------------------------------------
|
||||
|
||||
#if (AUTO_BOOT_EN != 0)
|
||||
uart_puts("Autoboot in "xstr(AUTO_BOOT_TIMEOUT)"s. Press any key to abort.\n");
|
||||
if (neorv32_clint_available()) {
|
||||
uint64_t timeout_time = neorv32_clint_time_get() + (uint64_t)(AUTO_BOOT_TIMEOUT * NEORV32_SYSINFO->CLK);
|
||||
while (1) {
|
||||
|
||||
// wait for user input via UART0
|
||||
if (neorv32_uart0_available()) {
|
||||
if (neorv32_uart0_char_received()) {
|
||||
neorv32_uart0_char_received_get(); // discard received char
|
||||
uart_puts("Aborted.\n\n");
|
||||
goto skip_auto_boot;
|
||||
}
|
||||
}
|
||||
|
||||
// timeout? start auto-boot sequence
|
||||
if (neorv32_clint_time_get() >= timeout_time) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try booting from SPI flash
|
||||
#if (SPI_EN != 0)
|
||||
if (load_exe(EXE_STREAM_SPI) == 0) { start_app(); }
|
||||
#endif
|
||||
|
||||
// try booting from TWI flash
|
||||
#if (TWI_EN != 0)
|
||||
if (load_exe(EXE_STREAM_TWI) == 0) { start_app(); }
|
||||
#endif
|
||||
|
||||
skip_auto_boot:
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------
|
||||
// User console
|
||||
// ------------------------------------------------
|
||||
|
||||
#if (UART_EN != 0)
|
||||
print_help();
|
||||
|
||||
char cmd;
|
||||
while (1) {
|
||||
|
||||
// prompt
|
||||
uart_puts("CMD:> ");
|
||||
cmd = uart_getc();
|
||||
uart_putc(cmd);
|
||||
uart_putc('\n');
|
||||
|
||||
if (cmd == 'r') { // restart bootloader
|
||||
asm volatile ("li t0, %[input_i]; jr t0" : : [input_i] "i" (NEORV32_BOOTROM_BASE)); // jump to beginning of boot ROM
|
||||
__builtin_unreachable();
|
||||
}
|
||||
else if (cmd == 'h') { // help menu
|
||||
print_help();
|
||||
}
|
||||
else if (cmd == 'u') { // get executable via UART
|
||||
load_exe(EXE_STREAM_UART);
|
||||
}
|
||||
#if (SPI_EN != 0)
|
||||
else if (cmd == 's') { // copy memory to SPI flash
|
||||
save_exe(EXE_STREAM_SPI);
|
||||
}
|
||||
else if (cmd == 'l') { // copy executable from SPI flash
|
||||
load_exe(EXE_STREAM_SPI);
|
||||
}
|
||||
#endif
|
||||
#if (TWI_EN != 0)
|
||||
else if (cmd == 'w') { // copy memory to TWI flash
|
||||
save_exe(EXE_STREAM_TWI);
|
||||
}
|
||||
else if (cmd == 't') { // copy executable from TWI flash
|
||||
load_exe(EXE_STREAM_TWI);
|
||||
}
|
||||
#endif
|
||||
else if (cmd == 'e') { // start application program from memory
|
||||
// executable available?
|
||||
if (exe_available == 0) {
|
||||
uart_puts("No executable.\n");
|
||||
uart_puts("Boot anyway (y/n)?\n");
|
||||
if (uart_getc() == 'y') {
|
||||
start_app();
|
||||
}
|
||||
}
|
||||
else {
|
||||
start_app();
|
||||
}
|
||||
}
|
||||
else if (cmd == 'd') { // for debugging only
|
||||
asm volatile ("ebreak");
|
||||
}
|
||||
else { // unknown command
|
||||
uart_puts("Invalid CMD\n");
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
while(1);
|
||||
return -1; // bootloader should never return
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Print help menu.
|
||||
**************************************************************************/
|
||||
void print_help(void) {
|
||||
|
||||
uart_puts(
|
||||
"Available CMDs:\n"
|
||||
" h: Help\n"
|
||||
" r: Restart\n"
|
||||
" u: Upload via UART\n"
|
||||
#if (SPI_EN != 0)
|
||||
" s: Store to SPI flash\n"
|
||||
" l: Load from SPI flash\n"
|
||||
#endif
|
||||
#if (TWI_EN != 0)
|
||||
" w: Store to TWI flash\n"
|
||||
" t: Load from TWI flash\n"
|
||||
#endif
|
||||
" e: Start executable\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Start application program.
|
||||
**************************************************************************/
|
||||
void start_app(void) {
|
||||
|
||||
// deactivate global IRQs
|
||||
neorv32_cpu_csr_clr(CSR_MSTATUS, 1 << CSR_MSTATUS_MIE);
|
||||
|
||||
uint32_t app_base = (uint32_t)EXE_BASE_ADDR;
|
||||
uart_puts("Booting from ");
|
||||
uart_puth(app_base);
|
||||
uart_puts("...\n\n");
|
||||
|
||||
// shut down heart beat LED
|
||||
#if (STATUS_LED_EN != 0)
|
||||
if (neorv32_gpio_available()) {
|
||||
neorv32_gpio_port_set(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
// wait for UART0 to finish transmitting
|
||||
while (neorv32_uart0_tx_busy());
|
||||
|
||||
// start application
|
||||
asm volatile ("csrw mepc, %0; mret" : : "r" (app_base));
|
||||
|
||||
__builtin_unreachable();
|
||||
while (1); // should never be reached
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Bare-metal Bootloader trap handler.
|
||||
* Used for the CLINT timer tick and to capture any other traps.
|
||||
**************************************************************************/
|
||||
void __attribute__((interrupt("machine"),aligned(4))) bootloader_trap_handler(void) {
|
||||
|
||||
uint32_t mcause = neorv32_cpu_csr_read(CSR_MCAUSE);
|
||||
|
||||
// machine timer interrupt
|
||||
if (mcause == TRAP_CODE_MTI) { // raw exception code for MTI
|
||||
#if (STATUS_LED_EN != 0)
|
||||
if (neorv32_gpio_available()) {
|
||||
neorv32_gpio_pin_toggle(STATUS_LED_PIN); // toggle status LED
|
||||
}
|
||||
#endif
|
||||
// set time for next IRQ
|
||||
if (neorv32_clint_available()) {
|
||||
neorv32_clint_mtimecmp_set(neorv32_clint_time_get() + (NEORV32_SYSINFO->CLK/4));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// unexpected trap
|
||||
#if (UART_EN != 0)
|
||||
if (neorv32_uart0_available()) {
|
||||
uart_puts("\a\nERROR_EXCEPTION ");
|
||||
uart_puth(mcause);
|
||||
uart_putc(' ');
|
||||
uart_puth(neorv32_cpu_csr_read(CSR_MEPC));
|
||||
uart_putc(' ');
|
||||
uart_puth(neorv32_cpu_csr_read(CSR_MTINST));
|
||||
uart_putc(' ');
|
||||
uart_puth(neorv32_cpu_csr_read(CSR_MTVAL));
|
||||
uart_putc('\n');
|
||||
}
|
||||
#endif
|
||||
|
||||
// deactivate IRQs
|
||||
neorv32_cpu_csr_clr(CSR_MSTATUS, 1 << CSR_MSTATUS_MIE);
|
||||
|
||||
// permanently light up status LED
|
||||
#if (STATUS_LED_EN != 0)
|
||||
if (neorv32_gpio_available()) {
|
||||
neorv32_gpio_port_set(1 << STATUS_LED_PIN);
|
||||
}
|
||||
#endif
|
||||
|
||||
// endless sleep mode
|
||||
while(1) {
|
||||
asm volatile("wfi");
|
||||
}
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Get executable stream.
|
||||
*
|
||||
* @param src Source of executable stream data. See #EXE_STREAM_SOURCE_enum.
|
||||
* @return 0 if success, != 0 if error.
|
||||
**************************************************************************/
|
||||
int load_exe(int src) {
|
||||
|
||||
int rc = 0;
|
||||
uint32_t src_addr = 0;
|
||||
|
||||
// no executable available yet
|
||||
exe_available = 0;
|
||||
|
||||
// get image from UART?
|
||||
#if (UART_EN != 0)
|
||||
if (src == EXE_STREAM_UART) {
|
||||
uart_puts("Awaiting neorv32_exe.bin... ");
|
||||
}
|
||||
#endif
|
||||
|
||||
// get image from SPI flash?
|
||||
#if (SPI_EN != 0)
|
||||
if (src == EXE_STREAM_SPI) {
|
||||
src_addr = SPI_FLASH_BASE_ADDR;
|
||||
uart_puts("Loading from SPI flash @");
|
||||
uart_puth(src_addr);
|
||||
uart_puts("... ");
|
||||
rc |= spi_flash_check();
|
||||
}
|
||||
#endif
|
||||
|
||||
// get image from TWI flash?
|
||||
#if (TWI_EN)
|
||||
if (src == EXE_STREAM_TWI) {
|
||||
src_addr = TWI_FLASH_BASE_ADDR;
|
||||
uart_puts("Loading from TWI flash ");
|
||||
uart_puth(TWI_DEVICE_ID);
|
||||
uart_puts(" @");
|
||||
uart_puth(src_addr);
|
||||
uart_puts("... ");
|
||||
}
|
||||
#endif
|
||||
|
||||
// get image header
|
||||
uint32_t exe_sign, exe_size, exe_check;
|
||||
rc |= get_exe_word(src, src_addr + EXE_OFFSET_SIGNATURE, &exe_sign);
|
||||
rc |= get_exe_word(src, src_addr + EXE_OFFSET_SIZE, &exe_size);
|
||||
rc |= get_exe_word(src, src_addr + EXE_OFFSET_CHECKSUM, &exe_check);
|
||||
|
||||
// checks
|
||||
if (rc) {
|
||||
uart_puts("ERROR_DEVICE\n");
|
||||
return 1;
|
||||
}
|
||||
if (exe_sign != EXE_SIGNATURE) {
|
||||
uart_puts("ERROR_SIGNATURE\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// transfer executable
|
||||
uint32_t *pnt = (uint32_t*)EXE_BASE_ADDR;
|
||||
uint32_t checksum = 0, tmp = 0, i = 0;
|
||||
src_addr = src_addr + EXE_OFFSET_DATA;
|
||||
while (i < (exe_size/4)) { // in words
|
||||
if (get_exe_word(src, src_addr, &tmp)) {
|
||||
rc |= 1;
|
||||
break;
|
||||
}
|
||||
checksum += tmp;
|
||||
pnt[i++] = tmp;
|
||||
src_addr += 4;
|
||||
}
|
||||
|
||||
// checks
|
||||
if (rc) {
|
||||
uart_puts("ERROR_DEVICE\n");
|
||||
return 1;
|
||||
}
|
||||
if ((checksum + exe_check) != 0) {
|
||||
uart_puts("ERROR_CHECKSUM\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uart_puts("OK\n");
|
||||
exe_available = exe_size;
|
||||
|
||||
// we might have caches so the executable might not yet have fully arrived in main memory yet
|
||||
asm volatile ("fence"); // flush data caches to main memory
|
||||
asm volatile ("fence.i"); // re-sync instruction fetch to updated main memory
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Copy memory content as executable to flash.
|
||||
*
|
||||
* @param dst Destination of executable. See #EXE_STREAM_SOURCE_enum.
|
||||
**************************************************************************/
|
||||
void save_exe(int dst) {
|
||||
|
||||
int rc = 0;
|
||||
uint32_t dst_addr = 0;
|
||||
|
||||
// size of last uploaded executable
|
||||
uint32_t size = exe_available;
|
||||
if (size == 0) {
|
||||
uart_puts("No executable.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// info prompt and flash address setup
|
||||
uart_puts("Write ");
|
||||
uart_puth(size);
|
||||
uart_puts(" bytes to ");
|
||||
if (dst == EXE_STREAM_SPI) {
|
||||
uart_puts("SPI");
|
||||
dst_addr = (uint32_t)SPI_FLASH_BASE_ADDR;
|
||||
}
|
||||
else {
|
||||
uart_puts("TWI "xstr(TWI_DEVICE_ID)"");
|
||||
dst_addr = (uint32_t)TWI_FLASH_BASE_ADDR;
|
||||
}
|
||||
uart_puts(" flash @");
|
||||
uart_puth(dst_addr);
|
||||
uart_puts(" (y/n)?\n");
|
||||
if (uart_getc() != 'y') {
|
||||
return;
|
||||
}
|
||||
|
||||
uart_puts("Flashing... ");
|
||||
|
||||
// prepare SPI flash
|
||||
if (dst == EXE_STREAM_SPI) {
|
||||
if (spi_flash_check()) { // SPI and flash OK?
|
||||
uart_puts("ERROR_DEVICE\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// clear memory before writing
|
||||
uint32_t num_sectors = (size / (SPI_FLASH_SECTOR_SIZE)) + 1; // clear at least 1 sector
|
||||
uint32_t sector_base_addr = dst_addr;
|
||||
while (num_sectors--) {
|
||||
rc |= spi_flash_erase_sector(sector_base_addr);
|
||||
sector_base_addr += SPI_FLASH_SECTOR_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// transfer executable
|
||||
uint32_t checksum = 0, tmp = 0, i = 0;
|
||||
uint32_t pnt = (uint32_t)EXE_BASE_ADDR;
|
||||
uint32_t addr = dst_addr + EXE_OFFSET_DATA;
|
||||
while (i < size) { // in chunks of 4 bytes
|
||||
tmp = neorv32_cpu_load_unsigned_word(pnt);
|
||||
pnt += 4;
|
||||
checksum += tmp;
|
||||
if (put_exe_word(dst, addr, tmp)) {
|
||||
rc |= 1;
|
||||
break;
|
||||
}
|
||||
addr += 4;
|
||||
i += 4;
|
||||
}
|
||||
|
||||
// write header
|
||||
rc |= put_exe_word(dst, dst_addr + EXE_OFFSET_SIGNATURE, EXE_SIGNATURE);
|
||||
rc |= put_exe_word(dst, dst_addr + EXE_OFFSET_SIZE, size);
|
||||
rc |= put_exe_word(dst, dst_addr + EXE_OFFSET_CHECKSUM, (~checksum)+1);
|
||||
|
||||
// checks
|
||||
if (rc) {
|
||||
uart_puts("ERROR_DEVICE\n");
|
||||
}
|
||||
else {
|
||||
uart_puts("OK\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Get word from executable stream.
|
||||
*
|
||||
* @param src Source of executable stream data. See #EXE_STREAM_SOURCE_enum.
|
||||
* @param addr Address when accessing SPI flash or TWI Device.
|
||||
* @param[in,out] rdata Pointer for returned data (uint32_t).
|
||||
* @return 0 if success, != 0 if error.
|
||||
**************************************************************************/
|
||||
int get_exe_word(int src, uint32_t addr, uint32_t *rdata) {
|
||||
|
||||
if (src == EXE_STREAM_UART) {
|
||||
return uart_getw(rdata);
|
||||
}
|
||||
else if (src == EXE_STREAM_SPI) {
|
||||
return spi_flash_read_word(addr, rdata);
|
||||
}
|
||||
else if (src == EXE_STREAM_TWI) {
|
||||
return twi_flash_read_word(addr, rdata);
|
||||
}
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Put word to executable stream.
|
||||
*
|
||||
* @param dst Source of executable stream data. See #EXE_STREAM_SOURCE_enum.
|
||||
* @param addr Address when accessing SPI flash or TWI Device.
|
||||
* @param[in] wdata Write data word (uint32_t).
|
||||
* @return 0 if success, != 0 if error.
|
||||
**************************************************************************/
|
||||
int put_exe_word(int dst, uint32_t addr, uint32_t wdata) {
|
||||
|
||||
if (dst == EXE_STREAM_SPI) {
|
||||
return spi_flash_write_word(addr, wdata);
|
||||
}
|
||||
else if (dst == EXE_STREAM_TWI) {
|
||||
return twi_flash_write_word(addr, wdata);
|
||||
}
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
|
@ -7,10 +7,13 @@ MABI = ilp32e
|
|||
# Optimize for minimal size
|
||||
EFFORT = -Os
|
||||
|
||||
# Hardware abstraction layer
|
||||
APP_SRC += $(wildcard ./*.c) $(wildcard ./hal/source/*.c)
|
||||
APP_INC += -I . -I ./hal/include
|
||||
|
||||
# Adjust "rom" memory size and base for BOOTROM
|
||||
# Just use a minimal "ram" size that should be available on any platform configuration.
|
||||
# Define MAKE_BOOTLOADER for SW library (reduces footprint)
|
||||
# Enable link-time-optimization
|
||||
# Just use a minimal "ram" size that should be available on any platform configuration
|
||||
# Define MAKE_BOOTLOADER for SW library optimizations (reduces footprint) and enable link-time-optimization
|
||||
USER_FLAGS += \
|
||||
-Wl,--defsym,__neorv32_rom_size=4k \
|
||||
-Wl,--defsym,__neorv32_rom_base=0xFFE00000 \
|
||||
|
@ -18,33 +21,6 @@ USER_FLAGS += \
|
|||
-DMAKE_BOOTLOADER \
|
||||
-flto
|
||||
|
||||
# Set which bootloader features to compile and associated settings.
|
||||
# Warning: Enabling them all while sticking to the minimal RISC-V ISA will result in a too-large binary!
|
||||
|
||||
#USER_FLAGS += -DUART_EN=1
|
||||
#USER_FLAGS += -DUART_BAUD=19200
|
||||
#USER_FLAGS += -DUART_HW_HANDSHAKE_EN=0
|
||||
|
||||
#USER_FLAGS += -DSTATUS_LED_EN=1
|
||||
#USER_FLAGS += -DSTATUS_LED_PIN=0
|
||||
|
||||
#USER_FLAGS += -DAUTO_BOOT_TIMEOUT=10
|
||||
|
||||
#USER_FLAGS += -DSPI_EN=0
|
||||
#USER_FLAGS += -DSPI_FLASH_CS=0
|
||||
#USER_FLAGS += -DSPI_FLASH_ADDR_BYTES=3
|
||||
#USER_FLAGS += -DSPI_FLASH_SECTOR_SIZE=65536
|
||||
#USER_FLAGS += -DSPI_FLASH_CLK_PRSC=CLK_PRSC_8
|
||||
#USER_FLAGS += -DSPI_BOOT_BASE_ADDR=0x00400000UL
|
||||
|
||||
#USER_FLAGS += -DTWI_EN=1
|
||||
#USER_FLAGS += -DTWI_CLK_PRSC=CLK_PRSC_64
|
||||
#USER_FLAGS += -DTWI_CLK_DIV=3
|
||||
#USER_FLAGS += -DTWI_DEVICE_ID=0x50
|
||||
#USER_FLAGS += -DTWI_ADDR_BYTES=1
|
||||
|
||||
# Set path to NEORV32 root directory
|
||||
NEORV32_HOME ?= ../..
|
||||
|
||||
# Include the main NEORV32 makefile
|
||||
NEORV32_HOME ?= ../..
|
||||
include $(NEORV32_HOME)/sw/common/common.mk
|
||||
|
|
Loading…
Add table
Reference in a new issue