This post is a draft for Medium.
Following on from Part 2, how do we compile this project and run it?
For this series of posts, my platform is a SiFive HiFive1 Rev B development board. It’s equipped with a 320MHz RV32IMAC (FE310 core). For software build and debug the choice is Platform IO, an IDE integrated into VS Code.
Setup
To setup up a new project in Platform IO just select the board, framework and path for the files. For this exercise the choices are HiFive1 Rev B, Freedom E SDK and a custom path. We need to do this to configure our C++ version and baremetal environment.
In this example platformio.ini
’s build_flags
has been extended with
options to target embedded C++17. In particular:
-nostartfiles
is used as this example includes a custom startup routine,-std=c++17
for all the needed modern C++ features, and-fno-threadsafe-statics
to prevent threadsafe code being emitted (these ensure static declarations are initialized only once).
The configuration build_flags
captures compiler, pre-processor and linker options, so -Wl,-Map,blinky.map
to generate a map file can be added here along side compiler options.
The complete set of build flags used is here:
build_flags =
-std=c++17
-O2
-g
-Wall
-ffunction-sections
-fno-exceptions
-fno-rtti
-fno-nonansi-builtins
-fno-use-cxa-atexit
-fno-threadsafe-statics
-nostartfiles
-Wl,-Map,blinky.map
That configuration is all we need to build this project.
Adding a Post Compile Action
For this exercise, we’ll want to check exactly how the compiler is transforming our C++ code. Disassembling the output will show us that.
How can Platform IO do this? We need to edit platform.ini
again, this
time adding a helper script and additional targets.
extra_scripts = post_build.py
targets = disasm
The build system behind Platform IO is Scons. This is a Python-based system, so the post build script is a python file.
The Scons environment is used to find the path to the output file,
${BUILD_DIR}/${PROGNAME}.elf
, and the toolchain objdump is found by
modifying the path to${OBJDUMP}
. The env.subst()
will expand the
environment variables, and env.Execute()
will run the command.
def after_build(source, target, env):
""" Run objdump on the target elf file and save the output in the top dir.
"""
objdump=env.subst("${OBJCOPY}").replace("objcopy","objdump")
src_elf=env.subst("${BUILD_DIR}/${PROGNAME}.elf")
cmd=" ".join([
objdump, "-SC","--file-start-context", "-w",
src_elf,">","${PROGNAME}.disasm"]) #
env.Execute(cmd)
The target is given a name disasm
via the
env.AddCustomTarget()
method. This ties into the platformio.ini
option targets = disasm
above.
Import("env")
env.AddCustomTarget(
"disasm",
"${BUILD_DIR}/${PROGNAME}.elf",
after_build,
title="Disasm 2",
description="Generate a disassembly file on demand",
always_build=True
)
The above is all that is needed to compile the project. Loading it is simple, Platform IO takes care of it.
The project for this example is here. You should be able to load this project with Platform IO and try it out. With some small modifications, it should work on any other toolchain, IDE, or target.
The next post looks at how the core starts from reset and executes the program.
The rest of this post will look at other development environments and target platform options.
Other C++ Development Environments
These posts use PlaformIO as it’s a great way to get started with no messing around, but let’s explore a few other options that are available.
The standard toolchain for RISC-V is GCC, although LLVM RISC-V support is also available. Platform IO will automatically install SiFive’s toolchain and SDK.
However, if like me your usual preference is command line oriented, then use of the command line tools with Make or CMake maybe your choice.
- The toolchain binaries used by GNU MCU Eclipse can be installed independently via xpack riscv-none-embed-gcc.
- CMake with a RISC-V toolchain is possible.
- For complete toolchain control, GCC and supporting libraries can be downloaded and compiled easily via the github riscv-gnu-toolchain project. This is useful for those looking for bleeding edge extension support, or to change variant that the default C libraries are compiled for. (For example, In the past I have used this option to target an RV32EC core before pre-compiled binaries were available.)
If you are like most developers, an IDE maybe your choice.
- SiFive, like most processor vendors, has a GCC and Eclipse based IDE, the Freedom Studio.
- For a vendor independent path GNU MCU Eclipse supports RISC-V compilation and debugging very well. I have personally used this to target proprietary RISC-V cores and simulators with success.
The IDEs above all use OpenOCD for debug support, and this can be downloaded and compiled from OpenOCD source on github, or binaries are available from xpack.
Other RISC-V Devices
These posts target SiFive’s HiFive1 Rev B as it’s easily available and easy to use. However, the point of programming RISC-V at this low level is to make use of the open architecture to build bigger custom systems. As I’ve been on the firmware side this list is not complete, but just a starting point:
The main IP vendors seems to be:
- SiFive. SiFive has a heritage from the original Berkeley RocketChip implementation.
- Andes Technology is a another major RISC-V processor IP vendor, found in designs such as MicroChips’s PolarFire SoC.
As RISC-V is an open architecture there are many other options.
- Professionally my experience has been with IQonIC Works RISC-V IP. The provide a small RV32EC cores targeted at deep embedded applications, where stripped down baremetal programming is essential.
- Many open source options.
The project for this example is here. You should be able to load this project with Platform IO and try it out. With some small modifications it should work on any other toolchain, IDE or target.