Overview
We continue to work with the RISC-V open source ISA as we pursue development of our own implementation for embedding in an FPGA. We need a software toolchain to create assembly instructions & sequences for the purpose of execution in both a simulator and target FPGA. Therefore, we have posted this page to document some of the getting started steps required to work with the software tools and produce executable assembly. Our goal is to develop a micro-controller for fixed-point operations inside an FPGA integrated with our Private Island ® open source project for FPGAs.
A good resource for reviewing the available RISC-V software related tools is the RISC-V Software Ecosystem Overview page, which has now been archived. In the steps shown below, we'll be working with the RISC-V toolchain repos found on the Github page RISC-V GNU Compiler Toolchain.
Build the Toolchain
The following steps are performed on an Ubuntu Linux machine and closely follow the documentation available on the aforementioned Github page. Also, refer to this page for a list of required packages for Ubuntu (e.g., libtool).
We first recursively clone the suite of open source GNU tools for RISC-V:
$ cd /build $ git clone --recursive https://github.com/riscv/riscv-gnu-toolchain Cloning into 'riscv-gnu-toolchain'... ... Submodule 'binutils' (https://sourceware.org/git/binutils-gdb.git) registered for path 'binutils' Submodule 'dejagnu' (https://git.savannah.gnu.org/git/dejagnu.git) registered for path 'dejagnu' Submodule 'gcc' (https://gcc.gnu.org/git/gcc.git) registered for path 'gcc' Submodule 'gdb' (https://sourceware.org/git/binutils-gdb.git) registered for path 'gdb' Submodule 'glibc' (https://sourceware.org/git/glibc.git) registered for path 'glibc' Submodule 'llvm' (https://github.com/llvm/llvm-project.git) registered for path 'llvm' Submodule 'musl' (https://git.musl-libc.org/git/musl) registered for path 'musl' Submodule 'newlib' (https://sourceware.org/git/newlib-cygwin.git) registered for path 'newlib' Submodule 'pk' (https://github.com/riscv-software-src/riscv-pk.git) registered for path 'pk' Submodule 'qemu' (https://gitlab.com/qemu-project/qemu.git) registered for path 'qemu' Submodule 'spike' (https://github.com/riscv-software-src/riscv-isa-sim.git) registered for path 'spike' ... Submodule 'roms/QemuMacDrivers' (https://gitlab.com/qemu-project/QemuMacDrivers.git) registered for path 'qemu/roms/QemuMacDrivers' Submodule 'roms/SLOF' (https://gitlab.com/qemu-project/SLOF.git) registered for path 'qemu/roms/SLOF' Submodule 'roms/edk2' (https://gitlab.com/qemu-project/edk2.git) registered for path 'qemu/roms/edk2' Submodule 'roms/ipxe' (https://gitlab.com/qemu-project/ipxe.git) registered for path 'qemu/roms/ipxe' Submodule 'roms/openbios' (https://gitlab.com/qemu-project/openbios.git) registered for path 'qemu/roms/openbios' Submodule 'roms/opensbi' (https://gitlab.com/qemu-project/opensbi.git) registered for path 'qemu/roms/opensbi' Submodule 'roms/qboot' (https://gitlab.com/qemu-project/qboot.git) registered for path 'qemu/roms/qboot' Submodule 'roms/qemu-palcode' (https://gitlab.com/qemu-project/qemu-palcode.git) registered for path 'qemu/roms/qemu-palcode' Submodule 'roms/seabios' (https://gitlab.com/qemu-project/seabios.git/) registered for path 'qemu/roms/seabios' Submodule 'roms/seabios-hppa' (https://gitlab.com/qemu-project/seabios-hppa.git) registered for path 'qemu/roms/seabios-hppa' Submodule 'roms/skiboot' (https://gitlab.com/qemu-project/skiboot.git) registered for path 'qemu/roms/skiboot' Submodule 'roms/u-boot' (https://gitlab.com/qemu-project/u-boot.git) registered for path 'qemu/roms/u-boot' Submodule 'roms/u-boot-sam460ex' (https://gitlab.com/qemu-project/u-boot-sam460ex.git) registered for path 'qemu/roms/u-boot-sam460ex' Submodule 'roms/vbootrom' (https://gitlab.com/qemu-project/vbootrom.git) registered for path 'qemu/roms/vbootrom' Submodule 'tests/lcitool/libvirt-ci' (https://gitlab.com/libvirt/libvirt-ci.git) registered for path 'qemu/tests/lcitool/libvirt-ci' ... Submodule 'SoftFloat' (https://github.com/ucb-bar/berkeley-softfloat-3.git) registered for path 'qemu/roms/edk2/ArmPkg/Library/ArmSoftFloatLib/berkeley-softfloat-3' Submodule 'BaseTools/Source/C/BrotliCompress/brotli' (https://github.com/google/brotli) registered for path 'qemu/roms/edk2/BaseTools/Source/C/BrotliCompress/brotli' Submodule 'CryptoPkg/Library/MbedTlsLib/mbedtls' (https://github.com/ARMmbed/mbedtls) registered for path 'qemu/roms/edk2/CryptoPkg/Library/MbedTlsLib/mbedtls' Submodule 'CryptoPkg/Library/OpensslLib/openssl' (https://github.com/openssl/openssl) registered for path 'qemu/roms/edk2/CryptoPkg/Library/OpensslLib/openssl' Submodule 'MdeModulePkg/Library/BrotliCustomDecompressLib/brotli' (https://github.com/google/brotli) registered for path 'qemu/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli' Submodule 'MdeModulePkg/Universal/RegularExpressionDxe/oniguruma' (https://github.com/kkos/oniguruma) registered for path 'qemu/roms/edk2/MdeModulePkg/Universal/RegularExpressionDxe/oniguruma' Submodule 'MdePkg/Library/BaseFdtLib/libfdt' (https://github.com/devicetree-org/pylibfdt.git) registered for path 'qemu/roms/edk2/MdePkg/Library/BaseFdtLib/libfdt' Submodule 'MdePkg/Library/MipiSysTLib/mipisyst' (https://github.com/MIPI-Alliance/public-mipi-sys-t.git) registered for path 'qemu/roms/edk2/MdePkg/Library/MipiSysTLib/mipisyst' Submodule 'RedfishPkg/Library/JsonLib/jansson' (https://github.com/akheron/jansson) registered for path 'qemu/roms/edk2/RedfishPkg/Library/JsonLib/jansson' Submodule 'UnitTestFrameworkPkg/Library/CmockaLib/cmocka' (https://github.com/tianocore/edk2-cmocka.git) registered for path 'qemu/roms/edk2/UnitTestFrameworkPkg/Library/CmockaLib/cmocka' Submodule 'UnitTestFrameworkPkg/Library/GoogleTestLib/googletest' (https://github.com/google/googletest.git) registered for path 'qemu/roms/edk2/UnitTestFrameworkPkg/Library/GoogleTestLib/googletest' Submodule 'UnitTestFrameworkPkg/Library/SubhookLib/subhook' (https://github.com/Zeex/subhook.git) registered for path 'qemu/roms/edk2/UnitTestFrameworkPkg/Library/SubhookLib/subhook' ... Submodule 'external/googletest' (https://github.com/google/googletest.git) registered for path 'qemu/roms/edk2/MdePkg/Library/MipiSysTLib/mipisyst/external/googletest' Submodule 'external/pugixml' (https://github.com/zeux/pugixml.git) registered for path 'qemu/roms/edk2/MdePkg/Library/MipiSysTLib/mipisyst/external/pugixml' ...
Let's make sure we understand that we just cloned a repository of repositories:
$ cd /build/riscv-gnu-toolchain/ $ git log commit 59ab58e8a4aed4ed8f711ebab307757a5ebaa1f5 (HEAD -> master, tag: 2024.02.02, origin/master, origin/HEAD) Author: Christoph Müllner Date: Thu Feb 1 09:31:15 2024 +0100 gdb: Bump version from 13.2 to 14.1 $ cd binutils $ git log commit c7f28aad0c99d1d2fec4e52ebfa3735d90ceb8e9 (HEAD, tag: binutils-2_42) Author: Nick Clifton Date: Mon Jan 29 14:51:43 2024 +0000 Update version number to 2.42
Next we configure our build in a separate sub directory to produce a toolchain for a 32-bit RISC-V core (RV32IM):
- RV32I: Base Integer Instruction Set
- M: Instructions that multiply and divide values held in two integer registers
$ cd /build/riscv-gnu-toolchain/ $ mkdir build; cd build $ ../configure --help | grep abi --with-abi=lp64d Sets the base RISC-V ABI, defaults to lp64d $ ../configure --prefix=/opt/riscv32 --with-arch=rv32im --with-abi=ilp32 checking for gcc... gcc ... config.status: creating Makefile config.status: creating scripts/wrapper/awk/awk config.status: creating scripts/wrapper/sed/sed
Note that ilp32 specifies that int, long, and pointers are all 32-bits
After configure is complete, we can make our code. Note that make also performs an install into the path specified by --prefix: /opt/riscv32.
$ make $ ls build-binutils-newlib build-gcc-newlib-stage2 build-newlib config.log install-newlib-nano scripts build-gcc-newlib-stage1 build-gdb-newlib build-newlib-nano config.status Makefile stamps
Note that the overall build took less than 30 minutes on a Dell 7740 Precision laptop with an 8-core I9 (2.3 GHz), 64 GB of RAM, and /build mounted on a 2.5" 1TB 7200RPM SATA Hard Drive.
Let's take a look at what we built & installed:
$ cd /opt/riscv32 $ tree -L 2 -d . ├── bin ├── include │ ├── gdb │ └── sim ├── lib │ ├── bfd-plugins │ └── gcc ├── libexec │ └── gcc ├── riscv32-unknown-elf │ ├── bin │ ├── include │ └── lib └── share ├── gcc-13.2.0 ├── gdb ├── info ├── locale └── man
Next we set up an env-riscv script that we can source when we need to work with our toolchain. Later we'll add environment variables like CFLAGS to it.
file: /opt/riscv32/env-riscv32:
export PATH=/opt/riscv32/bin:$PATH
Let's make sure we can execute our tools:
$ mkdir -p ~/Projects/riscv; cd ~/Projects/riscv $ source /opt/riscv32/env-riscv32 $ riscv32-unknown-elf-gcc --version riscv32-unknown-elf-gcc (gc891d8dc23e) 13.2.0 ... $ riscv32-unknown-elf-objcopy --version GNU objcopy (GNU Binutils) 2.42 ...
Great, we see that we're ready to go with our compiler and binutils. However, before we move on, let's do some inspection of our compiler to see how it's been configured:
$ riscv32-unknown-elf-gcc -dumpmachine riscv32-unknown-elf $ riscv32-unknown-elf-gcc -print-sysroot /opt/riscv32/riscv32-unknown-elf $ riscv32-unknown-elf-gcc -print-libgcc-file-name /opt/riscv32/lib/gcc/riscv32-unknown-elf/13.2.0/libgcc.a $ riscv32-unknown-elf-gcc -print-search-dirs install: /opt/riscv32/lib/gcc/riscv32-unknown-elf/13.2.0/ programs: =/opt/riscv32/libexec/gcc/riscv32-unknown-elf/13.2.0/:/opt/riscv32/libexec/gcc/riscv32-unknown-elf/13.2.0/:/opt/riscv32/libexec/gcc/riscv32-unknown-elf/:/opt/riscv32/lib/gcc/riscv32-unknown-elf/13.2.0/:/opt/riscv32/lib/gcc/riscv32-unknown-elf/:/opt/riscv32/lib/gcc/riscv32-unknown-elf/13.2.0/../../../../riscv32-unknown-elf/bin/riscv32-unknown-elf/13.2.0/:/opt/riscv32/lib/gcc/riscv32-unknown-elf/13.2.0/../../../../riscv32-unknown-elf/bin/ libraries: =/opt/riscv32/lib/gcc/riscv32-unknown-elf/13.2.0/:/opt/riscv32/lib/gcc/riscv32-unknown-elf/13.2.0/../../../../riscv32-unknown-elf/lib/riscv32-unknown-elf/13.2.0/:/opt/riscv32/lib/gcc/riscv32-unknown-elf/13.2.0/../../../../riscv32-unknown-elf/lib/:/opt/riscv32/riscv32-unknown-elf/lib/riscv32-unknown-elf/13.2.0/:/opt/riscv32/riscv32-unknown-elf/lib/:/opt/riscv32/riscv32-unknown-elf/usr/lib/riscv32-unknown-elf/13.2.0/:/opt/riscv32/riscv32-unknown-elf/usr/lib/
Let's confirm we're working with the newlib-nano library:
$ ls /opt/riscv32/riscv32-unknown-elf/lib crt0.o libc.a libg.a libgloss_nano.a libm.a libnosys.a libstdc++.a libstdc++exp.a libstdc++.la libsupc++.la nosys.specs sim.specs ldscripts libc_nano.a libgloss.a libg_nano.a libm_nano.a libsemihost.a libstdc++.a-gdb.py libstdc++exp.la libsupc++.a nano.specs semihost.specs
Build a simple function and analyze it against the specification
Shown below is a very simple C program that has a multiply function mult() for the purpose of obtaining the RV32IM instructions used to multiply two integers. This is certainly something we want to do in our FPGA with our RISC-V.
int mult() { int a=1000,b=3; return a*b; } int main() { mult(); }
We build the simple C application "tst.c" with our new RISC-V GCC compiler:
$ riscv32-unknown-elf-gcc -g tst.c -o tst $ file tst tst: ELF 32-bit LSB executable, UCB RISC-V, version 1 (SYSV), statically linked, with debug_info, not stripped
Since our ELF executable isn't stripped, it has section headers and objdump can be used to analyze the code:
$ riscv32-unknown-elf-objdump -d tst ... 00010188 <mult>: 10188: fe010113 addi sp,sp,-32 1018c: 00812e23 sw s0,28(sp) 10190: 02010413 addi s0,sp,32 10194: 3e800793 li a5,1000 10198: fef42623 sw a5,-20(s0) 1019c: 00300793 li a5,3 101a0: fef42423 sw a5,-24(s0) 101a4: fec42703 lw a4,-20(s0) 101a8: fe842783 lw a5,-24(s0) 101ac: 02f707b3 mul a5,a4,a5 101b0: 00078513 mv a0,a5 101b4: 01c12403 lw s0,28(sp) 101b8: 02010113 addi sp,sp,32 101bc: 00008067 ret ...
We can see in the dump of mult() above that our two operands are retrieved into registers using load immediate (li) but then pushed onto the stack before retrieving them again using load word (lw) into registers a4 and a5. The actual multiply operation is perfomed by the mul instruction.
We can find the definition of these instructions in the Unprivileged ISA specification. Specifically, the mul instruction is defined in Chapter 7. This is the "M" extension for our RV32IMA core.
We can see that the "mul a5,a4,a5" instruction is encoded as 0x02f707b3. Keep in mind that RISC-V is a little-endian system, especially when working with debuggers and viewing memory.
Referring to Chapter 25 (RISC-V Assembly Programmer’s Handbook) of the Unprivileged ISA Specification, we find that registers a2 through a7 are considered function argument registers and are mapped to x12 through x17. Therefore, a4-a5 are registers x14-x15 respectively.
Next, let's refer to Chapter 24 (RV32/64G Instruction Set Listings) and compare our instruction's encoded value against what is shown for MUL:
31-25 | 24-20 | 19-15 | 14-12 | 11-7 | 6-0 | |
MUL | 0000001 | rs2 | rs1 | 000 | rd | 0110011 |
15 | 14 | 15 |
So, now we have confirmed that the master branch of the GNU RISC-V tools do indeed create assembly that matches the RV32IM specification (at least for MUL). Perhaps it's now a little easier to envision some of the stages of a RISC-V pipeline (e.g., instruction/data fetch and instruction decode).
Some natural next steps for an FPGA-based microcontroller includes creating a bare metal software build environment, linker script, along with the proper startup code and exception table.
We'll continue to update this page as we progress with testing, developing, and integrating our RISC-V core.
Common RISC-V Terms and Acronyms
Terms
- Chisel: Constructing Hardware in a Scala Embedded Language
- Exception: "Unusual condition occurring at run time associated with an instruction in the current RISC-V hart"
- Hart: Hardware Thread
- Interrupt: "external asynchronous event that may cause a RISC-V hart to experience an unexpected transfer of control"
- SemVer: Semantic Versioning
- Tile: (Rocket) Core + Private Caches
- Trap: "Transfer of control to a trap handler caused by either an exception or an interrupt."
- XLEN: "Width of an integer register in bits"
Acronyms
- AEE: Application Execution Environment
- AUIPC: Add Upper Immediate to PC
- DSL: Domain-Specific Language
- EEI: Execution Environment Interface
- FESVR: Front End Server
- FIRRTL: Flexible Intermediate Representation for RTL
- HTIF: Host-Target Interface
- JAL: Jump and Link
- IR: Intermediate Representation
- MTVEC: Machine Trap-Vector Base-Address Register
- RISC: Reduced Instruction Set Computation/Computer
- SEE: Supervisor Execution Environment
Date: April 21, 2021
Author: Christopher Morales
Comment: