Introduction to FPGA Part 11 - RISC-V Softcore Processor
2022-01-24 | By ShawnHymel
License: Attribution
A field-programmable gate array (FPGA) is a reconfigurable integrated circuit (IC) that lets you implement a wide range of custom digital circuits. Throughout the series, we will examine how an FPGA works as well as demonstrate the basic building blocks of implementing digital circuits using the Verilog hardware description language (HDL).
RISC-V is an open-source instruction set architecture (ISA) that allows anyone to implement a central processing unit (CPU) or system-on-a-chip (SOC) design without paying a licensing fee. As a result, it is popular with FPGA enthusiasts as the starting point for a softcore processor implementation.
In the previous tutorial, we saw how to implement a first-in, first-out (FIFO) system to mitigate the effects of metastability. This time, we will load a pre-existing RISC-V implementation onto our FPGA and modify the design to allow for button inputs.
Video
If you have not done so, please watch the following video, which explains the concepts required to complete the challenge. It also demonstrates a working version of the challenge:
Required Hardware
For this challenge, you will need the following hardware:
- FPGA development board based on the Lattice iCE40. I recommend the iCEstick for this series. However, any of the development boards listed as “supported” by the apio project should work.
- Breadboard
- Pushbuttons
- Jumper wires
- (Optional) USB extension cable
Hardware Connections
The PMOD connector at the end of the iCEstick has the following pinout:
A full pinout of the iCEstick can be found here.
Connect 4 pushbuttons to the iCEstick as follows. Note that you do not need pull-up resistors on the buttons. We will use the internal pull-up resistors available in the FPGA.
Resources
The following datasheets and guides might be helpful as you tackle the challenges:
- GitHub repository - contains all examples and solutions for this series
- learn-fpga GitHub repository - contains Bruno Levy’s RISC-V implementation (FemtoRV)
- Verilog documentation
- Apio tool usage
- iCE40 LP/HX Datasheet
- iCEstick Evaluation Kit User’s Guide
Build FemtoRV
Find your board on this README page (note that the FemtoRV has limited support at this time, and I’ll be showing how to use FemtoRV with the iCEstick). Follow the directions to install the required libraries and drivers. Note that you will need Linux (I recommend Raspberry Pi OS or Ubuntu) to build this project.
When you are done installing all of the dependencies and libraries, you’ll want to make some slight configuration changes, as we are not using IRDA, SSD13151 (OLED driver), or MAX7219 (LED matrix driver). Start in the FemtoRV directory:
cd ~/Projects/fpga/learn-fpga/FemtoRV
nano RTL/CONFIGS/icestick_config.v
Comment out the following lines (leave everything else alone):
`define NRV_IO_LEDS
//`define NRV_IO_IRDA
`define NRV_IO_UARD
//`define NRV_IO_SSD13151
//`define NRV_IO_MAX7219
`define NRV_MAPPED_SPI_FLASH
Save and exit. From the FemtoRV directory, call the following:
make ICESTICK
This will take some time. The first time you run it, it will download and install the RISC-V build system (so you can compile C/C code).
Challenge
Your challenge is to make buttons work on the FemtoRV. By default, buttons aren’t defined for the iCEstick, so you will need to make some changes to the FemtoRV Verilog code. Hint: look at the port list in femtosoc.v to get an idea of what you need to change. Also, the `-pullup yes` parameter does not work with the build tool used, so you’ll have to use an external pullup or define the internal pullup with the SB_IO directive (you can read about it in this Lattice doc).
Write a simple C program to test your button functionality. For example, whenever a button is held, make the LEDs blink or count up.
Solution
Spoilers below! I highly encourage you to try the challenge on your own before comparing your answer to mine. Note that my solution may not be the only way to solve the challenge.
Navigate to the FemtoRV directory and modify the PCF file:
cd ~/Projects/fpga/learn-fpga/FemtoRV
nano BOARDS/icestick.pcf
There, comment out the OLED and LED matrix pins. Add new button pin definitions.
set_io pclk 21
#set_io oled_DIN 91
#set_io oled_CLK 90
#set_io oled_CS 88
#set_io oled_DC 87
#set_io oled_RST 78
set_io D1 99
set_io D2 98
set_io D3 97
set_io D4 96
set_io D5 95
set_io TXD 8
set_io RXD 9
#set_io ledmtx_DIN 81
#set_io ledmtx_CS 80
#set_io ledmtx_CLK 79
set_io spi_cs_n 71
set_io spi_miso 68
set_io spi_mosi 67
set_io spi_clk 70
set_io RESET 47
set_io irda_TXD 105
set_io irda_RXD 106
set_io irda_SD 107
set_io pmod[0] 78
set_io pmod[1] 79
set_io pmod[2] 80
set_io pmod[3] 81
Save and exit. Open the iCEstick configuration file:
nano RTL/CONFIGS/icestick_config.v
Define NRV_IO_BUTTONS (this tells the SOC module to instantiate the buttons module).
/************************* Devices **********************************************************************************/
`define NRV_IO_BUTTONS // Mapped IO to PMOD connector (78, 79, 80, 81)
`define NRV_IO_LEDS // Mapped IO, LEDs D1,D2,D3,D4 (D5 is used to display errors)
//`define NRV_IO_IRDA // In IO_LEDS, support for the IRDA on the IceStick (WIP)
`define NRV_IO_UART // Mapped IO, virtual UART (USB)
//`define NRV_IO_SSD1351 // Mapped IO, 128x128x64K OLED screen
//`define NRV_IO_MAX7219 // Mapped IO, 8x8 led matrix
`define NRV_MAPPED_SPI_FLASH // SPI flash mapped in address space. Use with MINIRV32 to run code from SPI flash.
Save and exit. Open the femtosoc.v file:
nano RTL/femtosoc.v
Define the pmod pins as inputs in the port list for the iCEstick:
…
`ifdef NRV_IO_BUTTONS
`ifdef ICE_FEATHER
input [3:0] buttons,
`elsif ICE_STICK
input [3:0] pmod,
`else
input [5:0] buttons,
`endif
`endif
…
In the “Buttons” section, use the SB_IO module to enable the pull-up resistors on the pmod pins. Because we defined NRV_IO_BUTTONS in the config file, the Buttons module will be instantiated, which will give us access to the buttons bus.
/********************* Buttons *************************************/
/*
* Directly wired to the buttons.
*/
`ifdef NRV_IO_BUTTONS
`ifdef ICE_STICK
wire [3:0] buttons;
SB_IO #(
.PIN_TYPE(6’b000001), // 0000: no output, 01: simple input
.PULLUP(1’b1) // Enable pull-up resistor
) sb_buttons [3:0] ( // Multiple modules
.PACKAGE_PIN({pmod[3], pmod[2], pmod[1], pmod[0]}),
.D_IN_0({buttons[3], buttons[2], buttons[1], buttons[0]})
);
`endif
wire [31:0] buttons_rdata;
Buttons buttons_driver(
.sel(io_word_address[IO_BUTTONS_bit]),
.rdata(buttons_rdata),
.BUTTONS(buttons)
);
`endif
Save and exit. Build and upload the SOC to board. Note that we use “ICESTICK” as our make target:
make ICESTICK
When that’s done, write a quick C program to test the buttons. Create a separate directory to keep your software and create main.c:
cd ~/Projects/fpga
mkdir -p femtorv32/button_test
cd femtorv32/button_test
nano main.c
Here’s one possible program. Note that we are using the IO_IN and IO_BUTTONS macros as defined in femtorv32.h. These read from the memory address associated with the buttons. This is not real memory (e.g. RAM)–it simply tells the SOC to read from the register in the Buttons module.
#include <femtorv32.h>
int main() {
int count = 0;
while (1) {
if ((IO_IN(IO_BUTTONS) & 1) == 0) {
count = (count 1) % 16;
IO_OUT(IO_LEDS, count);
delay(250);
}
}
return 0;
}
Levy created a Makefile template for us to use that handles importing header files, linking to the required libraries, and calling the RISC-V compiler. All we need to do is include it in a local Makefile:
nano Makefile
Add just the following line to that file:
include ../../learn-fpga/FemtoRV/FIRMWARE/makefile.inc
Save and exit. Use make to build the software. Note that we use “main.prog” as the target: “main” is the name of our main C file and “.prog” tells make to upload the compiled program to our board.
make main.prog
When that’s done, you should be able to hold the first button to see your LEDs counting!
Recommended Reading
The following content might be helpful if you would like to dig deeper:
Introduction to FPGA Part 1 - What is an FPGA?
Introduction to FPGA Part 2 - Toolchain Setup
Introduction to FPGA Part 3 - Getting Started with Verilog
Introduction to FPGA Part 4 - Clocks and Procedural Assignments
Introduction to FPGA Part 5 - Finite State Machine (FSM)
Introduction to FPGA Part 6 - Verilog Modules and Parameters
Introduction to FPGA Part 7 - Verilog Testbenches and Simulation
Introduction to FPGA Part 8 - Memory and Block RAM
Introduction to FPGA Part 9 - Phase-Locked Loop (PLL) and Glitches
Introduction to FPGA Part 10 - Metastability and FIFO
Introduction to FPGA Part 12 - RISC-V Custom Peripheral
Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.
Visit TechForum