Line 2... |
Line 2... |
|
|
To make your NEORV32 project run, follow the guides from the upcoming sections. Follow these guides
|
To make your NEORV32 project run, follow the guides from the upcoming sections. Follow these guides
|
step by step and in the presented order.
|
step by step and in the presented order.
|
|
|
:sectnums:
|
:sectnums:
|
== Toolchain Setup
|
== Software Toolchain Setup
|
|
|
There are two possibilities to get the actual RISC-V GCC toolchain:
|
To compile (and debug) executables for the NEORV32 a RISC-V toolchain is required.
|
|
There are two possibilities to get this:
|
|
|
1. Download and _build_ the official RISC-V GNU toolchain yourself
|
1. Download and _build_ the official RISC-V GNU toolchain yourself
|
2. Download and install a prebuilt version of the toolchain
|
2. Download and install a prebuilt version of the toolchain; this might also done via the package manager / app store of your OS
|
|
|
[NOTE]
|
[TIP]
|
The default toolchain prefix for this project is **`riscv32-unknown-elf`**. Of course you can use any other RISC-V
|
The default toolchain prefix for this project is **`riscv32-unknown-elf`**. Of course you can use any other RISC-V
|
toolchain (like `riscv64-unknown-elf`) that is capable to emit code for a `rv32` architecture. Just change the _RISCV_TOOLCHAIN_ variable in the application
|
toolchain (like `riscv64-unknown-elf`) that is capable to emit code for a `rv32` architecture. Just change the _RISCV_TOOLCHAIN_ variable in the application
|
makefile(s) according to your needs or define this variable when invoking the makefile.
|
makefile(s) according to your needs or define this variable when invoking the makefile.
|
|
|
[IMPORTANT]
|
[IMPORTANT]
|
Line 23... |
Line 24... |
|
|
|
|
:sectnums:
|
:sectnums:
|
=== Building the Toolchain from Scratch
|
=== Building the Toolchain from Scratch
|
|
|
To build the toolchain by yourself you can follow the guide from the official https://github.com/riscv/riscvgnu-toolchain GitHub page.
|
To build the toolchain by yourself you can follow the guide from the official https://github.com/riscv/riscv-gnu-toolchain GitHub page.
|
|
You need to make sure the generated toolchain fits the architecture of the NEORV32 core. To get a toolchain that even supports minimal
|
The official RISC-V repository uses submodules. You need the `--recursive` option to fetch the submodules
|
ISA extension configurations, it is recommend to compile for `rv32i` only. Please note that this minimal ISA also provides further ISA
|
automatically:
|
extensions like `m` or `c`. Of course you can use a `multilib` approach to generate
|
|
toolchains for several target ISAs.
|
[source,bash]
|
|
----
|
|
$ git clone --recursive https://github.com/riscv/riscv-gnu-toolchain
|
|
----
|
|
|
|
Download and install the prerequisite standard packages:
|
|
|
|
[source,bash]
|
|
----
|
|
$ sudo apt-get install autoconf automake autotools-dev curl python3 libmpc-dev libmpfrdev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev
|
|
----
|
|
|
|
To build the Linux cross-compiler, pick an install path. If you choose, say, `/opt/riscv`, then add
|
|
`/opt/riscv/bin` to your `PATH` variable.
|
|
|
|
[source,bash]
|
|
----
|
|
$ export PATH=$PATH:/opt/riscv/bin
|
|
----
|
|
|
|
Then, simply run the following commands and configuration in the RISC-V GNU toolchain source folder to compile a
|
|
`rv32i` toolchain:
|
|
|
|
|
.Configuring GCC build for `rv32i` (minimal ISA)
|
[source,bash]
|
[source,bash]
|
----
|
----
|
riscv-gnu-toolchain$ ./configure --prefix=/opt/riscv --with-arch=rv32i –-with-abi=ilp32
|
riscv-gnu-toolchain$ ./configure --prefix=/opt/riscv --with-arch=rv32i –-with-abi=ilp32
|
riscv-gnu-toolchain$ make
|
riscv-gnu-toolchain$ make
|
----
|
----
|
|
|
After a while you will get `riscv32-unknown-elf-gcc` and all of its friends in your `/opt/riscv/bin` folder.
|
|
|
|
|
|
:sectnums:
|
:sectnums:
|
=== Downloading and Installing a Prebuilt Toolchain
|
=== Downloading and Installing a Prebuilt Toolchain
|
|
|
Alternatively, you can download a prebuilt toolchain.
|
Alternatively, you can download a prebuilt toolchain.
|
|
|
**Use The Toolchain I have Build**
|
:sectnums:
|
|
==== Use The Toolchain I have Build
|
|
|
I have compiled the toolchain on a 64-bit x86 Ubuntu (Ubuntu on Windows, actually) and uploaded it to
|
I have compiled a GCC toolchain on a 64-bit x86 Ubuntu (Ubuntu on Windows, actually) and uploaded it to
|
GitHub. You can directly download the according toolchain archive as single _zip-file_ within a packed
|
GitHub. You can directly download the according toolchain archive as single _zip-file_ within a packed
|
release from github.com/stnolting/riscv-gcc-prebuilt.
|
release from https://github.com/stnolting/riscv-gcc-prebuilt.
|
|
|
Unpack the downloaded toolchain archive and copy the content to a location in your file system (e.g.
|
Unpack the downloaded toolchain archive and copy the content to a location in your file system (e.g.
|
`/opt/riscv`). More information about downloading and installing my prebuilt toolchains can be found in
|
`/opt/riscv`). More information about downloading and installing my prebuilt toolchains can be found in
|
the repository's README.
|
the repository's README.
|
|
|
**Use a Third Party Toolchain**
|
|
|
:sectnums:
|
|
==== Use a Third Party Toolchain
|
|
|
Of course you can also use any other prebuilt version of the toolchain. There are a lot RISC-V GCC packages out there -
|
Of course you can also use any other prebuilt version of the toolchain. There are a lot RISC-V GCC packages out there -
|
even for Windows.
|
even for Windows. On Linux system you might even be able to fetch a toolchain via your distribution's package manager.
|
|
|
[IMPORTANT]
|
[IMPORTANT]
|
Make sure the toolchain can (also) emit code for a `rv32i` architecture, uses the `ilp32` or `ilp32e` ABI and **was not build** using
|
Make sure the toolchain can (also) emit code for a `rv32i` architecture, uses the `ilp32` or `ilp32e` ABI and **was not build** using
|
CPU extensions that are not supported by the NEORV32 (like `D`).
|
CPU extensions that are not supported by the NEORV32 (like `D`).
|
|
|
|
|
:sectnums:
|
:sectnums:
|
=== Installation
|
=== Installation
|
|
|
Now you have the binaries. The last step is to add them to your `PATH` environment variable (if you have not
|
Now you have the toolchain binaries. The last step is to add them to your `PATH` environment variable (if you have not
|
already done so). Make sure to add the binaries folder (`bin`) of your toolchain.
|
already done so): make sure to add the _binaries_ folder (`bin`) of your toolchain.
|
|
|
[source,bash]
|
[source,bash]
|
----
|
----
|
$ export PATH:$PATH:/opt/riscv/bin
|
$ export PATH:$PATH:/opt/riscv/bin
|
----
|
----
|
Line 110... |
Line 91... |
[source,bash]
|
[source,bash]
|
----
|
----
|
neorv32/sw/example/blink_led$ make check
|
neorv32/sw/example/blink_led$ make check
|
----
|
----
|
|
|
This will test all the tools required for the NEORV32. Everything is working fine if "Toolchain check OK" appears at the end.
|
This will test all the tools required for the generating NEORV32 executables.
|
|
Everything is working fine if `Toolchain check OK` appears at the end.
|
|
|
|
|
|
|
|
|
// ####################################################################################################################
|
// ####################################################################################################################
|
:sectnums:
|
:sectnums:
|
== General Hardware Setup
|
== General Hardware Setup
|
|
|
The following steps are required to generate a bitstream for your FPGA board. If you want to run the
|
This guide will setup a NEORV32 project for FPGA implementation (or simulation only) _from scratch_
|
NEORV32 processor in simulation only, the following steps might also apply.
|
|
|
|
[TIP]
|
[TIP]
|
Check out the example setups in the `boards` folder (@GitHub: https://github.com/stnolting/neorv32/tree/master/boards), which provides script-based
|
If you want to use a complete pre-defined setup to start with, check out the
|
demo projects for various FPGA boars.
|
project's `setups` folder (https://github.com/stnolting/neorv32/tree/master/setups),
|
|
which provides (script-based) demo setups for various FPGA boards and toolchains.
|
In this tutorial we will use a test implementation of the processor – using many of the processor's optional
|
|
modules but just propagating the minimal signals to the outer world. Hence, this guide is intended as
|
This tutorial uses a _simplified_ test setup of the processor
|
evaluation or "hello world" project to check out the NEORV32. A little note: The order of the following
|
to keeps things simple at the beginning as this setup is intended as
|
steps might be a little different for your specific EDA tool.
|
evaluation or "hello world" project to check out the NEORV32.
|
|
|
[start=0]
|
[start=1]
|
. Create a new project with your FPGA EDA tool of choice.
|
. Create a new project with your FPGA EDA tool of choice.
|
. Add all VHDL files from the project's `rtl/core` folder to your project. Make sure to _reference_ the
|
. Add all VHDL files from the project's `rtl/core` folder to your project. Make sure to _reference_ the
|
files only – do not copy them.
|
files only – do not copy them.
|
. Make sure to add all the rtl files to a new library called **`neorv32`**. If your FPGA tools does not
|
. Make sure to add all the rtl files to a new library called `neorv32`. If your FPGA tools does not
|
provide a field to enter the library name, check out the "properties" menu of the rtl files.
|
provide a field to enter the library name, check out the "properties" menu of the added rtl files.
|
. The `rtl/core/neorv32_top.vhd` VHDL file is the top entity of the NEORV32 processor. If you
|
. The `rtl/core/neorv32_top.vhd` VHDL file is the top entity of the NEORV32 processor. If you
|
already have a design, instantiate this unit into your design and proceed.
|
already have a design, instantiate this unit into your design and proceed.
|
. If you do not have a design yet and just want to check out the NEORV32 – no problem! In this guide
|
|
we will use a simplified top entity, that encapsulated the actual processor top entity: add the
|
[IMPORTANT]
|
`rtl/core/top_templates/neorv32_test_setup.vhd` VHDL file to your project too, and
|
Make sure to include the `neorv32` package into your design when instantiating the processor: add
|
select it as top entity.
|
`library neorv32;` and `use neorv32.neorv32_package.all;` to your design unit.
|
|
|
|
[start=5]
|
|
. If you do not have a design yet and just want to check out the NEORV32 – no problem! This guide
|
|
uses a simplified top entity, that encapsulates the actual processor top entity: add the
|
|
`rtl/templates/processor/neorv32_ProcessorTop_Test.vhd` VHDL file to your project, too, and
|
|
select it as _top entity_.
|
. This test setup provides a minimal test hardware setup:
|
. This test setup provides a minimal test hardware setup:
|
|
|
.NEORV32 "hello world" test setup
|
.NEORV32 "hello world" test setup
|
image::neorv32_test_setup.png[align=center]
|
image::neorv32_test_setup.png[align=center]
|
|
|
[start=7]
|
[start=7]
|
. This test setup only implements some very basic processor and CPU features. Also, only the
|
. It only implements some very basic processor and CPU features. Also, only the
|
minimum number of signals is propagated to the outer world. Please note that the reset input signal
|
minimum number of signals is propagated to the outer world.
|
`rstn_i` is **low-active**.
|
. However, a minimal setup-specific configuration of the NEORV32 processor is required to make it run
|
. The configuration of the NEORV32 processor is done using the generics of the instantiated processor
|
on your FPGA board of choice. Only the absolutely required modifications will be made while
|
top entity. Let's keep things simple at first and use the default configuration:
|
keeping the default configuration for the remaining configuration options:
|
|
|
.Cut-out of `neorv32_test_setup.vhd` showing the processor instance and its configuration
|
.Cut-out of `neorv32_ProcessorTop_Test.vhd` showing the processor instance and its configuration
|
[source,vhdl]
|
[source,vhdl]
|
----
|
----
|
neorv32_top_inst: neorv32_top
|
neorv32_top_inst: neorv32_top
|
generic map (
|
generic map (
|
-- General --
|
-- General --
|
CLOCK_FREQUENCY => 100000000, -- in Hz # <1>
|
CLOCK_FREQUENCY => 100000000, -- in Hz # <1>
|
BOOTLOADER_EN => true,
|
INT_BOOTLOADER_EN => true,
|
USER_CODE => x"00000000",
|
|
...
|
...
|
-- Internal instruction memory --
|
-- Internal instruction memory --
|
MEM_INT_IMEM_EN => true,
|
MEM_INT_IMEM_EN => true,
|
MEM_INT_IMEM_SIZE => 16*1024, # <2>
|
MEM_INT_IMEM_SIZE => 16*1024, # <2>
|
MEM_INT_IMEM_ROM => false,
|
|
-- Internal data memory --
|
-- Internal data memory --
|
MEM_INT_DMEM_EN => true,
|
MEM_INT_DMEM_EN => true,
|
MEM_INT_DMEM_SIZE => 8*1024, # <3>
|
MEM_INT_DMEM_SIZE => 8*1024, # <3>
|
...
|
...
|
----
|
----
|
<1> Clock frequency of `clk_i` in Hertz
|
<1> Clock frequency of `clk_i` signal in Hertz
|
<2> Default size of internal instruction memory: 16kB (no need to change that _now_)
|
<2> Default size of internal instruction memory: 16kB
|
<3> Default size of internal data memory: 8kB (no need to change that _now_)
|
<3> Default size of internal data memory: 8kB
|
|
|
[start=9]
|
[start=9]
|
. There is one generic that has to be set according to your FPGA / board: The clock frequency of the
|
. There is one generic that has to be set according to your FPGA board setup: the actual clock frequency
|
top's clock input signal (`clk_i`). Use the _CLOCK_FREQUENC_Y generic to specify your clock source's
|
of the top's clock input signal (`clk_i`). Use the _CLOCK_FREQUENC_Y generic to specify your clock source's
|
frequency in Hertz (Hz) (note "1").
|
frequency in Hertz (Hz) (note "1").
|
. If you feel like it – or if your FPGA does not provide so many resources – you can modify the
|
. If you feel like it – or if your FPGA does not provide many resources – you can modify the
|
**memory sizes** (_MEM_INT_IMEM_SIZE_ and _MEM_INT_DMEM_SIZE_ – marked with notes "2" and "3") or even
|
**memory sizes** (_MEM_INT_IMEM_SIZE_ and _MEM_INT_DMEM_SIZE_ – marked with notes "2" and "3") or even
|
exclude certain ISa extensions and peripheral modules from implementation - but as mentioned above, let's keep things
|
exclude certain ISA extensions and peripheral modules from implementation - but as mentioned above, let's keep things
|
simple at first and use the standard configuration for now.
|
simple at first and use the standard configuration for now.
|
|
|
[NOTE]
|
[NOTE]
|
Keep the internal instruction and data memory sizes in mind – these values are required for setting
|
If you have changed the default memory configuration (_MEM_INT_IMEM_SIZE_ and _MEM_INT_DMEM_SIZE_ generics)
|
|
keep those new sizes in mind – these values are required for setting
|
up the software framework in the next section <<_general_software_framework_setup>>.
|
up the software framework in the next section <<_general_software_framework_setup>>.
|
|
|
[start=11]
|
[start=11]
|
. Depending on your FPGA tool of choice, it is time to assign the signals of the test setup top entity to
|
. Depending on your FPGA tool of choice, it is time to assign the signals of the test setup top entity to
|
the according pins of your FPGA board. All the signals can be found in the entity declaration:
|
the according pins of your FPGA board. All the signals can be found in the entity declaration:
|
Line 217... |
Line 203... |
[start=12]
|
[start=12]
|
. Attach the clock input `clk_i` to your clock source and connect the reset line `rstn_i` to a button of
|
. Attach the clock input `clk_i` to your clock source and connect the reset line `rstn_i` to a button of
|
your FPGA board. Check whether it is low-active or high-active – the reset signal of the processor is
|
your FPGA board. Check whether it is low-active or high-active – the reset signal of the processor is
|
**low-active**, so maybe you need to invert the input signal.
|
**low-active**, so maybe you need to invert the input signal.
|
. If possible, connected at least bit `0` of the GPIO output port `gpio_o` to a high-active LED (invert
|
. If possible, connected at least bit `0` of the GPIO output port `gpio_o` to a high-active LED (invert
|
the signal when your LEDs are low-active) - this LED will be used as status LED by the bootloader.
|
the signal when your LEDs are low-active). This LED will be used as status LED for the setup.
|
. Finally, connect the primary UART's (UART0) communication signals `uart0_txd_o` and
|
. Finally, if your FPGA board provides a serial host interface (USB-to-serial converter) interface,
|
`uart0_rxd_i` to your serial host interface (USB-to-serial converter).
|
connect the UART communication signals `uart0_txd_o` and `uart0_rxd_i`.
|
. Perform the project HDL compilation (synthesis, mapping, bitstream generation).
|
. Perform the project HDL compilation (synthesis, mapping, bitstream generation).
|
. Download the generated bitstream into your FPGA ("program" it) and press the reset button (just to
|
. Program the generated bitstream into your FPGA and press the button connected to the reset signal.
|
make sure everything is sync).
|
. Done! The assigned status LED should be flashing now for some sections before permanently lighting up.
|
. Done! If you have assigned the bootloader status LED , it should be
|
|
flashing now and you should receive the bootloader start prompt in your UART console (check the baudrate!).
|
|
|
|
|
|
|
|
|
|
// ####################################################################################################################
|
// ####################################################################################################################
|
:sectnums:
|
:sectnums:
|
== General Software Framework Setup
|
== General Software Framework Setup
|
|
|
While your synthesis tool is crunching the NEORV32 HDL files, it is time to configure the project's software
|
To allow executables to be _actually executed_ on the NEORV32 Processor the configuration of the software framework
|
framework for your processor hardware setup.
|
has to be aware to the hardware configuration. This guide focuses on the memory configuration. To enabled
|
|
certain CPU ISA festures refer to the <<_enabling_risc_v_cpu_extensions>> section.
|
|
|
|
[TIP]
|
|
If you have **not** changed the _default_ memory configuration in section <<_general_hardware_setup>>
|
|
you are already done and you can skip the rest of this guide.
|
|
|
[start=1]
|
[start=1]
|
. You need to tell the linker the actual size of the processor's instruction and data memories. This has to be always sync
|
|
to the *hardware memory configuration* (done in section <<_general_hardware_setup>>).
|
|
. Open the NEORV32 linker script `sw/common/neorv32.ld` with a text editor. Right at the
|
. Open the NEORV32 linker script `sw/common/neorv32.ld` with a text editor. Right at the
|
beginning of the linker script you will find the **MEMORY** configuration showing two regions: `rom` and `ram`
|
beginning of this script you will find the `MEMORY` configuration listing the different memory section:
|
|
|
.Cut-out of the linker script `neorv32.ld`: Memory configuration
|
.Cut-out of the linker script `neorv32.ld`: `ram` memory section configuration
|
[source,c]
|
[source,c]
|
----
|
----
|
MEMORY
|
MEMORY
|
{
|
{
|
rom (rx) : ORIGIN = DEFINED(make_bootloader) ? 0xFFFF0000 : 0x00000000, LENGTH = DEFINED(make_bootloader) ? 4*1024 : 16*1024 # <1>
|
ram (rwx) : ORIGIN = 0x80000000, LENGTH = DEFINED(make_bootloader) ? 512 : 8*1024 # <1>
|
ram (rwx) : ORIGIN = 0x80000000, LENGTH = 8*1024 # <2>
|
...
|
}
|
|
----
|
----
|
<1> Size of internal instruction memory (IMEM): 16kB
|
<1> Size of the data memory address space (right-most value) (internal/external DMEM); here 8kB
|
<2> Size of internal data memory (DMEM): 8kB
|
|
|
|
[WARNING]
|
[start=2]
|
The `rom` region provides conditional assignments (via the _make_bootloader_ symbol) for the _origin_
|
. We only need to change the `ram` section, which presents the available data address space.
|
and the _length_ configuration depending on whether the executable is built as normal application (for the IMEM) or
|
If you have changed the DMEM (_MEM_INT_DMEM_SIZE_ generic) size adapt the `LENGTH` parameter of the `ram`
|
as bootloader code (for the BOOTROM). To modify the IMEM configuration of the `rom` region,
|
section (here: `8*1024`) so it is equal to your DMEM hardware configuration.
|
make sure to **only edit the most right values** for `ORIGIN` and `LENGTH` (marked with notes "1" and "2").
|
|
|
|
[start=3]
|
|
. There are four parameters that are relevant here (only the right-most value for the `rom` section): The _origin_
|
|
and the _length_ of the instruction memory (region name `rom`) and the _origin_ and the _length_ of the data
|
|
memory (region name `ram`). These four parameters have to be always sync to your hardware memory
|
|
configuration as described in section <<_general_hardware_setup>>.
|
|
|
|
[IMPORTANT]
|
[IMPORTANT]
|
The `rom` _ORIGIN_ parameter has to be equal to the configuration of the NEORV32 ispace_base_c
|
Make sure you only modify the _right-most_ value (here: 8*1024)! +
|
(default: 0x00000000) VHDL package (`rtl/core/neorv32_package.vhd`) configuration constant. The `ram` _ORIGIN_ parameter has to
|
The "`512`" are not relevant for the application.
|
be equal to the configuration of the NEORV32 `dspace_base_c` (default: 0x80000000) VHDL
|
|
package (`rtl/core/neorv32_package.vhd`) configuration constant.
|
[start=3]
|
|
. Done! Save your changes and close the linker script.
|
|
|
|
.Advanced: Section base address and size
|
[IMPORTANT]
|
[IMPORTANT]
|
The `rom` _LENGTH_ and the `ram` _LENGTH_ parameters have to match the configured memory sizes. For
|
More information can be found in the datasheet section https://stnolting.github.io/neorv32/#_address_space[Address Space].
|
instance, if the system does not have any external memories connected, the `rom` _LENGTH_ parameter
|
|
has to be equal to the processor-internal IMEM size (defined via top's _MEM_INT_IMEM_SIZE_ generic)
|
|
and the `ram` _LENGTH_ parameter has to be equal to the processor-internal DMEM size (defined via top's
|
|
_MEM_INT_DMEM_SIZE_ generic).
|
|
|
|
|
|
|
|
|
|
// ####################################################################################################################
|
// ####################################################################################################################
|
:sectnums:
|
:sectnums:
|
== Application Program Compilation
|
== Application Program Compilation
|
|
|
|
This guide shows how to compile an example C-code application into a NEORV32 executable that
|
|
can be uploaded via the bootloader or the on-chip debugger.
|
|
|
|
[IMPORTANT]
|
|
If your FPGA board does not provide such an interface - don't worry!
|
|
Section <<_installing_an_executable_directly_into_memory>> shows how to
|
|
run custom programs on your FPGA setup without having a UART.
|
|
|
[start=1]
|
[start=1]
|
. Open a terminal console and navigate to one of the project's example programs. For instance navigate to the
|
. Open a terminal console and navigate to one of the project's example programs. For instance, navigate to the
|
simple `sw/example_blink_led` example program. This program uses the NEORV32 GPIO unit to display
|
simple `sw/example_blink_led` example program. This program uses the NEORV32 GPIO module to display
|
an 8-bit counter on the lowest eight bit of the `gpio_o` output port.
|
an 8-bit counter on the lowest eight bit of the `gpio_o` output port.
|
. To compile the project and generate an executable simply execute:
|
. To compile the project and generate an executable simply execute:
|
|
|
[source,bash]
|
[source,bash]
|
----
|
----
|
neorv32/sw/example/blink_led$ make exe
|
neorv32/sw/example/blink_led$ make clean_all exe
|
----
|
----
|
|
|
[start=3]
|
[start=3]
|
|
. We are using the `clean_all` taret to make sure everything is re-build.
|
. This will compile and link the application sources together with all the included libraries. At the end,
|
. This will compile and link the application sources together with all the included libraries. At the end,
|
your application is transformed into an ELF file (`main.elf`). The *NEORV32 image generator* (in `sw/image_gen`) takes this file and creates a
|
your application is transformed into an ELF file (`main.elf`). The _NEORV32 image generator_ (in `sw/image_gen`)
|
final executable. The makefile will show the resulting memory utilization and the executable size:
|
takes this file and creates a final executable. The makefile will show the resulting memory utilization and
|
|
the executable size:
|
|
|
[source,bash]
|
[source,bash]
|
----
|
----
|
neorv32/sw/example/blink_led$ make exe
|
neorv32/sw/example/blink_led$ make clean_all exe
|
Memory utilization:
|
Memory utilization:
|
text data bss dec hex filename
|
text data bss dec hex filename
|
852 0 0 852 354 main.elf
|
3176 0 120 3296 ce0 main.elf
|
|
Compiling ../../../sw/image_gen/image_gen
|
Executable (neorv32_exe.bin) size in bytes:
|
Executable (neorv32_exe.bin) size in bytes:
|
864
|
3188
|
----
|
----
|
|
|
[start=4]
|
[start=5]
|
. That's it. The `exe` target has created the actual executable `neorv32_exe.bin` in the current
|
. That's it. The `exe` target has created the actual executable `neorv32_exe.bin` in the current folder
|
folder, which is ready to be uploaded to the processor via the bootloader's UART interface.
|
that is ready to be uploaded to the processor.
|
|
|
[TIP]
|
[TIP]
|
The compilation process will also create a `main.asm` assembly listing file in the project directory, which
|
The compilation process will also create a `main.asm` assembly listing file in the current folder, which
|
shows the actual assembly code of the complete application.
|
shows the actual assembly code of the application.
|
|
|
|
|
|
|
|
|
// ####################################################################################################################
|
// ####################################################################################################################
|
:sectnums:
|
:sectnums:
|
== Uploading and Starting of a Binary Executable Image via UART
|
== Uploading and Starting of a Binary Executable Image via UART
|
|
|
You have just created the executable. Now it is time to upload it to the processor. There are basically two
|
Follow this guide to use the bootloader to upload an executable via UART.
|
options to do so.
|
|
|
|
[TIP]
|
|
Executables can also be uploaded via the **on-chip debugger**.
|
|
See section <<_debugging_with_gdb>> for more information.
|
|
|
|
**Option 1**
|
|
|
|
The NEORV32 makefiles provide an upload target that allows to directly upload an executable from the
|
|
command line. Reset the processor and execute:
|
|
|
|
[source,bash]
|
|
----
|
|
sw/example/blink_led$ make COM_PORT=/dev/ttyUSB1 upload
|
|
----
|
|
|
|
Replace `/dev/ttyUSB1` with the actual serial port you are using to communicate with the processor. You
|
|
might have to use `sudo make ...` if the targeted device requires elevated access rights.
|
|
|
|
|
|
**Option 2**
|
[NOTE]
|
|
This concept uses the default "Indirect Boot" scenario that uses the bootloader to upload new executables.
|
|
See datasheet section https://stnolting.github.io/neorv32/#_indirect_boot[Indirect Boot] for more information.
|
|
|
The "better" option is to use a standard terminal program to upload an executable. This provides a more
|
[IMPORTANT]
|
comfortable way as you can directly interact with the bootloader console. Additionally, using a terminal program
|
If your FPGA board does not provide such an interface - don't worry!
|
also allows to directly communicate with the uploaded application.
|
Section <<_installing_an_executable_directly_into_memory>> shows how to
|
|
run custom programs on your FPGA setup without having a UART.
|
|
|
[start=1]
|
[start=1]
|
. Connect the primary UART (UART0) interface of your FPGA board to a serial port of your
|
. Connect the primary UART (UART0) interface of your FPGA board to a serial port of your host computer.
|
computer or use an USB-to-serial adapter.
|
. Start a terminal program. In this tutorial, I am using TeraTerm for Windows. You can download it fore free
|
. Start a terminal program. In this tutorial, I am using TeraTerm for Windows. You can download it from https://ttssh2.osdn.jp/index.html.en
|
from https://ttssh2.osdn.jp/index.html.en
|
|
|
[WARNING]
|
[NOTE]
|
Make sure your terminal program can transfer the executable in raw byte mode without any protocol stuff around it.
|
_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 around it.
|
|
|
[start=3]
|
[start=3]
|
. Open a connection to the corresponding srial port. Configure the terminal according to the
|
. Open a connection to the the serial port your UART is connected to. Configure the terminal setting according to the
|
following parameters:
|
following parameters:
|
|
|
* 19200 Baud
|
* 19200 Baud
|
* 8 data bits
|
* 8 data bits
|
* 1 stop bit
|
* 1 stop bit
|
* no parity bits
|
* no parity bits
|
* no transmission/flow control protocol! (just raw byte mode)
|
* _no_ transmission/flow control protocol
|
* newline on `\r\n` (carriage return & newline)
|
* receiver (host computer) newline on `\r\n` (carriage return & newline)
|
|
|
[start=4]
|
[start=4]
|
. Also make sure, that single chars are transmitted without any consecutive "new line" or "carriage
|
. Also make sure that single chars are send from your computer _without_ any consecutive "new line" or "carriage
|
return" commands (this is highly dependent on your terminal application of choice, TeraTerm only
|
return" commands (this is highly dependent on your terminal application of choice, TeraTerm only
|
sends the raw chars by default).
|
sends the raw chars by default).
|
. Press the NEORV32 reset button to restart the bootloader. The status LED starts blinking and the
|
. Press the NEORV32 reset button to restart the bootloader. The status LED starts blinking and the
|
bootloader intro screen appears in your console. Hurry up and press any key (hit space!) to abort the
|
bootloader intro screen appears in your console. Hurry up and press any key (hit space!) to abort the
|
automatic boot sequence and to start the actual bootloader user interface console.
|
automatic boot sequence and to start the actual bootloader user interface console.
|
Line 389... |
Line 361... |
<< NEORV32 Bootloader >>
|
<< NEORV32 Bootloader >>
|
|
|
BLDV: Mar 23 2021
|
BLDV: Mar 23 2021
|
HWV: 0x01050208
|
HWV: 0x01050208
|
CLK: 0x05F5E100
|
CLK: 0x05F5E100
|
USER: 0x10000DE0
|
|
MISA: 0x40901105
|
MISA: 0x40901105
|
ZEXT: 0x00000023
|
ZEXT: 0x00000023
|
PROC: 0x0EFF0037
|
PROC: 0x0EFF0037
|
IMEM: 0x00004000 bytes @ 0x00000000
|
IMEM: 0x00004000 bytes @ 0x00000000
|
DMEM: 0x00002000 bytes @ 0x80000000
|
DMEM: 0x00002000 bytes @ 0x80000000
|
Line 410... |
Line 381... |
e: Execute
|
e: Execute
|
CMD:>
|
CMD:>
|
----
|
----
|
|
|
[start=6]
|
[start=6]
|
. Execute the "Upload" command by typing `u`. Now the bootloader is waiting for a binary executable
|
. Execute the "Upload" command by typing `u`. Now the bootloader is waiting for a binary executable to be send.
|
to be send.
|
|
|
|
[source,bash]
|
[source,bash]
|
----
|
----
|
CMD:> u
|
CMD:> u
|
Awaiting neorv32_exe.bin...
|
Awaiting neorv32_exe.bin...
|
----
|
----
|
|
|
[start=7]
|
[start=7]
|
. Use the "send file" option of your terminal program to transmit the previously generated binary executable `neorv32_exe.bin`.
|
. Use the "send file" option of your terminal program to send a NEORV32 executable (`neorv32_exe.bin`).
|
. Again, make sure to transmit the executable in raw binary mode (no transfer protocol, no additional
|
. Again, make sure to transmit the executable in raw binary mode (no transfer protocol).
|
header stuff). When using TeraTerm, select the "binary" option in the send file dialog.
|
When using TeraTerm, select the "binary" option in the send file dialog.
|
. If everything went fine, OK will appear in your terminal:
|
. If everything went fine, OK will appear in your terminal:
|
|
|
[source,bash]
|
[source,bash]
|
----
|
----
|
CMD:> u
|
CMD:> u
|
Awaiting neorv32_exe.bin... OK
|
Awaiting neorv32_exe.bin... OK
|
----
|
----
|
|
|
[start=10]
|
[start=10]
|
. The executable now resides in the instruction memory of the processor. To execute the program right
|
. The executable is now in the instruction memory of the processor. To execute the program right
|
now run the "Execute" command by typing `e`:
|
now run the "Execute" command by typing `e`:
|
|
|
[source,bash]
|
[source,bash]
|
----
|
----
|
CMD:> u
|
CMD:> u
|
Line 445... |
Line 415... |
Booting...
|
Booting...
|
Blinking LED demo program
|
Blinking LED demo program
|
----
|
----
|
|
|
[start=11]
|
[start=11]
|
. Now you should see the LEDs counting.
|
. If everything went fine, you should see the LEDs blinking.
|
|
|
|
[NOTE]
|
|
The bootloader will print error codes if something went wrong.
|
|
See section https://stnolting.github.io/neorv32/#_bootloader[Bootloader] of the NEORV32 datasheet for more information.
|
|
|
|
[TIP]
|
|
See section <<_programming_an_external_spi_flash_via_the_bootloader>> to learn how to use an external SPI
|
|
flash for nonvolatile program storage.
|
|
|
|
[TIP]
|
|
Executables can also be uploaded via the **on-chip debugger**.
|
|
See section <<_debugging_with_gdb>> for more information.
|
|
|
|
|
|
|
|
|
// ####################################################################################################################
|
// ####################################################################################################################
|
:sectnums:
|
:sectnums:
|
== Setup of a New Application Program Project
|
== Installing an Executable Directly Into Memory
|
|
|
|
If you do not want to use the bootloader (or the on-chip debugger) for executable upload or if your setup does not provide
|
|
a serial interface for that, you can also directly install an application into embedded memory.
|
|
|
|
This concept uses the "Direct Boot" scenario that implements the processor-internal IMEM as ROM, which is
|
|
pre-initialized with the application's executable during synthesis. Hence, it provides _non-volatile_ storage of the
|
|
executable inside the processor. This storage cannot be altered during runtime and any source code modification of
|
|
the application requires to re-program the FPGA via the bitstream.
|
|
|
|
[TIP]
|
|
See datasheet section https://stnolting.github.io/neorv32/#_direct_boot[Direct Boot] for more information.
|
|
|
|
|
|
|
|
Using the IMEM as ROM:
|
|
|
|
* for this boot concept the bootloader is no longer required
|
|
* this concept only works for the internal IMEM (but can be extended to work with external memories coupled via the processor's bus interface)
|
|
* make sure that the memory components (like block RAM) the IMEM is mapped to support an initialization via the bitstream
|
|
|
|
[start=1]
|
|
. At first, make sure your processor setup actually implements the internal IMEM: the `MEM_INT_IMEM_EN` generics has to be set to `true`:
|
|
|
|
.Processor top entity configuration - enable internal IMEM
|
|
[source,vhdl]
|
|
----
|
|
-- Internal Instruction memory --
|
|
MEM_INT_IMEM_EN => true, -- implement processor-internal instruction memory
|
|
----
|
|
|
|
[start=2]
|
|
. For this setup we do not want the bootloader to be implemented at all. Disable implementation of the bootloader by setting the
|
|
`INT_BOOTLOADER_EN` generic to `false`. This will also modify the processor-internal IMEM so it is initialized with the executable during synthesis.
|
|
|
|
.Processor top entity configuration - disable internal bootloader
|
|
[source,vhdl]
|
|
----
|
|
-- General --
|
|
INT_BOOTLOADER_EN => false, -- boot configuration: false = boot from int/ext (I)MEM
|
|
----
|
|
|
|
[start=3]
|
|
. To generate an "initialization image" for the IMEM that contains the actual application, run the `install` target when compiling your application:
|
|
|
|
[source,bash]
|
|
----
|
|
neorv32/sw/example/blink_led$ make clean_all install
|
|
Memory utilization:
|
|
text data bss dec hex filename
|
|
3176 0 120 3296 ce0 main.elf
|
|
Compiling ../../../sw/image_gen/image_gen
|
|
Installing application image to ../../../rtl/core/neorv32_application_image.vhd
|
|
----
|
|
|
|
[start=4]
|
|
. The `install` target has compiled all the application sources but instead of creating an executable (`neorv32_exe.bit`) that can be uploaded via the
|
|
bootloader, it has created a VHDL memory initialization image `core/neorv32_application_image.vhd`.
|
|
. This VHDL file is automatically copied to the core's rtl folder (`rtl/core`) so it will be included for the next synthesis.
|
|
. Perform a new synthesis. The IMEM will be build as pre-initialized ROM (inferring embedded memories if possible).
|
|
. Upload your bitstream. Your application code now resides unchangeable in the processor's IMEM and is directly executed after reset.
|
|
|
|
|
|
The synthesis tool / simulator will print asserts to inform about the (IMEM) memory / boot configuration:
|
|
|
|
[source]
|
|
----
|
|
NEORV32 PROCESSOR CONFIG NOTE: Boot configuration: Direct boot from memory (processor-internal IMEM).
|
|
NEORV32 PROCESSOR CONFIG NOTE: Implementing processor-internal IMEM as ROM (3176 bytes), pre-initialized with application.
|
|
----
|
|
|
|
|
Done with all the introduction tutorials and those example programs? Then it is time to start your own
|
|
application project!
|
|
|
// ####################################################################################################################
|
|
:sectnums:
|
|
== Setup of a New Application Program Project
|
|
|
[start=1]
|
[start=1]
|
. The easiest way of creating a *new* project is to make a copy of an *existing* project (like the
|
. The easiest way of creating a _new_ software application project is to copy an _existing_ one. This will keep all
|
`blink_led` project) inside the `sw/example` folder. By this, all file dependencies are kept and you can
|
file dependencies. For example you can copy `sw/example/blink_led` to `sw/example/flux_capacitor`.
|
start coding and compiling.
|
. If you want to place you application somewhere outside `sw/example` you need to adapt the application's makefile.
|
. If you want to place the project folder somewhere else you need to adapt the project's makefile. In
|
In the makefile you will find a variable that keeps the relative or absolute path to the NEORV32 repo home
|
the makefile you will find a variable that keeps the relative or absolute path to the NEORV32 home
|
|
folder. Just modify this variable according to your new project's home location:
|
folder. Just modify this variable according to your new project's home location:
|
|
|
[source,makefile]
|
[source,makefile]
|
----
|
----
|
# Relative or absolute path to the NEORV32 home folder (use default if not set by user)
|
# Relative or absolute path to the NEORV32 home folder (use default if not set by user)
|
NEORV32_HOME ?= ../../..
|
NEORV32_HOME ?= ../../..
|
----
|
----
|
|
|
[start=3]
|
[start=3]
|
. If your project contains additional source files outside of the project folder, you can add them to the _APP_SRC_ variable:
|
. If your project contains additional source files outside of the project folder, you can add them to
|
|
the `APP_SRC` variable:
|
|
|
[source,makefile]
|
[source,makefile]
|
----
|
----
|
# User's application sources (add additional files here)
|
# User's application sources (add additional files here)
|
APP_SRC = $(wildcard *.c) ../somewhere/some_file.c
|
APP_SRC = $(wildcard *.c) ../somewhere/some_file.c
|
----
|
----
|
|
|
[start=4]
|
[start=4]
|
. You also need to add the folder containing the include files of your new project to the _APP_INC variable_ (do not forget the `-I` prefix):
|
. You also can add a folder containing your application's include files to the
|
|
`APP_INC` variable (do not forget the `-I` prefix):
|
|
|
[source,makefile]
|
[source,makefile]
|
----
|
----
|
# User's application include folders (don't forget the '-I' before each entry)
|
# User's application include folders (don't forget the '-I' before each entry)
|
APP_INC = -I . -I ../somewhere/include_stuff_folder
|
APP_INC = -I . -I ../somewhere/include_stuff_folder
|
----
|
----
|
|
|
[start=5]
|
|
. If you feel like it, you can change the default optimization level:
|
|
|
|
[source,makefile]
|
|
----
|
|
# Compiler effort
|
|
EFFORT = -Os
|
|
----
|
|
|
|
[TIP]
|
|
All the assignments made to the makefile variable can also be done "inline" when invoking the makefile. For example: `$make EFFORT=-Os clean_all exe`
|
|
|
|
|
|
|
|
|
|
|
|
// ####################################################################################################################
|
// ####################################################################################################################
|
:sectnums:
|
:sectnums:
|
== Enabling RISC-V CPU Extensions
|
== Enabling RISC-V CPU Extensions
|
|
|
Whenever you enable/disable a RISC-V CPU extensions via the according _CPU_EXTENSION_RISCV_x_ generic, you need to
|
Whenever you enable/disable a RISC-V CPU extensions via the according `CPU_EXTENSION_RISCV_x` generic, you need to
|
adapt the toolchain configuration so the compiler can actually generate according code for it.
|
adapt the toolchain configuration so the compiler can actually generate according code for it.
|
|
|
To do so, open the makefile of your project (for example `sw/example/blink_led/makefile`) and scroll to the
|
To do so, open the makefile of your project (for example `sw/example/blink_led/makefile`) and scroll to the
|
"USER CONFIGURATION" section right at the beginning of the file. You need to modify the _MARCH_ variable and eventually
|
"USER CONFIGURATION" section right at the beginning of the file. You need to modify the `MARCH` variable and eventually
|
the _MABI_ variable according to your CPU hardware configuration.
|
the `MABI` variable according to your CPU hardware configuration.
|
|
|
[source,makefile]
|
[source,makefile]
|
----
|
----
|
# CPU architecture and ABI
|
# CPU architecture and ABI
|
MARCH = -march=rv32i # <1>
|
MARCH = -march=rv32i # <1>
|
MABI = -mabi=ilp32 # <2>
|
MABI = -mabi=ilp32 # <2>
|
----
|
----
|
<1> MARCH = Machine architecture ("ISA string")
|
<1> MARCH = Machine architecture ("ISA string")
|
<2> MABI = Machine binary interface
|
<2> MABI = Machine binary interface
|
|
|
For example when you enable the RISC-V `C` extension (16-bit compressed instructions) via the _CPU_EXTENSION_RISCV_C_ generic (set _true_) you need
|
For example, if you enable the RISC-V `C` extension (16-bit compressed instructions) via the `CPU_EXTENSION_RISCV_C`
|
to add the 'c' extension also to the _MARCH_ ISA string.
|
generic (set `true`) you need to add the 'c' extension also to the `MARCH` ISA string in order to make the compiler
|
|
emit compressed instructions.
|
|
|
|
[WARNING]
|
|
ISA extension enabled in hardware can be a superset of the extensions enabled in software, but not the other way
|
|
around. For example generating compressed instructions for a CPU configuration that has the `c` extension disabled
|
|
will cause _illegal instruction exceptions_ at runtime.
|
|
|
You can also override the default _MARCH_ and _MABI_ configurations from the makefile when invoking the makefile:
|
You can also override the default `MARCH` and `MABI` configurations from the makefile when invoking the makefile:
|
|
|
[source,bash]
|
[source,bash]
|
----
|
----
|
$ make MARCH=-march=rv32ic clean_all all
|
$ make MARCH=-march=rv32ic clean_all all
|
----
|
----
|
|
|
[NOTE]
|
[NOTE]
|
The RISC-V ISA string (for _MARCH_) follows a certain canonical structure:
|
The RISC-V ISA string (for _MARCH_) follows a certain _canonical_ structure:
|
`rev32[i/e][m][a][f][d][g][q][c][b][v][n]...` For example `rv32imac` is valid while `rv32icma` is not valid.
|
`rev32[i/e][m][a][f][d][g][q][c][b][v][n]...` For example `rv32imac` is valid while `rv32icma` is not valid.
|
|
|
|
|
|
|
|
|
|
|
// ####################################################################################################################
|
// ####################################################################################################################
|
:sectnums:
|
:sectnums:
|
== Building a Non-Volatile Application without External Boot Memory
|
== Customizing the Internal Bootloader
|
|
|
The primary purpose of the bootloader is to allow an easy and fast update of the current application. In particular, this is very handy
|
The NEORV32 bootloader provides several options to configure and customize it for a certain application setup.
|
during the development stage of a project as you can upload modified programs at any time via the UART.
|
This configuration is done by passing _defines_ when compiling the bootloader. Of course you can also
|
Maybe at some time your project has become mature and you want to actually _embed_ your processor
|
modify to bootloader source code to provide a setup that perfectly fits your needs.
|
including the application.
|
|
|
|
There are two options to provide _non-volatile_ storage of your application. The simplest (but also most constrained) one is to implement the IMEM
|
|
as true ROM to contain your program. The second option is to use an external boot memory - this concept is shown in a different section:
|
|
<<_programming_an_external_spi_flash_via_the_bootloader>>.
|
|
|
|
Using the IMEM as ROM:
|
[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.
|
|
|
* for this boot concept the bootloader is no longer required
|
[NOTE]
|
* this concept only works for the internal IMEM (but can be extended to work with external memories coupled via the processor's bus interface)
|
Keep in mind that the maximum size for the bootloader is limited to 32kB and should be compiled using the
|
* make sure that the memory components (like block RAM) the IMEM is mapped to support an initialization via the bitstream
|
base ISA `rv32i` only to ensure it can work independently of the actual CPU configuration.
|
|
|
[start=1]
|
.Bootloader configuration parameters
|
. At first, compile your application code by running the `make install` command:
|
[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
|
|
|=======================
|
|
|
[source,bash]
|
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_
|
neorv32/sw/example/blink_led$ make compile
|
(using `+=`) to the makefile's `USER_FLAGS` variable. Make sure to use the `-D` prefix here.
|
Memory utilization:
|
|
text data bss dec hex filename
|
|
852 0 0 852 354 main.elf
|
|
Executable (neorv32_exe.bin) size in bytes:
|
|
864
|
|
Installing application image to ../../../rtl/core/neorv32_application_image.vhd
|
|
----
|
|
|
|
[start=2]
|
For example, to configure a UART Baud rate of 57600 and redirecting the status LED to output pin 20
|
. The `install` target has created an executable, too, but this time also in the form of a VHDL memory
|
use the following command (_in_ the bootloader's source folder `sw/bootloader`):
|
initialization file. during synthesis, this initialization will become part of the final FPGA bitstream, which
|
|
in terms initializes the IMEM's memory primitives.
|
|
. To allow a direct boot of this image without interference of the bootloader you _can_ deactivate the implementation of
|
|
the bootloader via the according top entity's generic:
|
|
|
|
[source,vhdl]
|
.Example: customizing, re-compiling and re-installing the bootloader
|
|
[source,console]
|
----
|
----
|
BOOTLOADER_EN => false, -- implement processor-internal bootloader? # <1>
|
$ make USER_FLAGS+=-DUART_BAUD=57600 USER_FLAGS+=-DSTATUS_LED_PIN=20 clean_all bootloader
|
----
|
----
|
<1> Set to _false_ to make the CPU directly boot from the IMEM. In this case the BOOTROM is discarded from the design.
|
|
|
|
[start=4]
|
|
. When the bootloader is deactivated, the according module (BOOTROM) is removed from the design and the CPU will start booting
|
|
at the base address of the instruction memory space (IMEM base address) making the CPU directly executing your
|
|
application after reset.
|
|
. The IMEM could be still modified, since it is implemented as RAM by default, which might corrupt your
|
|
executable. To prevent this and to implement the IMEM as true ROM (and eventually saving some
|
|
more hardware resources), active the "IMEM as ROM" feature using the processor's according top entity
|
|
generic:
|
|
|
|
[source,vhdl]
|
[NOTE]
|
----
|
The `clean_all` target ensure that all libraries are re-compiled. The `bootloader` target will automatically
|
MEM_INT_IMEM_ROM => true, -- implement processor-internal instruction memory as ROM
|
compile and install the bootloader to the HDL boot ROM (updating `rtl/core/neorv32_bootloader_image.vhd`).
|
----
|
|
|
|
[start=6]
|
:sectnums:
|
. Perform a new synthesis and upload your bitstream. Your application code now resides unchangeable
|
=== Bootloader Boot Configuration
|
in the processor's IMEM and is directly executed after reset.
|
|
|
|
|
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.
|
|
|
|
:sectnums!:
|
|
==== Default Boot Configuration
|
|
|
|
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
|
:sectnums:
|
provides a "non volatile program storage" mechanism that automatically boot from external SPI flash
|
== Customizing the Internal Bootloader
|
(after `AUTO_BOOT_TIMEOUT`) while still providing the option to re-program SPI flash at any time
|
|
via the UART interface.
|
|
|
The bootloader provides several configuration options to customize it for your specific applications. The
|
:sectnums!:
|
most important user-defined configuration options are available as C `#defines` right at the beginning of the
|
==== `AUTO_BOOT_SPI_EN`
|
bootloader source code `sw/bootloader/bootloader.c`):
|
|
|
|
.Cut-out from the bootloader source code `bootloader.c`: configuration parameters
|
The automatic boot from SPI flash (enabled when `AUTO_BOOT_SPI_EN` is `1`) will fetch an executable from an external
|
[source,c]
|
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`.
|
/** 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 '#'
|
|
----
|
|
|
|
**Changing the Default Size of the Bootloader ROM**
|
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`).
|
|
|
The NEORV32 default bootloader uses 4kB of storage. This is also the default size of the BOOTROM memory component.
|
:sectnums!:
|
If your new/modified bootloader exceeds this size, you need to modify the boot ROM configurations.
|
==== `AUTO_BOOT_OCD_EN`
|
|
|
[start=1]
|
If `AUTO_BOOT_OCD_EN` is `1` the bootloader is implemented as minimal "halt loop" to be used with the on-chip debugger.
|
. Open the processor's main package file `rtl/core/neorv32_package.vhd` and edit the
|
After initializing the hardware, the CPU waits in this endless loop until the on-chip debugger takes control over
|
`boot_size_c` constant according to your requirements. The boot ROM size must not exceed 32kB
|
the core (to upload and run the actual executable). See section <<_debugging_using_the_on_chip_debugger>>
|
and should be a power of two (for optimal hardware mapping).
|
for more information on how to use the on-chip debugger to upload and run executables.
|
|
|
[source,vhdl]
|
[NOTE]
|
----
|
All bootloader boot configuration support uploading new executables via the on-chip debugger.
|
-- Bootloader ROM --
|
|
constant boot_size_c : natural := 4*1024; -- bytes
|
|
----
|
|
|
|
[start=2]
|
[WARNING]
|
. Now open the NEORV32 linker script `sw/common/neorv32.ld` and adapt the _LENGTH_ parameter
|
Note that this boot configuration does not load any executable at all! Hence,
|
of the `rom` according to your new memory size. `boot_size_c` and the `rom` _LENGTH_ attribute have to be always
|
this boot configuration is intended to be used with the on-chip debugger only.
|
identical. Do **not modify** the _ORIGIN_ of the `rom` section.
|
|
|
|
[source,c]
|
|
----
|
|
MEMORY
|
|
{
|
|
rom (rx) : ORIGIN = DEFINED(make_bootloader) ? 0xFFFF0000 : 0x00000000, LENGTH = DEFINED(make_bootloader) ? 4*1024 : 16*1024 # <1>
|
|
ram (rwx) : ORIGIN = 0x80000000, LENGTH = 8*1024
|
|
}
|
|
----
|
|
<1> Bootloader ROM default size = 4*1024 bytes (**left** value)
|
|
|
|
[IMPORTANT]
|
|
The `rom` region provides conditional assignments (via symbol `make_bootloader`) for the origin
|
|
and the length depending on whether the executable is built as normal application (for the IMEM) or
|
|
as bootloader code (for the BOOTROM). To modify the BOOTLOADER memory size, make
|
|
sure to edit the first value for the origin (note "1").
|
|
|
|
**Re-Compiling and Re-Installing the Bootloader**
|
|
|
// ####################################################################################################################
|
|
:sectnums:
|
|
== Programming an External SPI Flash via the Bootloader
|
|
|
Whenever you have modified the bootloader you need to recompile and re-install it and re-synthesize your design.
|
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]
|
[NOTE]
|
. Compile and install the bootloader using the explicit `bootloader` makefile target.
|
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).
|
|
|
[source,bash]
|
|
----
|
|
neorv32/sw/bootloader$ make bootloader
|
|
----
|
|
|
|
[start=1]
|
:sectnums:
|
. Now perform a new synthesis / HDL compilation to update the bitstream with the new bootloader
|
=== SPI Flash
|
image (some synthesis tools also allow to only update the BRAM initialization without re-running
|
|
the entire synthesis process).
|
|
|
|
[NOTE]
|
The bootloader can access an SPI compatible flash via the processor top entity's SPI port. By default, the flash
|
The bootloader is intended to work regardless of the actual NEORV32 hardware configuration –
|
chip-select line is to `spi_csn_o(0)` and uses 1/8 of the processor's main clock as clock frequency.
|
especially when it comes to CPU extensions. Hence, the bootloader should be build using the
|
The SPI flash has to support single-byte read and write, 24-bit addresses and at least the following standard commands:
|
minimal `rv32i` ISA only (`rv32e` would be even better).
|
|
|
|
|
* 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:
|
:sectnums:
|
== Programming an External SPI Flash via the Bootloader
|
=== Programming an Executable
|
|
|
As described in section https://stnolting.github.io/neorv32/#_external_spi_flash_for_booting[Documentation: External SPI Flash for Booting]
|
|
the bootloader provides an option to store an application image to an external SPI flash
|
|
and to read this image back for booting. These steps show how to store a
|
|
|
|
[start=1]
|
[start=1]
|
. At first, reset the NEORV32 processor and wait until the bootloader start screen appears in your terminal program.
|
. 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.
|
. Abort the auto boot sequence and start the user console by pressing any key.
|
. Press u to upload the program image, that you want to store to the external flash:
|
. Press u to upload the executable that you want to store to the external flash:
|
|
|
[source]
|
[source]
|
----
|
----
|
CMD:> u
|
CMD:> u
|
Awaiting neorv32_exe.bin...
|
Awaiting neorv32_exe.bin...
|
----
|
----
|
|
|
[start=4]
|
[start=4]
|
. Send the binary in raw binary via your terminal program. When the uploaded is completed and "OK"
|
. Send the binary in raw binary via your terminal program. When the upload is completed and "OK"
|
appears, press `p` to trigger the programming of the flash (do not execute the image via the `e`
|
appears, press `p` to trigger the programming of the flash (do not execute the image via the `e`
|
command as this might corrupt the image):
|
command as this might corrupt the image):
|
|
|
[source]
|
[source]
|
----
|
----
|
Line 744... |
Line 756... |
----
|
----
|
|
|
[start=5]
|
[start=5]
|
. The bootloader shows the size of the executable and the base address inside the SPI flash where the
|
. 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
|
executable is going to be stored. A prompt appears: Type `y` to start the programming or type `n` to
|
abort. See section <<_external_spi_flash_for_booting> for more information on how to configure the base address.
|
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]
|
[source]
|
----
|
----
|
CMD:> u
|
CMD:> u
|
Awaiting neorv32_exe.bin... OK
|
Awaiting neorv32_exe.bin... OK
|
CMD:> p
|
CMD:> p
|
Write 0x000013FC bytes to SPI flash @ 0x00800000? (y/n) y
|
Write 0x000013FC bytes to SPI flash @ 0x08000000? (y/n) y
|
Flashing... OK
|
Flashing... OK
|
CMD:>
|
CMD:>
|
----
|
----
|
|
|
[start=6]
|
[start=6]
|
Line 766... |
Line 782... |
|
|
|
|
|
|
// ####################################################################################################################
|
// ####################################################################################################################
|
:sectnums:
|
:sectnums:
|
|
== Packaging the Processor as IP block for Xilinx Vivado Block Designer
|
|
|
|
.WORK IN PROGRESS
|
|
[WARNING]
|
|
This Section Is Under Construction! +
|
|
+
|
|
FIXME!
|
|
|
|
|
|
|
|
|
|
// ####################################################################################################################
|
|
:sectnums:
|
== Simulating the Processor
|
== Simulating the Processor
|
|
|
**Testbench**
|
.WORK IN PROGRESS
|
|
[WARNING]
|
|
This Section Is Under Construction! +
|
|
+
|
|
FIXME!
|
|
|
The NEORV32 project features a simple default testbench (`sim/neorv32_tb.vhd`) that can be used to simulate
|
:sectnums:
|
|
=== Testbench
|
|
|
|
The NEORV32 project features a simple default testbench (`sim/neorv32_tb.simple.vhd`) that can be used to simulate
|
and test the processor setup. This testbench features a 100MHz clock and enables all optional peripheral and
|
and test the processor setup. This testbench features a 100MHz clock and enables all optional peripheral and
|
CPU extensions except for the `E` extension and the TRNG IO module (that CANNOT be simulated due to its
|
CPU extensions except for the `E` extension and the TRNG IO module (that CANNOT be simulated due to its
|
combinatorial (looped) oscillator architecture).
|
combinatorial (looped) oscillator architecture).
|
|
|
The simulation setup is configured via the "User Configuration" section located right at the beginning of
|
The simulation setup is configured via the "User Configuration" section located right at the beginning of
|
Line 822... |
Line 858... |
|
|
Data written to the NEORV32 UART0 / UART1 transmitter is send to a virtual UART receiver implemented
|
Data written to the NEORV32 UART0 / UART1 transmitter is send to a virtual UART receiver implemented
|
as part of the testbench. Received chars are send to the simulator console and are also stored to a log file
|
as part of the testbench. Received chars are send to the simulator console and are also stored to a log file
|
(`neorv32.testbench_uart0.out` for UART0, `neorv32.testbench_uart1.out` for UART1) inside the simulator home folder.
|
(`neorv32.testbench_uart0.out` for UART0, `neorv32.testbench_uart1.out` for UART1) inside the simulator home folder.
|
|
|
**Faster Simulation Console Output**
|
|
|
:sectnums:
|
|
=== Faster Simulation Console Output
|
|
|
When printing data via the UART the communication speed will always be based on the configured BAUD
|
When printing data via the UART the communication speed will always be based on the configured BAUD
|
rate. For a simulation this might take some time. To have faster output you can enable the **simulation mode**
|
rate. For a simulation this might take some time. To have faster output you can enable the **simulation mode**
|
or UART0/UART1 (see section https://stnolting.github.io/neorv32/#_primary_universal_asynchronous_receiver_and_transmitter_uart0[Documentation: Primary Universal Asynchronous Receiver and Transmitter (UART0)]).
|
or UART0/UART1 (see section https://stnolting.github.io/neorv32/#_primary_universal_asynchronous_receiver_and_transmitter_uart0[Documentation: Primary Universal Asynchronous Receiver and Transmitter (UART0)]).
|
|
|
Line 854... |
Line 892... |
|
|
[NOTE]
|
[NOTE]
|
The UART simulation output (to file and to screen) outputs "complete lines" at once. A line is
|
The UART simulation output (to file and to screen) outputs "complete lines" at once. A line is
|
completed with a line feed (newline, ASCII `\n` = 10).
|
completed with a line feed (newline, ASCII `\n` = 10).
|
|
|
**Simulation with Xilinx Vivado**
|
|
|
|
The project features default a Vivado simulation waveform configuration in `sim/vivado`.
|
|
|
|
**Simulation with GHDL**
|
:sectnums:
|
|
=== Simulation using GHDL
|
|
|
To simulate the processor using _GHDL_ navigate to the `sim` folder and run the provided shell script. All arguments are passed to GHDL.
|
To simulate the processor using _GHDL_ navigate to the `sim` folder and run the provided shell script.
|
For example the simulation time can be configured using `--stop-time=4ms` as argument.
|
Any arguments that are provided while executing this script are passed to GHDL.
|
|
For example the simulation time can be set to 20ms using `--stop-time=20ms` as argument.
|
|
|
[source, bash]
|
[source, bash]
|
----
|
----
|
neorv32/sim$ sh ghdl_sim.sh --stop-time=4ms
|
neorv32/sim$ sh ghdl_sim.sh --stop-time=20ms
|
----
|
----
|
|
|
|
|
|
|
|
|
// ####################################################################################################################
|
// ####################################################################################################################
|
:sectnums:
|
:sectnums:
|
== Building the Documentation
|
== Building the Documentation
|
|
|
The documentation is written using `asciidoc`. The according source files can be found in `docs/...`.
|
The documentation (datasheet + user guide) is written using `asciidoc`. The according source files
|
The documentation of the software framework is written _in-code_ using `doxygen`.
|
can be found in `docs/...`. The documentation of the software framework is written _in-code_ using `doxygen`.
|
|
|
A makefiles in the project's root directory is provided to either build all of the documentation as HTML pages
|
A makefiles in the project's root directory is provided to build all of the documentation as HTML pages
|
or as PDF documents.
|
or as PDF documents.
|
|
|
[TIP]
|
[TIP]
|
Pre-rendered PDFs are available online as nightly pre-releases: https://github.com/stnolting/neorv32/releases.
|
Pre-rendered PDFs are available online as _nightly pre-releases_: https://github.com/stnolting/neorv32/releases.
|
The HTML-based documentation is also available online at the project's https://stnolting.github.io/neorv32/[GitHub Pages].
|
The HTML-based documentation is also available online at the project's https://stnolting.github.io/neorv32/[GitHub Pages].
|
|
|
The makefile provides a help target to show all available build options and their according outputs.
|
The makefile provides a help target to show all available build options and their according outputs.
|
|
|
[source,bash]
|
[source,bash]
|
Line 904... |
Line 941... |
If you don't have `asciidoctor` / `asciidoctor-pdf` installed, you can still generate all the documentation using
|
If you don't have `asciidoctor` / `asciidoctor-pdf` installed, you can still generate all the documentation using
|
a _docker container_ via `make container`.
|
a _docker container_ via `make container`.
|
|
|
|
|
|
|
// ####################################################################################################################
|
|
:sectnums:
|
|
== Building the Project Documentation
|
|
|
|
|
|
|
|
|
|
// ####################################################################################################################
|
// ####################################################################################################################
|
:sectnums:
|
:sectnums:
|
== FreeRTOS Support
|
== FreeRTOS Support
|
|
|
A NEORV32-specific port and a simple demo for FreeRTOS (https://github.com/FreeRTOS/FreeRTOS) are
|
A NEORV32-specific port and a simple demo for FreeRTOS (https://github.com/FreeRTOS/FreeRTOS) are
|
available in the `sw/example/demo_freeRTOS` folder.
|
available in the `sw/example/demo_freeRTOS` folder. See the according documentation (`sw/example/demo_freeRTOS/README.md`)
|
|
for more information.
|
See the according documentation (`sw/example/demo_freeRTOS/README.md`) for more information.
|
|
|
|
|
|
|
|
// ####################################################################################################################
|
// ####################################################################################################################
|
:sectnums:
|
:sectnums:
|
Line 931... |
Line 961... |
The NEORV32 Processor passes the according tests provided by the official RISC-V Architecture Test Suite
|
The NEORV32 Processor passes the according tests provided by the official RISC-V Architecture Test Suite
|
(V2.0+), which is available online at GitHub: https://github.com/riscv/riscv-arch-test
|
(V2.0+), which is available online at GitHub: https://github.com/riscv/riscv-arch-test
|
|
|
All files required for executing the test framework on a simulated instance of the processor (including port
|
All files required for executing the test framework on a simulated instance of the processor (including port
|
files) are located in the `riscv-arch-test` folder in the root directory of the NEORV32 repository. Take a
|
files) are located in the `riscv-arch-test` folder in the root directory of the NEORV32 repository. Take a
|
look at the provided `riscv-arch-test/README.md` (https://github.com/stnolting/neorv32/blob/master/riscv-arch-test/README.md[online at GitHunb])
|
look at the provided `riscv-arch-test/README.md` (https://github.com/stnolting/neorv32/blob/master/riscv-arch-test/README.md[online at GitHub])
|
file for more information on how to run the tests and how testing is conducted in detail.
|
file for more information on how to run the tests and how testing is conducted in detail.
|
|
|
|
|
|
|
|
|
// ####################################################################################################################
|
// ####################################################################################################################
|
:sectnums:
|
:sectnums:
|
== Debugging using the On-Chip Debugger
|
== Debugging using the On-Chip Debugger
|
|
|
The NEORV32 https://stnolting.github.io/neorv32/#_on_chip_debugger_ocd[Documentation: On-Chip Debugger]
|
The NEORV32 on-chip debugger allows _online_ in-system debugging via an external JTAG access port from a
|
allows _online_ in-system debugging via an external JTAG access port from a
|
|
host machine. The general flow is independent of the host machine's operating system. However, this tutorial uses
|
host machine. The general flow is independent of the host machine's operating system. However, this tutorial uses
|
Windows and Linux (Ubuntu on Windows) in parallel.
|
Windows and Linux (Ubuntu on Windows) in parallel.
|
|
|
|
[TIP]
|
|
See datasheet section https://stnolting.github.io/neorv32/#_on_chip_debugger_ocd[On Chip Debugger (OCD)]
|
|
for more information.
|
|
|
[NOTE]
|
[NOTE]
|
This tutorial uses `gdb` to **directly upload an executable** to the processor. If you are using the default
|
This tutorial uses `gdb` to **directly upload an executable** to the processor. If you are using the default
|
processor setup _with_ internal instruction memory (IMEM) make sure it is implemented as RAM
|
processor setup _with_ internal instruction memory (IMEM) make sure it is implemented as RAM
|
(_MEM_INT_IMEM_ROM_ generic = false).
|
(_INT_BOOTLOADER_EN_ generic = true).
|
|
|
|
|
:sectnums:
|
:sectnums:
|
=== Hardware Requirements
|
=== Hardware Requirements
|
|
|
Line 1038... |
Line 1071... |
|
|
This will generate an ELF file `main.elf` that contains all the symbols required for debugging.
|
This will generate an ELF file `main.elf` that contains all the symbols required for debugging.
|
Furthermore, an assembly listing file `main.asm` is generated that we will use to define breakpoints.
|
Furthermore, an assembly listing file `main.asm` is generated that we will use to define breakpoints.
|
|
|
Open another terminal in `sw/example/blink_led` and start `gdb`.
|
Open another terminal in `sw/example/blink_led` and start `gdb`.
|
The GNU debugger is part of the toolchain (see <<_toolchain_setup>>).
|
The GNU debugger is part of the toolchain (see <<_software_toolchain_setup>>).
|
|
|
.Starting GDB (on Linux (Ubuntu on Windows))
|
.Starting GDB (on Linux (Ubuntu on Windows))
|
[source, bash]
|
[source, bash]
|
--------------------------
|
--------------------------
|
.../neorv32/sw/example/blink_led$ riscv32-unknown-elf-gdb
|
.../neorv32/sw/example/blink_led$ riscv32-unknown-elf-gdb
|