This commit is contained in:
Steven Dan
2025-12-11 09:43:42 +08:00
commit d8b2974133
1822 changed files with 280037 additions and 0 deletions

View 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()

View 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.

View 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();
}

View 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)