Five EmbedDev logo Five EmbedDev

An Embedded RISC-V Blog

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.

Platform IO

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:

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.

If you are like most developers, an IDE maybe your choice.

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:

As RISC-V is an open architecture there are many other 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.