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 macOSrel_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, Mac OS, 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:
Define a variable named
BUILD_PROFILES
which is a list of the names of all the targets.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
.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 app_xvf3800
directory with the contents shown:
sources/app_xvf3800
├── CMakeLists.txt
└── my_custom_lib
├── CMakeLists.txt
├── my_custom_source.c
├── my_other_source.c
└── my_custom_source.h
Somewhere near the top of 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 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 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/cmd_map_gen/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 libcommand_map.so
needs to be updated.
libcommand_map.so
is compiled from source files that are generated based on the YAML files. Updating libcommand_map.so
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.