init
This commit is contained in:
26
lib_audio_dsp/test/td_block_fir/CMakeLists.txt
Normal file
26
lib_audio_dsp/test/td_block_fir/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake autogen)
|
||||
project(td_fir_test)
|
||||
|
||||
set(APP_HW_TARGET XCORE-AI-EXPLORER)
|
||||
|
||||
set(APP_COMPILER_FLAGS
|
||||
-O3
|
||||
-g
|
||||
-report
|
||||
-Wall
|
||||
-Werror
|
||||
-fxscope)
|
||||
|
||||
set(APP_DEPENDENT_MODULES lib_audio_dsp)
|
||||
set(APP_C_SRCS src/main.c )
|
||||
set(APP_INCLUDES src ../fd_block_fir/src)
|
||||
file(GLOB C_SRC CONFIGURE_DEPENDS RELATIVE ${CMAKE_CURRENT_LIST_DIR} src/*.c)
|
||||
file(GLOB REF_SRC CONFIGURE_DEPENDS RELATIVE ${CMAKE_CURRENT_LIST_DIR} ../fd_block_fir/src/ref*.c)
|
||||
|
||||
set(APP_C_SRCS
|
||||
"${C_SRC};${REF_SRC}")
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
8
lib_audio_dsp/test/td_block_fir/README.rst
Normal file
8
lib_audio_dsp/test/td_block_fir/README.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
To run:
|
||||
~~~~~~~
|
||||
|
||||
pytest
|
||||
|
||||
This will build a test filter, make the project with cmake then clean out all autogenerated files.
|
||||
|
||||
89
lib_audio_dsp/test/td_block_fir/src/main.c
Normal file
89
lib_audio_dsp/test/td_block_fir/src/main.c
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "../autogen/dut.h"
|
||||
#include "../autogen/dut_debug.h"
|
||||
#include "ref_fir.h"
|
||||
|
||||
/*
|
||||
This controls the number of extra block long the data is vs the length of the coefficient
|
||||
array. It tests that with more data then you need the FIR is still correctly implemented.
|
||||
*/
|
||||
#define EXTRA_DATA_BLOCKS 2
|
||||
|
||||
/*
|
||||
This tests for equlivance between the FD implementation and the TD reference.
|
||||
It has an allowed error of 4 for mean abs error and abs mean error.
|
||||
*/
|
||||
int run_test(void){
|
||||
//allocate a TD FIR for reference
|
||||
int32_t __attribute__((aligned (8))) data_debug[debug_dut_DATA_BUFFER_ELEMENTS];
|
||||
|
||||
//allocate a TD BLOCK FIR for reference
|
||||
//one extra block for making the indexing easier and one as we are making TD_BLOCK_FIR_LENGTH outputs
|
||||
int32_t __attribute__((aligned (8))) block_data_td[dut_DATA_BUFFER_ELEMENTS+TD_BLOCK_FIR_LENGTH*EXTRA_DATA_BLOCKS];
|
||||
|
||||
for( int b=0;b<EXTRA_DATA_BLOCKS;b++){
|
||||
|
||||
int error_sum = 0;
|
||||
int abs_error_sum = 0;
|
||||
int count = 0;
|
||||
|
||||
td_block_fir_data_t data;
|
||||
|
||||
td_block_fir_data_init(&data, block_data_td, (dut_DATA_BUFFER_ELEMENTS + TD_BLOCK_FIR_LENGTH*b));
|
||||
|
||||
memset(block_data_td, 0, sizeof(block_data_td));
|
||||
memset(data_debug, 0, sizeof(data_debug));
|
||||
|
||||
int phases = td_block_fir_filter_dut.block_count;
|
||||
|
||||
for(int j=0;j<phases+1;j++)
|
||||
{
|
||||
int32_t new_data[TD_BLOCK_FIR_LENGTH];
|
||||
for(int i=0;i<TD_BLOCK_FIR_LENGTH;i++)
|
||||
new_data[i] = (rand()-rand())>>1;
|
||||
|
||||
int32_t td_processed[TD_BLOCK_FIR_LENGTH] = {0};
|
||||
int32_t fd_processed[TD_BLOCK_FIR_LENGTH] = {0};
|
||||
|
||||
for(int i=0;i<TD_BLOCK_FIR_LENGTH;i++)
|
||||
td_processed[i] = td_reference_fir(new_data[i], &td_block_debug_fir_filter_dut, data_debug);
|
||||
|
||||
td_block_fir_add_data(new_data, &data);
|
||||
|
||||
td_block_fir_compute(
|
||||
fd_processed,
|
||||
&data,
|
||||
&td_block_fir_filter_dut);
|
||||
|
||||
for(int i=0;i<TD_BLOCK_FIR_LENGTH;i++){
|
||||
int error = td_processed[i] - fd_processed[i];
|
||||
// printf("%ld %ld %.2f\n", td_processed[i], fd_processed[i], (float)td_processed[i] / (float)fd_processed[i]);
|
||||
error_sum += error;
|
||||
if(error < 0) error = -error;
|
||||
abs_error_sum += error;
|
||||
count++;
|
||||
}
|
||||
|
||||
}
|
||||
float error_ave_abs = (float)error_sum / count;
|
||||
if(error_ave_abs<0)error_ave_abs=-error_ave_abs;
|
||||
if (error_ave_abs > 4.0){
|
||||
printf("avg error:%f avg abs error:%f\n", (float)error_sum / count, (float)abs_error_sum / count);
|
||||
return 1;
|
||||
}
|
||||
if(((float)abs_error_sum / count) > 4.0){
|
||||
printf("avg error:%f avg abs error:%f\n", (float)error_sum / count, (float)abs_error_sum / count);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void){
|
||||
return run_test();
|
||||
}
|
||||
130
lib_audio_dsp/test/td_block_fir/test_td_block_fir.py
Normal file
130
lib_audio_dsp/test/td_block_fir/test_td_block_fir.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# Copyright 2024-2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import shutil
|
||||
import pytest
|
||||
from scipy.signal import firwin
|
||||
from audio_dsp.dsp.td_block_fir import generate_td_fir
|
||||
|
||||
import uuid
|
||||
from filelock import FileLock
|
||||
|
||||
# TODO move build utils somewhere else
|
||||
import os
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../pipeline/python')))
|
||||
from build_utils import build
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../fd_block_fir')))
|
||||
from ref_fir import generate_debug_fir
|
||||
|
||||
build_dir_name = "build"
|
||||
|
||||
bin_dir = Path(__file__).parent / "bin"
|
||||
gen_dir = Path(__file__).parent / "autogen"
|
||||
build_dir = Path(__file__).parent / build_dir_name
|
||||
|
||||
|
||||
def build_and_run_tests(dir_name, coefficients, frame_advance = 8, td_block_length = 8, frame_overlap = 0, sim = True, gain_db = 0.0):
|
||||
|
||||
local_build_dir_name = build_dir_name
|
||||
|
||||
bin_dir = Path(__file__).parent / "bin"
|
||||
gen_dir = Path(__file__).parent / "autogen"
|
||||
build_dir = Path(__file__).parent / local_build_dir_name
|
||||
|
||||
bin_dir.mkdir(exist_ok=True, parents=True)
|
||||
# the builds share files, so can't be built in parallel, but we can run xsim in parallel after
|
||||
with FileLock("build_blocker.lock"):
|
||||
gen_dir.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
# run the filter_generator on the coefs
|
||||
try:
|
||||
generate_td_fir(coefficients, "dut", gen_dir, gain_db=gain_db)
|
||||
generate_debug_fir(coefficients, "dut", gen_dir, gain_db = gain_db, verbose = False)
|
||||
except ValueError as e:
|
||||
# print('Success (Expected Fail)')
|
||||
print('coef count', len(coefficients), 'frame_advance', frame_advance, 'td_block_length', td_block_length, 'frame_overlap', frame_overlap)
|
||||
raise e
|
||||
except Exception as e:
|
||||
# print('Fail', repr(error))
|
||||
print('FAIL coef count', len(coefficients), 'frame_advance', frame_advance, 'td_block_length', td_block_length, 'frame_overlap', frame_overlap)
|
||||
raise e
|
||||
|
||||
# build the project
|
||||
build(Path(dir_name), Path(build_dir), "td_fir_test")
|
||||
|
||||
unique_xe = str(bin_dir / f"{uuid.uuid4().hex[:10]}_td_fir_test.xe")
|
||||
os.rename(str(bin_dir / "td_fir_test.xe"), unique_xe)
|
||||
|
||||
# Clean up
|
||||
shutil.rmtree(gen_dir)
|
||||
|
||||
app = "xsim" if sim else "xrun --io"
|
||||
run_cmd = app + " --args " + str(bin_dir / unique_xe)
|
||||
|
||||
proc = subprocess.run(run_cmd, capture_output=True, cwd = dir_name, shell = True)
|
||||
|
||||
sig_int = proc.returncode
|
||||
|
||||
if sig_int == 0:
|
||||
pass
|
||||
else:
|
||||
print('FAIL coef count', len(coefficients), 'frame_advance', frame_advance, 'td_block_length', td_block_length, 'frame_overlap', frame_overlap)
|
||||
raise RuntimeError(f"xsim failed: {sig_int}")
|
||||
return sig_int
|
||||
|
||||
dir_name = Path(__file__).parent
|
||||
|
||||
def test_trivial():
|
||||
build_and_run_tests(dir_name, np.ones(1))
|
||||
|
||||
@pytest.mark.parametrize("length", range(2, 17, 2))
|
||||
def test_constant_value_variable_length(length):
|
||||
build_and_run_tests(dir_name, np.ones(length))
|
||||
|
||||
@pytest.mark.parametrize("length", range(2, 17, 2))
|
||||
def test_random_value_variable_length(length):
|
||||
build_and_run_tests(dir_name, np.random.uniform(-1, 1, length))
|
||||
|
||||
@pytest.mark.parametrize("length", range(2, 17, 2))
|
||||
def test_extreme_value_variable_length(length):
|
||||
c = np.random.randint(0, 2, length)*2 - 1
|
||||
build_and_run_tests(dir_name, c)
|
||||
|
||||
@pytest.mark.parametrize("length", range(2, 17, 2))
|
||||
def test_all_negative_variable_length(length):
|
||||
c = -np.ones(length)
|
||||
build_and_run_tests(dir_name, c)
|
||||
|
||||
@pytest.mark.parametrize("length", range(2, 17, 2))
|
||||
def test_random_pos_value_variable_length(length):
|
||||
build_and_run_tests(dir_name, np.abs(np.random.uniform(-1, 1, length)))
|
||||
|
||||
@pytest.mark.parametrize("length", range(2, 17, 2))
|
||||
def test_random_neg_value_variable_length(length):
|
||||
build_and_run_tests(dir_name, np.abs(np.random.uniform(-1, 1, length)))
|
||||
|
||||
@pytest.mark.parametrize("length", [127, 128, 129])
|
||||
def test_real_filter(length):
|
||||
build_and_run_tests(dir_name, firwin(length, 0.5))
|
||||
|
||||
@pytest.mark.parametrize("length", range(2, 17, 2))
|
||||
def test_main(length):
|
||||
coeffs = np.abs(np.random.uniform(-1, 1, length))
|
||||
coeff_name = f"tmp_coeffs_{length}.npy"
|
||||
np.save(coeff_name, coeffs)
|
||||
|
||||
out_folder = f"autogen_{os.environ.get('PYTEST_XDIST_WORKER')}"
|
||||
os.makedirs(out_folder, exist_ok=True)
|
||||
|
||||
subprocess.check_output(f"python -m audio_dsp.dsp.td_block_fir {coeff_name} --output {out_folder}", shell=True)
|
||||
|
||||
shutil.rmtree(out_folder)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# test_random_pos_value_variable_length(2)
|
||||
test_main(10)
|
||||
Reference in New Issue
Block a user