RISC-V is the new thing on the block. Here we show how to build up the lemonade stand, using only those everyday things you can find around home. We go through the basic process of compiling, assembling, linking, and loading; describe the basics how JTAG works (and how it fails); and do it all from the comfort of one's Pi that happens to be lying around collecting dust. Reduced Instruction Set Computing is a simple thing, deserving of the reduced development platform. RISC-V (RV32I) has only 47 instructions, 32 registers, and about 236 pages of reading material. By comparison, ARM-32 has 50 instructions with over 500 distinct opcodes, only 16 registers, and about 2,736 pages of reading material. Lastly, x86-32 has 81 instructions, only 8 registers, and about 2,198 pages of reading material. The basic knowledge learned from this presentation will serve both the data storage professional and the computer enthusiast well for many years - and many variants - to come.
The novice and budding hardware enthusiast, who wishes to get their hands dirty and their feet wet, yet has only a few pennies in their pocket with which to spend, will enjoy this beginner-level presentation. Fundamental basics are covered in a fun and simple way using vernacular and practices of modern technology. By the end of this talk, everyone will be able to blink a light and display [store and retrieve] "Hello, World".
This presentation will show and remind all of us just what are the basic blocks and steps necessary for any development endeavor, in a simple and easy to follow manner. All knowledge and tips are completely self-contained, without reliance on any fancy tool or third party product. Thus, the reader gains fundamental knowledge which will be transferrable in timeless fashion for many years to come.
Paul Sherman is a computer engineer in the Silicon Valley with concerns for the problems of the people of the world. He has played an active role in the evolution of the data storage industry for the past 25 years, working with companies such as Western Digital and Seagate. He has published many articles with the SAS Institute user group community on statistical problems, co-authored numerous articles and books on progressive economics, and received four U.S. patents for inventions on testing and manufacture of data storage devices. He is an avid evangelist for MRAM. He earned an MS in Physics from University of California Irvine and a BA in Physics from University of California Santa Cruz.
Presented at the Flash Memory Summit, 2022 Aug https://flashmemorysummit.com
- First Things, First
- Installing the O/S on a Raspberry PI
- Building the "Tool Chain" for RISC-V
- Wiring the Hardware
- Assembling, Compiling, Linking, and Loading
- What Can Go Wrong
- Sample Program
- Simple Terminal
- Linux Logic Analyzer
- Further Reading
- Is RISC Five as easy as Mac or PC?
- Can I do it all with one click (or key press)?
- Are all RISC Five Boards as easy as PI?
- Any Other Questions or Comments?
Low voltage supply can and/or will quickly kill an SD card, especially when it’s used in a development system (assembler, compiler, linker, loader)
Use ~5.25 V, 2.5A supply with good, thick 20 AWG cables, such as www.adafruit.com/product/1995
Prevents this
and this
To Clean an older SD card, if needed, using the Windows system:
START → Run → diskpart → List Disk → Select disk x
→ List Partition → Select partition x → Delete partition
→ Create Partition Primary → Format fs=fat32
Get the Raspberry PI Imager program from www.raspberrypi.com/software
Choose OS → Raspberry PI OS (Other) → Raspberry PI OS Lite (32-bit)
→ Choose STORAGE → Generic STORAGE DEVICE USB DEVICE
→ (gear) set hostname, uid, pwd, wifi, locale as desired
→ WRITE
Put the newly imaged SD card into the PI, plug in the PI, and follow commands below.
sudo rasp-config
→ Localization [*] en_US UTF-8
Edit two files using the sudo vi
editor. Disable bt
and wifi
to save power, and only do so if you are using a direct connection to keyboard and ethernet.
/boot/cmdline.txt : console=tty1 root=... rootfstype=ext4 fsck.repair=yes
quiet loglevel=3 logo.nologo rootwait
/boot/config.txt : disable_splash=1
dtparam=audio=off
camera-auto-detect=0
enable_uart=1
dtoverlay=[pi3-]disable-bt
dtoverlay=[pi3-]disable-wifi
The following line prevents the start-up warning message WiFi is currently blocked by rfkill. Use this command if you decide to disable bt
and wifi
.
sudo sed –i ‘2i\ \ \ \ \ \ \ \ exit 0’ /etc/profile.d/wifi-check.sh
sudo apt-get update
sudo apt-get install autoconf automake autotools-dev curl python3 git
libmpc-dev libmpfr-dev libgmp-dev
gawk build-essential bison flex texinfo gperf
libtool patchutils bc zlib1g-dev libexpat-dev
libfdt-dev libisl-dev
sudo apt clean
sudo apt autoremove
For best Linux filesystem and SD flash memory card health: DON’T pull the plug before you sudo shutdown now
Before fetching and building a fresh copy of the tool chain, it's prudent to clear out old existing files. You may want to save any locally made changes before issuing the rm
commands.
The toolchain builds smoothly when the working directory is not at the same place as the source files. For this reason, do the configure
and make
steps one level below, in a separate folder.
DO NOT use the many thread -j
option of make
, it is too hard on the SD flash memory card.
sudo rm –fr /opt/riscv32
sudo rm –fr ./riscv-gnu-toolchain
git clone https://github.com/riscv/riscv-gnu-toolchain
cd riscv-gnu-toolchain
mkdir x-rv32imac-ilp32
cd x-rv32imac-ilp32
../configure –prefix=/opt/riscv32 --enable-languages=c,c++
--with-arch=rv32imac
--with-abi=ilp32
sudo make
export RISCV=/opt/riscv32
export PATH=$PATH:$RISCV/bin
The toolchain builds in the following sequence: binutils
→ gcc
→ newlib
→ gdb
As above, clear out old existing files from previous loader builds. Remember to save any locally made changes before issuing the rm
commands.
Since the OpenOCD make file internally manages the installation process, it is not necessary to explicitly export any environment variables when building the loader portion of the tool chain.
sudo apt-get install libusb-1.0-0 libusb-1.0-0-dev
sudo rm –fr ./openocd
git clone git://git.code.sf.net/p/openocd/code openocd
cd openocd
./bootstrap
./configure –prefix=/opt/openocd --enable-bcm2835gpio --enable-sysfsgpio
make
sudo make install
If all goes well, you can test your shiny new tool chain versions like so:
riscv32-unknown-elf-gcc --version <== should show something like 11.1.0
riscv32-unknown-as --version 2.38
riscv32-unknown-ld --version 2.38
riscv32-unknown-gdb --version 10.1
openocd --version 0.11.0
It takes only a few wires to connect a PI to a RISC-V chip. Throw in a few more if you wish to use serial or parallel I/O pins like the UART, SPI, I2C, or GPIO ports.
Oh, and please don't forget one wire for signal ground.
RPi | LoFive-R1 | ||
---|---|---|---|
GPIO 6 | 31 ------- 5 | TCK | JTAG connections |
GPIO 13 | 33 ------- 7 | TMS | |
GPIO 26 | 37 ------- 8 | TDI | |
GPIO 5 | 29 ------- 4 | TDO | |
GPIO 12 | 32 ------- 6 | SRST | |
GND | 39 ===== 28 | GND | |
UART TX | 8 ------- 20 | UART0.RX (GPIO 17) | UART serial port |
UART RX | 10 ------- 21 | UART0.TX (GPIO 16) | |
GPIO 17 | 11 ------- 15 | SPI1.SS2 (GPIO 9) | GPIO parallel port |
GPIO 27 | 13 ------- 16 | SPI1.SS3 (GPIO 10) | |
GPIO 22 | 15 ------- 17 | PWM2.1 (GPIO X) |
Physical pinout
The wiring pictorial described here is specific to the LoFive-R1 board, all discussion applies equally well to any board. See the later section Are all RISC Five Boards as easy as PI? for guidance on using other evaluation boards.
RPi (3B+) LoFive-R1
+---------------+ +----------------+
| Display | | 1 28 | <== note square
| | | 2 27 | pads on both
| 1 2 | | 3 26 | pins 1 and 28
| USB 3 4 | | 4 25 |
| 5 6 | | 5 24 |
| 7 8 | | 6 23 |
| 9 10 | | 7 22 |
| 11 12 | | 8 21 |
| HDMI 13 14 | | 9 20 |
| 15 16 | | 10 19 |
| 17 18 | | 11 18 |
| 29 20 | | 12 17 |
| 21 22 | | 13 16 |
| 23 24 | | 14 15 |
| 25 26 | +----------------+
| 27 28 |
| 39 30 |
| 31 32 |
| 33 34 |
| 35 36 |
| 37 38 |
| 39 40 |
| |
| LAN USB |
+---------------+
The MAKEFILE script foo.mk
does not need to be changed when switching between Flash and RAM
boot or code execution.
Notice the two places where the linker script file foo.lds
gets used in the build process.
foo.mk
:
RISCVGNU ?= riscv32-unknown-elf
AOPS = -march=riscv32imac –mabi=ilp32
COPS = -march=riscv32imac –mabi=ilp32 –Wall –O2 –nostdlib –nostartfiles –ffreestanding
start.o : start.s
$(RISCVGNU)-as $(AOPS) start.s –o start.o
... all other ASM and C source files go here ...
main.o : main.c
$(RISCVGNU)-gcc $(COPS) –c main.c –o main.o
foo.bin : foo.lds start.o ... main.o
$(RISCVGNU)-ld start.o ... main.o –T foo.lds –o foo.elf –Map foo.map
$(RISCVGNU)-objdump –D foo.elf > foo.lst
$(RISCVGNU)-objcopy foo.elf –O ihex foo.hex
$(RISCVGNU)-objcopy foo.elf –O binary foo.bin
clean:
rm –f *.o
rm –f *.elf
rm –f *.bin
rm –f *.lst
rm –f *.hex
rm –f *.map
Note that indented lines are with a single tab character, not many spaces, as standard practice for any MAKEFILE.
This is how to selectively load and/or boot from Flash (ROM) or RAM. It is a bit bare but should be easy to see all of the moving parts.
There are only two places to change when making the choice between Flash (ROM) or RAM: The linker script file “foo.lds” shown here, and the Loading & Running command lines, shown next.
foo.lds
OUTPUT_ARCH(“riscv”)
ENTRY( _start_ )
MEMORY`
{
rom : ORIGIN = 0x20000000, LENGTH = 0x2000
ram (rxa!ri) : ORIGIN = 0x80000000, LENGTH = 0x4000
}
SECTIONS
{
.text : { *(.text*) } > ram ... or ... rom
.rodata : { *(.rodata*) } > ram ... or ... rom
.bss : { *(.bss*) } > ram
}
It is easiest to make a pair of linker script files, suffixed with -ram
and -rom
. That way, you don't need to keep re-editing the linker script file and risk accidentally breaking something.
There are two main parts here, the physical wiring connections and the logical target device definition. They are mutually exclusive, and you can keep each in its own configuration file as shown.
Interface specification – How to tell OpenOCD which pins and wires of the host system to use.
jtag_nums # # # #
is where you define the four connection signals: TCK TMS TDI TDO
in that order! Note that these are gpio port numbers, not physical connector pin numbers. Similarly for swd_nums # #
which defines the two connection signals SWCLK SWDIO
in that order. The order of these signal number arguments is defined by the implementation of jtag_nums
and swd_nums
in the OpenOCD program. Although other choices of RPi port numbers are possible, the numbers shown here give greatest flexibility for using the other ports and their multiple functions.
rpi-3b.cfg
:
adapter driver bcm2835gpio
bcm2835gpio peripheral_base 0x3f000000
bcm2835gpio speed_coeffs 97469 24
bcm2835gpio jtag_nums 6 13 26 5
bcm2835gpio swd_nums 6 13
bcm2835gpio srst_nums 12
reset_config srst_only separate srst_nogate
Target specification – How to tell OpenOCD what kind of chip to talk to.
fe310-g002.cfg
:
transport select jtag
jtag newtap riscv cpu –irlen 5 –expected-id 0x20000913
target create riscv.cpu.0 riscv –chain-position riscv.cpu
riscv.cpu.0 configure –work-area-phys 0x80000000
-work-area-size 0x100000
-work-area-backup 0
The Load command line needs to change in two places when switching between RAM or Flash (ROM) boot, as shown by the use of the load_image
and verify_image
statements, and the flash bank
and flash write_image
commands.
The Run command line needs to change in one place when switching between RAM or Flash (ROM) boot, as shown by the target address 0x80000000
and 0x20000000
.
An encapsulation of all of the necessary steps, including physical wiring connections, logical target device definition, target device memory loading, and target device running, as well as great improvement in speed and efficiency of flashing code into ROM, is available at https://github.com/psherman42/demystifying-openocd. See the section below, Can I do it all with one click (or key press)? for further explanation.
Load to RAM
sudo openocd –f rpi-3b.cfg –f fe310-g002.cfg –c “adapter speed 1000”
–c init
–c “reset init”
–c “sleep 25”
–c “riscv set_reset_timeout_sec 25”
–c “adapter speed 2500”
–c “load_image foo.bin 0x80000000 bin”
–c “verify_image foo.bin 0x80000000 bin”
–c shutdown –c exit
Load to ROM
sudo openocd –f rpi-3b.cfg –f fe310-g002.cfg
–c “flash bank spi0 fespi 0x20000000 0 0 0 riscv.cpu.0 0x10014000”
–c “adapter speed 1000”
–c init
–c “reset init”
–c “sleep 25”
–c “riscv set_reset_timeout_sec 25”
–c “adapter speed 2500”
–c “flash write_image erase unlock foo.bin 0x20000000 bin”
–c shutdown –c exit
Run from RAM
sudo openocd –f rpi-3b.cfg –f fe310-g002.cfg –c “adapter speed 1000”
–c init
–c “reset init”
–c “sleep 25”
–c “adapter speed 2500”
–c “resume 0x80000000”
–c shutdown –c exit
Run from ROM
sudo openocd –f rpi-3b.cfg –f fe310-g002.cfg –c “adapter speed 1000”
–c init
–c “reset init”
–c “sleep 25”
–c “adapter speed 2500”
–c “resume 0x20000000”
–c shutdown –c exit
Load & Run Successful
A message like this is usually accompanied by a beer or other celebration.
Info : Examined RISC-V core: found 1 harts
Info : hart 0: XLEN=32, misa=0x40101105
Load & Run Unsuccessful
Anguish and melancholy arise when you see these. Don't despair. Both indicate the possibility of JTAG not reset, possibly due to insufficient reset pulse timing, low voltage, or noise supply lines such as from bad ground connections, and are easily remedied.
In rare cases a hard power reset of the target might be needed; see discussion of Understanding the PRCI Clock Path in the SiFive forums.
Error: Fatal: Hart 0 failed to halt during examine()
or
Error executing event examine-start on target riscv.cpu.0
Error: DMI operation didn't complete in 2 seconds. The target is either really slow or broken. You could increase the timeout with riscv set_command_timeout_sec.
Demonstration for Simple Terminal and Linux Logic Analyzer following below. Send characters F
, M
, S
, f
, m
, s
, ?
, or enter
in any order and watch the output in the Terminal and the Analyzer. All of the source files for an FE310 SoC are included in this repository.
main.c
#include <stdint.h> // for uint32_t
#include <stddef.h> // for size_t
#include “clock.h" // for clock_init()
#include “uart0.h” // for uart0_...()
#include “gpio.h” // for gpio_...()
unsigned int x;
uint32_t clk_hz;
void main() {
clk_hz = clock_init( PRCI_EXT_DIR );
gpio_init();
uart0_init( clk_hz, 115200 );
uart0_write_string( “welcome to uart test\r\n”);
gpio_dir( 9, GPIO_OUT ); // LoFive-R1 pin 15, signal GPIO9/SPI1.SS2, 48-QFN pin 33
gpio_dir( 10, GPIO_OUT ); // LoFive-R1 pin 16, signal GPIO10/PWM2.0, 48-QFN pin 34
gpio_dir( 11, GPIO_OUT ); // LoFive-R1 pin 17, signal GPIO11/PWM2.1, 48-QFN pin 35
while(1) {
x = uart_read();
switch( x ) {
case ‘F’: uart0_write_string(“Flash “); gpio_high( 9 ); break;
case ‘f’: uart0_write_string(“flash “); gpio_low( 9 ); break;
case ‘M’: uart0_write_string(“Memory “); gpio_high( 10 ); break;
case ‘m’: uart0_write_string(“memory “); gpio_low( 10 ); break;
case ‘S’: uart0_write_string(“Summit “); gpio_high( 11 ); break;
case ‘s’: uart0_write_string(“summit “); gpio_low( 11 ); break;
case ‘\r’: uart0_write_string(“\r\n”); break;
default: uart0_write( (uint8_t *) &x, 1); break;
}
}
}
Build the Sample Program with the command make -f uart.mk
. It will create the binary file uart.bin
along with the object files (*.o
), memory map file (*.map
), assembled listing file (*.lst
), and Intel .hex
and .elf
formats of the binary file.
Load the binary file into the target device with one of the Load commands shown above in the Loading & Running part of the Assembling, Compiling, Linking, Loading section. Replace the occurances of foo.bin
with uart.bin
, in the load_image, verify_image, and/or flash write_image portions, of course.
Instruct the target device to start running its code with one of the Run commands above.
Probably one of the world's smallest terminal emulators. Run in a separate session (Alt-F2, etc) for best results.
sudo ~/prj/boot/term.sh /dev/serial0 115200
Available at https://github.com/psherman42/simple-term
It's neither fast nor fancy but it shows what happens and when it happens. Run in a separate session (Alt-F3, etc) for best results.
sudo ~/prj/boot/sense.sh --c1 17 --c2 27 --c3 22
--tc 17 --tp + --tm norm
--cl1 GPIO17 --cl2 GPIO-27 --cl3 GPIO-22
Where
c1
,c2
,c2
– channel GPIO pin(s)
tc
– trigger channel GPIO pin
tp
– trigger polarity (+ or -)
tm
– trigger mode (auto or norm)
cl1
,cl2
,cl3
– channel label(s)
Available at https://github.com/psherman42/linux-logic-analyzer
SiFive Docs – https://www.sifive.com/documentation
E310 Manual - programmer's reference material
E310 Datasheet - electrical and physical specifications
E31 Core Complex Manual - complete general information
SiFive Technical Discussion - https://forums.sifive.com/u/pds
SiFive Hardware Design - https://github.com/sifive/sifive-blocks (complete set of rtl and scala files)
LoFive R1 – https://github.com/mwelling/lofive
RPi – Connector pinout and signal descriptions (pinout.xyz), Official software (raspberrypi.com) (use Raspberry Pi OS Lite, without desktop, for best results)
USB Adapters: Olimex, FTDI FT-2232, etc.
Availability: digikey, mouser, etc.
It sure is! Use the FT(2)232 chip with any USB port.
Mac - drivers already supported
PC - may need to disable the UEFI driver security check
JTAG Reset line glitches at startup, so revise a little bit as shown below.
The setting layout_init 0x0808 0x0a1b
shows the bug, which is a tiny 10uS glitch on nTRST at startup. Instead, the setting layout_init 0x0b08 0x0b1b
fixes the bug, by specifying the rst lines as outputs with push-pull drive.
ftdi.cfg
:
adapter driver ftdi
ftdi device_desc "Olimex OpenOCD JTAG ARM-USB-TINY-H“
ftdi vid_pid 0x15ba 0x002a
#----------------- P/U –-- DIR --
#ftdi layout_init 0x0808 0x0a1b
ftdi layout_init 0x0b08 0x0b1b
ftdi layout_signal nSRST -oe 0x0200
ftdi layout_signal nTRST -data 0x0100 -oe 0x0100
ftdi layout_signal LED -data 0x0800
The layout_init
setting words are defined by MPSSE below:
Sig MPSSE PIN BIT P/U DIR
--- ----- --- --- --- ---
TCK TCK/SK ADBUS0 0 0 1
TDI TDI/DO ADBUS1 1 0 1
TDO TDO/DI ADBUS2 2 0 0
TMS TMS/CS ADBUS3 3 1 1
??? GPIOL0 ADBUS4 4 0 1
. GPIOL1 ADBUS5 5 0 0
. GPIOL2 ADBUS6 6 0 0
. GPIOL3 ADBUS7 7 0 0
TRST GPIOH0 ACBUS0 8 1 1 <== P/U and DIR fix the bug noted above
SRST GPIOH1 ACBUS1 9 1 1 <== P/U fixes the bug noted above
. GPIOH2 ACBUS2 a 0 0
LED GPIOH3 ACBUS3 b 1 1
. GPIOH4 ACBUS4 c 0 0
. GPIOH5 ACBUS5 d 0 0
. GPIOH6 ACBUS6 e 0 0
. GPIOH7 ACBUS7 f 0 0
Yes!
make –f foo.mk ram –tgt=LOAD
The link step is invoked by the -ld
command, and the load step is invoked by the (optional) openocd
command, shown in the ram target below. Assembling and Compiling steps are not shown, for clarity.
foo.mk
:
ram : foo.lds start.o ... main.o
$(RISCVGNU)-ld start.o ... main.o -T foo.lds -o foo.elf -Map foo.map
$(RISCVGNU)-objdump -D foo.elf > foo.lst
$(RISCVGNU)-objcopy foo.elf -O ihex foo.hex
$(RISCVGNU)-objcopy foo.elf -O binary foo.bin
ifeq ($(tgt), LOAD)
@openocd -f interface/ftdi/olimex-arm-usb-tiny-h.cfg -f foo.cfg
-c init -c "asic_ram_load foo“
-c shutdown -c exit
else
@echo "target not changed“
endif
Indented lines are with a single tab character, not many spaces.
Note the @
symbol to run a shell command from within a makefile.
See Demystifying OpenOCD for more information and a full working example.
Almost ...
In fact, all of the tool chain make files, linker scripts, and loader scripts are the same, regardless of evaluation board of the target device. Not even the number of wires needs to change, either. Only the physical pins to where the wires connect.
What determines whether a board is as easy as PI is if it has externally accessible JTAG pins. Many boards do, although not all of them populate the connectors for those pins. Some boards have their own embedded USB-to-JTAG peripherals, which further complicate the process of learning RISC-V - you have to learn the specific differences of the JTAG peripheral and its required USB drivers first.
The following table can help you Wiring the Hardware to a few of the other popular evaluation boards.
All of these evaluation boards contain the FE310 SoC and cost less than $100, while the RED-V Thing is not as easy as PI because it does not expose its JTAG signals.
_ | RPi Pin (Port) |
RISC-V Signal |
LoFive-R1 | RED-V Thing | HiFive 1 Rev B |
RED-V RedBoard | ||
---|---|---|---|---|---|---|---|---|
JTAG | 31 (GPIO6) |
TCK | 5 | n/a | J1-4 | J5-4 | ||
JTAG | 33 (GPIO13) |
TMS | 7 | n/a | J1-2 | J5-2 | ||
JTAG | 37 (GPIO26) |
TDI | 8 | n/a | J1-8 | J5-8 | ||
JTAG | 29 (GPIO5) |
TDO | 4 | n/a | J1-6 | J5-6 | ||
JTAG | 32 (GPIO12) |
SRST | 6 | n/a | J1-10 | J5-10 | ||
GND | 39 | GND | 28 | J7-1,13 | J1-3,5,7,9 | J5-3,5,9 | ||
UART | 8 (GPIO14) |
UART0.RX / GPIO17 | 20 | J7-3 | J2-2 | JP11-1 | ||
UART | 10 (GPIO15) |
UART0.TX / GPIO16 | 21 | J7-2 | J2-1 | JP11-2 | ||
GPIO | 11 (GPIO17) |
SPI1.SS2 / GPIO9 | 15 | J6-6 | J4-2 | JP13-2 | ||
GPIO | 13 (GPIO27) |
SPI1.SS3 / GPIO10 | 16 | J6-7 | J4-3 | JP13-3 | ||
GPIO | 15 (GPIO22) |
PWM2.1 / GPIO11 | 17 | J6-8 | J4-4 | JP13-4 | ||
PWR | +5V | 1 | J6-10 | J6-8 | JP10-1 | |||
PWR | GND | 2 | J6-9 jmpr | J6-6,7 | JP10-2,3 | |||
Info | Docs | Schem. | Schem. | Schem. | Schem. | |||
Info | Descr | Avail. | Avail. | Avail. | Avail. | |||
Info | Price | $25.00 | $32.50 | $65 | $42.95 |
Of course, you might need to change your application or Sample Program, and re-make
it, if you select a different UART or GPIO pin for your specific program function.
Post them to the Issues of this repo!