diff --git a/tests/lib_aec/aec_unit_tests_new/CMakeLists.txt b/tests/lib_aec/aec_unit_tests_new/CMakeLists.txt new file mode 100644 index 00000000..893f2fca --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/CMakeLists.txt @@ -0,0 +1,59 @@ + +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) +project(aec_unit_tests_new) + +set(APP_HW_TARGET XK-EVK-XU316) +set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../../) +set(CONFIG_XSCOPE_PATH ${XMOS_SANDBOX_DIR}/lib_voice/tests/shared/file_utils) + +file(GLOB CONFIG_XSCOPE_REL_PATH + RELATIVE ${CMAKE_CURRENT_LIST_DIR} + CONFIGURE_DEPENDS + ${CONFIG_XSCOPE_PATH}/config.xscope) + +file(GLOB APP_C_SRCS + RELATIVE ${CMAKE_CURRENT_LIST_DIR} + CONFIGURE_DEPENDS + ${XMOS_SANDBOX_DIR}/lib_voice/tests/lib_vnr/vnr_unit_tests/src/main.c + ${XMOS_SANDBOX_DIR}/lib_voice/tests/shared/file_utils/src/*.c + ${CMAKE_CURRENT_LIST_DIR}/src/*.c) + +file(GLOB APP_XC_SRCS + RELATIVE ${CMAKE_CURRENT_LIST_DIR} + CONFIGURE_DEPENDS + ${XMOS_SANDBOX_DIR}/lib_voice/tests/lib_vnr/vnr_unit_tests/src/main.xc) + +set(APP_INCLUDES + ${CMAKE_CURRENT_LIST_DIR}/src + ${CMAKE_CURRENT_LIST_DIR}/../../shared/file_utils/src) + +file(GLOB_RECURSE tests RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS src/test*.c) + +foreach(test_file ${tests}) + get_filename_component(test_name ${test_file} NAME_WE) + set(SOURCE_FILES_${test_name} ${test_file}) + if (NOT BUILD_NATIVE) + set(APP_DEPENDENT_MODULES "lib_voice" + "xscope_fileio") + set(APP_COMPILER_FLAGS_${test_name} + -report + -Os + -g + -Wno-xcore-fptrgroup + -DSPEEDUP_FACTOR=${TEST_SPEEDUP_FACTOR} + -DTEST_WAV_XSCOPE=1) + + set(APP_XSCOPE_SRCS ${CONFIG_XSCOPE_REL_PATH}) + else() + set(APP_DEPENDENT_MODULES "lib_voice") + + set(APP_COMPILER_FLAGS_${test_name} + -Os + -DBUILD_NATIVE=1 + -DX86_BUILD=1) + endif() +endforeach() + +XMOS_REGISTER_APP() + diff --git a/tests/lib_aec/aec_unit_tests_new/conftest.py b/tests/lib_aec/aec_unit_tests_new/conftest.py new file mode 100644 index 00000000..d23d33eb --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/conftest.py @@ -0,0 +1,85 @@ +# Copyright 2022-2026 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. +import pytest +from pathlib import Path +import py_voice.modules.aec as aec +import py_voice.config.config as pv_config +import py_voice.core.fdaf_controller as fdc +import py_voice +import numpy as np +import py_vs_c_utils as pvc +from run_dut import run_dut + +PY_VOICE_ROOT = Path(py_voice.__file__).resolve().parent +default_conf_path = PY_VOICE_ROOT / "config" / "defaults.json" +default_conf = pv_config.get_config_dict(default_conf_path) +bin_dir_path = Path(__file__).parent / "bin" + +def gen_bfp(rng, len, hr_max, exps=(-31, 31)): + data = pvc.rand_int32_arr(rng, len, hr_max) + exp = rng.integers(exps[0], exps[1] + 1, size=1, dtype=np.int32) + data_fl64 = pvc.int32_to_double(data, exp) + data_int = np.concatenate([exp, data]) + return data_int, data_fl64 + +def gen_bfps(rng, hr_max, shape, exps=(-31, 31)): + bfp_len = shape[-1] + bfp_num = 1 + data_int = np.empty(0, dtype=np.int32) + data_fl64 = np.empty(0, dtype=np.float64) + for dim in range(len(shape) - 1): + bfp_num *= shape[dim] + + for _ in range(bfp_num): + bfp_int, arr_fl64 = gen_bfp(rng, bfp_len, hr_max, exps) + data_int = np.append(data_int, bfp_int) + data_fl64 = np.append(data_fl64, arr_fl64) + + return data_int, data_fl64 + +def set_aec_conf(y_ch, x_ch, main_ph, shadow_ph): + test_conf = default_conf + test_conf["general"]["modules"] = ["aec"] + test_conf["general"]["input_channel_count"] = y_ch + x_ch + test_conf["general"]["output_channel_count"] = y_ch + test_conf["aec"]["input_channel_count"] = y_ch + x_ch + test_conf["aec"]["output_channel_count"] = y_ch + test_conf["aec"]["y_channel_count"] = y_ch + test_conf["aec"]["x_channel_count"] = x_ch + test_conf["aec"]["phases"] = main_ph + test_conf["aec"]["phases_shadow"] = shadow_ph + + return test_conf + +@pytest.fixture +def aec_obj(y_ch, x_ch, main_ph, shadow_ph): + test_conf = set_aec_conf(y_ch, x_ch, main_ph, shadow_ph) + + return aec.aec(test_conf) + +@pytest.fixture +def fdaf_obj(y_ch, x_ch, main_ph, shadow_ph): + test_conf = set_aec_conf(y_ch, x_ch, main_ph, shadow_ph) + + return fdc.fdaf_controller(test_conf, "aec") + +@pytest.fixture +def dut_runner(request, target): + exe_path = bin_dir_path / request.node.originalname / f"aec_unit_tests_new_{request.node.originalname}" + + def _run_dut(input_bin): + op, _ = run_dut(input_bin, exe_path, target) + return op + + return _run_dut + +@pytest.fixture +def rng(): + return np.random.default_rng(1243) + +def pytest_generate_tests(metafunc): + if "target" in metafunc.fixturenames: + metafunc.parametrize("target", [ + 'native', + 'xs3a' + ]) diff --git a/tests/lib_aec/aec_unit_tests_new/src/test_calc_Error_and_Y_hat.c b/tests/lib_aec/aec_unit_tests_new/src/test_calc_Error_and_Y_hat.c new file mode 100644 index 00000000..1bada14b --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/src/test_calc_Error_and_Y_hat.c @@ -0,0 +1,78 @@ +// Copyright 2022-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include +#include +#include +#include "aec.h" +#include "aec_priv.h" + +#define Y_CH 1 +#define X_CH 2 +#define MAIN_PH 5 +#define SHADOW_PH 3 + +#define NUM_BINS ((AEC_PROC_FRAME_LENGTH / 2) + 1) +#define BFP_LEN (NUM_BINS * 2) +#define BFP_BIN_LEN (BFP_LEN + 1) // for exponent +#define Y_LEN (Y_CH * BFP_BIN_LEN) +#define X_LEN (X_CH * MAIN_PH * BFP_BIN_LEN) +#define H_HAT_LEN (Y_CH * X_CH * MAIN_PH * BFP_BIN_LEN) + +static aec_state_t aec_state; +void test_init() +{ + #if BUILD_NATIVE + aec_task_distribution_t tdist = aec_tdist_chans2_threads1; + #else + aec_task_distribution_t tdist = aec_tdist_chans2_threads2; + #endif + + aec_init(&aec_state, Y_CH, X_CH, MAIN_PH, SHADOW_PH, &tdist); +} + +void test(int32_t *output, int32_t *input) +{ + aec_filter_state_t *state_ptr = &aec_state.main_state; + for(unsigned ch = 0; ch < Y_CH; ch++){ + unsigned offset = ch * BFP_BIN_LEN; + bfp_complex_s32_init(&state_ptr->shared_state->Y[ch], (complex_s32_t *)&input[offset + 1], input[offset], NUM_BINS, 1); + } + + for(unsigned ch = 0; ch < X_CH; ch++) { + for(unsigned ph = 0; ph < MAIN_PH; ph++) { + unsigned offset = (ch * MAIN_PH + ph) * BFP_BIN_LEN + Y_LEN; + bfp_complex_s32_init(&state_ptr->shared_state->X_fifo[ch][ph], (complex_s32_t *)&input[offset + 1], input[offset], NUM_BINS, 1); + } + } + //aec init only initialises the 2d Xfifo. Since we're using the 1d fifo for error computation, call aec_update_X_fifo_1d() + //to update the 1d Fifo + aec_update_X_fifo_1d(state_ptr); + + for(unsigned ch = 0; ch < Y_CH; ch++) { + for(unsigned ph = 0; ph < (X_CH * MAIN_PH); ph++) { + unsigned offset = (ch * (X_CH * MAIN_PH) + ph) * BFP_BIN_LEN + Y_LEN + X_LEN; + bfp_complex_s32_init(&state_ptr->H_hat[ch][ph], (complex_s32_t *)&input[offset + 1], input[offset], NUM_BINS, 1); + } + } + + for(unsigned ch = 0; ch < Y_CH; ch++) { + // Y_hat is accumulated via bfp_complex_s32_macc() inside aec_l2_calc_Error_and_Y_hat(). + // The production pipeline clears Y_hat each frame in aec_process_frame(); do the same here + // to keep frames independent and prevent drift. + state_ptr->Y_hat[ch].exp = AEC_ZEROVAL_EXP; + state_ptr->Y_hat[ch].hr = AEC_ZEROVAL_HR; + memset(&state_ptr->Y_hat[ch].data[0], 0, NUM_BINS * sizeof(complex_s32_t)); + + aec_calc_Error_and_Y_hat(state_ptr, ch); + + unsigned offset = ch * BFP_BIN_LEN * 2; + unsigned offset2 = offset + BFP_BIN_LEN; + + memcpy(&output[offset], &state_ptr->Y_hat[ch].exp, sizeof(int32_t)); + memcpy(&output[offset + 1], state_ptr->Y_hat[ch].data, BFP_LEN * sizeof(int32_t)); + memcpy(&output[offset2], &state_ptr->Error[ch].exp, sizeof(int32_t)); + memcpy(&output[offset2 + 1], state_ptr->Error[ch].data, BFP_LEN * sizeof(int32_t)); + } +} diff --git a/tests/lib_aec/aec_unit_tests_new/src/test_calc_coherence.c b/tests/lib_aec/aec_unit_tests_new/src/test_calc_coherence.c new file mode 100644 index 00000000..aa07980d --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/src/test_calc_coherence.c @@ -0,0 +1,40 @@ +// Copyright 2022-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include +#include +#include +#include "aec.h" +#include "aec_priv.h" + +static aec_state_t aec_state; +void test_init() +{ + #if BUILD_NATIVE + aec_task_distribution_t tdist = aec_tdist_chans2_threads1; + #else + aec_task_distribution_t tdist = aec_tdist_chans2_threads2; + #endif + + aec_init(&aec_state, 1, 1, 9, 0, &tdist); + aec_state.main_state.shared_state->ref_active_flag = 1; +} + +void test(int32_t *output, int32_t *input) +{ + // test_calc_coherence is interested in y_hat[240:480] and prev_y[0:240] + aec_state.main_state.shared_state->prev_y[0].data = &input[1]; + aec_state.main_state.shared_state->prev_y[0].exp = input[0]; + + bfp_s32_init(&aec_state.main_state.y_hat[0], (int32_t*)&aec_state.main_state.Y_hat[0].data[0], 0, AEC_PROC_FRAME_LENGTH, 0); + memcpy(&aec_state.main_state.y_hat[0].data[AEC_FRAME_ADVANCE], &input[AEC_FRAME_ADVANCE + 1 + 1], AEC_FRAME_ADVANCE * sizeof(int32_t)); + aec_state.main_state.y_hat[0].exp = input[AEC_FRAME_ADVANCE + 1]; + + aec_calc_coherence(&aec_state.main_state, 0); + + coherence_mu_params_t *coh_mu_state_ptr = &aec_state.main_state.shared_state->coh_mu_state[0]; + + memcpy(output, &coh_mu_state_ptr->coh, sizeof(float_s32_t)); + memcpy((int8_t *)output + sizeof(float_s32_t), &coh_mu_state_ptr->coh_slow, sizeof(float_s32_t)); +} diff --git a/tests/lib_aec/aec_unit_tests_new/src/test_calc_corr_factor.c b/tests/lib_aec/aec_unit_tests_new/src/test_calc_corr_factor.c new file mode 100644 index 00000000..a5f84a18 --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/src/test_calc_corr_factor.c @@ -0,0 +1,38 @@ +// Copyright 2022-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include +#include +#include +#include "aec.h" +#include "aec_priv.h" + +#define TEST_FRAME_LEN (AEC_FRAME_ADVANCE - 32) + +static aec_state_t aec_state; +void test_init() +{ + #if BUILD_NATIVE + aec_task_distribution_t tdist = aec_tdist_chans2_threads1; + #else + aec_task_distribution_t tdist = aec_tdist_chans2_threads2; + #endif + + aec_init(&aec_state, 1, 1, 10, 0, &tdist); +} + +void test(int32_t *output, int32_t *input) +{ + // aec_calc_corr_factor uses y_hat[240:480-32] and prev_y[0:240-32] + aec_state.main_state.shared_state->prev_y[0].data = &input[1]; + aec_state.main_state.shared_state->prev_y[0].exp = input[0]; + + bfp_s32_init(&aec_state.main_state.y_hat[0], (int32_t*)&aec_state.main_state.Y_hat[0].data[0], 0, AEC_PROC_FRAME_LENGTH, 0); + memcpy(&aec_state.main_state.y_hat[0].data[AEC_FRAME_ADVANCE], &input[TEST_FRAME_LEN + 1 + 1], TEST_FRAME_LEN * sizeof(int32_t)); + aec_state.main_state.y_hat[0].exp = input[AEC_PROC_FRAME_LENGTH + 1]; + + float_s32_t corr = aec_calc_corr_factor(&aec_state.main_state, 0); + + memcpy(output, &corr, sizeof(float_s32_t)); +} diff --git a/tests/lib_aec/aec_unit_tests_new/src/test_calc_freq_domain_energy.c b/tests/lib_aec/aec_unit_tests_new/src/test_calc_freq_domain_energy.c new file mode 100644 index 00000000..c6a3ead5 --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/src/test_calc_freq_domain_energy.c @@ -0,0 +1,34 @@ +// Copyright 2022-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include +#include +#include +#include "aec.h" +#include "aec_priv.h" + +#define TEST_FRAME_LEN (AEC_PROC_FRAME_LENGTH / 2 + 1) + +static aec_state_t aec_state; +void test_init() +{ + #if BUILD_NATIVE + aec_task_distribution_t tdist = aec_tdist_chans2_threads1; + #else + aec_task_distribution_t tdist = aec_tdist_chans2_threads2; + #endif + + aec_init(&aec_state, 1, 1, 10, 0, &tdist); +} + +void test(int32_t *output, int32_t *input) +{ + bfp_complex_s32_t data; + float_s32_t energy; + bfp_complex_s32_init(&data, (int32_t *)&input[1], input[0], TEST_FRAME_LEN, 1); + + aec_calc_freq_domain_energy(&energy, &data); + + memcpy(output, &energy, sizeof(float_s32_t)); +} diff --git a/tests/lib_aec/aec_unit_tests_new/src/test_calc_inv_X_energy.c b/tests/lib_aec/aec_unit_tests_new/src/test_calc_inv_X_energy.c new file mode 100644 index 00000000..a3f73fb8 --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/src/test_calc_inv_X_energy.c @@ -0,0 +1,54 @@ +// Copyright 2022-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include +#include +#include +#include "aec.h" +#include "aec_priv.h" + +#define Y_CH 1 +#define X_CH 2 +#define MAIN_PH 6 +#define SHADOW_PH 2 + +#define NUM_BINS ((AEC_PROC_FRAME_LENGTH / 2) + 1) +#define BFP_BIN_LEN (NUM_BINS + 1) // for exponent + +static aec_state_t aec_state; +void test_init() +{ + #if BUILD_NATIVE + aec_task_distribution_t tdist = aec_tdist_chans2_threads1; + #else + aec_task_distribution_t tdist = aec_tdist_chans2_threads2; + #endif + + aec_init(&aec_state, Y_CH, X_CH, MAIN_PH, SHADOW_PH, &tdist); +} + +void test(int32_t *output, int32_t *input) +{ + int32_t is_shadow = input[0]; + float_s32_t delta = {input[1], input[2]}; + + aec_filter_state_t *state_ptr = (is_shadow) ? &aec_state.shadow_state : &aec_state.main_state; + + state_ptr->delta = delta; + for(unsigned ch = 0; ch < X_CH; ch++) { + unsigned offset = 3 + ch * BFP_BIN_LEN; + // unsigned offset2 = 3 + int32_t scratch[NUM_BINS] = {0}; + bfp_s32_init(&state_ptr->shared_state->sigma_XX[ch], &input[offset + 1], input[offset], NUM_BINS, 1); + offset += BFP_BIN_LEN * 2; + bfp_s32_init(&state_ptr->X_energy[ch], &input[offset + 1], input[offset], NUM_BINS, 1); + bfp_s32_init(&state_ptr->inv_X_energy[ch], scratch, 0, NUM_BINS, 0); + + aec_calc_normalisation_spectrum(state_ptr, ch, is_shadow); + + unsigned out_offset = ch * BFP_BIN_LEN; + memcpy(&output[out_offset], &state_ptr->inv_X_energy[ch].exp, sizeof(int32_t)); + memcpy(&output[out_offset + 1], state_ptr->inv_X_energy[ch].data, NUM_BINS * sizeof(int32_t)); + } +} diff --git a/tests/lib_aec/aec_unit_tests_new/src/test_calc_max_ref_energy.c b/tests/lib_aec/aec_unit_tests_new/src/test_calc_max_ref_energy.c new file mode 100644 index 00000000..2efa1686 --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/src/test_calc_max_ref_energy.c @@ -0,0 +1,20 @@ +// Copyright 2022-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include + +#include "aec.h" + +#define NUM_CHANNELS (4) + +void test_init() {} + +void test(int32_t *output, int32_t *input) +{ + // const int32_t (*input_data)[AEC_FRAME_ADVANCE] = (const int32_t (*)[AEC_FRAME_ADVANCE]) &input[0]; + + // float_s32_t max_energy = aec_calc_max_input_energy(input_data, NUM_CHANNELS); + float_s32_t max_energy = aec_calc_max_input_energy(input, NUM_CHANNELS); + memcpy(output, &max_energy, sizeof(float_s32_t)); +} diff --git a/tests/lib_aec/aec_unit_tests_new/src/test_create_output.c b/tests/lib_aec/aec_unit_tests_new/src/test_create_output.c new file mode 100644 index 00000000..03ae0ba7 --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/src/test_create_output.c @@ -0,0 +1,61 @@ +// Copyright 2022-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include + +#include "aec.h" +#include "aec_priv.h" + +#define Y_CH (1) +#define X_CH (1) +#define MAIN_PH (9) +#define SHADOW_PH (9) + +static aec_state_t aec_state; + +void test_init() +{ +#if BUILD_NATIVE + aec_task_distribution_t tdist = aec_tdist_chans2_threads1; +#else + aec_task_distribution_t tdist = aec_tdist_chans2_threads2; +#endif + + aec_init(&aec_state, Y_CH, X_CH, MAIN_PH, SHADOW_PH, &tdist); + + // aec_state.main_state.error is normally initialised during the Error->error IFFT call. + // Initialise here for standalone testing, reusing Error complex buffer memory. + bfp_s32_init(&aec_state.main_state.error[0], (int32_t*)&aec_state.main_state.Error[0].data[0], 0, AEC_PROC_FRAME_LENGTH, 0); +} + +void test(int32_t *output, int32_t *input) +{ + // Input layout per frame: + // [error_exp, error_data[0:PROC_FRAME_LEN-1]] + const int32_t error_exp = input[0]; + const int32_t *error_data = &input[1]; + + bfp_s32_t *err = &aec_state.main_state.error[0]; + err->exp = error_exp; + memcpy(err->data, error_data, AEC_PROC_FRAME_LENGTH * sizeof(int32_t)); + err->hr = bfp_s32_headroom(err); + + int32_t out_frame[1][AEC_FRAME_ADVANCE]; + aec_calc_output(&aec_state.main_state, out_frame, 0); + + // Output layout per frame: + // [output_q31[0:AEC_FRAME_ADVANCE-1], overlap_exp, overlap_data[0:31], error_exp, error_data[0:PROC_FRAME_LEN-1]] + unsigned o = 0; + + memcpy(&output[o], &out_frame[0][0], AEC_FRAME_ADVANCE * sizeof(int32_t)); + o += AEC_FRAME_ADVANCE; + + bfp_s32_t *ov = &aec_state.main_state.overlap[0]; + output[o++] = ov->exp; + memcpy(&output[o], ov->data, ov->length * sizeof(int32_t)); + o += ov->length; + + output[o++] = err->exp; + memcpy(&output[o], err->data, err->length * sizeof(int32_t)); +} diff --git a/tests/lib_aec/aec_unit_tests_new/test_calc_Error_and_Y_hat.py b/tests/lib_aec/aec_unit_tests_new/test_calc_Error_and_Y_hat.py new file mode 100644 index 00000000..bc12bc1c --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/test_calc_Error_and_Y_hat.py @@ -0,0 +1,69 @@ + +import pytest +import numpy as np +import py_vs_c_utils as pvc +from conftest import gen_bfps + +def float64_to_comp128_reshape(data, shape): + data = data.view(np.complex128) + data = data.reshape(shape) + return data + +@pytest.mark.parametrize("y_ch, x_ch, main_ph, shadow_ph", [[1, 2, 5, 3]]) +def test_calc_Error_and_Y_hat(fdaf_obj, y_ch, x_ch, main_ph, rng, dut_runner): + f_bin_count = fdaf_obj.f_bin_count + + # all complex and need an exponent (per BFP) + bfp_size = f_bin_count * 2 + 1 # exponent + complex data (real + imag for each bin) + Y_len = y_ch * bfp_size + H_hat_len = y_ch * x_ch * main_ph * bfp_size + X_len = x_ch * main_ph * bfp_size + Error_len = Y_len + Y_hat_len = Y_len + + out_len = Error_len + Y_hat_len + in_len = Y_len + H_hat_len + X_len + input_data = np.array([in_len, out_len], dtype=np.int32) + + test_frames = 1<<10 + ref_Error = np.empty(0, dtype=np.float64) + ref_Y_hat = np.empty(0, dtype=np.float64) + + for _ in range(test_frames): + # Keep exponent ranges small so the reference doesn't hit huge |X*H| products + # that make Error ~= -Y_hat due to |Y_hat| >> |Y|. + Y, Y_fl = gen_bfps(rng, 3, (y_ch, f_bin_count * 2), (-30, -24)) + input_data = np.append(input_data, Y) + Y_fl = float64_to_comp128_reshape(Y_fl, (y_ch, f_bin_count)) + + X, X_fl = gen_bfps(rng, 3, (x_ch, main_ph, f_bin_count * 2), (-30, -24)) + input_data = np.append(input_data, X) + X_fl = float64_to_comp128_reshape(X_fl, (x_ch, main_ph, f_bin_count)) + + # H_hat has 3 dimentions in C and 4 in python + H_hat, H_hat_fl = gen_bfps(rng, 3, (y_ch, x_ch * main_ph, f_bin_count * 2), (-30, -24)) + input_data = np.append(input_data, H_hat) + H_hat_fl = float64_to_comp128_reshape(H_hat_fl, (y_ch, x_ch, main_ph, f_bin_count)) + + # Set state on the filter where calc_Error_and_Y_hat will access it + fdaf_obj.main_filter.X_data[:] = X_fl + fdaf_obj.main_filter.H[:] = H_hat_fl + fdaf_obj.main_filter.calc_Error_and_Y_hat(Y_fl) + + ref_Y_hat = np.append(ref_Y_hat, fdaf_obj.main_filter.Y_hat.view(np.float64)) + ref_Error = np.append(ref_Error, fdaf_obj.main_filter.Error.view(np.float64)) + + op = dut_runner(input_data) + + # Both Y_hat and Error have [y_ch][f_bin_count] dimentions and are complex + sections = np.cumsum(np.tile([bfp_size, bfp_size], test_frames * y_ch))[:-1].astype(np.int32) + op_split = np.split(op, sections) + + dut_Y_hat = np.concatenate(op_split[0::2]) + dut_Y_hat = pvc.bfp_s32_arr_to_double(dut_Y_hat, f_bin_count * 2, test_frames) + dut_Error = np.concatenate(op_split[1::2]) + dut_Error = pvc.bfp_s32_arr_to_double(dut_Error, f_bin_count * 2, test_frames) + + np.testing.assert_allclose(ref_Y_hat, dut_Y_hat, rtol=0, atol=2e-4) + np.testing.assert_allclose(ref_Error, dut_Error, rtol=0, atol=2e-4) + diff --git a/tests/lib_aec/aec_unit_tests_new/test_calc_coherence.py b/tests/lib_aec/aec_unit_tests_new/test_calc_coherence.py new file mode 100644 index 00000000..b227270e --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/test_calc_coherence.py @@ -0,0 +1,58 @@ + +import pytest +import numpy as np +import py_vs_c_utils as pvc + + +@pytest.mark.parametrize("y_ch, x_ch, main_ph, shadow_ph", [[1, 1, 9, 0]]) +def test_calc_coherence(aec_obj, rng, dut_runner): + frame_advance = aec_obj.frame_advance + + y_len = frame_advance + 1 # for exp + y_hat_len = y_len + + out_len = 2 * 2 # 2 x float_s32_t + in_len = y_len + y_hat_len + input_data = np.array([in_len, out_len], dtype=np.int32) + + test_frames = 1<<10 + ref_coh = np.empty(0, dtype=np.float64) + ref_coh_slow = np.empty(0, dtype=np.float64) + + for _ in range(test_frames): + y = pvc.rand_int32_arr(rng, frame_advance, 4) + y_exp = rng.integers(-31, 32, size=1, dtype=np.int32) + + input_data = np.append(input_data, y_exp) + input_data = np.append(input_data, y) + + y_hat = pvc.rand_int32_arr(rng, frame_advance, 4) + y_hat_exp = rng.integers(-31, 32, size=1, dtype=np.int32) + + input_data = np.append(input_data, y_hat_exp) + input_data = np.append(input_data, y_hat) + + y_fl = pvc.int32_to_double(y, y_exp) + y_hat_fl = pvc.int32_to_double(y_hat, y_hat_exp) + + start_i = frame_advance + stop_i = frame_advance * 2 + aec_obj.y_data[0][start_i:stop_i] = y_fl + aec_obj.y_hat[0][start_i:stop_i] = y_hat_fl + aec_obj.calc_coherence() + + ref_coh = np.append(ref_coh, aec_obj.coh) + ref_coh_slow = np.append(ref_coh_slow, aec_obj.coh_slow) + + op = dut_runner(input_data) + + sections = np.cumsum(np.tile([2, 2], test_frames))[:-1].astype(np.int32) + op_split = np.split(op, sections) + + dut_coh = np.concatenate(op_split[0::2]) + dut_coh = pvc.float_s32_arr_to_double(dut_coh) + dut_coh_slow = np.concatenate(op_split[1::2]) + dut_coh_slow = pvc.float_s32_arr_to_double(dut_coh_slow) + + np.testing.assert_allclose(ref_coh, dut_coh, rtol=0, atol=2e-1) + np.testing.assert_allclose(ref_coh_slow, dut_coh_slow, rtol=0, atol=1e-3) diff --git a/tests/lib_aec/aec_unit_tests_new/test_calc_corr_factor.py b/tests/lib_aec/aec_unit_tests_new/test_calc_corr_factor.py new file mode 100644 index 00000000..6f66b920 --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/test_calc_corr_factor.py @@ -0,0 +1,50 @@ + +import pytest +import numpy as np +import py_vs_c_utils as pvc + + +@pytest.mark.parametrize("y_ch, x_ch, main_ph, shadow_ph", [[1, 1, 10, 0]]) +def test_calc_corr_factor(aec_obj, rng, dut_runner): + frame_advance = aec_obj.frame_advance + test_fr_len = frame_advance - aec_obj.overlap_width + + y_len = test_fr_len + 1 # for exp + y_hat_len = y_len + + out_len = 2 # float_s32_t + in_len = y_len + y_hat_len + input_data = np.array([in_len, out_len], dtype=np.int32) + + test_frames = 1<<10 + ref_corr = np.empty(0, dtype=np.float64) + + for _ in range(test_frames): + y= pvc.rand_int32_arr(rng, test_fr_len, 4) + y_exp = rng.integers(-31, 32, size=1, dtype=np.int32) + + input_data = np.append(input_data, y_exp) + input_data = np.append(input_data, y) + + y_hat= pvc.rand_int32_arr(rng, test_fr_len, 4) + y_hat_exp = rng.integers(-31, 32, size=1, dtype=np.int32) + + input_data = np.append(input_data, y_hat_exp) + input_data = np.append(input_data, y_hat) + + y_fl = pvc.int32_to_double(y, y_exp) + y_hat_fl = pvc.int32_to_double(y_hat, y_hat_exp) + + start_i = frame_advance + stop_i = start_i + test_fr_len + aec_obj.y_data[0][start_i:stop_i] = y_fl + aec_obj.y_hat[0][start_i:stop_i] = y_hat_fl + ref_corr_one = aec_obj.calc_corr_factor() + + ref_corr = np.append(ref_corr, ref_corr_one) + + op = dut_runner(input_data) + + dut_corr = pvc.float_s32_arr_to_double(op) + + np.testing.assert_allclose(ref_corr, dut_corr, rtol=0, atol=1e-9) diff --git a/tests/lib_aec/aec_unit_tests_new/test_calc_freq_domain_energy.py b/tests/lib_aec/aec_unit_tests_new/test_calc_freq_domain_energy.py new file mode 100644 index 00000000..dcb668a7 --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/test_calc_freq_domain_energy.py @@ -0,0 +1,38 @@ + +import pytest +import numpy as np +import py_vs_c_utils as pvc + +@pytest.mark.parametrize("y_ch, x_ch, main_ph, shadow_ph", [[1, 1, 10, 0]]) +def test_calc_freq_domain_energy(aec_obj, rng, dut_runner): + fd_frame_len = aec_obj.f_bin_count + + data_len = fd_frame_len * 2 + 1 # for exp + + out_len = 2 # float_s32_t + in_len = data_len + input_data = np.array([in_len, out_len], dtype=np.int32) + + test_frames = 1<<10 + ref_energy = np.empty(0, dtype=np.float64) + + for _ in range(test_frames): + data = pvc.rand_int32_arr(rng, fd_frame_len * 2, 4) + data_exp = rng.integers(-31, -24, size=1, dtype=np.int32) + + input_data = np.append(input_data, data_exp) + input_data = np.append(input_data, data) + + data_fl = pvc.int32_to_double(data, data_exp) + data_fl = data_fl.view(np.complex128) + data_fl = data_fl.reshape((1, fd_frame_len)) + + ref_energy_one = aec_obj.calc_ov_energy(data_fl) + + ref_energy = np.append(ref_energy, ref_energy_one) + + op = dut_runner(input_data) + + dut_energy = pvc.float_s32_arr_to_double(op) + + np.testing.assert_allclose(ref_energy, dut_energy, rtol=0, atol=6e-4) diff --git a/tests/lib_aec/aec_unit_tests_new/test_calc_inv_X_energy.py b/tests/lib_aec/aec_unit_tests_new/test_calc_inv_X_energy.py new file mode 100644 index 00000000..d7cd4584 --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/test_calc_inv_X_energy.py @@ -0,0 +1,81 @@ + +import pytest +import numpy as np +import py_vs_c_utils as pvc +from conftest import gen_bfps + +@pytest.mark.parametrize("y_ch, x_ch, main_ph, shadow_ph", [[1, 2, 6, 2]]) +def test_calc_inv_X_energy(aec_obj, x_ch, rng, dut_runner): + f_bin_count = aec_obj.f_bin_count + + sigma_xx_len = x_ch * (f_bin_count + 1) # for exp + X_energy_len = sigma_xx_len + delta_len = 2 # float_s32_t + + inv_X_energy_len = sigma_xx_len + + in_len = sigma_xx_len + X_energy_len + delta_len + 1 # for is_shadow + out_len = inv_X_energy_len + input_data = np.array([in_len, out_len], dtype=np.int32) + + test_frames = 1<<10 + ref_inv_X_energy = np.empty(0, dtype=np.float64) + + for _ in range(test_frames): + is_shadow = rng.integers(2, size=1, dtype=np.int32) + input_data = np.append(input_data, is_shadow) + + delta_mant = pvc.rand_int32_arr(rng, size=1) + delta_exp = rng.integers(-95, -31, size=1, dtype=np.int32) + input_data = np.append(input_data, [delta_mant, delta_exp]) + delta_fl = np.ldexp(delta_mant, delta_exp) + + sigma_xx, sigma_xx_fl = gen_bfps(rng, 4, (x_ch, f_bin_count), (-31, 31)) + input_data = np.append(input_data, sigma_xx) + sigma_xx_fl = sigma_xx_fl.reshape((x_ch, f_bin_count)) + + X_energy, X_energy_fl = gen_bfps(rng, 4, (x_ch, f_bin_count), (-31, 31)) + input_data = np.append(input_data, X_energy) + X_energy_fl = X_energy_fl.reshape((x_ch, f_bin_count)) + + aec_obj.sigma_xx = sigma_xx_fl + + if not is_shadow: + aec_obj.main_filter.X_energy = X_energy_fl + aec_obj.main_filter.delta = delta_fl + inv_X_energy_one = aec_obj.main_filter.calc_inv_x_energy(aec_obj) + else: + aec_obj.shadow_filter.X_energy = X_energy_fl + aec_obj.shadow_filter.delta = delta_fl + inv_X_energy_one = aec_obj.shadow_filter.calc_inv_x_energy(aec_obj) + + ref_inv_X_energy = np.append(ref_inv_X_energy, inv_X_energy_one.real) + + # Extra test, for the case where delta is zero and X_energy is zero + # currenty handled differently in python and C, hence uncommented.. + + # test_frames += 1 + # input_data = np.append(input_data, 1) # shadow only + # input_data = np.append(input_data, [0, -1024]) # zero delta + # input_data = np.append(input_data, sigma_xx) # same sigma + # # getting 0/int32max * 2 ** exp array from X_energy + # (X_energy_exp0, X_energy_exp1) = (X_energy[0], X_energy[1 + f_bin_count]) + # X_energy = np.signbit(X_energy).astype(np.int32) * np.iinfo(np.int32).max + # X_energy[0] = X_energy_exp0 + # X_energy[1 + f_bin_count] = X_energy_exp1 + # input_data = np.append(input_data, X_energy) + + # X_energy_fl[0][:] = pvc.int32_to_double(X_energy[1:f_bin_count + 1], X_energy_exp0) + # X_energy_fl[1][:] = pvc.int32_to_double(X_energy[f_bin_count + 2:], X_energy_exp1) + # aec_obj.shadow_filter.X_energy = X_energy_fl + # # not setting the delta this time letting the API handle it + # aec_obj.shadow_filter.calc_delta() + # inv_X_energy_one = aec_obj.shadow_filter.calc_inv_x_energy(aec_obj) + + # ref_inv_X_energy = np.append(ref_inv_X_energy, inv_X_energy_one.real) + + op = dut_runner(input_data) + + dut_inv_X_energy = pvc.bfp_s32_arr_to_double(op, f_bin_count, test_frames * x_ch) + + np.testing.assert_allclose(ref_inv_X_energy, dut_inv_X_energy, rtol=1e-3, atol=1e-6) diff --git a/tests/lib_aec/aec_unit_tests_new/test_calc_max_ref_energy.py b/tests/lib_aec/aec_unit_tests_new/test_calc_max_ref_energy.py new file mode 100644 index 00000000..4ec44e66 --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/test_calc_max_ref_energy.py @@ -0,0 +1,36 @@ + +import pytest +import numpy as np +import py_vs_c_utils as pvc + + +@pytest.mark.parametrize("y_ch, x_ch, main_ph, shadow_ph", [[1, 4, 10, 0]]) +def test_calc_max_ref_energy(aec_obj, x_ch, rng, dut_runner): + frame_advance = aec_obj.frame_advance + + in_len = x_ch * frame_advance + out_len = 2 # float_s32_t + input_data = np.array([in_len, out_len], dtype=np.int32) + + test_frames = 1 << 10 + ref_energy = np.empty(0, dtype=np.float64) + + for _ in range(test_frames): + frame_int = np.empty((x_ch, frame_advance), dtype=np.int32) + for ch in range(x_ch): + frame_int[ch] = pvc.rand_int32_arr(rng, frame_advance, hr_max=12) + + input_data = np.append(input_data, frame_int.ravel()) + + far_frame = pvc.int32_to_double(frame_int, np.int32(-31)) + + # py_voice returns mean squared power; C returns sum of squares (energy). + ref_power = aec_obj.get_max_ref_power(far_frame) + ref_energy_one = ref_power * frame_advance + ref_energy = np.append(ref_energy, ref_energy_one) + + op = dut_runner(input_data) + + dut_energy = pvc.float_s32_arr_to_double(op) + + np.testing.assert_allclose(ref_energy, dut_energy, rtol=0, atol=1e-7) diff --git a/tests/lib_aec/aec_unit_tests_new/test_create_output.py b/tests/lib_aec/aec_unit_tests_new/test_create_output.py new file mode 100644 index 00000000..d96dd32f --- /dev/null +++ b/tests/lib_aec/aec_unit_tests_new/test_create_output.py @@ -0,0 +1,58 @@ + +import pytest +import numpy as np +import py_vs_c_utils as pvc + + +@pytest.mark.parametrize("y_ch, x_ch, main_ph, shadow_ph", [[1, 1, 9, 9]]) +def test_create_output(aec_obj, rng, dut_runner): + frame_advance = aec_obj.frame_advance + proc_frame_length = aec_obj.proc_frame_length + overlap_width = aec_obj.overlap_width + + in_len = 1 + proc_frame_length # for exp + out_len = frame_advance + (1 + overlap_width) + (1 + proc_frame_length) + input_data = np.array([in_len, out_len], dtype=np.int32) + + test_frames = 1 << 10 + ref_out = np.empty(0, dtype=np.float64) + ref_ov = np.empty(0, dtype=np.float64) + ref_err = np.empty(0, dtype=np.float64) + + # In this test we have to set the same exponent for all the tests as + # doing otherwise will make C to saturate (cause it has to output in q0.31) + # whereas python does not do so. + error_exp = rng.integers(-31, -27, size=1, dtype=np.int32) + + for _ in range(test_frames): + err_int = pvc.rand_int32_arr(rng, proc_frame_length, hr_max=4) + + input_data = np.append(input_data, error_exp) + input_data = np.append(input_data, err_int) + + err_fl = pvc.int32_to_double(err_int, error_exp).reshape(1, proc_frame_length) + + # Apply the same time-domain modifications as C (zero + window) before overlap-add. + # window_error mutates err_fl in-place. + aec_obj.window_error(err_fl) + # aec.created output would overwrite the error memory so coppying the array before calling the API + err_win_fl = err_fl.copy() + out_fl = aec_obj.create_output(err_fl) + + ref_out = np.append(ref_out, out_fl.ravel()) + ref_ov = np.append(ref_ov, aec_obj.overlap.ravel()) + ref_err = np.append(ref_err, err_win_fl.ravel()) + + op = dut_runner(input_data) + op = op.reshape(test_frames, out_len) + + dut_out_int = op[:, :frame_advance].ravel().astype(np.int32) + dut_out = pvc.int32_to_double(dut_out_int, np.int32(-31)) + + dut_ov = pvc.bfp_s32_arr_to_double(op[:, frame_advance: frame_advance + 1 + overlap_width].ravel(), overlap_width, test_frames) + + dut_err = pvc.bfp_s32_arr_to_double(op[:, frame_advance + 1 + overlap_width:].ravel(), proc_frame_length, test_frames) + + np.testing.assert_allclose(ref_out, dut_out, rtol=0, atol=3e-7) + np.testing.assert_allclose(ref_ov, dut_ov, rtol=0, atol=3e-7) + np.testing.assert_allclose(ref_err, dut_err, rtol=0, atol=3e-7) diff --git a/tests/lib_vnr/vnr_unit_tests/src/main.c b/tests/lib_vnr/vnr_unit_tests/src/main.c index 8ac07248..ffbbde1e 100644 --- a/tests/lib_vnr/vnr_unit_tests/src/main.c +++ b/tests/lib_vnr/vnr_unit_tests/src/main.c @@ -34,8 +34,8 @@ void test_vnr_unit(const char *input_file_name, const char *output_file_name) int num_frames = (filesize_words - 2)/input_framesize; printf("num_frames = %d\n",num_frames); - int32_t biggest_possible_input_frame[2048]; - int32_t biggest_possible_output_frame[512]; + int32_t biggest_possible_input_frame[12000]; + int32_t biggest_possible_output_frame[2048]; test_init(); for(int fr=0; fr>= hr - return data - def stft(x_data, new_x_frame, x_data_len, new_x_frame_len, nfft): x_data = np.roll(x_data, -new_x_frame_len, axis=0) x_data[x_data_len - new_x_frame_len:] = new_x_frame diff --git a/tests/lib_vnr/vnr_unit_tests/test_vnr_extract_features.py b/tests/lib_vnr/vnr_unit_tests/test_vnr_extract_features.py index 2ab184f7..4f1d03af 100644 --- a/tests/lib_vnr/vnr_unit_tests/test_vnr_extract_features.py +++ b/tests/lib_vnr/vnr_unit_tests/test_vnr_extract_features.py @@ -3,7 +3,7 @@ import numpy as np import py_voice.modules.vnr as vnr import py_vs_c_utils as pvc -from test_utils import rand_int32_arr, stft, BATCH_SIZE +from test_utils import stft, BATCH_SIZE def test_vnr_extract_features(rng, quantise, vnr_obj, dut_runner): @@ -23,7 +23,7 @@ def test_vnr_extract_features(rng, quantise, vnr_obj, dut_runner): for _ in range(test_frames): enable_highpass = rng.integers(2) - data = rand_int32_arr(rng, vnr.FRAME_ADVANCE, 8) + data = pvc.rand_int32_arr(rng, vnr.FRAME_ADVANCE, 8) input_data = np.append(input_data, data) input_data = np.append(input_data, enable_highpass) diff --git a/tests/lib_vnr/vnr_unit_tests/test_vnr_form_input_frame.py b/tests/lib_vnr/vnr_unit_tests/test_vnr_form_input_frame.py index 0e6d7dd7..893b4b70 100644 --- a/tests/lib_vnr/vnr_unit_tests/test_vnr_form_input_frame.py +++ b/tests/lib_vnr/vnr_unit_tests/test_vnr_form_input_frame.py @@ -3,7 +3,7 @@ import numpy as np import py_voice.modules.vnr as vnr import py_vs_c_utils as pvc -from test_utils import rand_int32_arr, stft +from test_utils import stft def test_vnr_form_input_frame(rng, dut_runner): input_words_per_frame = vnr.FRAME_ADVANCE #No. of int32 values sent to dut as input per frame @@ -19,7 +19,7 @@ def test_vnr_form_input_frame(rng, dut_runner): ref_output = np.empty(0, dtype=np.complex128) for _ in range(test_frames): # Generate input data - data = rand_int32_arr(rng, vnr.FRAME_ADVANCE, 8) + data = pvc.rand_int32_arr(rng, vnr.FRAME_ADVANCE, 8) input_data = np.append(input_data, data) # Ref form input frame implementation diff --git a/tests/lib_vnr/vnr_unit_tests/test_vnr_full.py b/tests/lib_vnr/vnr_unit_tests/test_vnr_full.py index 687b0d64..050c05ea 100644 --- a/tests/lib_vnr/vnr_unit_tests/test_vnr_full.py +++ b/tests/lib_vnr/vnr_unit_tests/test_vnr_full.py @@ -3,7 +3,7 @@ import numpy as np import py_voice.modules.vnr as vnr import py_vs_c_utils as pvc -from test_utils import rand_int32_arr, stft +from test_utils import stft def test_vnr_full(rng, vnr_obj, dut_runner): @@ -20,7 +20,7 @@ def test_vnr_full(rng, vnr_obj, dut_runner): for _ in range(test_frames): enable_highpass = rng.integers(2) # Generate input data - data = rand_int32_arr(rng, vnr.FRAME_ADVANCE, 8) + data = pvc.rand_int32_arr(rng, vnr.FRAME_ADVANCE, 8) input_data = np.append(input_data, data) input_data = np.append(input_data, enable_highpass) diff --git a/tests/lib_vnr/vnr_unit_tests/test_vnr_inference.py b/tests/lib_vnr/vnr_unit_tests/test_vnr_inference.py index ccb7d3ad..8ab72763 100644 --- a/tests/lib_vnr/vnr_unit_tests/test_vnr_inference.py +++ b/tests/lib_vnr/vnr_unit_tests/test_vnr_inference.py @@ -3,7 +3,7 @@ import numpy as np import py_voice.modules.vnr as vnr import py_vs_c_utils as pvc -from test_utils import rand_int32_arr, BATCH_SIZE +from test_utils import BATCH_SIZE def test_vnr_inference(rng, vnr_obj, dut_runner): @@ -15,7 +15,7 @@ def test_vnr_inference(rng, vnr_obj, dut_runner): ref_output_double = np.empty(0, dtype=np.float64) for _ in range(test_frames): - data = rand_int32_arr(rng, BATCH_SIZE, max=0) + data = pvc.rand_int32_arr(rng, BATCH_SIZE, max=0) exp = rng.integers(-31, 0) # exp input_data = np.append(input_data, exp) input_data = np.append(input_data, data) diff --git a/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_feature_quantise.py b/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_feature_quantise.py index 754d43cf..deded11a 100644 --- a/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_feature_quantise.py +++ b/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_feature_quantise.py @@ -2,7 +2,7 @@ # This Software is subject to the terms of the XMOS Public Licence: Version 1. import numpy as np import py_vs_c_utils as pvc -from test_utils import rand_int32_arr, BATCH_SIZE +from test_utils import BATCH_SIZE def test_vnr_priv_feature_quantise(rng, quantise, dut_runner): input_words_per_frame = BATCH_SIZE + 1 # 96 mantissas and 1 exponent @@ -13,7 +13,7 @@ def test_vnr_priv_feature_quantise(rng, quantise, dut_runner): ref_output = np.empty(0, dtype=np.int8) for _ in range(test_frames): # By setting high=1 we enure no value is greater than 0 since max normalised output is 0 - data = rand_int32_arr(rng, BATCH_SIZE, max=1) + data = pvc.rand_int32_arr(rng, BATCH_SIZE, max=1) exp = rng.integers(-31, 0) # exp input_data = np.append(input_data, exp) input_data = np.append(input_data, data) diff --git a/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_log2.py b/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_log2.py index 07ecb316..d28d7fb6 100644 --- a/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_log2.py +++ b/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_log2.py @@ -4,7 +4,6 @@ import numpy as np import py_voice.modules.vnr as vnr import py_vs_c_utils as pvc -from test_utils import rand_int32_arr def test_vnr_priv_log2(rng, dut_runner): @@ -21,8 +20,8 @@ def test_vnr_priv_log2(rng, dut_runner): for _ in range(test_frames): data = np.zeros(vnr.MEL_FILTERS*2, dtype=np.int32) - data[0::2] = rand_int32_arr(rng, vnr.MEL_FILTERS, 5, min=1) - data[1::2] = rand_int32_arr(rng, vnr.MEL_FILTERS, min=-32, max=16) # exp + data[0::2] = pvc.rand_int32_arr(rng, vnr.MEL_FILTERS, 5, min=1) + data[1::2] = pvc.rand_int32_arr(rng, vnr.MEL_FILTERS, min=-32, max=16) # exp data = np.array(data, dtype=np.int32) input_data = np.append(input_data, data) diff --git a/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_make_slice.py b/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_make_slice.py index 883a5974..ba655ed1 100644 --- a/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_make_slice.py +++ b/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_make_slice.py @@ -3,7 +3,7 @@ import numpy as np import py_voice.modules.vnr as vnr import py_vs_c_utils as pvc -from test_utils import rand_int32_arr, stft +from test_utils import stft def test_vnr_priv_make_slice(rng, vnr_obj, dut_runner): @@ -20,7 +20,7 @@ def test_vnr_priv_make_slice(rng, vnr_obj, dut_runner): for _ in range(test_frames): enable_highpass = rng.integers(2) # Generate input data - data = rand_int32_arr(rng, vnr.FRAME_ADVANCE, 8) + data = pvc.rand_int32_arr(rng, vnr.FRAME_ADVANCE, 8) input_data = np.append(input_data, data) input_data = np.append(input_data, enable_highpass) diff --git a/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_mel_compute.py b/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_mel_compute.py index 86da4b12..c7337f1e 100644 --- a/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_mel_compute.py +++ b/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_mel_compute.py @@ -4,7 +4,6 @@ import numpy as np import py_voice.modules.vnr as vnr import py_vs_c_utils as pvc -from test_utils import rand_int32_arr def test_vnr_priv_mel_compute(rng, vnr_obj, dut_runner): @@ -22,7 +21,7 @@ def test_vnr_priv_mel_compute(rng, vnr_obj, dut_runner): ref_output_float = np.empty(0, dtype=np.float64) for _ in range(test_frames): exp = rng.integers(-32, -8) - data = rand_int32_arr(rng, fd_frame_len*2, 5) + data = pvc.rand_int32_arr(rng, fd_frame_len*2, 5) input_data = np.append(input_data, exp) input_data = np.append(input_data, data) diff --git a/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_normalise_patch.py b/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_normalise_patch.py index 4e48175a..412cd846 100644 --- a/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_normalise_patch.py +++ b/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_normalise_patch.py @@ -3,7 +3,7 @@ import numpy as np import py_voice.modules.vnr as vnr import py_vs_c_utils as pvc -from test_utils import rand_int32_arr, BATCH_SIZE +from test_utils import BATCH_SIZE def test_vnr_priv_normalise_patch(rng, vnr_obj, dut_runner): @@ -17,7 +17,7 @@ def test_vnr_priv_normalise_patch(rng, vnr_obj, dut_runner): ref_output_float = np.empty(0, dtype=np.float64) for _ in range(test_frames): # Generate input data - data = rand_int32_arr(rng, vnr.MEL_FILTERS, 8) + data = pvc.rand_int32_arr(rng, vnr.MEL_FILTERS, 8) input_data = np.append(input_data, data) # Ref form input frame implementation diff --git a/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_output_dequantise.py b/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_output_dequantise.py index 1116612c..5a34809c 100644 --- a/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_output_dequantise.py +++ b/tests/lib_vnr/vnr_unit_tests/test_vnr_priv_output_dequantise.py @@ -1,7 +1,6 @@ # Copyright 2025-2026 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import numpy as np -from test_utils import rand_int32_arr import py_vs_c_utils as pvc def test_vnr_priv_output_dequantise(rng, dequantise, dut_runner): @@ -14,7 +13,7 @@ def test_vnr_priv_output_dequantise(rng, dequantise, dut_runner): ref_output_double = np.empty(0, dtype=np.float64) for _ in range(test_frames): - data = rand_int32_arr(rng, 1, min=np.iinfo(np.int8).min, max=np.iinfo(np.int8).max + 1) + data = pvc.rand_int32_arr(rng, 1, min=np.iinfo(np.int8).min, max=np.iinfo(np.int8).max + 1) input_data = np.append(input_data, data) # Reference dequantise implementation diff --git a/tests/shared/python/py_vs_c_utils.py b/tests/shared/python/py_vs_c_utils.py index 715b5dea..f30fa802 100644 --- a/tests/shared/python/py_vs_c_utils.py +++ b/tests/shared/python/py_vs_c_utils.py @@ -6,6 +6,12 @@ from pathlib import Path import sys +def rand_int32_arr(rng, size=None, hr_max=1, min=np.iinfo(np.int32).min, max=np.iinfo(np.int32).max+1): + hr = rng.integers(hr_max) + data = rng.integers(min, max, size=size, dtype=np.int32) + data >>= hr + return data + # Turn a float32 from C into an np float scalar def float_s32_to_float(float_s32): return np.ldexp(float_s32.mant, float_s32.exp) @@ -54,6 +60,8 @@ def float_s32_arr_to_double(flat_data): # of form: exponent (i32), data array (i32) # to the np.float64 array def bfp_s32_arr_to_double(flat_data, bfp_len, num_frames): + in_len = (bfp_len + 1) * num_frames + assert in_len == len(flat_data), f"Binary data len: {len(flat_data)} does not align with the frame len: {in_len}" # Do cumulative sum to get indexes for exponents and data array starts sections = np.cumsum(np.tile([1, bfp_len], num_frames))[:-1].astype(np.int32) split = np.split(flat_data, sections)