Cross Compile OpenCV for RISC-V

Pranav Natekar
8 min readAug 26, 2021

This set of instructions will walk you through the basic command line cross-compilation of OpenCV for RISC-V on a Linux x86 system.

Assumptions:

  • Linux host system is used as the cross-compiling station.
  • Examples were tested on fully updated Ubuntu 18.04 and 20.04 releases.
  • The toolchain is specifically built for a target device(SiFive U74 core) and can be configured according to the needs.

Why RISC-V?

RISC-V is an open standard instruction set architecture (ISA) based on established reduced instruction set computer (RISC) principles. Unlike most other ISA designs, the RISC-V ISA is provided under open source licenses that do not require fees to use. — Source: Wikipedia

Avoiding obscure terminology and jargon, it can be concisely stated that RISC-V ISA is a free or open license ISA, and because of its extensible software and hardware freedom on architecture, it is most likely to be widely used in the future, mainly for applications centred around AI, parallel computing and data centres!

Part 1: The RISC-V GNU Compiler Toolchain

Several cross-compilation tools are provided in the official RISC-V repository. Below are some examples of the tools. For this particular article, we would be proceeding with the toolchain and the QEMU(quick EMUlator).

Building the toolchain:

The toolchain can be cloned from the RISC-V official GitHub. Once the dependencies are installed, it’s straightforward to compile. The toolchain supports two build modes: a generic ELF/Newlib toolchain and a more sophisticated linux-ELF/glibc toolchain; we’ll be proceeding with the glibc toolchain.

1. Getting the prerequisites

Several standard packages are needed to build the toolchain. However, executing the following command should suffice:

$ sudo apt update && sudo apt upgrade
$ sudo apt install gcc g++ git make cmake python python3 gcc-multilib vim autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev pkg-config libglib2.0-dev cmake-gui

2. Clone the repository and fetch the submodules

Cloning would be done in the home(~) directory and the toolchain can be installed to the desired directory by setting the PREFIX during the configuring step.

$ cd ~
$ git clone https://github.com/riscv/riscv-gnu-toolchain.git
$ cd riscv-gnu-toolchain
$ git submodule update --init --recursive
# This would take around 6.65 GB of the disk.

3. Configuring the toolchain

You have a choice to compile for just one architecture or build the multilib(both 32 and 64-bit) support for a predefined set of architectures. We’ll be configuring the toolchain for the combination of rv64imafdc, where rv64i is the base ISA and mafdc are the ISA extensions. More on this, below::

Base ISA

By default, using the base ISA paves an excellent way for the area and power optimization. However, additional functionality is sometimes desired. Therefore, RISC-V comes with standard extensions that enable other functionality beyond the core ISA, such as floating-point operations and bit manipulation. Extensions can be implemented and omitted as desired. Those extensions are::

ISA Extensions

Naming Convention:

There’s an exact order that must be used to define the RISC-V ISA subset:

RV [32, 64, 128] I, M, A, F, D, G, Q, L, C, B, J, T, P, V, N

For example, RV32IMAFDQC is legal, whereas RV32IMAFDCQ is not.

Let’s actually get configuring the toolchain!!

$ ./configure --prefix=/opt/riscv --with-arch=rv32imafdc --with-abi=lp64d
  1. The configure script is used to set the machine architecture(arch) and the abi(Application Binary Interface). —-prefix sets the directory where the cross-compiler will be installed. The build defaults to targeting RV64GC (64-bit), even on a 32-bit build environment, so be aware. To build the 32-bit RV32GC toolchain, use: ./configure --prefix=/opt/riscv --with-arch=rv32gc --with-abi=ilp32d
  2. The -march= option selects the base ISA + extensions. The extensions should be specified in the order as stated above. Supported architectures are rv32i or rv64i plus standard extensions (a)tomics, (m)ultiplication and division, (f)loat, (d)ouble, or (g)eneral for MAFD.
  3. The -mabi= option selects the calling convention ABI. Supported ABIs are ilp32(32-bit soft-float), ilp32d(32-bit hard-float), ilp32f(32-bit with single-precision in registers and double in memory, niche use only), lp64 lp64f lp64d (same but with 64-bit long and pointers).

The options for --march and --mabi are in the GCC documentation. Look at configure.ac for multilib support.

4. Building the toolchain and QEMU

# We're still in riscv-gnu-toolchain directory.
$ make linux -j$(nproc)
$ make build-qemu -j$(nproc)

Now, you’ll have the toolchain, binutils and QEMU built under /opt/riscv/bin folder and we are good to go.

Part 2: Cross-compiling OpenCV

OpenCV runs on many hardware platforms and makes use of the SIMD (Single Instruction Multiple Data) acceleration on the ones that support it. OpenCV provides a relatively convenient method to port the optimised kernels to a whole new architecture, as long as it supports SIMD/vector instructions. We use OpenCV’s Wide Universal Intrinsics for that. The flow of the conversion would be:

OpenCV frontend -> Wide Universal Intrinsics(HAL) -> rv64gcv_zch

HAL(Hardware Acceleration Layer) is a separate module that contains the optimized implementation of some basic functions. It is used to accelerate OpenCV for different platforms.

But in our case, we are not cross-compiling for the Vector Extension of the ISA.

  1. Get the prerequisites for OpenCV
$ sudo apt install build-essential cmake git unzip pkg-config
$ sudo apt install libjpeg-dev libpng-dev libtiff-dev
$ sudo apt install libavcodec-dev libavformat-dev libswscale-dev
$ sudo apt install libgtk2.0-dev libcanberra-gtk*
$ sudo apt install python3-dev python3-numpy python3-pip
$ sudo apt install libxvidcore-dev libx264-dev libgtk-3-dev
$ sudo apt install libtbb2 libtbb-dev libdc1394-22-dev
$ sudo apt install libv4l-dev v4l-utils
$ sudo apt install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
$ sudo apt install libavresample-dev libvorbis-dev libxine2-dev
$ sudo apt install libfaac-dev libmp3lame-dev libtheora-dev
$ sudo apt install libopencore-amrnb-dev libopencore-amrwb-dev
$ sudo apt install libopenblas-dev libatlas-base-dev libblas-dev
$ sudo apt install liblapack-dev libeigen3-dev gfortran
$ sudo apt install libhdf5-dev protobuf-compiler
$ sudo apt install libprotobuf-dev libgoogle-glog-dev libgflags-dev
# a symlink to videodev.h
$ cd /usr/include/linux
$ sudo ln -s -f ../libv4l1-videodev.h videodev.h
$ cd ~

2. Cloning OpenCV

Now that we’ve all the prerequisites, let’s get the OpenCV repository.

$ git clone https://github.com/opencv/opencv
$ cd opencv
$ mkdir build && cd build

3. Building OpenCV for RISC-V

Here comes a bit daunting part of the whole process. To make our lives better, we will be proceeding with cmake-gui .

  1. Into the build directory itself spawn an instance of cmake-gui by typing in $ cmake-gui.
CMake-GUI window

2. Take a closer look at the Where is the source code and Where to build the binaries and set the path accordingly.

3. After the respective paths are set, click configure and a window will pop up. Set the generator for the project as Unix Makefiles and check the radio button forSpecify options for cross-compiling.

4. After clicking Next, you’ll be prompted with the below dialog box. Here, we’ve to set the paths for the GCC and G++ cross-compilers and also the Target Root.

5. After clicking Finish, the GUI will look like this:

6. Here, the desired modules can be checked for installation. For example:

7. Be sure to do these:

  • Set CMAKE_BUILD_TYPE to Release.
  • Check BUILD_ZLIB.
  • Set CMAKE_LINKER_FLAGS to -lrt -lpthread.
  • Change the CMAKE_INSTALL_PREFIX to a different directory so as it doesn’t intervene with the local architecture libs.
  • Disable WITH_1394

8. Once you have set all the values as you like, you can click the Generate button to generate the MakeFile and exit.

After exiting the CMake-GUI, you have to build and install the OpenCV at the prefix that was given earlier.

# We are in ~/opencv/build directory
$ make -j$(nproc)
# After the above command runs upto 100%, run the below command:
$ sudo make install -j$(nproc)

Finally, you have successfully built and Cross-compiled OpenCV for RISC-V!!

Part 3: Testing the built library using QEMU

In this part, we will be testing a simple Edge detection code with the help of QEMU and the cross-compiler.

Code for the Edge Detection:

Cross compile the above code with the following command:

$ riscv64-unknown-linux-gnu-g++ egde.cpp -o edge -I /usr/local/opencv-rvv/include/opencv4 -L /usr/local/opencv-rvv/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc

With this, an executable named edge will be compiled and ready to run on QEMU. To run on QEMU, simply run this command:

$ qemu-riscv64 edge img.jpg

Below is the illustration of the same.

Errors faced during the testing:

  1. The header cannot be found.
fatal error: opencv2/opencv.hpp: No such file or directory

Solution: Include this -I /usr/local/opencv-rvv/include/opencv4while compilation.

2. Cannot find the library file.

cannot find -lopencv_core ld: cannot find -lopencv_imgcodecs

Solution: Include this -L /usr/local/opencv-rvv/libwhile compilation.

3. After the successful compilation, the dynamic library cannot be found while loading during the runtime.

error while loading shared libraries: libopencv_core.so.4.5: cannot open shared object file: No such file or directory

Solution: Export the library path(before compilation).

export LD_LIBRARY_PATH=”/usr/local/opencv-rvv/lib:$LD_LIBRARY_PATH”

And we are done with this!!

--

--