diff --git a/app/boards/intel_adsp_cavs25.conf b/app/boards/intel_adsp_cavs25.conf index 7cd938ec7ff8..e1c6df6e44f6 100644 --- a/app/boards/intel_adsp_cavs25.conf +++ b/app/boards/intel_adsp_cavs25.conf @@ -12,6 +12,13 @@ CONFIG_COMP_MFCC=y CONFIG_COMP_MULTIBAND_DRC=y CONFIG_COMP_VOLUME_WINDOWS_FADE=y CONFIG_FORMAT_CONVERT_HIFI3=n +CONFIG_SOF_STAGING=y +CONFIG_CPP=y +CONFIG_STD_CPP17=y +# cavs2.5 has no LLEXT/module-manager support (see CONFIG_LIBRARY_MANAGER=n +# below), so build tensorflow in statically rather than as an LLEXT module. +CONFIG_COMP_TENSORFLOW=y +CONFIG_STACK_SIZE_EDF=32768 CONFIG_PCM_CONVERTER_FORMAT_S16LE=y CONFIG_PCM_CONVERTER_FORMAT_S24LE=y CONFIG_PCM_CONVERTER_FORMAT_S32LE=y @@ -33,7 +40,8 @@ CONFIG_SOF_LOG_LEVEL_INF=y CONFIG_DEBUG_COREDUMP=y CONFIG_DEBUG_COREDUMP_BACKEND_INTEL_ADSP_MEM_WINDOW=y CONFIG_DEBUG_COREDUMP_MEMORY_DUMP_MIN=y -CONFIG_HEAP_MEM_POOL_SIZE=8192 +CONFIG_HEAP_MEM_POOL_SIZE=32768 +CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=32768 # Zephyr / device drivers CONFIG_DAI_INIT_PRIORITY=70 diff --git a/scripts/tensorflow-clone.sh b/scripts/tensorflow-clone.sh index d3f67d0a835e..044a46ed4578 100755 --- a/scripts/tensorflow-clone.sh +++ b/scripts/tensorflow-clone.sh @@ -24,8 +24,9 @@ declare -a COMMIT_ID=( "d37128311b445e758136b8602d1bbd2a755e115d" ) -# Directory where repositories will be cloned/updated. -BASE_DIR="$HOME/work/sof" # Or any other desired location +# Directory where repositories will be cloned/updated. Override by exporting +# BASE_DIR before invoking the script. +BASE_DIR="${BASE_DIR:-$HOME/work/sof}" # Function to check if a commit ID exists in a repository check_commit() { diff --git a/src/audio/buffers/audio_buffer.c b/src/audio/buffers/audio_buffer.c index 1ecf8472dd65..f8a3087b0e64 100644 --- a/src/audio/buffers/audio_buffer.c +++ b/src/audio/buffers/audio_buffer.c @@ -24,7 +24,10 @@ int audio_buffer_attach_secondary_buffer(struct sof_audio_buffer *buffer, bool at_input, struct sof_audio_buffer *secondary_buffer) { - if (buffer->secondary_buffer_sink || buffer->secondary_buffer_source) + /* check per-side: allow attaching on both sides (needed for DP-to-DP) */ + if (at_input && buffer->secondary_buffer_sink) + return -EINVAL; + if (!at_input && buffer->secondary_buffer_source) return -EINVAL; /* secondary buffer must share audio params with the primary buffer */ @@ -48,6 +51,50 @@ int audio_buffer_sync_secondary_buffer(struct sof_audio_buffer *buffer, size_t l struct sof_source *data_src; struct sof_sink *data_dst; + if (buffer->secondary_buffer_sink && buffer->secondary_buffer_source) { + /* + * DP-to-DP case: both secondary buffers present. + * Data flows: input_ring_buffer -> comp_buffer -> output_ring_buffer + * + * This buffer may be synced by two DP modules during the same LL cycle: + * - The source DP module syncs it via comp_dev_for_each_consumer (output) + * - The sink DP module syncs it via comp_dev_for_each_producer (input) + * + * Both steps run in order (source DP first, then sink DP). Performing + * them both here in a single call ensures atomicity and correct + * rate-limiting. The second call for the same buffer will be a no-op + * since the comp_buffer will be empty. + * + * Step 1: copy from input secondary buffer to primary (comp_buffer). + * No limit on input side - copy all available data. + */ + data_src = audio_buffer_get_source(buffer->secondary_buffer_sink); + data_dst = &buffer->_sink_api; + + size_t data_available = source_get_data_available(data_src); + size_t free_size = sink_get_free_size(data_dst); + size_t to_copy = MIN(data_available, free_size); + + err = source_to_sink_copy(data_src, data_dst, true, to_copy); + if (err) + return err; + + /* + * Step 2: copy from primary (comp_buffer) to output secondary buffer. + * Apply the limit to the output side to control how much data + * is made available to the downstream DP module per LL cycle. + */ + data_src = &buffer->_source_api; + data_dst = audio_buffer_get_sink(buffer->secondary_buffer_source); + + data_available = source_get_data_available(data_src); + free_size = sink_get_free_size(data_dst); + to_copy = MIN(MIN(data_available, free_size), limit); + + err = source_to_sink_copy(data_src, data_dst, true, to_copy); + return err; + } + if (buffer->secondary_buffer_sink) { /* * audio_buffer sink API is shadowed, that means there's a secondary_buffer @@ -203,18 +250,16 @@ uint32_t audio_buffer_sink_get_lft(struct sof_sink *sink) return us_in_buffer; /* - * TODO, Currently there's no DP to DP connection - * >>> the code below is never accessible and won't work because of cache incoherence <<< - * - * to make DP to DP connection possible: + * NOTE: DP-to-DP connections are now supported via dual ring_buffers + * attached as secondary buffers on both sides of a comp_buffer. + * Data cascades: ring_buf_src -> comp_buffer -> ring_buf_sink + * with syncing during each LL cycle. * - * 1) module data must be ALWAYS located in non cached memory alias, allowing - * cross core access to params like period (needed below) and calling - * module_get_deadline for the next module, regardless of cores the modules are - * running on - * 2) comp_buffer must be removed from all pipeline code, replaced with a generic abstract - * class audio_buffer - allowing using comp_buffer and ring_buffer without current - * "hybrid buffer" solution + * Future improvements: + * 1) module data should be in non-cached memory alias for reliable + * cross-core access to params like period and deadlines + * 2) comp_buffer should be replaced with generic audio_buffer + * throughout pipeline code (Pipeline 2.0) */ } diff --git a/src/audio/buffers/ring_buffer.c b/src/audio/buffers/ring_buffer.c index 51245e91ca40..4f66c5b639ef 100644 --- a/src/audio/buffers/ring_buffer.c +++ b/src/audio/buffers/ring_buffer.c @@ -86,7 +86,6 @@ static inline void ring_buffer_writeback_shared(struct ring_buffer *ring_buffer, dcache_writeback_region(ptr, size); } - /** * @brief remove the queue from the list, free memory */ @@ -101,6 +100,12 @@ static void ring_buffer_free(struct sof_audio_buffer *audio_buffer) sof_ctx_free(alloc, (__sparse_force void *)ring_buffer->_data_buffer); sof_ctx_free(alloc, ring_buffer); + + /* matches vregion_get() in ipc_comp_connect() for each ring_buffer */ + if (alloc && alloc->vreg) { + if (!vregion_put(alloc->vreg)) + rfree(alloc); + } } static void ring_buffer_reset(struct sof_audio_buffer *audio_buffer) diff --git a/src/audio/mfcc/mfcc.c b/src/audio/mfcc/mfcc.c index 724d3d7faf06..89294a6a9ac8 100644 --- a/src/audio/mfcc/mfcc.c +++ b/src/audio/mfcc/mfcc.c @@ -241,7 +241,16 @@ static int mfcc_prepare(struct processing_module *mod, /* Initialize MFCC, max_frames is set to dev->frames + 4 */ if (cd->config && data_size > 0) { - ret = mfcc_setup(mod, dev->frames + 4, audio_stream_get_rate(&sourceb->stream), + int max_frames = dev->frames + 4; + + /* DP wakes on ibs (~1 hop of input); consume the whole + * hop per call so we don't re-enter the DP thread many + * times per LL tick just to nibble dev->frames at a time. + */ + if (dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP) + max_frames = MAX(max_frames, cd->config->frame_shift); + + ret = mfcc_setup(mod, max_frames, audio_stream_get_rate(&sourceb->stream), audio_stream_get_channels(&sourceb->stream)); if (ret < 0) { comp_err(dev, "setup failed."); diff --git a/src/audio/mfcc/tune/setup_mfcc.m b/src/audio/mfcc/tune/setup_mfcc.m index dbf69587a74f..8192d104d556 100644 --- a/src/audio/mfcc/tune/setup_mfcc.m +++ b/src/audio/mfcc/tune/setup_mfcc.m @@ -31,6 +31,26 @@ function setup_mfcc() setup.tplg_fn = 'mel80_compress.conf'; export_mfcc_setup(gen_cfg, setup); + % Blob for 40-bin/20ms-hop mel spectrogram, matching TFLM micro_speech's + % front-end shape (TFLM_FEATURE_SIZE=40, TFLM_FEATURE_STRIDE_MS=20, + % TFLM_FEATURE_DURATION_MS=30) for interim wake-word sanity-checking. + setup = get_mel_spectrogram_config(); + setup.frame_length = 30.0; % 480 samples at 16 kHz + setup.frame_shift = 20.0; % 320 samples at 16 kHz + setup.num_mel_bins = 40; + setup.tplg_fn = 'mel40.conf'; + export_mfcc_setup(gen_cfg, setup); + + % Same 40-bin/20ms-hop mel spectrogram with compress PCM output for the + % on-device TFLM wake-word path (KPB -> SRC -> MFCC -> tflmcly). + setup = get_mel_spectrogram_config(); + setup.frame_length = 30.0; + setup.frame_shift = 20.0; + setup.num_mel_bins = 40; + setup.compress_output = true; + setup.tplg_fn = 'mel40_compress.conf'; + export_mfcc_setup(gen_cfg, setup); + % Blob for mel spectrogram with compress PCM output and DTX setup = get_mel_spectrogram_config(); setup.compress_output = true; diff --git a/src/audio/stft_process/stft_process-generic.c b/src/audio/stft_process/stft_process-generic.c index 5241c372261f..cbbe859b3e9a 100644 --- a/src/audio/stft_process/stft_process-generic.c +++ b/src/audio/stft_process/stft_process-generic.c @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include "stft_process.h" diff --git a/src/audio/stft_process/stft_process-hifi3.c b/src/audio/stft_process/stft_process-hifi3.c index 6cf7c3dc7e85..efcef00ca474 100644 --- a/src/audio/stft_process/stft_process-hifi3.c +++ b/src/audio/stft_process/stft_process-hifi3.c @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include "stft_process.h" diff --git a/src/audio/tensorflow/CMakeLists.txt b/src/audio/tensorflow/CMakeLists.txt index 5cb3086f46b1..c76af666c0b9 100644 --- a/src/audio/tensorflow/CMakeLists.txt +++ b/src/audio/tensorflow/CMakeLists.txt @@ -1,6 +1,19 @@ # Copyright (c) 2025 Intel Corporation. # SPDX-License-Identifier: Apache-2.0 +# Newer xt-clang LLVMs accept this to keep literals in .rodata; older +# xt-clang (e.g. RI-2022.10, LLVM 10) rejects the sub-option. Detect it. +set(TFLM_TEXT_SECTION_LITERALS_FALSE_FLAG "") +if(CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag("-mllvm --text-section-literals=false" + TFLM_HAS_MLLVM_TEXT_SECTION_LITERALS_FALSE) + if(TFLM_HAS_MLLVM_TEXT_SECTION_LITERALS_FALSE) + set(TFLM_TEXT_SECTION_LITERALS_FALSE_FLAG -mllvm --text-section-literals=false) + add_compile_options(${TFLM_TEXT_SECTION_LITERALS_FALSE_FLAG}) + endif() +endif() + # are we building the llext module ? if(CONFIG_COMP_TENSORFLOW STREQUAL "m" AND DEFINED CONFIG_LLEXT) add_subdirectory(llext ${PROJECT_BINARY_DIR}/tflm_llext) @@ -8,73 +21,68 @@ if(CONFIG_COMP_TENSORFLOW STREQUAL "m" AND DEFINED CONFIG_LLEXT) return() endif() -# TODO: detect Hifi4/5 for NN lib kernels +# nnlib-hifi4's NN kernels are HiFi4-specific (HiFi4 TIE intrinsics/headers); +# they don't exist for HiFi3 targets (e.g. tgl/cavs2.5) and there is no +# nnlib-hifi3 equivalent checked out. Gate on the actual core ISA +# (CONFIG_XTENSA_HIFI4), not just "is the compiler Clang" -- Clang is also +# used to build for HiFi3 targets, where this library must not be built. +set(TENSORFLOW_HAVE_NNLIB_HIFI4 FALSE) +if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CONFIG_XTENSA_HIFI4) + set(TENSORFLOW_HAVE_NNLIB_HIFI4 TRUE) +endif() + set(NN_HIFI_PATH ${sof_top_dir}/../nnlib-hifi4/xa_nnlib) -# paths for dependencies -set(TFLM_PATH ${sof_top_dir}/../tflite-micro) -set(FLATBUFFERS_PATH ${sof_top_dir}/../flatbuffers) -set(GEMMLOWP_PATH ${sof_top_dir}/../gemmlowp) -set(RUY_PATH ${sof_top_dir}/../ruy) +# paths for dependencies (each may be overridden with -D=) +if(NOT TFLM_PATH) + set(TFLM_PATH ${sof_top_dir}/../tflite-micro) +endif() +if(NOT FLATBUFFERS_PATH) + set(FLATBUFFERS_PATH ${sof_top_dir}/../flatbuffers) +endif() +if(NOT GEMMLOWP_PATH) + set(GEMMLOWP_PATH ${sof_top_dir}/../gemmlowp) +endif() +if(NOT RUY_PATH) + set(RUY_PATH ${sof_top_dir}/../ruy) +endif() + +# Toolchain include dirs are per-platform (SOC_TOOLCHAIN_NAME matches both +# the zephyr-sdk gnu/xtensa-_zephyr-elf toolchain dir and the +# modules/hal/xtensa/zephyr/soc/ HAL dir for every supported SoC). +set(TFLM_TOOLCHAIN_INCLUDE_ROOT + ${ZEPHYR_SDK_INSTALL_DIR}/gnu/xtensa-${SOC_TOOLCHAIN_NAME}_zephyr-elf/xtensa-${SOC_TOOLCHAIN_NAME}_zephyr-elf/include) +set(TFLM_HAL_SOC_PATH ${sof_top_dir}/../modules/hal/xtensa/zephyr/soc/${SOC_TOOLCHAIN_NAME}) + +if(TENSORFLOW_HAVE_NNLIB_HIFI4) add_library(nn_hifi_lib STATIC ${NN_HIFI_PATH}/algo/common/src/xa_nnlib_common_api.c - ${NN_HIFI_PATH}/algo/kernels/reorg/hifi4/xa_nn_pad_8.c - ${NN_HIFI_PATH}/algo/kernels/reorg/hifi4/xa_nn_stride_slice_int16.c - ${NN_HIFI_PATH}/algo/kernels/reorg/hifi4/xa_nn_batch_to_space_nd_8.c - ${NN_HIFI_PATH}/algo/kernels/reorg/hifi4/xa_nn_transpose_8.c - ${NN_HIFI_PATH}/algo/kernels/reorg/hifi4/xa_nn_depth_to_space_8.c - ${NN_HIFI_PATH}/algo/kernels/reorg/hifi4/xa_nn_pad_16.c - ${NN_HIFI_PATH}/algo/kernels/reorg/hifi4/xa_nn_stride_slice_int32.c - ${NN_HIFI_PATH}/algo/kernels/reorg/hifi4/xa_nn_space_to_batch_nd_8.c - ${NN_HIFI_PATH}/algo/kernels/reorg/hifi4/xa_nn_pad_32.c - ${NN_HIFI_PATH}/algo/kernels/reorg/hifi4/xa_nn_stride_slice_int8.c - ${NN_HIFI_PATH}/algo/kernels/reorg/hifi4/xa_nn_space_to_depth_8.c ${NN_HIFI_PATH}/algo/kernels/activations/hifi4/xa_nn_activations_32_32.c ${NN_HIFI_PATH}/algo/kernels/activations/hifi4/xa_nn_activations_f32_f32.c - ${NN_HIFI_PATH}/algo/kernels/activations/hifi4/xa_nn_activations_sym16_sym16.c - ${NN_HIFI_PATH}/algo/kernels/activations/hifi4/xa_nn_softmax_asym8_asym8.c - ${NN_HIFI_PATH}/algo/kernels/activations/hifi4/xa_nn_activations_32_16.c - ${NN_HIFI_PATH}/algo/kernels/activations/hifi4/xa_nn_activations_16_16.c - ${NN_HIFI_PATH}/algo/kernels/activations/hifi4/xa_nn_activations_8_8.c - ${NN_HIFI_PATH}/algo/kernels/activations/hifi4/xa_nn_activations_asym16_asym16.c - ${NN_HIFI_PATH}/algo/kernels/activations/hifi4/xa_nn_activations_32_8.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_matXvec_16x16_16_circ_nb.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_pointwise_16x16.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_depthwise_16x16.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_transpose_conv_circ_buf.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_group_sym8sxasym8s.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_matXvec_asym8xasym8_asym8_circ_nb.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_depthwise_sym8sxasym8s.c + ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_add_f32.c + ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_sub_f32.c + ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_mul_f32.c + ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_abs_f32.c + ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/nanf_tbl.c + ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/inff_tbl.c + ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/pow2f_tbl.c + ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/expf_tbl.c + ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_matXvec_8x8_8_circ.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_pointwise_sym8sxsym16s.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_pointwise_8x16.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_circ_buf.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_std_8x8.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_depthwise.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_matXvec_16x16_16_circ.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_transpose_conv_f32.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_depthwise_f32.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_matXvec_f32_circ.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_std_circ_buf.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_transpose_conv_sym8sxasym8s.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_matXvec_8x16_16_circ.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_matXvec_sym8sxsym16s_sym16s_circ.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv1d_std_8x8.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_std_8x16.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_std_sym8sxsym16s.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_pointwise_f32.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_matXvec_f32_circ_nb.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_std_sym8sxasym8s.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_matXvec_asym8xasym8_asym8_circ.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_pointwise_sym8sxasym8s.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_depthwise_8x8.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_transpose_conv_sym8sxsym16s.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_std_f32.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv1d_std_8x16.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_pointwise_8x8.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_std_16x16.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_matXvec_sym8sxasym8s_asym8s_circ.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_pointwise_asym8xasym8.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_std_asym8xasym8.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv1d_std_circ_buf.c @@ -82,117 +90,11 @@ add_library(nn_hifi_lib STATIC ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_matXvec_8x16_16_circ_nb.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv1d_std_16x16.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_matXvec_8x8_8_circ_nb.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_std_sym4sxasym8s.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv1d_std_f32.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_depthwise_sym8sxsym16s.c ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_depthwise_asym8xasym8.c - ${NN_HIFI_PATH}/algo/kernels/cnn/hifi4/xa_nn_conv2d_depthwise_8x16.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matmul_f32.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_asym8xasym8_batch.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matmul_sym8sxasym8s.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matmul_8x8.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matmul_8x16.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_asym4sxasym8s.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matmul_sym8sxsym16s.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_f32_batch.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_f32.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_16x16_batch.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_sym8sxsym16s.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matmul_asym8sxasym8s.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_16x8.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_8x16.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_8x8.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_asym8sxasym8s.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matmul_asym8xasym8.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matmul_16x16.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_8x16_batch.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_16x16.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_asym8xasym8.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_sym8sxasym8s.c - ${NN_HIFI_PATH}/algo/kernels/matXvec/hifi4/xa_nn_matXvec_8x8_batch.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_maxpool_f32.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_maxpool_8.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_maxpool_asym8_nhwc.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_avgpool_f32_nhwc.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_maxpool_16_nhwc.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_avgpool_16.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_avgpool_16_nhwc.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_avgpool_asym8.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_avgpool.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_inv_256_tbl.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_maxpool.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_maxpool_8_nhwc.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_maxpool_16.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_avgpool_8.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_maxpool_f32_nhwc.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_avgpool_asym8_nhwc.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_avgpool_f32.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_maxpool_asym8.c - ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_avgpool_8_nhwc.c - ${NN_HIFI_PATH}/algo/kernels/norm/hifi4/xa_nn_l2_norm_asym8s.c - ${NN_HIFI_PATH}/algo/kernels/norm/hifi4/xa_nn_l2_norm_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_sqrt_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_mul_16x16.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_add_quant8.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_mul_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_lstm_utils.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_dot_prod_16x16.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_sub_quant16.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_round_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_mul_acc_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_cosine_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_reduce_asym8s_asym8s.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_memset_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_floor_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_squared_diff_quant8.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_add_quant16.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_reduce_asym16s_asym16s.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_minmax_8.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_logn_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_div_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_sub_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_compare_quant8.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_logical_bool.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_broadcast_8_8.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_quantize.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_neg_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_mul_quant16.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_add_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_vec_interpolation_q15.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_square_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_sine_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_abs_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_memmove.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_mul_quant8.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_squared_diff_quant16.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_memmove_16.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_sub_quant8.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_ceil_f32.c - ${NN_HIFI_PATH}/algo/kernels/basic/hifi4/xa_nn_elm_rsqrt_f32.c ${NN_HIFI_PATH}/algo/kernels/fc/hifi4/xa_nn_fully_connected.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/vec_tanh32x32_hifi4.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/scl_sigmoidf_hifi4.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/inv2pif_tbl.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/nanf_tbl.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/pow2f_tbl.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/vec_sigmoidf_hifi4.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/vec_reluf_hifi4.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/expf_tbl.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/vec_tanhf_hifi4.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/vec_sigmoid32x32_hifi4.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/lognf_tbl.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/sinf_tbl.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/vec_cosinef_hifi4.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/vec_sinef_hifi4.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/vec_softmaxf_hifi4.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/scl_tanhf_hifi4.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/vec_softmax32x32_hifi4.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/vec_lognf_hifi4.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/sqrt2f_tbl.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/inff_tbl.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/vec_alognf_hifi4.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/vec_relu32x32_hifi4.c - ${NN_HIFI_PATH}/algo/ndsp/hifi4/src/tanhf_tbl.c + ${NN_HIFI_PATH}/algo/kernels/pool/hifi4/xa_nn_inv_256_tbl.c + ${NN_HIFI_PATH}/algo/kernels/reorg/hifi4/xa_nn_transpose_8.c + ${NN_HIFI_PATH}/algo/kernels/reorg/hifi4/xa_nn_pad_8.c ) target_include_directories(nn_hifi_lib PRIVATE @@ -200,31 +102,63 @@ target_include_directories(nn_hifi_lib PRIVATE ${NN_HIFI_PATH}/algo/common/include/ ${NN_HIFI_PATH}/include/nnlib/ ${NN_HIFI_PATH}/algo/ndsp/hifi4/include + ${TFLM_TOOLCHAIN_INCLUDE_ROOT} + ${TFLM_HAL_SOC_PATH} ) +target_compile_options(nn_hifi_lib PRIVATE + ${TFLM_TEXT_SECTION_LITERALS_FALSE_FLAG} +) + +endif() # TENSORFLOW_HAVE_NNLIB_HIFI4 + + +if(TENSORFLOW_HAVE_NNLIB_HIFI4) + # TODO: Need to detect and add mul16/32 options. -#ifeq "$(has_mul16)" "0" -#CFLAGS += -mno-mul16 -#endif -#ifeq "$(has_mul32)" "0" -#CFLAGS += -mno-mul32 -mno-div32 -#endif +target_compile_definitions(nn_hifi_lib PRIVATE + -DHIFI4=1 + -DHAVE_VFPU=1 + -DHAVE_VFPU_SINGLE_PRECISION=1 + -DXCHAL_HAVE_HIFI4=1 + -DXCHAL_HAVE_HIFI4_VFPU=1 + + __xtensa__=1 + __XTENSA__=1 + __XCC__ + __XCC_CLANG__ + "XT_MAX(a,b)=((a)>(b)?(a):(b))" + "XT_MIN(a,b)=((a)<(b)?(a):(b))" + "AE_MOVINT16_FROMINT32(v)=((ae_int16)(v))" + "AE_CVT64F32_H(v)=((ae_int64)(int64_t)(int32_t)AE_MOVAD32_H(v))" + "AE_CVT64F32_L(v)=((ae_int64)(int64_t)(int32_t)AE_MOVAD32_L(v))" +) + target_compile_options(nn_hifi_lib PRIVATE -fsigned-char -fno-exceptions -mlongcalls - -INLINE:requested - -mcoproc -fno-zero-initialized-in-bss - -mtext-section-literals -Wsign-compare - -m32 -DMODEL_INT16 -DNNLIB_V2 -Dhifi4 -DTFLITE_SINGLE_ROUNDING=1 ) +target_compile_definitions(nn_hifi_lib PRIVATE + __XCC__ + __XCC_CLANG__ +) +target_compile_options(nn_hifi_lib PRIVATE + -mcpu=${SOC_TOOLCHAIN_NAME} + "SHELL:-include xtensahifiintrin.h" + "SHELL:-include ${NN_HIFI_PATH}/algo/common/include/xa_nnlib_hifi_isa_compat.h" +) + +endif() # TENSORFLOW_HAVE_NNLIB_HIFI4 (nn_hifi_lib defs/opts) + + # TODO: complete sources have been added here from userspace build but # not all are needed so this is a list of "needed" sources to build # a memory and performance optimized TFLM for SOF. @@ -302,7 +236,7 @@ add_library(tflm_lib STATIC #${TFLM_PATH}/tensorflow/lite/micro/kernels/mul_common.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/mul.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/neg.cc - #${TFLM_PATH}/tensorflow/lite/micro/kernels/pack.cc + ${TFLM_PATH}/tensorflow/lite/micro/kernels/pack.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/pooling_common.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/prelu_common.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/prelu.cc @@ -314,7 +248,7 @@ add_library(tflm_lib STATIC #${TFLM_PATH}/tensorflow/lite/micro/kernels/resize_nearest_neighbor.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/round.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/select.cc - #${TFLM_PATH}/tensorflow/lite/micro/kernels/shape.cc + ${TFLM_PATH}/tensorflow/lite/micro/kernels/shape.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/slice.cc ${TFLM_PATH}/tensorflow/lite/micro/kernels/softmax_common.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/space_to_batch_nd.cc @@ -323,7 +257,8 @@ add_library(tflm_lib STATIC #${TFLM_PATH}/tensorflow/lite/micro/kernels/split_v.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/squared_difference.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/squeeze.cc - #${TFLM_PATH}/tensorflow/lite/micro/kernels/strided_slice_common.cc + ${TFLM_PATH}/tensorflow/lite/micro/kernels/strided_slice.cc + ${TFLM_PATH}/tensorflow/lite/micro/kernels/strided_slice_common.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/sub_common.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/svdf_common.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/tanh.cc @@ -332,46 +267,12 @@ add_library(tflm_lib STATIC #${TFLM_PATH}/tensorflow/lite/micro/kernels/var_handle.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/while.cc #${TFLM_PATH}/tensorflow/lite/micro/kernels/zeros_like.cc - # xtensa kernels - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/sub.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/conv_hifi.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/pooling_vision.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/softmax_vision.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/unidirectional_sequence_lstm.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/transpose_conv.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/fully_connected_common_xtensa.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/lstm_eval_hifi.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/add_vision.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/depthwise_conv.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/reshape.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/softmax.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/depthwise_conv_hifi.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/conv_int16_reference.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/conv_int8_reference.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/fully_connected_int8.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/pad_vision.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/fully_connected_vision.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/conv_int8_int16.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/conv.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/softmax_int8_int16.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/logistic.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/reduce.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/lstm_eval.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/reshape_vision.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/pad.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/reduce_vision.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/dequantize.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/add.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/pooling_int8.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/depthwise_conv_vision.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/strided_slice.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/conv_common_xtensa.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/svdf.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/fully_connected.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/leaky_relu.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/quantize.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/pooling.cc - ${TFLM_PATH}/tensorflow/lite/micro/kernels/xtensa/conv_vision.cc + # reference kernels for speech model ops + ${TFLM_PATH}/tensorflow/lite/micro/kernels/conv.cc + ${TFLM_PATH}/tensorflow/lite/micro/kernels/depthwise_conv.cc + ${TFLM_PATH}/tensorflow/lite/micro/kernels/fully_connected.cc + ${TFLM_PATH}/tensorflow/lite/micro/kernels/reshape.cc + ${TFLM_PATH}/tensorflow/lite/micro/kernels/softmax.cc ${TFLM_PATH}/tensorflow/lite/micro/mock_micro_graph.cc ${TFLM_PATH}/tensorflow/lite/micro/flatbuffer_utils.cc ${TFLM_PATH}/tensorflow/lite/micro/micro_interpreter_graph.cc @@ -393,7 +294,8 @@ add_library(tflm_lib STATIC ${TFLM_PATH}/tensorflow/lite/micro/micro_profiler.cc ${TFLM_PATH}/tensorflow/lite/micro/micro_time.cc #${TFLM_PATH}/tensorflow/lite/micro/static_vector_test.cc - ${TFLM_PATH}/tensorflow/lite/micro/debug_log.cc + # debug_log.cc is replaced by our printk-backed DebugLog() in tflm-classify.c. + #${TFLM_PATH}/tensorflow/lite/micro/debug_log.cc ${TFLM_PATH}/tensorflow/lite/micro/test_helpers.cc ${TFLM_PATH}/tensorflow/lite/micro/micro_op_resolver.cc ${TFLM_PATH}/tensorflow/lite/micro/recording_micro_allocator.cc @@ -404,7 +306,7 @@ add_library(tflm_lib STATIC ${TFLM_PATH}/tensorflow/lite/micro/memory_planner/linear_memory_planner.cc ${TFLM_PATH}/tensorflow/lite/micro/micro_utils.cc ${TFLM_PATH}/tensorflow/lite/micro/micro_interpreter.cc - micro_speech_quantized_model_data.cc + sof_tflm_quantized_model_data.cc speech.cc ) @@ -413,10 +315,16 @@ target_include_directories(tflm_lib PRIVATE ${FLATBUFFERS_PATH}/include ${GEMMLOWP_PATH} ${RUY_PATH} + ${sof_top_dir}/posix/include + ${sof_top_dir}/../modules/hal/xtensa/include + ${TFLM_HAL_SOC_PATH} +) +if(TENSORFLOW_HAVE_NNLIB_HIFI4) +target_include_directories(tflm_lib PRIVATE ${NN_HIFI_PATH} ${NN_HIFI_PATH}/include - ${sof_top_dir}/posix/include ) +endif() # TODO: Need to detect and add mul16/32 options. #ifeq "$(has_mul16)" "0" @@ -425,9 +333,38 @@ target_include_directories(tflm_lib PRIVATE #ifeq "$(has_mul32)" "0" #CFLAGS += -mno-mul32 -mno-div32 #endif + +# These select/enable the HiFi4 nnlib-optimized code paths in some TFLM +# kernels. Only valid when nn_hifi_lib is actually built and linked -- +# forcing XCHAL_HAVE_HIFI4 on a non-HiFi4 target risks enabling HiFi4-only +# branches in system/toolchain headers that don't apply to this core. +if(TENSORFLOW_HAVE_NNLIB_HIFI4) +target_compile_definitions(tflm_lib PRIVATE + -DHIFI4=1 + -DHAVE_VFPU=1 + -DHAVE_VFPU_SINGLE_PRECISION=1 + -DXCHAL_HAVE_HIFI4=1 + -DXCHAL_HAVE_HIFI4_VFPU=1 + + __xtensa__=1 + __XTENSA__=1 + __XCC__ + __XCC_CLANG__ + "XT_MAX(a,b)=((a)>(b)?(a):(b))" + "XT_MIN(a,b)=((a)<(b)?(a):(b))" + "AE_MOVINT16_FROMINT32(v)=((ae_int16)(v))" + "AE_CVT64F32_H(v)=((ae_int64)(int64_t)(int32_t)AE_MOVAD32_H(v))" + "AE_CVT64F32_L(v)=((ae_int64)(int64_t)(int32_t)AE_MOVAD32_L(v))" +) +target_compile_options(tflm_lib PRIVATE + -DHIFI4 + -DKERNELS_OPTIMIZED_FOR_SPEED + -DNNLIB_V2 +) +endif() # TENSORFLOW_HAVE_NNLIB_HIFI4 + target_compile_options(tflm_lib PRIVATE -std=c++17 - -stdlib=libc++ -fno-rtti -fno-exceptions -fno-threadsafe-statics @@ -447,19 +384,118 @@ target_compile_options(tflm_lib PRIVATE -Wstrict-aliasing -Wno-unused-parameter -DXTENSA - -DKERNELS_OPTIMIZED_FOR_SPEED -DTF_LITE_MCU_DEBUG_LOG -DTF_LITE_USE_CTIME - --xtensa-core=ace10_LX7HiFi4_2022_10 - -mcoproc - -DHIFI4 -mlongcalls - -DNNLIB_V2 -Wno-shadow ) +if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_compile_options(tflm_lib PRIVATE + -stdlib=libc++ + "SHELL:-include xtensahifiintrin.h" + -fno-vectorize + -fno-slp-vectorize + ) +else() + # gcc equivalents for vectorize disabling + target_compile_options(tflm_lib PRIVATE + -fno-tree-vectorize + -fno-tree-slp-vectorize + ) +endif() + +if(CMAKE_C_COMPILER_ID STREQUAL "Xtensa") + target_compile_options(tflm_lib PRIVATE + --xtensa-core=ace10_LX7HiFi4_2022_10 + -mcoproc + ) +elseif(CMAKE_C_COMPILER_ID STREQUAL "Clang") + target_compile_options(tflm_lib PRIVATE + -mcpu=${SOC_TOOLCHAIN_NAME} + ) +endif() + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_include_directories(tflm_lib SYSTEM PRIVATE + ${TFLM_TOOLCHAIN_INCLUDE_ROOT}/c++/14.3.0 + ${TFLM_TOOLCHAIN_INCLUDE_ROOT}/c++/14.3.0/xtensa-${SOC_TOOLCHAIN_NAME}_zephyr-elf + ${TFLM_TOOLCHAIN_INCLUDE_ROOT} + ) +endif() + add_local_sources(sof tflm-classify.c llext-wrap.c) -# Need to link libc++ and libc after tflm and nnlib -zephyr_link_libraries(tflm_lib nn_hifi_lib c++ c) +# Link nnlib only when it was actually built (HiFi4 target + Clang) +if(TENSORFLOW_HAVE_NNLIB_HIFI4) + zephyr_link_libraries(tflm_lib nn_hifi_lib) +else() + zephyr_link_libraries(tflm_lib) +endif() + +# CONFIG_MINIMAL_LIBC (SOF's global default) has no libm and is missing a +# few libc functions (e.g. abs()) that TFLM needs in a statically-linked +# (non-LLEXT) image. Linking the toolchain's whole libc.a is too broad: it +# conflicts with Zephyr's own malloc/free (multiple definition) and pulls in +# an __assert_no_args that needs an unavailable stderr. Instead, extract just +# the specific archive members TFLM actually needs into a small private +# archive and link only that. +if(NOT CONFIG_COMP_TENSORFLOW STREQUAL "m") + set(TFLM_TOOLCHAIN_LIBC_ARCHIVE + ${ZEPHYR_SDK_INSTALL_DIR}/gnu/xtensa-${SOC_TOOLCHAIN_NAME}_zephyr-elf/xtensa-${SOC_TOOLCHAIN_NAME}_zephyr-elf/lib/libc.a) + set(TFLM_LIBC_SHIM_MEMBERS + libc_stdlib_abs.c.o + libm_math_s_floor.c.o + # TFLM QuantizeMultiplier() uses double frexp() + round() at Prepare time. + libm_math_s_frexp.c.o + libm_common_s_round.c.o + libm_math_sf_exp.c.o + libm_math_sf_log.c.o + libm_math_sf_ceil.c.o + libm_common_sf_fmax.c.o + libm_common_sf_fmin.c.o + libm_common_sf_round.c.o + # error-handling/predicate helpers the above call internally + libm_common_sf_isnan.c.o + libm_common_sf_issignaling.c.o + libm_common_math_errf_uflowf.c.o + libm_common_math_errf_oflowf.c.o + libm_common_math_errf_divzerof.c.o + libm_common_math_errf_invalidf.c.o + ) + set(TFLM_LIBC_SHIM_DIR ${CMAKE_CURRENT_BINARY_DIR}/tflm_libc_shim) + set(TFLM_LIBC_SHIM_ARCHIVE ${CMAKE_CURRENT_BINARY_DIR}/libtflm_libc_shim.a) + file(MAKE_DIRECTORY ${TFLM_LIBC_SHIM_DIR}) + add_custom_command( + OUTPUT ${TFLM_LIBC_SHIM_ARCHIVE} + COMMAND ${CMAKE_AR} x ${TFLM_TOOLCHAIN_LIBC_ARCHIVE} ${TFLM_LIBC_SHIM_MEMBERS} + COMMAND ${CMAKE_AR} rcs ${TFLM_LIBC_SHIM_ARCHIVE} ${TFLM_LIBC_SHIM_MEMBERS} + WORKING_DIRECTORY ${TFLM_LIBC_SHIM_DIR} + DEPENDS ${TFLM_TOOLCHAIN_LIBC_ARCHIVE} ${CMAKE_CURRENT_LIST_FILE} + COMMENT "Extracting abs()/libm members TFLM needs from the toolchain libc.a" + ) + add_custom_target(tflm_libc_shim_gen DEPENDS ${TFLM_LIBC_SHIM_ARCHIVE}) + add_library(tflm_libc_shim STATIC IMPORTED GLOBAL) + set_target_properties(tflm_libc_shim PROPERTIES IMPORTED_LOCATION ${TFLM_LIBC_SHIM_ARCHIVE}) + add_dependencies(tflm_libc_shim tflm_libc_shim_gen) + zephyr_link_libraries(tflm_libc_shim) + + # TFLM/flatbuffers use assert() internally; the toolchain's own + # __assert_no_args implementation needs an unavailable stderr, so disable + # assert() outright instead (standard practice for release TFLM builds). + target_compile_definitions(tflm_lib PRIVATE NDEBUG) +endif() zephyr_include_directories(${TFLM_PATH}) +zephyr_include_directories(${FLATBUFFERS_PATH}/include) +zephyr_include_directories(${GEMMLOWP_PATH}) +zephyr_include_directories(${RUY_PATH}) +if(TENSORFLOW_HAVE_NNLIB_HIFI4) +zephyr_include_directories(${NN_HIFI_PATH}/algo/kernels/include) +zephyr_include_directories(${NN_HIFI_PATH}/include) +endif() +target_include_directories(modules_sof SYSTEM PRIVATE + ${TFLM_TOOLCHAIN_INCLUDE_ROOT}/c++/14.3.0 + ${TFLM_TOOLCHAIN_INCLUDE_ROOT}/c++/14.3.0/xtensa-${SOC_TOOLCHAIN_NAME}_zephyr-elf + ${TFLM_TOOLCHAIN_INCLUDE_ROOT} +) + diff --git a/src/audio/tensorflow/Kconfig b/src/audio/tensorflow/Kconfig index 446f9add2849..3d4455042b71 100644 --- a/src/audio/tensorflow/Kconfig +++ b/src/audio/tensorflow/Kconfig @@ -10,3 +10,25 @@ config COMP_TENSORFLOW Tensorflow Micro library. It is used for running machine learning models on DSP. It is a lightweight version of Tensorflow library designed for microcontrollers and embedded systems. + +if COMP_TENSORFLOW + +config COMP_TENSORFLOW_DEBUG_TRACE + bool "Verbose per-hop / per-inference debug traces" + default n + help + Enable the tflmcly per-hop ("[DBG hop N]"), per-inference + ("[PERF] TFLM Inference #N", "[DBG raw_output]", "TFLM top + prediction", per-node cycle counters, model-graph node count) + and sliding-window ("[DBG window] nonsat=…") debug prints in + the tflm-classify hot path. These fire ~50 times per second + (once per 20 ms MFCC hop) plus a burst every 500 ms and go + through printk/mtrace, which is not free on the DSP and can + contribute to backpressure on log/RTT paths. + + Leave disabled for production and normal capture tuning. Enable + only when actively debugging feature extraction, quantization, + or per-node timing. The keyword-detected line and the shutdown + summary remain enabled regardless of this option. + +endif # COMP_TENSORFLOW diff --git a/src/audio/tensorflow/README.md b/src/audio/tensorflow/README.md index a8e3f59ba3ce..89b25c812ca5 100644 --- a/src/audio/tensorflow/README.md +++ b/src/audio/tensorflow/README.md @@ -1,22 +1,649 @@ -# TensorFlow Lite Micro (TFLM) Architecture +# TensorFlow Lite Micro (TFLM) & Wake-on-Voice (WoV) Architecture -This directory acts as the bridge for running ML models. +This directory provides the TensorFlow Lite for Microcontrollers (TFLM) classification module (`TFLMCLY`) for Sound Open Firmware (SOF), including integration with MFCC feature extraction, `mtrace` stream shutdown summary logging, IPC host notifications, Key Phrase Buffer (KPB) Wake-on-Voice (WoV) trigger infrastructure, and abstracted audio input sources across **HDA**, **DMIC**, **SSP (I2S)**, and **SoundWire (ALH)**. + +Two build modes are supported, selected automatically per target core: + +- **LLEXT module** (HiFi4/HiFi5 targets: MTL, PTL, ARL, ...) — `CONFIG_COMP_TENSORFLOW=m`, built with Clang, linked against the HiFi4-optimized `nnlib-hifi4` kernels. +- **Statically linked into `zephyr.elf`** (HiFi3 targets with no LLEXT/module-manager support, e.g. TGL/cavs2.5) — `CONFIG_COMP_TENSORFLOW=y`, built with the Zephyr SDK GCC toolchain, `nnlib-hifi4` NOT built or linked (see [Build Instructions](#build-instructions) below). + +--- ## Overview -Integrates TensorFlow Lite for Microcontrollers into the SOF audio pipeline. Evaluates pre-trained neural network topologies inline with the audio stream for tasks like wake-word, noise cancellation, or sound classification. +The TFLM module evaluates pre-trained micro speech neural network models inline within the SOF audio processing graph. It receives pre-processed audio feature tensors (mel-log spectrograms from the `mfcc` component), runs model inference in the **Data Processing (DP) domain**, logs keyword detections and stream shutdown event summaries to `mtrace`, issues IPC4 notifications to the host audio driver, and signals the KPB module to drain pre-roll audio history upon keyword detection. + +--- + +## Architecture & Data Flow + +### Dual-Path Wake-on-Voice (WoV) Architecture -## Architecture Diagram +To allow continuous keyword evaluation without streaming audio to the host until a keyword is detected, the pipeline separates real-time keyword detection from host PCM draining via KPB: ```mermaid -graph LR - Feat[Audio Features] --> TFLM[TFLM Inference Engine] - SubGraph[FlatBuffer Model] -.-> TFLM - TFLM --> Out[Inference Labels/Scores] +graph TD + subgraph Audio_Inputs ["Abstracted Hardware DAI Input Sources"] + HDA["HDA Analog Input (dai_type: HDA)"] + DMIC["PCH DMIC Digital Mic (dai_type: DMIC)"] + SSP["I2S / Bluetooth Codec (dai_type: SSP)"] + SDW["SoundWire SmartMic (dai_type: ALH)"] + end + + subgraph DAI_Abstraction ["Backend DAI Copier Widget"] + DAI["dai-copier.1 (dai_type: $CAPTURE_DAI_TYPE)"] + end + + subgraph KPB_Pipeline ["Capture & KPB Pipeline"] + Gain["gain.2.1 (Volume Control)"] + KPB["kpb.2.1 (Key Phrase Buffer)"] + end + + subgraph Detect_Pipeline ["Real-Time Detection Path (KPB Pin 1) - DP Domain"] + SRC["src.1.1 (Resampler: 48kHz -> 16kHz)"] + MFCC["mfcc.1.1 (Mel-40 Feature Extractor)"] + TFLM["tflmcly.1.1 (TFLM Keyword Classifier)"] + DetHost["host-copier.1.capture (PCM device 1: arm/observe only, see Usage)"] + end + + subgraph Host_Pipeline ["Host WoV Draining Path (KPB Pin 2)"] + Host["host-copier.0.capture (PCM device 0: real drain target)"] + end + + HDA --> DAI + DMIC --> DAI + SSP --> DAI + SDW --> DAI + + DAI --> Gain + Gain --> KPB + KPB -- Pin 1: Live Audio --> SRC + SRC --> MFCC + MFCC -- Mel-40 Q9.23 Tensors --> TFLM + TFLM --> DetHost + + TFLM -.->|1. KPB_EVENT_BEGIN_DRAINING Notifier| KPB + TFLM -.->|2. IPC4 Notification (scaffolded, not wired up)| Host_Driver[Host Driver] + TFLM -.->|3. Stream Shutdown Event Logging| MTrace[mtrace / Trace Log] + + KPB -- Pin 2: Pre-roll History Buffer --> Host +``` + +**Note on pipe 1's host-copier**: `host-copier.1.capture` (PCM device 1, "HDA Mic TFLM Detect") exists to *arm/instantiate* the detection pipeline and to give a host-visible device for debugging — it is **not** the WoV drain target. `arecord`-ing it will get an immediate `Input/output error` once its ring buffer underruns, because `tflmcly` (not the host) is the real consumer on this pipe; this is expected. The pipeline stays fully armed and running as long as the PCM is *opened*, whether or not a read ever succeeds — see [Usage](#usage) for how to hold it open for testing without triggering the read-error teardown. The actual pre-roll audio delivered to the host on keyword detection always arrives via `host-copier.0.capture` (PCM device 0). + +--- + +## Domain Execution Model (LL vs. DP) + +- **Low Latency (LL) Domain (Timer Task / 1ms tick loop)**: + - Components: `dai-copier`, `eqiir`, `tdfb`, `drc`, `host-copier` + - Purpose: Fixed 1 ms tick loop executed on Core 0 to meet hard real-time audio hardware deadlines. + - Metrics: Reported via `ll_schedule.stats_report` (e.g. `ll core 0 timer avg 22,049 cycles (~56 µs)`). + +- **Data Processing (DP) Domain (Asynchronous Task)**: + - Components: `src`, `micsel`, `mfcc`, `tflmcly` + - Purpose: Runs asynchronously in a background task whenever a new feature hop is produced by `mfcc` — measured empirically at roughly one inference every ~500ms (bring-up wall-clock gate, see [Known Limitations](#known-limitations--open-issues)). + - **Separation**: Because TFLM runs in the DP domain, model inference cycles take place outside the 1 ms LL tick loop, guaranteeing zero impact on real-time LL audio latency or buffer overruns. On targets without a pre-existing DP scheduler user, `platform_init()` now calls `scheduler_dp_init()` explicitly so `mfcc`/`tflmcly` have somewhere to run (`src/platform/intel/cavs/platform.c`). + +### MFCC frame format + +Per hop, MFCC emits a 24-byte `struct mfcc_data_header` (magic/frame_number/reserved/energy/noise_energy/vad_flag) followed by `TFLM_FEATURE_SIZE` (40) `int32_t` Q9.23 mel-log values — `24 + 40*4 = 184` bytes (`MFCC_FRAME_BYTES` in the pipeline template). `tflm_process()` strips this header and requantizes each Q9.23 value into `int8_t` against the model's *real* input tensor `scale`/`zero_point` (read from the interpreter at prepare time, not assumed) before feeding the model's 49-hop sliding feature window. + +--- + +## Known Limitations / Open Issues + +- **Feature-representation mismatch (stock model only)**: the shipped 4-class `silence/unknown/yes/no` model was trained against TFLM's original `micro_speech` frontend, which applies a *nonlinear* PCAN auto-gain-control normalization before quantization. SOF's MFCC produces *linear* mel-log values. Empirically, real captured audio normalizes to `norm ≈ 0.03–1.5` (Q9.23 mel-log value / 2^23), while this model's actual `input_scale=0.101715`/`zero_point=-128` need `norm ≈ 0–26` to use its int8 dynamic range — so every real input saturates into the bottom ~8% of the range and the model outputs a flat, content-independent prediction. No linear rescale of `mfcc_mel_q23_to_int8()` fixes this; it needs either a model retrained directly on real SOF MFCC mel-log features (recommended — see [Training a Custom Keyword Model](#training-a-custom-keyword-model-with-piper-tts)), or a from-scratch PCAN-AGC-equivalent normalization stage ahead of quantization. **Training a new model on real SOF features, as described below, avoids this problem entirely** since train-time and inference-time feature extraction then match by construction. +- **500ms inference cadence is a bring-up shortcut**, not the model's trained stride (20ms/hop, 49-hop/~1s sliding window). Fine for initial bring-up; revisit before judging a new model's real-world accuracy, since a cadence mismatch vs. training assumptions can look like a model-quality problem. +- **IPC4 host notification is scaffolded but not wired up** (`tflm_ipc_notification_init()` never allocates/registers `cd->msg`, so `tflm_send_keyword_notification()` silently no-ops). KPB draining (a separate mechanism) still works. Finish this if the host driver needs an explicit "keyword X detected" event rather than just observing PCM start flowing on device 0. +- **Per-instance state is global**, not per-`comp_dev` (`g_tflm_cd`, `g_tflm_initialized`, per-category counters, etc.). Fine for a single detector instance; would need reworking for concurrent multi-`tflmcly` use. +- **Category count/labels are still hardcoded in a few places** beyond `TFLM_CATEGORY_DATA` (shutdown-summary format string, the KPB-trigger rule `max_idx >= 2`) — generalize before changing the category set. + +--- + +## Abstracted Audio Input Sources in Topology v2 + +The TFLM Keyword Detection and KPB pre-roll pipeline is decoupled from the physical DAI input source using the generic `dai-copier` widget: + +```conf +Object.Widget.dai-copier.1 { + dai_type $CAPTURE_DAI_TYPE # "HDA", "DMIC", "SSP", or "ALH" (SoundWire) + copier_type $CAPTURE_COPIER_TYPE # "HDA", "DMIC", "SSP", or "ALH" + stream_name $CAPTURE_DAI_NAME # "Analog", "DMIC01", "SSP0", "SDW0-Capture" + node_type $CAPTURE_NODE_TYPE # $HDA_LINK_INPUT_CLASS, $DMIC_LINK_INPUT_CLASS, etc. +} +``` + +Platform wrappers select the input source cleanly via configuration defines: +```conf +Define { + CAPTURE_SOURCE "hda" # Options: "hda", "dmic", "ssp", "soundwire" +} + +IncludeByKey.CAPTURE_SOURCE { + "hda" "platform/intel/capture-hda.conf" + "dmic" "platform/intel/capture-dmic.conf" + "ssp" "platform/intel/capture-ssp.conf" + "soundwire" "platform/intel/capture-sdw.conf" +} +``` + +--- + +## Topology v2 Integration & Usage + +### 1. Component Widget Definition (`include/components/tflm.conf`) + +Defines `Class.Widget."tflmcly"`: +- **UUID**: `42:c6:1d:c5:e1:a2:df:48:a4:90:e2:74:8c:b6:36:3e` (`c51dc642-a2e1-48df-a490e2748cb6363e`) +- **Type**: `effect` + +### 2. Detection Pipeline Template (`include/pipelines/cavs/host-gateway-micsel-mfcc-tflm-capture.conf`) + +Instantiates the real-time detection graph — `mfcc.1` carries a real bytes-control config default (`HDA_MIC_MFCC_PARAMS`, defaulting to `include/components/mfcc/mel40_compress.conf`), and `tflmcly.1`'s output is routed to a real host-copier rather than a terminal virtual sink: + +```conf +Object.Widget { + host-copier."1" { + type "aif_out" + node_type $HDA_HOST_INPUT_CLASS + stream_name "HDA Mic TFLM Detect" + pcm_id $index + } + src."1" { ... } + mfcc."1" { + Object.Control.bytes."1" { + name "HDA Mic MFCC bytes" + IncludeByKey.HDA_MIC_MFCC_PARAMS { + "default" "include/components/mfcc/mel40_compress.conf" + } + } + } + tflmcly."1" { scheduler_domain "DP" } +} + +Object.Base.route [ + { source tflmcly.$index.1; sink "host-copier.$index.capture" } +] +``` + +### 3. Top-Level Topology Configuration (`sof-hda-generic.conf` + `hda-mic-tflm-kpb.conf` overlay) + +Instantiates the complete HDA Mic WoV topology with dual-path KPB routing: +```conf +Object.Base.route [ + # DAI -> Gain -> KPB + { source "dai-copier.HDA.Analog.capture"; sink "gain.2.1" } + { source "gain.2.1"; sink "kpb.2.1" } + + # KPB Pin 1 -> Real-time Detection Path (DP Domain) + { source "kpb.2.1"; sink "src.1.1" } + + # KPB Pin 2 -> Host WoV Draining Path + { source "kpb.2.1"; sink "host-copier.0.capture" } +] +``` + +A "HDA Mic TFLM Detect" PCM entry (`$HDA_TFLM_DETECT_PIPELINE_ID`, mono S32_LE @16kHz) exposes pipe 1's host-copier as PCM device 1. + +--- + +## Build Instructions + +### HiFi4/HiFi5 targets (MTL, PTL, ARL, ...) — LLEXT module, Clang + +```bash +source .venv/bin/activate +export LLVM_TOOLCHAIN_PATH= +west build -b intel_adsp/ app -d build--tflm -- \ + -DCONFIG_COMP_TENSORFLOW=m +``` + +### HiFi3 targets with no LLEXT support (TGL/cavs2.5) — static link, GCC + +Requires `CONFIG_COMP_TENSORFLOW=y` (not `m`) plus C++17 and enough stack/heap for the interpreter's arena, TFLM's own allocations, and `avcodec`-style blocking calls off the DP task — all already added to `app/boards/intel_adsp_cavs25.conf` on this branch: + +```conf +CONFIG_SOF_STAGING=y +CONFIG_CPP=y +CONFIG_STD_CPP17=y +CONFIG_COMP_TENSORFLOW=y +CONFIG_STACK_SIZE_EDF=32768 +CONFIG_HEAP_MEM_POOL_SIZE=32768 +CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=32768 +``` + +Build with the Zephyr SDK GCC toolchain (no Clang/LLVM involved): + +```bash +source .venv/bin/activate +export ZEPHYR_SDK_INSTALL_DIR=/home/lrg/zephyr-sdk-1.0.1 +export ZEPHYR_TOOLCHAIN_VARIANT=zephyr +unset LLVM_TOOLCHAIN_PATH +west build -b intel_adsp/cavs25 app -d build-tgl-tflm-gcc +``` + +`src/audio/tensorflow/CMakeLists.txt` auto-detects that this target has no `nnlib-hifi4` support (`CONFIG_XTENSA_HIFI4` unset, even though the compiler may still be Clang for other targets) and skips building/linking `nn_hifi_lib`, falling back to plain-C TFLM reference kernels. It also extracts just the `abs()`/libm archive members TFLM needs from the toolchain's `libc.a` into a small private `tflm_libc_shim`, since `CONFIG_MINIMAL_LIBC` lacks them and linking the whole `libc.a` collides with Zephyr's own `malloc`/`free`. + +### Building topology targets + +From `sof/tools/build_tools`: + +```bash +# Build HDA TFLM KPB Topologies (MTL / PTL / TGL) +ninja topology2_prod_sof-mtl-hda-tflm-kpb +ninja topology2_prod_sof-ptl-hda-tflm-kpb +ninja topology2_prod_sof-tgl-hda-tflm-kpb + +# Build SoundWire TFLM Topology (ARL-S) +ninja topology2_dev_sof-arl-cs42l43-l0-cs35l56-l23-mfcc-mel-normal +``` + +--- + +## Usage + +### PCM device map (`sof-hda-tflm` topology) + +| Device | Widget | Role | +|---|---|---| +| `hw:0,0` | `host-copier.0.capture` | Real WoV output: KPB pin 2's pre-roll drain target. Reading this exercises the full `dai-copier -> gain -> kpb -> host` chain and is the right place to capture the actual detected/drained audio. | +| `hw:0,1` | `host-copier.1.capture` ("HDA Mic TFLM Detect") | Arms/observes the detection pipeline only — see the [pipeline diagram note](#dual-path-wake-on-voice-wov-architecture). Not a continuous PCM stream; see below. | + +### Sanity-checking the WoV drain path end-to-end + +```bash +ssh root@ 'arecord -D hw:0,0 -f S32_LE -r 48000 -c 2 -d 4 /tmp/sanity.wav' +``` + +A clean, error-free capture here confirms the physical DAI, gain, and KPB chain are all healthy independent of TFLM/MFCC. + +### Arming the detection pipeline and watching inferences + +`hw:0,1` is not meant to be read continuously: `tflmcly` (not the host) is the real consumer on this pipe, so `arecord`'s first failed `read()` tears the pipeline straight back down. To arm it and hold it running for observation, open the PCM directly via `libasound` without ever reading from it — e.g. a small ctypes/C snippet calling `snd_pcm_open()` + `snd_pcm_set_params()` + `snd_pcm_start()` on `hw:0,1` and then just sleeping. In parallel, tail `mtrace` on the DUT: + +```bash +ssh root@ '/usr/local/bin/mtrace-reader.py' > /tmp/mtrace.log & +# ... arm hw:0,1 and play/speak keywords into the mic ... +# look for periodic "[TFLM PREPARE]", "[DBG hop]", "[DBG raw_output]" lines +``` + +On a high-confidence detection, `KPB_EVENT_BEGIN_DRAINING` fires and pre-roll history starts flowing via `host-copier.0.capture` — i.e. it shows up on `hw:0,0`, not `hw:0,1`. + +### Stream shutdown summary + +Upon stream reset or module destruction (`tflm_reset()` / `tflm_free()`), TFLM emits a summary to `printk`/`mtrace`: + +```text +[TFLM STREAM SHUTDOWN SUMMARY] Total Inferences=142 | Keyword Events: Silence=120, Unknown=18, Yes=3, No=1 | Total KPB Triggers=4 ``` -## Configuration and Scripts +- `Total Inferences`: cumulative classification inferences completed during the stream session. +- `Keyword Events`: per-category classification breakdown. +- `Total KPB Triggers`: high-confidence detections that triggered `KPB_EVENT_BEGIN_DRAINING`. + +--- + +## Training a Custom Keyword Model with Piper-TTS + +The stock model only recognizes `yes`/`no` (plus `silence`/`unknown`). The +shipped `sof_tflm_quantized_model_data.{cc,h}` was retrained end-to-end +against real SOF mel40 features (currently against the `hey_linux` +keyword) using the scripts under [./tune/](./tune/). This section +documents that exact recipe so the model can be reproduced, a different +keyword substituted, or several keywords combined into one model. + +### Pipeline overview + +The training flow is orchestrated by +[sof_tflm_train_pipeline.sh](./tune/sof_tflm_train_pipeline.sh), which +chains four steps into one command: + +| Step | Script | What it does | +|------|--------|-------------| +| 0a | [sof_tflm_generate_keyword_dataset.sh](./tune/sof_tflm_generate_keyword_dataset.sh) | Synthesize `