Building the Software#

This section will provide details on how the software is constructed. The basic steps and build requirements can be found in the README.md file which is distributed with the source.

Building the Firmware#

The XVF3800 firmware follows a standard approach to building software using CMake. CMake is a cross platform build tool that supports most targets through configurable toolchains. For more details on CMake and to learn more about what the build scripts do, see the documentation at the CMake website. Each release is built with CMake 3.24.1, but any version greater than 3.21 should work.

The build process for XVF3800 works by creating CMake targets with add_executable() and add_library() that specify source files and compile flags. Each target is then linked together with target_link_libraries() forming the final binary. If a library is STATIC then it is compiled into an archive (.a file) using only the compilation flags that are added to that library and the ones it inherits from the libraries it is linked to. If a library is INTERFACE then it is not compiled into an archive; instead, all the source files and compilation flags are added to the library or executable it is linked against. The key difference is that when applying compilation flags to an executable, they will be applied to linked INTERFACE libraries, but not the linked STATIC libraries. The XVF3800 build process uses both INTERFACE and STATIC libraries, but mostly uses INTERFACE. This means that adding flags to the executable will usually have the desired effect.

While the above describes a standard CMake build, the toolchain used in the XVF3800 presents one key difference to the standard approach, which stems from the multi-tile design of the xcore. To enable each tile to have a separate build configuration, the toolchain constructs the XVF3800 application by merging two applications together. Multiple “.xe” files are produced for each target, one for each tile and a final one that is their combination. The CMake function merge_binaries(), which is defined in xmos_macros.cmake, creates the combined application. Only the merged binary is important; the others can be ignored.

The CMake build process follows two stages. The first is configuration; in this stage the script in the top level CMakeLists.txt is run, which in turn runs many other CMakeLists.txt and CMake scripts. To run the configuration a number of parameters need to be passed into CMake. In order to ensure the correct flags are used, a CMakePresets.json has been included which provides the necessary configuration presets:

  • rel_app_xvf3800 for Linux and macOS

  • rel_app_xvf3800_windows for Windows

Configuration will generate the Makefiles for building the executable in the directory specified by the preset. The XVF3800 build process is designed so that one CMake configuration defines all target executables so managing multiple configurations is not required.

The second stage is the build stage. In this stage, GNU Make (when using Linux, macOS, or Raspberry Pi OS) or Ninja (when using Windows) is used to compile the sources as specified in CMakeLists.txt. To build a specific target there is a choice of using one of the build presets specified in CMakePresets.json or to name the target explicitly. Presets are available for existing targets, but will not exist for any new targets added to the released package. Below shows the two ways to build the same target:

# Build with preset.
cmake --build --preset=intdev-lr48-lin-i2c

# Build with explicit target name.
cmake --build build/app_xvf3800 --target application_xvf3800_intdev-lr48-lin-i2c

Adding or Modifying Build Configurations#

The source release of XVF3800 defines a number of executables with a different combination of features. Each of these are defined in a section titled “Build profiles” in the CMakeLists.txt that can be found at sources/app_xvf3800 in the source release package. The script takes the following steps:

  1. Define a variable named BUILD_PROFILES which is a list of the names of all the targets.

  2. Iterate through the build profiles, defining a list of definitions, source files and libraries for each one. A set of patterns are checked for common build flags; this is documented as comments in CMakeLists.txt.

  3. Iterate through them again defining the libraries and executables required for the target, including the extra flags defined in the previous step.

To add a new target, the name must be included in the BUILD_PROFILES list. There are two pathways available for configuring a new target. The first is to build the name out of the patterns described in CMakeLists.txt to configure the desired combination of data rate, microphone geometry, etc. Alternatively, choose a completely different name and then add the flags that are needed. Care must be taken to ensure a valid combination of flags and sources are used, start by copying from an existing target. An example of what that may look like is shown in this example which creates a target named “my-custom-target”. This shows the sections of CMakeLists.txt which would need to be modified, “…” represents skipped parts of the file.

set(BUILD_PROFILES
    "intdev-lr16-lin-spi"
    "intdev-lr16-sqr-spi"
    # ...
    "my-custom-target"    # 1. Add target to the list.
)

# ...

foreach(PROFILE ${BUILD_PROFILES})

    # ... assorted checks for common patterns ...

    # 2. Add build flags for the target with the new name.
    #    Note that this must be *above* the check for EXTRA_BUILD_INCLUDED.
    if(PROFILE STREQUAL "my-custom-target")
        add_to_build_opts("INT_DEVICE=1")
        add_to_build_opts("appconfLRCLK_NOMINAL_HZ=48000")
        add_to_build_opts("appconfSPI_CTRL_ENABLED=0")
        add_to_build_opts("appconfI2C_CTRL_ENABLED=1")
        add_to_build_opts("appconfUSER_CONFIG_ENABLED=0")
        add_to_build_sources(src/default_params/product_defaults.c)
        add_to_build_libs(fwk_xvf::bsp_config::xk_voice_sq66_evk)

        # Set a boolean to indicate if this is an extra build.
        set(EXTRA_BUILD_INCLUDED ON)
    endif()
endforeach()

This will create a target named application_xvf3800_my-custom-target which can be compiled as explained in section Building the Firmware.

Adding New Files and Compilation Flags to the Build#

For simple changes, such as adding individual files and new definitions, the example shown in Adding or Modifying Build Configurations can easily be extended to add as many files and defines as desired using add_to_build_sources() and add_to_build_opts(). If something more complex is desired, the best option will be to define a new INTERFACE library and include it in the build.

The following is a basic example of how to do this. This will not cover anything an experienced CMake user hasn’t seen before. To create a library named my_custom_lib, create a directory named my_custom_lib under the sources/app_xvf3800 directory with the contents shown:

sources/app\_\ |project_lc|
├── CMakeLists.txt
└── my_custom_lib
   ├── CMakeLists.txt
   ├── my_custom_source.c
   ├── my_other_source.c
   └── my_custom_source.h

Somewhere near the top of sources/app_xvf3800/CMakeLists.txt add the following line so that CMake knows the new directory exists:

add_subdirectory(my_custom_lib)

Now add the following to sources/app_xvf3800/my_custom_lib/CMakeLists.txt:

# create the target.
add_library(my_custom_lib INTERFACE)

# add the sources.
target_sources(my_custom_lib INTERFACE my_custom_source.c my_other_source.c)

# add current directory to the search path so the header
# file can be used in other places.
target_include_directories(my_custom_lib INTERFACE ${CMAKE_CURRENT_LIST_DIR})

# Add arbitrary defines and flags and anything else CMake will allow.
target_compile_definitions(my_custom_lib INTERFACE MY_CUSTOM_FEATURE=1)

# Add flag to individual file.
set_source_files_properties(my_other_source.c PROPERTIES COMPILE_OPTIONS "-O0")

The final step is to link the library against the new target. This requires adding the following line to sources/app_xvf3800/CMakeLists.txt alongside the rest of the configuration for the target that is shown in Adding or Modifying Build Configurations.

add_to_build_libs(my_custom_lib)

Building the Host Control App#

The host control application is distributed with the release as a binary alongside its associated shared libraries. The control app requires awareness of all the control parameters in the software. The control parameters are defined in YAML files that are located at sources/app_xvf3800/autogeneration/yaml_files in the source release package. When one of these files is modified it is not necessary to rebuild the whole host control application; only the shared library containing the command map needs to be updated. This shared library is named command_map.dll for Windows, libcommand_map.so for Linux and Raspbian, and libcommand_map.dylib for macOS.

Note

The shf_aec_cmds.yaml and shf_pp_cmds.yaml files are auto-generated and should not be manually updated.

(lib)command_map.(so/dll/dylib) is compiled from source files that are generated based on the YAML files. Updating the command map library requires:

  • the XVF3800 source release package, including all sub-modules

  • CMake with minimum version 3.13

  • Python with the non-standard packages pyyaml and jinja2.

CMake can be installed from any location using:

sudo apt install -y cmake

The non-standard Python packages can be installed from any location using:

pip install pyyaml jinja2

To build the command map, run the following commands from the directory containing the source release package:

pushd sources/modules/fwk_xvf/modules/host_cmd_map
cmake -B build
pushd build
make
popd
popd

The compiled libcommand_map.so should then be copied to the same directory as xvf_host, and it will be used automatically. The build process is defined by sources/modules/fwk_xvf/modules/host_cmd_map/CMakeLists.txt. This includes finding the YAML files, generating the source code and finally building the shared library.