Merge "Optimize dot product in audio MFCC pre-processing via RVV"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f932680..ce382f2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,7 +12,6 @@
   ${CMAKE_CURRENT_LIST_DIR}/cmake/
 )
 
-set(BUILD_INTERNAL_MODELS OFF CACHE BOOL "Build applications from internal models (default: OFF)")
 set(IREE_SOURCE_DIR "$ENV{ROOTDIR}/toolchain/iree" CACHE PATH
     "IREE source code path. (default: $ENV{ROOTDIR}/toolchain/iree)")
 
@@ -29,6 +28,7 @@
 set(SPRINGBOK_LINKER_SCRIPT "$ENV{ROOTDIR}/sw/vec/springbok/springbok.ld" CACHE PATH "Springbok linker script path (default: springbok.ld)")
 set(BUILD_WITH_SPRINGBOK ON CACHE BOOL "Build the target with springbok BSP (default: ON)")
 set(BUILD_ISP_WITH_RVV ON CACHE BOOL "Build the ISP pipeline with RVV (default: ON)")
+set(BUILD_MFCC_WITH_RVV OFF CACHE BOOL "Build audio preprocessing with RVV (default: OFF)")
 
 #-------------------------------------------------------------------------------
 # IREE-specific settings
diff --git a/samples/audio_prep/CMakeLists.txt b/samples/audio_prep/CMakeLists.txt
index 6f3467b..c8085de 100644
--- a/samples/audio_prep/CMakeLists.txt
+++ b/samples/audio_prep/CMakeLists.txt
@@ -20,6 +20,10 @@
     ::util
 )
 
+if (${BUILD_MFCC_WITH_RVV})
+  target_compile_definitions(samples_audio_prep_mfcc PUBLIC MFCC_WITH_RVV)
+endif()
+
 springbok_test(
   NAME
     mfcc_test
diff --git a/samples/audio_prep/mfcc.c b/samples/audio_prep/mfcc.c
index bc01d8b..b0f6334 100644
--- a/samples/audio_prep/mfcc.c
+++ b/samples/audio_prep/mfcc.c
@@ -24,6 +24,33 @@
 #include "samples/audio_prep/mfcc.h"
 #include "samples/audio_prep/util.h"
 
+#ifdef MFCC_WITH_RVV
+#include <riscv_vector.h>
+
+static const uint8_t kWeightsFracBits = 8;
+static const uint8_t kSpectraFracBits = 7;
+
+// Calculate the dot product of two int vectors using RVV
+static uint32_t dot_product_rvv(uint32_t* u, uint32_t* w, int n) {
+  size_t vl;
+  // auxiliary variables
+  vuint32m8_t vx;
+  vuint32m8_t vu, vw;
+  vuint32m1_t v_sum;
+  uint32_t sum = 0;
+  for (size_t i = 0; i < n; i += vl) {
+    vl = vsetvl_e32m8(n - i);
+    vu = vle32_v_u32m8(u + i, vl);          // load
+    vw = vle32_v_u32m8(w + i, vl);          // load
+    vx = vmul(vu, vw, vl);                  // multiply
+    v_sum = vmv_s(v_sum, 0, vl);            // init
+    v_sum = vredsum(v_sum, vx, v_sum, vl);  // sum
+    sum += vmv_x(v_sum);
+  }
+  return sum;
+}
+#endif
+
 // config struct
 typedef struct {
   MfccParams params;
@@ -77,7 +104,11 @@
 
 // Calculate short-time Fourier transform magnitude for one frame
 // output shape: num_spectra_bins
+#ifdef MFCC_WITH_RVV
+static void stft_magnitude(float* in, float* window, uint32_t* out) {
+#else
 static void stft_magnitude(float* in, float* window, float* out) {
+#endif
   float* frame = (float*)malloc(config.fft_len * sizeof(float));
   memset(frame, 0, config.fft_len * sizeof(float));
   memcpy(frame, in, config.win_len * sizeof(float));
@@ -91,13 +122,19 @@
   rfft(frame, config.fft_order);
 
   // compute STFT magnitude
-  out[0] = frame[0] > 0 ? frame[0] : -frame[0];
-  out[config.fft_len / 2] = frame[config.fft_len / 2] > 0
-                                ? frame[config.fft_len / 2]
-                                : -frame[config.fft_len / 2];
-  for (int j = 1; j < config.fft_len / 2; j++) {
-    out[j] = sqrtf(frame[j] * frame[j] +
+  float temp = 0.0;
+  for (int j = 0; j <= config.fft_len / 2; j++) {
+    if (j == 0 || j == config.fft_len / 2) {
+      temp = frame[j] > 0 ? frame[j] : -frame[j];
+    } else {
+      temp = sqrtf(frame[j] * frame[j] +
                    frame[config.fft_len - j] * frame[config.fft_len - j]);
+    }
+#ifdef MFCC_WITH_RVV
+    out[j] = (uint32_t)(temp * (1 << kSpectraFracBits));
+#else
+    out[j] = temp;
+#endif
   }
 
   free(frame);
@@ -105,7 +142,11 @@
 
 // Return a matrix that can post-multiply spectrogram rows to make mel
 // output shape: params.num_mel_bins * num_spectra_bins
+#ifdef MFCC_WITH_RVV
+static void spectra_to_mel_matrix(uint32_t* weights) {
+#else
 static void spectra_to_mel_matrix(float* weights) {
+#endif
   MfccParams* params = &config.params;
   float nyquist_hz = params->audio_samp_rate / 2;
   float* spectra_bins = (float*)malloc(config.num_spectra_bins * sizeof(float));
@@ -123,7 +164,7 @@
   float lower_slope = 0.0, upper_slope = 0.0;
   for (int i = 0; i < params->num_mel_bins; i++) {
     // spectrogram DC bin
-    weights[i * config.num_spectra_bins] = 0.0;
+    weights[i * config.num_spectra_bins] = 0;
 
     lower = band_edges[i];
     center = band_edges[i + 1];
@@ -133,7 +174,12 @@
       upper_slope = (upper - spectra_bins[j]) / (upper - center);
       float clamp = (lower_slope < upper_slope) ? lower_slope : upper_slope;
       clamp = (clamp < 0) ? 0 : clamp;
+#ifdef MFCC_WITH_RVV
+      weights[i * config.num_spectra_bins + j] =
+          (uint32_t)(clamp * (1 << kWeightsFracBits));
+#else
       weights[i * config.num_spectra_bins + j] = clamp;
+#endif
     }
   }
 
@@ -151,20 +197,28 @@
   float* window = (float*)malloc(config.win_len * sizeof(float));
   hanning(window);
 
-  // Compute weights
+#ifdef MFCC_WITH_RVV
+  uint32_t* weights = (uint32_t*)malloc(
+      params->num_mel_bins * config.num_spectra_bins * sizeof(uint32_t));
+  uint32_t* spectra =
+      (uint32_t*)malloc(config.num_spectra_bins * sizeof(uint32_t));
+#else
   float* weights = (float*)malloc(params->num_mel_bins *
                                   config.num_spectra_bins * sizeof(float));
+  float* spectra = (float*)malloc(config.num_spectra_bins * sizeof(float));
+#endif
+
+  // Compute weights
   spectra_to_mel_matrix(weights);
 
   float* frame = (float*)malloc(config.win_len * sizeof(float));
   memset(frame, 0, config.win_len * sizeof(float));
-  float* spectra = (float*)malloc(config.num_spectra_bins * sizeof(float));
 
   for (int i = 0; i < params->num_frames; i++) {
     // update buffer
-    for (int j = 0; j < config.win_len - config.hop_len; j++) {
-      frame[j] = frame[j + config.hop_len];
-    }
+    memmove(frame, frame + config.hop_len,
+            (config.win_len - config.hop_len) * sizeof(float));
+
     // feed in new samples
     for (int j = 0; j < config.hop_len; j++) {
       int idx = i * config.hop_len + j;
@@ -177,12 +231,19 @@
 
     // compute MFCC
     for (int j = 0; j < params->num_mel_bins; j++) {
-      float temp = dot_product(spectra, weights + j * config.num_spectra_bins,
-                               config.num_spectra_bins);
-      if (temp < params->log_floor) temp = params->log_floor;
-      temp = params->log_scaler * logf(temp);
-      temp = temp < 0.0 ? 0.0 : (temp > 255.0 ? 255.0 : temp);
-      out[i * params->num_mel_bins + j] = (uint8_t)temp;
+#ifdef MFCC_WITH_RVV
+      uint32_t temp =
+          dot_product_rvv(spectra, weights + j * config.num_spectra_bins,
+                          config.num_spectra_bins);
+      float tempf = (float)temp / (1 << (kSpectraFracBits + kWeightsFracBits));
+#else
+      float tempf = dot_product(spectra, weights + j * config.num_spectra_bins,
+                                config.num_spectra_bins);
+#endif
+      if (tempf < params->log_floor) tempf = params->log_floor;
+      tempf = params->log_scaler * logf(tempf);
+      tempf = tempf < 0.0 ? 0.0 : (tempf > 255.0 ? 255.0 : tempf);
+      out[i * params->num_mel_bins + j] = (uint8_t)tempf;
     }
   }
 
diff --git a/samples/audio_prep/mfcc_test.cc b/samples/audio_prep/mfcc_test.cc
index b801649..f39f68e 100644
--- a/samples/audio_prep/mfcc_test.cc
+++ b/samples/audio_prep/mfcc_test.cc
@@ -39,8 +39,14 @@
   // extract MFCC
   extract_mfcc(golden_input, out, sizeof(golden_input) / sizeof(int16_t));
 
+  int kTolerance = 0;
+#ifdef MFCC_WITH_RVV
+  kTolerance = 1;
+#endif
   for (int i = 0; i < out_len; i++) {
-    ASSERT_EQ(out[i], golden_output[i]);
+    int diff = out[i] - golden_output[i];
+    if (diff < 0) diff = -diff;
+    ASSERT_LE(diff, kTolerance);
   }
   free(out);
 }