URL
https://opencores.org/ocsvn/neorv32/neorv32/trunk
Subversion Repositories neorv32
[/] [neorv32/] [trunk/] [docs/] [userguide/] [content.adoc] - Rev 60
Go to most recent revision | Compare with Previous | Blame | View Log
Let's Get It Started!
To make your NEORV32 project run, follow the guides from the upcoming sections. Follow these guides
step by step and in the presented order.
:sectnums:
== Toolchain Setup
There are two possibilities to get the actual RISC-V GCC toolchain:
1. Download and _build_ the official RISC-V GNU toolchain yourself
2. Download and install a prebuilt version of the toolchain
[NOTE]
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
makefile(s) according to your needs or define this variable when invoking the makefile.
[IMPORTANT]
Keep in mind that – for instance – a rv32imc toolchain only provides library code compiled with
compressed (_C_) and `mul`/`div` instructions (_M_)! Hence, this code cannot be executed (without
emulation) on an architecture without these extensions!
:sectnums:
=== 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.
The official RISC-V repository uses submodules. You need the `--recursive` option to fetch the submodules
automatically:
[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:
[source,bash]
----
riscv-gnu-toolchain$ ./configure --prefix=/opt/riscv --with-arch=rv32i –-with-abi=ilp32
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:
=== Downloading and Installing a Prebuilt Toolchain
Alternatively, you can download a prebuilt toolchain.
**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
GitHub. You can directly download the according toolchain archive as single _zip-file_ within a packed
release from github.com/stnolting/riscv-gcc-prebuilt.
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
the repository's README.
**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 -
even for Windows.
[IMPORTANT]
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`).
:sectnums:
=== Installation
Now you have the 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.
[source,bash]
----
$ export PATH:$PATH:/opt/riscv/bin
----
You should add this command to your `.bashrc` (if you are using bash) to automatically add the RISC-V
toolchain at every console start.
:sectnums:
=== Testing the Installation
To make sure everything works fine, navigate to an example project in the NEORV32 example folder and
execute the following command:
[source,bash]
----
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.
<<<
// ####################################################################################################################
:sectnums:
== General Hardware Setup
The following steps are required to generate a bitstream for your FPGA board. If you want to run the
NEORV32 processor in simulation only, the following steps might also apply.
[TIP]
Check out the example setups in the `boards` folder (@GitHub: https://github.com/stnolting/neorv32/tree/master/boards), which provides script-based
demo projects for various FPGA boars.
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
evaluation or "hello world" project to check out the NEORV32. A little note: The order of the following
steps might be a little different for your specific EDA tool.
[start=0]
. 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
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
provide a field to enter the library name, check out the "properties" menu of the rtl files.
. 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.
. 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
`rtl/core/top_templates/neorv32_test_setup.vhd` VHDL file to your project too, and
select it as top entity.
. This test setup provides a minimal test hardware setup:
.NEORV32 "hello world" test setup
image::neorv32_test_setup.png[align=center]
[start=7]
. This test setup 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
`rstn_i` is **low-active**.
. The configuration of the NEORV32 processor is done using the generics of the instantiated processor
top entity. Let's keep things simple at first and use the default configuration:
.Cut-out of `neorv32_test_setup.vhd` showing the processor instance and its configuration
[source,vhdl]
----
neorv32_top_inst: neorv32_top
generic map (
-- General --
CLOCK_FREQUENCY => 100000000, -- in Hz # <1>
BOOTLOADER_EN => true,
USER_CODE => x"00000000",
...
-- Internal instruction memory --
MEM_INT_IMEM_EN => true,
MEM_INT_IMEM_SIZE => 16*1024, # <2>
MEM_INT_IMEM_ROM => false,
-- Internal data memory --
MEM_INT_DMEM_EN => true,
MEM_INT_DMEM_SIZE => 8*1024, # <3>
...
----
<1> Clock frequency of `clk_i` in Hertz
<2> Default size of internal instruction memory: 16kB (no need to change that _now_)
<3> Default size of internal data memory: 8kB (no need to change that _now_)
[start=9]
. There is one generic that has to be set according to your FPGA / board: The clock frequency 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").
. If you feel like it – or if your FPGA does not provide so 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
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.
[NOTE]
Keep the internal instruction and data memory sizes in mind – these values are required for setting
up the software framework in the next section <<_general_software_framework_setup>>.
[start=11]
. 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:
.Entity signals of `neorv32_test_setup.vhd`
[source,vhdl]
----
entity neorv32_test_setup is
port (
-- Global control --
clk_i : in std_ulogic := '0'; -- global clock, rising edge
rstn_i : in std_ulogic := '0'; -- global reset, low-active, async
-- GPIO --
gpio_o : out std_ulogic_vector(7 downto 0); -- parallel output
-- UART0 --
uart0_txd_o : out std_ulogic; -- UART0 send data
uart0_rxd_i : in std_ulogic := '0' -- UART0 receive data
);
end neorv32_test_setup;
----
[start=12]
. 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
**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
the signal when your LEDs are low-active) - this LED will be used as status LED by the bootloader.
. Finally, connect the primary UART's (UART0) communication signals `uart0_txd_o` and
`uart0_rxd_i` to your serial host interface (USB-to-serial converter).
. 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
make sure everything is sync).
. 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:
== General Software Framework Setup
While your synthesis tool is crunching the NEORV32 HDL files, it is time to configure the project's software
framework for your processor hardware setup.
[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
beginning of the linker script you will find the **MEMORY** configuration showing two regions: `rom` and `ram`
.Cut-out of the linker script `neorv32.ld`: Memory configuration
[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 # <2>
}
----
<1> Size of internal instruction memory (IMEM): 16kB
<2> Size of internal data memory (DMEM): 8kB
[WARNING]
The `rom` region provides conditional assignments (via the _make_bootloader_ symbol) for the _origin_
and the _length_ configuration depending on whether the executable is built as normal application (for the IMEM) or
as bootloader code (for the BOOTROM). To modify the IMEM configuration of the `rom` region,
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]
The `rom` _ORIGIN_ parameter has to be equal to the configuration of the NEORV32 ispace_base_c
(default: 0x00000000) VHDL package (`rtl/core/neorv32_package.vhd`) configuration constant. The `ram` _ORIGIN_ parameter has to
be equal to the configuration of the NEORV32 `dspace_base_c` (default: 0x80000000) VHDL
package (`rtl/core/neorv32_package.vhd`) configuration constant.
[IMPORTANT]
The `rom` _LENGTH_ and the `ram` _LENGTH_ parameters have to match the configured memory sizes. For
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:
== Application Program Compilation
[start=1]
. 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
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:
[source,bash]
----
neorv32/sw/example/blink_led$ make exe
----
[start=3]
. 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
final executable. The makefile will show the resulting memory utilization and the executable size:
[source,bash]
----
neorv32/sw/example/blink_led$ make exe
Memory utilization:
text data bss dec hex filename
852 0 0 852 354 main.elf
Executable (neorv32_exe.bin) size in bytes:
864
----
[start=4]
. That's it. The `exe` target has created the actual executable `neorv32_exe.bin` in the current
folder, which is ready to be uploaded to the processor via the bootloader's UART interface.
[TIP]
The compilation process will also create a `main.asm` assembly listing file in the project directory, which
shows the actual assembly code of the complete application.
<<<
// ####################################################################################################################
:sectnums:
== 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
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**
The "better" option is to use a standard terminal program to upload an executable. This provides a more
comfortable way as you can directly interact with the bootloader console. Additionally, using a terminal program
also allows to directly communicate with the uploaded application.
[start=1]
. Connect the primary UART (UART0) interface of your FPGA board to a serial port of your
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 from https://ttssh2.osdn.jp/index.html.en
[WARNING]
Make sure your terminal program can transfer the executable in raw byte mode without any protocol stuff around it.
[start=3]
. Open a connection to the corresponding srial port. Configure the terminal according to the
following parameters:
* 19200 Baud
* 8 data bits
* 1 stop bit
* no parity bits
* no transmission/flow control protocol! (just raw byte mode)
* newline on `\r\n` (carriage return & newline)
[start=4]
. Also make sure, that single chars are transmitted without any consecutive "new line" or "carriage
return" commands (this is highly dependent on your terminal application of choice, TeraTerm only
sends the raw chars by default).
. 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
automatic boot sequence and to start the actual bootloader user interface console.
.Bootloader console; aborted auto-boot sequence
[source,bash]
----
<< NEORV32 Bootloader >>
BLDV: Mar 23 2021
HWV: 0x01050208
CLK: 0x05F5E100
USER: 0x10000DE0
MISA: 0x40901105
ZEXT: 0x00000023
PROC: 0x0EFF0037
IMEM: 0x00004000 bytes @ 0x00000000
DMEM: 0x00002000 bytes @ 0x80000000
Autoboot in 8s. Press key to abort.
Aborted.
Available commands:
h: Help
r: Restart
u: Upload
s: Store to flash
l: Load from flash
e: Execute
CMD:>
----
[start=6]
. Execute the "Upload" command by typing `u`. Now the bootloader is waiting for a binary executable
to be send.
[source,bash]
----
CMD:> u
Awaiting neorv32_exe.bin...
----
[start=7]
. Use the "send file" option of your terminal program to transmit the previously generated binary executable `neorv32_exe.bin`.
. Again, make sure to transmit the executable in raw binary mode (no transfer protocol, no additional
header stuff). When using TeraTerm, select the "binary" option in the send file dialog.
. If everything went fine, OK will appear in your terminal:
[source,bash]
----
CMD:> u
Awaiting neorv32_exe.bin... OK
----
[start=10]
. The executable now resides in the instruction memory of the processor. To execute the program right
now run the "Execute" command by typing `e`:
[source,bash]
----
CMD:> u
Awaiting neorv32_exe.bin... OK
CMD:> e
Booting...
Blinking LED demo program
----
[start=11]
. Now you should see the LEDs counting.
<<<
// ####################################################################################################################
:sectnums:
== Setup of a New Application Program Project
Done with all the introduction tutorials and those example programs? Then it is time to start your own
application project!
[start=1]
. The easiest way of creating a *new* project is to make a copy of an *existing* project (like the
`blink_led` project) inside the `sw/example` folder. By this, all file dependencies are kept and you can
start coding and compiling.
. If you want to place the project folder somewhere else you need to adapt the project's makefile. In
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:
[source,makefile]
----
# Relative or absolute path to the NEORV32 home folder (use default if not set by user)
NEORV32_HOME ?= ../../..
----
[start=3]
. If your project contains additional source files outside of the project folder, you can add them to the _APP_SRC_ variable:
[source,makefile]
----
# User's application sources (add additional files here)
APP_SRC = $(wildcard *.c) ../somewhere/some_file.c
----
[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):
[source,makefile]
----
# User's application include folders (don't forget the '-I' before each entry)
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:
== 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
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
"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.
[source,makefile]
----
# CPU architecture and ABI
MARCH = -march=rv32i # <1>
MABI = -mabi=ilp32 # <2>
----
<1> MARCH = Machine architecture ("ISA string")
<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
to add the 'c' extension also to the _MARCH_ ISA string.
You can also override the default _MARCH_ and _MABI_ configurations from the makefile when invoking the makefile:
[source,bash]
----
$ make MARCH=-march=rv32ic clean_all all
----
[NOTE]
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.
<<<
// ####################################################################################################################
:sectnums:
== Building a Non-Volatile Application without External Boot Memory
The primary purpose of the bootloader is to allow an easy and fast update of the current application. In particular, this is very handy
during the development stage of a project as you can upload modified programs at any time via the UART.
Maybe at some time your project has become mature and you want to actually _embed_ your processor
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:
* 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, compile your application code by running the `make install` command:
[source,bash]
----
neorv32/sw/example/blink_led$ make compile
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]
. The `install` target has created an executable, too, but this time also in the form of a VHDL memory
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]
----
BOOTLOADER_EN => false, -- implement processor-internal bootloader? # <1>
----
<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]
----
MEM_INT_IMEM_ROM => true, -- implement processor-internal instruction memory as ROM
----
[start=6]
. Perform a new synthesis and upload your bitstream. Your application code now resides unchangeable
in the processor's IMEM and is directly executed after reset.
<<<
// ####################################################################################################################
:sectnums:
== Customizing the Internal Bootloader
The bootloader provides several configuration options to customize it for your specific applications. The
most important user-defined configuration options are available as C `#defines` right at the beginning of the
bootloader source code `sw/bootloader/bootloader.c`):
.Cut-out from the bootloader source code `bootloader.c`: configuration parameters
[source,c]
----
/** 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**
The NEORV32 default bootloader uses 4kB of storage. This is also the default size of the BOOTROM memory component.
If your new/modified bootloader exceeds this size, you need to modify the boot ROM configurations.
[start=1]
. Open the processor's main package file `rtl/core/neorv32_package.vhd` and edit the
`boot_size_c` constant according to your requirements. The boot ROM size must not exceed 32kB
and should be a power of two (for optimal hardware mapping).
[source,vhdl]
----
-- Bootloader ROM --
constant boot_size_c : natural := 4*1024; -- bytes
----
[start=2]
. Now open the NEORV32 linker script `sw/common/neorv32.ld` and adapt the _LENGTH_ parameter
of the `rom` according to your new memory size. `boot_size_c` and the `rom` _LENGTH_ attribute have to be always
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**
Whenever you have modified the bootloader you need to recompile and re-install it and re-synthesize your design.
[start=1]
. Compile and install the bootloader using the explicit `bootloader` makefile target.
[source,bash]
----
neorv32/sw/bootloader$ make bootloader
----
[start=1]
. Now perform a new synthesis / HDL compilation to update the bitstream with the new bootloader
image (some synthesis tools also allow to only update the BRAM initialization without re-running
the entire synthesis process).
[NOTE]
The bootloader is intended to work regardless of the actual NEORV32 hardware configuration –
especially when it comes to CPU extensions. Hence, the bootloader should be build using the
minimal `rv32i` ISA only (`rv32e` would be even better).
<<<
// ####################################################################################################################
:sectnums:
== Programming an External SPI Flash via the Bootloader
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]
. 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 program image, 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 uploaded is completed and "OK"
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):
[source]
----
CMD:> u
Awaiting neorv32_exe.bin... OK
CMD:> p
Write 0x000013FC bytes to SPI flash @ 0x00800000? (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. See section <<_external_spi_flash_for_booting> for more information on how to configure the base address.
[source]
----
CMD:> u
Awaiting neorv32_exe.bin... OK
CMD:> p
Write 0x000013FC bytes to SPI flash @ 0x00800000? (y/n) y
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.
<<<
// ####################################################################################################################
:sectnums:
== Simulating the Processor
**Testbench**
The NEORV32 project features a simple default testbench (`sim/neorv32_tb.vhd`) that can be used to simulate
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
combinatorial (looped) oscillator architecture).
The simulation setup is configured via the "User Configuration" section located right at the beginning of
the testbench's architecture. Each configuration constant provides comments to explain the functionality.
Besides the actual NEORV32 Processor, the testbench also simulates "external" components that are connected
to the processor's external bus/memory interface. These components are:
* an external instruction memory (that also allows booting from it)
* an external data memory
* an external memory to simulate "external IO devices"
* a memory-mapped registers to trigger the processor's interrupt signals
The following table shows the base addresses of these four components and their default configuration and
properties (attributes: `r` = read, `w` = write, `e` = execute, `a` = atomic accesses possible, `8` = byte-accessible, `16` =
half-word-accessible, `32` = word-accessible).
.Testbench: processor-external memories
[cols="^4,>3,^5,<11"]
[options="header",grid="rows"]
|=======================
| Base address | Size | Attributes | Description
| `0x00000000` | `imem_size_c` | `r/w/e, a, 8/16/32` | external IMEM (initialized with application image)
| `0x80000000` | `dmem_size_c` | `r/w/e, a, 8/16/32` | external DMEM
| `0xf0000000` | 64 bytes | `r/w/e, !a, 8/16/32` | external "IO" memory, atomic accesses will fail
| `0xff000000` | 4 bytes | `-/w/-, a, -/-/32` | memory-mapped register to trigger "machine external", "machine software" and "SoC Fast Interrupt" interrupts
|=======================
The simulated NEORV32 does not use the bootloader and directly boots the current application image (from
the `rtl/core/neorv32_application_image.vhd` image file). Make sure to use the `all` target of the
makefile to install your application as VHDL image after compilation:
[source, bash]
----
sw/example/blink_led$ make clean_all all
----
.Simulation-Optimized CPU/Processors Modules
[NOTE]
The `sim/rtl_modules` folder provides simulation-optimized versions of certain CPU/processor modules.
These alternatives can be used to replace the default CPU/processor HDL files to allow faster/easier/more
efficient simulation. **These files are not intended for synthesis!**
**Simulation Console Output**
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
(`neorv32.testbench_uart0.out` for UART0, `neorv32.testbench_uart1.out` for UART1) inside the simulator home folder.
**Faster Simulation Console Output**
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**
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)]).
ASCII data send to UART0 will be immediately printed to the simulator console. Additionally, the
ASCII data is logged in a file (`neorv32.uart0.sim_mode.text.out`) in the simulator home folder. All
written 32-bit data is also dumped as 8-char hexadecimal value into a file
(`neorv32.uart0.sim_mode.data.out`) also in the simulator home folder.
ASCII data send to UART1 will be immediately printed to the simulator console. Additionally, the
ASCII data is logged in a file (`neorv32.uart1.sim_mode.text.out`) in the simulator home folder. All
written 32-bit data is also dumped as 8-char hexadecimal value into a file
(`neorv32.uart1.sim_mode.data.out`) also in the simulator home folder.
You can "automatically" enable the simulation mode of UART0/UART1 when compiling an application. In this case the
"real" UART0/UART1 transmitter unit is permanently disabled. To enable the simulation mode just compile
and install your application and add _UART0_SIM_MODE_ for UART0 and/or _UART1_SIM_MODE_ for UART1 to
the compiler's _USER_FLAGS_ variable (do not forget the `-D` suffix flag):
[source, bash]
----
sw/example/blink_led$ make USER_FLAGS+=-DUART0_SIM_MODE clean_all all
----
The provided define will change the default UART0/UART1 setup function in order to set the simulation mode flag in the according UART's control register.
[NOTE]
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).
**Simulation with Xilinx Vivado**
The project features default a Vivado simulation waveform configuration in `sim/vivado`.
**Simulation with GHDL**
To simulate the processor using _GHDL_ navigate to the `sim` folder and run the provided shell script. All arguments are passed to GHDL.
For example the simulation time can be configured using `--stop-time=4ms` as argument.
[source, bash]
----
neorv32/sim$ sh ghdl_sim.sh --stop-time=4ms
----
<<<
// ####################################################################################################################
:sectnums:
== Building the Documentation
The documentation is written using `asciidoc`. The according source files 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
or as PDF documents.
[TIP]
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 makefile provides a help target to show all available build options and their according outputs.
[source,bash]
----
neorv32$ make help
----
.Example: Generate HTML documentation (data sheet) using `asciidoctor`
[source,bash]
----
neorv32$ make html
----
[TIP]
If you don't have `asciidoctor` / `asciidoctor-pdf` installed, you can still generate all the documentation using
a _docker container_ via `make container`.
// ####################################################################################################################
:sectnums:
== Building the Project Documentation
<<<
// ####################################################################################################################
:sectnums:
== FreeRTOS Support
A NEORV32-specific port and a simple demo for FreeRTOS (https://github.com/FreeRTOS/FreeRTOS) are
available in the `sw/example/demo_freeRTOS` folder.
See the according documentation (`sw/example/demo_freeRTOS/README.md`) for more information.
// ####################################################################################################################
:sectnums:
== RISC-V Architecture Test Framework
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
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
look at the provided `riscv-arch-test/README.md` (https://github.com/stnolting/neorv32/blob/master/riscv-arch-test/README.md[online at GitHunb])
file for more information on how to run the tests and how testing is conducted in detail.
<<<
// ####################################################################################################################
:sectnums:
== Debugging using the On-Chip Debugger
The NEORV32 https://stnolting.github.io/neorv32/#_on_chip_debugger_ocd[Documentation: On-Chip Debugger]
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
Windows and Linux (Ubuntu on Windows) in parallel.
[NOTE]
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
(_MEM_INT_IMEM_ROM_ generic = false).
:sectnums:
=== Hardware Requirements
Make sure the on-chip debugger of your NEORV32 setups is implemented (_ON_CHIP_DEBUGGER_EN_ generic = true).
Connect a JTAG adapter to the NEORV32 `jtag_*` interface signals. If you do not have a full-scale JTAG adapter, you can
also use a FTDI-based adapter like the "FT2232H-56Q Mini Module", which is a simple and inexpensive FTDI breakout board.
.JTAG pin mapping
[cols="^3,^2,^2"]
[options="header",grid="rows"]
|=======================
| NEORV32 top signal | JTAG signal | FTDI port
| `jtag_tck_i` | TCK | D0
| `jtag_tdi_i` | TDI | D1
| `jtag_tdo_o` | TDO | D2
| `jtag_tms_i` | TMS | D3
| `jtag_trst_i` | TRST | D4
|=======================
[TIP]
The low-active JTAG _test reset_ (TRST) signals is _optional_ as a reset can also be triggered via the TAP controller.
If TRST is not used make sure to pull the signal _high_.
:sectnums:
=== OpenOCD
The NEORV32 on-chip debugger can be accessed using the https://github.com/riscv/riscv-openocd[RISC-V port of OpenOCD].
Prebuilt binaries can be obtained - for example - from https://www.sifive.com/software[SiFive]. A pre-configured
OpenOCD configuration file (`sw/openocd/openocd_neorv32.cfg`) is available that allows easy access to the NEORV32 CPU.
[NOTE]
You might need to adapt `ftdi_vid_pid`, `ftdi_channel` and `ftdi_layout_init` in `sw/openocd/openocd_neorv32.cfg`
according to your interface chip and your operating system.
[TIP]
If you want to modify the JTAG clock speed (via `adapter speed` in `sw/openocd/openocd_neorv32.cfg`) make sure to meet
the clock requirements noted in https://stnolting.github.io/neorv32/#_debug_module_dm[Documentation: Debug Transport Module (DTM)].
To access the processor using OpenOCD, open a terminal and start OpenOCD with the pre-configured configuration file.
.Connecting via OpenOCD (on Windows)
[source, bash]
--------------------------
N:\Projects\neorv32\sw\openocd>openocd -f openocd_neorv32.cfg
Open On-Chip Debugger 0.11.0-rc1+dev (SiFive OpenOCD 0.10.0-2020.12.1)
Licensed under GNU GPL v2
For bug reports:
https://github.com/sifive/freedom-tools/issues
1
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 1000 kHz
Info : JTAG tap: neorv32.cpu tap/device found: 0x0cafe001 (mfg: 0x000 (<invalid>), part: 0xcafe, ver: 0x0)
Info : datacount=1 progbufsize=2
Info : Disabling abstract command reads from CSRs.
Info : Examined RISC-V core; found 1 harts
Info : hart 0: XLEN=32, misa=0x40801105
Info : starting gdb server for neorv32.cpu.0 on 3333
Info : Listening on port 3333 for gdb connections
--------------------------
OpenOCD has successfully connected to the NEORV32 on-chip debugger and has examined the CPU (showing the content of
the `misa` CSRs). Now you can use `gdb` to connect via port 3333.
:sectnums:
=== Debugging with GDB
This guide uses the simple "blink example" from `sw/example/blink_led` as simplified test application to
show the basics of in-system debugging.
At first, the application needs to be compiled. We will use the minimal machine architecture configuration
(`rv32i`) here to be independent of the actual processor/CPU configuration.
Navigate to `sw/example/blink_led` and compile the application:
.Compile the test application
[source, bash]
--------------------------
.../neorv32/sw/example/blink_led$ make MARCH=-march=rv32i clean_all all
--------------------------
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.
Open another terminal in `sw/example/blink_led` and start `gdb`.
The GNU debugger is part of the toolchain (see <<_toolchain_setup>>).
.Starting GDB (on Linux (Ubuntu on Windows))
[source, bash]
--------------------------
.../neorv32/sw/example/blink_led$ riscv32-unknown-elf-gdb
GNU gdb (GDB) 10.1
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=riscv32-unknown-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb)
--------------------------
Now connect to OpenOCD using the default port 3333 on your local machine.
Set the ELF file we want to debug to the recently generated `main.elf` from the `blink_led` example.
Finally, upload the program to the processor.
[NOTE]
The executable that is uploaded to the processor is **not** the default NEORV32 executable (`neorv32_exe.bin`) that
is used for uploading via the bootloader. Instead, all the required sections (like `.text`) are extracted from `mail.elf`
by GDB and uploaded via the debugger's indirect memory access.
.Running GDB
[source, bash]
--------------------------
(gdb) target remote localhost:3333 <1>
Remote debugging using localhost:3333
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
0xffff0c94 in ?? () <2>
(gdb) file main.elf <3>
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from main.elf...
(gdb) load <4>
Loading section .text, size 0xd0c lma 0x0
Loading section .rodata, size 0x39c lma 0xd0c
Start address 0x00000000, load size 4264
Transfer rate: 43 KB/sec, 2132 bytes/write.
(gdb)
--------------------------
<1> Connect to OpenOCD
<2> The CPU was still executing code from the bootloader ROM - but that does not matter here
<3> Select `mail.elf` from the `blink_led` example
<4> Upload the executable
After the upload, GDB will make the processor jump to the beginning of the uploaded executable
(by default, this is the beginning of the instruction memory at `0x00000000`) skipping the bootloader
and halting the CPU right before executing the `blink_led` application.
:sectnums:
==== Breakpoint Example
The following steps are just a small showcase that illustrate a simple debugging scheme.
While compiling `blink_led`, an assembly listing file `main.asm` was generated.
Open this file with a text editor to check out what the CPU is going to do when resumed.
The `blink_led` example implements a simple counter on the 8 lowest GPIO output ports. The program uses
"busy wait" to have a visible delay between increments. This waiting is done by calling the `neorv32_cpu_delay_ms`
function. We will add a _breakpoint_ right at the end of this wait function so we can step through the iterations
of the counter.
.Cut-out from `main.asm` generated from the `blink_led` example
[source, assembly]
--------------------------
00000688 <__neorv32_cpu_delay_ms_end>:
688: 01c12083 lw ra,28(sp)
68c: 02010113 addi sp,sp,32
690: 00008067 ret
--------------------------
The very last instruction of the `neorv32_cpu_delay_ms` function is `ret` (= return)
at hexadecimal `690` in this example. Add this address as _breakpoint_ to GDB.
[NOTE]
The address might be different if you use a different version of the software framework or
if different ISA options are configured.
.Adding a GDB breakpoint
[source, bash]
--------------------------
(gdb) b * 0x690
Breakpoint 1 at 0x690
--------------------------
Now execute `c` (= continue). The CPU will resume operation until it hits the break-point.
By this we can "step" from increment to increment.
.Iterating from breakpoint to breakpoint
[source, bash]
--------------------------
Breakpoint 1 at 0x690
(gdb) c
Continuing.
Breakpoint 1, 0x00000690 in neorv32_cpu_delay_ms ()
(gdb) c
Continuing.
Breakpoint 1, 0x00000690 in neorv32_cpu_delay_ms ()
(gdb) c
Continuing.
--------------------------
include::../legal.adoc[]
Go to most recent revision | Compare with Previous | Blame | View Log