diff --git a/.gitignore b/.gitignore index 833049a0..e50d6bee 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,5 @@ build.xcore # Python cache information lib_mic_array.egg-info +examples/app_mic_array_basic/output.wav +examples/app_mic_array_basic/mic_array_output.bin diff --git a/Jenkinsfile b/Jenkinsfile index 13b738c2..b33fc207 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,8 +15,13 @@ pipeline { string( name: 'XMOSDOC_VERSION', defaultValue: 'v8.0.1', - description: 'The xmosdoc version') - + description: 'The xmosdoc version' + ) + string( + name: 'TOOLS_VX4_VERSION', + defaultValue: '-j --repo arch_vx_slipgate -b master -a XTC 112', + description: 'The XTC Slipgate tools version' + ) string( name: 'INFR_APPS_VERSION', defaultValue: 'v3.3.0', @@ -139,10 +144,8 @@ pipeline { stage('Tests') { parallel { - stage('XS3 tests') { - agent { - label 'xcore.ai' - } + stage('XS3 Tests') { + agent {label 'xcore.ai'} stages { stage("Checkout and Build") { steps { @@ -209,11 +212,40 @@ pipeline { } // stage('Run tests') } // stages post { - cleanup { - xcoreCleanSandbox() - } - } - } // stage('HW tests') + cleanup {xcoreCleanSandbox()} + } // post + } // XS3 Tests + + stage('VX4 Tests') { + agent {label "vx4"} + stages { + stage("Checkout and Build") { + steps { + dir(REPO_NAME){ + checkoutScmShallow() + dir("tests/unit") { + xcoreBuild(buildTool: "xmake", toolsVersion: params.TOOLS_VX4_VERSION) + } + dir ("tests/signal/BasicMicArray") { + createVenv(reqFile: "requirements.txt") + withVenv { + xcoreBuild(buildTool: "xmake", toolsVersion: params.TOOLS_VX4_VERSION) + } + } + }} + } // stage("Checkout and Build") + stage('Run tests') { + steps { + dir(REPO_NAME){ + dir("tests/unit") { + withTools(params.TOOLS_VX4_VERSION) {sh "xrun --xscope bin/tests-unit.xe"} + }}}} // stage('Run tests') + } // stages + post { + cleanup {xcoreCleanSandbox()} + } //post + } // VX4 Tests + } // parallel } // stage('Tests') diff --git a/doc/exclude_patterns.inc b/doc/exclude_patterns.inc index 3a948a03..9ddf9cb8 100644 --- a/doc/exclude_patterns.inc +++ b/doc/exclude_patterns.inc @@ -7,3 +7,4 @@ LICENSE.rst build.xcore tests/**/.pytest_cache/*.md tests/.pytest_cache/*.md +**/app_mic_array_basic/*.md diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2e687aa0..99700f7d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.21) include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) project(mic_array_examples) add_subdirectory(app_mic_array) +add_subdirectory(app_mic_array_basic) add_subdirectory(app_shutdown) add_subdirectory(app_par_decimator) add_subdirectory(app_custom_filter) diff --git a/examples/app_mic_array_basic/CMakeLists.txt b/examples/app_mic_array_basic/CMakeLists.txt new file mode 100644 index 00000000..6b7da34d --- /dev/null +++ b/examples/app_mic_array_basic/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) +project(app_mic_array) + +set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) + +set(APP_HW_TARGET src/XK-EVK-XU316-AIV.xn) +set(APP_DEPENDENT_MODULES "lib_mic_array") +set(APP_INCLUDES src) + +set(APP_COMPILER_FLAGS + -Os + -g + -report + -Wall + -fxscope + -Wno-xcore-fptrgroup + # Mic array config + -DMIC_ARRAY_CONFIG_SAMPLES_PER_FRAME=320 + -DMIC_ARRAY_CONFIG_MIC_COUNT=1 + -DMIC_ARRAY_CONFIG_USE_PDM_ISR=1 +) + +XMOS_REGISTER_APP() diff --git a/examples/app_mic_array_basic/README.md b/examples/app_mic_array_basic/README.md new file mode 100644 index 00000000..f548144a --- /dev/null +++ b/examples/app_mic_array_basic/README.md @@ -0,0 +1,30 @@ +# Basic Mic Array Example + +## Hardware Required + +- **XK-EVK-XU316-AIV.XN** + +## Compile + +```sh +cmake -G "Unix Makefiles" -B build +xmake -C build +``` + +## Run + +```sh +xrun --xscope bin/app_mic_array.xe +``` + +## Convert Binary Data to WAV + +```sh +python convert.py +``` + +**Output:** + +``` +Converted mic_array_output.bin to output.wav with 1 channels, 16000 Hz sample rate, and 32 bits per sample. +``` diff --git a/examples/app_mic_array_basic/convert.py b/examples/app_mic_array_basic/convert.py new file mode 100644 index 00000000..95ed982a --- /dev/null +++ b/examples/app_mic_array_basic/convert.py @@ -0,0 +1,28 @@ +# Copyright 2026 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +import numpy as np +import wave +import soundfile as sf + +APP_OUT_FREQ_HZ = 12000 # 4KHz + +def convert_to_wav( + input_file, output_file, num_channels=1, sample_rate=16000, bits_per_sample=32 +): + with open(input_file, "rb") as inp_f: + data = inp_f.read() + data = np.frombuffer(data, dtype=np.int32) + + sf.write(output_file, data, sample_rate, subtype='PCM_32') + print(f"Converted {input_file} to {output_file} with {num_channels} channels, {sample_rate} Hz sample rate, and {bits_per_sample} bits per sample.") + + +if __name__ == "__main__": + convert_to_wav( + input_file="mic_array_output.bin", + output_file="output.wav", + num_channels=1, + sample_rate=APP_OUT_FREQ_HZ, + bits_per_sample=32 + ) diff --git a/examples/app_mic_array_basic/mic_array_output.bin b/examples/app_mic_array_basic/mic_array_output.bin new file mode 100644 index 00000000..4f69a22c Binary files /dev/null and b/examples/app_mic_array_basic/mic_array_output.bin differ diff --git a/examples/app_mic_array_basic/output.wav b/examples/app_mic_array_basic/output.wav new file mode 100644 index 00000000..dc809209 Binary files /dev/null and b/examples/app_mic_array_basic/output.wav differ diff --git a/examples/app_mic_array_basic/src/XK-EVK-XU316-AIV.xn b/examples/app_mic_array_basic/src/XK-EVK-XU316-AIV.xn new file mode 100644 index 00000000..b4eb8fff --- /dev/null +++ b/examples/app_mic_array_basic/src/XK-EVK-XU316-AIV.xn @@ -0,0 +1,66 @@ + + + Board + xcore.ai Vision Development Kit + + + tileref tile[2] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/app_mic_array_basic/src/app.c b/examples/app_mic_array_basic/src/app.c new file mode 100644 index 00000000..a1d8155c --- /dev/null +++ b/examples/app_mic_array_basic/src/app.c @@ -0,0 +1,117 @@ +// Copyright 2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "mic_array.h" +#include "device_pll_ctrl.h" +#include "small_768k_to_12k_filter.h" + +#include "app_config.h" + +#define APP_FILENAME ("mic_array_output.bin") + +DECLARE_JOB(user_mic, (chanend_t)); +DECLARE_JOB(user_audio, (chanend_t)); + +static pdm_rx_resources_t pdm_res = PDM_RX_RESOURCES_SDR( + MIC_ARRAY_CONFIG_PORT_MCLK, + MIC_ARRAY_CONFIG_PORT_PDM_CLK, + MIC_ARRAY_CONFIG_PORT_PDM_DATA, + MIC_ARRAY_CONFIG_MCLK_FREQ, + MIC_ARRAY_CONFIG_PDM_FREQ, + MIC_ARRAY_CONFIG_CLOCK_BLOCK_A); + + + +void init_mic_conf(mic_array_conf_t *mic_array_conf, mic_array_filter_conf_t filter_conf[2], unsigned *channel_map) +{ + static int32_t stg1_filter_state[APP_MIC_COUNT][8]; + static int32_t stg2_filter_state[APP_MIC_COUNT][SMALL_768K_TO_12K_FILTER_STG2_TAP_COUNT]; + memset(mic_array_conf, 0, sizeof(mic_array_conf_t)); + + //decimator + mic_array_conf->decimator_conf.filter_conf = &filter_conf[0]; + mic_array_conf->decimator_conf.num_filter_stages = 2; + // filter stage 1 + filter_conf[0].coef = (int32_t*)small_768k_to_12k_filter_stg1_coef; + filter_conf[0].num_taps = SMALL_768K_TO_12K_FILTER_STG1_TAP_COUNT; + filter_conf[0].decimation_factor = SMALL_768K_TO_12K_FILTER_STG1_DECIMATION_FACTOR; + filter_conf[0].state = (int32_t*)stg1_filter_state; + filter_conf[0].shr = SMALL_768K_TO_12K_FILTER_STG1_SHR; + filter_conf[0].state_words_per_channel = filter_conf[0].num_taps/32; // works on 1-bit samples + // filter stage 2 + filter_conf[1].coef = (int32_t*)small_768k_to_12k_filter_stg2_coef; + filter_conf[1].num_taps = SMALL_768K_TO_12K_FILTER_STG2_TAP_COUNT; + filter_conf[1].decimation_factor = SMALL_768K_TO_12K_FILTER_STG2_DECIMATION_FACTOR; + filter_conf[1].state = (int32_t*)stg2_filter_state; + filter_conf[1].shr = SMALL_768K_TO_12K_FILTER_STG2_SHR; + filter_conf[1].state_words_per_channel = SMALL_768K_TO_12K_FILTER_STG2_TAP_COUNT; + + // pdm rx + static uint32_t pdmrx_out_block[APP_MIC_COUNT][SMALL_768K_TO_12K_FILTER_STG2_DECIMATION_FACTOR]; + static uint32_t pdmrx_out_block_double_buf[2][APP_MIC_COUNT * SMALL_768K_TO_12K_FILTER_STG2_DECIMATION_FACTOR] __attribute__((aligned(8))); + mic_array_conf->pdmrx_conf.pdm_out_words_per_channel = SMALL_768K_TO_12K_FILTER_STG2_DECIMATION_FACTOR; + mic_array_conf->pdmrx_conf.pdm_out_block = (uint32_t*)pdmrx_out_block; + mic_array_conf->pdmrx_conf.pdm_in_double_buf = (uint32_t*)pdmrx_out_block_double_buf; + mic_array_conf->pdmrx_conf.channel_map = channel_map; +} + +void user_mic(chanend_t c_mic_audio) +{ + printf("Mic Init\n"); + device_pll_init(); + unsigned channel_map[1] = {0}; + mic_array_conf_t mic_array_conf; + mic_array_filter_conf_t filter_conf[2]; + init_mic_conf(&mic_array_conf, filter_conf, channel_map); + mic_array_init_custom_filter(&pdm_res, &mic_array_conf); + mic_array_start(c_mic_audio); +} + +void user_audio(chanend_t c_mic_audio) +{ + int32_t WORD_ALIGNED tmp_buff[APP_BUFF_SIZE] = {0}; + int32_t *buff_ptr = &tmp_buff[0]; + unsigned frame_counter = APP_N_FRAMES; + while (frame_counter--) + { + ma_frame_rx(buff_ptr, (chanend_t)c_mic_audio, MIC_ARRAY_CONFIG_MIC_COUNT, APP_N_SAMPLES); + buff_ptr += APP_N_SAMPLES; + for (unsigned i = 0; i < APP_N_SAMPLES; i++) + { + tmp_buff[i] <<= 6; + } + } + + // write samples to a binary file + printf("Writing output to %s\n", APP_FILENAME); + FILE *f = fopen(APP_FILENAME, "wb"); + assert(f != NULL); + fwrite(tmp_buff, sizeof(int32_t), APP_BUFF_SIZE, f); + fclose(f); + ma_shutdown(c_mic_audio); + printf("Done\n"); +} + +void main_tile_1(){ + channel_t c_mic_audio = chan_alloc(); + // Parallel Jobs + PAR_JOBS( + PJOB(user_mic, (c_mic_audio.end_a)), + PJOB(user_audio, (c_mic_audio.end_b)) + ); + chan_free(c_mic_audio); +} + +void main_tile_0(){ + // intentionally left empty + return; +} diff --git a/examples/app_mic_array_basic/src/app_config.h b/examples/app_mic_array_basic/src/app_config.h new file mode 100644 index 00000000..83271a05 --- /dev/null +++ b/examples/app_mic_array_basic/src/app_config.h @@ -0,0 +1,20 @@ +// Copyright 2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +// -------------------- Frecuency and Port definitions -------------------- +#define MIC_ARRAY_CONFIG_MCLK_FREQ (24576000) /* 24 MHz */ +#define MIC_ARRAY_CONFIG_PDM_FREQ (768000) /* 768 KHz */ +#define MIC_ARRAY_CONFIG_PORT_MCLK XS1_PORT_1D /* X0D11, J14 - Pin 15, '11' */ +#define MIC_ARRAY_CONFIG_PORT_PDM_CLK PORT_MIC_CLK /* X0D00, J14 - Pin 2, '00' */ +#define MIC_ARRAY_CONFIG_PORT_PDM_DATA PORT_MIC_DATA /* X0D14..X0D21 | J14 - Pin 3,5,12,14 and Pin 6,7,10,11 */ +#define MIC_ARRAY_CONFIG_CLOCK_BLOCK_A XS1_CLKBLK_2 + +// ------------------------- App Definitions ----------------------------------- +#define APP_N_SAMPLES (320) +#define APP_OUT_FREQ_HZ (12000) // 12KHz +#define APP_SAMPLE_SECONDS (2) +#define APP_N_FRAMES (APP_OUT_FREQ_HZ * APP_SAMPLE_SECONDS / APP_N_SAMPLES) +#define APP_BUFF_SIZE (APP_N_FRAMES * APP_N_SAMPLES) +#define APP_MIC_COUNT (MIC_ARRAY_CONFIG_MIC_COUNT) diff --git a/examples/app_mic_array_basic/src/config.xscope b/examples/app_mic_array_basic/src/config.xscope new file mode 100644 index 00000000..d3a3da63 --- /dev/null +++ b/examples/app_mic_array_basic/src/config.xscope @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/examples/app_mic_array_basic/src/device_pll_ctrl.c b/examples/app_mic_array_basic/src/device_pll_ctrl.c new file mode 100644 index 00000000..83b48726 --- /dev/null +++ b/examples/app_mic_array_basic/src/device_pll_ctrl.c @@ -0,0 +1,32 @@ +// Copyright 2022-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include +#include +#include "device_pll_ctrl.h" + + +void device_pll_init(void) +{ + unsigned tileid = get_local_tile_id(); + + const unsigned DEVICE_PLL_DISABLE = 0x0201FF04; + const unsigned DEVICE_PLL_DIV_0 = 0x80000004; + + write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, + DEVICE_PLL_DISABLE); + + hwtimer_t tmr = hwtimer_alloc(); + { + xassert(tmr != 0); + hwtimer_delay(tmr, 100000); // 1ms with 100 MHz timer tick + } + hwtimer_free(tmr); + + write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, DEVICE_PLL_CTL_VAL); + write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, DEVICE_PLL_CTL_VAL); + write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, DEVICE_PLL_FRAC_NOM); + write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_CLK_DIVIDER_NUM, DEVICE_PLL_DIV_0); +} diff --git a/examples/app_mic_array_basic/src/device_pll_ctrl.h b/examples/app_mic_array_basic/src/device_pll_ctrl.h new file mode 100644 index 00000000..7fe002e6 --- /dev/null +++ b/examples/app_mic_array_basic/src/device_pll_ctrl.h @@ -0,0 +1,9 @@ +// Copyright 2022-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#define DEVICE_PLL_CTL_VAL 0x0A019803 // Valid for all fractional values +#define DEVICE_PLL_FRAC_NOM 0x800095F9 // 24.576000 MHz + +void device_pll_init(void); diff --git a/examples/app_mic_array_basic/src/main.xc b/examples/app_mic_array_basic/src/main.xc new file mode 100644 index 00000000..2c695375 --- /dev/null +++ b/examples/app_mic_array_basic/src/main.xc @@ -0,0 +1,25 @@ +// Copyright 2023-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include +#include + +#include +#include + +extern "C" { + void main_tile_0(); + void main_tile_1(); +} + +int main(void) +{ + // Initialize parallel tasks + par{ + on tile[0]: main_tile_0(); + on tile[1]: main_tile_1(); + } + return 0; +} diff --git a/examples/app_mic_array_basic/src/small_768k_to_12k_filter.h b/examples/app_mic_array_basic/src/small_768k_to_12k_filter.h new file mode 100644 index 00000000..867c2290 --- /dev/null +++ b/examples/app_mic_array_basic/src/small_768k_to_12k_filter.h @@ -0,0 +1,59 @@ +// Copyright 2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#ifndef SMALL_768K_TO_12K_FILTER_H +#define SMALL_768K_TO_12K_FILTER_H + +/* Autogenerated by running 'python combined.py small_768k_to_12k_filter_int.pkl -fp small_768k_to_12k_filter'. Do not edit */ + +#include + + +#define SMALL_768K_TO_12K_FILTER_STG1_DECIMATION_FACTOR 32 +#define SMALL_768K_TO_12K_FILTER_STG1_TAP_COUNT 256 +#define SMALL_768K_TO_12K_FILTER_STG1_SHR 0 /*shr not relevant for stage 1*/ + + +uint32_t small_768k_to_12k_filter_stg1_coef[128] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFF2DBBA, 0x1E443FC2, 0x2788F9F1, 0x1E443FC2, 0x2785DDB4, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFF86BEB, 0x1C91CEC9, 0x8DC6F6F6, 0x3B193738, 0x938D7D61, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFDBC29, 0x211BF8E9, 0x323BF6FD, 0xC4C971FD, 0x884943DB, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFE89A2, 0x721D515E, 0x02D0A650, 0xB407A8AB, 0x84E45917, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF26BF, 0x614B35F7, 0xE678C631, 0xE67EFACD, 0x286FD64F, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFCA48, 0x0C0BC045, 0x42E8F9F1, 0x742A203D, 0x0301253F, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF358, 0x5EE51139, 0x80C16668, 0x3019C88A, 0x77A1ACFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFC6D, 0x3F5E4E54, 0xAB2F696F, 0x4D52A727, 0xAFCB63FF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF8E, 0x553F9533, 0x994F30CF, 0x299CCA9F, 0xCAA71FFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFF0, 0x66554CF0, 0x78DA4025, 0xB1E0F32A, 0xA660FFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x879996A5, 0x5293801C, 0x94AA5699, 0x9E1FFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xF81E18C6, 0x631C0003, 0x8C663187, 0x81FFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE01F07, 0x83E00000, 0x7C1E0F80, 0x7FFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFE007, 0xFC000000, 0x03FE007F, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFF8, 0x00000000, 0x0001FFFF, 0xFFFFFFFF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, +}; + + +#define SMALL_768K_TO_12K_FILTER_STG2_DECIMATION_FACTOR 2 +#define SMALL_768K_TO_12K_FILTER_STG2_TAP_COUNT 48 +#define SMALL_768K_TO_12K_FILTER_STG2_SHR 1 + + +int32_t small_768k_to_12k_filter_stg2_coef[48] = { +-0x6b2e, 0x9bb0, 0x867bf, 0x6abc3, +-0x1d6951, -0x37fde1, 0x1b8845, 0xad6445, +0x6737ac, -0x11a7f35, -0x1d79ea4, 0x7ee25c, +0x3e05795, 0x27d0754, -0x49e8388, -0x834e523, +0xb8e3a0, 0xe48a501, 0xb3d7d09, -0xe33d15c, +-0x212034e8, -0x6b83320, 0x408190d3, 0x7fffffff, +0x7fffffff, 0x408190d3, -0x6b83320, -0x212034e8, +-0xe33d15c, 0xb3d7d09, 0xe48a501, 0xb8e3a0, +-0x834e523, -0x49e8388, 0x27d0754, 0x3e05795, +0x7ee25c, -0x1d79ea4, -0x11a7f35, 0x6737ac, +0xad6445, 0x1b8845, -0x37fde1, -0x1d6951, +0x6abc3, 0x867bf, 0x9bb0, -0x6b2e, +}; + +#define NUM_DECIMATION_STAGES (2) + +#endif diff --git a/examples/app_par_decimator/src/app.cpp b/examples/app_par_decimator/src/app.cpp index 2b3f5874..1b01f7e4 100644 --- a/examples/app_par_decimator/src/app.cpp +++ b/examples/app_par_decimator/src/app.cpp @@ -43,8 +43,12 @@ pdm_rx_resources_t pdm_res = PDM_RX_RESOURCES_DDR( #define APP_N_MICS_IN APP_N_MICS #endif #define STAGE2_DEC_FACTOR_48KHZ 2 -#define CLRSR(c) asm volatile("clrsr %0" : : "n"(c)); -#define CLEAR_KEDI() CLRSR(XS1_SR_KEDI_MASK) + +#if defined(__XS3A__) +#define CLEAR_KEDI() asm volatile("clrsr %0" : : "n"(XS1_SR_KEDI_MASK)); +#else +#define CLEAR_KEDI() ((void)0) // not defined in !xs3a +#endif using TMicArray = mic_array::MicArray 0; k--) { + buff[k] = buff[k-1]; + } + #endif } diff --git a/lib_mic_array/api/mic_array/cpp/Decimator.hpp b/lib_mic_array/api/mic_array/cpp/Decimator.hpp index 06003b59..1f3b8eaf 100644 --- a/lib_mic_array/api/mic_array/cpp/Decimator.hpp +++ b/lib_mic_array/api/mic_array/cpp/Decimator.hpp @@ -181,5 +181,12 @@ void mic_array::shift_buffer(uint32_t* buff) #if defined(__XS3A__) uint32_t* src = &buff[-1]; asm volatile("vldd %0[0]; vstd %1[0];" :: "r"(src), "r"(buff) : "memory" ); - #endif // __XS3A__ + #elif defined(__VX4B__) + uint32_t* src = &buff[-1]; + asm volatile("xm.vldd %0; xm.vstd %1;" :: "r"(src), "r"(buff) : "memory" ); + #else // C fallback + for (unsigned k = 7; k > 0; k--) { + buff[k] = buff[k-1]; + } + #endif } diff --git a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp index 938b1968..5634de11 100644 --- a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp +++ b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp @@ -164,9 +164,10 @@ extern "C" { : : "r"(p_pdm_mics), "r"(XS1_SETC_IE_MODE_INTERRUPT) : "r11" ); - #endif // __XS3A__ + #else + #warning "PDM rx ISR not supported yet on this architecture." + #endif } - } diff --git a/lib_mic_array/api/mic_array/etc/xcore_compat.h b/lib_mic_array/api/mic_array/etc/xcore_compat.h index 2d70e63d..c4c5e9b2 100644 --- a/lib_mic_array/api/mic_array/etc/xcore_compat.h +++ b/lib_mic_array/api/mic_array/etc/xcore_compat.h @@ -32,11 +32,9 @@ extern "C" { #else //__XC__ -#include #include #include #include #include #endif //__XC__ - diff --git a/lib_mic_array/api/mic_array/impl/setup_impl.h b/lib_mic_array/api/mic_array/impl/setup_impl.h index cf967363..1eb68780 100644 --- a/lib_mic_array/api/mic_array/impl/setup_impl.h +++ b/lib_mic_array/api/mic_array/impl/setup_impl.h @@ -14,4 +14,4 @@ unsigned mic_array_mclk_divider( return master_clock_freq / pdm_clock_freq; } -#endif \ No newline at end of file +#endif diff --git a/lib_mic_array/api/mic_array/setup.h b/lib_mic_array/api/mic_array/setup.h index 479d2f2f..64746883 100644 --- a/lib_mic_array/api/mic_array/setup.h +++ b/lib_mic_array/api/mic_array/setup.h @@ -122,4 +122,4 @@ unsigned mic_array_mclk_divider( #include "mic_array/impl/setup_impl.h" -C_API_END \ No newline at end of file +C_API_END diff --git a/lib_mic_array/lib_build_info.cmake b/lib_mic_array/lib_build_info.cmake index b4c7e5ba..9a5cd3f2 100644 --- a/lib_mic_array/lib_build_info.cmake +++ b/lib_mic_array/lib_build_info.cmake @@ -1,6 +1,6 @@ set(LIB_NAME lib_mic_array) set(LIB_VERSION 6.0.0) -set(LIB_DEPENDENT_MODULES "lib_xcore_math(2.4.0)") +set(LIB_DEPENDENT_MODULES "lib_xcore_math(develop)") #TODO pin version set(LIB_INCLUDES api api/mic_array diff --git a/lib_mic_array/src/deinterleave16.S b/lib_mic_array/src/deinterleave16.S index efebb43e..2860f442 100644 --- a/lib_mic_array/src/deinterleave16.S +++ b/lib_mic_array/src/deinterleave16.S @@ -102,7 +102,7 @@ deinterleave16: std f, b, x[6] std h, d, x[7] - + // part2 ldd a, b, x[0] ldd c, d, x[4] unzip b, d, 0 @@ -143,3 +143,127 @@ deinterleave16: .size deinterleave16, .L_end - deinterleave16 #endif // __XS3A__ + +#if defined(__VX4A__) || defined(__VX4B__) + +#define FUNCTION_NAME deinterleave16 +#define NSTACK_WORDS 8 +#define NSTACK_BYTES (NSTACK_WORDS*4) + +#define x a0 +#define a a1 +#define b a2 + +#define c s2 +#define d s3 +#define e s4 +#define f s5 +#define g s6 +#define h s7 + +// Note: ldd and std are reversed in vx4 + +.p2align 4 +.globl FUNCTION_NAME +.type FUNCTION_NAME,@function +FUNCTION_NAME: + // save regs + xm.entsp NSTACK_BYTES + xm.stdsp s3,s2,0 + xm.stdsp s5,s4,8 + xm.stdsp s7,s6,16 + + // Lower half + xm.ldd b, a, (8*3)(x) + xm.ldd d, c, (8*2)(x) + xm.ldd f, e, (8*1)(x) + xm.ldd h, g, (8*0)(x) + + xm.unzip b, a, 2 + xm.unzip d, c, 2 + xm.unzip f, e, 2 + xm.unzip h, g, 2 + + xm.unzip c, a, 1 + xm.unzip d, b, 1 + xm.unzip g, e, 1 + xm.unzip h, f, 1 + + xm.unzip e, a, 0 + xm.unzip f, b, 0 + xm.unzip g, c, 0 + xm.unzip h, d, 0 + + xm.std a, e, (8*0)(x) + xm.std c, g, (8*1)(x) + xm.std b, f, (8*2)(x) + xm.std d, h, (8*3)(x) + + // Upper half + xm.ldd b, a, (8*7)(x) + xm.ldd d, c, (8*6)(x) + xm.ldd f, e, (8*5)(x) + xm.ldd h, g, (8*4)(x) + + xm.unzip b, a, 2 + xm.unzip d, c, 2 + xm.unzip f, e, 2 + xm.unzip h, g, 2 + + xm.unzip c, a, 1 + xm.unzip d, b, 1 + xm.unzip g, e, 1 + xm.unzip h, f, 1 + + xm.unzip e, a, 0 + xm.unzip f, b, 0 + xm.unzip g, c, 0 + xm.unzip h, d, 0 + + xm.std a, e, (8*4)(x) + xm.std c, g, (8*5)(x) + xm.std b, f, (8*6)(x) + xm.std d, h, (8*7)(x) + + // part2 + xm.ldd b, a, (8*0)(x) + xm.ldd d, c, (8*4)(x) + xm.unzip b, d, 0 + xm.unzip a, c, 0 + xm.std b, a, (8*4)(x) + xm.std d, c, (8*0)(x) + + xm.ldd b, a, (8*1)(x) + xm.ldd d, c, (8*5)(x) + xm.unzip b, d, 0 + xm.unzip a, c, 0 + xm.std b, a, (8*5)(x) + xm.std d, c, (8*1)(x) + + xm.ldd b, a, (8*2)(x) + xm.ldd d, c, (8*6)(x) + xm.unzip b, d, 0 + xm.unzip a, c, 0 + xm.std b, a, (8*6)(x) + xm.std d, c, (8*2)(x) + + xm.ldd b, a, (8*3)(x) + xm.ldd d, c, (8*7)(x) + xm.unzip b, d, 0 + xm.unzip a, c, 0 + xm.std b, a, (8*7)(x) + xm.std d, c, (8*3)(x) + + // restore regs + xm.lddsp s3,s2,0 + xm.lddsp s5,s4,8 + xm.lddsp s7,s6,16 + xm.retsp NSTACK_BYTES + +.size FUNCTION_NAME, . -FUNCTION_NAME +.resource_const FUNCTION_NAME, "stack_frame_bytes", NSTACK_BYTES +.resource_list_empty FUNCTION_NAME, "callees" +.resource_list_empty FUNCTION_NAME, "tail_callees" +.resource_list_empty FUNCTION_NAME, "parallel_callees" + +#endif diff --git a/lib_mic_array/src/deinterleave2.S b/lib_mic_array/src/deinterleave2.S index 6c08f352..923c5461 100644 --- a/lib_mic_array/src/deinterleave2.S +++ b/lib_mic_array/src/deinterleave2.S @@ -41,3 +41,28 @@ deinterleave2: .size deinterleave2, .L_end - deinterleave2 #endif // __XS3A__ + +#if defined(__VX4A__) || defined(__VX4B__) + +#define FUNCTION_NAME deinterleave2 +#define NSTACK_BYTES 16 // minimum + +// Note: ldd and std are reversed in vx4 + +.p2align 1 +.globl FUNCTION_NAME +.type FUNCTION_NAME,@function +FUNCTION_NAME: + xm.entsp NSTACK_BYTES + xm.ldd a2, a1, 0(a0) + xm.unzip a2, a1, 0 + xm.std a1, a2, 0(a0) + xm.retsp NSTACK_BYTES + +.size FUNCTION_NAME, . -FUNCTION_NAME +.resource_const FUNCTION_NAME, "stack_frame_bytes", NSTACK_BYTES +.resource_list_empty FUNCTION_NAME, "callees" +.resource_list_empty FUNCTION_NAME, "tail_callees" +.resource_list_empty FUNCTION_NAME, "parallel_callees" + +#endif // __VX4A__ || __VX4B__ diff --git a/lib_mic_array/src/deinterleave4.S b/lib_mic_array/src/deinterleave4.S index 0d383e9c..5a4a8ef9 100644 --- a/lib_mic_array/src/deinterleave4.S +++ b/lib_mic_array/src/deinterleave4.S @@ -85,3 +85,49 @@ deinterleave4: .size deinterleave4, .L_end - deinterleave4 #endif // __XS3A__ + +#if defined(__VX4A__) || defined(__VX4B__) + +#define FUNCTION_NAME deinterleave4 +#define NSTACK_WORDS 4 +#define NSTACK_BYTES (NSTACK_WORDS*4) + +#define x a0 +#define a a1 +#define b a2 +#define c s2 +#define d s3 + +// Note: ldd and std are reversed in vx4 + +.p2align 1 +.globl FUNCTION_NAME +.type FUNCTION_NAME,@function +FUNCTION_NAME: + xm.entsp NSTACK_BYTES + xm.stdsp s2, s3, 0*8 + + // Save and Load + xm.ldd b, a, 8(a0) + xm.ldd d, c, 0(a0) + + // Deinterleave + xm.unzip b, a, 1 + xm.unzip d, c, 1 + xm.unzip c, a, 0 + xm.unzip d, b, 0 + + // Store and Restore regs + xm.std a, c, 0(a0) + xm.std b, d, 8(a0) + + xm.lddsp s2, s3, 0*8 + xm.retsp NSTACK_BYTES + +.size FUNCTION_NAME, . -FUNCTION_NAME +.resource_const FUNCTION_NAME, "stack_frame_bytes", NSTACK_BYTES +.resource_list_empty FUNCTION_NAME, "callees" +.resource_list_empty FUNCTION_NAME, "tail_callees" +.resource_list_empty FUNCTION_NAME, "parallel_callees" + +#endif diff --git a/lib_mic_array/src/deinterleave8.S b/lib_mic_array/src/deinterleave8.S index c3d6a955..cc9a876c 100644 --- a/lib_mic_array/src/deinterleave8.S +++ b/lib_mic_array/src/deinterleave8.S @@ -115,3 +115,73 @@ deinterleave8: .size deinterleave8, .L_end - deinterleave8 #endif // __XS3A__ + + +#if defined(__VX4A__) || defined(__VX4B__) + +#define FUNCTION_NAME deinterleave8 +#define NSTACK_WORDS 8 +#define NSTACK_BYTES (NSTACK_WORDS*4) + +// Note: ldd and std are reversed in vx4 + +#define x a0 +#define a a1 +#define b a2 + +#define c s2 +#define d s3 +#define e s4 +#define f s5 +#define g s6 +#define h s7 + +.p2align 1 +.globl FUNCTION_NAME +.type FUNCTION_NAME,@function +FUNCTION_NAME: + // save regs + xm.entsp NSTACK_BYTES + xm.stdsp c, d, 0*8 + xm.stdsp e, f, 1*8 + xm.stdsp g, h, 2*8 + + // deinterleave + xm.ldd b, a, 24(x) + xm.ldd d, c, 16(x) + xm.ldd f, e, 8(x) + xm.ldd h, g, 0(x) + + xm.unzip b, a, 2 + xm.unzip d, c, 2 + xm.unzip f, e, 2 + xm.unzip h, g, 2 + + xm.unzip c, a, 1 + xm.unzip d, b, 1 + xm.unzip g, e, 1 + xm.unzip h, f, 1 + + xm.unzip e, a, 0 + xm.unzip f, b, 0 + xm.unzip g, c, 0 + xm.unzip h, d, 0 + + xm.std a, e, 0(a0) + xm.std c, g, 8(a0) + xm.std b, f, 16(a0) + xm.std d, h, 24(a0) + + // restore regs + xm.lddsp c, d, 0*8 + xm.lddsp e, f, 1*8 + xm.lddsp g, h, 2*8 + xm.retsp NSTACK_BYTES + +.size FUNCTION_NAME, . -FUNCTION_NAME +.resource_const FUNCTION_NAME, "stack_frame_bytes", NSTACK_BYTES +.resource_list_empty FUNCTION_NAME, "callees" +.resource_list_empty FUNCTION_NAME, "tail_callees" +.resource_list_empty FUNCTION_NAME, "parallel_callees" + +#endif // __VX4A__ || __VX4B__ diff --git a/lib_mic_array/src/fir_1x16_bit.S b/lib_mic_array/src/fir_1x16_bit.S index 576d4ef9..1033d6a1 100644 --- a/lib_mic_array/src/fir_1x16_bit.S +++ b/lib_mic_array/src/fir_1x16_bit.S @@ -72,3 +72,68 @@ macc_coeffs: .cc_bottom fir_1x16_bit.func #endif // __XS3A__ + + +#if defined(__VX4A__) || defined(__VX4B__) + +/** + * This function is the optimal FIR on a 1-bit signal with 16-bit coefficients. + * + * NOTE: This version is optimized for the mic array and takes only a single block of coefficients + * + * r0: argument 1, signal (word aligned) + * r1: argument 2, coefficients (arranged as 16 1-bit arrays, word aligned) + * r2: spare + * r3: spare + * r11: spare +*/ + +#define FUNCTION_NAME fir_1x16_bit +#define NSTACK_WORDS 16 +#define NSTACK_BYTES (NSTACK_WORDS*4) + +.p2align 4 +.globl FUNCTION_NAME +.type FUNCTION_NAME,@function +FUNCTION_NAME: + { li a3, 32 ; xm.entsp NSTACK_BYTES} + { slli t3, a3, 3 ; xm.vclrdr} + { xm.nop ; xm.vsetc t3} + { xm.nop ; xm.vldc a0} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { add a1, a1, a3 ; xm.vlmaccrb a1} + { addi t3,sp, 0 ; xm.vlmaccrb a1} + //TODO Below we could save max of 2 cycles? + xm.vstr t3 + { xm.vclrdr; addi a2, sp, 0} + xm.vldc t3 + xm.ldap t3, macc_coeffs + xm.vlmaccr0 t3 + xm.vlmaccr1 t3 + { addi a2, a2, 4 ; xm.vstr a2} + xm.vstd a2 + xm.lddsp a0, a1, 0 + xm.zip a1, a0, 4 + slli a0, a0, 8 + xm.retsp NSTACK_BYTES + +// The order of these coefficients tells us that whatever gets VLMACCR1'ed last is going to be multiplied by +// the largest coefficient. Thus, if the bipolar coefficient matrix B[,] has shape 16x32, then B[0,:] must +// correspond to the LEAST significant bits of each coefficient +macc_coeffs: + .short 0x7fff, 0x4000, 0x2000, 0x1000, 0x0800, 0x0400, 0x0200, 0x0100, 0x0080, 0x0040, 0x0020, 0x0010, 0x0008, 0x0004, 0x0002, 0x0001 + +#endif // __VX4A__ || __VX4B__ diff --git a/lib_mic_array/src/mic_array_setup.c b/lib_mic_array/src/mic_array_setup.c index 3a9ceac6..2bb4a136 100644 --- a/lib_mic_array/src/mic_array_setup.c +++ b/lib_mic_array/src/mic_array_setup.c @@ -48,11 +48,16 @@ void mic_array_resources_configure( static inline void mic_array_inpw8(const port_t p_pdm_mics) { - #if defined(__XS3A__) uint32_t tmp; + #if defined(__XS3A__) asm volatile("inpw %0, res[%1], 8" : "=r"(tmp) : "r" (p_pdm_mics)); - #endif // __XS3A__ + #elif defined(__VX4B__) + asm volatile("xm.inpw %0, %1, 8": "=r"(tmp): "r"(p_pdm_mics)); + #else + #warning "mic_array_inpw8 not supported yet on this architecture." + (void) tmp; + #endif } void mic_array_pdm_clock_start( diff --git a/lib_mic_array/src/mic_array_task.cpp b/lib_mic_array/src/mic_array_task.cpp index 962ac90a..3e10d4bd 100644 --- a/lib_mic_array/src/mic_array_task.cpp +++ b/lib_mic_array/src/mic_array_task.cpp @@ -19,7 +19,7 @@ bool use_3_stg_decimator = false; // until mic_array_start() completes. mic_array_start() performs shutdown and // then sets g_mics back to nullptr. -#ifdef __XS3A__ +#if defined(__XS3A__) || defined(__VX4B__) //////////////////// // Mic array init // //////////////////// @@ -105,12 +105,10 @@ void default_ma_task_start_decimator_3stg(TMicArray_3stg_decimator& mics, chanen } #if defined(__XS3A__) -#define CLRSR(c) asm volatile("clrsr %0" : : "n"(c)); +#define CLEAR_KEDI() asm volatile("clrsr %0" : : "n"(XS1_SR_KEDI_MASK)); #else -#define CLRSR(c) ((void)0) -#warning "CLRSR not defined for this architecture." +#define CLEAR_KEDI() ((void)0) // not defined in !xs3a #endif -#define CLEAR_KEDI() CLRSR(XS1_SR_KEDI_MASK) template void start_mics_with_pdm_isr(TMics* mics_ptr, chanend_t c_frames_out) @@ -171,4 +169,11 @@ void _mic_array_override_pdm_port(chanend_t c_pdm) g_mics->PdmRx.SetPort((port_t)c_pdm); } } -#endif + +// C wrapper +extern "C" void _mic_array_override_pdm_port_c(chanend_t c_pdm) +{ + _mic_array_override_pdm_port(c_pdm); +} + +#endif // __XS3A__ or __VX4B__ diff --git a/lib_mic_array/src/pdm_rx_isr.S b/lib_mic_array/src/pdm_rx_isr.S index 96c61243..6c649f1e 100644 --- a/lib_mic_array/src/pdm_rx_isr.S +++ b/lib_mic_array/src/pdm_rx_isr.S @@ -109,3 +109,24 @@ pdm_rx_isr: .global pdm_rx_isr #endif //defined(__XS3A__) + +#if defined(__VX4A__) || defined(__VX4B__) + +#define FUNCTION_NAME pdm_rx_isr +#define NSTACK_BYTES 16 // minimum + +.p2align 1 +.globl FUNCTION_NAME +.type FUNCTION_NAME,@function +FUNCTION_NAME: + xm.entsp NSTACK_BYTES + unimp //TODO unimplemented + xm.retsp NSTACK_BYTES + +.size FUNCTION_NAME, . -FUNCTION_NAME +.resource_const FUNCTION_NAME, "stack_frame_bytes", NSTACK_BYTES +.resource_list_empty FUNCTION_NAME, "callees" +.resource_list_empty FUNCTION_NAME, "tail_callees" +.resource_list_empty FUNCTION_NAME, "parallel_callees" + +#endif // __VX4A__ || __VX4B__ diff --git a/tests/signal/BasicMicArray/CMakeLists.txt b/tests/signal/BasicMicArray/CMakeLists.txt index 97a1e98f..ca69a493 100644 --- a/tests/signal/BasicMicArray/CMakeLists.txt +++ b/tests/signal/BasicMicArray/CMakeLists.txt @@ -4,9 +4,26 @@ project(test_ma) set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../..) -include(${CMAKE_CURRENT_LIST_DIR}/../../../examples/deps.cmake) - -set(APP_HW_TARGET XK-EVK-XU316) +set(APP_DEPENDENT_MODULES "lib_mic_array") + +# conditional depending on target +if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL "3.6.0") # XS3 (XTC 15.3.1) + set(APP_HW_TARGET XK-EVK-XU316) + set(COMMON_COMPILER_FLAGS -O2 + -g + -report + -mcmodel=large + -Wno-xcore-fptrgroup + -Wno-unknown-pragmas + -Wno-format) + +else() # VX4 + set(APP_HW_TARGET XK-EVK-XU416) + set(COMMON_COMPILER_FLAGS -Os + -g + -Wno-fptrgroup + -Wno-format) +endif() set_property(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/test_params.json") @@ -84,13 +101,7 @@ foreach(l RANGE 0 ${NUM_SAMP_FREQ}) set(CONFIG "${N_MICS}ch_${FRAME_SIZE}smp_${USE_ISR}isr_${samp_freq_str}") message(${CONFIG}) - set(APP_COMPILER_FLAGS_${CONFIG} -O2 - -g - -report - -mcmodel=large - -Wno-xcore-fptrgroup - -Wno-unknown-pragmas - -Wno-format + set(APP_COMPILER_FLAGS_${CONFIG} ${COMMON_COMPILER_FLAGS} -DMIC_ARRAY_CONFIG_USE_PDM_ISR=${USE_ISR} -DMIC_ARRAY_CONFIG_SAMPLES_PER_FRAME=${FRAME_SIZE} -DMIC_ARRAY_CONFIG_MIC_COUNT=${N_MICS} @@ -113,4 +124,3 @@ foreach(target ${APP_BUILD_TARGETS}) add_dependencies(${target} gen_custom_filter) endif() endforeach() - diff --git a/tests/signal/BasicMicArray/src/app.cpp b/tests/signal/BasicMicArray/src/app.c similarity index 69% rename from tests/signal/BasicMicArray/src/app.cpp rename to tests/signal/BasicMicArray/src/app.c index d8b8aeda..255b067d 100644 --- a/tests/signal/BasicMicArray/src/app.cpp +++ b/tests/signal/BasicMicArray/src/app.c @@ -1,30 +1,45 @@ // 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 +#include #include -#include #include - +#include +#include +#include #include #include #include - -extern "C" { -#include "xscope.h" -} +#include #include "mic_array.h" +#include "app_config.h" -#include "app.h" #if USE_CUSTOM_FILTER #include "custom_filter.h" #endif +#ifndef META_OUT +#define META_OUT (0) +#endif + +#ifndef DATA_OUT +#define DATA_OUT (1) +#endif + +#define BUFF_SIZE (256) + +typedef chanend_t streaming_chanend_t; + +DECLARE_JOB(app_output_task, (chanend_t, chanend_t)); +DECLARE_JOB(app_fifo_to_xscope_task, (chanend_t)); +DECLARE_JOB(app_mic, (chanend_t, chanend_t)); +DECLARE_JOB(host_words_to_app, (chanend_t, streaming_chanend_t)); + + typedef struct { unsigned stg1_tap_count; unsigned stg1_decimation_factor; @@ -39,14 +54,24 @@ typedef struct { int32_t *stg3_coef_ptr; }filt_config_t; -static void get_filter_config(unsigned fs, filt_config_t *cfg) { +// ------------------------------- HELPER FUNCTIONS ------------------------------- + +static inline +void hwtimer_delay_microseconds(unsigned delay) { + hwtimer_t tmr = hwtimer_alloc(); + hwtimer_delay(tmr, delay * XS1_TIMER_MHZ); + hwtimer_free(tmr); +} + +static +void get_filter_config(unsigned fs, filt_config_t *cfg) { #if !USE_CUSTOM_FILTER cfg->stg1_tap_count = 256; cfg->stg1_decimation_factor = 32; cfg->stg3_tap_count = 0; cfg->stg3_decimation_factor = 1; // set as 1 to not mess up blocksize calculation at the host end for 2 stage decimator - cfg->stg3_coef_ptr = nullptr; + cfg->stg3_coef_ptr = NULL; cfg->stg3_shr = 0; if(fs == 16000) { cfg->stg2_tap_count = STAGE2_TAP_COUNT; @@ -91,7 +116,102 @@ static void get_filter_config(unsigned fs, filt_config_t *cfg) { #endif } -extern void _mic_array_override_pdm_port(chanend_t c_pdm); +static inline +void app_print_filters() +{ + filt_config_t filt_cfg; + get_filter_config(APP_SAMP_FREQ, &filt_cfg); + + unsigned stg1_tap_words = filt_cfg.stg1_tap_count / 2; + + + printf("stage1 filter length: %d 16bit coefs -> %d 32b words\n", stg1_tap_words*2, stg1_tap_words); + int initial_list = stg1_tap_words/4; + printf("stage1_coef = [\n"); + for(int a = 0; a < initial_list; a++){ + printf("0x%08X, 0x%08X, 0x%08X, 0x%08X, \n", + filt_cfg.stg1_coef_ptr[a*4+0], filt_cfg.stg1_coef_ptr[a*4+1], + filt_cfg.stg1_coef_ptr[a*4+2], filt_cfg.stg1_coef_ptr[a*4+3]); + } + for(int a = initial_list*4; a < stg1_tap_words; a++){ + printf("0x%08X, ", filt_cfg.stg1_coef_ptr[a]); + } + printf("]\n"); + + printf("stage2 filter length: %d\n", filt_cfg.stg2_tap_count); + printf("stage2_coef = [\n"); + initial_list = filt_cfg.stg2_tap_count/4; + for(int a = 0; a < initial_list; a++){ + printf("0x%08X, 0x%08X, 0x%08X, 0x%08X, \n", + filt_cfg.stg2_coef_ptr[4*a+0], filt_cfg.stg2_coef_ptr[4*a+1], + filt_cfg.stg2_coef_ptr[4*a+2], filt_cfg.stg2_coef_ptr[4*a+3]); + } + for(int a = initial_list*4; a < filt_cfg.stg2_tap_count; a++){ + printf("0x%08X, ", filt_cfg.stg2_coef_ptr[a]); + } + printf("]\n"); + + printf("stage2_shr = %d\n", filt_cfg.stg2_shr); +} + +static inline +void cmd_print_msg(unsigned i){ + const char* msg[4] = { + "[CMD] Resume data loop.\n", + "[CMD] Terminate application.\n", + "[CMD] Print Filters.\n", + "[CMD] Unknown command.\n" + }; + printf("%s\n", msg[i]); +} + +static inline +void cmd_perform_action(unsigned cmd){ + switch(cmd){ + case 0: break; + case 1: exit(0); break; + case 2: app_print_filters(); break; + default: assert(0); break; + } +} + +static inline +void cmd_loop(chanend_t c_from_host) +{ + char cmd_buff[BUFF_SIZE]; + int pp; + SELECT_RES( + CASE_THEN(c_from_host, c_from_host_handler) + ) + c_from_host_handler:{ + xscope_data_from_host(c_from_host, &cmd_buff[0], &pp); + assert((pp-1) == 4); + uint32_t cmd = ((uint32_t*)(void*) &cmd_buff[0])[0]; + cmd_print_msg(cmd); + cmd_perform_action(cmd); + continue; + } +} + +static inline +int send_words_to_app(streaming_chanend_t c_to_app, char* buff, int buff_lvl) +{ + int* next_word = (int*)(void*) &buff[0]; + while(buff_lvl >= sizeof(int)){ + s_chan_out_word(c_to_app, next_word[0]); + next_word++; + buff_lvl -= sizeof(int); + hwtimer_delay_microseconds(15); + } + if(buff_lvl) + { + memmove(&buff[0], &next_word[0], buff_lvl); + } + return buff_lvl; +} + + +extern void _mic_array_override_pdm_port_c(chanend_t c_pdm); // We'll be using a fairly standard mic array setup here, with one big // exception. Instead of feeding the PDM rx service with a port, we're going @@ -110,15 +230,15 @@ pdm_rx_resources_t pdm_res = PDM_RX_RESOURCES_SDR( XS1_CLKBLK_1); #if USE_CUSTOM_FILTER -void init_mic_conf(mic_array_conf_t &mic_array_conf, mic_array_filter_conf_t (&filter_conf)[NUM_DECIMATION_STAGES], unsigned *channel_map) +static void init_mic_conf(mic_array_conf_t *mic_array_conf, mic_array_filter_conf_t filter_conf[NUM_DECIMATION_STAGES], unsigned *channel_map) { static int32_t stg1_filter_state[MIC_ARRAY_CONFIG_MIC_COUNT][8]; static int32_t stg2_filter_state[MIC_ARRAY_CONFIG_MIC_COUNT][CUSTOM_FILTER_STG2_TAP_COUNT]; - memset(&mic_array_conf, 0, sizeof(mic_array_conf_t)); + memset(mic_array_conf, 0, sizeof(mic_array_conf_t)); //decimator - mic_array_conf.decimator_conf.filter_conf = &filter_conf[0]; - mic_array_conf.decimator_conf.num_filter_stages = NUM_DECIMATION_STAGES; + mic_array_conf->decimator_conf.filter_conf = &filter_conf[0]; + mic_array_conf->decimator_conf.num_filter_stages = NUM_DECIMATION_STAGES; // stage 1 filter_conf[0].coef = (int32_t*)custom_filter_stg1_coef; filter_conf[0].num_taps = CUSTOM_FILTER_STG1_TAP_COUNT; @@ -148,13 +268,16 @@ void init_mic_conf(mic_array_conf_t &mic_array_conf, mic_array_filter_conf_t (&f // pdm rx static uint32_t pdmrx_out_block[MIC_ARRAY_CONFIG_MIC_COUNT][CUSTOM_FILTER_STG2_DECIMATION_FACTOR * CUSTOM_FILTER_STG3_DECIMATION_FACTOR]; static uint32_t __attribute__((aligned(8))) pdmrx_out_block_double_buf[2][MIC_ARRAY_CONFIG_MIC_COUNT * CUSTOM_FILTER_STG2_DECIMATION_FACTOR * CUSTOM_FILTER_STG3_DECIMATION_FACTOR]; - mic_array_conf.pdmrx_conf.pdm_out_words_per_channel = CUSTOM_FILTER_STG2_DECIMATION_FACTOR * CUSTOM_FILTER_STG3_DECIMATION_FACTOR; - mic_array_conf.pdmrx_conf.pdm_out_block = (uint32_t*)pdmrx_out_block; - mic_array_conf.pdmrx_conf.pdm_in_double_buf = (uint32_t*)pdmrx_out_block_double_buf; - mic_array_conf.pdmrx_conf.channel_map = channel_map; + mic_array_conf->pdmrx_conf.pdm_out_words_per_channel = CUSTOM_FILTER_STG2_DECIMATION_FACTOR * CUSTOM_FILTER_STG3_DECIMATION_FACTOR; + mic_array_conf->pdmrx_conf.pdm_out_block = (uint32_t*)pdmrx_out_block; + mic_array_conf->pdmrx_conf.pdm_in_double_buf = (uint32_t*)pdmrx_out_block_double_buf; + mic_array_conf->pdmrx_conf.channel_map = channel_map; } #endif + +// ------------------------------- THREADS ------------------------------- + void app_mic( chanend_t c_pdm_in, chanend_t c_frames_out) //non-streaming @@ -164,10 +287,10 @@ void app_mic( #else mic_array_conf_t mic_array_conf; mic_array_filter_conf_t filter_conf[NUM_DECIMATION_STAGES]; - init_mic_conf(mic_array_conf, filter_conf, NULL); + init_mic_conf(&mic_array_conf, filter_conf, NULL); mic_array_init_custom_filter(&pdm_res, &mic_array_conf); #endif - _mic_array_override_pdm_port((port_t)c_pdm_in); // get pdm input from channel instead of port. + _mic_array_override_pdm_port_c((port_t)c_pdm_in); // get pdm input from channel instead of port. // mic_array_init() calls mic_array_resources_configure which would crash // if a chanend were to be passed instead of a port for the pdm data port, so // this overriding has to be done only after calling mic_array_init() @@ -219,6 +342,7 @@ void app_output_task(chanend_t c_frames_in, chanend_t c_fifo) } } + void app_fifo_to_xscope_task(chanend_t c_fifo) { while(1){ @@ -235,40 +359,46 @@ void app_fifo_to_xscope_task(chanend_t c_fifo) } - -void app_print_filters() +void host_words_to_app(chanend_t c_from_host, streaming_chanend_t c_to_app) { - filt_config_t filt_cfg; - get_filter_config(APP_SAMP_FREQ, &filt_cfg); - - unsigned stg1_tap_words = filt_cfg.stg1_tap_count / 2; - - - printf("stage1 filter length: %d 16bit coefs -> %d 32b words\n", stg1_tap_words*2, stg1_tap_words); - int initial_list = stg1_tap_words/4; - printf("stage1_coef = [\n"); - for(int a = 0; a < initial_list; a++){ - printf("0x%08X, 0x%08X, 0x%08X, 0x%08X, \n", - filt_cfg.stg1_coef_ptr[a*4+0], filt_cfg.stg1_coef_ptr[a*4+1], - filt_cfg.stg1_coef_ptr[a*4+2], filt_cfg.stg1_coef_ptr[a*4+3]); - } - for(int a = initial_list*4; a < stg1_tap_words; a++){ - printf("0x%08X, ", filt_cfg.stg1_coef_ptr[a]); - } - printf("]\n"); + xscope_connect_data_from_host(c_from_host); + + char buff[100*BUFF_SIZE+3]; + int buff_lvl = 0; + int dd = 0; + + SELECT_RES( + CASE_THEN(c_from_host, c_from_host_handler) + ){ + c_from_host_handler:{ + xscope_data_from_host(c_from_host, &buff[0], &dd); + dd--; + buff_lvl += dd; + if(dd == 0) { + cmd_loop(c_from_host); + } + else { + buff_lvl = send_words_to_app(c_to_app, buff, buff_lvl); + } + continue; + }} +} - printf("stage2 filter length: %d\n", filt_cfg.stg2_tap_count); - printf("stage2_coef = [\n"); - initial_list = filt_cfg.stg2_tap_count/4; - for(int a = 0; a < initial_list; a++){ - printf("0x%08X, 0x%08X, 0x%08X, 0x%08X, \n", - filt_cfg.stg2_coef_ptr[4*a+0], filt_cfg.stg2_coef_ptr[4*a+1], - filt_cfg.stg2_coef_ptr[4*a+2], filt_cfg.stg2_coef_ptr[4*a+3]); - } - for(int a = initial_list*4; a < filt_cfg.stg2_tap_count; a++){ - printf("0x%08X, ", filt_cfg.stg2_coef_ptr[a]); - } - printf("]\n"); - printf("stage2_shr = %d\n", filt_cfg.stg2_shr); +int main(){ + channel_t c_frames = chan_alloc(); + channel_t c_fifo = chan_alloc(); + chanend_t xscope_chan = chanend_alloc(); // tools will start the other channel end + streaming_channel_t c_to_app = s_chan_alloc(); + + PAR_JOBS( + PJOB(app_mic, (c_to_app.end_a, c_frames.end_a)), + PJOB(app_output_task, (c_frames.end_b, c_fifo.end_a)), + PJOB(app_fifo_to_xscope_task, (c_fifo.end_b)), + PJOB(host_words_to_app, (xscope_chan, c_to_app.end_b)) + ); + s_chan_free(c_to_app); + chan_free(c_frames); + chan_free(c_fifo); + return 0; } diff --git a/tests/signal/BasicMicArray/src/app.h b/tests/signal/BasicMicArray/src/app.h deleted file mode 100644 index c1c45572..00000000 --- a/tests/signal/BasicMicArray/src/app.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2022-2026 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -#pragma once - -#include "mic_array.h" - -C_API_START - -// Thread that runs the decimator -MA_C_API -void app_mic(chanend_t c_pdm_in, chanend_t c_frames_out); - -// Thread that receives samples and pushes them into a FIFO -MA_C_API -void app_output_task(chanend_t c_frames_in, chanend_t c_fifo); - -// Thread that sends FIFO results back to host -MA_C_API -void app_fifo_to_xscope_task(chanend_t c_fifo); - -// Print filters (for debug purposes) -MA_C_API -void app_print_filters(); - -C_API_END diff --git a/tests/signal/BasicMicArray/src/app_config.h b/tests/signal/BasicMicArray/src/app_config.h new file mode 100644 index 00000000..9d0e7644 --- /dev/null +++ b/tests/signal/BasicMicArray/src/app_config.h @@ -0,0 +1,22 @@ +// Copyright 2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#if defined(__VX4B__) + +#include + +#ifndef PORT_MCLK_IN +#define PORT_MCLK_IN XS1_PORT_1D +#endif + +#ifndef PORT_PDM_CLK +#define PORT_PDM_CLK XS1_PORT_1G +#endif + +#ifndef PORT_PDM_DATA +#define PORT_PDM_DATA XS1_PORT_1F +#endif + +#endif diff --git a/tests/signal/BasicMicArray/src/main.xc b/tests/signal/BasicMicArray/src/main.xc deleted file mode 100644 index 0141041d..00000000 --- a/tests/signal/BasicMicArray/src/main.xc +++ /dev/null @@ -1,162 +0,0 @@ -// 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 "app.h" - -unsafe { - - -// We cannot be guaranteed to read less than this, and we cannot read more than -// this -#define BUFF_SIZE 256 - -void host_words_to_app( - chanend c_from_host, - streaming chanend c_to_app) -{ - xscope_connect_data_from_host(c_from_host); - - // +3 is for any partial word at the end of the read. It will get moved to the - // front and we'll read again, which could be another BUFF_SIZE bytes - char buff[100*BUFF_SIZE+3]; - int buff_lvl = 0; - - /* - The host app can attempt to send [0, 255] bytes on a write attempt. The - xscope logic always adds a null byte at the end, so we expect to receive - [1,256] bytes here. We ignore the null byte at the end, and just consider - the other bytes (i.e. we pretend we received one fewer byte). Then here's - the basic protocol: - - - Loop forever: - - Receive N bytes of data. (after subtracting 1) - - if N > 0: send all whole words to app and loop back. - - if N == 0: - - Enter inner (command) loop: - - Listen for 1 word (4 bytes) from host: - - assert() 4 bytes were received - - Interpret command based on received word value: - - CMD 0: break from command loop back to data loop - - CMD 1: terminate application - - The reason for the command loop is so that the application can terminate - gracefully. This design should also make it relatively easy to add - additional commands if need be. - */ - while(1){ - int dd; - select { - case xscope_data_from_host(c_from_host, &buff[0], dd): - { - dd--; // last byte is always 0 (for some reason) - buff_lvl += dd; - - if(dd == 0){ - // Enter command loop - - // use a separate buffer in case there is a fractional word in the - // data buffer. - char cmd_buff[BUFF_SIZE]; - int cmd_loop = 1; - while(cmd_loop){ - int pp; - select { - case xscope_data_from_host(c_from_host, &cmd_buff[0], pp): - { - assert( (pp-1) == 4 ); - uint32_t cmd = ((uint32_t*)(void*) &cmd_buff[0])[0]; - if(cmd == 0){ - // Break back to data loop - printf("[CMD] Resume data loop.\n"); - cmd_loop = 0; - } else if (cmd == 1){ - // Terminate application - printf("[CMD] Terminate application.\n"); - exit(0); - } else if (cmd == 2){ - // Print filters - printf("[CMD] Print Filters.\n"); - app_print_filters(); - cmd_loop = 0; - } else { - // Unknown command. - printf("[CMD] Unknown command.\n"); - assert(0); - } - break; - } - } - } - - } else { - - // Send all (complete) words to app - int* next_word = ((int*) (void*) &buff[0]); - while(buff_lvl >= sizeof(int)){ - c_to_app <: next_word[0]; - next_word++; - buff_lvl -= sizeof(int); - // We have to be careful about how quickly we push data to the PDM - // rx service. Sending too quickly can mess stuff up or (if PDM rx - // is running in ISR mode) cause a deadlock. This is unfortunate, - // but is only because we're pretending a channel is a port. - // Ultimately, we just need to avoid sending PDM data faster than - // the decimator thread can process the data. Unfortunately, because - // frames are being used, we don't actually know when it's done - // processing a given block of data. So, we'll just insert a - // reasonable delay here. - - // With a 3.072 MHz PDM clock and 1 microphone, we expect to have - // port reads at a rate of 3.072 MHz / 32 = 96 kHz. That rate is - // linear in the number of channels, but we're also always safe if - // we go slower than we need to here. (96 kHz)^(-1) = 10.41 us - delay_microseconds(15); - } - - // if there's 1-3 bytes left move it to the front. - if(buff_lvl) memmove(&buff[0], &next_word[0], buff_lvl); - } - break; - } - } - - // repeat forever - } -} - - -int main() -{ - chan c_from_host; - streaming chan c_to_app; - chan c_frames; - chan c_fifo; - - par { - xscope_host_data(c_from_host); - - on tile[0]: { - host_words_to_app(c_from_host, c_to_app); - } - - on tile[0]: { - xscope_mode_lossless(); - - par { - app_mic((chanend_t) c_to_app, (chanend_t) c_frames); - app_output_task((chanend_t) c_frames, (chanend_t)c_fifo); - app_fifo_to_xscope_task((chanend_t)c_fifo); - } - } - } - return 0; -} - -} diff --git a/tests/signal/BasicMicArray/test_params.json b/tests/signal/BasicMicArray/test_params.json index cdca885c..33755163 100644 --- a/tests/signal/BasicMicArray/test_params.json +++ b/tests/signal/BasicMicArray/test_params.json @@ -1,6 +1,6 @@ { - "N_MICS": [1, 2, 4, 8], - "FRAME_SIZE": [1, 16], - "USE_ISR": [0, 1], - "SAMP_FREQ": [16000, 32000, 48000, "good_3_stage_filter_int.pkl"] -} + "N_MICS": [1], + "FRAME_SIZE": [16], + "USE_ISR": [0], + "SAMP_FREQ": [16000] +} \ No newline at end of file diff --git a/tests/signal/profile/app_memory/src/app.cpp b/tests/signal/profile/app_memory/src/app.cpp index 77c7e465..69df901a 100644 --- a/tests/signal/profile/app_memory/src/app.cpp +++ b/tests/signal/profile/app_memory/src/app.cpp @@ -50,8 +50,12 @@ pdm_rx_resources_t pdm_res = PDM_RX_RESOURCES_DDR( #ifndef APP_N_MICS_IN #define APP_N_MICS_IN APP_N_MICS #endif -#define CLRSR(c) asm volatile("clrsr %0" : : "n"(c)); -#define CLEAR_KEDI() CLRSR(XS1_SR_KEDI_MASK) + +#if defined(__XS3A__) +#define CLEAR_KEDI() asm volatile("clrsr %0" : : "n"(XS1_SR_KEDI_MASK)); +#else +#define CLEAR_KEDI() ((void)0) // not defined in !xs3a +#endif using TMicArray = mic_array::MicArray, @@ -135,4 +139,3 @@ void app_mic_array_task(chanend_t c_frames_out) #endif } #endif - diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 557f0416..9352cc9a 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -4,10 +4,20 @@ include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) project(tests-unit) set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) -set(APP_HW_TARGET XK-EVK-XU316) set(APP_INCLUDES src) -set(APP_DEPENDENT_MODULES "lib_mic_array" "lib_unity(2.5.2)") -set(APP_COMPILER_FLAGS -O2 +set(APP_DEPENDENT_MODULES "lib_mic_array" "lib_unity(main)") #TODO release lib_unity + +# conditional depending on target +if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL "3.6.0") + set(__XS3__ ON) # XS3 (XTC 15.3.1) +else() + set(__XS3__ OFF) # VX4 +endif() + +# Target specific compiler flags +if(__XS3__) # xs3 + set(APP_HW_TARGET XK-EVK-XU316) + set(APP_COMPILER_FLAGS -O2 -g -report -mcmodel=large @@ -17,5 +27,13 @@ set(APP_COMPILER_FLAGS -O2 -Wno-format -fxscope -DUNITY_INCLUDE_CONFIG_H=1) +else() # vx4 + set(APP_HW_TARGET XK-EVK-XU416) + set(APP_COMPILER_FLAGS + -Os + -g + -Wno-fptrgroup + -DUNITY_INCLUDE_CONFIG_H=1) +endif() XMOS_REGISTER_APP() diff --git a/tests/unit/src/main.c b/tests/unit/src/main.c index 88172bff..849766a9 100644 --- a/tests/unit/src/main.c +++ b/tests/unit/src/main.c @@ -8,7 +8,6 @@ int main(int argc, const char* argv[]) { - xscope_config_io(XSCOPE_IO_BASIC); UnityGetCommandLineOptions(argc, argv); UnityBegin(argv[0]); @@ -28,8 +27,8 @@ int main(int argc, const char* argv[]) RUN_TEST_GROUP(deinterleave4); RUN_TEST_GROUP(deinterleave8); RUN_TEST_GROUP(deinterleave16); - RUN_TEST_GROUP(deinterleave_pdm_samples); - + RUN_TEST_GROUP(fir_1x16_bit); + return UNITY_END(); } diff --git a/tests/unit/src/test_fir_1x16_bit.c b/tests/unit/src/test_fir_1x16_bit.c new file mode 100644 index 00000000..3916646c --- /dev/null +++ b/tests/unit/src/test_fir_1x16_bit.c @@ -0,0 +1,102 @@ +// Copyright 2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include +#include + +#include + +#include "unity.h" +#include "unity_fixture.h" + +#include "mic_array/etc/fir_1x16_bit.h" +#include "mic_array/etc/filters_default.h" + +TEST_GROUP_RUNNER(fir_1x16_bit) { + RUN_TEST_CASE(fir_1x16_bit, symmetry_test); + RUN_TEST_CASE(fir_1x16_bit, single_val); + RUN_TEST_CASE(fir_1x16_bit, random_test); +} + +TEST_GROUP(fir_1x16_bit); +TEST_SETUP(fir_1x16_bit) {} +TEST_TEAR_DOWN(fir_1x16_bit) {} + +// Test that opposite signals produce opposite results +TEST(fir_1x16_bit, symmetry_test) +{ + uint32_t signal_pos[1024]; + uint32_t signal_neg[1024]; + + // Using real stage 1 coefficients + extern uint32_t stage1_coef[STAGE1_WORDS]; + + memset(signal_pos, 0x00, sizeof(signal_pos)); // All +1 + memset(signal_neg, 0xFF, sizeof(signal_neg)); // All -1 + + int result_pos = fir_1x16_bit(signal_pos, stage1_coef); + int result_neg = fir_1x16_bit(signal_neg, stage1_coef); + + // Opposite signals should give opposite results + TEST_ASSERT_EQUAL_INT(-result_pos, result_neg); +} + +// Test zero signal with known inputs/outputs +TEST(fir_1x16_bit, single_val) +{ + const int expected_result = 268435456; + const unsigned max_cycles = 35; + + unsigned elapsed = 0; + int result = -1; + uint32_t signal[1024]; + memset(signal, 0, sizeof(signal)); + + elapsed = get_reference_time(); + result = fir_1x16_bit(signal, stage1_coef); + elapsed = get_reference_time() - elapsed; + + TEST_ASSERT_EQUAL_INT(expected_result, result); + TEST_ASSERT_LESS_OR_EQUAL(max_cycles, elapsed); +} + +TEST(fir_1x16_bit, random_test) +{ + #define n_vpu 16 + #define sig_len (n_vpu * 20) + #define PRINT_OUT (1) + + const int sig_exp[n_vpu] = { + -58529792,34287616,70240256,17392640,52816384, + -51980800,54905856,40349696,-60945408,14667776, + -3800064,33825280,-1670656,879616,-23246848,-11620864, + }; + + uint32_t sig_in[sig_len] = {0}; + int sig_out[n_vpu] = {0}; + + // seed + srand(12345); + for (unsigned i = 0; i < sig_len; i++) + { + sig_in[i] = rand() & 0xFFFFFFFF; // Random 32-bit word + } + + // Using real stage 1 coefficients + for (unsigned i = 0; i < n_vpu; i++) + { + uint32_t *sig_ptr = &sig_in[i * 20]; // 20 words per VPU block + sig_out[i] = fir_1x16_bit(sig_ptr, stage1_coef); + } + + #if PRINT_OUT + printf("\nExpected vs Actual:\n"); + for (unsigned i = 0; i < n_vpu; i++) + { + printf("sig_out[%u] = %d, sig_exp = %d\n", i, sig_out[i], sig_exp[i]); + } + #endif + + TEST_ASSERT_EQUAL_INT_ARRAY(sig_exp, sig_out, n_vpu); +}