neorv32 logo transparent
riscv logo

GitHub stnolting%2Fneorv32 ffbd00?style=flat square&logo=github& neorv32?longCache=true&style=flat square data%20sheet PDF ffbd00?longCache=true&style=flat square&logo=asciidoctor  HTML ffbd00?longCache=true&style=flat square user%20guide PDF ffbd00?longCache=true&style=flat square&logo=asciidoctor doxygen HTML ffbd00?longCache=true&style=flat square&logo=Doxygen

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.

1. Software Toolchain Setup

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

  2. Download and install a prebuilt version of the toolchain; this might also done via the package manager / app store of your OS

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.
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!

1.1. Building the Toolchain from Scratch

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 ISA extension configurations, it is recommend to compile for rv32i only. Please note that this minimal ISA also provides further ISA extensions like m or c. Of course you can use a multilib approach to generate toolchains for several target ISAs.

Listing 1. Configuring GCC build for rv32i (minimal ISA)
riscv-gnu-toolchain$ ./configure --prefix=/opt/riscv --with-arch=rv32i –-with-abi=ilp32
riscv-gnu-toolchain$ make

1.2. Downloading and Installing a Prebuilt Toolchain

Alternatively, you can download a prebuilt toolchain.

1.2.1. Use The Toolchain I have Build

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 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. /opt/riscv). More information about downloading and installing my prebuilt toolchains can be found in the repository’s README.

1.2.2. 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. On Linux system you might even be able to fetch a toolchain via your distribution’s package manager.

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).

1.3. Installation

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.

$ 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.

1.4. Testing the Installation

To make sure everything works fine, navigate to an example project in the NEORV32 example folder and execute the following command:

neorv32/sw/example/blink_led$ make check

This will test all the tools required for the generating NEORV32 executables. Everything is working fine if Toolchain check OK appears at the end.

2. General Hardware Setup

This guide will setup a NEORV32 project for FPGA implementation (or simulation only) from scratch

If you want to use a complete pre-defined setup to start with, check out the project’s setups folder (https://github.com/stnolting/neorv32/tree/master/setups), which provides (script-based) demo setups for various FPGA boards and toolchains.

This tutorial uses a simplified test setup of the processor to keeps things simple at the beginning as this setup is intended as evaluation or "hello world" project to check out the NEORV32.

  1. Create a new project with your FPGA EDA tool of choice.

  2. 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.

  3. 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 added rtl files.

  4. 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.

Make sure to include the neorv32 package into your design when instantiating the processor: add library neorv32; and use neorv32.neorv32_package.all; to your design unit.
  1. 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.

  2. This test setup provides a minimal test hardware setup:

neorv32 test setup
Figure 1. NEORV32 "hello world" test setup
  1. It only implements some very basic processor and CPU features. Also, only the minimum number of signals is propagated to the outer world.

  2. However, a minimal setup-specific configuration of the NEORV32 processor is required to make it run on your FPGA board of choice. Only the absolutely required modifications will be made while keeping the default configuration for the remaining configuration options:

Listing 2. Cut-out of neorv32_ProcessorTop_Test.vhd showing the processor instance and its configuration
neorv32_top_inst: neorv32_top
generic map (
  -- General --
  CLOCK_FREQUENCY   => 100000000, -- in Hz (1)
  INT_BOOTLOADER_EN => true,
  ...
  -- Internal instruction memory --
  MEM_INT_IMEM_EN   => true,
  MEM_INT_IMEM_SIZE => 16*1024, (2)
  -- Internal data memory --
  MEM_INT_DMEM_EN   => true,
  MEM_INT_DMEM_SIZE => 8*1024, (3)
  ...
1 Clock frequency of clk_i signal 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)
  1. There is one generic that has to be set according to your FPGA board setup: the actual 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").

  2. 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 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.

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.
  1. 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:

Listing 3. Entity signals of neorv32_test_setup.vhd
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;
  1. 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.

  2. 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 for the setup.

  3. Finally, if your FPGA board provides a serial host interface (USB-to-serial converter) interface, connect the UART communication signals uart0_txd_o and uart0_rxd_i.

  4. Perform the project HDL compilation (synthesis, mapping, bitstream generation).

  5. Program the generated bitstream into your FPGA and press the button connected to the reset signal.

  6. Done! The assigned status LED should be flashing now for some sections before permanently lighting up.

3. General Software Framework Setup

To allow executables to be actually executed on the NEORV32 Processor the configuration of the software framework 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.

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.
  1. Open the NEORV32 linker script sw/common/neorv32.ld with a text editor. Right at the beginning of this script you will find the MEMORY configuration listing the different memory section:

Listing 4. Cut-out of the linker script neorv32.ld: ram memory section configuration
MEMORY
{
  ram (rwx)  : ORIGIN = 0x80000000, LENGTH = 8*1024 (1)
...
1 Size of the data memory address space (internal/external DMEM); here 8kB
  1. We only need to change the ram section, which presents the available data address space. If you have changed the DMEM (MEM_INT_DMEM_SIZE generic) size adapt the LENGTH parameter of the ram section (here: 8*1024) so it is equal to your DMEM hardware configuration.

  2. Done! Save your changes and close the linker script.

Advanced: Section base address and size
More information can be found in the datasheet section Address Space.

4. 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.

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.
  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 module to display an 8-bit counter on the lowest eight bit of the gpio_o output port.

  2. To compile the project and generate an executable simply execute:

neorv32/sw/example/blink_led$ make clean_all exe
  1. We are using the clean_all taret to make sure everything is re-build.

  2. 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:

neorv32/sw/example/blink_led$ make clean_all exe
Memory utilization:
   text    data     bss     dec     hex filename
   3176       0     120    3296     ce0 main.elf
Compiling ../../../sw/image_gen/image_gen
Executable (neorv32_exe.bin) size in bytes:
3188
  1. That’s it. The exe target has created the actual executable neorv32_exe.bin in the current folder that is ready to be uploaded to the processor.

The compilation process will also create a main.asm assembly listing file in the current folder, which shows the actual assembly code of the application.

5. Uploading and Starting of a Binary Executable Image via UART

Follow this guide to use the bootloader to upload an executable via UART.

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.
Executables can also be uploaded via the on-chip debugger. See section Debugging with GDB for more information.
  1. Connect the primary UART (UART0) interface of your FPGA board to a serial port of your host computer.

  2. Start a terminal program. In this tutorial, I am using TeraTerm for Windows. You can download it fore free from https://ttssh2.osdn.jp/index.html.en

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.
  1. Open a connection to the the serial port your UART is connected to. Configure the terminal setting according to the following parameters:

    • 19200 Baud

    • 8 data bits

    • 1 stop bit

    • no parity bits

    • no transmission/flow control protocol

    • receiver (host computer) newline on \r\n (carriage return & newline)

  1. 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 sends the raw chars by default).

  2. 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.

Listing 5. Bootloader console; aborted auto-boot sequence
<< 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:>
  1. Execute the "Upload" command by typing u. Now the bootloader is waiting for a binary executable to be send.

CMD:> u
Awaiting neorv32_exe.bin...
  1. Use the "send file" option of your terminal program to send a NEORV32 executable (neorv32_exe.bin).

  2. Again, make sure to transmit the executable in raw binary mode (no transfer protocol). When using TeraTerm, select the "binary" option in the send file dialog.

  3. If everything went fine, OK will appear in your terminal:

CMD:> u
Awaiting neorv32_exe.bin... OK
  1. The executable is now in the instruction memory of the processor. To execute the program right now run the "Execute" command by typing e:

CMD:> u
Awaiting neorv32_exe.bin... OK
CMD:> e
Booting...
Blinking LED demo program
  1. If everything went fine, you should see the LEDs blinking.

The bootloader will print error codes if something went wrong. See section Bootloader of the NEORV32 datasheet for more information.
See section Programming an External SPI Flash via the Bootloader to learn how to use an external SPI flash for nonvolatile program storage.

6. 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 "boot scenario 2a" 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.

See datasheet section Boot Scenario 2 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

  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:

Listing 6. Processor top entity configuration - enable internal IMEM
  -- Internal Instruction memory --
  MEM_INT_IMEM_EN => true, -- implement processor-internal instruction memory
  1. 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.

Listing 7. Processor top entity configuration - disable internal bootloader
  -- General --
  INT_BOOTLOADER_EN => false, -- boot configuration: false = boot from int/ext (I)MEM
  1. To generate an "initialization image" for the IMEM that contains the actual application, run the install target when compiling your application:

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
  1. 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.

  2. This VHDL file is automatically copied to the core’s rtl folder (rtl/core) so it will be included for the next synthesis.

  3. Perform a new synthesis. The IMEM will be build as pre-initialized ROM (inferring embedded memories if possible).

  4. 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:

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.

7. Setup of a New Application Program Project

  1. The easiest way of creating a new software application project is to copy an existing one. This will keep all file dependencies. For example you can copy sw/example/blink_led to sw/example/flux_capacitor.

  2. If you want to place you application somewhere outside sw/example you need to adapt the application’s makefile. In the makefile you will find a variable that keeps the relative or absolute path to the NEORV32 repo home folder. Just modify this variable according to your new project’s home location:

# Relative or absolute path to the NEORV32 home folder (use default if not set by user)
NEORV32_HOME ?= ../../..
  1. If your project contains additional source files outside of the project folder, you can add them to the APP_SRC variable:

# User's application sources (add additional files here)
APP_SRC = $(wildcard *.c) ../somewhere/some_file.c
  1. You also can add a folder containing your application’s include files to the APP_INC variable (do not forget the -I prefix):

# User's application include folders (don't forget the '-I' before each entry)
APP_INC = -I . -I ../somewhere/include_stuff_folder

8. 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.

# 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, if 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 in order to make the compiler emit compressed instructions.

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:

$ make MARCH=-march=rv32ic clean_all all
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.

9. Customizing the Internal Bootloader

You can either customized the default bootloader via provided configuration options or you can write a completely new bootloader that is specialized for your application.

Keep in mind that the maximum size for the bootloader is limited to 32kB.

The most important user-defined configuration options of the default bootloader are available as C-language #defines right at the beginning of the bootloader source code (sw/bootloader/bootloader.c):

Listing 8. Cut-out from the bootloader source code bootloader.c: configuration parameters
/** 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 '#'

9.1. Re-Compiling and Re-Installing the Bootloader

Whenever you have modified the bootloader’s C sources you need to recompile and re-install it and re-synthesize your design afterwards.

Listing 9. Compile and install the bootloader using the explicit bootloader makefile target.
neorv32/sw/bootloader$ make clean_all bootloader
Advanced
You can also use the bootloader makefile target for any "normal" application. This will install that application directly to the processor’s internal boot ROM.
The bootloader is intended to work regardless of the actual NEORV32 hardware configuration – especially when it comes to CPU ISA extensions. Hence, the bootloader should be compiled using the minimal rv32i ISA only.

10. Programming an External SPI Flash via the Bootloader

The default processor-internal NEORV32 bootloader supports automatic booting from an external SPI flash. This guide shows how to write an executable to the SPI flash via the bootloader so it can be automatically fetched and executed after processor reset.

The SPI flash requirements are shown in section External SPI Flash for Booting of the datasheet.
  1. At first, reset the NEORV32 processor and wait until the bootloader start screen appears in your terminal program.

  2. Abort the auto boot sequence and start the user console by pressing any key.

  3. Press u to upload the executable that you want to store to the external flash:

CMD:> u
Awaiting neorv32_exe.bin...
  1. 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 command as this might corrupt the image):

CMD:> u
Awaiting neorv32_exe.bin... OK
CMD:> p
Write 0x000013FC bytes to SPI flash @ 0x00800000? (y/n)
  1. 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.

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.
CMD:> u
Awaiting neorv32_exe.bin... OK
CMD:> p
Write 0x000013FC bytes to SPI flash @ 0x00800000? (y/n) y
Flashing... OK
CMD:>
  1. 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.

11. Packaging the Processor as IP block for Xilinx Vivado Block Designer

WORK IN PROGRESS
This Section Is Under Construction!

FIXME!

12. Simulating the Processor

WORK IN PROGRESS
This Section Is Under Construction!

FIXME!

12.1. 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 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).

Table 1. Testbench: processor-external memories
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:

sw/example/blink_led$ make clean_all all
Simulation-Optimized CPU/Processors Modules
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.

12.2. 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 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):

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.

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).

12.3. Simulation using GHDL

To simulate the processor using GHDL navigate to the sim folder and run the provided shell script. 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.

neorv32/sim$ sh ghdl_sim.sh --stop-time=20ms

13. Building the Documentation

The documentation (datasheet + user guide) 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 build all of the documentation as HTML pages or as PDF documents.

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 GitHub Pages.

The makefile provides a help target to show all available build options and their according outputs.

neorv32$ make help
Listing 10. Example: Generate HTML documentation (data sheet) using asciidoctor
neorv32$ make html
If you don’t have asciidoctor / asciidoctor-pdf installed, you can still generate all the documentation using a docker container via make container.

14. 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.

15. 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 (online at GitHub) file for more information on how to run the tests and how testing is conducted in detail.

16. Debugging using the On-Chip Debugger

The NEORV32 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.

See datasheet section On Chip Debugger (OCD) for more information.
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 (INT_BOOTLOADER_EN generic = true).

16.1. 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.

Table 2. JTAG pin mapping
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

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.

16.2. OpenOCD

The NEORV32 on-chip debugger can be accessed using the RISC-V port of OpenOCD. Prebuilt binaries can be obtained - for example - from SiFive. A pre-configured OpenOCD configuration file (sw/openocd/openocd_neorv32.cfg) is available that allows easy access to the NEORV32 CPU.

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.
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 Documentation: Debug Transport Module (DTM).

To access the processor using OpenOCD, open a terminal and start OpenOCD with the pre-configured configuration file.

Listing 11. Connecting via OpenOCD (on Windows)
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.

16.3. 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:

Listing 12. Compile the test application
.../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 Software Toolchain Setup).

Listing 13. Starting GDB (on Linux (Ubuntu on Windows))
.../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.

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.
Listing 14. Running GDB
(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.

16.3.1. 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.

Listing 15. Cut-out from main.asm generated from the blink_led example
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.

The address might be different if you use a different version of the software framework or if different ISA options are configured.
Listing 16. Adding a GDB breakpoint
(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.

Listing 17. Iterating from breakpoint to breakpoint
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.

License

BSD 3-Clause License

Copyright (c) 2021, Stephan Nolting. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF

The NEORV32 RISC-V Processor
Copyright (c) 2021, by Dipl.-Ing. Stephan Nolting. All rights reserved.
HQ: https://github.com/stnolting/neorv32
Contact: stnolting@gmail.com
made in Hanover, Germany

Proprietary Notice

  • "GitHub" is a Subsidiary of Microsoft Corporation.

  • "Vivado" and "Artix" are trademarks of Xilinx Inc.

  • "AXI" and "AXI4-Lite" are trademarks of Arm Holdings plc.

  • "ModelSim" is a trademark of Mentor Graphics – A Siemens Business.

  • "Quartus Prime" and "Cyclone" are trademarks of Intel Corporation.

  • "iCE40", "UltraPlus" and "Radiant" are trademarks of Lattice Semiconductor Corporation.

  • "Windows" is a trademark of Microsoft Corporation.

  • "Tera Term" copyright by T. Teranishi.

  • Timing diagrams made with WaveDrom Editor.

  • "NeoPixel" is a trademark of Adafruit Industries.

  • Documentation made with asciidoctor.

Disclaimer

This project is released under the BSD 3-Clause license. No copyright infringement intended. Other implied or used projects might have different licensing – see their documentation to get more information.

This document contains links to the websites of third parties ("external links"). As the content of these websites is not under our control, we cannot assume any liability for such external content. In all cases, the provider of information of the linked websites is liable for the content and accuracy of the information provided. At the point in time when the links were placed, no infringements of the law were recognizable to us. As soon as an infringement of the law becomes known to us, we will immediately remove the link in question.

Citing

If you are using the NEORV32 or parts of the project in some kind of publication, please cite it as follows:

Listing 18. BibTeX
@misc{nolting20,
  author       = {Nolting, S.},
  title        = {The NEORV32 RISC-V Processor},
  year         = {2020},
  publisher    = {GitHub},
  journal      = {GitHub repository},
  howpublished = {\url{https://github.com/stnolting/neorv32}}
}

Acknowledgments

A big shoutout to all contributors, who helped improving this project! ❤️

RISC-V - instruction sets want to be free!