RISC-V

 
 
RISC-V is an open standard instruction set architecture (ISA) based on established reduced instruction set computer (RISC) principles. The RISC-V ISA is provided under open source licenses that do not require fees to use.

More information about RISC-V can be found at:
https://riscv.org/








Toolchain riscv-nuclei-elf-gcc (gcc) examples.



Information
In this tutorial I will be using the riscv-nuclei-elf-gcc tool.
For more information: How to build the Nuclei RISC-V GNU Compiler Toolchain (riscv-gnu-toolchain) on macOS

Software prerequisites
riscv-gnu-toolchain

Procedure
  • The compilation process of a C-program consists of 4 steps:
    • Preprocessing
      Processes include-files (#include), substitute macros and inline functions (#define).

    • Compiling
      Takes the output of the preprocessor, and the source code, and generates assembler source code.

    • Assembly
      Takes the assembly source code and produces an assembly listing with offsets.
      The assembler output is stored in an object file.

    • Linking
      Takes one or more object files or libraries as input and combines them to produce an executable file.

  • Write a test program.
    Create a file hello.c with the following content:

    #include <stdio.h>
    int main(void) {
       printf("Hello World\n");
       return 0;
    }


  • 1. Preprocessing examples
    Example 1a (from .c to .i using riscv-nuclei-elf-gcc):
    Type: riscv-nuclei-elf-gcc -E -march=rv32imac -mabi=ilp32 hello.c -o hello.i

    Example 1b (from .c to .i using riscv-nuclei-elf-cpp):
    Type: riscv-nuclei-elf-cpp -march=rv32imac -mabi=ilp32 hello.c -o hello.i

  • 2. Compiling examples
    Example 2a (from .c to .s using riscv-nuclei-elf-gcc):
    Type: riscv-nuclei-elf-gcc -S -march=rv32imac -mabi=ilp32 hello.c -o hello.s

    Example 2b (from .i to .s using riscv-nuclei-elf-gcc):
    Type: riscv-nuclei-elf-gcc -S -march=rv32imac -mabi=ilp32 hello.i -o hello.s

  • 3. Assembly examples
    Example 3a (from .c to .o using riscv-nuclei-elf-gcc):
    Type: riscv-nuclei-elf-gcc -c -march=rv32imac -mabi=ilp32 hello.c -o hello.o

    Example 3b (from .s to .o using riscv-nuclei-elf-as):
    Type: riscv-nuclei-elf-as -march=rv32imac -mabi=ilp32 hello.s -o hello.o

  • 4. Linking examples
    Example 4a (from .c to executable using riscv-nuclei-elf-gcc):
    Type: riscv-nuclei-elf-gcc -march=rv32imac -mabi=ilp32 hello.c -o hello
    Type: spike $PK_PATH/pk hello

    Example 4b (from .o to executable using riscv-nuclei-elf-gcc):
    Type: riscv-nuclei-elf-gcc -march=rv32imac -mabi=ilp32 hello.o -o hello
    Type: spike $PK_PATH/pk hello

    Note:
    To run the RISC-V executable you can use the Spike RISC-V ISA Simulator.
    More information: How to build Spike emulator and Proxy Kernel (PK) on macOS

  • To combine all 4 steps in one:
    Example 1:
    Type: riscv-nuclei-elf-gcc -march=rv32imac -mabi=ilp32 hello.c (Output: a.out)
    Type: spike $PK_PATH/pk a.out

    Example 2:
    Type: riscv-nuclei-elf-gcc -march=rv32imac -mabi=ilp32 hello.c -o hello
    Type: spike $PK_PATH/pk hello

    Example 3:
    Type: riscv-nuclei-elf-gcc -march=rv32imac -mabi=ilp32 -Wall -g -O2 -static hello.c -o hello
    Type: spike $PK_PATH/pk hello

  • Few gcc options explained.

    -Wall: Shows all warnings



    -g: Include debugging information in the binary.

    Option Description
    -g0 no debug information
    -g1 minimal debug information
    -g default debug information
    -g3 maximal debug information



    -c: Compile source files without linking.



    -D: Defines a macro to be used by the preprocessor.
    Example: -DDEBUG
    Example: -DFILE=output.txt



    -fdata-sections : Place each data item into its own section in the output file.
    Use these options where the linker can perform optimizations to improve locality of reference in the instruction space.
    When you specify this option, the assembler and linker will create larger object and executable files and will also be slower.
    f stands for flag.



    -ffunction-sections: Place each function into its own section in the output file.
    Generates a separate ELF section for each function in the source file.
    The unused section elimination feature of the linker can then remove unused functions at link time.
    Use these options where the linker can perform optimizations to improve locality of reference in the instruction space.
    When you specify this option, the assembler and linker will create larger object and executable files and will also be slower.
    f stands for flag.



    -static: By default the gcc compiler uses dynamic libraries (.so).
    To force the use of static libraries (.a), use the option -static.



    -std=gnu11: Choose the C11 standard with GNU extensions according to which a compilation unit should be compiled.
    -std Determines the language standard.



    -v: To display detailed information about the exact sequence of commands used to compile and link a program.



    -I (Uppercase i): Adds include directory of header files.
    For example: -Iproj/src -Iproj/src/includes



    -L: Looks in directory for library files.
    For example: -Lproj/src/libs



    -l (Lowercase L): Links with a library file (proj/src/libs/mylib.a).
    For example: -Lproj/src/libs -lmylib.a

    -lm (Lowercase L): The -l option is used for linking particular library.
    Use -lm, when we want to link the C math library (math.h), this library is typically stored in the file libm.a or libm.so.
    The extension .a stands for archive (static library files), which is a file containing a bunch of .o (object) files.
    The extension .so stands for shared object (dynamic library files), which is a file containing a bunch of .o (object) files.

    -lc (Lowercase L): The -l option is used for linking particular library.
    Use -lc, when we want to link the standard C library (for example stdio.h is part of the C standard library), this library is typically stored in the file libc.a or libc.so.



    -march: Generate instructions for a machine type.
    The m stands for mode.
    For example: -march=rv32imac



    -mabi: Specifies both the integer and floating-point ABIs to which the generated code complies.
    The m stands for mode.
    For example: -mabi=ilp32 or -mabi=lp64
    ilp32 means int, long, and pointers are all 32-bits long.
    long long is a 64-bit type, char is 8-bit, and short is 16-bit.

    lp64 means long and pointers are 64-bits long, while int is a 32-bit type.
    The other types remain the same as ilp32.



    -MMD: Creates a makefile dependency file for each source file, using a .d suffix. The file lists only user header files.



    -print-libgcc-file-name: Use this compiler option to obtain the path to libgcc or an equivalent compiler runtime library.
    LIBGCC=riscv-nuclei-elf-gcc -march=rv32imac -mabi=ilp32 -print-libgcc-file-name

    LIBGCC=/Users/robertlie/riscv_toolchain/install/lib/gcc/
    riscv-nuclei-elf/9.2.0/rv32imac/ilp32/libgcc.a




    -print-file-name: Find the path of a library via the library name.
    LIBC=riscv-nuclei-elf-gcc -march=rv32imac -mabi=ilp32 -print-file-name=libc.a

    LIBC=/Users/robertlie/riscv_toolchain/install/riscv-nuclei-elf/
    lib/rv32imac/ilp32/libc.a


    LIBM=riscv-nuclei-elf-gcc -march=rv32imac -mabi=ilp32 -print-file-name=libm.a

    LIBM=/Users/robertlie/riscv_toolchain/install/riscv-nuclei-elf/
    lib/rv32imac/ilp32/libm.a




    -O: Optimization flag

    Option Optimization level Execution time Code size Memory usage Compile time
    -O0 Reduce compilation time and make debugging produce the expected results. This is the default. + + - -
    -O1
    -O
    optimization for code size and execution time - - + +
    -O2 optimization more for code size and execution time --   + ++
    -O3 optimization more for code size and execution time ---   + +++
    -Os optimization for code size   --   ++
    -Ofast O3 with fast none accurate math calculations ---   + +++

    +increase
    ++increase more
    +++increase even more
    -reduce
    --reduce more
    ---reduce even more



    -Wl,-Bstatic : Indicates that -Bstatic is passed down to the linker.

    The -Wl,xxx option for gcc passes a comma-separated list of tokens as a space-separated list of arguments to the linker.

    For example:

    -Wl,-Bdynamic links against shared libraries.
    -Wl,-Bstatic links against static libraries.



    -Wl,--cref : Output a cross reference table.

    The -Wl,xxx option for gcc passes a comma-separated list of tokens as a space-separated list of arguments to the linker.
    If a linker map file is being generated, the cross reference table is printed to the map file. Otherwise, it is printed on the standard output.



    -Wl,--gc-sections : Instructs the linker to remove unused sections.

    The -Wl,xxx option for gcc passes a comma-separated list of tokens as a space-separated list of arguments to the linker.
    --gc-sections reduces the size of the final executable program and works in combination with -ffunction-sections and -fdata-sections.



    -Wl,-Map=firmware.map : Create a map file.

    The -Wl,xxx option for gcc passes a comma-separated list of tokens as a space-separated list of arguments to the linker.
    The map file provides valuable information that can help you understand and optimize memory.



    -Wl,--no-relax : Do not perform target specific, global optimizations.

    The -Wl,xxx option for gcc passes a comma-separated list of tokens as a space-separated list of arguments to the linker.
    On some platforms the --relax option performs target specific, global optimizations that become possible when the linker resolves addressing in the program, such as relaxing address modes, synthesizing new instructions, selecting shorter version of current instructions, and combining constant values.

    On platforms where the feature is supported, the option --no-relax will disable it.



    -x assembler-with-cpp: assembler-with-cpp means assembler with c preprocessor.
    The assembly code contains C directives and gcc must run the C preprocessor.
    By default, gcc uses the assembly code source file suffix to determine whether to run the C preprocessor:

    • The .s (lowercase) suffix indicates assembly code that does not require preprocessing.

    • The .S (uppercase) suffix indicates assembly code that requires preprocessing.

    The -x option lets you override the default by specifying the language of the subsequent source files, rather than inferring the language from the file suffix. Specifically, -x assembler-with-cpp indicates that the assembly code contains C directives and gcc must run the C preprocessor.