From 989d6a9ccc76bce86b8ae9851b04021344895b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 19 Mar 2026 10:46:14 +0000 Subject: [PATCH 01/17] Translate mic_array core assembly files to VX4 --- lib_mic_array/lib_build_info.cmake | 2 +- lib_mic_array/src/deinterleave16.S | 126 ++++++++++++++++++++++++++++- lib_mic_array/src/deinterleave2.S | 25 ++++++ lib_mic_array/src/deinterleave4.S | 46 +++++++++++ lib_mic_array/src/deinterleave8.S | 70 ++++++++++++++++ lib_mic_array/src/fir_1x16_bit.S | 71 ++++++++++++++++ lib_mic_array/src/pdm_rx_isr.S | 122 +++++++++++++++++++++++++++- 7 files changed, 459 insertions(+), 3 deletions(-) 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..e94a4344 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(__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 // __VX4B__ diff --git a/lib_mic_array/src/deinterleave2.S b/lib_mic_array/src/deinterleave2.S index 6c08f352..778aef84 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(__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 // __VX4B__ diff --git a/lib_mic_array/src/deinterleave4.S b/lib_mic_array/src/deinterleave4.S index 0d383e9c..27b42c15 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(__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 // __VX4B__ diff --git a/lib_mic_array/src/deinterleave8.S b/lib_mic_array/src/deinterleave8.S index c3d6a955..f3e259b6 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(__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 // __VX4B__ diff --git a/lib_mic_array/src/fir_1x16_bit.S b/lib_mic_array/src/fir_1x16_bit.S index 576d4ef9..e11376f2 100644 --- a/lib_mic_array/src/fir_1x16_bit.S +++ b/lib_mic_array/src/fir_1x16_bit.S @@ -72,3 +72,74 @@ macc_coeffs: .cc_bottom fir_1x16_bit.func #endif // __XS3A__ + + +#if 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 + +.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" + +// 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 // __VX4B__ diff --git a/lib_mic_array/src/pdm_rx_isr.S b/lib_mic_array/src/pdm_rx_isr.S index 96c61243..938b37e9 100644 --- a/lib_mic_array/src/pdm_rx_isr.S +++ b/lib_mic_array/src/pdm_rx_isr.S @@ -105,7 +105,127 @@ pdm_rx_isr: kret .L_func_end: .cc_bottom pdm_rx_isr.function - .global pdm_rx_isr #endif //defined(__XS3A__) + +#if defined(__VX4B__) + +//TODO: This function is not verified. + +#define STRUCT_NAME pdm_rx_isr_context +#define FUNCTION_NAME pdm_rx_isr +#define NSTACK_WORDS 8 +#define NSTACK_BYTES (NSTACK_WORDS*4) + +.section .data.STRUCT_NAME, "aw" +.p2align 1 +.globl STRUCT_NAME +STRUCT_NAME: + .word 0 // .L_port + .word 0 // .L_buffA + .word 0 // .L_buffB + .word 0 // .L_phase1 + .word 0 // .L_phase1_reset + .word 0 // .L_c_out + .word 0 // .L_credit + .word -1 // .L_missed_blocks + +.section .text +.p2align 4 +.globl FUNCTION_NAME +.type FUNCTION_NAME,@function +FUNCTION_NAME: + unimp //TODO - this function is not verified and may contain errors, so prevent it from being accidentally used without review + addi sp, sp, -16 + sw s2, 0(sp) + sw s3, 4(sp) + sw s4, 8(sp) + sw s5, 12(sp) + + // Load base address of context + la s0, pdm_rx_isr_context + + // Read PDM port (offset 0) + lw s2, 0(s0) + xm.in s2, s2 + + // Store sample into active buffer + lw s5, 4(s0) // .L_buffA at offset 4 + lw s4, 12(s0) // .L_phase1 at offset 12 + xm.stw s2, s4(s5) + + // Emit buffer if phase reached zero + beqz s4, .L_emit + + // Decrement phase counter + addi s4, s4, -1 + sw s4, 12(s0) // Store back to .L_phase1 + + // Restore context and return + lw s2, 0(sp) + lw s3, 4(sp) + lw s4, 8(sp) + lw s5, 12(sp) + addi sp, sp, 16 + csrr t5, xm.smtval + csrw mtval, t5 + mret + +.L_emit: + // Reset phase counter + lw s2, 16(s0) // .L_phase1_reset at offset 16 + sw s2, 12(s0) // Store to .L_phase1 + + // Swap PDM buffers + lw s2, 8(s0) // .L_buffB at offset 8 + sw s2, 4(s0) // Store to .L_buffA + sw s5, 8(s0) // Store to .L_buffB + + // Check transmit credit + lw s2, 24(s0) // .L_credit at offset 24 + bnez s2, .L_has_credit + +.L_no_credit: + // Undo buffer swap + lw s2, 4(s0) // .L_buffA + sw s2, 8(s0) // .L_buffB + lw s2, 8(s0) // restored .L_buffB + sw s2, 4(s0) // .L_buffA + + // Increment missed block counter + lw s2, 28(s0) // .L_missed_blocks at offset 28 + xm.not s5, s2 + xm.assert s5 + addi s2, s2, 1 + sw s2, 28(s0) + tail .L_finish + +.L_has_credit: + // Consume credit and send buffer + addi s2, s2, -1 + sw s2, 24(s0) // Store back to .L_credit + lw s2, 20(s0) // .L_c_out at offset 20 + xm.out s2, s5 + +.L_finish: + // Restore context and exit ISR + lw s2, 0(sp) + lw s3, 4(sp) + lw s4, 8(sp) + lw s5, 12(sp) + addi sp, sp, 16 + csrr t5, xm.smtval + csrw mtval, t5 + mret + +.L_func_end: + +.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 // __VX4B__ From bb5d740a689ccbc8a4d63821631c4365c195dc30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 19 Mar 2026 10:46:33 +0000 Subject: [PATCH 02/17] Add VX4 architecture support for examples --- examples/CMakeLists.txt | 1 + examples/app_custom_filter/src/main.xc | 2 + examples/app_mic_array_basic/CMakeLists.txt | 42 +++++ examples/app_mic_array_basic/README.md | 30 ++++ examples/app_mic_array_basic/convert.py | 27 ++++ examples/app_mic_array_basic/src/app.c | 151 ++++++++++++++++++ examples/app_mic_array_basic/src/app_config.h | 32 ++++ .../app_mic_array_basic/src/config.xscope | 18 +++ .../app_mic_array_basic/src/device_pll_ctrl.h | 6 + .../src/small_768k_to_12k_filter.h | 59 +++++++ .../app_mic_array_basic/vx4/device_pll_ctrl.c | 84 ++++++++++ examples/app_mic_array_basic/vx4/mapfile.c | 11 ++ .../xs3/XK-EVK-XU316-AIV.xn | 66 ++++++++ .../app_mic_array_basic/xs3/device_pll_ctrl.c | 35 ++++ examples/app_mic_array_basic/xs3/mapfile.xc | 25 +++ examples/app_par_decimator/src/app.cpp | 9 +- .../app_par_decimator/src/decimator_subtask.c | 9 +- 17 files changed, 603 insertions(+), 4 deletions(-) create mode 100644 examples/app_mic_array_basic/CMakeLists.txt create mode 100644 examples/app_mic_array_basic/README.md create mode 100644 examples/app_mic_array_basic/convert.py create mode 100644 examples/app_mic_array_basic/src/app.c create mode 100644 examples/app_mic_array_basic/src/app_config.h create mode 100644 examples/app_mic_array_basic/src/config.xscope create mode 100644 examples/app_mic_array_basic/src/device_pll_ctrl.h create mode 100644 examples/app_mic_array_basic/src/small_768k_to_12k_filter.h create mode 100644 examples/app_mic_array_basic/vx4/device_pll_ctrl.c create mode 100644 examples/app_mic_array_basic/vx4/mapfile.c create mode 100644 examples/app_mic_array_basic/xs3/XK-EVK-XU316-AIV.xn create mode 100644 examples/app_mic_array_basic/xs3/device_pll_ctrl.c create mode 100644 examples/app_mic_array_basic/xs3/mapfile.xc 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_custom_filter/src/main.xc b/examples/app_custom_filter/src/main.xc index 62f9fa7d..85584763 100644 --- a/examples/app_custom_filter/src/main.xc +++ b/examples/app_custom_filter/src/main.xc @@ -72,6 +72,8 @@ void init_mic_conf(mic_array_conf_t &mic_array_conf, mic_array_filter_conf_t (&f 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.num_channels_in = APP_MIC_COUNT; + mic_array_conf.pdmrx_conf.num_channels_out = APP_MIC_COUNT; } int main() { diff --git a/examples/app_mic_array_basic/CMakeLists.txt b/examples/app_mic_array_basic/CMakeLists.txt new file mode 100644 index 00000000..0b35ca79 --- /dev/null +++ b/examples/app_mic_array_basic/CMakeLists.txt @@ -0,0 +1,42 @@ +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}/../../..) + +# conditional depending on target +set(APP_C_SRCS src/app.c) + +if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL "3.6.0") + set(APP_HW_TARGET xs3/XK-EVK-XU316-AIV.xn) + set(APP_INCLUDES src xs3) + list(APPEND APP_C_SRCS + xs3/device_pll_ctrl.c + ) + list(APPEND APP_XC_SRCS + xs3/mapfile.xc + ) +else() + set(APP_HW_TARGET XK-EVK-XU416) + set(APP_INCLUDES src vx4) + list(APPEND APP_C_SRCS + vx4/device_pll_ctrl.c + vx4/mapfile.c + ) +endif() + +set(APP_DEPENDENT_MODULES "lib_mic_array") + +set(APP_COMPILER_FLAGS + -Os + -g + -report + -Wall + -fxscope + # Mic array config + -DMIC_ARRAY_CONFIG_SAMPLES_PER_FRAME=320 + -DMIC_ARRAY_CONFIG_MIC_COUNT=1 + -DMIC_ARRAY_CONFIG_USE_PDM_ISR=0 +) + +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..3c305b91 --- /dev/null +++ b/examples/app_mic_array_basic/README.md @@ -0,0 +1,30 @@ +# Basic Mic Array Example + +## Hardware Required + +- **XMS0016** + +## 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..d415dd23 --- /dev/null +++ b/examples/app_mic_array_basic/convert.py @@ -0,0 +1,27 @@ +# 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 + + +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=12000, + bits_per_sample=32 + ) 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..492be761 --- /dev/null +++ b/examples/app_mic_array_basic/src/app.c @@ -0,0 +1,151 @@ +// 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 + +#include "app_config.h" +#include "mic_array.h" +#include "device_pll_ctrl.h" +#include "small_768k_to_12k_filter.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; + mic_array_conf->pdmrx_conf.num_channels_in = APP_MIC_COUNT; + mic_array_conf->pdmrx_conf.num_channels_out = APP_MIC_COUNT; +} + +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) +{ + static int32_t WORD_ALIGNED tmp_buff[APP_BUFF_SIZE] = {0}; + int32_t *buff_ptr = &tmp_buff[0]; + unsigned frame_counter = APP_N_FRAMES; + + hwtimer_t tmr = hwtimer_alloc(); + unsigned t0 = 0, t1 = 0; + unsigned t2 = 0, t3 = 0; + uint64_t num = 0; + uint64_t den = 0; + + printf("mic start\n"); + t2 = hwtimer_get_time(tmr); + while (frame_counter--) + { + t0 = hwtimer_get_time(tmr); + ma_frame_rx(buff_ptr, (chanend_t)c_mic_audio, MIC_ARRAY_CONFIG_MIC_COUNT, APP_N_SAMPLES); + buff_ptr += APP_N_SAMPLES; + t1 = hwtimer_get_time(tmr); + num += (t1 - t0); + den += 1; + } + t3 = hwtimer_get_time(tmr); + printf("mic end\n"); + + // Profile the average time taken per frame + const float ma_expected = (float)(APP_N_SAMPLES) / (float)(APP_OUT_FREQ_HZ); + const float tilef = 600.0; + const float ref = tilef / (5.0 + 1.0); + + float avg = (float)num / (float)den; + float total = (float)(t3 - t2); + float avg_us = avg / ref; + float total_us = total / ref; + float ma_exp_us = ma_expected * 1e6; + float perc_err = ((avg_us - ma_exp_us) / ma_exp_us) * 100.0; + + printf("Tile freq: %.2f MHz\n", tilef); + printf("Reference freq: %.2f MHz\n", ref); + printf("ma_frame_rx avg: %.2f ticks\n", avg); + printf("ma_frame_rx avg: %.2f us\n", avg_us); + printf("ma_frame_rx expected: %.2f us\n", ma_exp_us); + printf("ma_frame_rx error: %.2f %%\n", perc_err); + printf("total ticks: %.2f\n", total); + printf("total us: %.2f us\n", total_us); + + // 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(); + xscope_mode_lossless(); + + // 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..c864943f --- /dev/null +++ b/examples/app_mic_array_basic/src/app_config.h @@ -0,0 +1,32 @@ +// Copyright 2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#if defined(__VX4B__) +#include +#define PORT_MCLK_IN VX_PORT_1D +#define PORT_PDM_CLK VX_PORT_1G +#define PORT_PDM_DATA VX_PORT_1F +#elif defined(__XS3A__) +#include +#define PORT_MCLK_IN XS1_PORT_1D +#define PORT_PDM_CLK PORT_MIC_CLK +#define PORT_PDM_DATA PORT_MIC_DATA +#endif + +// -------------------- 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 PORT_MCLK_IN /* X0D11, J14 - Pin 15, '11' */ +#define MIC_ARRAY_CONFIG_PORT_PDM_CLK PORT_PDM_CLK /* X0D00, J14 - Pin 2, '00' */ +#define MIC_ARRAY_CONFIG_PORT_PDM_DATA PORT_PDM_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 (MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME) +#define APP_OUT_FREQ_HZ (12000) // 12KHz +#define APP_SAMPLE_SECONDS (5) +#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.h b/examples/app_mic_array_basic/src/device_pll_ctrl.h new file mode 100644 index 00000000..ecc0c83e --- /dev/null +++ b/examples/app_mic_array_basic/src/device_pll_ctrl.h @@ -0,0 +1,6 @@ +// Copyright 2022-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +void device_pll_init(void); 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_mic_array_basic/vx4/device_pll_ctrl.c b/examples/app_mic_array_basic/vx4/device_pll_ctrl.c new file mode 100644 index 00000000..e9262b51 --- /dev/null +++ b/examples/app_mic_array_basic/vx4/device_pll_ctrl.c @@ -0,0 +1,84 @@ +// 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 "device_pll_ctrl.h" + +static +void delay_1ms(){ + hwtimer_t tmr = hwtimer_alloc(); + assert(tmr != 0); + hwtimer_delay(tmr, 100000); // 1ms with 100 MHz timer tick + hwtimer_free(tmr); +} + +/* + * PLL1 Control Register Fields: + * + * PLL1_R_DIVIDER - Input divisor value. + * PLL1_F_MULTIPLIER - Feedback multiplier value. + * PLL1_OD_DIVIDER - Output divider value. + * PLL1_DISABLE - Disable the PLL when this is 1. + * PLL1_BYPASS - When set to 1 the PLL will be bypassed. + * PLL1_NLOCK - If set to 1 the chip will not wait for the PLL to relock. + */ + +void device_pll_init(void) +{ + printf("Initializing PLL\n"); + xsystem_tile_id_t tileid = get_local_tile_id(); + + // [0] PLL CTL DISABLE + uint32_t DEVICE_PLL_DISABLE = 0x00000000; + DEVICE_PLL_DISABLE = VX_PLL1_DISABLE_SET(DEVICE_PLL_DISABLE, 0); + + // [1] Mux + uint32_t DEVICE_PLL_MUX_VAL = 0x00000000; + DEVICE_PLL_MUX_VAL = VX_APP_CLK1_MUX_BIT_SET(DEVICE_PLL_MUX_VAL, 1); + DEVICE_PLL_MUX_VAL = VX_APP_CLK_IN_PHASE_BIT_SET(DEVICE_PLL_MUX_VAL, 1); + + // [2] PLL CTL + uint32_t DEVICE_PLL_CTL_VAL = 0x00000000; + DEVICE_PLL_CTL_VAL = VX_PLL1_R_DIVIDER_SET(DEVICE_PLL_CTL_VAL, 0); // input divider: 24 MHz ref / R=1 -> 24 MHz + DEVICE_PLL_CTL_VAL = VX_PLL1_F_MULTIPLIER_SET(DEVICE_PLL_CTL_VAL, 101); // feedback mult: 24 MHz * (F + 1 + 2/5 = 102.4) -> 2457.60 MHz + DEVICE_PLL_CTL_VAL = VX_PLL1_OD_DIVIDER_SET(DEVICE_PLL_CTL_VAL, 4); // output divider: 2457.60 MHz / (OD + 1) / 2 -> 245.76 MHz + DEVICE_PLL_CTL_VAL = VX_PLL1_DISABLE_SET(DEVICE_PLL_CTL_VAL, 0); // disable PLL before configuration + DEVICE_PLL_CTL_VAL = VX_PLL1_BYPASS_SET(DEVICE_PLL_CTL_VAL, 0); // no bypass + DEVICE_PLL_CTL_VAL = VX_PLL1_NLOCK_SET(DEVICE_PLL_CTL_VAL, 1); // wait for PLL lock + + // [3] FRAC (2/5) + uint32_t DEVICE_PLL_FRAC_NOM = 0x00000000; + DEVICE_PLL_FRAC_NOM = VX_SS_FRAC_N_ENABLE_SET(DEVICE_PLL_FRAC_NOM, 1); // enable fractional mode + DEVICE_PLL_FRAC_NOM = VX_SS_FRAC_N_PERIOD_CYC_CNT_SET(DEVICE_PLL_FRAC_NOM, 4); // +1 -> 5 + DEVICE_PLL_FRAC_NOM = VX_SS_FRAC_N_F_HIGH_CYC_CNT_SET(DEVICE_PLL_FRAC_NOM, 1); // +1 -> 2 + + // [4] APP DIVIDER + uint32_t DEVICE_PLL_DIV_0 = 0x00000000; + DEVICE_PLL_DIV_0 = VX_APP_CLK_DIV_ENABLE_SET(DEVICE_PLL_DIV_0, 1); // enable app clock divider + DEVICE_PLL_DIV_0 = VX_APP_CLK_DIV_VALUE_SET(DEVICE_PLL_DIV_0, 4); // set divider to 4 -> 245.76 MHz / (4 + 1) / 2 -> 24.576 MHz + + // print reg values + printf("PLL Configuration:\n"); + printf("PLL DISABLE: 0x%08lX\n", DEVICE_PLL_DISABLE); + printf("PLL MUX VAL: 0x%08lX\n", DEVICE_PLL_MUX_VAL); + printf("PLL CTL VAL: 0x%08lX\n", DEVICE_PLL_CTL_VAL); + printf("PLL DIV VAL: 0x%08lX\n", DEVICE_PLL_DIV_0); + printf("PLL FRAC_NOM: 0x%08lX\n", DEVICE_PLL_FRAC_NOM); + + // CONFIGURE + sswitch_reg_try_write(tileid, VX_SSB_CSR_PLL1_CTRL_NUM, DEVICE_PLL_DISABLE); // disable PLL before configuration + sswitch_reg_try_write(tileid, VX_SSB_CSR_CLK_SWITCH_CTRL_NUM, DEVICE_PLL_MUX_VAL); // switch app clock to PLL1 output + sswitch_reg_try_write(tileid, VX_SSB_CSR_PLL1_CTRL_NUM, DEVICE_PLL_CTL_VAL); // configure PLL control register + sswitch_reg_try_write(tileid, VX_SSB_CSR_PLL1_FRACN_CTRL_NUM, DEVICE_PLL_FRAC_NOM); // configure PLL fractional control register + sswitch_reg_try_write(tileid, VX_SSB_CSR_APP_CLK1_DIV_NUM, DEVICE_PLL_DIV_0); // configure app clock divider + delay_1ms(); +} diff --git a/examples/app_mic_array_basic/vx4/mapfile.c b/examples/app_mic_array_basic/vx4/mapfile.c new file mode 100644 index 00000000..7066fc17 --- /dev/null +++ b/examples/app_mic_array_basic/vx4/mapfile.c @@ -0,0 +1,11 @@ +// Copyright 2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include + +extern void main_tile_0(); +extern void main_tile_1(); + +NETWORK_MAIN( + TILE_MAIN(main_tile_1, 1, ()), + TILE_MAIN(main_tile_0, 0, ()) +) diff --git a/examples/app_mic_array_basic/xs3/XK-EVK-XU316-AIV.xn b/examples/app_mic_array_basic/xs3/XK-EVK-XU316-AIV.xn new file mode 100644 index 00000000..b4eb8fff --- /dev/null +++ b/examples/app_mic_array_basic/xs3/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/xs3/device_pll_ctrl.c b/examples/app_mic_array_basic/xs3/device_pll_ctrl.c new file mode 100644 index 00000000..a50d04f8 --- /dev/null +++ b/examples/app_mic_array_basic/xs3/device_pll_ctrl.c @@ -0,0 +1,35 @@ +// 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" + + +#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) +{ + 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/xs3/mapfile.xc b/examples/app_mic_array_basic/xs3/mapfile.xc new file mode 100644 index 00000000..2c695375 --- /dev/null +++ b/examples/app_mic_array_basic/xs3/mapfile.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_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 } From 7f3054c8b66b08748852424c9977ac843689bb6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 19 Mar 2026 10:46:51 +0000 Subject: [PATCH 03/17] Add VX4 support to Jenkins --- .gitignore | 2 ++ Jenkinsfile | 83 ++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 65 insertions(+), 20 deletions(-) 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 f015d1bc..b26580aa 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,6 +1,6 @@ // This file relates to internal XMOS infrastructure and should be ignored by external users -@Library('xmos_jenkins_shared_library@v0.46.0') _ +@Library('xmos_jenkins_shared_library@v0.48.0') _ getApproval() pipeline { @@ -8,18 +8,23 @@ pipeline { parameters { string( - name: 'TOOLS_VERSION', + name: 'TOOLS_XS3_VERSION', defaultValue: '15.3.1', description: 'The XTC tools version' ) + string( + name: 'TOOLS_VX4_VERSION', + defaultValue: '-j --repo arch_vx_slipgate -b master -a XTC 116', + description: 'The XTC Slipgate tools version' + ) string( name: 'XMOSDOC_VERSION', defaultValue: 'v8.0.1', - description: 'The xmosdoc version') - + description: 'The xmosdoc version' + ) string( name: 'INFR_APPS_VERSION', - defaultValue: 'v3.3.0', + defaultValue: 'develop', //TODO pin after release description: 'The infr_apps version' ) choice( @@ -60,7 +65,7 @@ pipeline { stage('Examples build') { steps { dir("${REPO_NAME}/examples") { - xcoreBuild() + xcoreBuild(toolsVersion: params.TOOLS_XS3_VERSION) } } } @@ -106,7 +111,7 @@ pipeline { dir("tests") { createVenv(reqFile: "requirements.txt") withVenv { - xcoreBuild() + xcoreBuild(toolsVersion: params.TOOLS_XS3_VERSION) stash includes: '**/*.xe', name: 'test_bin', useDefaultExcludes: false } } @@ -124,7 +129,7 @@ pipeline { sh "git clone git@github.com:xmos/xmos_cmake_toolchain.git --branch v1.0.0" dir(REPO_NAME) { checkoutScmShallow() - withTools(params.TOOLS_VERSION) { + withTools(params.TOOLS_XS3_VERSION) { sh "cmake -B build.xcore -DDEV_LIB_MIC_ARRAY=1 -DCMAKE_TOOLCHAIN_FILE=../xmos_cmake_toolchain/xs3a.cmake" sh "cd build.xcore && make all -j 16" } @@ -136,13 +141,11 @@ pipeline { } } } // stage('Custom CMake build') - + stage('Tests') { parallel { - stage('XS3 tests') { - agent { - label 'xcore.ai' - } + stage('XS3 Tests') { + agent {label 'xcore.ai'} stages { stage("Checkout and Build") { steps { @@ -159,7 +162,7 @@ pipeline { stage('Run tests') { steps { dir("${REPO_NAME}/tests") { - withTools(params.TOOLS_VERSION) { + withTools(params.TOOLS_XS3_VERSION) { withVenv { // This ensures a project for XS2 can be built and runs OK @@ -183,7 +186,7 @@ pipeline { if(params.TEST_LEVEL == 'smoke') { echo "Running tests with fixed seed 12345" - sh "pytest -v --junitxml=pytest_basic_mic.xml --seed 12345 --level ${params.TEST_LEVEL} " + sh "pytest -v --junitxml=pytest_basic_mic.xml --seed 12345 --level ${params.TEST_LEVEL} -k 'not 16frame-8n'" } else { @@ -209,11 +212,51 @@ 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") { + createVenv(reqFile: "requirements.txt") + withVenv { + dir("unit") { + xcoreBuild(toolsVersion: params.TOOLS_VX4_VERSION) + } + dir ("signal/BasicMicArray") { + withTools(params.TOOLS_VX4_VERSION){ + xcoreBuild(toolsVersion: params.TOOLS_VX4_VERSION, jobs:8) + } + } + } // withVenv + } // dir("tests") + } // dir(REPO_NAME) + } // steps + } // stage("Checkout and Build") + stage('Run tests') { + steps { + dir("${REPO_NAME}/tests") { + withVenv { + dir("unit") { + withTools(params.TOOLS_VX4_VERSION) {sh "xrun --xscope bin/tests-unit.xe"} + } + dir("signal/BasicMicArray") { + withTools(params.TOOLS_VX4_VERSION) {sh 'python -m pytest --level nightly --seed 12345 -k "(0_isr or lowpower) and not 16frame-8n" -v'} // Skipping 16frame-8n. See https://github.com/xmos/lib_mic_array/issues/288 + } + } // withVenv + }}} // stage('Run tests') + } // stages + post { + cleanup {xcoreCleanSandbox()} + } //post + } // VX4 Tests + } // parallel } // stage('Tests') From d6f295f0a2fd6f031910adfb9dc4fd66800699a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 19 Mar 2026 10:47:15 +0000 Subject: [PATCH 04/17] Add VX4 support to tests/unit --- tests/unit/CMakeLists.txt | 24 ++++++- tests/unit/src/main.c | 5 +- tests/unit/src/test_fir_1x16_bit.c | 102 +++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 tests/unit/src/test_fir_1x16_bit.c 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); +} From 13d496aa3eebdeb08e00eae3a2341cc4127b3370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 19 Mar 2026 10:47:49 +0000 Subject: [PATCH 05/17] Add VX4 support to tests/signal --- tests/requirements.txt | 3 +- tests/signal/BasicMicArray/CMakeLists.txt | 43 ++++-- tests/signal/BasicMicArray/micarray_device.py | 7 +- .../small_768k_to_12k_filter_int.pkl | Bin 0 -> 718 bytes tests/signal/BasicMicArray/src/app.c | 80 ++++++++++- tests/signal/BasicMicArray/src/app_config.h | 23 +++ .../BasicMicArray/{ => src}/config.xscope | 0 .../src/small_768k_to_12k_filter.h | 59 ++++++++ tests/signal/BasicMicArray/test_mic_array.py | 67 +++++++++ tests/signal/BasicMicArray/test_params.json | 2 +- tests/signal/BasicMicArray/test_thdn.py | 132 ++++++++++++++++++ tests/signal/TwoStageDecimator/CMakeLists.txt | 2 +- tests/signal/TwoStageDecimator/src/main.xc | 81 +++++++++++ .../src/{app.cpp => run.cpp} | 79 +---------- tests/signal/pdmrx_isr/CMakeLists.txt | 20 +-- tests/signal/pdmrx_isr/src/app.cpp | 12 +- .../signal/profile/app_memory/CMakeLists.txt | 2 +- tests/signal/profile/app_memory/src/app.cpp | 11 +- tests/signal/profile/app_memory/src/main.c | 72 ---------- tests/signal/profile/app_memory/src/main.xc | 50 +++++-- tests/signal/profile/app_mips/CMakeLists.txt | 23 ++- tests/signal/profile/app_mips/src/app.c | 8 +- .../signal/profile/app_mips/src/app_config.h | 7 + tests/signal/profile/app_mips/src/app_pll.c | 89 ++++++++++++ tests/signal/profile/app_mips/src/main.c | 24 ++++ tests/signal/profile/app_mips/src/main.xc | 4 + .../profile/app_mips/src/mips/burn_mips.S | 20 +++ .../profile/app_mips/src/mips/count_mips.S | 61 ++++++++ tests/signal/profile/mic_array_memory.json | 32 ++--- .../signal/profile/mic_array_memory_table.rst | 32 ++--- tests/signal/profile/mic_array_mips.json | 14 -- tests/signal/profile/mic_array_mips_table.rst | 24 ++-- tests/signal/profile/mic_array_mips_vx4.json | 8 ++ tests/signal/profile/mic_array_mips_xs3.json | 14 ++ tests/signal/profile/test_measure_mips.py | 39 +++++- 35 files changed, 879 insertions(+), 265 deletions(-) create mode 100644 tests/signal/BasicMicArray/small_768k_to_12k_filter_int.pkl create mode 100644 tests/signal/BasicMicArray/src/app_config.h rename tests/signal/BasicMicArray/{ => src}/config.xscope (100%) create mode 100644 tests/signal/BasicMicArray/src/small_768k_to_12k_filter.h create mode 100644 tests/signal/TwoStageDecimator/src/main.xc rename tests/signal/TwoStageDecimator/src/{app.cpp => run.cpp} (64%) delete mode 100644 tests/signal/profile/app_memory/src/main.c create mode 100644 tests/signal/profile/app_mips/src/app_pll.c create mode 100644 tests/signal/profile/app_mips/src/main.c delete mode 100644 tests/signal/profile/mic_array_mips.json create mode 100644 tests/signal/profile/mic_array_mips_vx4.json create mode 100644 tests/signal/profile/mic_array_mips_xs3.json diff --git a/tests/requirements.txt b/tests/requirements.txt index 2dd37e03..55949430 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,4 +1,5 @@ -# python_version 3.11.9 +# pip_version 25.* +# python_version 3.12 pytest==8.3.2 pytest-xdist==3.6.1 diff --git a/tests/signal/BasicMicArray/CMakeLists.txt b/tests/signal/BasicMicArray/CMakeLists.txt index 86674faf..d8066a5b 100644 --- a/tests/signal/BasicMicArray/CMakeLists.txt +++ b/tests/signal/BasicMicArray/CMakeLists.txt @@ -6,7 +6,25 @@ set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../..) set(APP_DEPENDENT_MODULES "lib_mic_array") -set(APP_HW_TARGET XK-EVK-XU316) +# 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 + -lxc + -Wno-fptrgroup + -Wno-format) +endif() set_property(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/test_params.json") @@ -32,6 +50,12 @@ math(EXPR NUM_FRAME_SIZE "${NUM_FRAME_SIZE} - 1") math(EXPR NUM_USE_ISR "${NUM_USE_ISR} - 1") math(EXPR NUM_SAMP_FREQ "${NUM_SAMP_FREQ} - 1") +# Remove ISR if vx4 as it's not supported + if (APP_HW_TARGET STREQUAL "XK-EVK-XU416") + set(USE_ISR_LIST "[0]") + set(NUM_USE_ISR 0) +endif() + # Count how many SAMP_FREQ entries are custom (.pkl) set(CUSTOM_PKL_COUNT 0) foreach(idx RANGE 0 ${NUM_SAMP_FREQ}) @@ -84,13 +108,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} @@ -106,6 +124,15 @@ endforeach() set(APP_INCLUDES src ${AUTOGEN_OUT_DIR}) +set(APP_COMPILER_FLAGS_lp_1stg_decimator ${COMMON_COMPILER_FLAGS} + -DMIC_ARRAY_CONFIG_USE_PDM_ISR=0 + -DMIC_ARRAY_CONFIG_SAMPLES_PER_FRAME=2 + -DMIC_ARRAY_CONFIG_MIC_COUNT=1 + -DMIC_ARRAY_CONFIG_USE_DC_ELIMINATION=0 + -DAPP_SAMP_FREQ=24000 + -DUSE_CUSTOM_FILTER=0 + -DAPP_CONFIG_LOW_POWER=1 + ) XMOS_REGISTER_APP() foreach(target ${APP_BUILD_TARGETS}) diff --git a/tests/signal/BasicMicArray/micarray_device.py b/tests/signal/BasicMicArray/micarray_device.py index 98770b11..bc646d0e 100644 --- a/tests/signal/BasicMicArray/micarray_device.py +++ b/tests/signal/BasicMicArray/micarray_device.py @@ -41,7 +41,7 @@ def send_command(self, cmd_id): # Then, send the command ID self.send_word(cmd_id) - def process_signal(self, signal: PdmSignal): + def process_signal(self, signal: PdmSignal, sample_count_override=None): # First, send the entire signal to the device. Any output it sends over the # data probe will get queued up by our parent class, so that it doesn't back @@ -53,7 +53,10 @@ def process_signal(self, signal: PdmSignal): sig_bytes = signal.to_bytes_interleaved() self.send_bytes(sig_bytes) - sample_count = signal.len // ( 32 * self.param["s2.dec_factor"] * self.param["s3.dec_factor"]) + if sample_count_override: + sample_count = sample_count_override + else: + sample_count = signal.len // ( 32 * self.param["s2.dec_factor"] * self.param["s3.dec_factor"]) device_output = np.zeros((self.param["channels"], sample_count), dtype=np.int32) diff --git a/tests/signal/BasicMicArray/small_768k_to_12k_filter_int.pkl b/tests/signal/BasicMicArray/small_768k_to_12k_filter_int.pkl new file mode 100644 index 0000000000000000000000000000000000000000..54a527348b4fe85c948bb16e656f020d299837d4 GIT binary patch literal 718 zcmZo*nR=Ls0SscNXaG@)~FGd*F-Wd6^*oF$pngiVHBhQo|An`LIGzXGZAmGMv0qJ zfwHgU7b#V!=4jMtFVlNr=wQ0qLfvMgy}R=>w@xozzw?1vp}!+)W8TC&CD)`)$!yK> z&VO9!RZ?44Sz%pupqisrpzcOpT>U~IjH|ypbt5i)&YGh%lHK7^B!Ob1OP)q z6Br^wz>r}Bh7cpe6kv#XE2IMBErTV49TfZ-oUp(*0CM<%L2iNw@(dv?0q%U@(v<)I zH_T>W*q_e9aCkKv!}@8D{>w9+`0v^w&ET4{mSK%~I>YIPr~hISHU7mss$-ZM{(w1z zy_Tt>Ch_-Osng#VJl?^;xYUD>v(}b-iQbuy5~uIqdvM16=j92F4#3E*2VoE!BnFZL zsR5}4=>h2lnE^5jWG2XLkR2eqKz4%c2Dt&`7Lc1jZUeay #include "mic_array.h" +#include "app_config.h" #if USE_CUSTOM_FILTER #include "custom_filter.h" #endif -#define BUFF_SIZE (256) +#if APP_CONFIG_LOW_POWER +#include "small_768k_to_12k_filter.h" +#endif + +#define BUFF_SIZE (256) + +#ifndef META_OUT +#define META_OUT (0) +#endif + +#ifndef DATA_OUT +#define DATA_OUT (1) +#endif typedef chanend_t streaming_chanend_t; @@ -54,9 +67,23 @@ void hwtimer_delay_microseconds(unsigned delay) { hwtimer_free(tmr); } -static +static void get_filter_config(unsigned fs, filt_config_t *cfg) { -#if !USE_CUSTOM_FILTER + +#if APP_CONFIG_LOW_POWER + cfg->stg1_tap_count = SMALL_768K_TO_12K_FILTER_STG1_TAP_COUNT; + cfg->stg1_decimation_factor = SMALL_768K_TO_12K_FILTER_STG1_DECIMATION_FACTOR; + cfg->stg2_tap_count = SMALL_768K_TO_12K_FILTER_STG2_TAP_COUNT; + cfg->stg2_decimation_factor = SMALL_768K_TO_12K_FILTER_STG2_DECIMATION_FACTOR; + cfg->stg1_coef_ptr = small_768k_to_12k_filter_stg1_coef; + cfg->stg2_coef_ptr = small_768k_to_12k_filter_stg2_coef; + cfg->stg2_shr = SMALL_768K_TO_12K_FILTER_STG2_SHR; + + cfg->stg3_tap_count = 0; + cfg->stg3_decimation_factor = 1; // for PDM RX block size calculation in the test to work for both 2 and 3 stage filters + cfg->stg3_coef_ptr = NULL; + cfg->stg3_shr = 0; +#elif !USE_CUSTOM_FILTER cfg->stg1_tap_count = 256; cfg->stg1_decimation_factor = 32; @@ -194,7 +221,7 @@ int send_words_to_app(streaming_chanend_t c_to_app, char* buff, int buff_lvl) buff_lvl -= sizeof(int); hwtimer_delay_microseconds(15); } - if(buff_lvl) + if(buff_lvl) { memmove(&buff[0], &next_word[0], buff_lvl); } @@ -263,9 +290,44 @@ static void init_mic_conf(mic_array_conf_t *mic_array_conf, mic_array_filter_con 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.num_channels_in = MIC_ARRAY_CONFIG_MIC_COUNT; + mic_array_conf->pdmrx_conf.num_channels_out = MIC_ARRAY_CONFIG_MIC_COUNT; } #endif +#if APP_CONFIG_LOW_POWER +static +void init_mic_conf_lp_filter( + mic_array_conf_t *mic_array_conf, + mic_array_filter_conf_t filter_conf[2], + 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][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 = 1; + // 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 + + // pdm rx + static uint32_t pdmrx_out_block[MIC_ARRAY_CONFIG_MIC_COUNT][MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME]; + static uint32_t pdmrx_out_block_double_buf[2][MIC_ARRAY_CONFIG_MIC_COUNT * MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME] __attribute__((aligned(8))); + mic_array_conf->pdmrx_conf.pdm_out_words_per_channel = MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME; + 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.num_channels_in = MIC_ARRAY_CONFIG_MIC_COUNT; + mic_array_conf->pdmrx_conf.num_channels_out = MIC_ARRAY_CONFIG_MIC_COUNT; +} +#endif // ------------------------------- THREADS ------------------------------- @@ -273,7 +335,12 @@ void app_mic( chanend_t c_pdm_in, chanend_t c_frames_out) //non-streaming { -#if !USE_CUSTOM_FILTER +#if APP_CONFIG_LOW_POWER + mic_array_conf_t mic_array_conf; + mic_array_filter_conf_t filter_conf[NUM_DECIMATION_STAGES]; + init_mic_conf_lp_filter(&mic_array_conf, filter_conf, NULL); + mic_array_init_custom_filter_1mic_1stg_decimator(&pdm_res, &mic_array_conf); +#elif !USE_CUSTOM_FILTER mic_array_init(&pdm_res, NULL, APP_SAMP_FREQ); #else mic_array_conf_t mic_array_conf; @@ -317,7 +384,6 @@ void app_output_task(chanend_t c_frames_in, chanend_t c_fifo) // receive the output of the mic array and send it to the host via a fifo to decouple the backpressure from xscope int32_t frame[MIC_ARRAY_CONFIG_MIC_COUNT][MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME]; uint8_t fifo_idx = 0; - while(1){ ma_frame_rx(&frame[0][0], c_frames_in, MIC_ARRAY_CONFIG_MIC_COUNT, MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME); memcpy(frame_fifo[fifo_idx], &frame[0][0], sizeof(ma_frame_t)); @@ -382,7 +448,7 @@ int main(){ streaming_channel_t c_to_app = s_chan_alloc(); // xscope init note: only one channel end is needed - // the second one and the xscope service will be + // the second one and the xscope service will be // automatically started and routed by the tools chanend_t xscope_chan = chanend_alloc(); xscope_mode_lossless(); diff --git a/tests/signal/BasicMicArray/src/app_config.h b/tests/signal/BasicMicArray/src/app_config.h new file mode 100644 index 00000000..06d5c0d1 --- /dev/null +++ b/tests/signal/BasicMicArray/src/app_config.h @@ -0,0 +1,23 @@ +// 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 VX_PORT_1D +#endif + +#ifndef PORT_PDM_CLK +#define PORT_PDM_CLK VX_PORT_1G +#endif + +#ifndef PORT_PDM_DATA +#define PORT_PDM_DATA VX_PORT_1F +#endif + + +#endif diff --git a/tests/signal/BasicMicArray/config.xscope b/tests/signal/BasicMicArray/src/config.xscope similarity index 100% rename from tests/signal/BasicMicArray/config.xscope rename to tests/signal/BasicMicArray/src/config.xscope diff --git a/tests/signal/BasicMicArray/src/small_768k_to_12k_filter.h b/tests/signal/BasicMicArray/src/small_768k_to_12k_filter.h new file mode 100644 index 00000000..867c2290 --- /dev/null +++ b/tests/signal/BasicMicArray/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/tests/signal/BasicMicArray/test_mic_array.py b/tests/signal/BasicMicArray/test_mic_array.py index 9e2a4fd9..66b84b1a 100644 --- a/tests/signal/BasicMicArray/test_mic_array.py +++ b/tests/signal/BasicMicArray/test_mic_array.py @@ -126,3 +126,70 @@ def test_BasicMicArray(self, request, chans, frame_size, use_isr, fs): threshold = 12 assert result_diff <= threshold, f"max diff between python and xcore mic array output ({result_diff}) exceeds threshold ({threshold})" + + @pytest.mark.parametrize("decimator_stgs", [1], ids=["1stg"]) + def test_BasicMicArrayLowPower(self, request, decimator_stgs): + cwd = Path(request.fspath).parent + filter = self.filter(Path(__file__).parent / "small_768k_to_12k_filter_int.pkl") + + stg1_output_words_per_frame = int(filter.DecimationFactor / filter.s1.DecimationFactor) + assert stg1_output_words_per_frame == 2 + + samp_per_frame = 32 + frames = request.config.getoption("frames") + + # --- num decimator stages dependent behaviour --- + stg1_only = (decimator_stgs == 1) + samp_total = samp_per_frame * frames * (2 if stg1_only else 1) + device_output_delay_samps = 0 if stg1_only else 1 + sample_override = frames * 2 if stg1_only else None + output_frame_size = 2 if stg1_only else 1 + # ------------------------------------------------- + + sig = PdmSignal.random(1, samp_total) + + expected = filter.Filter(sig.signal, stg1_only=stg1_only) + + if self.print_output: + print(f"Expected output: {expected}") + + cfg = f"lp_{decimator_stgs}stg_decimator" + xe_path = f"{cwd}/bin/{cfg}/test_ma_{cfg}.xe" + assert Path(xe_path).exists(), f"Cannot find {xe_path}" + + with MicArrayDevice( + xe_path, + quiet_xgdb=not self.print_xgdb, + extra_xrun_args="--id 0" + ) as dev: + + assert dev.param["channels"] == 1 + assert dev.param["s1.dec_factor"] == filter.s1.DecimationFactor + assert dev.param["s1.tap_count"] == filter.s1.TapCount + assert dev.param["s2.dec_factor"] == filter.s2.DecimationFactor + assert dev.param["s2.tap_count"] == filter.s2.TapCount + assert dev.param["frame_size"] == output_frame_size + assert dev.param["use_isr"] == 0 + + if self.debug_print_filters: + dev.send_command(DevCommand.PRINT_FILTERS.value) + + device_output = dev.process_signal(sig, sample_count_override=sample_override) + + if self.print_output: + print(f"Device output: {device_output}") + + dev.send_command(DevCommand.TERMINATE.value) + + end = -device_output_delay_samps or None + start = device_output_delay_samps + + result_diff = np.max(np.abs(expected[:, :end] - device_output[:, start:])) + + print(f"result_diff = {result_diff}") + + threshold = 12 + assert result_diff <= threshold, ( + f"max diff between python and xcore mic array output ({result_diff}) " + f"exceeds threshold ({threshold})" + ) \ No newline at end of file diff --git a/tests/signal/BasicMicArray/test_params.json b/tests/signal/BasicMicArray/test_params.json index cdca885c..dc52dce4 100644 --- a/tests/signal/BasicMicArray/test_params.json +++ b/tests/signal/BasicMicArray/test_params.json @@ -3,4 +3,4 @@ "FRAME_SIZE": [1, 16], "USE_ISR": [0, 1], "SAMP_FREQ": [16000, 32000, 48000, "good_3_stage_filter_int.pkl"] -} +} \ No newline at end of file diff --git a/tests/signal/BasicMicArray/test_thdn.py b/tests/signal/BasicMicArray/test_thdn.py index fae62912..08f7f604 100644 --- a/tests/signal/BasicMicArray/test_thdn.py +++ b/tests/signal/BasicMicArray/test_thdn.py @@ -146,3 +146,135 @@ def test_thdn(self, pytestconfig, request, fs, platform): print(f"result_diff = {result_diff}") assert result_diff <= threshold, f"max diff between python and xcore mic array output ({result_diff}) exceeds threshold ({threshold})" + + + def thdn_test_lowpower_uncollect(config, platform, decimator_stgs, test_freq): + level = config.getoption("level") + if level == "smoke": + if "xcore" in platform: + return True # uncollect xcore run for smoke. Takes 2-3mins per test so run in nightly + return False + + def to_float_array(self, x): + """Convert integer array to float64 normalized to [-1, 1], or leave floats unchanged.""" + if np.issubdtype(x.dtype, np.integer): + return x.astype(np.float64) / np.iinfo(x.dtype).max + return x + + @pytest.mark.uncollect_if(func=thdn_test_lowpower_uncollect) + @pytest.mark.parametrize("platform", ["python_only", "python_xcore"]) + @pytest.mark.parametrize("decimator_stgs", [1], ids=["1stg"]) + @pytest.mark.parametrize("test_freq", [300, 5000], ids=["300hz", "5000hz"]) + def test_thdn_lowpower(self, pytestconfig, request, platform, decimator_stgs, test_freq): + duration_s = 2 # running reduced duration. See https://github.com/xmos/lib_mic_array/issues/289 + pdm_freq = 768_000 + + thdn_threshold = { + (12000, 300): -111.0, + (12000, 5000): -105.0, + (24000, 300): -79.0, + (24000, 5000): -76.0, + } + + cwd = Path(request.fspath).parent + filter = self.filter(Path(__file__).parent / "small_768k_to_12k_filter_int.pkl") + + # --- num decimator stages dependent behaviour --- + stg1_only = (decimator_stgs == 1) + dec_factor = filter.s1.DecimationFactor if stg1_only else filter.DecimationFactor + fs = int(pdm_freq / dec_factor) + device_output_delay_samps = 0 if stg1_only else 1 + sample_override = duration_s * fs if stg1_only else None + output_frame_size = 2 if stg1_only else 1 + print(f"decimator_stgs = {decimator_stgs}, fs = {fs}") + # ------------------------------------------------- + + cfg = f"lp_{decimator_stgs}stg_decimator" + xe_path = f"{cwd}/bin/{cfg}/test_ma_{cfg}.xe" + assert Path(xe_path).exists(), f"Cannot find {xe_path}" + + print(f"Test frequency {test_freq}\n") + + # Generate PDM input + # Test one freq at a time since low-power mic array is mono + sig_sine_pdm, sig_sine_pcm = PdmSignal.sine( + [test_freq], + [0.52], + fs, + duration_s, + fs_pdm=pdm_freq + ) + + print("Running python") + expected = filter.Filter(sig_sine_pdm.signal, stg1_only=stg1_only) + + if self.print_output: + print(f"Expected output: {expected}") + + print(f"Expected output shape: {expected.shape}") + + expected_output_float = self.to_float_array(expected) + + input_thdn = THDN(sig_sine_pcm[0], fs, fund_freq=test_freq) + python_output_thdn = THDN(expected_output_float[0], fs, fund_freq=test_freq) + + threshold = thdn_threshold[(fs, test_freq)] + + print( + f"test_freq {test_freq}, " + f"python_output_thdn = {python_output_thdn}, " + f"input_thdn = {input_thdn}" + ) + + assert python_output_thdn < threshold, ( + f"At sampling rate {fs}, test_freq {test_freq}, " + f"Python output THDN {python_output_thdn} exceeds threshold {threshold}" + ) + + if "xcore" in platform: + print("Running xcore") + with MicArrayDevice(xe_path, quiet_xgdb=not self.print_xgdb, extra_xrun_args="--id 0") as dev: + assert dev.param["channels"] == 1 + assert dev.param["s1.dec_factor"] == filter.s1.DecimationFactor + assert dev.param["s1.tap_count"] == filter.s1.TapCount + assert dev.param["s2.dec_factor"] == filter.s2.DecimationFactor + assert dev.param["s2.tap_count"] == filter.s2.TapCount + assert dev.param["frame_size"] == output_frame_size + assert dev.param["use_isr"] == 0 + + if self.debug_print_filters: + dev.send_command(DevCommand.PRINT_FILTERS.value) + + device_output = dev.process_signal(sig_sine_pdm, sample_count_override=sample_override) + + print(f"device_output shape: {device_output.shape}") + + device_output_float = self.to_float_array(device_output) + + xcore_output_thdn = THDN(device_output_float[0][int(fs/10):], fs, fund_freq=test_freq) + + print( + f"test_freq {test_freq}, " + f"xcore_output_thdn = {xcore_output_thdn}, " + f"input_thdn = {input_thdn}" + ) + + assert xcore_output_thdn < threshold, ( + f"At sampling rate {fs}, test_freq {test_freq}, " + f"XCORE output THDN {xcore_output_thdn} exceeds threshold {threshold}" + ) + + if self.print_output: + print(f"Device output: {device_output}") + + dev.send_command(DevCommand.TERMINATE.value) + + end = -device_output_delay_samps or None + start = device_output_delay_samps + result_diff = np.max(np.abs(expected[:, :end] - device_output[:, start:])) + threshold = 12 + print(f"result_diff = {result_diff}") + assert result_diff <= threshold, ( + f"max diff between python and xcore mic array output ({result_diff}) " + f"exceeds threshold ({threshold})" + ) \ No newline at end of file diff --git a/tests/signal/TwoStageDecimator/CMakeLists.txt b/tests/signal/TwoStageDecimator/CMakeLists.txt index 183f4ab3..ed0346a5 100644 --- a/tests/signal/TwoStageDecimator/CMakeLists.txt +++ b/tests/signal/TwoStageDecimator/CMakeLists.txt @@ -4,7 +4,7 @@ project(tests-signal-decimator) set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../..) -set(APP_DEPENDENT_MODULES "lib_mic_array") +include(${CMAKE_CURRENT_LIST_DIR}/../../../examples/deps.cmake) set(APP_HW_TARGET XK-EVK-XU316) diff --git a/tests/signal/TwoStageDecimator/src/main.xc b/tests/signal/TwoStageDecimator/src/main.xc new file mode 100644 index 00000000..5b2a020a --- /dev/null +++ b/tests/signal/TwoStageDecimator/src/main.xc @@ -0,0 +1,81 @@ +// Copyright 2020-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include + +#include +#include + +void run(streaming chanend); + +unsafe { + + +// We can't 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 + char buff[BUFF_SIZE+3]; + int buff_lvl = 0; + + 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; + // printf("& Received %d bytes.\n", dd); + + // 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); + } + + // 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; + + 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(); + run(c_to_app); + printf("Done.\n"); + exit(0); + } + } + return 0; +} + +} \ No newline at end of file diff --git a/tests/signal/TwoStageDecimator/src/app.cpp b/tests/signal/TwoStageDecimator/src/run.cpp similarity index 64% rename from tests/signal/TwoStageDecimator/src/app.cpp rename to tests/signal/TwoStageDecimator/src/run.cpp index 2b2690d5..9ee7f37a 100644 --- a/tests/signal/TwoStageDecimator/src/app.cpp +++ b/tests/signal/TwoStageDecimator/src/run.cpp @@ -1,21 +1,16 @@ // Copyright 2020-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 extern "C" { -#include +#include "xscope.h" } #include "mic_array.h" @@ -30,20 +25,12 @@ extern "C" { # error S2_DEC_FACT must be defined. #endif -#define BUFF_SIZE 256 - -typedef chanend_t streaming_chanend_t; - // Will be loaded from file static uint32_t test_stage1_coef[128]; static int32_t test_stage2_coef[S2_TAPS]; static right_shift_t test_stage2_shr; -DECLARE_JOB(run, (chanend_t)); -DECLARE_JOB(host_words_to_app, (chanend_t, streaming_chanend_t)); - -// ------------------------------- HELPER FUNCTIONS ------------------------------- void load_stage1(chanend_t c_from_host) { @@ -95,7 +82,7 @@ void process_signal(chanend_t c_from_host) filter_conf[1].state_words_per_channel = filter_conf[1].num_taps; filter_conf[1].state = (int32_t*)stg2_filter_state; - dec.Init(decimator_conf); + dec.Init(decimator_conf, S2_DEC_FACT); // Host will tell us how many blocks it intends to send unsigned block_count = s_chan_in_word(c_from_host); @@ -114,6 +101,7 @@ void process_signal(chanend_t c_from_host) printf("Finished processing PDM signal.\n"); } +extern "C" void run(chanend_t c_from_host) { // Tell the host script what parameters are currently being used @@ -128,60 +116,3 @@ void run(chanend_t c_from_host) process_signal(c_from_host); } - - -void host_words_to_app(chanend_t c_from_host, streaming_chanend_t c_to_app) -{ - xscope_connect_data_from_host(c_from_host); - - // +3 is for any partial word at the end of the read - char buff[BUFF_SIZE+3]; - int buff_lvl = 0; - - SELECT_RES( - CASE_THEN(c_from_host, c_from_host_handler) - ){ - c_from_host_handler:{ - int dd; - xscope_data_from_host(c_from_host, &buff[0], &dd); - dd--; // last byte is always 0 (for some reason) - buff_lvl += dd; - - // Send all (complete) words to app - 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); - } - - // if there's 1-3 bytes left move it to the front. - if(buff_lvl) { - memmove(&buff[0], &next_word[0], buff_lvl); - } - - continue; - }} -} - - -int main() -{ - streaming_channel_t c_to_app = s_chan_alloc(); - - // xscope init note: only one channel end is needed - // the second one and the xscope service will be - // automatically started and routed by the tools - chanend_t xscope_chan = chanend_alloc(); - xscope_mode_lossless(); - - PAR_JOBS( - PJOB(host_words_to_app, (xscope_chan, c_to_app.end_a)), - PJOB(run, (c_to_app.end_b)) - ); - - s_chan_free(c_to_app); - - printf("Done.\n"); - return 0; -} diff --git a/tests/signal/pdmrx_isr/CMakeLists.txt b/tests/signal/pdmrx_isr/CMakeLists.txt index 0073a866..3878863d 100644 --- a/tests/signal/pdmrx_isr/CMakeLists.txt +++ b/tests/signal/pdmrx_isr/CMakeLists.txt @@ -4,16 +4,18 @@ project(tests_signal_pdmrx_isr) 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_COMPILER_FLAGS -O3 +set(APP_DEPENDENT_MODULES "lib_mic_array") +set(APP_COMPILER_FLAGS -Os -g -report - -DXASSERT_ENABLE_ASSERTIONS=1 - -DXASSERT_ENABLE_DEBUG=1 - -DXASSERT_ENABLE_LINE_NUMBERS=1) + -DLIBXCORE_XASSERT_IS_ASSERT + -DMIC_ARRAY_CONFIG_USE_PDM_ISR=1 + ) -XMOS_REGISTER_APP() +if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL "3.6.0") # XS3 (XTC 15.3.1) +set(APP_HW_TARGET XK-EVK-XU316) +else() # VX4 +set(APP_HW_TARGET XK-EVK-XU416) +endif() +XMOS_REGISTER_APP() diff --git a/tests/signal/pdmrx_isr/src/app.cpp b/tests/signal/pdmrx_isr/src/app.cpp index e6a76cf4..9056ce4e 100644 --- a/tests/signal/pdmrx_isr/src/app.cpp +++ b/tests/signal/pdmrx_isr/src/app.cpp @@ -5,14 +5,14 @@ #include #include #include -#include #include #include #include #include +#include #include -#include "xassert.h" + #include "mic_array.h" #include "app.h" @@ -31,12 +31,13 @@ void app_pdm_rx_isr_setup( pdm_rx_config.pdm_out_words_per_channel = MY_STAGE2_DEC_FACTOR; pdm_rx_config.pdm_out_block = (uint32_t*)pdmrx_out_block; pdm_rx_config.pdm_in_double_buf = (uint32_t*)pdmrx_in_block_double_buf; + pdm_rx_config.num_channels_in = 1; + pdm_rx_config.num_channels_out = 1; my_pdm_rx.Init((port_t)c_from_host, pdm_rx_config); my_pdm_rx.AssertOnDroppedBlock(false); my_pdm_rx.InstallISR(); my_pdm_rx.UnmaskISR(); - } void test() @@ -90,7 +91,7 @@ void test() s_chan_out_word(c, frame++); pdm_samples = my_pdm_rx.GetPdmBlock(); - printf("Received block %d\n", *pdm_samples); + printf("Received block %lu\n", *pdm_samples); s_chan_out_word(c, frame++); } @@ -109,9 +110,8 @@ void assert_when_timeout() CASE_THEN(t, timer_handler)) { timer_handler: - assert(0 && msg("Error: test timed out due to deadlock")); + xassert(0 && "Error: test timed out due to deadlock"); break; } - hwtimer_free(t); } diff --git a/tests/signal/profile/app_memory/CMakeLists.txt b/tests/signal/profile/app_memory/CMakeLists.txt index bad811cf..c9410a47 100644 --- a/tests/signal/profile/app_memory/CMakeLists.txt +++ b/tests/signal/profile/app_memory/CMakeLists.txt @@ -4,7 +4,7 @@ project(test_memory) set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../../..) -include(${CMAKE_CURRENT_LIST_DIR}/../../../../examples/deps.cmake) +set(APP_DEPENDENT_MODULES "lib_mic_array") set(APP_HW_TARGET XK-VOICE-L71.xn) diff --git a/tests/signal/profile/app_memory/src/app.cpp b/tests/signal/profile/app_memory/src/app.cpp index 77c7e465..2b150e49 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, @@ -89,7 +93,7 @@ void app_mic_array_init() filter_conf[1].state_words_per_channel = decimator_conf.filter_conf[1].num_taps; filter_conf[1].state = (int32_t*)filter_state_df_2; - mics.Decimator.Init(decimator_conf); + mics.Decimator.Init(decimator_conf, STAGE2_DEC_FACTOR); static uint32_t pdmrx_out_block_df_2[APP_N_MICS][STAGE2_DEC_FACTOR]; static uint32_t __attribute__((aligned (8))) pdmrx_out_block_double_buf_df_2[2][APP_N_MICS_IN * STAGE2_DEC_FACTOR]; @@ -135,4 +139,3 @@ void app_mic_array_task(chanend_t c_frames_out) #endif } #endif - diff --git a/tests/signal/profile/app_memory/src/main.c b/tests/signal/profile/app_memory/src/main.c deleted file mode 100644 index bf977939..00000000 --- a/tests/signal/profile/app_memory/src/main.c +++ /dev/null @@ -1,72 +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 -#include - -#include "app_config.h" -#include "mic_array.h" - -#if !USE_DEFAULT_API -#include "app.h" -#endif - -static inline void mic_array_init_1_mic(void) -{ - pdm_rx_resources_t pdm_res = PDM_RX_RESOURCES_SDR( - PORT_MCLK_IN, PORT_PDM_CLK, PORT_PDM_DATA, - APP_MCLK_FREQUENCY, APP_PDM_CLOCK_FREQUENCY, XS1_CLKBLK_1); - mic_array_init(&pdm_res, NULL, APP_SAMP_FREQ); -} - -static inline void mic_array_init_2_mics(void) -{ - pdm_rx_resources_t pdm_res = PDM_RX_RESOURCES_DDR( - PORT_MCLK_IN, PORT_PDM_CLK, PORT_PDM_DATA, - APP_MCLK_FREQUENCY, APP_PDM_CLOCK_FREQUENCY, XS1_CLKBLK_1, XS1_CLKBLK_2); - mic_array_init(&pdm_res, NULL, APP_SAMP_FREQ); -} - -static inline void mic_array_init_start_default(chanend_t c_audio_frames) -{ -#if (MIC_ARRAY_CONFIG_MIC_COUNT == 2) - mic_array_init_2_mics(); -#elif (MIC_ARRAY_CONFIG_MIC_COUNT == 1) - mic_array_init_1_mic(); -#else -#error "Unsupported mic count configuration" -#endif - mic_array_start(c_audio_frames); -} - -static inline void mic_array_init_start_custom(chanend_t c_audio_frames) -{ - app_mic_array_init(); - app_mic_array_task(c_audio_frames); -} - -static void mic_array_init_start(chanend_t c_audio_frames) -{ -#if USE_DEFAULT_API - mic_array_init_start_default(c_audio_frames); -#else - mic_array_init_start_custom(c_audio_frames); -#endif -} - -int main_tile_0(chanend_t c_audio_frames) -{ - (void)c_audio_frames; - return 0; -} - -int main_tile_1(chanend_t c_audio_frames) -{ - mic_array_init_start(c_audio_frames); - return 0; // should never reach here -} diff --git a/tests/signal/profile/app_memory/src/main.xc b/tests/signal/profile/app_memory/src/main.xc index 5bc6e940..e351f90c 100644 --- a/tests/signal/profile/app_memory/src/main.xc +++ b/tests/signal/profile/app_memory/src/main.xc @@ -1,21 +1,55 @@ // 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 "app_config.h" +#include "mic_array.h" +#if !USE_DEFAULT_API + #include "app.h" +#else +// mic array resources +on tile[PORT_PDM_CLK_TILE_NUM]: in port p_mclk = PORT_MCLK_IN; +on tile[PORT_PDM_CLK_TILE_NUM] : port p_pdm_clk = PORT_PDM_CLK; +on tile[PORT_PDM_CLK_TILE_NUM] : port p_pdm_data = PORT_PDM_DATA; +on tile[PORT_PDM_CLK_TILE_NUM] : clock clk_a = XS1_CLKBLK_1; +on tile[PORT_PDM_CLK_TILE_NUM] : clock clk_b = XS1_CLKBLK_2; +#endif -extern "C"{ - int main_tile_0(chanend c_audio_frames); - int main_tile_1(chanend c_audio_frames); -} +unsafe{ int main() { + chan c_audio_frames; + par { - on tile[0]: main_tile_0(c_audio_frames); - on tile[1]: main_tile_1(c_audio_frames); + + on tile[1]: { +#if USE_DEFAULT_API +#if (MIC_ARRAY_CONFIG_MIC_COUNT == 2) + pdm_rx_resources_t pdm_res = PDM_RX_RESOURCES_DDR(p_mclk, p_pdm_clk, p_pdm_data, APP_MCLK_FREQUENCY, APP_PDM_CLOCK_FREQUENCY, clk_a, clk_b); +#else + pdm_rx_resources_t pdm_res = PDM_RX_RESOURCES_SDR(p_mclk, p_pdm_clk, p_pdm_data, APP_MCLK_FREQUENCY, APP_PDM_CLOCK_FREQUENCY, clk_a); +#endif + + mic_array_init(&pdm_res, null, APP_SAMP_FREQ); + mic_array_start((chanend_t) c_audio_frames); + +#else + app_mic_array_init(); + app_mic_array_task((chanend_t) c_audio_frames); +#endif + + } } + return 0; } + +} diff --git a/tests/signal/profile/app_mips/CMakeLists.txt b/tests/signal/profile/app_mips/CMakeLists.txt index 6b667605..068f602e 100644 --- a/tests/signal/profile/app_mips/CMakeLists.txt +++ b/tests/signal/profile/app_mips/CMakeLists.txt @@ -4,12 +4,16 @@ project(test_mips) set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../../..) -include(${CMAKE_CURRENT_LIST_DIR}/../../../../examples/deps.cmake) - -set(APP_HW_TARGET XK-VOICE-L71.xn) +set(APP_DEPENDENT_MODULES "lib_mic_array") set(AUTOGEN_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/autogen") +if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL "3.6.0") # XS3 (XTC 15.3.1) +set(ISR_LIST 0 1) # ISR and thread-based PDM handling +else() # VX4 +set(ISR_LIST 0) # Only thread-based PDM handling supported on VX4 +endif() + set(NAME_MAP thread;isr) # Exactly one custom filter (.pkl) may be listed alongside numeric sample rates. # The .pkl file is expected to be in the ${CMAKE_CURRENT_LIST_DIR}/../../BasicMicArray/ directory @@ -49,14 +53,13 @@ foreach(SAMP_FREQ 16000 32000 48000 "good_3_stage_filter_int.pkl") set(samp_freq_str "${SAMP_FREQ}fs") endif() foreach(N_MICS 1 2) - foreach(USE_ISR 1 0) + foreach(USE_ISR ${ISR_LIST}) list(GET NAME_MAP ${USE_ISR} tmp) set(CONFIG "${N_MICS}mic_${tmp}_${samp_freq_str}") set(APP_COMPILER_FLAGS_${CONFIG} -Os -g -report - -mcmodel=large -DAPP_NAME="MIC_ARRAY_MEASURE_MIPS_${CONFIG}" -DMIC_ARRAY_CONFIG_USE_PDM_ISR=${USE_ISR} -DMIC_ARRAY_CONFIG_MIC_COUNT=${N_MICS} @@ -68,6 +71,16 @@ endforeach() set(APP_INCLUDES src src/mips/ ${AUTOGEN_OUT_DIR}) +# ---- Target specific ---- +if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL "3.6.0") # XS3 (XTC 15.3.1) +set(APP_HW_TARGET src/XK-VOICE-L71.xn) +list(APPEND APP_DEPENDENT_MODULES "lib_board_support(1.5.0)") +else() # VX4 +set(APP_HW_TARGET XK-EVK-XU416) +set(APP_XC_SRCS "") # prevents including xc +endif() + + XMOS_REGISTER_APP() foreach(target ${APP_BUILD_TARGETS}) diff --git a/tests/signal/profile/app_mips/src/app.c b/tests/signal/profile/app_mips/src/app.c index a913ee68..bc8c48b1 100644 --- a/tests/signal/profile/app_mips/src/app.c +++ b/tests/signal/profile/app_mips/src/app.c @@ -12,10 +12,12 @@ #include #include -#include "sw_pll.h" #include "mic_array.h" #include "app_config.h" +// defined in app_pll.c +extern void app_pll_init(void); + #define AUDIO_FRAME_LEN ( \ MIC_ARRAY_CONFIG_MIC_IN_COUNT * MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME) @@ -98,13 +100,15 @@ void init_mic_conf( 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.num_channels_in = MIC_ARRAY_CONFIG_MIC_COUNT; + mic_array_conf->pdmrx_conf.num_channels_out = MIC_ARRAY_CONFIG_MIC_COUNT; } #endif void mic_array_initialise() { // Set the pll to the required frequency for the mic array - sw_pll_fixed_clock(APP_MCLK_FREQUENCY); + app_pll_init(); // Set up the mic array resources #if (MIC_ARRAY_CONFIG_MIC_COUNT == 2) diff --git a/tests/signal/profile/app_mips/src/app_config.h b/tests/signal/profile/app_mips/src/app_config.h index ea44e21e..d40b700b 100644 --- a/tests/signal/profile/app_mips/src/app_config.h +++ b/tests/signal/profile/app_mips/src/app_config.h @@ -5,3 +5,10 @@ #define APP_MCLK_FREQUENCY 24576000 #define APP_PDM_CLOCK_FREQUENCY 3072000 + +#if defined(__VX4B__) +#include +#define PORT_MCLK_IN VX_PORT_1D +#define PORT_PDM_CLK VX_PORT_1G +#define PORT_PDM_DATA VX_PORT_1F +#endif // defined(__VX4B__) diff --git a/tests/signal/profile/app_mips/src/app_pll.c b/tests/signal/profile/app_mips/src/app_pll.c new file mode 100644 index 00000000..c9d266c4 --- /dev/null +++ b/tests/signal/profile/app_mips/src/app_pll.c @@ -0,0 +1,89 @@ +// Copyright 2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#if defined(__VX4B__) + +#include +#include + +#include +#include +#include +#include +#include +#include + +static +void delay_1ms(){ + hwtimer_t tmr = hwtimer_alloc(); + assert(tmr != 0); + hwtimer_delay(tmr, 100000); // 1ms with 100 MHz timer tick + hwtimer_free(tmr); +} + +void app_pll_init(void) +{ + printf("Initializing PLL\n"); + xsystem_tile_id_t tileid = get_local_tile_id(); + + // [0] PLL CTL DISABLE + uint32_t DEVICE_PLL_DISABLE = 0x00000000; + DEVICE_PLL_DISABLE = VX_PLL1_DISABLE_SET(DEVICE_PLL_DISABLE, 0); + + // [1] Mux + uint32_t DEVICE_PLL_MUX_VAL = 0x00000000; + DEVICE_PLL_MUX_VAL = VX_APP_CLK1_MUX_BIT_SET(DEVICE_PLL_MUX_VAL, 1); + DEVICE_PLL_MUX_VAL = VX_APP_CLK_IN_PHASE_BIT_SET(DEVICE_PLL_MUX_VAL, 1); + + // [2] PLL CTL + uint32_t DEVICE_PLL_CTL_VAL = 0x00000000; + DEVICE_PLL_CTL_VAL = VX_PLL1_R_DIVIDER_SET(DEVICE_PLL_CTL_VAL, 0); // input divider: 24 MHz ref / R=1 -> 24 MHz + DEVICE_PLL_CTL_VAL = VX_PLL1_F_MULTIPLIER_SET(DEVICE_PLL_CTL_VAL, 101); // feedback mult: 24 MHz * (F + 1 + 2/5 = 102.4) -> 2457.60 MHz + DEVICE_PLL_CTL_VAL = VX_PLL1_OD_DIVIDER_SET(DEVICE_PLL_CTL_VAL, 4); // output divider: 2457.60 MHz / (OD + 1) / 2 -> 245.76 MHz + DEVICE_PLL_CTL_VAL = VX_PLL1_DISABLE_SET(DEVICE_PLL_CTL_VAL, 0); // disable PLL before configuration + DEVICE_PLL_CTL_VAL = VX_PLL1_BYPASS_SET(DEVICE_PLL_CTL_VAL, 0); // no bypass + DEVICE_PLL_CTL_VAL = VX_PLL1_NLOCK_SET(DEVICE_PLL_CTL_VAL, 1); // wait for PLL lock + + // [3] FRAC (2/5) + uint32_t DEVICE_PLL_FRAC_NOM = 0x00000000; + DEVICE_PLL_FRAC_NOM = VX_SS_FRAC_N_ENABLE_SET(DEVICE_PLL_FRAC_NOM, 1); // enable fractional mode + DEVICE_PLL_FRAC_NOM = VX_SS_FRAC_N_PERIOD_CYC_CNT_SET(DEVICE_PLL_FRAC_NOM, 4); // +1 -> 5 + DEVICE_PLL_FRAC_NOM = VX_SS_FRAC_N_F_HIGH_CYC_CNT_SET(DEVICE_PLL_FRAC_NOM, 1); // +1 -> 2 + + // [4] APP DIVIDER + uint32_t DEVICE_PLL_DIV_0 = 0x00000000; + DEVICE_PLL_DIV_0 = VX_APP_CLK_DIV_ENABLE_SET(DEVICE_PLL_DIV_0, 1); // enable app clock divider + DEVICE_PLL_DIV_0 = VX_APP_CLK_DIV_VALUE_SET(DEVICE_PLL_DIV_0, 4); // set divider to 4 -> 245.76 MHz / (4 + 1) / 2 -> 24.576 MHz + + // print reg values + printf("PLL Configuration:\n"); + printf("PLL DISABLE: 0x%08lX\n", DEVICE_PLL_DISABLE); + printf("PLL MUX VAL: 0x%08lX\n", DEVICE_PLL_MUX_VAL); + printf("PLL CTL VAL: 0x%08lX\n", DEVICE_PLL_CTL_VAL); + printf("PLL DIV VAL: 0x%08lX\n", DEVICE_PLL_DIV_0); + printf("PLL FRAC_NOM: 0x%08lX\n", DEVICE_PLL_FRAC_NOM); + + // CONFIGURE + sswitch_reg_try_write(tileid, VX_SSB_CSR_PLL1_CTRL_NUM, DEVICE_PLL_DISABLE); // disable PLL before configuration + sswitch_reg_try_write(tileid, VX_SSB_CSR_CLK_SWITCH_CTRL_NUM, DEVICE_PLL_MUX_VAL); // switch app clock to PLL1 output + sswitch_reg_try_write(tileid, VX_SSB_CSR_PLL1_CTRL_NUM, DEVICE_PLL_CTL_VAL); // configure PLL control register + sswitch_reg_try_write(tileid, VX_SSB_CSR_PLL1_FRACN_CTRL_NUM, DEVICE_PLL_FRAC_NOM); // configure PLL fractional control register + sswitch_reg_try_write(tileid, VX_SSB_CSR_APP_CLK1_DIV_NUM, DEVICE_PLL_DIV_0); // configure app clock divider + delay_1ms(); +} + +#elif defined(__XS3A__) + +#include "app_config.h" +#include "sw_pll.h" + +#define DEVICE_PLL_CTL_VAL 0x0A019803 // Valid for all fractional values +#define DEVICE_PLL_FRAC_NOM 0x800095F9 // 24.576000 MHz + + +void app_pll_init(void) +{ + sw_pll_fixed_clock(APP_MCLK_FREQUENCY); +} + +#endif // defined(__XS3A__) diff --git a/tests/signal/profile/app_mips/src/main.c b/tests/signal/profile/app_mips/src/main.c new file mode 100644 index 00000000..1f72ac80 --- /dev/null +++ b/tests/signal/profile/app_mips/src/main.c @@ -0,0 +1,24 @@ +// Copyright 2022-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#if defined(__VX4B__) + +#include + +#include +#include + +#include + +extern int main_tile_0(chanend_t c_audio_frames); +extern int main_tile_1(chanend_t c_audio_frames); + +// Network main +DECLARE_CHAN(c) + +NETWORK_MAIN( + TILE_MAIN(main_tile_1, 1, (CHAN(c))), + TILE_MAIN(main_tile_0, 0, (CHAN(c))) +) + +#endif // defined(__VX4B__) diff --git a/tests/signal/profile/app_mips/src/main.xc b/tests/signal/profile/app_mips/src/main.xc index 5bc6e940..415eae84 100644 --- a/tests/signal/profile/app_mips/src/main.xc +++ b/tests/signal/profile/app_mips/src/main.xc @@ -1,6 +1,8 @@ // Copyright 2022-2026 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. +#if defined(__XC__) + #include #include @@ -19,3 +21,5 @@ int main() { } return 0; } + +#endif // defined(__XC__) diff --git a/tests/signal/profile/app_mips/src/mips/burn_mips.S b/tests/signal/profile/app_mips/src/mips/burn_mips.S index fb98fe49..4ef41108 100644 --- a/tests/signal/profile/app_mips/src/mips/burn_mips.S +++ b/tests/signal/profile/app_mips/src/mips/burn_mips.S @@ -39,4 +39,24 @@ FUNCTION_NAME: #endif //defined(__XS3A__) +#if defined(__VX4B__) +#define FUNCTION_NAME burn_mips +#define NSTACK_BYTES 16 // minimum + +.p2align 1 +.globl FUNCTION_NAME +.type FUNCTION_NAME,@function +FUNCTION_NAME: + xm.entsp NSTACK_BYTES + .L_loop_back: + xm.bu .L_loop_back + 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 // __VX4B__ diff --git a/tests/signal/profile/app_mips/src/mips/count_mips.S b/tests/signal/profile/app_mips/src/mips/count_mips.S index 47041092..855a9a5c 100644 --- a/tests/signal/profile/app_mips/src/mips/count_mips.S +++ b/tests/signal/profile/app_mips/src/mips/count_mips.S @@ -103,4 +103,65 @@ FUNCTION_NAME: #endif //defined(__XS3A__) +#if defined(__VX4B__) +#define FUNCTION_NAME count_mips +#define NSTACK_BYTES 16 +#define LOOP_INST 8 + +.section .data +.p2align 3 +.global tick_count +.global inst_count +tick_count: .word 0, 0 +inst_count: .word 0, 0 + +.section .text +.p2align 3 +.globl FUNCTION_NAME +.type FUNCTION_NAME,@function + +FUNCTION_NAME: + xm.entsp NSTACK_BYTES + // Never returns, so no need to save any registers + // Get pointers for the two counters + lui t3, %hi(tick_count) + addi t3, t3, %lo(tick_count) + mv a0, t3 + lui t3, %hi(inst_count) + addi t3, t3, %lo(inst_count) + mv a1, t3 + // Initialize counters to 0 + { li a3, 0 ; li s2, 0 } + { li s3, 0 ; li s4, 0 } + // maccu coefficient is s5 because we just want to add + { li s5, 1 ; nop } + // initialize the last timestamp, and jump to the loop + { xm.gettime a2 ; xm.bu .L_loop_top } +.p2align 4 +.L_loop_top: + // this loop should be 8 thread cycles long (no FNOPS needed) + xm.ldcu s6, LOOP_INST + // increment instruction counter + xm.maccu s3, s4, s5, s6 + // Get current time + xm.gettime s6 + // Subtract previous time + { sub s6, s6, a2 ; mv a2, s6 } + // increment tick counter + xm.maccu a3, s2, s5, s6 + // Store both counters in memory, and repeat + xm.stdi s2, a3, 0(a0) + xm.stdi s4, s3, 0(a1) + xm.bu .L_loop_top +.L_loop_bot: +.L_func_end: + 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 // __VX4B__ diff --git a/tests/signal/profile/mic_array_memory.json b/tests/signal/profile/mic_array_memory.json index f12f52b7..34a4881f 100644 --- a/tests/signal/profile/mic_array_memory.json +++ b/tests/signal/profile/mic_array_memory.json @@ -1,34 +1,34 @@ { "1mic_custom": { "available": 524288, - "used": 12572, + "used": 12636, "status": "OKAY", - "stack": 572, - "code": 8070, - "data": 3930 + "stack": 580, + "code": 8102, + "data": 3954 }, "1mic_default": { "available": 524288, - "used": 16580, + "used": 17092, "status": "OKAY", - "stack": 636, - "code": 9058, - "data": 6886 + "stack": 644, + "code": 9230, + "data": 7218 }, "2mic_custom": { "available": 524288, - "used": 13964, + "used": 14036, "status": "OKAY", - "stack": 580, - "code": 8278, - "data": 5106 + "stack": 588, + "code": 8326, + "data": 5122 }, "2mic_default": { "available": 524288, - "used": 18084, + "used": 18668, "status": "OKAY", - "stack": 636, - "code": 9378, - "data": 8070 + "stack": 652, + "code": 9550, + "data": 8466 } } \ No newline at end of file diff --git a/tests/signal/profile/mic_array_memory_table.rst b/tests/signal/profile/mic_array_memory_table.rst index dd73fb18..7c00e8bd 100644 --- a/tests/signal/profile/mic_array_memory_table.rst +++ b/tests/signal/profile/mic_array_memory_table.rst @@ -12,25 +12,25 @@ - Data * - 1mic_custom - 524288 - - 12572 - - 572 - - 8070 - - 3930 + - 12636 + - 580 + - 8102 + - 3954 * - 1mic_default - 524288 - - 16580 - - 636 - - 9058 - - 6886 + - 17092 + - 644 + - 9230 + - 7218 * - 2mic_custom - 524288 - - 13964 - - 580 - - 8278 - - 5106 + - 14036 + - 588 + - 8326 + - 5122 * - 2mic_default - 524288 - - 18084 - - 636 - - 9378 - - 8070 \ No newline at end of file + - 18668 + - 652 + - 9550 + - 8466 \ No newline at end of file diff --git a/tests/signal/profile/mic_array_mips.json b/tests/signal/profile/mic_array_mips.json deleted file mode 100644 index 12f472d2..00000000 --- a/tests/signal/profile/mic_array_mips.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "1mic_isr_16000fs": 14.1459, - "1mic_isr_32000fs": 17.2336, - "1mic_isr_48000fs": 21.3055, - "1mic_thread_16000fs": 12.9298, - "1mic_thread_32000fs": 15.9536, - "1mic_thread_48000fs": 19.9614, - "2mic_isr_16000fs": 29.3098, - "2mic_isr_32000fs": 34.6215, - "2mic_isr_48000fs": 41.9335, - "2mic_thread_16000fs": 27.0056, - "2mic_thread_32000fs": 32.2854, - "2mic_thread_48000fs": 39.5335 -} \ No newline at end of file diff --git a/tests/signal/profile/mic_array_mips_table.rst b/tests/signal/profile/mic_array_mips_table.rst index b5f738ef..7619596b 100644 --- a/tests/signal/profile/mic_array_mips_table.rst +++ b/tests/signal/profile/mic_array_mips_table.rst @@ -11,48 +11,48 @@ * - 1 - ISR - 16000 - - 14.146 + - 13.906 * - 1 - ISR - 32000 - - 17.234 + - 16.945 * - 1 - ISR - 48000 - - 21.305 + - 20.970 * - 1 - THREAD - 16000 - - 12.930 + - 12.498 * - 1 - THREAD - 32000 - - 15.954 + - 15.474 * - 1 - THREAD - 48000 - - 19.961 + - 19.433 * - 2 - ISR - 16000 - - 29.310 + - 28.907 * - 2 - ISR - 32000 - - 34.621 + - 34.269 * - 2 - ISR - 48000 - - 41.934 + - 41.645 * - 2 - THREAD - 16000 - - 27.006 + - 26.174 * - 2 - THREAD - 32000 - - 32.285 + - 31.485 * - 2 - THREAD - 48000 - - 39.533 \ No newline at end of file + - 38.765 \ No newline at end of file diff --git a/tests/signal/profile/mic_array_mips_vx4.json b/tests/signal/profile/mic_array_mips_vx4.json new file mode 100644 index 00000000..d0f837b8 --- /dev/null +++ b/tests/signal/profile/mic_array_mips_vx4.json @@ -0,0 +1,8 @@ +{ + "1mic_thread_16000fs": 10.81, + "1mic_thread_32000fs": 13.16, + "1mic_thread_48000fs": 16.50, + "2mic_thread_16000fs": 22.74, + "2mic_thread_32000fs": 26.94, + "2mic_thread_48000fs": 33.10 +} diff --git a/tests/signal/profile/mic_array_mips_xs3.json b/tests/signal/profile/mic_array_mips_xs3.json new file mode 100644 index 00000000..89917c26 --- /dev/null +++ b/tests/signal/profile/mic_array_mips_xs3.json @@ -0,0 +1,14 @@ +{ + "1mic_isr_16000fs": 13.9057, + "1mic_isr_32000fs": 16.9454, + "1mic_isr_48000fs": 20.9695, + "1mic_thread_16000fs": 12.4976, + "1mic_thread_32000fs": 15.4736, + "1mic_thread_48000fs": 19.4335, + "2mic_isr_16000fs": 28.9067, + "2mic_isr_32000fs": 34.2693, + "2mic_isr_48000fs": 41.6454, + "2mic_thread_16000fs": 26.1739, + "2mic_thread_32000fs": 31.4854, + "2mic_thread_48000fs": 38.7653 +} \ No newline at end of file diff --git a/tests/signal/profile/test_measure_mips.py b/tests/signal/profile/test_measure_mips.py index 37185a28..bedfeffa 100644 --- a/tests/signal/profile/test_measure_mips.py +++ b/tests/signal/profile/test_measure_mips.py @@ -7,6 +7,34 @@ import re import json +cwd = Path(__file__).parent + +def get_xcc_version() -> str: + output = subprocess.check_output(["xcc", "--version"], text=True) + for line in output.splitlines(): + if line.startswith("XTC version:"): + return line.split(":")[1].strip() + raise RuntimeError("XTC version not found") + +def get_mips_file() -> Path: + xcc_version = get_xcc_version() + if "15.3.1" in xcc_version: + mips_file = cwd / "mic_array_mips_xs3.json" + elif "99.99.99" in xcc_version: + mips_file = cwd / "mic_array_mips_vx4.json" + else: + raise RuntimeError(f"Unsupported XCC version: {xcc_version}") + return mips_file + +def get_isr_list(): + xcc_version = get_xcc_version() + if "15.3.1" in xcc_version: + return ["isr", "thread"] + elif "99.99.99" in xcc_version: + return ["thread"] # Only thread-based PDM handling supported on VX4 + else: + raise RuntimeError(f"Unsupported XCC version: {xcc_version}") + def max_mips(lines): mips_values = [] for line in lines: @@ -74,21 +102,22 @@ def test_measure_mips(pytestconfig): mic_array_mips_table.rst - autogenerated RST table of results """ update = pytestconfig.getoption("--update") - cwd = Path(__file__).parent mics = [1, 2] - pdmrx = ["isr", "thread"] + pdmrx = get_isr_list() fs = [16000, 32000, 48000] results = {} + print("\n\n") for chans, pdmrx_type, samp_freq in itertools.product(mics, pdmrx, fs): cfg = f"{chans}mic_{pdmrx_type}_{samp_freq}fs" xe_path = f'{cwd}/app_mips/bin/{cfg}/test_mips_{cfg}.xe' assert Path(xe_path).exists(), f"Cannot find {xe_path}" ret = subprocess.run(["xrun", "--xscope", "--id", "0", xe_path], capture_output=True, text=True, check=True, timeout=15) results[cfg] = max_mips(ret.stdout.splitlines()) + print(f"Measured {results[cfg]:.4f} MIPS for config {cfg}") # Compare against mic_array_mips.json that's already there to ensure MIPS # number are in the same ballpark, before overwriting mic_array_mips.json - outfile = cwd / "mic_array_mips.json" + outfile = get_mips_file() with outfile.open("r") as f: ref_data = json.load(f) for cfg in ref_data: @@ -96,7 +125,7 @@ def test_measure_mips(pytestconfig): assert cfg in results, f"cfg {cfg} not found in results.\nresults = {results}" test_mips = results[cfg] if not update: - threshold = 0.05 + threshold = 0.50 #TODO replace by 0.05 once stable assert abs(test_mips - ref_mips) < threshold, (f"For cfg {cfg}, test_mips {test_mips} differ " f"from ref_mips {ref_mips} by more than the allowed threshold of {threshold}.\n" f"If this is expected, run test with pytest test_measure_mips --update " @@ -109,5 +138,3 @@ def test_measure_mips(pytestconfig): # RST table output rst_out = cwd / "mic_array_mips_table.rst" write_rst_table(results, rst_out) - - From cee5d87d10fd9efd6adb5e6fd9b62ff293257008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 19 Mar 2026 10:49:18 +0000 Subject: [PATCH 06/17] Add support for single stage decimation and mic_array task in C --- doc/exclude_patterns.inc | 1 + lib_mic_array/api/mic_array/cpp/Decimator.hpp | 68 ++++- lib_mic_array/api/mic_array/cpp/MicArray.hpp | 43 ++- lib_mic_array/api/mic_array/cpp/PdmRx.hpp | 102 +++---- .../api/mic_array/cpp/ThreeStageDecimator.hpp | 11 +- .../api/mic_array/etc/xcore_compat.h | 2 - lib_mic_array/api/mic_array/impl/setup_impl.h | 2 +- .../api/mic_array/mic_array_conf_struct.h | 3 + lib_mic_array/api/mic_array/mic_array_task.h | 2 + lib_mic_array/api/mic_array/setup.h | 2 +- lib_mic_array/src/mic_array_setup.c | 9 +- lib_mic_array/src/mic_array_task.c | 106 ++++++++ lib_mic_array/src/mic_array_task.cpp | 250 +++++++++++------- lib_mic_array/src/mic_array_task_internal.hpp | 93 +++---- python/mic_array/filters.py | 7 +- 15 files changed, 480 insertions(+), 221 deletions(-) create mode 100644 lib_mic_array/src/mic_array_task.c diff --git a/doc/exclude_patterns.inc b/doc/exclude_patterns.inc index 3a948a03..f5a5bea5 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/lib_mic_array/api/mic_array/cpp/Decimator.hpp b/lib_mic_array/api/mic_array/cpp/Decimator.hpp index 06003b59..7a26eac4 100644 --- a/lib_mic_array/api/mic_array/cpp/Decimator.hpp +++ b/lib_mic_array/api/mic_array/cpp/Decimator.hpp @@ -63,8 +63,14 @@ class TwoStageDecimator * Per-mic channel filter state (PDM history) size in 32-bit words for stage-1 filter. */ unsigned pdm_history_sz; + + unsigned pdm_out_words_per_mic; } stage1; + public: + chanend_t c_decimator; + constexpr TwoStageDecimator() noexcept { } + /** * Stage 2 decimation configuration and state. */ @@ -79,10 +85,6 @@ class TwoStageDecimator unsigned decimation_factor; } stage2; - public: - - constexpr TwoStageDecimator() noexcept { } - /** * @brief Initialize the two-stage decimator from a configuration struct * @ref mic_array_decimator_conf_t @p decimator_conf @@ -95,7 +97,7 @@ class TwoStageDecimator * * @param decimator_conf Decimator pipeline configuration. */ - void Init(mic_array_decimator_conf_t &decimator_conf); + void Init(mic_array_decimator_conf_t &decimator_conf, unsigned pdm_out_words_per_mic); /** * @brief Process one block of PDM data. @@ -126,6 +128,22 @@ class TwoStageDecimator void ProcessBlock( int32_t sample_out[MIC_COUNT], uint32_t *pdm_block); + + /** + * @brief Process a single mic, 2 sample PDM block using only the 1st stage decimation filters + * + * Consumes two PDM words from `pdm_block` and runs the + * stage-1 FIR twice. Two output samples are written to + * `sample_out[0]` and `sample_out[1]`. This path is used in low-power + * configurations where only the stage-1 filter is active. + * + * @param sample_out Output sample vector with two consecutive samples. + * @param pdm_block PDM data to be processed (two words). + */ + void ProcessBlockSingleStage( + int32_t *sample_out, + uint32_t *pdm_block); + }; } @@ -134,20 +152,25 @@ class TwoStageDecimator ////////////////////////////////////////////// template -void mic_array::TwoStageDecimator::Init( - mic_array_decimator_conf_t &decimator_conf) +void mic_array::TwoStageDecimator + ::Init( + mic_array_decimator_conf_t &decimator_conf, + unsigned pdm_out_words_per_mic) { this->stage1.filter_coef = (const uint32_t*)decimator_conf.filter_conf[0].coef; this->stage1.pdm_history_ptr = (uint32_t*)decimator_conf.filter_conf[0].state; this->stage1.pdm_history_sz = decimator_conf.filter_conf[0].state_words_per_channel; + this->stage1.pdm_out_words_per_mic = pdm_out_words_per_mic; memset(this->stage1.pdm_history_ptr, 0x55, sizeof(int32_t) * MIC_COUNT * this->stage1.pdm_history_sz); - for(int k = 0; k < MIC_COUNT; k++){ - filter_fir_s32_init(&this->stage2.filters[k], decimator_conf.filter_conf[1].state + (k * decimator_conf.filter_conf[1].state_words_per_channel), - decimator_conf.filter_conf[1].num_taps, decimator_conf.filter_conf[1].coef, decimator_conf.filter_conf[1].shr); + if(decimator_conf.num_filter_stages == 2) { + for(int k = 0; k < MIC_COUNT; k++){ + filter_fir_s32_init(&this->stage2.filters[k], decimator_conf.filter_conf[1].state + (k * decimator_conf.filter_conf[1].state_words_per_channel), + decimator_conf.filter_conf[1].num_taps, decimator_conf.filter_conf[1].coef, decimator_conf.filter_conf[1].shr); + } + this->stage2.decimation_factor = decimator_conf.filter_conf[1].decimation_factor; } - this->stage2.decimation_factor = decimator_conf.filter_conf[1].decimation_factor; } @@ -175,11 +198,32 @@ void mic_array::TwoStageDecimator } +template +void mic_array::TwoStageDecimator + ::ProcessBlockSingleStage( + int32_t *sample_out, + uint32_t *pdm_block) +{ + uint32_t* hist = this->stage1.pdm_history_ptr; + for(unsigned k = 0; k < this->stage1.pdm_out_words_per_mic; k++) { + hist[0] = pdm_block[k]; + sample_out[k] = fir_1x16_bit(hist, this->stage1.filter_coef); + shift_buffer(hist); + } +} + static inline 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/MicArray.hpp b/lib_mic_array/api/mic_array/cpp/MicArray.hpp index f9bf9dbf..8cb7d72d 100644 --- a/lib_mic_array/api/mic_array/cpp/MicArray.hpp +++ b/lib_mic_array/api/mic_array/cpp/MicArray.hpp @@ -5,8 +5,9 @@ #include #include -#include +#include #include +#include #include #include @@ -177,6 +178,19 @@ namespace mic_array { * OutputHandler. */ void ThreadEntry(); + + /** + * @brief Entry point for the low-power single-stage decimation thread. + * + * This function loops, collecting PDM + * blocks from @ref PdmRx and running the single-stage decimator. Each + * block produces two output samples which are delivered sequentially + * through @ref OutputHandler. On shutdown it calls @ref PdmRx::Shutdown() + * and then completes the output shutdown handshake. + */ + void ThreadEntryLowPower_1Mic1StgDecimator(); + + static constexpr unsigned MAX_PDM_OUT_WORDS_PER_CHANNEL = 10; }; } @@ -208,3 +222,30 @@ void mic_array::MicArray +void mic_array::MicArray::ThreadEntryLowPower_1Mic1StgDecimator() +{ + volatile bool shutdown = false; + chanend_t c_frame_out = OutputHandler.FrameTx.GetChannel(); + unsigned pdm_out_words_per_channel = PdmRx.pdm_out_words_per_channel; + int32_t sample_out[MAX_PDM_OUT_WORDS_PER_CHANNEL]; + + while(!shutdown){ + uint32_t *pdm_samples = PdmRx.GetPdmBlockLowPowerOneMic(); + Decimator.ProcessBlockSingleStage(sample_out, pdm_samples); + shutdown = ma_frame_tx(c_frame_out, + reinterpret_cast(sample_out), + 1, pdm_out_words_per_channel); + } + PdmRx.Shutdown(); + OutputHandler.CompleteShutdown(); // Exchange end token with the app to close channel and indicate completion. + // ma_shutdown() will now return + return; +} \ No newline at end of file diff --git a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp index 938b1968..31154817 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 } - } @@ -325,8 +326,9 @@ namespace mic_array { uint32_t* blocks[2]; volatile bool shutdown = false; volatile bool shutdown_complete = false; - uint32_t pdm_out_words_per_channel; // number of 32-sample subblocks per channel uint32_t num_phases; + uint32_t num_channels_in; + uint32_t num_channels_out; /** * @brief Streaming channel over which PDM blocks are sent. @@ -350,21 +352,6 @@ namespace mic_array { public: - /** - * @brief Read a word of PDM data from the port. - * - * @return A `uint32_t` containing 32 PDM samples. If `MIC_COUNT >= 2` the - * samples from each port will be interleaved together. - */ - uint32_t ReadPort(); - - /** - * @brief Send a block of PDM data to a listener. - * - * @param block PDM data to send. - */ - void SendBlock(uint32_t *block); - /** * @brief Initialize the PDM RX service. * @@ -441,6 +428,8 @@ namespace mic_array { */ uint32_t* GetPdmBlock(); + uint32_t* GetPdmBlockLowPowerOneMic(); + /** * @brief Set whether dropped PDM samples should cause an assertion. * @@ -465,8 +454,9 @@ namespace mic_array { * every iteration. */ void ThreadEntry(); - }; + uint32_t pdm_out_words_per_channel; // number of 32-sample subblocks per channel + }; } ////////////////////////////////////////////// @@ -488,7 +478,7 @@ template void mic_array::StandardPdmRxService::ThreadEntry() { while(1){ - this->blocks[0][--phase] = this->ReadPort(); + this->blocks[0][--phase] = port_in(this->p_pdm_mics); if(!phase){ this->phase = this->num_phases; @@ -496,7 +486,7 @@ void mic_array::StandardPdmRxService::ThreadEntry() this->blocks[0] = this->blocks[1]; this->blocks[1] = ready_block; - this->SendBlock(ready_block); + s_chan_out_word(this->c_pdm_blocks.end_a, reinterpret_cast(ready_block)); // Check for shutdown only after sending a block so we know there's atleast one pending block at the time of shutdown if(this->shutdown) { @@ -508,29 +498,15 @@ void mic_array::StandardPdmRxService::ThreadEntry() } -template -uint32_t mic_array::StandardPdmRxService - ::ReadPort() -{ - return port_in(this->p_pdm_mics); -} - - -template -void mic_array::StandardPdmRxService - ::SendBlock(uint32_t *block) -{ - s_chan_out_word(this->c_pdm_blocks.end_a, - reinterpret_cast( &block[0] )); -} - template void mic_array::StandardPdmRxService ::Init(port_t p_pdm_mics, pdm_rx_conf_t &pdm_rx_config) { + this->num_channels_in = pdm_rx_config.num_channels_in; + this->num_channels_out = pdm_rx_config.num_channels_out; this->pdm_out_block_ptr = pdm_rx_config.pdm_out_block; this->pdm_out_words_per_channel = pdm_rx_config.pdm_out_words_per_channel; - this->num_phases = CHANNELS_IN * this->pdm_out_words_per_channel; + this->num_phases = this->num_channels_in * this->pdm_out_words_per_channel; this->phase = this->num_phases; @@ -595,14 +571,15 @@ template uint32_t* mic_array::StandardPdmRxService ::GetPdmBlock() { - // Has to be in a critical section to avoid race conditions with ISR. - interrupt_mask_all(); - // Limiting credit to 1 prevents the ISR from attempting to enqueue an additional block - // while two buffers are already occupied (which would happen if the ISR gets triggered between interrupt_unmask_all() - // and s_chan_in_word()), thereby avoiding deadlock. - pdm_rx_isr_context.credit = 1; - interrupt_unmask_all(); - + if(this->isr_used) { + // Has to be in a critical section to avoid race conditions with ISR. + interrupt_mask_all(); + // Limiting credit to 1 prevents the ISR from attempting to enqueue an additional block + // while two buffers are already occupied (which would happen if the ISR gets triggered between interrupt_unmask_all() + // and s_chan_in_word()), thereby avoiding deadlock. + pdm_rx_isr_context.credit = 1; + interrupt_unmask_all(); + } uint32_t* full_block = (uint32_t*) s_chan_in_word(this->c_pdm_blocks.end_b); mic_array::deinterleave_pdm_samples(full_block, this->pdm_out_words_per_channel); @@ -619,6 +596,36 @@ uint32_t* mic_array::StandardPdmRxService return this->pdm_out_block_ptr; } +template +uint32_t* mic_array::StandardPdmRxService + ::GetPdmBlockLowPowerOneMic() +{ + if(this->isr_used) { + // Has to be in a critical section to avoid race conditions with ISR. + interrupt_mask_all(); + // Limiting credit to 1 prevents the ISR from attempting to enqueue an additional block + // while two buffers are already occupied (which would happen if the ISR gets triggered between interrupt_unmask_all() + // and s_chan_in_word()), thereby avoiding deadlock. + pdm_rx_isr_context.credit = 1; + interrupt_unmask_all(); + } + + uint32_t* full_block = (uint32_t*) s_chan_in_word(this->c_pdm_blocks.end_b); + mic_array::deinterleave_pdm_samples<1>(full_block, this->pdm_out_words_per_channel); + + uint32_t (*block)[1] = (uint32_t (*)[1]) full_block; + uint32_t *out_ptr; + for(int ch = 0; ch < 1; ch++) { + out_ptr = this->pdm_out_block_ptr + (ch * this->pdm_out_words_per_channel); + for(int sb = 0; sb < this->pdm_out_words_per_channel; sb++) { + unsigned d = this->channel_map[ch]; + out_ptr[sb] = block[this->pdm_out_words_per_channel - 1 - sb][d]; + } + } + return this->pdm_out_block_ptr; +} + + template void mic_array::StandardPdmRxService ::Shutdown() { @@ -641,7 +648,8 @@ void mic_array::StandardPdmRxService continue; } // Now that we're sure that PdmRx thread has exited, drain any pending blocks - SELECT_RES(CASE_THEN(this->c_pdm_blocks.end_b, rx_pending_block), + chanend_t c_pdm_blocks_end_b = this->c_pdm_blocks.end_b; + SELECT_RES(CASE_THEN(c_pdm_blocks_end_b, rx_pending_block), DEFAULT_THEN(empty)) { rx_pending_block: diff --git a/lib_mic_array/api/mic_array/cpp/ThreeStageDecimator.hpp b/lib_mic_array/api/mic_array/cpp/ThreeStageDecimator.hpp index 57cc436e..5510bf53 100644 --- a/lib_mic_array/api/mic_array/cpp/ThreeStageDecimator.hpp +++ b/lib_mic_array/api/mic_array/cpp/ThreeStageDecimator.hpp @@ -52,6 +52,8 @@ class ThreeStageDecimator * Per-mic channel filter state (PDM history) size in 32-bit words for stage-1 filter. */ unsigned pdm_history_sz; + + unsigned pdm_out_words_per_mic; } stage1; /** @@ -98,7 +100,7 @@ class ThreeStageDecimator * * @param decimator_conf Decimator pipeline configuration. */ - void Init(mic_array_decimator_conf_t &decimator_conf); + void Init(mic_array_decimator_conf_t &decimator_conf, unsigned pdm_out_words_per_mic); /** * @brief Process one block of PDM data. @@ -137,12 +139,15 @@ class ThreeStageDecimator ////////////////////////////////////////////// template -void mic_array::ThreeStageDecimator::Init( - mic_array_decimator_conf_t &decimator_conf) +void mic_array::ThreeStageDecimator + ::Init( + mic_array_decimator_conf_t &decimator_conf, + unsigned pdm_out_words_per_mic) { this->stage1.filter_coef = (const uint32_t*)decimator_conf.filter_conf[0].coef; this->stage1.pdm_history_ptr = (uint32_t*)decimator_conf.filter_conf[0].state; this->stage1.pdm_history_sz = decimator_conf.filter_conf[0].state_words_per_channel; + this->stage1.pdm_out_words_per_mic = pdm_out_words_per_mic; memset(this->stage1.pdm_history_ptr, 0x55, sizeof(int32_t) * MIC_COUNT * this->stage1.pdm_history_sz); 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/mic_array_conf_struct.h b/lib_mic_array/api/mic_array/mic_array_conf_struct.h index 3eff142a..d3ad27e2 100644 --- a/lib_mic_array/api/mic_array/mic_array_conf_struct.h +++ b/lib_mic_array/api/mic_array/mic_array_conf_struct.h @@ -136,6 +136,9 @@ typedef struct { * stage decimation filter's decimation factor (in case of a 2 stage decimator). */ unsigned pdm_out_words_per_channel; // per channel pdm rx output block (input to the decimator) size + + unsigned num_channels_in; + unsigned num_channels_out; }pdm_rx_conf_t; diff --git a/lib_mic_array/api/mic_array/mic_array_task.h b/lib_mic_array/api/mic_array/mic_array_task.h index 8b64e171..19d920a4 100644 --- a/lib_mic_array/api/mic_array/mic_array_task.h +++ b/lib_mic_array/api/mic_array/mic_array_task.h @@ -56,6 +56,8 @@ void mic_array_init(pdm_rx_resources_t *pdm_res, const unsigned *channel_map, un MA_C_API void mic_array_init_custom_filter(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf); +MA_C_API +void mic_array_init_custom_filter_1mic_1stg_decimator(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf); /** * @brief Start the mic array task * 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/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.c b/lib_mic_array/src/mic_array_task.c new file mode 100644 index 00000000..85fdda5b --- /dev/null +++ b/lib_mic_array/src/mic_array_task.c @@ -0,0 +1,106 @@ +// Copyright 2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include +#include + +#include "mic_array.h" +#include "mic_array_task_internal.hpp" + +//////////////////// +// Mic array init // +//////////////////// +void mic_array_init(pdm_rx_resources_t *pdm_res, const unsigned *channel_map, unsigned output_samp_freq) +{ + unsigned stg2_decimation_factor = (pdm_res->pdm_freq/STAGE1_DEC_FACTOR)/output_samp_freq; + assert ((output_samp_freq*STAGE1_DEC_FACTOR*stg2_decimation_factor) == pdm_res->pdm_freq); // assert if it doesn't divide cleanly + // assert if unsupported decimation factor. (for example. when starting with a pdm_freq of 3.072MHz, supported + // output sampling freqs are [48000, 32000, 16000] + assert ((stg2_decimation_factor == 2) || (stg2_decimation_factor == 3) || (stg2_decimation_factor == 6)); + + bool use_3_stg_decimator = false; + init_mic_array_storage(use_3_stg_decimator); + init_mics_default_filter(pdm_res, channel_map, stg2_decimation_factor); +} + +void mic_array_init_custom_filter(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf) +{ + assert(pdm_res); + assert(mic_array_conf); + assert(mic_array_conf->decimator_conf.num_filter_stages == 2 || + mic_array_conf->decimator_conf.num_filter_stages == 3); + + init_mic_array_storage(mic_array_conf->decimator_conf.num_filter_stages == 3); + init_mics_custom_filter(pdm_res, mic_array_conf); + + // Configure and start clocks + const unsigned divide = pdm_res->mclk_freq / pdm_res->pdm_freq; + mic_array_resources_configure(pdm_res, divide); + mic_array_pdm_clock_start(pdm_res); +} + +void mic_array_init_custom_filter_1mic_1stg_decimator(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf) +{ + assert(pdm_res); + assert(mic_array_conf); + assert(mic_array_conf->decimator_conf.num_filter_stages == 1); + + init_mic_array_storage(mic_array_conf->decimator_conf.num_filter_stages == 3); + init_mics_custom_filter_1mic_1stg_decimator(pdm_res, mic_array_conf); + + // Configure and start clocks + const unsigned divide = pdm_res->mclk_freq / pdm_res->pdm_freq; + mic_array_resources_configure(pdm_res, divide); + mic_array_pdm_clock_start(pdm_res); +} + +///////////////////// +// Mic array start // +///////////////////// + +// Parallel jobs for when XUA_PDM_MIC_USE_PDM_ISR == 0, run separate decimator and pdm rx tasks +DECLARE_JOB(default_ma_task_start_pdm, (void)); +void default_ma_task_start_pdm(void) +{ + start_pdm_task(); +} + +DECLARE_JOB(default_ma_task_start_decimator, (void)); +void default_ma_task_start_decimator() +{ + start_decimator_task(); +} + +DECLARE_JOB(default_ma_task_start_pdm_3stg, (void)); +void default_ma_task_start_pdm_3stg(void) +{ + start_pdm_task_3stg(); +} + +DECLARE_JOB(default_ma_task_start_decimator_3stg, (void)); +void default_ma_task_start_decimator_3stg(void) +{ + start_decimator_task_3stg(); +} + +void mic_array_start(chanend_t c_frames_out) +{ +#if MIC_ARRAY_CONFIG_USE_PDM_ISR + start_mic_array_pdm_isr(c_frames_out); +#else + set_output_channel(c_frames_out); + bool use_3_stg_decimator = get_decimator_stg_count(); + if (use_3_stg_decimator) { + PAR_JOBS( + PJOB(default_ma_task_start_pdm_3stg, ()), + PJOB(default_ma_task_start_decimator_3stg, ())); + } else { + PAR_JOBS( + PJOB(default_ma_task_start_pdm, ()), + PJOB(default_ma_task_start_decimator, ())); + } +#endif // MIC_ARRAY_CONFIG_USE_PDM_ISR + shutdown_mic_array(); +} diff --git a/lib_mic_array/src/mic_array_task.cpp b/lib_mic_array/src/mic_array_task.cpp index f9691534..c827a9f5 100644 --- a/lib_mic_array/src/mic_array_task.cpp +++ b/lib_mic_array/src/mic_array_task.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -12,41 +11,90 @@ #include "mic_array/etc/filters_default.h" #include "mic_array_task_internal.hpp" -TMicArray *g_mics = nullptr; // Global mic array instance. -TMicArray_3stg_decimator *g_mics_3stg = nullptr; -bool use_3_stg_decimator = false; -// NOTE: g_mics must persist (remain non-null and its backing storage valid) +static TMicArray *s_mics = nullptr; +static TMicArray_3stg_decimator *s_mics_3stg = nullptr; +static bool s_use_3_stg_decimator = false; +static bool s_run_1mic_1stg_decimator = false; +// NOTE: s_mics or s_mics_3stg must persist (remain non-null with its backing storage valid) // until mic_array_start() completes. mic_array_start() performs shutdown and -// then sets g_mics back to nullptr. +// then sets s_mics or s_mics_3stg back to nullptr. + +#if !defined (__XS2A__) +///////////////////////////// +// Static variable getters // +///////////////////////////// +bool get_decimator_stg_count(void) +{ + return s_use_3_stg_decimator; +} -#if !defined(__XS2A__) //////////////////// // Mic array init // //////////////////// -void mic_array_init(pdm_rx_resources_t *pdm_res, const unsigned *channel_map, unsigned output_samp_freq) +void init_mics_default_filter(pdm_rx_resources_t* pdm_res, const unsigned* channel_map, unsigned stg2_dec_factor) { - assert(g_mics == nullptr); // Mic array instance already initialised + static int32_t stg1_filter_state[MIC_ARRAY_CONFIG_MIC_COUNT][8]; + mic_array_decimator_conf_t decimator_conf; + memset(&decimator_conf, 0, sizeof(decimator_conf)); + mic_array_filter_conf_t filter_conf[2] = {{0}}; + + // decimator + decimator_conf.filter_conf = &filter_conf[0]; + decimator_conf.num_filter_stages = 2; + //filter stage 1 + filter_conf[0].coef = (int32_t*)stage_1_filter(stg2_dec_factor); + filter_conf[0].num_taps = 256; + filter_conf[0].decimation_factor = 32; + filter_conf[0].shr = 0; + filter_conf[0].state_words_per_channel = filter_conf[0].num_taps/32; + filter_conf[0].state = (int32_t*)stg1_filter_state; + + // filter stage 2 + filter_conf[1].coef = (int32_t*)stage_2_filter(stg2_dec_factor); + filter_conf[1].num_taps = stage_2_num_taps(stg2_dec_factor); + filter_conf[1].decimation_factor = stg2_dec_factor; + filter_conf[1].shr = stage_2_shift(stg2_dec_factor); + filter_conf[1].state_words_per_channel = decimator_conf.filter_conf[1].num_taps; + filter_conf[1].state = stage_2_state_memory(stg2_dec_factor); + + pdm_rx_conf_t pdm_rx_config; + pdm_rx_config.pdm_out_words_per_channel = stg2_dec_factor; + pdm_rx_config.pdm_out_block = get_pdm_rx_out_block(stg2_dec_factor); + pdm_rx_config.pdm_in_double_buf = get_pdm_rx_out_block_double_buf(stg2_dec_factor); + pdm_rx_config.num_channels_in = MIC_ARRAY_CONFIG_MIC_IN_COUNT; + pdm_rx_config.num_channels_out = MIC_ARRAY_CONFIG_MIC_COUNT; + + s_mics->Decimator.Init(decimator_conf, pdm_rx_config.pdm_out_words_per_channel); + + s_mics->PdmRx.Init(pdm_res->p_pdm_mics, pdm_rx_config); + + if(channel_map) { + s_mics->PdmRx.MapChannels(channel_map); + } - use_3_stg_decimator = false; + int divide = pdm_res->mclk_freq / pdm_res->pdm_freq; + mic_array_resources_configure(pdm_res, divide); + mic_array_pdm_clock_start(pdm_res); +} - unsigned stg2_decimation_factor = (pdm_res->pdm_freq/STAGE1_DEC_FACTOR)/output_samp_freq; - assert ((output_samp_freq*STAGE1_DEC_FACTOR*stg2_decimation_factor) == pdm_res->pdm_freq); // assert if it doesn't divide cleanly - // assert if unsupported decimation factor. (for example. when starting with a pdm_freq of 3.072MHz, supported - // output sampling freqs are [48000, 32000, 16000] - assert ((stg2_decimation_factor == 2) || (stg2_decimation_factor == 3) || (stg2_decimation_factor == 6)); - static uint8_t __attribute__((aligned(8))) mic_storage[sizeof(TMicArray)]; - g_mics = new (mic_storage) TMicArray(); - init_mics_default_filter(g_mics, pdm_res, channel_map, stg2_decimation_factor); +void init_mic_array_storage(bool use_3_stg_decimator) +{ + assert(s_mics == nullptr && s_mics_3stg == nullptr); // Mic array instance already initialised + s_use_3_stg_decimator = use_3_stg_decimator; + if(s_use_3_stg_decimator) { + static uint8_t __attribute__((aligned(8))) mic_storage[sizeof(TMicArray_3stg_decimator)]; + s_mics_3stg = new (mic_storage) TMicArray_3stg_decimator(); + } else { + static uint8_t __attribute__((aligned(8))) mic_storage[sizeof(TMicArray)]; + s_mics = new (mic_storage) TMicArray(); + } } template -static inline void init_from_conf(TMics*& mics_ptr, - uint8_t* storage, - pdm_rx_resources_t* pdm_res, - mic_array_conf_t* conf) { - mics_ptr = new (storage) TMics(); - mics_ptr->Decimator.Init(conf->decimator_conf); +static inline void init_from_conf(TMics*& mics_ptr, pdm_rx_resources_t* pdm_res, mic_array_conf_t* conf) +{ + mics_ptr->Decimator.Init(conf->decimator_conf, conf->pdmrx_conf.pdm_out_words_per_channel); mics_ptr->PdmRx.Init(pdm_res->p_pdm_mics, conf->pdmrx_conf); if (conf->pdmrx_conf.channel_map) { mics_ptr->PdmRx.MapChannels(conf->pdmrx_conf.channel_map); @@ -54,69 +102,72 @@ static inline void init_from_conf(TMics*& mics_ptr, mics_ptr->PdmRx.AssertOnDroppedBlock(false); } -void mic_array_init_custom_filter(pdm_rx_resources_t* pdm_res, - mic_array_conf_t* mic_array_conf) +void init_mics_custom_filter(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf) { - assert(pdm_res); - assert(mic_array_conf); - assert(g_mics == nullptr && g_mics_3stg == nullptr); - static uint8_t __attribute__((aligned(8))) mic_storage[sizeof(UAnyMicArray)]; - - if(mic_array_conf->decimator_conf.num_filter_stages == 2) - { - use_3_stg_decimator = false; - init_from_conf(g_mics, mic_storage, pdm_res, mic_array_conf); - } - else if(mic_array_conf->decimator_conf.num_filter_stages == 3) - { - init_from_conf(g_mics_3stg, mic_storage, pdm_res, mic_array_conf); - use_3_stg_decimator = true; + if((mic_array_conf->decimator_conf.num_filter_stages == 1) || (mic_array_conf->decimator_conf.num_filter_stages == 2)) { + init_from_conf(s_mics, pdm_res, mic_array_conf); + } else if(mic_array_conf->decimator_conf.num_filter_stages == 3) { + init_from_conf(s_mics_3stg, pdm_res, mic_array_conf); + } else { + assert(false && "Unsupported number of filter stages in mic_array_conf"); } - // Configure and start clocks - const unsigned divide = pdm_res->mclk_freq / pdm_res->pdm_freq; - mic_array_resources_configure(pdm_res, divide); - mic_array_pdm_clock_start(pdm_res); } +void init_mics_custom_filter_1mic_1stg_decimator(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf) +{ + assert(mic_array_conf->pdmrx_conf.pdm_out_words_per_channel <= TMicArray::MAX_PDM_OUT_WORDS_PER_CHANNEL); + s_run_1mic_1stg_decimator = true; + init_mics_custom_filter(pdm_res, mic_array_conf); +} ///////////////////// // Mic array start // ///////////////////// - -// Parallel jobs for when XUA_PDM_MIC_USE_PDM_ISR == 0, run separate decimator and pdm rx tasks -DECLARE_JOB(default_ma_task_start_pdm, (TMicArray&)); -void default_ma_task_start_pdm(TMicArray& mics){ - mics.PdmRx.ThreadEntry(); -} - -DECLARE_JOB(default_ma_task_start_decimator, (TMicArray&, chanend_t)); -void default_ma_task_start_decimator(TMicArray& mics, chanend_t c_audio_frames){ - mics.ThreadEntry(); +void set_output_channel(chanend_t c_frames_out) +{ + if (s_use_3_stg_decimator) { + assert(s_mics_3stg != nullptr); + s_mics_3stg->OutputHandler.FrameTx.SetChannel(c_frames_out); + } else { + assert(s_mics != nullptr); + s_mics->OutputHandler.FrameTx.SetChannel(c_frames_out); + } } -DECLARE_JOB(default_ma_task_start_pdm_3stg, (TMicArray_3stg_decimator&)); -void default_ma_task_start_pdm_3stg(TMicArray_3stg_decimator& mics){ - mics.PdmRx.ThreadEntry(); -} +void shutdown_mic_array(void) +{ + if (s_use_3_stg_decimator) { + s_mics_3stg->~TMicArray_3stg_decimator(); + } + else { + s_mics->~TMicArray(); + } -DECLARE_JOB(default_ma_task_start_decimator_3stg, (TMicArray_3stg_decimator&, chanend_t)); -void default_ma_task_start_decimator_3stg(TMicArray_3stg_decimator& mics, chanend_t c_audio_frames){ - mics.ThreadEntry(); + s_mics_3stg = nullptr; + s_mics = nullptr; + s_use_3_stg_decimator = false; + s_run_1mic_1stg_decimator = false; } #if defined(__XS3A__) -#define CLRSR(c) asm volatile("clrsr %0" : : "n"(c)); +#define CLEAR_KEDI() asm volatile("clrsr %0" : : "n"(XS1_SR_KEDI_MASK)); +#elif defined(__VX4B__) +// VX4 processors do not have a dual-issue mode due to VLIW instructions. +// Remove any definition of CLEAR_KEDI so any acciddental use of it will be caught at compile time. +#undef CLEAR_KEDI #else -#define CLRSR(c) ((void)0) -#warning "CLRSR not defined for this architecture." +#undef CLEAR_KEDI // Catch at compile time if attempting to use CLEAR_KEDI on unsupported architectures. #endif -#define CLEAR_KEDI() CLRSR(XS1_SR_KEDI_MASK) template void start_mics_with_pdm_isr(TMics* mics_ptr, chanend_t c_frames_out) { assert(mics_ptr != nullptr); - CLEAR_KEDI(); + + #if defined(__XS3A__) + CLEAR_KEDI(); // Disable dual-issue mode on XS3A processors. VX4 processors do not have a dual-issue mode. + #endif + mics_ptr->OutputHandler.FrameTx.SetChannel(c_frames_out); mics_ptr->PdmRx.AssertOnDroppedBlock(false); mics_ptr->PdmRx.InstallISR(); @@ -124,56 +175,59 @@ void start_mics_with_pdm_isr(TMics* mics_ptr, chanend_t c_frames_out) mics_ptr->ThreadEntry(); } -void mic_array_start( - chanend_t c_frames_out) +void start_mic_array_pdm_isr(chanend_t c_frames_out) { #if MIC_ARRAY_CONFIG_USE_PDM_ISR - if (use_3_stg_decimator) { - start_mics_with_pdm_isr(g_mics_3stg, c_frames_out); + if (s_use_3_stg_decimator) { + start_mics_with_pdm_isr(s_mics_3stg, c_frames_out); } else { - start_mics_with_pdm_isr(g_mics, c_frames_out); - } -#else - if (use_3_stg_decimator) { - assert(g_mics_3stg != nullptr); // Attempting to start mic_array before initialising it - g_mics_3stg->OutputHandler.FrameTx.SetChannel(c_frames_out); - PAR_JOBS( - PJOB(default_ma_task_start_pdm_3stg, (*g_mics_3stg)), - PJOB(default_ma_task_start_decimator_3stg, (*g_mics_3stg, c_frames_out))); - } - else - { - g_mics->OutputHandler.FrameTx.SetChannel(c_frames_out); - PAR_JOBS( - PJOB(default_ma_task_start_pdm, (*g_mics)), - PJOB(default_ma_task_start_decimator, (*g_mics, c_frames_out))); + start_mics_with_pdm_isr(s_mics, c_frames_out); } #endif - // shutdown - if (use_3_stg_decimator) { - g_mics_3stg->~TMicArray_3stg_decimator(); - g_mics_3stg = nullptr; +} + +// Helper functions for starting separate tasks +void start_pdm_task(void) +{ + s_mics->PdmRx.ThreadEntry(); +} + +void start_decimator_task() +{ + if(s_run_1mic_1stg_decimator) { + s_mics->ThreadEntryLowPower_1Mic1StgDecimator(); } else { - g_mics->~TMicArray(); - g_mics = nullptr; + s_mics->ThreadEntry(); } } + +void start_pdm_task_3stg(void) +{ + s_mics_3stg->PdmRx.ThreadEntry(); +} + +void start_decimator_task_3stg(void) +{ + s_mics_3stg->ThreadEntry(); +} + // Override pdm data port. Only used in tests where a chanend is used as a 'port' for input pdm data. void _mic_array_override_pdm_port(chanend_t c_pdm) { - if (use_3_stg_decimator) { - assert(g_mics_3stg != nullptr); - g_mics_3stg->PdmRx.SetPort((port_t)c_pdm); + if (s_use_3_stg_decimator) { + assert(s_mics_3stg != nullptr); + s_mics_3stg->PdmRx.SetPort((port_t)c_pdm); } else { - assert(g_mics != nullptr); - g_mics->PdmRx.SetPort((port_t)c_pdm); + assert(s_mics != nullptr); + s_mics->PdmRx.SetPort((port_t)c_pdm); } } // C wrapper -extern "C" void _mic_array_override_pdm_port_c(chanend_t c_pdm) +MA_C_API +void _mic_array_override_pdm_port_c(chanend_t c_pdm) { _mic_array_override_pdm_port(c_pdm); } diff --git a/lib_mic_array/src/mic_array_task_internal.hpp b/lib_mic_array/src/mic_array_task_internal.hpp index 33386bd8..e9613d06 100644 --- a/lib_mic_array/src/mic_array_task_internal.hpp +++ b/lib_mic_array/src/mic_array_task_internal.hpp @@ -6,6 +6,7 @@ #include "mic_array.h" #include "mic_array/etc/filters_default.h" +#ifdef __cplusplus using TMicArray = mic_array::MicArray, mic_array::StandardPdmRxService>; -union UAnyMicArray { - TMicArray m_2stg; - TMicArray_3stg_decimator m_3stg; -}; - union UStg2_filter_state { int32_t filter_state_df_6[MIC_ARRAY_CONFIG_MIC_COUNT][STAGE2_TAP_COUNT]; int32_t filter_state_df_3[MIC_ARRAY_CONFIG_MIC_COUNT][MIC_ARRAY_32K_STAGE_2_TAP_COUNT]; @@ -54,11 +50,9 @@ union UPdmRx_out_block_double_buf { uint32_t __attribute__((aligned (8))) out_block_double_buf_df_2[2][MIC_ARRAY_CONFIG_MIC_IN_COUNT * 2]; }; -extern TMicArray* g_mics; - -UStg2_filter_state stg2_filter_state_mem; -UPdmRx_out_block pdm_rx_out_block; -UPdmRx_out_block_double_buf __attribute__((aligned (8))) pdm_rx_out_block_double_buf; // deinterleave() functions expect dword alignment +union UStg2_filter_state stg2_filter_state_mem; +union UPdmRx_out_block pdm_rx_out_block; +union UPdmRx_out_block_double_buf __attribute__((aligned (8))) pdm_rx_out_block_double_buf; // deinterleave() functions expect dword alignment inline const uint32_t* stage_1_filter(unsigned stg2_dec_factor) { // stg2 decimation factor also seems to affect the stage1 filter used @@ -91,45 +85,40 @@ inline uint32_t* get_pdm_rx_out_block_double_buf(unsigned stg2_dec_factor) { : (uint32_t*)pdm_rx_out_block_double_buf.out_block_double_buf_df_2); } -inline void init_mics_default_filter(TMicArray* m, pdm_rx_resources_t* pdm_res, const unsigned* channel_map, unsigned stg2_dec_factor) { - static int32_t stg1_filter_state[MIC_ARRAY_CONFIG_MIC_COUNT][8]; - mic_array_decimator_conf_t decimator_conf; - memset(&decimator_conf, 0, sizeof(decimator_conf)); - mic_array_filter_conf_t filter_conf[2] = {{0}}; - - // decimator - decimator_conf.filter_conf = &filter_conf[0]; - decimator_conf.num_filter_stages = 2; - //filter stage 1 - filter_conf[0].coef = (int32_t*)stage_1_filter(stg2_dec_factor); - filter_conf[0].num_taps = 256; - filter_conf[0].decimation_factor = 32; - filter_conf[0].shr = 0; - filter_conf[0].state_words_per_channel = filter_conf[0].num_taps/32; - filter_conf[0].state = (int32_t*)stg1_filter_state; - - // filter stage 2 - filter_conf[1].coef = (int32_t*)stage_2_filter(stg2_dec_factor); - filter_conf[1].num_taps = stage_2_num_taps(stg2_dec_factor); - filter_conf[1].decimation_factor = stg2_dec_factor; - filter_conf[1].shr = stage_2_shift(stg2_dec_factor); - filter_conf[1].state_words_per_channel = decimator_conf.filter_conf[1].num_taps; - filter_conf[1].state = stage_2_state_memory(stg2_dec_factor); - - m->Decimator.Init(decimator_conf); - - pdm_rx_conf_t pdm_rx_config; - pdm_rx_config.pdm_out_words_per_channel = stg2_dec_factor; - pdm_rx_config.pdm_out_block = get_pdm_rx_out_block(stg2_dec_factor); - pdm_rx_config.pdm_in_double_buf = get_pdm_rx_out_block_double_buf(stg2_dec_factor); - - - m->PdmRx.Init(pdm_res->p_pdm_mics, pdm_rx_config); - - if(channel_map) { - m->PdmRx.MapChannels(channel_map); - } - int divide = pdm_res->mclk_freq / pdm_res->pdm_freq; - mic_array_resources_configure(pdm_res, divide); - mic_array_pdm_clock_start(pdm_res); -} +#endif // __cplusplus + +MA_C_API +bool get_decimator_stg_count(void); + +MA_C_API +void init_mic_array_storage(bool use_3_stg_decimator); + +MA_C_API +void init_mics_custom_filter(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf); + +MA_C_API +void init_mics_custom_filter_1mic_1stg_decimator(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf); + +MA_C_API +void init_mics_default_filter(pdm_rx_resources_t* pdm_res, const unsigned* channel_map, unsigned stg2_dec_factor); + +MA_C_API +void set_output_channel(chanend_t c_frames_out); + +MA_C_API +void shutdown_mic_array(void); + +MA_C_API +void start_decimator_task(); + +MA_C_API +void start_decimator_task_3stg(void); + +MA_C_API +void start_mic_array_pdm_isr(chanend_t c_frames_out); + +MA_C_API +void start_pdm_task(void); + +MA_C_API +void start_pdm_task_3stg(void); diff --git a/python/mic_array/filters.py b/python/mic_array/filters.py index 6e11fd68..20f1f3cc 100644 --- a/python/mic_array/filters.py +++ b/python/mic_array/filters.py @@ -217,9 +217,12 @@ def DecimationFactor(self): def NumStages(self): return 2 - def Filter(self, pdm_signal: np.ndarray) -> np.ndarray: + def Filter(self, pdm_signal: np.ndarray, stg1_only=False) -> np.ndarray: s1_output = self.s1.FilterInt16(pdm_signal) - return self.s2.FilterInt32(s1_output) + if stg1_only: + return s1_output + else: + return self.s2.FilterInt32(s1_output) class ThreeStageFilter(object): From ee89cdd8d9bf16d1fa4dc92ba34f6f39f0461765 Mon Sep 17 00:00:00 2001 From: Shuchita Khare Date: Wed, 18 Mar 2026 15:58:21 +0000 Subject: [PATCH 07/17] Add support for single stage decimation. Add support for 1-mic override. Combine 1, 2, and 3 stages decimation functionality into one decimation class. Add tests for 1mic override and single state decimation features to test_mic_array.py and test_thdn.py. Rename MicArray::TwoStageDecimator to MicArray::Decimator. --- Jenkinsfile | 22 +- doc/rst/src/advanced_usage.rst | 6 +- doc/rst/src/custom_filters.rst | 12 +- doc/rst/src/decimator_stages.rst | 4 +- doc/rst/src/getting_started.rst | 2 +- doc/rst/src/reference/c/filters_default.rst | 2 +- doc/rst/src/reference/cpp/cpp_api.rst | 6 +- doc/rst/src/software_structure.rst | 2 +- examples/app_custom_filter/src/main.xc | 2 - examples/app_mic_array_basic/src/app.c | 2 - .../app_par_decimator/src/app_decimator.hpp | 14 +- lib_mic_array/api/mic_array/cpp/Decimator.hpp | 142 +++++++++--- lib_mic_array/api/mic_array/cpp/MicArray.hpp | 75 +++++-- lib_mic_array/api/mic_array/cpp/PdmRx.hpp | 40 +--- .../api/mic_array/cpp/ThreeStageDecimator.hpp | 204 ------------------ .../api/mic_array/mic_array_conf_struct.h | 3 - lib_mic_array/api/mic_array/mic_array_task.h | 16 +- lib_mic_array/src/mic_array_task.c | 57 ++--- lib_mic_array/src/mic_array_task.cpp | 202 +++++++++-------- lib_mic_array/src/mic_array_task_internal.hpp | 33 ++- python/stage1.py | 2 +- tests/signal/BasicMicArray/CMakeLists.txt | 60 ++++-- tests/signal/BasicMicArray/src/app.c | 127 ++++++----- .../src/small_768k_to_12k_filter.h | 25 +-- tests/signal/BasicMicArray/test_mic_array.py | 81 +++++-- tests/signal/BasicMicArray/test_params.json | 3 +- tests/signal/BasicMicArray/test_thdn.py | 81 +++---- tests/signal/TwoStageDecimator/src/run.cpp | 4 +- tests/signal/TwoStageDecimator/test_stage1.py | 20 +- tests/signal/TwoStageDecimator/test_stage2.py | 24 +-- tests/signal/pdmrx_isr/src/app.cpp | 2 - tests/signal/profile/app_memory/src/app.cpp | 2 +- tests/signal/profile/app_mips/src/app.c | 2 - tests/signal/profile/mic_array_memory.json | 32 +-- .../signal/profile/mic_array_memory_table.rst | 32 +-- tests/signal/profile/mic_array_mips_table.rst | 24 +-- tests/signal/profile/mic_array_mips_xs3.json | 24 +-- 37 files changed, 651 insertions(+), 740 deletions(-) delete mode 100644 lib_mic_array/api/mic_array/cpp/ThreeStageDecimator.hpp diff --git a/Jenkinsfile b/Jenkinsfile index b26580aa..c80817e0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -102,6 +102,7 @@ pipeline { label 'x86_64 && linux' } steps { + println "Stage running on ${env.NODE_NAME}" script { def (server, user, repo) = extractFromScmUrl() env.REPO_NAME = repo @@ -111,7 +112,7 @@ pipeline { dir("tests") { createVenv(reqFile: "requirements.txt") withVenv { - xcoreBuild(toolsVersion: params.TOOLS_XS3_VERSION) + xcoreBuild(toolsVersion: params.TOOLS_XS3_VERSION, jobs:31) stash includes: '**/*.xe', name: 'test_bin', useDefaultExcludes: false } } @@ -242,14 +243,17 @@ pipeline { stage('Run tests') { steps { dir("${REPO_NAME}/tests") { - withVenv { - dir("unit") { - withTools(params.TOOLS_VX4_VERSION) {sh "xrun --xscope bin/tests-unit.xe"} - } - dir("signal/BasicMicArray") { - withTools(params.TOOLS_VX4_VERSION) {sh 'python -m pytest --level nightly --seed 12345 -k "(0_isr or lowpower) and not 16frame-8n" -v'} // Skipping 16frame-8n. See https://github.com/xmos/lib_mic_array/issues/288 - } - } // withVenv + withTools(params.TOOLS_VX4_VERSION) { + sh "xflash --erase-all --target XK-EVK-XU416" + } + withVenv { + dir("unit") { + withTools(params.TOOLS_VX4_VERSION) {sh "xrun --xscope bin/tests-unit.xe"} + } + dir("signal/BasicMicArray") { + withTools(params.TOOLS_VX4_VERSION) {sh 'python -m pytest --level nightly --seed 12345 -k "(0isr or OneStageFilter) and not 16frame-8n" -v'} // Skipping 16frame-8n. See https://github.com/xmos/lib_mic_array/issues/288 + } + } // withVenv }}} // stage('Run tests') } // stages post { diff --git a/doc/rst/src/advanced_usage.rst b/doc/rst/src/advanced_usage.rst index b903db41..0b42d55c 100644 --- a/doc/rst/src/advanced_usage.rst +++ b/doc/rst/src/advanced_usage.rst @@ -55,7 +55,7 @@ the :cpp:class:`MicArray `: .. code-block:: c++ using TMicArray = mic_array::MicArray, mic_array::StandardPdmRxService`: TMicArray mics; -- ``TwoStageDecimator``, ``StandardPdmRxService``, ``DcoeSampleFilter``, +- ``Decimator``, ``StandardPdmRxService``, ``DcoeSampleFilter``, and ``FrameOutputHandler`` can all be replaced with custom classes if needed. - Any custom class must implement the same interface expected by :cpp:class:`MicArray `. @@ -82,7 +82,7 @@ the :cpp:class:`MicArray `: .. note:: - If the application requires custom decimation filters but they're compatible with the :cpp:class:`TwoStageDecimator ` implementation, + If the application requires custom decimation filters but they're compatible with the :cpp:class:`Decimator ` implementation, refer to :ref:`custom_filters` to see how to do so. Define app-callable functions diff --git a/doc/rst/src/custom_filters.rst b/doc/rst/src/custom_filters.rst index 84865a34..f592a244 100644 --- a/doc/rst/src/custom_filters.rst +++ b/doc/rst/src/custom_filters.rst @@ -4,7 +4,7 @@ Custom decimation filters ************************* -In the :cpp:class:`TwoStageDecimator `, the tap count and decimation factor +In the :cpp:class:`Decimator `, the tap count and decimation factor for the first stage decimator are fixed to ``256`` and ``32`` respectively, as described in :ref:`decimator_stage_1`. These parameters cannot be changed without implementing a custom decimator, which is outside the scope of this document. @@ -24,7 +24,7 @@ of ``lib_mic_array`` and save them as ``.pkl`` files. Using these functions as a guide, the script can be extended to generate custom filters tailored to the application's needs. -Note that in :cpp:class:`TwoStageDecimator `, +Note that in :cpp:class:`Decimator `, both the first and second stage filters are implemented using fixed-point arithmetic, which requires the coefficients to be presented in a specific format. @@ -68,15 +68,15 @@ From the ``python`` directory, the workflow is typically: Using custom filters ==================== -When using the :cpp:class:`TwoStageDecimator ` provided by the +When using the :cpp:class:`Decimator ` provided by the library, the :c:func:`mic_array_init_custom_filter` function is used to initialize a mic array instance with a custom 2-stage decimation filter. .. note:: The custom filter provided to :c:func:`mic_array_init_custom_filter` must - be compatible with the :cpp:class:`TwoStageDecimator - ` requirements. Specifically, it must be a + be compatible with the :cpp:class:`Decimator + ` requirements. Specifically, it must be a 2-stage filter. The tap count and decimation factor for the first-stage decimator are fixed at ``256`` and ``32``, respectively, and the filter must be compatible with the :ref:`stage_1_filter_impl`. @@ -84,7 +84,7 @@ initialize a mic array instance with a custom 2-stage decimation filter. The second-stage decimation filter tap count and decimation ratio are flexible, provided it is a standard FIR filter compatible with :ref:`stage_2_filter_impl`. Using custom filters that are incompatible with the implementation in - :cpp:class:`TwoStageDecimator ` is outside the + :cpp:class:`Decimator ` is outside the scope of this documentation. The :c:type:`mic_array_conf_t` structure is populated with the decimator and diff --git a/doc/rst/src/decimator_stages.rst b/doc/rst/src/decimator_stages.rst index a994420f..a93abb0e 100644 --- a/doc/rst/src/decimator_stages.rst +++ b/doc/rst/src/decimator_stages.rst @@ -5,7 +5,7 @@ Decimation filters ****************** The mic array unit provided by this library uses a two-stage decimation process, -implemented in :cpp:class:`TwoStageDecimator `, +implemented in :cpp:class:`Decimator `, to convert a high sample rate stream of (1-bit) PDM samples into a lower sample rate stream of (32-bit) PCM samples. This is shown in :ref:`decimator_stages_simplified`. @@ -162,7 +162,7 @@ overall combined response provides a nice flat passband. 48 kHz output sampling rate filter freq response The following sections provide more details about the first and second stage decimation filters, -implemented in :cpp:class:`TwoStageDecimator `. +implemented in :cpp:class:`Decimator `. .. _decimator_stage_1: diff --git a/doc/rst/src/getting_started.rst b/doc/rst/src/getting_started.rst index 0c80f4b1..e4adc594 100644 --- a/doc/rst/src/getting_started.rst +++ b/doc/rst/src/getting_started.rst @@ -307,7 +307,7 @@ following constraints: library (see :ref:`default_filters`) are designed for a small set of decimation factors and they assume a fixed input PDM frequency of **3.072 MHz**. See :ref:`custom_filters` and :ref:`mic_array_example_custom_filter` for using custom filters for the mic array - (provided they are compatible with the :cpp:class:`TwoStageDecimator ` + (provided they are compatible with the :cpp:class:`Decimator ` implementation) - Only one mic array instance: diff --git a/doc/rst/src/reference/c/filters_default.rst b/doc/rst/src/reference/c/filters_default.rst index f7d73dc9..35627147 100644 --- a/doc/rst/src/reference/c/filters_default.rst +++ b/doc/rst/src/reference/c/filters_default.rst @@ -3,7 +3,7 @@ filters_default.h The filters described below are the first and second stage filters provided by this library which are used with the -:cpp:class:`TwoStageDecimator ` class template by +:cpp:class:`Decimator ` class template by default. Stage 1 - PDM-to-PCM Decimating FIR Filter diff --git a/doc/rst/src/reference/cpp/cpp_api.rst b/doc/rst/src/reference/cpp/cpp_api.rst index 01a45d52..7b37481d 100644 --- a/doc/rst/src/reference/cpp/cpp_api.rst +++ b/doc/rst/src/reference/cpp/cpp_api.rst @@ -37,10 +37,10 @@ StandardPdmRxService -TwoStageDecimator ------------------ +Decimator +--------- -.. doxygenclass:: mic_array::TwoStageDecimator +.. doxygenclass:: mic_array::Decimator :members: .. raw:: latex diff --git a/doc/rst/src/software_structure.rst b/doc/rst/src/software_structure.rst index 784d8579..55438b3c 100644 --- a/doc/rst/src/software_structure.rst +++ b/doc/rst/src/software_structure.rst @@ -189,7 +189,7 @@ Decimator The :cpp:member:`Decimator ` sub-component encapsulates the logic of converting blocks of PDM samples into PCM samples. The -:cpp:class:`TwoStageDecimator ` class is a +:cpp:class:`Decimator ` class is a decimator implementation that uses a pair of decimating FIR filters to accomplish this. diff --git a/examples/app_custom_filter/src/main.xc b/examples/app_custom_filter/src/main.xc index 85584763..62f9fa7d 100644 --- a/examples/app_custom_filter/src/main.xc +++ b/examples/app_custom_filter/src/main.xc @@ -72,8 +72,6 @@ void init_mic_conf(mic_array_conf_t &mic_array_conf, mic_array_filter_conf_t (&f 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.num_channels_in = APP_MIC_COUNT; - mic_array_conf.pdmrx_conf.num_channels_out = APP_MIC_COUNT; } int main() { diff --git a/examples/app_mic_array_basic/src/app.c b/examples/app_mic_array_basic/src/app.c index 492be761..1643c93a 100644 --- a/examples/app_mic_array_basic/src/app.c +++ b/examples/app_mic_array_basic/src/app.c @@ -60,8 +60,6 @@ void init_mic_conf(mic_array_conf_t *mic_array_conf, mic_array_filter_conf_t fil 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.num_channels_in = APP_MIC_COUNT; - mic_array_conf->pdmrx_conf.num_channels_out = APP_MIC_COUNT; } void user_mic(chanend_t c_mic_audio) diff --git a/examples/app_par_decimator/src/app_decimator.hpp b/examples/app_par_decimator/src/app_decimator.hpp index 4b6b0e98..754fd704 100644 --- a/examples/app_par_decimator/src/app_decimator.hpp +++ b/examples/app_par_decimator/src/app_decimator.hpp @@ -185,9 +185,19 @@ class MyTwoStageDecimator * @param sample_out Output sample vector. * @param pdm_block PDM data to be processed. */ - void ProcessBlock( + void ProcessBlockTwoStage( int32_t sample_out[MIC_COUNT], uint32_t pdm_block[BLOCK_SIZE]); + + void ProcessBlockSingleStage( + int32_t *sample_out, + uint32_t *pdm_block) {} + + void ProcessBlockThreeStage( + int32_t sample_out[MIC_COUNT], + uint32_t *pdm_block) {} + + unsigned num_stages = 2; }; } @@ -214,7 +224,7 @@ void par_mic_array::MyTwoStageDecimator::I template void par_mic_array::MyTwoStageDecimator - ::ProcessBlock( + ::ProcessBlockTwoStage( int32_t sample_out[MIC_COUNT], uint32_t pdm_block[BLOCK_SIZE]) { diff --git a/lib_mic_array/api/mic_array/cpp/Decimator.hpp b/lib_mic_array/api/mic_array/cpp/Decimator.hpp index 7a26eac4..3a25431b 100644 --- a/lib_mic_array/api/mic_array/cpp/Decimator.hpp +++ b/lib_mic_array/api/mic_array/cpp/Decimator.hpp @@ -30,10 +30,11 @@ void shift_buffer(uint32_t* buff); /** - * @brief First and Second Stage Decimator + * @brief PDM Decimator (1, 2, or 3 stage) * - * This class template represents a two stage decimator which converts a stream - * of PDM samples to a lower sample rate stream of PCM samples. + * This class template represents a decimator which converts a stream + * of PDM samples to a lower sample rate stream of PCM samples using + * one, two, or three cascaded FIR decimation stages. * * Concrete implementations of this class template are meant to be used as the * `TDecimator` template parameter in the @ref MicArray class template. @@ -41,7 +42,7 @@ void shift_buffer(uint32_t* buff); * @tparam MIC_COUNT Number of microphone channels. */ template -class TwoStageDecimator +class Decimator { private: @@ -68,8 +69,7 @@ class TwoStageDecimator } stage1; public: - chanend_t c_decimator; - constexpr TwoStageDecimator() noexcept { } + constexpr Decimator() noexcept { } /** * Stage 2 decimation configuration and state. @@ -86,21 +86,34 @@ class TwoStageDecimator } stage2; /** - * @brief Initialize the two-stage decimator from a configuration struct + * Stage 3 decimation configuration and state. + */ + struct { + /** + * Stage 3 FIR filters + */ + filter_fir_s32_t filters[MIC_COUNT]; + /** + * Stage 3 filter decimation factor. + */ + unsigned decimation_factor; + } stage3; + + /** + * @brief Initialize the decimator from a configuration struct * @ref mic_array_decimator_conf_t @p decimator_conf * - * Reads stage-1 and stage-2 filter parameters from @p decimator_conf and prepares - * internal state: - * The caller must ensure all pointers inside @p decimator_conf.filter_conf[0] - * and @p decimator_conf.filter_conf[1] are valid and persist for the - * lifetime of the decimator. + * Reads filter parameters for all configured stages from @p decimator_conf and prepares + * internal state. + * The caller must ensure all pointers inside @p decimator_conf.filter_conf[] + * are valid and persist for the lifetime of the decimator. * * @param decimator_conf Decimator pipeline configuration. */ void Init(mic_array_decimator_conf_t &decimator_conf, unsigned pdm_out_words_per_mic); /** - * @brief Process one block of PDM data. + * @brief Process one block of PDM data through the 2-stage decimator. * * Processes a block of PDM data to produce an output sample from the * second stage decimator. @@ -125,25 +138,42 @@ class TwoStageDecimator * @param sample_out Output sample vector. * @param pdm_block PDM data to be processed. */ - void ProcessBlock( + void ProcessBlockTwoStage( int32_t sample_out[MIC_COUNT], uint32_t *pdm_block); /** - * @brief Process a single mic, 2 sample PDM block using only the 1st stage decimation filters + * @brief Process one block of PDM data through only the 1st stage decimation filter. * - * Consumes two PDM words from `pdm_block` and runs the - * stage-1 FIR twice. Two output samples are written to - * `sample_out[0]` and `sample_out[1]`. This path is used in low-power - * configurations where only the stage-1 filter is active. + * Consumes `pdm_out_words_per_mic` PDM words per microphone from `pdm_block`, runs the + * stage-1 FIR, and produces `pdm_out_words_per_mic` PCM output samples per microphone. * - * @param sample_out Output sample vector with two consecutive samples. - * @param pdm_block PDM data to be processed (two words). + * @param sample_out Output sample array, written in [MIC_COUNT][pdm_out_words_per_mic] order. + * @param pdm_block Input PDM data, read in [MIC_COUNT][pdm_out_words_per_mic] order. */ void ProcessBlockSingleStage( int32_t *sample_out, uint32_t *pdm_block); + /** + * @brief Process one block of PDM data through the 3-stage decimator. + * + * Consumes `stage2.decimation_factor * stage3.decimation_factor` PDM words per microphone + * from `pdm_block`, runs the stage-1 FIR on each word, decimates through stage-2, then + * stage-3, and produces one PCM output sample per microphone. + * + * @param sample_out Output sample vector, one value per microphone channel. + * @param pdm_block Input PDM data, read in [MIC_COUNT][stage2.decimation_factor * stage3.decimation_factor] order. + */ + void ProcessBlockThreeStage( + int32_t sample_out[MIC_COUNT], + uint32_t *pdm_block); + + /** Number of active decimation stages (1, 2, or 3). Set by @ref Init from + * `decimator_conf.num_filter_stages`. Determines which ProcessBlock variant + * should be called. */ + unsigned num_stages; + }; } @@ -152,11 +182,12 @@ class TwoStageDecimator ////////////////////////////////////////////// template -void mic_array::TwoStageDecimator +void mic_array::Decimator ::Init( mic_array_decimator_conf_t &decimator_conf, unsigned pdm_out_words_per_mic) { + this->num_stages = decimator_conf.num_filter_stages; this->stage1.filter_coef = (const uint32_t*)decimator_conf.filter_conf[0].coef; this->stage1.pdm_history_ptr = (uint32_t*)decimator_conf.filter_conf[0].state; this->stage1.pdm_history_sz = decimator_conf.filter_conf[0].state_words_per_channel; @@ -164,19 +195,27 @@ void mic_array::TwoStageDecimator memset(this->stage1.pdm_history_ptr, 0x55, sizeof(int32_t) * MIC_COUNT * this->stage1.pdm_history_sz); - if(decimator_conf.num_filter_stages == 2) { + if(decimator_conf.num_filter_stages >= 2) { for(int k = 0; k < MIC_COUNT; k++){ filter_fir_s32_init(&this->stage2.filters[k], decimator_conf.filter_conf[1].state + (k * decimator_conf.filter_conf[1].state_words_per_channel), decimator_conf.filter_conf[1].num_taps, decimator_conf.filter_conf[1].coef, decimator_conf.filter_conf[1].shr); } this->stage2.decimation_factor = decimator_conf.filter_conf[1].decimation_factor; } + + if(decimator_conf.num_filter_stages == 3) { + for(int k = 0; k < MIC_COUNT; k++){ + filter_fir_s32_init(&this->stage3.filters[k], decimator_conf.filter_conf[2].state + (k * decimator_conf.filter_conf[2].state_words_per_channel), + decimator_conf.filter_conf[2].num_taps, decimator_conf.filter_conf[2].coef, decimator_conf.filter_conf[2].shr); + } + this->stage3.decimation_factor = decimator_conf.filter_conf[2].decimation_factor; + } } template -void mic_array::TwoStageDecimator - ::ProcessBlock( +void mic_array::Decimator + ::ProcessBlockTwoStage( int32_t sample_out[MIC_COUNT], uint32_t *pdm_block) { @@ -197,18 +236,59 @@ void mic_array::TwoStageDecimator } } +template +void mic_array::Decimator + ::ProcessBlockThreeStage( + int32_t sample_out[MIC_COUNT], + uint32_t *pdm_block) +{ + unsigned stage1_output_words = this->stage2.decimation_factor * this->stage3.decimation_factor; + for(unsigned mic = 0; mic < MIC_COUNT; mic++){ + uint32_t* hist = this->stage1.pdm_history_ptr + (mic * this->stage1.pdm_history_sz); + uint32_t* mic_base = pdm_block + (mic * stage1_output_words); + int count2 = this->stage2.decimation_factor - 1; + int count3 = this->stage3.decimation_factor - 1; + for(unsigned k = 0; k < stage1_output_words; k++) + { + hist[0] = mic_base[k]; + + int32_t streamA_sample = fir_1x16_bit(hist, this->stage1.filter_coef); + shift_buffer(hist); + + if(count2) { + filter_fir_s32_add_sample(&this->stage2.filters[mic], streamA_sample); + count2 -= 1; + continue; + } + int32_t streamB_sample = filter_fir_s32(&this->stage2.filters[mic], streamA_sample); + count2 = this->stage2.decimation_factor - 1; + if(count3) { + filter_fir_s32_add_sample(&this->stage3.filters[mic], streamB_sample); + count3 -= 1; + } + else { + sample_out[mic] = filter_fir_s32(&this->stage3.filters[mic], streamB_sample); + count3 = this->stage3.decimation_factor - 1; + } + } + } +} template -void mic_array::TwoStageDecimator +void mic_array::Decimator ::ProcessBlockSingleStage( int32_t *sample_out, uint32_t *pdm_block) { - uint32_t* hist = this->stage1.pdm_history_ptr; - for(unsigned k = 0; k < this->stage1.pdm_out_words_per_mic; k++) { - hist[0] = pdm_block[k]; - sample_out[k] = fir_1x16_bit(hist, this->stage1.filter_coef); - shift_buffer(hist); + // pdm_block expected to be in [MIC_COUNT][stage1.pdm_out_words_per_mic] format + // sample_out is also updated in [MIC_COUNT][stage1.pdm_out_words_per_mic] format + for(unsigned mic = 0; mic < MIC_COUNT; mic++) { + uint32_t* hist = this->stage1.pdm_history_ptr + (mic * this->stage1.pdm_history_sz); + for(unsigned k = 0; k < this->stage1.pdm_out_words_per_mic; k++) { + hist[0] = *pdm_block++; + *sample_out++ = fir_1x16_bit(hist, this->stage1.filter_coef); + shift_buffer(hist); + } } } diff --git a/lib_mic_array/api/mic_array/cpp/MicArray.hpp b/lib_mic_array/api/mic_array/cpp/MicArray.hpp index 8cb7d72d..ff91730f 100644 --- a/lib_mic_array/api/mic_array/cpp/MicArray.hpp +++ b/lib_mic_array/api/mic_array/cpp/MicArray.hpp @@ -13,7 +13,6 @@ #include "PdmRx.hpp" #include "Decimator.hpp" -#include "ThreeStageDecimator.hpp" #include "SampleFilter.hpp" #include "OutputHandler.hpp" @@ -50,6 +49,11 @@ namespace mic_array { class MicArray { + private: + void ThreadEntryOneStage(); + void ThreadEntryTwoStage(); + void ThreadEntryThreeStage(); + public: /** * @brief The PDM rx service. @@ -198,6 +202,32 @@ namespace mic_array { ////////////////////////////////////////////// // Template function implementations below. // ////////////////////////////////////////////// +template +void mic_array::MicArray::ThreadEntryOneStage() +{ + volatile bool shutdown = false; + chanend_t c_frame_out = OutputHandler.FrameTx.GetChannel(); + unsigned pdm_out_words_per_channel = PdmRx.pdm_out_words_per_channel; + int32_t sample_out[MIC_COUNT * MAX_PDM_OUT_WORDS_PER_CHANNEL]; + + while(!shutdown){ + uint32_t *pdm_samples = PdmRx.GetPdmBlock(); + Decimator.ProcessBlockSingleStage(sample_out, pdm_samples); + shutdown = ma_frame_tx(c_frame_out, + reinterpret_cast(sample_out), + MIC_COUNT, pdm_out_words_per_channel); + } + PdmRx.Shutdown(); + OutputHandler.CompleteShutdown(); // Exchange end token with the app to close channel and indicate completion. + // ma_shutdown() will now return + return; +} template void mic_array::MicArray::ThreadEntry() + TOutputHandler>::ThreadEntryTwoStage() { int32_t sample_out[MIC_COUNT] = {0}; volatile bool shutdown = false; while(!shutdown){ uint32_t *pdm_samples = PdmRx.GetPdmBlock(); - Decimator.ProcessBlock(sample_out, pdm_samples); + Decimator.ProcessBlockTwoStage(sample_out, pdm_samples); SampleFilter.Filter(sample_out); shutdown = OutputHandler.OutputSample(sample_out); } @@ -230,22 +260,41 @@ template void mic_array::MicArray::ThreadEntryLowPower_1Mic1StgDecimator() + TOutputHandler>::ThreadEntryThreeStage() { + int32_t sample_out[MIC_COUNT] = {0}; volatile bool shutdown = false; - chanend_t c_frame_out = OutputHandler.FrameTx.GetChannel(); - unsigned pdm_out_words_per_channel = PdmRx.pdm_out_words_per_channel; - int32_t sample_out[MAX_PDM_OUT_WORDS_PER_CHANNEL]; while(!shutdown){ - uint32_t *pdm_samples = PdmRx.GetPdmBlockLowPowerOneMic(); - Decimator.ProcessBlockSingleStage(sample_out, pdm_samples); - shutdown = ma_frame_tx(c_frame_out, - reinterpret_cast(sample_out), - 1, pdm_out_words_per_channel); + uint32_t *pdm_samples = PdmRx.GetPdmBlock(); + Decimator.ProcessBlockThreeStage(sample_out, pdm_samples); + SampleFilter.Filter(sample_out); + shutdown = OutputHandler.OutputSample(sample_out); } PdmRx.Shutdown(); OutputHandler.CompleteShutdown(); // Exchange end token with the app to close channel and indicate completion. // ma_shutdown() will now return return; -} \ No newline at end of file + +} + + +template +void mic_array::MicArray::ThreadEntry() +{ + if(Decimator.num_stages == 1) { + ThreadEntryOneStage(); + } + else if(Decimator.num_stages == 2) { + ThreadEntryTwoStage(); + } + else { + ThreadEntryThreeStage(); + } +} diff --git a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp index 31154817..6d522c2d 100644 --- a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp +++ b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp @@ -327,8 +327,6 @@ namespace mic_array { volatile bool shutdown = false; volatile bool shutdown_complete = false; uint32_t num_phases; - uint32_t num_channels_in; - uint32_t num_channels_out; /** * @brief Streaming channel over which PDM blocks are sent. @@ -428,8 +426,6 @@ namespace mic_array { */ uint32_t* GetPdmBlock(); - uint32_t* GetPdmBlockLowPowerOneMic(); - /** * @brief Set whether dropped PDM samples should cause an assertion. * @@ -502,11 +498,9 @@ template void mic_array::StandardPdmRxService ::Init(port_t p_pdm_mics, pdm_rx_conf_t &pdm_rx_config) { - this->num_channels_in = pdm_rx_config.num_channels_in; - this->num_channels_out = pdm_rx_config.num_channels_out; this->pdm_out_block_ptr = pdm_rx_config.pdm_out_block; this->pdm_out_words_per_channel = pdm_rx_config.pdm_out_words_per_channel; - this->num_phases = this->num_channels_in * this->pdm_out_words_per_channel; + this->num_phases = CHANNELS_IN * this->pdm_out_words_per_channel; this->phase = this->num_phases; @@ -588,36 +582,7 @@ uint32_t* mic_array::StandardPdmRxService uint32_t *out_ptr; for(int ch = 0; ch < CHANNELS_OUT; ch++) { out_ptr = this->pdm_out_block_ptr + (ch * this->pdm_out_words_per_channel); - for(int sb = 0; sb < this->pdm_out_words_per_channel; sb++) { - unsigned d = this->channel_map[ch]; - out_ptr[sb] = block[this->pdm_out_words_per_channel - 1 - sb][d]; - } - } - return this->pdm_out_block_ptr; -} - -template -uint32_t* mic_array::StandardPdmRxService - ::GetPdmBlockLowPowerOneMic() -{ - if(this->isr_used) { - // Has to be in a critical section to avoid race conditions with ISR. - interrupt_mask_all(); - // Limiting credit to 1 prevents the ISR from attempting to enqueue an additional block - // while two buffers are already occupied (which would happen if the ISR gets triggered between interrupt_unmask_all() - // and s_chan_in_word()), thereby avoiding deadlock. - pdm_rx_isr_context.credit = 1; - interrupt_unmask_all(); - } - - uint32_t* full_block = (uint32_t*) s_chan_in_word(this->c_pdm_blocks.end_b); - mic_array::deinterleave_pdm_samples<1>(full_block, this->pdm_out_words_per_channel); - - uint32_t (*block)[1] = (uint32_t (*)[1]) full_block; - uint32_t *out_ptr; - for(int ch = 0; ch < 1; ch++) { - out_ptr = this->pdm_out_block_ptr + (ch * this->pdm_out_words_per_channel); - for(int sb = 0; sb < this->pdm_out_words_per_channel; sb++) { + for(int sb = 0; sb < (int)this->pdm_out_words_per_channel; sb++) { unsigned d = this->channel_map[ch]; out_ptr[sb] = block[this->pdm_out_words_per_channel - 1 - sb][d]; } @@ -625,7 +590,6 @@ uint32_t* mic_array::StandardPdmRxService return this->pdm_out_block_ptr; } - template void mic_array::StandardPdmRxService ::Shutdown() { diff --git a/lib_mic_array/api/mic_array/cpp/ThreeStageDecimator.hpp b/lib_mic_array/api/mic_array/cpp/ThreeStageDecimator.hpp deleted file mode 100644 index 5510bf53..00000000 --- a/lib_mic_array/api/mic_array/cpp/ThreeStageDecimator.hpp +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2022-2026 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -#pragma once - -#include -#include -#include - -#include "xmath/xmath.h" -#include "mic_array/etc/fir_1x16_bit.h" - -// This has caused problems previously, so just catch the problems here. -#if defined (MIC_COUNT) -# error Application must not define the following as precompiler macros: MIC_COUNT, S2_DEC_FACTOR. -#endif - -namespace mic_array { - - -/** - * @brief Three Stage Decimator - * - * This class template represents a three stage decimator which converts a stream - * of PDM samples to a lower sample rate stream of PCM samples. - * - * Concrete implementations of this class template are meant to be used as the - * `TDecimator` template parameter in the @ref MicArray class template. - * - * @tparam MIC_COUNT Number of microphone channels. - */ -template -class ThreeStageDecimator -{ - private: - - /** - * Stage 1 decimator configuration and state. - */ - struct { - /** - * Pointer to filter coefficients for Stage 1 - */ - - const uint32_t* filter_coef; - /** - * Pointer to filter state (PDM history) for stage-1 filter. - */ - uint32_t *pdm_history_ptr; - - /** - * Per-mic channel filter state (PDM history) size in 32-bit words for stage-1 filter. - */ - unsigned pdm_history_sz; - - unsigned pdm_out_words_per_mic; - } stage1; - - /** - * Stage 2 decimation configuration and state. - */ - struct { - /** - * Stage 2 FIR filters - */ - filter_fir_s32_t filters[MIC_COUNT]; - /** - * Stage 2 filter decimation factor. - */ - unsigned decimation_factor; - } stage2; - - /** - * Stage 3 decimation configuration and state. - */ - struct { - /** - * Stage 3 FIR filters - */ - filter_fir_s32_t filters[MIC_COUNT]; - /** - * Stage 3 filter decimation factor. - */ - unsigned decimation_factor; - } stage3; - - public: - - constexpr ThreeStageDecimator() noexcept { } - - /** - * @brief Initialize the three-stage decimator from a configuration struct - * @ref mic_array_decimator_conf_t @p decimator_conf - * - * Reads stage-1, stage-2 and stage-3 filter parameters from @p decimator_conf and prepares - * internal state: - * The caller must ensure all pointers inside @p decimator_conf.filter_conf[0] - * and @p decimator_conf.filter_conf[0] are valid and remain alive for the - * lifetime of the decimator. - * - * @param decimator_conf Decimator pipeline configuration. - */ - void Init(mic_array_decimator_conf_t &decimator_conf, unsigned pdm_out_words_per_mic); - - /** - * @brief Process one block of PDM data. - * - * Processes a block of PDM data to produce an output sample from the - * third stage decimator. - * - * `pdm_block` contains exactly enough PDM samples to produce a single - * output sample from the third stage decimator. The layout of `pdm_block` - * should (effectively) be: - * - * @code{.cpp} - * struct { - * struct { - * // lower word indices are older samples. - * // less significant bits in a word are older samples. - * uint32_t samples[S2_DEC_FACTOR * S3_DEC_FACTOR]; - * } microphone[MIC_COUNT]; // mic channels are in ascending order - * } pdm_block; - * @endcode - * - * A single output sample from the third stage decimator is computed and - * written to `sample_out[]`. - * - * @param sample_out Output sample vector. - * @param pdm_block PDM data to be processed. - */ - void ProcessBlock( - int32_t sample_out[MIC_COUNT], - uint32_t *pdm_block); -}; -} - -////////////////////////////////////////////// -// Template function implementations below. // -////////////////////////////////////////////// - -template -void mic_array::ThreeStageDecimator - ::Init( - mic_array_decimator_conf_t &decimator_conf, - unsigned pdm_out_words_per_mic) -{ - this->stage1.filter_coef = (const uint32_t*)decimator_conf.filter_conf[0].coef; - this->stage1.pdm_history_ptr = (uint32_t*)decimator_conf.filter_conf[0].state; - this->stage1.pdm_history_sz = decimator_conf.filter_conf[0].state_words_per_channel; - this->stage1.pdm_out_words_per_mic = pdm_out_words_per_mic; - - memset(this->stage1.pdm_history_ptr, 0x55, sizeof(int32_t) * MIC_COUNT * this->stage1.pdm_history_sz); - - for(int k = 0; k < MIC_COUNT; k++){ - filter_fir_s32_init(&this->stage2.filters[k], decimator_conf.filter_conf[1].state + (k * decimator_conf.filter_conf[1].state_words_per_channel), - decimator_conf.filter_conf[1].num_taps, decimator_conf.filter_conf[1].coef, decimator_conf.filter_conf[1].shr); - } - this->stage2.decimation_factor = decimator_conf.filter_conf[1].decimation_factor; - - for(int k = 0; k < MIC_COUNT; k++){ - filter_fir_s32_init(&this->stage3.filters[k], decimator_conf.filter_conf[2].state + (k * decimator_conf.filter_conf[2].state_words_per_channel), - decimator_conf.filter_conf[2].num_taps, decimator_conf.filter_conf[2].coef, decimator_conf.filter_conf[2].shr); - } - this->stage3.decimation_factor = decimator_conf.filter_conf[2].decimation_factor; -} - - -template -void mic_array::ThreeStageDecimator - ::ProcessBlock( - int32_t sample_out[MIC_COUNT], - uint32_t *pdm_block) -{ - unsigned stage1_output_words = this->stage2.decimation_factor * this->stage3.decimation_factor; - for(unsigned mic = 0; mic < MIC_COUNT; mic++){ - uint32_t* hist = this->stage1.pdm_history_ptr + (mic * this->stage1.pdm_history_sz); - uint32_t* mic_base = pdm_block + (mic * stage1_output_words); - int count2 = this->stage2.decimation_factor - 1; - int count3 = this->stage3.decimation_factor - 1; - for(unsigned k = 0; k < stage1_output_words; k++) - { - hist[0] = mic_base[k]; - - int32_t streamA_sample = fir_1x16_bit(hist, this->stage1.filter_coef); - shift_buffer(hist); - - if(count2) { - filter_fir_s32_add_sample(&this->stage2.filters[mic], streamA_sample); - count2 -= 1; - continue; - } - int32_t streamB_sample = filter_fir_s32(&this->stage2.filters[mic], streamA_sample); - count2 = this->stage2.decimation_factor - 1; - if(count3) { - filter_fir_s32_add_sample(&this->stage3.filters[mic], streamB_sample); - count3 -= 1; - } - else { - sample_out[mic] = filter_fir_s32(&this->stage3.filters[mic], streamB_sample); - count3 = this->stage3.decimation_factor - 1; - } - } - } -} diff --git a/lib_mic_array/api/mic_array/mic_array_conf_struct.h b/lib_mic_array/api/mic_array/mic_array_conf_struct.h index d3ad27e2..3eff142a 100644 --- a/lib_mic_array/api/mic_array/mic_array_conf_struct.h +++ b/lib_mic_array/api/mic_array/mic_array_conf_struct.h @@ -136,9 +136,6 @@ typedef struct { * stage decimation filter's decimation factor (in case of a 2 stage decimator). */ unsigned pdm_out_words_per_channel; // per channel pdm rx output block (input to the decimator) size - - unsigned num_channels_in; - unsigned num_channels_out; }pdm_rx_conf_t; diff --git a/lib_mic_array/api/mic_array/mic_array_task.h b/lib_mic_array/api/mic_array/mic_array_task.h index 19d920a4..e79d1443 100644 --- a/lib_mic_array/api/mic_array/mic_array_task.h +++ b/lib_mic_array/api/mic_array/mic_array_task.h @@ -56,8 +56,6 @@ void mic_array_init(pdm_rx_resources_t *pdm_res, const unsigned *channel_map, un MA_C_API void mic_array_init_custom_filter(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf); -MA_C_API -void mic_array_init_custom_filter_1mic_1stg_decimator(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf); /** * @brief Start the mic array task * @@ -75,5 +73,19 @@ void mic_array_init_custom_filter_1mic_1stg_decimator(pdm_rx_resources_t* pdm_re MA_C_API void mic_array_start(chanend_t c_frames_out); +/** + * @brief Enable single-microphone output override. + * + * When enabled, the mic array behaves as if both `MIC_ARRAY_CONFIG_MIC_COUNT` + * and `MIC_ARRAY_CONFIG_MIC_IN_COUNT` were set to 1. Only the first + * microphone channel is processed and emitted. + * + * @note Because this overrides both input and output microphone counts to 1, + * it is intended for use with a 1-bit PDM data port configuration. + * + * @pre Call this before mic array initialization (@ref mic_array_init or @ref mic_array_init_custom_filter). + */ +MA_C_API +void mic_array_enable_1mic_override(void); C_API_END diff --git a/lib_mic_array/src/mic_array_task.c b/lib_mic_array/src/mic_array_task.c index 85fdda5b..3364ab31 100644 --- a/lib_mic_array/src/mic_array_task.c +++ b/lib_mic_array/src/mic_array_task.c @@ -12,6 +12,11 @@ //////////////////// // Mic array init // //////////////////// +void mic_array_enable_1mic_override(void) +{ + init_1mic_override(); +} + void mic_array_init(pdm_rx_resources_t *pdm_res, const unsigned *channel_map, unsigned output_samp_freq) { unsigned stg2_decimation_factor = (pdm_res->pdm_freq/STAGE1_DEC_FACTOR)/output_samp_freq; @@ -20,35 +25,24 @@ void mic_array_init(pdm_rx_resources_t *pdm_res, const unsigned *channel_map, un // output sampling freqs are [48000, 32000, 16000] assert ((stg2_decimation_factor == 2) || (stg2_decimation_factor == 3) || (stg2_decimation_factor == 6)); - bool use_3_stg_decimator = false; - init_mic_array_storage(use_3_stg_decimator); + init_mic_array_storage(); init_mics_default_filter(pdm_res, channel_map, stg2_decimation_factor); -} - -void mic_array_init_custom_filter(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf) -{ - assert(pdm_res); - assert(mic_array_conf); - assert(mic_array_conf->decimator_conf.num_filter_stages == 2 || - mic_array_conf->decimator_conf.num_filter_stages == 3); - - init_mic_array_storage(mic_array_conf->decimator_conf.num_filter_stages == 3); - init_mics_custom_filter(pdm_res, mic_array_conf); - // Configure and start clocks const unsigned divide = pdm_res->mclk_freq / pdm_res->pdm_freq; mic_array_resources_configure(pdm_res, divide); mic_array_pdm_clock_start(pdm_res); } -void mic_array_init_custom_filter_1mic_1stg_decimator(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf) +void mic_array_init_custom_filter(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf) { assert(pdm_res); assert(mic_array_conf); - assert(mic_array_conf->decimator_conf.num_filter_stages == 1); + assert((mic_array_conf->decimator_conf.num_filter_stages == 1) || + (mic_array_conf->decimator_conf.num_filter_stages == 2) || + (mic_array_conf->decimator_conf.num_filter_stages == 3)); - init_mic_array_storage(mic_array_conf->decimator_conf.num_filter_stages == 3); - init_mics_custom_filter_1mic_1stg_decimator(pdm_res, mic_array_conf); + init_mic_array_storage(); + init_mics_custom_filter(pdm_res, mic_array_conf); // Configure and start clocks const unsigned divide = pdm_res->mclk_freq / pdm_res->pdm_freq; @@ -73,34 +67,17 @@ void default_ma_task_start_decimator() start_decimator_task(); } -DECLARE_JOB(default_ma_task_start_pdm_3stg, (void)); -void default_ma_task_start_pdm_3stg(void) -{ - start_pdm_task_3stg(); -} - -DECLARE_JOB(default_ma_task_start_decimator_3stg, (void)); -void default_ma_task_start_decimator_3stg(void) -{ - start_decimator_task_3stg(); -} - void mic_array_start(chanend_t c_frames_out) { + assert_mic_array_start_ready(); #if MIC_ARRAY_CONFIG_USE_PDM_ISR start_mic_array_pdm_isr(c_frames_out); #else set_output_channel(c_frames_out); - bool use_3_stg_decimator = get_decimator_stg_count(); - if (use_3_stg_decimator) { - PAR_JOBS( - PJOB(default_ma_task_start_pdm_3stg, ()), - PJOB(default_ma_task_start_decimator_3stg, ())); - } else { - PAR_JOBS( - PJOB(default_ma_task_start_pdm, ()), - PJOB(default_ma_task_start_decimator, ())); - } + PAR_JOBS( + PJOB(default_ma_task_start_pdm, ()), + PJOB(default_ma_task_start_decimator, ())); + #endif // MIC_ARRAY_CONFIG_USE_PDM_ISR shutdown_mic_array(); } diff --git a/lib_mic_array/src/mic_array_task.cpp b/lib_mic_array/src/mic_array_task.cpp index c827a9f5..3630c021 100644 --- a/lib_mic_array/src/mic_array_task.cpp +++ b/lib_mic_array/src/mic_array_task.cpp @@ -12,35 +12,62 @@ #include "mic_array_task_internal.hpp" static TMicArray *s_mics = nullptr; -static TMicArray_3stg_decimator *s_mics_3stg = nullptr; -static bool s_use_3_stg_decimator = false; -static bool s_run_1mic_1stg_decimator = false; -// NOTE: s_mics or s_mics_3stg must persist (remain non-null with its backing storage valid) +static TMicArray1MicOverride *s_mics_1mic_override = nullptr; +static bool s_1mic_override_active = false; +// NOTE: s_mics must persist (remain non-null with its backing storage valid) // until mic_array_start() completes. mic_array_start() performs shutdown and -// then sets s_mics or s_mics_3stg back to nullptr. +// then sets s_mics back to nullptr. #if !defined (__XS2A__) -///////////////////////////// -// Static variable getters // -///////////////////////////// -bool get_decimator_stg_count(void) -{ - return s_use_3_stg_decimator; -} //////////////////// // Mic array init // //////////////////// +void init_1mic_override(void) +{ + // init_1mic_override() should be called before initialising the mic array + assert((s_mics == nullptr) && (s_mics_1mic_override == nullptr)); // Mic array instance already initialised + s_1mic_override_active = true; +} + +void init_mic_array_storage() +{ + assert((s_mics == nullptr) && (s_mics_1mic_override == nullptr)); // Mic array instance already initialised + if(s_1mic_override_active) { + static uint8_t __attribute__((aligned(8))) mic_storage[sizeof(TMicArray1MicOverride)]; + s_mics_1mic_override = new (mic_storage) TMicArray1MicOverride(); + } else { + static uint8_t __attribute__((aligned(8))) mic_storage[sizeof(TMicArray)]; + s_mics = new (mic_storage) TMicArray(); + } +} + +template +static inline void init_from_conf(TMics*& mics_ptr, pdm_rx_resources_t* pdm_res, mic_array_conf_t* conf) +{ + if(conf->decimator_conf.num_filter_stages == 1) + { + // For 1-stage only filters, the number of 32-bit PDM RX output words should match the PCM samples expected at the output of the mic array + assert(conf->pdmrx_conf.pdm_out_words_per_channel == MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME); + } + mics_ptr->Decimator.Init(conf->decimator_conf, conf->pdmrx_conf.pdm_out_words_per_channel); + mics_ptr->PdmRx.Init(pdm_res->p_pdm_mics, conf->pdmrx_conf); + if (conf->pdmrx_conf.channel_map) { + mics_ptr->PdmRx.MapChannels(conf->pdmrx_conf.channel_map); + } + mics_ptr->PdmRx.AssertOnDroppedBlock(false); +} + void init_mics_default_filter(pdm_rx_resources_t* pdm_res, const unsigned* channel_map, unsigned stg2_dec_factor) { static int32_t stg1_filter_state[MIC_ARRAY_CONFIG_MIC_COUNT][8]; - mic_array_decimator_conf_t decimator_conf; - memset(&decimator_conf, 0, sizeof(decimator_conf)); + mic_array_conf_t mic_array_conf; + memset(&mic_array_conf, 0, sizeof(mic_array_conf_t)); mic_array_filter_conf_t filter_conf[2] = {{0}}; // decimator - decimator_conf.filter_conf = &filter_conf[0]; - decimator_conf.num_filter_stages = 2; + 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*)stage_1_filter(stg2_dec_factor); filter_conf[0].num_taps = 256; @@ -54,99 +81,55 @@ void init_mics_default_filter(pdm_rx_resources_t* pdm_res, const unsigned* chann filter_conf[1].num_taps = stage_2_num_taps(stg2_dec_factor); filter_conf[1].decimation_factor = stg2_dec_factor; filter_conf[1].shr = stage_2_shift(stg2_dec_factor); - filter_conf[1].state_words_per_channel = decimator_conf.filter_conf[1].num_taps; + filter_conf[1].state_words_per_channel = mic_array_conf.decimator_conf.filter_conf[1].num_taps; filter_conf[1].state = stage_2_state_memory(stg2_dec_factor); - pdm_rx_conf_t pdm_rx_config; - pdm_rx_config.pdm_out_words_per_channel = stg2_dec_factor; - pdm_rx_config.pdm_out_block = get_pdm_rx_out_block(stg2_dec_factor); - pdm_rx_config.pdm_in_double_buf = get_pdm_rx_out_block_double_buf(stg2_dec_factor); - pdm_rx_config.num_channels_in = MIC_ARRAY_CONFIG_MIC_IN_COUNT; - pdm_rx_config.num_channels_out = MIC_ARRAY_CONFIG_MIC_COUNT; - - s_mics->Decimator.Init(decimator_conf, pdm_rx_config.pdm_out_words_per_channel); - - s_mics->PdmRx.Init(pdm_res->p_pdm_mics, pdm_rx_config); - - if(channel_map) { - s_mics->PdmRx.MapChannels(channel_map); - } - - int divide = pdm_res->mclk_freq / pdm_res->pdm_freq; - mic_array_resources_configure(pdm_res, divide); - mic_array_pdm_clock_start(pdm_res); -} - -void init_mic_array_storage(bool use_3_stg_decimator) -{ - assert(s_mics == nullptr && s_mics_3stg == nullptr); // Mic array instance already initialised + mic_array_conf.pdmrx_conf.pdm_out_words_per_channel = stg2_dec_factor; + mic_array_conf.pdmrx_conf.pdm_out_block = get_pdm_rx_out_block(stg2_dec_factor); + mic_array_conf.pdmrx_conf.pdm_in_double_buf = get_pdm_rx_out_block_double_buf(stg2_dec_factor); + mic_array_conf.pdmrx_conf.channel_map = channel_map; - s_use_3_stg_decimator = use_3_stg_decimator; - if(s_use_3_stg_decimator) { - static uint8_t __attribute__((aligned(8))) mic_storage[sizeof(TMicArray_3stg_decimator)]; - s_mics_3stg = new (mic_storage) TMicArray_3stg_decimator(); - } else { - static uint8_t __attribute__((aligned(8))) mic_storage[sizeof(TMicArray)]; - s_mics = new (mic_storage) TMicArray(); + if(s_1mic_override_active) { + assert(mic_array_conf.pdmrx_conf.pdm_out_words_per_channel <= TMicArray::MAX_PDM_OUT_WORDS_PER_CHANNEL); + init_from_conf(s_mics_1mic_override, pdm_res, &mic_array_conf); } -} - -template -static inline void init_from_conf(TMics*& mics_ptr, pdm_rx_resources_t* pdm_res, mic_array_conf_t* conf) -{ - mics_ptr->Decimator.Init(conf->decimator_conf, conf->pdmrx_conf.pdm_out_words_per_channel); - mics_ptr->PdmRx.Init(pdm_res->p_pdm_mics, conf->pdmrx_conf); - if (conf->pdmrx_conf.channel_map) { - mics_ptr->PdmRx.MapChannels(conf->pdmrx_conf.channel_map); + else { + init_from_conf(s_mics, pdm_res, &mic_array_conf); } - mics_ptr->PdmRx.AssertOnDroppedBlock(false); } void init_mics_custom_filter(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf) { - if((mic_array_conf->decimator_conf.num_filter_stages == 1) || (mic_array_conf->decimator_conf.num_filter_stages == 2)) { + if(s_1mic_override_active) { + assert(mic_array_conf->pdmrx_conf.pdm_out_words_per_channel <= TMicArray::MAX_PDM_OUT_WORDS_PER_CHANNEL); + init_from_conf(s_mics_1mic_override, pdm_res, mic_array_conf); + } + else { init_from_conf(s_mics, pdm_res, mic_array_conf); - } else if(mic_array_conf->decimator_conf.num_filter_stages == 3) { - init_from_conf(s_mics_3stg, pdm_res, mic_array_conf); - } else { - assert(false && "Unsupported number of filter stages in mic_array_conf"); } } -void init_mics_custom_filter_1mic_1stg_decimator(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf) -{ - assert(mic_array_conf->pdmrx_conf.pdm_out_words_per_channel <= TMicArray::MAX_PDM_OUT_WORDS_PER_CHANNEL); - s_run_1mic_1stg_decimator = true; - init_mics_custom_filter(pdm_res, mic_array_conf); -} - ///////////////////// // Mic array start // ///////////////////// -void set_output_channel(chanend_t c_frames_out) -{ - if (s_use_3_stg_decimator) { - assert(s_mics_3stg != nullptr); - s_mics_3stg->OutputHandler.FrameTx.SetChannel(c_frames_out); - } else { +void assert_mic_array_start_ready(void) { + if(s_1mic_override_active) { + assert(s_mics_1mic_override != nullptr); + } + else + { assert(s_mics != nullptr); - s_mics->OutputHandler.FrameTx.SetChannel(c_frames_out); } } -void shutdown_mic_array(void) +void set_output_channel(chanend_t c_frames_out) { - if (s_use_3_stg_decimator) { - s_mics_3stg->~TMicArray_3stg_decimator(); + if(s_1mic_override_active) { + s_mics_1mic_override->OutputHandler.FrameTx.SetChannel(c_frames_out); } else { - s_mics->~TMicArray(); + s_mics->OutputHandler.FrameTx.SetChannel(c_frames_out); } - - s_mics_3stg = nullptr; - s_mics = nullptr; - s_use_3_stg_decimator = false; - s_run_1mic_1stg_decimator = false; } #if defined(__XS3A__) @@ -162,10 +145,8 @@ void shutdown_mic_array(void) template void start_mics_with_pdm_isr(TMics* mics_ptr, chanend_t c_frames_out) { - assert(mics_ptr != nullptr); - #if defined(__XS3A__) - CLEAR_KEDI(); // Disable dual-issue mode on XS3A processors. VX4 processors do not have a dual-issue mode. + CLEAR_KEDI(); // Disable dual-issue mode on XS3A processors. VX4 processors do not have a dual-issue mode. #endif mics_ptr->OutputHandler.FrameTx.SetChannel(c_frames_out); @@ -178,8 +159,8 @@ void start_mics_with_pdm_isr(TMics* mics_ptr, chanend_t c_frames_out) void start_mic_array_pdm_isr(chanend_t c_frames_out) { #if MIC_ARRAY_CONFIG_USE_PDM_ISR - if (s_use_3_stg_decimator) { - start_mics_with_pdm_isr(s_mics_3stg, c_frames_out); + if(s_1mic_override_active) { + start_mics_with_pdm_isr(s_mics_1mic_override, c_frames_out); } else { start_mics_with_pdm_isr(s_mics, c_frames_out); @@ -190,37 +171,50 @@ void start_mic_array_pdm_isr(chanend_t c_frames_out) // Helper functions for starting separate tasks void start_pdm_task(void) { - s_mics->PdmRx.ThreadEntry(); + if(s_1mic_override_active) { + s_mics_1mic_override->PdmRx.ThreadEntry(); + } + else { + s_mics->PdmRx.ThreadEntry(); + } } void start_decimator_task() { - if(s_run_1mic_1stg_decimator) { - s_mics->ThreadEntryLowPower_1Mic1StgDecimator(); + if(s_1mic_override_active) { + s_mics_1mic_override->ThreadEntry(); } else { s_mics->ThreadEntry(); } } -void start_pdm_task_3stg(void) -{ - s_mics_3stg->PdmRx.ThreadEntry(); -} +//////////////////////// +// Mic array shutdown // +//////////////////////// -void start_decimator_task_3stg(void) +void shutdown_mic_array(void) { - s_mics_3stg->ThreadEntry(); + if (s_1mic_override_active) { + s_mics_1mic_override->~TMicArray1MicOverride(); + } + else { + s_mics->~TMicArray(); + } + + s_mics_1mic_override = nullptr; + s_mics = nullptr; + s_1mic_override_active = false; } + // Override pdm data port. Only used in tests where a chanend is used as a 'port' for input pdm data. void _mic_array_override_pdm_port(chanend_t c_pdm) { - if (s_use_3_stg_decimator) { - assert(s_mics_3stg != nullptr); - s_mics_3stg->PdmRx.SetPort((port_t)c_pdm); - } else { - assert(s_mics != nullptr); + if(s_1mic_override_active) { + s_mics_1mic_override->PdmRx.SetPort((port_t)c_pdm); + } + else { s_mics->PdmRx.SetPort((port_t)c_pdm); } } diff --git a/lib_mic_array/src/mic_array_task_internal.hpp b/lib_mic_array/src/mic_array_task_internal.hpp index e9613d06..551d7d31 100644 --- a/lib_mic_array/src/mic_array_task_internal.hpp +++ b/lib_mic_array/src/mic_array_task_internal.hpp @@ -8,7 +8,7 @@ #ifdef __cplusplus using TMicArray = mic_array::MicArray, + mic_array::Decimator, mic_array::StandardPdmRxService, // std::conditional uses USE_DCOE to determine which @@ -20,16 +20,13 @@ using TMicArray = mic_array::MicArray>; -using TMicArray_3stg_decimator = mic_array::MicArray, - mic_array::StandardPdmRxService, - // std::conditional uses USE_DCOE to determine which - // sample filter is used. +using TMicArray1MicOverride = mic_array::MicArray<1, + mic_array::Decimator<1>, + mic_array::StandardPdmRxService<1, 1>, typename std::conditional, - mic_array::NopSampleFilter>::type, - mic_array::FrameOutputHandler, + mic_array::NopSampleFilter<1>>::type, + mic_array::FrameOutputHandler<1, MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME, mic_array::ChannelFrameTransmitter>>; union UStg2_filter_state { @@ -88,17 +85,11 @@ inline uint32_t* get_pdm_rx_out_block_double_buf(unsigned stg2_dec_factor) { #endif // __cplusplus MA_C_API -bool get_decimator_stg_count(void); - -MA_C_API -void init_mic_array_storage(bool use_3_stg_decimator); +void init_mic_array_storage(); MA_C_API void init_mics_custom_filter(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf); -MA_C_API -void init_mics_custom_filter_1mic_1stg_decimator(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf); - MA_C_API void init_mics_default_filter(pdm_rx_resources_t* pdm_res, const unsigned* channel_map, unsigned stg2_dec_factor); @@ -111,9 +102,6 @@ void shutdown_mic_array(void); MA_C_API void start_decimator_task(); -MA_C_API -void start_decimator_task_3stg(void); - MA_C_API void start_mic_array_pdm_isr(chanend_t c_frames_out); @@ -121,4 +109,7 @@ MA_C_API void start_pdm_task(void); MA_C_API -void start_pdm_task_3stg(void); +void init_1mic_override(void); + +MA_C_API +void assert_mic_array_start_ready(void); diff --git a/python/stage1.py b/python/stage1.py index 782258ac..d05d2c23 100644 --- a/python/stage1.py +++ b/python/stage1.py @@ -60,7 +60,7 @@ def main(coef_pkl_file, prefix="custom_filt", outstreams=[sys.stdout]): with open(out_path, "w") as f: header_utils.print_header(args, [sys.stdout, f]) main(args.coef_pkl_file, prefix=args.file_prefix, outstreams=[sys.stdout, f]) - header_utils.print_footer([sys.stdout, f]) + header_utils.print_footer([sys.stdout, f], num_filter_stages=1) else: main(args.coef_pkl_file, outstreams=[sys.stdout]) diff --git a/tests/signal/BasicMicArray/CMakeLists.txt b/tests/signal/BasicMicArray/CMakeLists.txt index d8066a5b..39f596e0 100644 --- a/tests/signal/BasicMicArray/CMakeLists.txt +++ b/tests/signal/BasicMicArray/CMakeLists.txt @@ -37,18 +37,21 @@ string(JSON N_MICS_LIST GET ${JSON_CONTENT} N_MICS) string(JSON FRAME_SIZE_LIST GET ${JSON_CONTENT} FRAME_SIZE) string(JSON USE_ISR_LIST GET ${JSON_CONTENT} USE_ISR) string(JSON SAMP_FREQ_LIST GET ${JSON_CONTENT} SAMP_FREQ) +string(JSON ONE_MIC_OVERRIDE_LIST GET ${JSON_CONTENT} 1MIC_OVERRIDE) # Convert JSON lists to CMake lists string(JSON NUM_N_MICS LENGTH ${N_MICS_LIST}) string(JSON NUM_FRAME_SIZE LENGTH ${FRAME_SIZE_LIST}) string(JSON NUM_USE_ISR LENGTH ${USE_ISR_LIST}) string(JSON NUM_SAMP_FREQ LENGTH ${SAMP_FREQ_LIST}) +string(JSON NUM_ONE_MIC_OVERRIDE LENGTH ${ONE_MIC_OVERRIDE_LIST}) # Subtract one off each of the lengths because RANGE includes last element math(EXPR NUM_N_MICS "${NUM_N_MICS} - 1") math(EXPR NUM_FRAME_SIZE "${NUM_FRAME_SIZE} - 1") math(EXPR NUM_USE_ISR "${NUM_USE_ISR} - 1") math(EXPR NUM_SAMP_FREQ "${NUM_SAMP_FREQ} - 1") +math(EXPR NUM_ONE_MIC_OVERRIDE "${NUM_ONE_MIC_OVERRIDE} - 1") # Remove ISR if vx4 as it's not supported if (APP_HW_TARGET STREQUAL "XK-EVK-XU416") @@ -105,34 +108,49 @@ foreach(l RANGE 0 ${NUM_SAMP_FREQ}) string(JSON FRAME_SIZE GET ${FRAME_SIZE_LIST} ${j}) foreach(k RANGE 0 ${NUM_USE_ISR}) string(JSON USE_ISR GET ${USE_ISR_LIST} ${k}) - set(CONFIG "${N_MICS}ch_${FRAME_SIZE}smp_${USE_ISR}isr_${samp_freq_str}") - - message(${CONFIG}) - 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} - -DMIC_ARRAY_CONFIG_MIC_IN_COUNT=${N_MICS} - -DMIC_ARRAY_CONFIG_USE_DC_ELIMINATION=0 - -DAPP_SAMP_FREQ=${APP_SAMP_FREQ} - -DUSE_CUSTOM_FILTER=${USE_CUSTOM_FILT} - ) + foreach(m RANGE 0 ${NUM_ONE_MIC_OVERRIDE}) + string(JSON USE_1MIC_OVERRIDE GET ${ONE_MIC_OVERRIDE_LIST} ${m}) + set(CONFIG "${N_MICS}ch_${FRAME_SIZE}smp_${USE_ISR}isr_${USE_1MIC_OVERRIDE}mo_${samp_freq_str}") + + message(${CONFIG}) + 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} + -DMIC_ARRAY_CONFIG_MIC_IN_COUNT=${N_MICS} + -DMIC_ARRAY_CONFIG_USE_DC_ELIMINATION=0 + -DAPP_SAMP_FREQ=${APP_SAMP_FREQ} + -DUSE_CUSTOM_FILTER=${USE_CUSTOM_FILT} + -DAPP_CONFIG_ONE_MIC_OVERRIDE=${USE_1MIC_OVERRIDE} + ) + endforeach() endforeach() endforeach() endforeach() endforeach() set(APP_INCLUDES src ${AUTOGEN_OUT_DIR}) +# One stage filter (small_768k_to_12k_filter.h) + 1-mic override +set(APP_COMPILER_FLAGS_1stg_filter_1mic_override ${COMMON_COMPILER_FLAGS} + -DMIC_ARRAY_CONFIG_USE_PDM_ISR=0 + -DMIC_ARRAY_CONFIG_SAMPLES_PER_FRAME=2 + -DMIC_ARRAY_CONFIG_MIC_COUNT=2 # Set to something other than 1 to test 1-mic overriding + -DMIC_ARRAY_CONFIG_USE_DC_ELIMINATION=0 + -DAPP_SAMP_FREQ=24000 + -DAPP_CONFIG_ONE_STAGE_DECIMATOR=1 + -DAPP_CONFIG_ONE_MIC_OVERRIDE=1 + ) + +# One stage filter (small_768k_to_12k_filter.h) +set(APP_COMPILER_FLAGS_1stg_filter ${COMMON_COMPILER_FLAGS} + -DMIC_ARRAY_CONFIG_USE_PDM_ISR=0 + -DMIC_ARRAY_CONFIG_SAMPLES_PER_FRAME=2 + -DMIC_ARRAY_CONFIG_MIC_COUNT=2 # Set to something other than 1 to test 1-mic overriding + -DMIC_ARRAY_CONFIG_USE_DC_ELIMINATION=0 + -DAPP_SAMP_FREQ=24000 + -DAPP_CONFIG_ONE_STAGE_DECIMATOR=1 + ) -set(APP_COMPILER_FLAGS_lp_1stg_decimator ${COMMON_COMPILER_FLAGS} - -DMIC_ARRAY_CONFIG_USE_PDM_ISR=0 - -DMIC_ARRAY_CONFIG_SAMPLES_PER_FRAME=2 - -DMIC_ARRAY_CONFIG_MIC_COUNT=1 - -DMIC_ARRAY_CONFIG_USE_DC_ELIMINATION=0 - -DAPP_SAMP_FREQ=24000 - -DUSE_CUSTOM_FILTER=0 - -DAPP_CONFIG_LOW_POWER=1 - ) XMOS_REGISTER_APP() foreach(target ${APP_BUILD_TARGETS}) diff --git a/tests/signal/BasicMicArray/src/app.c b/tests/signal/BasicMicArray/src/app.c index e8234310..093cc6d4 100644 --- a/tests/signal/BasicMicArray/src/app.c +++ b/tests/signal/BasicMicArray/src/app.c @@ -18,11 +18,32 @@ #include "mic_array.h" #include "app_config.h" +// If enabled use the one stage filter specified in small_768k_to_12k_filter.h +#ifndef APP_CONFIG_ONE_STAGE_DECIMATOR +#define APP_CONFIG_ONE_STAGE_DECIMATOR (0) +#endif + +// If enabled use filter corresponding to the .pkl file specified in test_params.json +#ifndef USE_CUSTOM_FILTER +#define USE_CUSTOM_FILTER (0) +#endif + +// If enabled, override the mic input and output channels to 1, despite MIC_ARRAY_CONFIG_MIC_COUNT set to something greater than 1 +#ifndef APP_CONFIG_ONE_MIC_OVERRIDE +#define APP_CONFIG_ONE_MIC_OVERRIDE (0) +#endif + +#if APP_CONFIG_ONE_MIC_OVERRIDE +#define APP_MIC_COUNT (1) +#else +#define APP_MIC_COUNT (MIC_ARRAY_CONFIG_MIC_COUNT) +#endif + #if USE_CUSTOM_FILTER #include "custom_filter.h" #endif -#if APP_CONFIG_LOW_POWER +#if APP_CONFIG_ONE_STAGE_DECIMATOR #include "small_768k_to_12k_filter.h" #endif @@ -45,6 +66,7 @@ DECLARE_JOB(host_words_to_app, (chanend_t, streaming_chanend_t)); typedef struct { + unsigned num_stages; unsigned stg1_tap_count; unsigned stg1_decimation_factor; unsigned stg2_tap_count; @@ -70,20 +92,13 @@ void hwtimer_delay_microseconds(unsigned delay) { static void get_filter_config(unsigned fs, filt_config_t *cfg) { -#if APP_CONFIG_LOW_POWER +#if APP_CONFIG_ONE_STAGE_DECIMATOR cfg->stg1_tap_count = SMALL_768K_TO_12K_FILTER_STG1_TAP_COUNT; cfg->stg1_decimation_factor = SMALL_768K_TO_12K_FILTER_STG1_DECIMATION_FACTOR; - cfg->stg2_tap_count = SMALL_768K_TO_12K_FILTER_STG2_TAP_COUNT; - cfg->stg2_decimation_factor = SMALL_768K_TO_12K_FILTER_STG2_DECIMATION_FACTOR; cfg->stg1_coef_ptr = small_768k_to_12k_filter_stg1_coef; - cfg->stg2_coef_ptr = small_768k_to_12k_filter_stg2_coef; - cfg->stg2_shr = SMALL_768K_TO_12K_FILTER_STG2_SHR; - - cfg->stg3_tap_count = 0; - cfg->stg3_decimation_factor = 1; // for PDM RX block size calculation in the test to work for both 2 and 3 stage filters - cfg->stg3_coef_ptr = NULL; - cfg->stg3_shr = 0; + cfg->num_stages = 1; #elif !USE_CUSTOM_FILTER + cfg->num_stages = 2; cfg->stg1_tap_count = 256; cfg->stg1_decimation_factor = 32; @@ -113,6 +128,7 @@ void get_filter_config(unsigned fs, filt_config_t *cfg) { cfg->stg2_shr = stage2_48k_shift; } #else + cfg->num_stages = 2; cfg->stg1_tap_count = CUSTOM_FILTER_STG1_TAP_COUNT; cfg->stg1_decimation_factor = CUSTOM_FILTER_STG1_DECIMATION_FACTOR; cfg->stg2_tap_count = CUSTOM_FILTER_STG2_TAP_COUNT; @@ -121,6 +137,7 @@ void get_filter_config(unsigned fs, filt_config_t *cfg) { cfg->stg2_coef_ptr = custom_filter_stg2_coef; cfg->stg2_shr = CUSTOM_FILTER_STG2_SHR; #if (NUM_DECIMATION_STAGES==3) + cfg->num_stages = 3; cfg->stg3_tap_count = CUSTOM_FILTER_STG3_TAP_COUNT; cfg->stg3_decimation_factor = CUSTOM_FILTER_STG3_DECIMATION_FACTOR; cfg->stg3_coef_ptr = custom_filter_stg3_coef; @@ -156,20 +173,22 @@ void app_print_filters() } 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"); + if(filt_cfg.num_stages > 1) { + 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); + printf("stage2_shr = %d\n", filt_cfg.stg2_shr); + } } static inline @@ -207,7 +226,7 @@ void cmd_loop(chanend_t c_from_host) uint32_t cmd = ((uint32_t*)(void*) &cmd_buff[0])[0]; cmd_print_msg(cmd); cmd_perform_action(cmd); - continue; + break; } } @@ -250,8 +269,8 @@ pdm_rx_resources_t pdm_res = PDM_RX_RESOURCES_SDR( #if USE_CUSTOM_FILTER 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]; + static int32_t stg1_filter_state[APP_MIC_COUNT][8]; + static int32_t stg2_filter_state[APP_MIC_COUNT][CUSTOM_FILTER_STG2_TAP_COUNT]; memset(mic_array_conf, 0, sizeof(mic_array_conf_t)); //decimator @@ -273,7 +292,7 @@ static void init_mic_conf(mic_array_conf_t *mic_array_conf, mic_array_filter_con filter_conf[1].state_words_per_channel = CUSTOM_FILTER_STG2_TAP_COUNT; // stage 3 #if (NUM_DECIMATION_STAGES==3) - static int32_t stg3_filter_state[MIC_ARRAY_CONFIG_MIC_COUNT][CUSTOM_FILTER_STG3_TAP_COUNT]; + static int32_t stg3_filter_state[APP_MIC_COUNT][CUSTOM_FILTER_STG3_TAP_COUNT]; filter_conf[2].coef = (int32_t*)custom_filter_stg3_coef; filter_conf[2].num_taps = CUSTOM_FILTER_STG3_TAP_COUNT; filter_conf[2].decimation_factor = CUSTOM_FILTER_STG3_DECIMATION_FACTOR; @@ -284,26 +303,23 @@ static void init_mic_conf(mic_array_conf_t *mic_array_conf, mic_array_filter_con #define CUSTOM_FILTER_STG3_DECIMATION_FACTOR (1) /*for PDM RX block size calculation below to work for both 2 and 3 stage filter*/ #endif // 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]; + static uint32_t pdmrx_out_block[APP_MIC_COUNT][CUSTOM_FILTER_STG2_DECIMATION_FACTOR * CUSTOM_FILTER_STG3_DECIMATION_FACTOR]; + static uint32_t __attribute__((aligned(8))) pdmrx_out_block_double_buf[2][APP_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.num_channels_in = MIC_ARRAY_CONFIG_MIC_COUNT; - mic_array_conf->pdmrx_conf.num_channels_out = MIC_ARRAY_CONFIG_MIC_COUNT; } #endif -#if APP_CONFIG_LOW_POWER +#if APP_CONFIG_ONE_STAGE_DECIMATOR static -void init_mic_conf_lp_filter( +void init_mic_conf_one_stage_filter( mic_array_conf_t *mic_array_conf, - mic_array_filter_conf_t filter_conf[2], + mic_array_filter_conf_t *filter_conf, 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][SMALL_768K_TO_12K_FILTER_STG2_TAP_COUNT]; + static int32_t stg1_filter_state[APP_MIC_COUNT][8]; memset(mic_array_conf, 0, sizeof(mic_array_conf_t)); //decimator @@ -318,14 +334,12 @@ void init_mic_conf_lp_filter( filter_conf[0].state_words_per_channel = filter_conf[0].num_taps/32; // works on 1-bit samples // pdm rx - static uint32_t pdmrx_out_block[MIC_ARRAY_CONFIG_MIC_COUNT][MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME]; - static uint32_t pdmrx_out_block_double_buf[2][MIC_ARRAY_CONFIG_MIC_COUNT * MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME] __attribute__((aligned(8))); + static uint32_t pdmrx_out_block[APP_MIC_COUNT][MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME]; // PDM RX output block size has to be the same as Mic array output frame size! + static uint32_t pdmrx_out_block_double_buf[2][APP_MIC_COUNT * MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME] __attribute__((aligned(8))); mic_array_conf->pdmrx_conf.pdm_out_words_per_channel = MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME; 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.num_channels_in = MIC_ARRAY_CONFIG_MIC_COUNT; - mic_array_conf->pdmrx_conf.num_channels_out = MIC_ARRAY_CONFIG_MIC_COUNT; } #endif @@ -335,19 +349,26 @@ void app_mic( chanend_t c_pdm_in, chanend_t c_frames_out) //non-streaming { -#if APP_CONFIG_LOW_POWER +#if (APP_CONFIG_ONE_STAGE_DECIMATOR || USE_CUSTOM_FILTER) mic_array_conf_t mic_array_conf; mic_array_filter_conf_t filter_conf[NUM_DECIMATION_STAGES]; - init_mic_conf_lp_filter(&mic_array_conf, filter_conf, NULL); - mic_array_init_custom_filter_1mic_1stg_decimator(&pdm_res, &mic_array_conf); -#elif !USE_CUSTOM_FILTER - mic_array_init(&pdm_res, NULL, APP_SAMP_FREQ); +#if APP_CONFIG_ONE_STAGE_DECIMATOR + init_mic_conf_one_stage_filter(&mic_array_conf, filter_conf, NULL); #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); +#endif +#endif // (APP_CONFIG_ONE_STAGE_DECIMATOR || USE_CUSTOM_FILTER) + +#if APP_CONFIG_ONE_MIC_OVERRIDE + mic_array_enable_1mic_override(); +#endif + +#if (APP_CONFIG_ONE_STAGE_DECIMATOR || USE_CUSTOM_FILTER) mic_array_init_custom_filter(&pdm_res, &mic_array_conf); +#else + mic_array_init(&pdm_res, NULL, APP_SAMP_FREQ); #endif + _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 @@ -358,7 +379,7 @@ void app_mic( // Sometimes xscope doesn't keep up causing backpressure so add a FIFO to decouple this, at least up to 8 frames. // We can buffer up to 8 chars in a same tile chanend. const unsigned fifo_entries = 8; -typedef int32_t ma_frame_t[MIC_ARRAY_CONFIG_MIC_COUNT][MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME]; +typedef int32_t ma_frame_t[APP_MIC_COUNT][MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME]; ma_frame_t frame_fifo[fifo_entries]; @@ -370,7 +391,7 @@ void app_output_task(chanend_t c_frames_in, chanend_t c_fifo) filt_config_t filt_cfg; get_filter_config(APP_SAMP_FREQ, &filt_cfg); - xscope_int(META_OUT, MIC_ARRAY_CONFIG_MIC_COUNT); + xscope_int(META_OUT, APP_MIC_COUNT); xscope_int(META_OUT, filt_cfg.stg1_tap_count); xscope_int(META_OUT, filt_cfg.stg1_decimation_factor); xscope_int(META_OUT, filt_cfg.stg2_tap_count); @@ -382,10 +403,10 @@ void app_output_task(chanend_t c_frames_in, chanend_t c_fifo) // receive the output of the mic array and send it to the host via a fifo to decouple the backpressure from xscope - int32_t frame[MIC_ARRAY_CONFIG_MIC_COUNT][MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME]; + int32_t frame[APP_MIC_COUNT][MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME]; uint8_t fifo_idx = 0; while(1){ - ma_frame_rx(&frame[0][0], c_frames_in, MIC_ARRAY_CONFIG_MIC_COUNT, MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME); + ma_frame_rx(&frame[0][0], c_frames_in, APP_MIC_COUNT, MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME); memcpy(frame_fifo[fifo_idx], &frame[0][0], sizeof(ma_frame_t)); int t0 = get_reference_time(); chanend_out_byte(c_fifo, fifo_idx++); @@ -408,7 +429,7 @@ void app_fifo_to_xscope_task(chanend_t c_fifo) // Send it to host sample by sample rather than channel by channel for(int smp = 0; smp < MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME; smp++) { - for(int ch = 0; ch < MIC_ARRAY_CONFIG_MIC_COUNT; ch++){ + for(int ch = 0; ch < APP_MIC_COUNT; ch++){ xscope_int(DATA_OUT, (*ptr)[ch][smp]); } } diff --git a/tests/signal/BasicMicArray/src/small_768k_to_12k_filter.h b/tests/signal/BasicMicArray/src/small_768k_to_12k_filter.h index 867c2290..29ccc17f 100644 --- a/tests/signal/BasicMicArray/src/small_768k_to_12k_filter.h +++ b/tests/signal/BasicMicArray/src/small_768k_to_12k_filter.h @@ -4,7 +4,7 @@ #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 */ +/* Autogenerated by running 'python combined.py ../tests/signal/BasicMicArray/small_768k_to_12k_filter_int.pkl -fp small_768k_to_12k_filter'. Do not edit */ #include @@ -33,27 +33,6 @@ uint32_t small_768k_to_12k_filter_stg1_coef[128] = { 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) +#define NUM_DECIMATION_STAGES (1) #endif diff --git a/tests/signal/BasicMicArray/test_mic_array.py b/tests/signal/BasicMicArray/test_mic_array.py index 66b84b1a..bfc1678d 100644 --- a/tests/signal/BasicMicArray/test_mic_array.py +++ b/tests/signal/BasicMicArray/test_mic_array.py @@ -40,32 +40,57 @@ with open(Path(__file__).parent / "test_params.json") as f: params = json.load(f) -smoke_test_chans = [8] +smoke_test_chans = [4] # Since 8n-16frame is currently disabled. See https://github.com/xmos/lib_mic_array/issues/288 smoke_test_frame_sz = [1, 16] -def ma_test_uncollect(config, chans, frame_size, use_isr, fs): +def ma_test_uncollect(config, chans, frame_size, use_isr, one_mic_override, fs): + """Determine whether to skip test cases based on test level. + + Returns True to uncollect (skip), False to collect (run). + + Smoke tests: Collect only 4ch with 1 or 16 frame sizes. + Nightly tests with one_mic_override=0: Collect all configurations. + Nightly tests with one_mic_override=1: Collect only 4ch/16frame with custom filters and 16kHz. + """ level = config.getoption("level") if level == "smoke": if((chans in smoke_test_chans) and (frame_size in smoke_test_frame_sz)): return False else: - return True # uncollect everything other than 1_isr-16frame-8n_mics - return False # for level != smoke, collect everything + return True # uncollect everything other than 4ch with 1 or 16 frame sizes + else: # nightly + if one_mic_override == 0: # Collect everything for which one_mic_override=0 + return False + else: # one_mic_override = 1 + # collect only one config (4ch, 16 frame), for the custom pkl file and one of the supported rates (16000) to keep the test time reasonable + # No particular reason why this specific config was selected + if((chans == 4) and (frame_size == 16)): + if (not isinstance(fs, int)): # custom pkl file + return False + elif fs == 16000: + return False + else: + # If we've reached here, uncollect! + return True + else: + # If we've reached here, uncollect! + return True class Test_BasicMicArray(MicArraySharedBase): @pytest.mark.uncollect_if(func=ma_test_uncollect) @pytest.mark.parametrize("chans", params["N_MICS"], ids=[f"{nm}n_mics" for nm in params["N_MICS"]]) @pytest.mark.parametrize("frame_size", params["FRAME_SIZE"], ids=[f"{fs}frame" for fs in params["FRAME_SIZE"]]) - @pytest.mark.parametrize("use_isr", params["USE_ISR"], ids=[f"{ui}_isr" for ui in params["USE_ISR"]]) + @pytest.mark.parametrize("use_isr", params["USE_ISR"], ids=[f"{ui}isr" for ui in params["USE_ISR"]]) + @pytest.mark.parametrize("one_mic_override", params["1MIC_OVERRIDE"], ids=[f"{ui}mo" for ui in params["1MIC_OVERRIDE"]]) @pytest.mark.parametrize("fs", params["SAMP_FREQ"], ids=[f"{s}" for s in params["SAMP_FREQ"]]) - def test_BasicMicArray(self, request, chans, frame_size, use_isr, fs): + def test_BasicMicArray(self, request, chans, frame_size, use_isr, one_mic_override, fs): cwd = Path(request.fspath).parent custom_filter_file = None if not isinstance(fs, int): # fs must contain the name of the filter .pkl file custom_filter_file = fs - cfg = f"{chans}ch_{frame_size}smp_{use_isr}isr_customfs" + cfg = f"{chans}ch_{frame_size}smp_{use_isr}isr_{one_mic_override}mo_customfs" else: - cfg = f"{chans}ch_{frame_size}smp_{use_isr}isr_{fs}fs" + cfg = f"{chans}ch_{frame_size}smp_{use_isr}isr_{one_mic_override}mo_{fs}fs" xe_path = f'{cwd}/bin/{cfg}/test_ma_{cfg}.xe' assert Path(xe_path).exists(), f"Cannot find {xe_path}" @@ -86,6 +111,9 @@ def test_BasicMicArray(self, request, chans, frame_size, use_isr, fs): # Total PDM samples (per channel) samp_total = samp_per_frame * frames + if one_mic_override == 1: + chans = 1 # Override chans to 1 + # Generate random PDM signal sig = PdmSignal.random(chans, samp_total) @@ -127,8 +155,17 @@ def test_BasicMicArray(self, request, chans, frame_size, use_isr, fs): assert result_diff <= threshold, f"max diff between python and xcore mic array output ({result_diff}) exceeds threshold ({threshold})" - @pytest.mark.parametrize("decimator_stgs", [1], ids=["1stg"]) - def test_BasicMicArrayLowPower(self, request, decimator_stgs): + @pytest.mark.parametrize("chans", [1, 2], ids=["1mic_override", "2mic"]) + def test_BasicMicArrayOneStageFilter(self, request, chans): + """Verify sample-level correctness of the 1-stage filter path using small_768k_to_12k_filter_int.pkl. + + Tests two configurations of the stage-1-only decimator: + - chans=1: 1-mic override enabled (MIC_ARRAY_CONFIG_MIC_COUNT set > 1 in the build, overridden at runtime to 1). + - chans=2: no override, 2-mic normal operation. + + Generates random PDM input, computes the expected output via the Python stage-1 filter, + then compares against the xcore device output sample-by-sample within a fixed tolerance. + """ cwd = Path(request.fspath).parent filter = self.filter(Path(__file__).parent / "small_768k_to_12k_filter_int.pkl") @@ -137,23 +174,28 @@ def test_BasicMicArrayLowPower(self, request, decimator_stgs): samp_per_frame = 32 frames = request.config.getoption("frames") - + decimator_stgs = 1 # --- num decimator stages dependent behaviour --- stg1_only = (decimator_stgs == 1) - samp_total = samp_per_frame * frames * (2 if stg1_only else 1) - device_output_delay_samps = 0 if stg1_only else 1 - sample_override = frames * 2 if stg1_only else None output_frame_size = 2 if stg1_only else 1 + samp_total = samp_per_frame * frames * output_frame_size + device_output_delay_samps = 0 if stg1_only else 1 + sample_override = frames * output_frame_size if stg1_only else None # ------------------------------------------------- - sig = PdmSignal.random(1, samp_total) + sig = PdmSignal.random(chans, samp_total) expected = filter.Filter(sig.signal, stg1_only=stg1_only) if self.print_output: print(f"Expected output: {expected}") - cfg = f"lp_{decimator_stgs}stg_decimator" + assert chans in [1,2] + if chans == 1: + cfg = f"{decimator_stgs}stg_filter_1mic_override" + elif chans == 2: + cfg = f"{decimator_stgs}stg_filter" + xe_path = f"{cwd}/bin/{cfg}/test_ma_{cfg}.xe" assert Path(xe_path).exists(), f"Cannot find {xe_path}" @@ -163,11 +205,12 @@ def test_BasicMicArrayLowPower(self, request, decimator_stgs): extra_xrun_args="--id 0" ) as dev: - assert dev.param["channels"] == 1 + assert dev.param["channels"] == chans assert dev.param["s1.dec_factor"] == filter.s1.DecimationFactor assert dev.param["s1.tap_count"] == filter.s1.TapCount - assert dev.param["s2.dec_factor"] == filter.s2.DecimationFactor - assert dev.param["s2.tap_count"] == filter.s2.TapCount + if decimator_stgs > 1: + assert dev.param["s2.dec_factor"] == filter.s2.DecimationFactor + assert dev.param["s2.tap_count"] == filter.s2.TapCount assert dev.param["frame_size"] == output_frame_size assert dev.param["use_isr"] == 0 diff --git a/tests/signal/BasicMicArray/test_params.json b/tests/signal/BasicMicArray/test_params.json index dc52dce4..1be5f8d6 100644 --- a/tests/signal/BasicMicArray/test_params.json +++ b/tests/signal/BasicMicArray/test_params.json @@ -2,5 +2,6 @@ "N_MICS": [1, 2, 4, 8], "FRAME_SIZE": [1, 16], "USE_ISR": [0, 1], + "1MIC_OVERRIDE": [0, 1], "SAMP_FREQ": [16000, 32000, 48000, "good_3_stage_filter_int.pkl"] -} \ No newline at end of file +} diff --git a/tests/signal/BasicMicArray/test_thdn.py b/tests/signal/BasicMicArray/test_thdn.py index 08f7f604..1b4f64d2 100644 --- a/tests/signal/BasicMicArray/test_thdn.py +++ b/tests/signal/BasicMicArray/test_thdn.py @@ -63,9 +63,9 @@ def test_thdn(self, pytestconfig, request, fs, platform): custom_filter_file = None if not isinstance(fs, int): # fs must contain the name of the filter .pkl file custom_filter_file = fs - cfg = f"{chans}ch_{frame_size}smp_{use_isr}isr_customfs" + cfg = f"{chans}ch_{frame_size}smp_{use_isr}isr_0mo_customfs" else: - cfg = f"{chans}ch_{frame_size}smp_{use_isr}isr_{fs}fs" + cfg = f"{chans}ch_{frame_size}smp_{use_isr}isr_0mo_{fs}fs" if custom_filter_file: filter = self.filter(Path(__file__).parent / f"{custom_filter_file}") @@ -148,7 +148,7 @@ def test_thdn(self, pytestconfig, request, fs, platform): - def thdn_test_lowpower_uncollect(config, platform, decimator_stgs, test_freq): + def thdn_test_OneStageFilter_uncollect(config, platform, decimator_stgs): level = config.getoption("level") if level == "smoke": if "xcore" in platform: @@ -161,20 +161,22 @@ def to_float_array(self, x): return x.astype(np.float64) / np.iinfo(x.dtype).max return x - @pytest.mark.uncollect_if(func=thdn_test_lowpower_uncollect) + @pytest.mark.uncollect_if(func=thdn_test_OneStageFilter_uncollect) @pytest.mark.parametrize("platform", ["python_only", "python_xcore"]) @pytest.mark.parametrize("decimator_stgs", [1], ids=["1stg"]) - @pytest.mark.parametrize("test_freq", [300, 5000], ids=["300hz", "5000hz"]) - def test_thdn_lowpower(self, pytestconfig, request, platform, decimator_stgs, test_freq): + def test_thdn_OneStageFilter(self, pytestconfig, request, platform, decimator_stgs): + """Validate THD+N of the 1-stage filter path (768 kHz PDM -> 24 kHz PCM first stage decimator). + + Uses `small_768k_to_12k_filter_int.pkl` with only stage-1 decimation active. + Tests a 2-channel 2-tone input and checks both Python reference and xcore outputs + against per-tone THD+N thresholds. Also verifies sample-level diff between + Python and xcore integer outputs within a fixed tolerance. + """ duration_s = 2 # running reduced duration. See https://github.com/xmos/lib_mic_array/issues/289 pdm_freq = 768_000 - - thdn_threshold = { - (12000, 300): -111.0, - (12000, 5000): -105.0, - (24000, 300): -79.0, - (24000, 5000): -76.0, - } + chans = 2 + freq_hz = [300, 5000] + thdn_threshold = [-79.0, -76.0] cwd = Path(request.fspath).parent filter = self.filter(Path(__file__).parent / "small_768k_to_12k_filter_int.pkl") @@ -189,17 +191,17 @@ def test_thdn_lowpower(self, pytestconfig, request, platform, decimator_stgs, te print(f"decimator_stgs = {decimator_stgs}, fs = {fs}") # ------------------------------------------------- - cfg = f"lp_{decimator_stgs}stg_decimator" + cfg = f"{decimator_stgs}stg_filter" xe_path = f"{cwd}/bin/{cfg}/test_ma_{cfg}.xe" assert Path(xe_path).exists(), f"Cannot find {xe_path}" - print(f"Test frequency {test_freq}\n") + print(f"Test frequencies {freq_hz}\n") # Generate PDM input # Test one freq at a time since low-power mic array is mono sig_sine_pdm, sig_sine_pcm = PdmSignal.sine( - [test_freq], - [0.52], + freq_hz, + [0.52]*len(freq_hz), fs, duration_s, fs_pdm=pdm_freq @@ -211,34 +213,23 @@ def test_thdn_lowpower(self, pytestconfig, request, platform, decimator_stgs, te if self.print_output: print(f"Expected output: {expected}") - print(f"Expected output shape: {expected.shape}") - expected_output_float = self.to_float_array(expected) - input_thdn = THDN(sig_sine_pcm[0], fs, fund_freq=test_freq) - python_output_thdn = THDN(expected_output_float[0], fs, fund_freq=test_freq) - - threshold = thdn_threshold[(fs, test_freq)] - - print( - f"test_freq {test_freq}, " - f"python_output_thdn = {python_output_thdn}, " - f"input_thdn = {input_thdn}" - ) - - assert python_output_thdn < threshold, ( - f"At sampling rate {fs}, test_freq {test_freq}, " - f"Python output THDN {python_output_thdn} exceeds threshold {threshold}" - ) + for i in range(len(freq_hz)): + input_thdn = THDN(sig_sine_pcm[i], fs, fund_freq=freq_hz[i]) + python_output_thdn = THDN(expected_output_float[i], fs, fund_freq=freq_hz[i]) + print(f"At fundamental freq {freq_hz[i]}, samp freq {fs}: python_output_thdn = {python_output_thdn}, input_thdn = {input_thdn}") + assert python_output_thdn < thdn_threshold[i], ( + f"At sampling rate {fs}, freq {freq_hz[i]}, " + f"Python output THDN {python_output_thdn} exceeds threshold {thdn_threshold[i]}" + ) if "xcore" in platform: print("Running xcore") with MicArrayDevice(xe_path, quiet_xgdb=not self.print_xgdb, extra_xrun_args="--id 0") as dev: - assert dev.param["channels"] == 1 + assert dev.param["channels"] == chans assert dev.param["s1.dec_factor"] == filter.s1.DecimationFactor assert dev.param["s1.tap_count"] == filter.s1.TapCount - assert dev.param["s2.dec_factor"] == filter.s2.DecimationFactor - assert dev.param["s2.tap_count"] == filter.s2.TapCount assert dev.param["frame_size"] == output_frame_size assert dev.param["use_isr"] == 0 @@ -251,18 +242,10 @@ def test_thdn_lowpower(self, pytestconfig, request, platform, decimator_stgs, te device_output_float = self.to_float_array(device_output) - xcore_output_thdn = THDN(device_output_float[0][int(fs/10):], fs, fund_freq=test_freq) - - print( - f"test_freq {test_freq}, " - f"xcore_output_thdn = {xcore_output_thdn}, " - f"input_thdn = {input_thdn}" - ) - - assert xcore_output_thdn < threshold, ( - f"At sampling rate {fs}, test_freq {test_freq}, " - f"XCORE output THDN {xcore_output_thdn} exceeds threshold {threshold}" - ) + for i in range(len(freq_hz)): + xcore_output_thdn = THDN(device_output_float[i][int(fs/10):], fs, fund_freq=freq_hz[i]) + print(f"At fundamental freq {freq_hz[i]}, samp freq {fs}: xcore_output_thdn = {xcore_output_thdn}") + assert xcore_output_thdn < thdn_threshold[i], f"At sampling rate {fs}, freq {freq_hz[i]}, XCORE output THDN {xcore_output_thdn} exceeds threshold {thdn_threshold[i]}" if self.print_output: print(f"Device output: {device_output}") diff --git a/tests/signal/TwoStageDecimator/src/run.cpp b/tests/signal/TwoStageDecimator/src/run.cpp index 9ee7f37a..53f95422 100644 --- a/tests/signal/TwoStageDecimator/src/run.cpp +++ b/tests/signal/TwoStageDecimator/src/run.cpp @@ -56,7 +56,7 @@ void process_signal(chanend_t c_from_host) { constexpr unsigned BLOCK_WORDS = CHAN_COUNT * S2_DEC_FACT; - using TDecimator = mic_array::TwoStageDecimator; + using TDecimator = mic_array::Decimator; TDecimator dec; static int32_t stg1_filter_state[CHAN_COUNT][8]; @@ -93,7 +93,7 @@ void process_signal(chanend_t c_from_host) printf("Processing %u blocks of PDM samples..\n", block_count); for(int k = 0; k < block_count; k++){ s_chan_in_buf_word(c_from_host, &buffer[0], BLOCK_WORDS); - dec.ProcessBlock(sample_out, buffer); + dec.ProcessBlockTwoStage(sample_out, buffer); for(int c = 0; c < CHAN_COUNT; c++){ xscope_int(DATA_OUT, sample_out[c]); } diff --git a/tests/signal/TwoStageDecimator/test_stage1.py b/tests/signal/TwoStageDecimator/test_stage1.py index d9632d80..6e44610e 100644 --- a/tests/signal/TwoStageDecimator/test_stage1.py +++ b/tests/signal/TwoStageDecimator/test_stage1.py @@ -1,11 +1,11 @@ # Copyright 2022-2026 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. - + ###### -# Test: TwoStageDecimator stage 1 test +# Test: Decimator::ProcessBlockTwoStage stage 1 test # -# This test is intended to make sure the first stage of the TwoStageDecimator is -# producing correct results. +# This test is intended to make sure the first stage of the Decimator::ProcessBlockTwoStage is +# producing correct results. # # Notes: # - This test assumes that the CMake targets for this app are all already @@ -34,16 +34,16 @@ class Test_Stage1(object): @pytest.fixture(autouse=True) def __init_case(self, request): - np.set_printoptions(threshold=sys.maxsize, + np.set_printoptions(threshold=sys.maxsize, linewidth=80) self.print_out = request.config.getoption("print_output") def gen_filter(self, s2_tap_count, s2_dec_factor): - # This test uses a random first stage filter. No arithmetic saturation is + # This test uses a random first stage filter. No arithmetic saturation is # possible, regardless of what we pick. s1_coef = np.round(np.ldexp((np.random.random_sample(256) - 0.5), 15)).astype(np.int16) s1_filter = filters.Stage1Filter(s1_coef) - + # This test uses a simple pass-through filter for the second stage # decimator. (i.e. b = [1.0, 0, 0, 0, 0, ...]) The output from the full # decimator should then be exactly what was output by the first stage, with @@ -54,7 +54,7 @@ def gen_filter(self, s2_tap_count, s2_dec_factor): assert s2_filter.Shr == 0 return filters.TwoStageFilter(s1_filter, s2_filter) - + @pytest.mark.parametrize("config", params["CONFIG"], ids=[str(param) for param in params["CONFIG"]]) def test_stage1(self, request, config): @@ -95,8 +95,8 @@ def test_stage1(self, request, config): if self.print_out: print(f"Device output: {device_output}") # The second stage filter will usually yield exactly correct results, but - # not always, because the 64-bit partial products of the inner product - # (i.e. filter_state[:] * filter_coef[:]) have a rounding-right-shift + # not always, because the 64-bit partial products of the inner product + # (i.e. filter_state[:] * filter_coef[:]) have a rounding-right-shift # applied to them prior to being summed. result_diff = np.max(np.abs(expected - device_output)) assert result_diff <= 1 diff --git a/tests/signal/TwoStageDecimator/test_stage2.py b/tests/signal/TwoStageDecimator/test_stage2.py index 0b6f9cc4..e267eb80 100644 --- a/tests/signal/TwoStageDecimator/test_stage2.py +++ b/tests/signal/TwoStageDecimator/test_stage2.py @@ -1,14 +1,14 @@ # Copyright 2022-2026 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. - + ###### -# Test: TwoStageDecimator stage 2 test +# Test: Decimator::ProcessBlockTwoStage stage 2 test # -# This test is intended to make sure the second stage of the TwoStageDecimator -# is producing correct results. This test relies on the assumption that the +# This test is intended to make sure the second stage of the Decimator::ProcessBlockTwoStage +# is producing correct results. This test relies on the assumption that the # first stage decimator works correctly (all test vectors go through the first # stage before they get to the second stage), so technically it's looking at the -# whole TwoStageDecimator input->output. If this test is failing but +# whole Decimator::ProcessBlockTwoStage input->output. If this test is failing but # test_stage1.py works, then there is an issue in the second stage (or this test # is wrong). # @@ -40,17 +40,17 @@ class Test_Stage2(object): @pytest.fixture(autouse=True) def __init_case(self, request): - np.set_printoptions(threshold=sys.maxsize, + np.set_printoptions(threshold=sys.maxsize, linewidth=80) self.print_out = request.config.getoption("print_output") def gen_filter(self, s2_tap_count, s2_dec_factor): - # This test uses a random first stage filter. No arithmetic saturation is + # This test uses a random first stage filter. No arithmetic saturation is # possible, regardless of what we pick. s1_coef = np.round(np.ldexp((np.random.random_sample(256) - 0.5), 15)).astype(np.int16) s1_filter = filters.Stage1Filter(s1_coef) - + # We'll generate a random filter for the second stage as well. We'll # normalize it so that we're not worried about saturating. s2_coef = np.random.random_sample(s2_tap_count) - 0.5 @@ -59,7 +59,7 @@ def gen_filter(self, s2_tap_count, s2_dec_factor): s2_filter = filters.Stage2Filter(s2_coef, s2_dec_factor) return filters.TwoStageFilter(s1_filter, s2_filter) - + @pytest.mark.parametrize("config", params["CONFIG"], ids=[str(param) for param in params["CONFIG"]]) def test_stage2(self, request, config): @@ -100,9 +100,9 @@ def test_stage2(self, request, config): if self.print_out: print(f"Device output: {device_output}") # The second stage filter will usually yield exactly correct results, but - # not always, because the 64-bit partial products of the inner product - # (i.e. filter_state[:] * filter_coef[:]) have a rounding-right-shift + # not always, because the 64-bit partial products of the inner product + # (i.e. filter_state[:] * filter_coef[:]) have a rounding-right-shift # applied to them prior to being summed. result_diff = np.max(np.abs(expected - device_output)) - assert result_diff <= 5 # This used to be 4 but we get a very occaisonal test failure when it becomes 5. This is an acceptable relaxation of the test. + assert result_diff <= 5 # This used to be 4 but we get a very occasional test failure when it becomes 5. This is an acceptable relaxation of the test. diff --git a/tests/signal/pdmrx_isr/src/app.cpp b/tests/signal/pdmrx_isr/src/app.cpp index 9056ce4e..26f6cbfc 100644 --- a/tests/signal/pdmrx_isr/src/app.cpp +++ b/tests/signal/pdmrx_isr/src/app.cpp @@ -31,8 +31,6 @@ void app_pdm_rx_isr_setup( pdm_rx_config.pdm_out_words_per_channel = MY_STAGE2_DEC_FACTOR; pdm_rx_config.pdm_out_block = (uint32_t*)pdmrx_out_block; pdm_rx_config.pdm_in_double_buf = (uint32_t*)pdmrx_in_block_double_buf; - pdm_rx_config.num_channels_in = 1; - pdm_rx_config.num_channels_out = 1; my_pdm_rx.Init((port_t)c_from_host, pdm_rx_config); my_pdm_rx.AssertOnDroppedBlock(false); diff --git a/tests/signal/profile/app_memory/src/app.cpp b/tests/signal/profile/app_memory/src/app.cpp index 2b150e49..ab6e287f 100644 --- a/tests/signal/profile/app_memory/src/app.cpp +++ b/tests/signal/profile/app_memory/src/app.cpp @@ -58,7 +58,7 @@ pdm_rx_resources_t pdm_res = PDM_RX_RESOURCES_DDR( #endif using TMicArray = mic_array::MicArray, + mic_array::Decimator, mic_array::StandardPdmRxService, typename std::conditionalpdmrx_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.num_channels_in = MIC_ARRAY_CONFIG_MIC_COUNT; - mic_array_conf->pdmrx_conf.num_channels_out = MIC_ARRAY_CONFIG_MIC_COUNT; } #endif diff --git a/tests/signal/profile/mic_array_memory.json b/tests/signal/profile/mic_array_memory.json index 34a4881f..d214686c 100644 --- a/tests/signal/profile/mic_array_memory.json +++ b/tests/signal/profile/mic_array_memory.json @@ -1,34 +1,34 @@ { "1mic_custom": { "available": 524288, - "used": 12636, + "used": 13308, "status": "OKAY", - "stack": 580, - "code": 8102, - "data": 3954 + "stack": 588, + "code": 8694, + "data": 4026 }, "1mic_default": { "available": 524288, - "used": 17092, + "used": 17236, "status": "OKAY", - "stack": 644, - "code": 9230, - "data": 7218 + "stack": 652, + "code": 9578, + "data": 7006 }, "2mic_custom": { "available": 524288, - "used": 14036, + "used": 14892, "status": "OKAY", - "stack": 588, - "code": 8326, - "data": 5122 + "stack": 604, + "code": 9078, + "data": 5210 }, "2mic_default": { "available": 524288, - "used": 18668, + "used": 20508, "status": "OKAY", - "stack": 652, - "code": 9550, - "data": 8466 + "stack": 668, + "code": 11450, + "data": 8390 } } \ No newline at end of file diff --git a/tests/signal/profile/mic_array_memory_table.rst b/tests/signal/profile/mic_array_memory_table.rst index 7c00e8bd..f55b7829 100644 --- a/tests/signal/profile/mic_array_memory_table.rst +++ b/tests/signal/profile/mic_array_memory_table.rst @@ -12,25 +12,25 @@ - Data * - 1mic_custom - 524288 - - 12636 - - 580 - - 8102 - - 3954 + - 13308 + - 588 + - 8694 + - 4026 * - 1mic_default - 524288 - - 17092 - - 644 - - 9230 - - 7218 + - 17236 + - 652 + - 9578 + - 7006 * - 2mic_custom - 524288 - - 14036 - - 588 - - 8326 - - 5122 + - 14892 + - 604 + - 9078 + - 5210 * - 2mic_default - 524288 - - 18668 - - 652 - - 9550 - - 8466 \ No newline at end of file + - 20508 + - 668 + - 11450 + - 8390 \ No newline at end of file diff --git a/tests/signal/profile/mic_array_mips_table.rst b/tests/signal/profile/mic_array_mips_table.rst index 7619596b..ecf671c4 100644 --- a/tests/signal/profile/mic_array_mips_table.rst +++ b/tests/signal/profile/mic_array_mips_table.rst @@ -11,48 +11,48 @@ * - 1 - ISR - 16000 - - 13.906 + - 13.810 * - 1 - ISR - 32000 - - 16.945 + - 16.849 * - 1 - ISR - 48000 - - 20.970 + - 20.873 * - 1 - THREAD - 16000 - - 12.498 + - 12.514 * - 1 - THREAD - 32000 - - 15.474 + - 15.409 * - 1 - THREAD - 48000 - - 19.433 + - 19.290 * - 2 - ISR - 16000 - - 28.907 + - 28.685 * - 2 - ISR - 32000 - - 34.269 + - 34.013 * - 2 - ISR - 48000 - - 41.645 + - 41.358 * - 2 - THREAD - 16000 - - 26.174 + - 26.142 * - 2 - THREAD - 32000 - - 31.485 + - 31.421 * - 2 - THREAD - 48000 - - 38.765 \ No newline at end of file + - 38.670 \ No newline at end of file diff --git a/tests/signal/profile/mic_array_mips_xs3.json b/tests/signal/profile/mic_array_mips_xs3.json index 89917c26..f3183261 100644 --- a/tests/signal/profile/mic_array_mips_xs3.json +++ b/tests/signal/profile/mic_array_mips_xs3.json @@ -1,14 +1,14 @@ { - "1mic_isr_16000fs": 13.9057, - "1mic_isr_32000fs": 16.9454, - "1mic_isr_48000fs": 20.9695, - "1mic_thread_16000fs": 12.4976, - "1mic_thread_32000fs": 15.4736, - "1mic_thread_48000fs": 19.4335, - "2mic_isr_16000fs": 28.9067, - "2mic_isr_32000fs": 34.2693, - "2mic_isr_48000fs": 41.6454, - "2mic_thread_16000fs": 26.1739, - "2mic_thread_32000fs": 31.4854, - "2mic_thread_48000fs": 38.7653 + "1mic_isr_16000fs": 13.8098, + "1mic_isr_32000fs": 16.8495, + "1mic_isr_48000fs": 20.8734, + "1mic_thread_16000fs": 12.5135, + "1mic_thread_32000fs": 15.4095, + "1mic_thread_48000fs": 19.2896, + "2mic_isr_16000fs": 28.6851, + "2mic_isr_32000fs": 34.0132, + "2mic_isr_48000fs": 41.3575, + "2mic_thread_16000fs": 26.1418, + "2mic_thread_32000fs": 31.4214, + "2mic_thread_48000fs": 38.6696 } \ No newline at end of file From 79dae8e5a35be33f2a740f7f8bdd3e63c39f1f7a Mon Sep 17 00:00:00 2001 From: Shuchita Khare Date: Mon, 23 Mar 2026 13:36:09 +0000 Subject: [PATCH 08/17] Document single stage decimator and 1-mic override features --- doc/rst/lib_mic_array.rst | 1 + doc/rst/src/custom_filters.rst | 93 ++++++++++++++----- doc/rst/src/decimator_stages.rst | 47 +++++++--- doc/rst/src/getting_started.rst | 7 +- doc/rst/src/mic_switching.rst | 46 +++++++++ doc/rst/src/overview.rst | 16 ++-- .../reference/c/mic_array_default_model.rst | 2 + doc/rst/src/sample_filters.rst | 8 +- doc/rst/src/software_structure.rst | 41 ++++---- lib_mic_array/api/mic_array/cpp/Decimator.hpp | 3 + lib_mic_array/api/mic_array/cpp/MicArray.hpp | 53 +++++++---- 11 files changed, 225 insertions(+), 92 deletions(-) create mode 100644 doc/rst/src/mic_switching.rst diff --git a/doc/rst/lib_mic_array.rst b/doc/rst/lib_mic_array.rst index 4d066a0d..846d0216 100644 --- a/doc/rst/lib_mic_array.rst +++ b/doc/rst/lib_mic_array.rst @@ -13,6 +13,7 @@ lib_mic_array: PDM microphone array library src/getting_started src/examples src/resource_usage + src/mic_switching src/software_structure src/decimator_stages src/custom_filters diff --git a/doc/rst/src/custom_filters.rst b/doc/rst/src/custom_filters.rst index f592a244..145f2577 100644 --- a/doc/rst/src/custom_filters.rst +++ b/doc/rst/src/custom_filters.rst @@ -4,14 +4,13 @@ Custom decimation filters ************************* -In the :cpp:class:`Decimator `, the tap count and decimation factor -for the first stage decimator are fixed to ``256`` and ``32`` respectively, as described in :ref:`decimator_stage_1`. +The :cpp:class:`Decimator ` supports 1-, 2-, or 3-stage decimation pipelines configured via custom filters. +This flexibility allows applications to tailor the filter chain to their specific latency and computational requirements. -These parameters cannot be changed without implementing a custom decimator, which is outside the scope of this document. +Custom filters must comply with implementation requirements for each stage. See :ref:`stage_constraints_custom` +for details on stage-specific constraints. -However, both the first-stage and second-stage filter coefficients may be -replaced, and the second-stage decimation factor and tap count may be freely -modified by running the mic array component with custom filters. This is described in the following sections. +This document explains how to design and deploy custom decimation filters for 1-, 2-, or 3-stage configurations. .. _designing_custom_filters: @@ -25,7 +24,7 @@ a guide, the script can be extended to generate custom filters tailored to the application's needs. Note that in :cpp:class:`Decimator `, -both the first and second stage filters are implemented +the filters are implemented using fixed-point arithmetic, which requires the coefficients to be presented in a specific format. The helper scripts ``python/stage1.py`` and ``python/stage2.py`` generate @@ -70,22 +69,7 @@ Using custom filters When using the :cpp:class:`Decimator ` provided by the library, the :c:func:`mic_array_init_custom_filter` function is used to -initialize a mic array instance with a custom 2-stage decimation filter. - -.. note:: - - The custom filter provided to :c:func:`mic_array_init_custom_filter` must - be compatible with the :cpp:class:`Decimator - ` requirements. Specifically, it must be a - 2-stage filter. The tap count and decimation factor for the first-stage - decimator are fixed at ``256`` and ``32``, respectively, and the filter must - be compatible with the :ref:`stage_1_filter_impl`. - - The second-stage decimation filter tap count and decimation ratio are flexible, - provided it is a standard FIR filter compatible with :ref:`stage_2_filter_impl`. - Using custom filters that are incompatible with the implementation in - :cpp:class:`Decimator ` is outside the - scope of this documentation. +initialize a mic array instance with a custom decimation filter. The :c:type:`mic_array_conf_t` structure is populated with the decimator and PDM RX configurations before calling @@ -178,3 +162,66 @@ and started by calling :c:func:`mic_array_start`: as including the mic array in an application, declaring resources, and overriding build-time default configuration, are exactly the same as in the default usage model described in :ref:`using_mic_array`. + +.. _stage_constraints_custom: + +Filter stage constraints +======================== + +**Stage 1 (mandatory)** + +The first stage decimator has fixed constraints that cannot be changed: + +- Tap count: ``256`` (fixed) +- Decimation factor: ``32`` (fixed) +- Implementation: Must be compatible with :c:func:`fir_1x16_bit` as described in :ref:`stage_1_filter_impl` + +Only the filter coefficients may be customized. The coefficients must be quantized to 16-bit precision +and formatted appropriately for the VPU implementation. Use the Python helper script ``python/stage1.py`` +to convert floating-point coefficients to the required format. + +**Stage 2 (optional)** + +If a second stage is included, it must meet these requirements: + +- Implementation: Must be compatible with the 32-bit FIR filter from `lib_xcore_math `_, + specifically :c:func:`xs3_filter_fir_s32()` as described in :ref:`stage_2_filter_impl` +- Tap count: Configurable (no fixed constraint) +- Decimation factor: Configurable integer value + +Use the Python helper script ``python/stage2.py`` to convert floating-point coefficients to the required format. + +**Stage 3 (optional)** + +A third stage, if included, must also be compatible with the 32-bit FIR filter from `lib_xcore_math `_. +It has the same flexibility as stage 2: + +- Tap count: Configurable +- Decimation factor: Configurable integer value + +**Single-stage decimator configuration** + +Single-stage decimation is a special case in which additional decimation is +expected to be performed in the application. This is useful when downstream +decimation requirements are not directly represented by the integer-factor FIR +stages used by :cpp:class:`Decimator ` (for example, +rational-factor resampling). + +When only stage 1 is used, the decimation factor is fixed at ``32``. If a +different final output sample rate is required, the application must perform +the remaining decimation after receiving the stage-1 output from the mic array. + +Because further decimation is expected downstream, single-stage operation has +the following additional constraints: + +- The DCOE filter is forcibly disabled, and + :c:macro:`MIC_ARRAY_CONFIG_USE_DC_ELIMINATION` is ignored. If required, + the application must apply DC elimination after all decimation is complete. +- The number of output words per channel from PDM RX + (:c:member:`pdm_rx_conf_t.pdm_out_words_per_channel`) must match + :c:macro:`MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME`. +- :c:member:`pdm_rx_conf_t.pdm_out_words_per_channel` must not exceed + :cpp:member:`mic_array::MicArray::MAX_PDM_OUT_WORDS_PER_CHANNEL`. + +These two conditions are checked at runtime. If either condition is violated, +the mic array initialization asserts. diff --git a/doc/rst/src/decimator_stages.rst b/doc/rst/src/decimator_stages.rst index a93abb0e..75a83d54 100644 --- a/doc/rst/src/decimator_stages.rst +++ b/doc/rst/src/decimator_stages.rst @@ -4,10 +4,14 @@ Decimation filters ****************** -The mic array unit provided by this library uses a two-stage decimation process, -implemented in :cpp:class:`Decimator `, +The mic array unit provided by this library supports 1-, 2-, or 3-stage +decimation, implemented in :cpp:class:`Decimator `, to convert a high sample rate stream of (1-bit) PDM samples into a lower sample -rate stream of (32-bit) PCM samples. This is shown in :ref:`decimator_stages_simplified`. +rate stream of (32-bit) PCM samples. + +The default and most widely used configuration is a two-stage decimation +pipeline, and that two-stage case is the focus of this page. This is shown in +:ref:`decimator_stages_simplified`. .. _decimator_stages_simplified: @@ -17,23 +21,24 @@ rate stream of (32-bit) PCM samples. This is shown in :ref:`decimator_stages_sim Simplified Decimator Model -The first stage filter is a decimating FIR filter with a fixed tap count -(``S1_TAP_COUNT``) of ``256`` and a fixed decimation factor (``S1_DEC_FACTOR``) -of ``32``. +In a two-stage pipeline, the first stage filter is a decimating FIR +filter with a fixed tap count (``S1_TAP_COUNT``) of ``256`` and a fixed +decimation factor (``S1_DEC_FACTOR``) of ``32``. -The second stage decimator is a fully configurable FIR filter with tap count -``S2_TAP_COUNT`` and a decimation factor of ``S2_DEC_FACTOR`` (this can be -``1``). +The second stage decimator is a fully +configurable FIR filter with tap count ``S2_TAP_COUNT`` and a decimation factor +of ``S2_DEC_FACTOR`` (this can be ``1``). .. _default_filters: Filters provided as part of ``lib_mic_array`` ============================================= -``lib_mic_array`` provides first and second stage decimation filter coefficients for filters -targeting output sampling rates of 16 kHz, 32 kHz and 48 kHz from a starting input PDM frequency -of 3.072 MHz. The first stage decimation filters have a fixed decimation factor of ``32`` and a -fixed tap count of ``256``. +``lib_mic_array`` provides pre-designed two-stage decimation filter +coefficients targeting output sampling rates of 16 kHz, 32 kHz and 48 kHz from +a starting input PDM frequency of 3.072 MHz. The first stage decimation +filters have a fixed decimation factor of ``32`` and a fixed tap count of +``256``. The second stage filters decimation factors vary based on the output sampling rate. @@ -216,6 +221,14 @@ rearranged bit-by-bit into a block form suitable for VPU processing. The filter state (delay line) consists of 256 one-bit PDM samples (equal to the number of filter taps) and requires a buffer of 8 unsigned 32-bit words for storage. +.. note:: + + If providing stage-1 custom coefficients, they must remain compatible with the underlying + :c:func:`fir_1x16_bit` implementation (tap count ``256``, decimation factor + ``32``, and expected coefficient/state format for that kernel). + For details on preparing and supplying compatible custom coefficients, + see :ref:`custom_filters`. + Filter Conversion Script ------------------------ @@ -254,3 +267,11 @@ The second stage filter uses the 32-bit FIR filter implementation from The filter state (delay line) consists of as many 32-bit samples as there are taps in the stage-2 filter, and requires that many 32-bit words for storage. + +.. note:: + + If providing stage-2 custom coefficients, they must remain compatible with the underlying + 32-bit FIR implementation from + `lib_xcore_math `_ + (for example, tap/shift/state configuration must match the ``xs3_filter_fir_s32()`` function requirements). + For details on supplying custom coefficients via configuration structures, see :ref:`custom_filters`. diff --git a/doc/rst/src/getting_started.rst b/doc/rst/src/getting_started.rst index e4adc594..608d8a54 100644 --- a/doc/rst/src/getting_started.rst +++ b/doc/rst/src/getting_started.rst @@ -218,7 +218,7 @@ thread to receive PCM blocks from the mic array for further processing. Shutdown -------- -The application can to shut down the mic array task by calling :c:func:`ma_shutdown()`. +The application can shut down the mic array task by calling :c:func:`ma_shutdown()`. The shutdown request is sent over the same channel end that is used by :c:func:`ma_frame_rx()`. Therefore, the application must ensure that :c:func:`ma_frame_rx()` is not being called concurrently when invoking :c:func:`ma_shutdown()`. @@ -237,6 +237,11 @@ again to restart the mic array. is running; instead, call :c:func:`ma_shutdown()`, reconfigure the desired rate, and then restart the mic array. +.. note:: + + The same shutdown-restart cycle is used to switch between single-mic and + multi-mic operation at runtime. See :ref:`mic_switching` for details. + .. _mic_array_default_use_example: Example code diff --git a/doc/rst/src/mic_switching.rst b/doc/rst/src/mic_switching.rst new file mode 100644 index 00000000..3cb36c54 --- /dev/null +++ b/doc/rst/src/mic_switching.rst @@ -0,0 +1,46 @@ +.. _mic_switching: + +******************** +Single-mic override +******************** + +The number of microphone input and output channels is ordinarily fixed at compile time +via :c:macro:`MIC_ARRAY_CONFIG_MIC_COUNT` and :c:macro:`MIC_ARRAY_CONFIG_MIC_IN_COUNT`. + +In some cases, however, an application may need to dynamically switch between operating +with a single microphone and its normal multi-microphone configuration (as defined by +its compile time configuration). This can be useful, for example, to reduce processing load during idle periods. + +The :c:func:`mic_array_enable_1mic_override` function facilitates this. When called +before :c:func:`mic_array_init` (or :c:func:`mic_array_init_custom_filter`), it causes +the mic array to behave as if both :c:macro:`MIC_ARRAY_CONFIG_MIC_COUNT` and +:c:macro:`MIC_ARRAY_CONFIG_MIC_IN_COUNT` were set to ``1``, regardless of their +configured values. In this mode, only the first microphone channel is processed and emitted. + +.. note:: + + :c:func:`mic_array_enable_1mic_override` is intended for use with a 1-bit PDM + data port configuration. It is not applicable for configurations where the PDM + data port width is greater than 1. + +Switching from multi-mic to single-mic mode +=========================================== + +To switch to single-mic mode: + +* Call :c:func:`ma_shutdown` to terminate the running mic array. +* Call :c:func:`mic_array_enable_1mic_override` to activate the override. +* Call :c:func:`mic_array_init` (or :c:func:`mic_array_init_custom_filter`) and :c:func:`mic_array_start` to restart in single-mic mode. + +.. note:: + + The output frame size received by :c:func:`ma_frame_rx` changes when switching + modes, because the number of channels changes. The receiving thread must be updated + accordingly. + +Switching back to multi-mic mode +================================= + +The override is automatically cleared by :c:func:`ma_shutdown`. To return to +multi-mic operation, simply shut down and restart **without** calling +:c:func:`mic_array_enable_1mic_override`: diff --git a/doc/rst/src/overview.rst b/doc/rst/src/overview.rst index cc2a6a90..536013cf 100644 --- a/doc/rst/src/overview.rst +++ b/doc/rst/src/overview.rst @@ -20,12 +20,12 @@ Capabilities are supported * Configurable clock divider allows user-selectable PDM sample clock frequency (3.072 MHz typical) -* Configurable :ref:`two-stage decimating FIR filter ` +* Supports :ref:`1-, 2-, or 3-stage decimation FIR filters ` - * First stage has fixed tap count of 256 and decimation factor of 32 - * Second stage has fully configurable tap count and decimation factor - * Custom filter coefficients can be used for either stage - * Pre-designed reference filters with total decimation factor of 192, 96 and 64 are provided + * First stage is compulsary and has a fixed tap count of 256 and decimation factor of 32 + * Further stages are optional and hav fully configurable tap count and decimation factor + * Custom filter coefficients can be used for any stage + * Pre-designed two-stage reference filters with total decimation factor of 192, 96 and 64 are provided (16 kHz, 32 kHz and 48 kHz output sample rates with 3.072 MHz input PDM clock). * Filter generation scripts and examples are included to support custom filter design. @@ -103,9 +103,9 @@ Step 2: First stage decimation ------------------------------ The conversion from the high-sample-rate PDM stream to lower-sample-rate PCM -stream involves two stages of decimating filters. After the decimation thread -receives a block of PDM samples, the samples are filtered by the first stage -decimator. +stream involves one or more stages of decimating FIR filters. After the +decimation thread receives a block of PDM samples, the samples are filtered by +the first stage decimator. The first stage decimator has a fixed decimation factor of ``32`` and a fixed tap count of ``256``. An application is free to supply its own filter diff --git a/doc/rst/src/reference/c/mic_array_default_model.rst b/doc/rst/src/reference/c/mic_array_default_model.rst index c4531271..adf4c48a 100644 --- a/doc/rst/src/reference/c/mic_array_default_model.rst +++ b/doc/rst/src/reference/c/mic_array_default_model.rst @@ -32,3 +32,5 @@ needs to call to initialise and start a mic array instance when using the defaul .. doxygenfunction:: mic_array_start .. doxygenfunction:: mic_array_init_custom_filter + +.. doxygenfunction:: mic_array_enable_1mic_override diff --git a/doc/rst/src/sample_filters.rst b/doc/rst/src/sample_filters.rst index 896df3ec..fc424c24 100644 --- a/doc/rst/src/sample_filters.rst +++ b/doc/rst/src/sample_filters.rst @@ -4,10 +4,10 @@ Sample filters ************** -Following the two-stage decimation procedure is an optional post-processing -stage called the sample filter. This stage operates on each sample emitted by -the second stage decimator, one at a time, before the samples are handed off for -framing or transfer to the rest of the application's audio pipeline. +Following the decimation pipeline is an optional post-processing stage called +the sample filter. This stage operates on each sample emitted by the final +decimation stage, one at a time, before the samples are handed off for framing +or transfer to the rest of the application's audio pipeline. .. note:: diff --git a/doc/rst/src/software_structure.rst b/doc/rst/src/software_structure.rst index 55438b3c..c3777430 100644 --- a/doc/rst/src/software_structure.rst +++ b/doc/rst/src/software_structure.rst @@ -9,9 +9,8 @@ mic array unit and its sub-components. The template parameters of these class templates are (mainly) used for two different purposes. Non-type template parameters are used to specify certain -quantitative configuration values, such as the number of microphone channels or -the second stage decimator tap count. Type template parameters, on the other -hand, are used for configuring the behaviour of sub-components. +quantitative configuration values, such as the number of microphone channels. +Type template parameters, on the other hand, are used for configuring the behaviour of sub-components. High level view =============== @@ -59,7 +58,7 @@ A ``MicArray`` object comprises 4 sub-components: - Capturing PDM data from a port * - :cpp:member:`Decimator ` - ``TDecimator`` - - 2-stage decimation on blocks of PDM data + - 1-, 2-, or 3-stage decimation on blocks of PDM data * - :cpp:member:`SampleFilter ` - ``TSampleFilter`` - Post-processing of decimated samples @@ -105,26 +104,22 @@ Aside from aggregating its sub-components into a single logical entity, the ``MicArray`` class template also holds the high-level logic for capturing, processing and coordinating movement of the audio stream data. -The following code snippet is the implementation for the main mic array thread -(or "decimation thread"; not to be confused with (optional) PDM capture thread). +At the top level, ``MicArray::ThreadEntry()`` dispatches to a stage-specific +thread entry function based on the configured number of decimator stages: -.. code-block:: c++ - - int32_t sample_out[MIC_COUNT] = {0}; - volatile bool shutdown = false; +.. literalinclude:: ../../../lib_mic_array/api/mic_array/cpp/MicArray.hpp + :start-after: // MicArray::ThreadEntry() + :end-at: // ThreadEntry - while(!shutdown){ - uint32_t *pdm_samples = PdmRx.GetPdmBlock(); - Decimator.ProcessBlock(sample_out, pdm_samples); - SampleFilter.Filter(sample_out); - shutdown = OutputHandler.OutputSample(sample_out); - } - PdmRx.Shutdown(); - OutputHandler.CompleteShutdown(); - } +The two-stage path is the most common configuration. Its thread loop is shown +below: +.. literalinclude:: ../../../lib_mic_array/api/mic_array/cpp/MicArray.hpp + :start-after: // MicArray::ThreadEntryTwoStage() + :end-at: // ThreadEntryTwoStage -The thread loops till ``OutputHandler.OutputSample()`` indicates a shutdown request and on each iteration, +The thread loops until ``OutputHandler.OutputSample()`` +indicates a shutdown request. On each iteration, it: * Requests a block of PDM sample data from the PDM rx service. This is a blocking call which only returns once a complete block becomes @@ -190,11 +185,11 @@ Decimator The :cpp:member:`Decimator ` sub-component encapsulates the logic of converting blocks of PDM samples into PCM samples. The :cpp:class:`Decimator ` class is a -decimator implementation that uses a pair of decimating FIR filters to -accomplish this. +decimator implementation that supports 1-, 2-, or 3-stage cascaded FIR +decimation. The first stage has a fixed tap count of ``256`` and a fixed decimation factor -of ``32``. The second stage has a configurable tap count and decimation factor. +of ``32``. Additional stages have configurable tap counts and decimation factors. For more details, see :ref:`decimator_stages`. diff --git a/lib_mic_array/api/mic_array/cpp/Decimator.hpp b/lib_mic_array/api/mic_array/cpp/Decimator.hpp index 3a25431b..7e785113 100644 --- a/lib_mic_array/api/mic_array/cpp/Decimator.hpp +++ b/lib_mic_array/api/mic_array/cpp/Decimator.hpp @@ -65,6 +65,9 @@ class Decimator */ unsigned pdm_history_sz; + /** + * Per-mic, 32-bit PDM output words from the PDM RX stage. + */ unsigned pdm_out_words_per_mic; } stage1; diff --git a/lib_mic_array/api/mic_array/cpp/MicArray.hpp b/lib_mic_array/api/mic_array/cpp/MicArray.hpp index ff91730f..d722853f 100644 --- a/lib_mic_array/api/mic_array/cpp/MicArray.hpp +++ b/lib_mic_array/api/mic_array/cpp/MicArray.hpp @@ -83,19 +83,31 @@ namespace mic_array { * @brief The Decimator. * * The template parameter `TDecimator` is the concrete class implementing - * the microphone array's decimation procedure. `TDecimator` is only - * required to implement one function, `ProcessBlock()`: + * the microphone array's decimation procedure. Depending on the number of + * configured stages, `MicArray` will call one of three stage-specific + * processing functions: + * * @code{.cpp} - * void ProcessBlock( + * // 1-stage: produces pdm_out_words_per_mic PCM samples per mic per call + * void ProcessBlockSingleStage( + * int32_t *sample_out, + * uint32_t *pdm_block); + * + * // 2-stage: produces one PCM sample per mic per call + * void ProcessBlockTwoStage( + * int32_t sample_out[MIC_COUNT], + * uint32_t *pdm_block); + * + * // 3-stage: produces one PCM sample per mic per call + * void ProcessBlockThreeStage( * int32_t sample_out[MIC_COUNT], * uint32_t *pdm_block); * @endcode * - * `ProcessBlock()` takes a block of PDM samples via its `pdm_block` - * parameter, applies the appropriate decimation logic, and outputs a - * single (multi-channel) sample via its `sample_out` parameter. - * The size and formatting of the PDM block expected by the decimator - * depends on its particular implementation. + * Each function takes a block of PDM samples via `pdm_block`, applies the + * appropriate decimation logic, and writes output samples to `sample_out`. + * The active stage count is determined at initialisation and controls which + * function is dispatched by @ref ThreadEntry. * */ TDecimator Decimator; @@ -184,16 +196,15 @@ namespace mic_array { void ThreadEntry(); /** - * @brief Entry point for the low-power single-stage decimation thread. + * @brief Maximum supported value for PDM RX output words per channel. Only relevant for single-stage decimator mode. * - * This function loops, collecting PDM - * blocks from @ref PdmRx and running the single-stage decimator. Each - * block produces two output samples which are delivered sequentially - * through @ref OutputHandler. On shutdown it calls @ref PdmRx::Shutdown() - * and then completes the output shutdown handshake. + * In single-stage decimator mode, this limits + * `pdm_rx_conf_t::pdm_out_words_per_channel`. + * + * The limit is used to size the local output buffer in + * `ThreadEntryOneStage()`, and the mic array initialization path asserts + * if the configured value exceeds this bound. */ - void ThreadEntryLowPower_1Mic1StgDecimator(); - static constexpr unsigned MAX_PDM_OUT_WORDS_PER_CHANNEL = 10; }; @@ -227,8 +238,9 @@ void mic_array::MicArray Date: Tue, 24 Mar 2026 09:22:55 +0000 Subject: [PATCH 09/17] Add 1-mic override example, app_1mic_override --- doc/rst/src/examples.rst | 28 ++++ doc/rst/src/mic_switching.rst | 4 +- examples/app_1mic_override/CMakeLists.txt | 28 ++++ .../app_1mic_override/src/XK-VOICE-L71.xn | 75 +++++++++ examples/app_1mic_override/src/app.cpp | 84 ++++++++++ examples/app_1mic_override/src/app.h | 22 +++ examples/app_1mic_override/src/app_config.h | 13 ++ examples/app_1mic_override/src/app_i2s.c | 156 ++++++++++++++++++ examples/app_1mic_override/src/config.xscope | 3 + examples/app_1mic_override/src/main.xc | 53 ++++++ 10 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 examples/app_1mic_override/CMakeLists.txt create mode 100644 examples/app_1mic_override/src/XK-VOICE-L71.xn create mode 100644 examples/app_1mic_override/src/app.cpp create mode 100644 examples/app_1mic_override/src/app.h create mode 100644 examples/app_1mic_override/src/app_config.h create mode 100644 examples/app_1mic_override/src/app_i2s.c create mode 100644 examples/app_1mic_override/src/config.xscope create mode 100644 examples/app_1mic_override/src/main.xc diff --git a/doc/rst/src/examples.rst b/doc/rst/src/examples.rst index e5b81019..0d39c0df 100644 --- a/doc/rst/src/examples.rst +++ b/doc/rst/src/examples.rst @@ -158,3 +158,31 @@ The terminal stdout displays the current sampling rate on each button press: Starting at sample rate 16000 Starting at sample rate 48000 Starting at sample rate 16000 + + +.. _mic_array_app_1mic_override: + +``app_1mic_override`` +^^^^^^^^^^^^^^^^^^^^^ + +The ``app_1mic_override`` example demonstrates the :c:func:`mic_array_enable_1mic_override` API. +Each time ``Button A`` on the ``XK-VOICE-L71`` is pressed, the mic array is +shut down and restarted while toggling the 1-mic override. + +This alternates between: + +- single-mic operation (override enabled), and +- normal operation using ``MIC_ARRAY_CONFIG_MIC_COUNT`` microphones + (``2`` in this example). + +This behaviour can be verified by listening to the DAC output of the +``XK-VOICE-L71`` board. + +The terminal stdout displays the mic-override value on each button press: + +.. code-block:: console + + Starting at sample rate 16000, 1mic override 1 + Starting at sample rate 16000, 1mic override 0 + Starting at sample rate 16000, 1mic override 1 + Starting at sample rate 16000, 1mic override 0 \ No newline at end of file diff --git a/doc/rst/src/mic_switching.rst b/doc/rst/src/mic_switching.rst index 3cb36c54..f213c254 100644 --- a/doc/rst/src/mic_switching.rst +++ b/doc/rst/src/mic_switching.rst @@ -43,4 +43,6 @@ Switching back to multi-mic mode The override is automatically cleared by :c:func:`ma_shutdown`. To return to multi-mic operation, simply shut down and restart **without** calling -:c:func:`mic_array_enable_1mic_override`: +:c:func:`mic_array_enable_1mic_override`. + +An example of the :c:func:`mic_array_enable_1mic_override` API can be found in :ref:`mic_array_app_1mic_override` diff --git a/examples/app_1mic_override/CMakeLists.txt b/examples/app_1mic_override/CMakeLists.txt new file mode 100644 index 00000000..b1242d89 --- /dev/null +++ b/examples/app_1mic_override/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) +project(app_1mic_override) + +set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) + +include(${CMAKE_CURRENT_LIST_DIR}/../deps.cmake) +set(APP_HW_TARGET XK-VOICE-L71.xn) + +set(NAME_MAP thread;isr) +foreach(USE_ISR 1 0) + list(GET NAME_MAP ${USE_ISR} tmp) + set(CONFIG "${N_MICS}mic_${tmp}") + + set(APP_COMPILER_FLAGS_${CONFIG} -Os + -g + -report + -mcmodel=large + -DBOARD_SUPPORT_BOARD=XK_VOICE_L71 + -DAPP_NAME="MA_1MIC_OVERRIDE_${CONFIG}" + -DMIC_ARRAY_CONFIG_USE_PDM_ISR=${USE_ISR} + -DMIC_ARRAY_CONFIG_MIC_COUNT=2 + ) +endforeach() + +set(APP_INCLUDES src) + +XMOS_REGISTER_APP() diff --git a/examples/app_1mic_override/src/XK-VOICE-L71.xn b/examples/app_1mic_override/src/XK-VOICE-L71.xn new file mode 100644 index 00000000..28447f96 --- /dev/null +++ b/examples/app_1mic_override/src/XK-VOICE-L71.xn @@ -0,0 +1,75 @@ + + + Device + XU316-1024-QF60A-C24 Device + + + tileref tile[2] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/app_1mic_override/src/app.cpp b/examples/app_1mic_override/src/app.cpp new file mode 100644 index 00000000..76bfad4b --- /dev/null +++ b/examples/app_1mic_override/src/app.cpp @@ -0,0 +1,84 @@ +// Copyright 2025-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include + +#include +#include +#include +#include "app.h" +#include "app_config.h" +#include +#include + +pdm_rx_resources_t pdm_res_ddr = PDM_RX_RESOURCES_DDR( + PORT_MCLK_IN, + PORT_PDM_CLK, + PORT_PDM_DATA, + MCLK_48, + PDM_FREQ, + XS1_CLKBLK_1, + XS1_CLKBLK_2); + +pdm_rx_resources_t pdm_res_sdr = PDM_RX_RESOURCES_SDR( + PORT_MCLK_IN, + PORT_PDM_CLK, + PORT_PDM_DATA, + MCLK_48, + PDM_FREQ, + XS1_CLKBLK_1); + +void app_mic(chanend_t c_frames_out) +{ + while (1) + { + int fs = chan_in_word(c_frames_out); + unsigned override = chan_in_word(c_frames_out); + if (override) { + mic_array_enable_1mic_override(); + mic_array_init(&pdm_res_sdr, NULL, fs); + } + else { + mic_array_init(&pdm_res_ddr, NULL, fs); + } + + mic_array_start(c_frames_out); + } +} + +void button_task(chanend_t c_sync) +{ + port_t my_port = PORT_GPI; + port_enable(my_port); + bool init = true; + unsigned current_state, prev_state; + unsigned debounce_delay_ms = 50; + unsigned debounce_delay_ticks = debounce_delay_ms * XS1_TIMER_KHZ; + + hwtimer_t tmr = hwtimer_alloc(); + while (1) + { + current_state = ((port_in(my_port) & (1 << 5))) >> 5; // get the 'button' bit from the port read value + if (init) + { + prev_state = current_state; + init = false; + } + else + { + if (prev_state != current_state) + { + if ((prev_state == 1) && (current_state == 0)) + { + // button pressed + chan_out_word(c_sync, 1); + } + hwtimer_delay(tmr, debounce_delay_ticks); + } + prev_state = current_state; + } + } + hwtimer_free(tmr); +} diff --git a/examples/app_1mic_override/src/app.h b/examples/app_1mic_override/src/app.h new file mode 100644 index 00000000..c531e683 --- /dev/null +++ b/examples/app_1mic_override/src/app.h @@ -0,0 +1,22 @@ +// 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" + +/** + * @brief + */ +C_API_START + +MA_C_API +void app_i2s_task( chanend_t c_from_mic_array, chanend_t c_sync, chanend_t c_i2c ); + +MA_C_API +void app_mic(chanend_t c_frames_out); + +MA_C_API +void button_task(chanend_t c_sync); + +C_API_END diff --git a/examples/app_1mic_override/src/app_config.h b/examples/app_1mic_override/src/app_config.h new file mode 100644 index 00000000..5ed9d89b --- /dev/null +++ b/examples/app_1mic_override/src/app_config.h @@ -0,0 +1,13 @@ +// Copyright 2022-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + + +#define MCLK_48 (512 * 48000) /* 48kHz family master clock frequency */ + +#define MASTER_CLOCK_FREQUENCY MCLK_48 +#define PDM_FREQ (3072000) +#define DATA_BITS 32 +#define CHANS_PER_FRAME I2S_CHANS_PER_FRAME +#define NUM_I2S_LINES 1 diff --git a/examples/app_1mic_override/src/app_i2s.c b/examples/app_1mic_override/src/app_i2s.c new file mode 100644 index 00000000..3d287204 --- /dev/null +++ b/examples/app_1mic_override/src/app_i2s.c @@ -0,0 +1,156 @@ +// 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 "app.h" +#include "i2s.h" +#include "app_config.h" +#include "xk_voice_l71/board.h" + +#define I2S_CLKBLK XS1_CLKBLK_3 + +typedef struct { + chanend_t c_from_mic_array; + chanend_t c_button_sync; + chanend_t c_i2c; + unsigned override; +}ma_interface_t; + +ma_interface_t ma_interface; + +static int i2s_mclk_bclk_ratio( + const unsigned audio_clock_frequency, + const unsigned sample_rate) +{ + return audio_clock_frequency / (sample_rate * (8 * sizeof(int32_t)) * I2S_CHANS_PER_FRAME); +} + + + +I2S_CALLBACK_ATTR +void app_i2s_send(ma_interface_t* app_data, + size_t num_out, + int32_t* samples) +{ + int32_t frame[MIC_ARRAY_CONFIG_MIC_COUNT] = {0}; + if(app_data->override) + { + ma_frame_rx(frame, app_data->c_from_mic_array, 1, 1); + } + else + { + ma_frame_rx(frame, app_data->c_from_mic_array, MIC_ARRAY_CONFIG_MIC_COUNT, 1); + } + + for(int c = 0; c < num_out; c++) { + int32_t samp = frame[c] << 6; + samples[c] = samp; + } +} + + + +I2S_CALLBACK_ATTR +static +void app_i2s_init(ma_interface_t* app_data, + i2s_config_t* config) +{ + static unsigned sample_rate = 16000; // dac3101_configure() in lib_board_support, xk_voice_l71.xc only allows these 2 + app_data->override = app_data->override ^ 1; + printf("Starting at sample rate %u, 1mic override %u\n", sample_rate, app_data->override); + static const xk_voice_l71_config_t hw_config = { + CLK_FIXED, + ENABLE_MCLK | ENABLE_I2S, + DAC_DIN_SEC, + MCLK_48 + }; + // Initialise dac for the required sampling freq + xk_voice_l71_AudioHwChanInit(app_data->c_i2c); + xk_voice_l71_AudioHwInit(&hw_config); + xk_voice_l71_AudioHwConfig(&hw_config, sample_rate, MCLK_48); + + chan_out_word(app_data->c_from_mic_array, sample_rate); // convey samp freq to app_mic() + chan_out_word(app_data->c_from_mic_array, app_data->override); + + config->mode = I2S_MODE_I2S; + config->mclk_bclk_ratio = i2s_mclk_bclk_ratio(MCLK_48, + sample_rate); +} + + + + +I2S_CALLBACK_ATTR +static +i2s_restart_t app_i2s_restart(ma_interface_t* app_data) +{ + unsigned temp; + SELECT_RES(CASE_THEN(app_data->c_button_sync, fs_change), + DEFAULT_THEN(empty)) + { + fs_change: + temp = chan_in_word(app_data->c_button_sync); + (void)temp; + ma_shutdown(app_data->c_from_mic_array); + return 1; + break; + empty: + break; + } + return 0; +} + + + + + +I2S_CALLBACK_ATTR +static +void app_i2s_receive(void* app_data, + size_t num_in, + const int32_t* samples) +{ + +} + + + +i2s_callback_group_t i2s_context = { + .init = (i2s_init_t) app_i2s_init, + .restart_check = (i2s_restart_check_t) app_i2s_restart, + .receive = (i2s_receive_t) app_i2s_receive, + .send = (i2s_send_t) app_i2s_send, + .app_data = NULL, +}; + + + +void app_i2s_task( chanend_t c_from_mic_array, chanend_t c_button_sync, chanend_t c_i2c) +{ + ma_interface.c_from_mic_array = c_from_mic_array; + ma_interface.c_button_sync = c_button_sync; + ma_interface.c_i2c = c_i2c; + ma_interface.override = 0; + i2s_context.app_data = &ma_interface; + + port_t p_i2s_dout[] = { PORT_I2S_DAC0 }; + port_t p_i2s_din[0]; + + i2s_master(&i2s_context, + p_i2s_dout, 1, + p_i2s_din, 0, + PORT_I2S_BCLK, + PORT_I2S_LRCLK, + PORT_MCLK_IN, + I2S_CLKBLK); +} diff --git a/examples/app_1mic_override/src/config.xscope b/examples/app_1mic_override/src/config.xscope new file mode 100644 index 00000000..72069113 --- /dev/null +++ b/examples/app_1mic_override/src/config.xscope @@ -0,0 +1,3 @@ + + + diff --git a/examples/app_1mic_override/src/main.xc b/examples/app_1mic_override/src/main.xc new file mode 100644 index 00000000..b030aab6 --- /dev/null +++ b/examples/app_1mic_override/src/main.xc @@ -0,0 +1,53 @@ +// 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 "xk_voice_l71/board.h" +#include "app_config.h" +#include "mic_array.h" +#include "app.h" + + + +// mic array resources +on tile[PORT_PDM_CLK_TILE_NUM]: in port p_mclk = PORT_MCLK_IN; +on tile[PORT_PDM_CLK_TILE_NUM] : port p_pdm_clk = PORT_PDM_CLK; +on tile[PORT_PDM_CLK_TILE_NUM] : port p_pdm_data = PORT_PDM_DATA; +on tile[PORT_PDM_CLK_TILE_NUM] : clock clk_a = XS1_CLKBLK_1; +on tile[PORT_PDM_CLK_TILE_NUM] : clock clk_b = XS1_CLKBLK_2; + +unsafe{ + +int main() { + chan c_audio_frames; + chan c_i2c; + chan c_button_sync; + + par { + + on tile[0]: { + par { + xk_voice_l71_AudioHwRemote(c_i2c); // Startup remote I2C master server task + button_task((chanend_t)c_button_sync); + } + } + + + on tile[1]: { + par { + app_mic((chanend_t) c_audio_frames); + + app_i2s_task( (chanend_t)c_audio_frames, (chanend_t)c_button_sync, (chanend_t)c_i2c ); + } + } + } + + return 0; +} +} From 61a61c1b73738c1364b934be896badef2bf9639b Mon Sep 17 00:00:00 2001 From: shuchitak <38428600+shuchitak@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:47:52 +0000 Subject: [PATCH 10/17] Address review comments Co-authored-by: xalbertoisorna <124703429+xalbertoisorna@users.noreply.github.com> --- Jenkinsfile | 3 --- doc/rst/src/examples.rst | 10 +++------ examples/app_1mic_override/CMakeLists.txt | 22 ++++++------------- lib_mic_array/api/mic_array/cpp/PdmRx.hpp | 2 +- lib_mic_array/src/mic_array_task_internal.hpp | 2 +- 5 files changed, 12 insertions(+), 27 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c80817e0..143e78c0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -243,9 +243,6 @@ pipeline { stage('Run tests') { steps { dir("${REPO_NAME}/tests") { - withTools(params.TOOLS_VX4_VERSION) { - sh "xflash --erase-all --target XK-EVK-XU416" - } withVenv { dir("unit") { withTools(params.TOOLS_VX4_VERSION) {sh "xrun --xscope bin/tests-unit.xe"} diff --git a/doc/rst/src/examples.rst b/doc/rst/src/examples.rst index 0d39c0df..dab59996 100644 --- a/doc/rst/src/examples.rst +++ b/doc/rst/src/examples.rst @@ -166,14 +166,10 @@ The terminal stdout displays the current sampling rate on each button press: ^^^^^^^^^^^^^^^^^^^^^ The ``app_1mic_override`` example demonstrates the :c:func:`mic_array_enable_1mic_override` API. -Each time ``Button A`` on the ``XK-VOICE-L71`` is pressed, the mic array is -shut down and restarted while toggling the 1-mic override. -This alternates between: - -- single-mic operation (override enabled), and -- normal operation using ``MIC_ARRAY_CONFIG_MIC_COUNT`` microphones - (``2`` in this example). +Each time ``Button A`` on the ``XK-VOICE-L71`` is pressed, the mic array is shut down and alternates between: +- Single-mic operation (1mic_override enabled). +- Two-mic operation. (1mic_override disabled, uses ``MIC_ARRAY_CONFIG_MIC_COUNT`` microphones). This behaviour can be verified by listening to the DAC output of the ``XK-VOICE-L71`` board. diff --git a/examples/app_1mic_override/CMakeLists.txt b/examples/app_1mic_override/CMakeLists.txt index b1242d89..8a5e31a5 100644 --- a/examples/app_1mic_override/CMakeLists.txt +++ b/examples/app_1mic_override/CMakeLists.txt @@ -7,21 +7,13 @@ set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) include(${CMAKE_CURRENT_LIST_DIR}/../deps.cmake) set(APP_HW_TARGET XK-VOICE-L71.xn) -set(NAME_MAP thread;isr) -foreach(USE_ISR 1 0) - list(GET NAME_MAP ${USE_ISR} tmp) - set(CONFIG "${N_MICS}mic_${tmp}") - - set(APP_COMPILER_FLAGS_${CONFIG} -Os - -g - -report - -mcmodel=large - -DBOARD_SUPPORT_BOARD=XK_VOICE_L71 - -DAPP_NAME="MA_1MIC_OVERRIDE_${CONFIG}" - -DMIC_ARRAY_CONFIG_USE_PDM_ISR=${USE_ISR} - -DMIC_ARRAY_CONFIG_MIC_COUNT=2 - ) -endforeach() +set(APP_COMPILER_FLAGS -Os + -g + -report + -mcmodel=large + -DBOARD_SUPPORT_BOARD=XK_VOICE_L71 + -DMIC_ARRAY_CONFIG_USE_PDM_ISR=0 + -DMIC_ARRAY_CONFIG_MIC_COUNT=2) set(APP_INCLUDES src) diff --git a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp index 6d522c2d..8c77ab3e 100644 --- a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp +++ b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp @@ -582,7 +582,7 @@ uint32_t* mic_array::StandardPdmRxService uint32_t *out_ptr; for(int ch = 0; ch < CHANNELS_OUT; ch++) { out_ptr = this->pdm_out_block_ptr + (ch * this->pdm_out_words_per_channel); - for(int sb = 0; sb < (int)this->pdm_out_words_per_channel; sb++) { + for(unsigned sb = 0; sb < this->pdm_out_words_per_channel; sb++) { unsigned d = this->channel_map[ch]; out_ptr[sb] = block[this->pdm_out_words_per_channel - 1 - sb][d]; } diff --git a/lib_mic_array/src/mic_array_task_internal.hpp b/lib_mic_array/src/mic_array_task_internal.hpp index 551d7d31..7dc34bbf 100644 --- a/lib_mic_array/src/mic_array_task_internal.hpp +++ b/lib_mic_array/src/mic_array_task_internal.hpp @@ -85,7 +85,7 @@ inline uint32_t* get_pdm_rx_out_block_double_buf(unsigned stg2_dec_factor) { #endif // __cplusplus MA_C_API -void init_mic_array_storage(); +void init_mic_array_storage(void); MA_C_API void init_mics_custom_filter(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf); From 2e44763582c9285cfe3adf80e0038ec9218b7fbb Mon Sep 17 00:00:00 2001 From: Shuchita Khare Date: Wed, 25 Mar 2026 13:47:19 +0000 Subject: [PATCH 11/17] Add delay before xscope_ep_request_upload() to fix test failures on vx4 when run on linux agents https://github.com/xmos/lib_mic_array/issues/301 --- Jenkinsfile | 4 ++-- python/mic_array/device_context.py | 18 +++++++++--------- python/mic_array/xscope.py | 10 ++++++---- tests/signal/BasicMicArray/test_thdn.py | 2 +- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 143e78c0..de4ed6e4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -187,7 +187,7 @@ pipeline { if(params.TEST_LEVEL == 'smoke') { echo "Running tests with fixed seed 12345" - sh "pytest -v --junitxml=pytest_basic_mic.xml --seed 12345 --level ${params.TEST_LEVEL} -k 'not 16frame-8n'" + sh "pytest -v --junitxml=pytest_basic_mic.xml --seed 12345 --level ${params.TEST_LEVEL}" } else { @@ -248,7 +248,7 @@ pipeline { withTools(params.TOOLS_VX4_VERSION) {sh "xrun --xscope bin/tests-unit.xe"} } dir("signal/BasicMicArray") { - withTools(params.TOOLS_VX4_VERSION) {sh 'python -m pytest --level nightly --seed 12345 -k "(0isr or OneStageFilter) and not 16frame-8n" -v'} // Skipping 16frame-8n. See https://github.com/xmos/lib_mic_array/issues/288 + withTools(params.TOOLS_VX4_VERSION) {sh 'python -m pytest --level nightly --seed 12345 -k "0isr or OneStageFilter" -v'} } } // withVenv }}} // stage('Run tests') diff --git a/python/mic_array/device_context.py b/python/mic_array/device_context.py index 855b4541..f2105a1a 100644 --- a/python/mic_array/device_context.py +++ b/python/mic_array/device_context.py @@ -6,11 +6,11 @@ from time import sleep class DeviceContext(object): - + XRUN_CMD_BASE = ('xrun', '--xscope', '--xscope-port','localhost:10234') def __init__(self, xe_path, /, probes=[], extra_xrun_args="", **kwargs): - + self.xe_path = xe_path self.xrun_cmd = list(DeviceContext.XRUN_CMD_BASE) + extra_xrun_args.split() + [self.xe_path] @@ -21,17 +21,17 @@ def __init__(self, xe_path, /, probes=[], extra_xrun_args="", **kwargs): self.probe_timeout = ( 10.0 if "probe_timeout" not in kwargs else kwargs["probe_timeout"] ) - self.probes = { k: xscope.QueueConsumer(self.ep, k, + self.probes = { k: xscope.QueueConsumer(self.ep, k, probe_timeout=self.probe_timeout) for k in probes } - self.connect_retries = ( 5 if "connect_retries" not in kwargs + self.connect_retries = ( 5 if "connect_retries" not in kwargs else kwargs["connect_retries"] ) def _on_connect(self): pass # for subclasses to override def __enter__(self): - + self.xrun = subprocess.Popen(self.xrun_cmd, stdout=subprocess.DEVNULL) sleep(3) @@ -61,22 +61,22 @@ def __exit__(self, exc_type, exc_val, exc_tb): try : self.xrun.terminate() self.xrun = None - except: + except: pass def send_bytes(self, data): if len(data) == 0: self.ep.publish(data) return - + while (len(data) > 0): self.ep.publish(data[:128]) data = data[128:] - + def send_word(self, word): self.send_bytes(int(word).to_bytes(4,'little')) def probe_next(self, probe, count=1): return self.probes[probe].next(count) - + diff --git a/python/mic_array/xscope.py b/python/mic_array/xscope.py index d76fea15..4660b18f 100644 --- a/python/mic_array/xscope.py +++ b/python/mic_array/xscope.py @@ -5,6 +5,7 @@ from collections import defaultdict import ctypes.util import numpy as np +from time import sleep """ Function prototypes to match the c functions defined in xscope_endpoint.h @@ -159,7 +160,7 @@ def connect(self, hostname='localhost', port='10234'): 0 for success 1 for failure """ - return self.lib_xscope.xscope_ep_connect(hostname.encode(), + return self.lib_xscope.xscope_ep_connect(hostname.encode(), port.encode()) def disconnect(self): @@ -192,6 +193,7 @@ def publish(self, data): 0 for success 1 for failure """ + sleep(0.0001) # Add some delay to fix vx4 tests failing on linux agents. https://github.com/xmos/lib_mic_array/issues/301 return self.lib_xscope.xscope_ep_request_upload(ctypes.c_uint(len(data)+1), ctypes.c_char_p(data)) if __name__ == '__main__': @@ -241,14 +243,14 @@ def _consume(self, timestamp, probe_name, value): def next(self, count=1): # If the queue Empty exception is raised from here it's because the probes - # timed out when trying to get values from xscope. At least in some + # timed out when trying to get values from xscope. At least in some # scenarios (pytest on my machine), ctrl-c fails to interrupt the script and - # just hangs forever if it's blocking on queue.get(). The timeout is + # just hangs forever if it's blocking on queue.get(). The timeout is # currently serving as a watchdog so that the pytest process doesn't need # to be killed through extraordinary means. if count == 1: return self.queue.get(timeout=self.probe_timeout) - + r = [] for _ in range(count): r.append( self.queue.get(timeout=self.probe_timeout) ) diff --git a/tests/signal/BasicMicArray/test_thdn.py b/tests/signal/BasicMicArray/test_thdn.py index 1b4f64d2..9d5038b6 100644 --- a/tests/signal/BasicMicArray/test_thdn.py +++ b/tests/signal/BasicMicArray/test_thdn.py @@ -172,7 +172,7 @@ def test_thdn_OneStageFilter(self, pytestconfig, request, platform, decimator_st against per-tone THD+N thresholds. Also verifies sample-level diff between Python and xcore integer outputs within a fixed tolerance. """ - duration_s = 2 # running reduced duration. See https://github.com/xmos/lib_mic_array/issues/289 + duration_s = 4 pdm_freq = 768_000 chans = 2 freq_hz = [300, 5000] From 8b9b2b7a15e731a06089f8df51dc6bf7bc68664d Mon Sep 17 00:00:00 2001 From: xalbertoisorna Date: Fri, 22 May 2026 15:47:17 +0100 Subject: [PATCH 12/17] Add basic example for vx4 only --- examples/CMakeLists.txt | 1 - examples/app_mic_array_basic/CMakeLists.txt | 22 +------ examples/app_mic_array_basic/README.md | 30 --------- .../{vx4 => src}/device_pll_ctrl.c | 10 +-- .../{vx4 => src}/mapfile.c | 0 .../xs3/XK-EVK-XU316-AIV.xn | 66 ------------------- .../app_mic_array_basic/xs3/device_pll_ctrl.c | 35 ---------- examples/app_mic_array_basic/xs3/mapfile.xc | 25 ------- examples/deps.cmake | 2 +- 9 files changed, 9 insertions(+), 182 deletions(-) delete mode 100644 examples/app_mic_array_basic/README.md rename examples/app_mic_array_basic/{vx4 => src}/device_pll_ctrl.c (92%) rename examples/app_mic_array_basic/{vx4 => src}/mapfile.c (100%) delete mode 100644 examples/app_mic_array_basic/xs3/XK-EVK-XU316-AIV.xn delete mode 100644 examples/app_mic_array_basic/xs3/device_pll_ctrl.c delete mode 100644 examples/app_mic_array_basic/xs3/mapfile.xc diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 99700f7d..2e687aa0 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -2,7 +2,6 @@ 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 index 0b35ca79..b5628f85 100644 --- a/examples/app_mic_array_basic/CMakeLists.txt +++ b/examples/app_mic_array_basic/CMakeLists.txt @@ -4,25 +4,9 @@ project(app_mic_array) set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) -# conditional depending on target -set(APP_C_SRCS src/app.c) - -if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL "3.6.0") - set(APP_HW_TARGET xs3/XK-EVK-XU316-AIV.xn) - set(APP_INCLUDES src xs3) - list(APPEND APP_C_SRCS - xs3/device_pll_ctrl.c - ) - list(APPEND APP_XC_SRCS - xs3/mapfile.xc - ) -else() - set(APP_HW_TARGET XK-EVK-XU416) - set(APP_INCLUDES src vx4) - list(APPEND APP_C_SRCS - vx4/device_pll_ctrl.c - vx4/mapfile.c - ) +# targets (IF NOT DEFINED OR NOT SUPPORTED) +if(NOT DEFINED APP_HW_TARGET OR NOT APP_HW_TARGET STREQUAL "XK-EVK-XU416") + message(FATAL_ERROR "This application is only supported for XK-EVK-XU416. Please re-run cmake with -DAPP_HW_TARGET=XK-EVK-XU416.") endif() set(APP_DEPENDENT_MODULES "lib_mic_array") diff --git a/examples/app_mic_array_basic/README.md b/examples/app_mic_array_basic/README.md deleted file mode 100644 index 3c305b91..00000000 --- a/examples/app_mic_array_basic/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Basic Mic Array Example - -## Hardware Required - -- **XMS0016** - -## 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/vx4/device_pll_ctrl.c b/examples/app_mic_array_basic/src/device_pll_ctrl.c similarity index 92% rename from examples/app_mic_array_basic/vx4/device_pll_ctrl.c rename to examples/app_mic_array_basic/src/device_pll_ctrl.c index e9262b51..2690337b 100644 --- a/examples/app_mic_array_basic/vx4/device_pll_ctrl.c +++ b/examples/app_mic_array_basic/src/device_pll_ctrl.c @@ -68,11 +68,11 @@ void device_pll_init(void) // print reg values printf("PLL Configuration:\n"); - printf("PLL DISABLE: 0x%08lX\n", DEVICE_PLL_DISABLE); - printf("PLL MUX VAL: 0x%08lX\n", DEVICE_PLL_MUX_VAL); - printf("PLL CTL VAL: 0x%08lX\n", DEVICE_PLL_CTL_VAL); - printf("PLL DIV VAL: 0x%08lX\n", DEVICE_PLL_DIV_0); - printf("PLL FRAC_NOM: 0x%08lX\n", DEVICE_PLL_FRAC_NOM); + printf("PLL DISABLE: 0x%08X\n", DEVICE_PLL_DISABLE); + printf("PLL MUX VAL: 0x%08X\n", DEVICE_PLL_MUX_VAL); + printf("PLL CTL VAL: 0x%08X\n", DEVICE_PLL_CTL_VAL); + printf("PLL DIV VAL: 0x%08X\n", DEVICE_PLL_DIV_0); + printf("PLL FRAC_NOM: 0x%08X\n", DEVICE_PLL_FRAC_NOM); // CONFIGURE sswitch_reg_try_write(tileid, VX_SSB_CSR_PLL1_CTRL_NUM, DEVICE_PLL_DISABLE); // disable PLL before configuration diff --git a/examples/app_mic_array_basic/vx4/mapfile.c b/examples/app_mic_array_basic/src/mapfile.c similarity index 100% rename from examples/app_mic_array_basic/vx4/mapfile.c rename to examples/app_mic_array_basic/src/mapfile.c diff --git a/examples/app_mic_array_basic/xs3/XK-EVK-XU316-AIV.xn b/examples/app_mic_array_basic/xs3/XK-EVK-XU316-AIV.xn deleted file mode 100644 index b4eb8fff..00000000 --- a/examples/app_mic_array_basic/xs3/XK-EVK-XU316-AIV.xn +++ /dev/null @@ -1,66 +0,0 @@ - - - Board - xcore.ai Vision Development Kit - - - tileref tile[2] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/app_mic_array_basic/xs3/device_pll_ctrl.c b/examples/app_mic_array_basic/xs3/device_pll_ctrl.c deleted file mode 100644 index a50d04f8..00000000 --- a/examples/app_mic_array_basic/xs3/device_pll_ctrl.c +++ /dev/null @@ -1,35 +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 "device_pll_ctrl.h" - - -#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) -{ - 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/xs3/mapfile.xc b/examples/app_mic_array_basic/xs3/mapfile.xc deleted file mode 100644 index 2c695375..00000000 --- a/examples/app_mic_array_basic/xs3/mapfile.xc +++ /dev/null @@ -1,25 +0,0 @@ -// 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/deps.cmake b/examples/deps.cmake index 1eb6236f..55ebaaa8 100644 --- a/examples/deps.cmake +++ b/examples/deps.cmake @@ -6,7 +6,7 @@ if(NOT EXISTS ${XMOS_SANDBOX_DIR}/fwk_io) FetchContent_Declare( fwk_io GIT_REPOSITORY git@github.com:xmos/fwk_io - GIT_TAG feature/xcommon_cmake + GIT_TAG b88f0d700a8f8eda8e686c4a088ff7d648ef69f6 SOURCE_DIR ${XMOS_SANDBOX_DIR}/fwk_io ) FetchContent_Populate(fwk_io) From f8d49763191584b6ba5e15529415fd361e54b77c Mon Sep 17 00:00:00 2001 From: xalbertoisorna Date: Fri, 22 May 2026 15:48:51 +0100 Subject: [PATCH 13/17] Mic array /api doc update --- .gitignore | 1 + lib_mic_array/api/mic_array/cpp/PdmRx.hpp | 7 +++++-- lib_mic_array/api/mic_array/etc/fir_1x16_bit.h | 10 ++++------ lib_mic_array/api/mic_array/pdm_resources.h | 6 ++---- lib_mic_array/lib_build_info.cmake | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index e50d6bee..7921088e 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,4 @@ build.xcore lib_mic_array.egg-info examples/app_mic_array_basic/output.wav examples/app_mic_array_basic/mic_array_output.bin +tests/src/audio-test-tools diff --git a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp index 8c77ab3e..a95ffdce 100644 --- a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp +++ b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp @@ -613,8 +613,10 @@ void mic_array::StandardPdmRxService } // Now that we're sure that PdmRx thread has exited, drain any pending blocks chanend_t c_pdm_blocks_end_b = this->c_pdm_blocks.end_b; - SELECT_RES(CASE_THEN(c_pdm_blocks_end_b, rx_pending_block), - DEFAULT_THEN(empty)) + SELECT_RES( + CASE_THEN(c_pdm_blocks_end_b, rx_pending_block), + DEFAULT_THEN(empty) + ) { rx_pending_block: pdm_samples = GetPdmBlock(); @@ -623,6 +625,7 @@ void mic_array::StandardPdmRxService empty: break; } + (void)pdm_samples; // Avoid unused variable warning. } // Now that shutdown is complete, free the pdmrx channel s_chan_free(this->c_pdm_blocks); diff --git a/lib_mic_array/api/mic_array/etc/fir_1x16_bit.h b/lib_mic_array/api/mic_array/etc/fir_1x16_bit.h index 79f14129..4b2e87cb 100644 --- a/lib_mic_array/api/mic_array/etc/fir_1x16_bit.h +++ b/lib_mic_array/api/mic_array/etc/fir_1x16_bit.h @@ -11,18 +11,16 @@ C_API_START /** Function that computes an FIR over a 1-bit signal with 16-bit coefficients. * The one-bit signal is stored as a sequence of bits, each of them representing - * -1 or +1. The coefficients are notionally a vector of 16-bti values, but with + * -1 or +1. The coefficients are notionally a vector of 16-bit values, but with * a few provisos: * - * * The number of coefficients must be a multiple of 256, and N_256 (the third - * parameter) represents how many multiples there are. N must be at least 1 * * * The coefficients shall be in the range [-32767 .. 32767] * * * Coefficients are stored in slices of bits. A slice of the coefficient vector - * comprises 256 coefficients, so there are N_256 slices. + * comprises 256 coefficients. * Each slice occupies 256 * 16 / 32 = 128 words: 16 bits for each of the 256 - * elements, and there are 23 bits per word. The coefficients are decomposed + * elements, and there are 32 bits per word. The coefficients are decomposed * over the words, and each group of eight words (256 bits) store subsequent * bits of the coefficients values as follows. The bits in * - words 0..7 have magnitude +/-1 @@ -48,4 +46,4 @@ C_API_START MA_C_API int fir_1x16_bit(uint32_t signal[], const uint32_t coeff_1[]); -C_API_END \ No newline at end of file +C_API_END diff --git a/lib_mic_array/api/mic_array/pdm_resources.h b/lib_mic_array/api/mic_array/pdm_resources.h index 8ca9fbda..1c2ac74a 100644 --- a/lib_mic_array/api/mic_array/pdm_resources.h +++ b/lib_mic_array/api/mic_array/pdm_resources.h @@ -2,14 +2,12 @@ // This Software is subject to the terms of the XMOS Public Licence: Version 1. #pragma once -#include // for clock_t -#include +#include + #include "api.h" #include "etc/xcore_compat.h" - - C_API_START diff --git a/lib_mic_array/lib_build_info.cmake b/lib_mic_array/lib_build_info.cmake index 4212826a..77d907b3 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 7.0.0) -set(LIB_DEPENDENT_MODULES "lib_xcore_math(2.4.1)") +set(LIB_DEPENDENT_MODULES "lib_xcore_math(develop)") set(LIB_INCLUDES api api/mic_array From 7a90c19ca5f17eaec3e66a4975828948d6c32f2c Mon Sep 17 00:00:00 2001 From: xalbertoisorna Date: Fri, 22 May 2026 15:50:02 +0100 Subject: [PATCH 14/17] Add support for vx4 tests/signal --- Jenkinsfile | 34 ++++++++-- tests/signal/BasicMicArray/CMakeLists.txt | 47 +++++++------- tests/signal/BasicMicArray/src/app.c | 8 ++- tests/signal/BasicMicArray/test_mic_array.py | 30 +++++---- tests/signal/pdmrx_isr/CMakeLists.txt | 13 ++-- .../signal/profile/app_memory/CMakeLists.txt | 4 ++ tests/signal/profile/app_mips/CMakeLists.txt | 27 ++++---- tests/signal/profile/app_mips/src/app_pll.c | 10 +-- tests/signal/profile/conftest.py | 5 ++ tests/signal/profile/mic_array_mips_vx4.json | 14 ++-- tests/signal/profile/test_measure_mips.py | 65 ++++++++++--------- tests/unit/CMakeLists.txt | 16 ++--- .../unit/src/test_ChannelFrameTransmitter.cpp | 4 +- 13 files changed, 160 insertions(+), 117 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 8fa86acc..f7075e8f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -14,7 +14,7 @@ pipeline { ) string( name: 'TOOLS_VX4_VERSION', - defaultValue: '-j --repo arch_vx_slipgate -b master -a XTC 116', + defaultValue: '-j --repo arch_vx_slipgate -b develop -a XTC 1184', description: 'The XTC Slipgate tools version' ) string( @@ -225,17 +225,32 @@ pipeline { dir(REPO_NAME){ checkoutScmShallow() dir("tests") { + withTools(params.TOOLS_VX4_VERSION){ createVenv(reqFile: "requirements.txt") withVenv { dir("unit") { - xcoreBuild(toolsVersion: params.TOOLS_VX4_VERSION) + xcoreBuild( + toolsVersion: params.TOOLS_VX4_VERSION, + cmakeOpts: '-DAPP_HW_TARGET=XK-EVK-XU416', + jobs:8 + ) } dir ("signal/BasicMicArray") { - withTools(params.TOOLS_VX4_VERSION){ - xcoreBuild(toolsVersion: params.TOOLS_VX4_VERSION, jobs:8) - } + xcoreBuild( + toolsVersion: params.TOOLS_VX4_VERSION, + cmakeOpts: '-DAPP_HW_TARGET=XK-EVK-XU416', + jobs:8 + ) + } + dir ("signal/profile/app_mips") { + xcoreBuild( + toolsVersion: params.TOOLS_VX4_VERSION, + cmakeOpts: '-DAPP_HW_TARGET=XK-EVK-XU416', + jobs:8 + ) } } // withVenv + } // withTools } // dir("tests") } // dir(REPO_NAME) } // steps @@ -243,13 +258,18 @@ pipeline { stage('Run tests') { steps { dir("${REPO_NAME}/tests") { + withTools(params.TOOLS_VX4_VERSION) { withVenv { dir("unit") { - withTools(params.TOOLS_VX4_VERSION) {sh "xrun --xscope bin/tests-unit.xe"} + sh "xrun --xscope bin/tests-unit.xe" } dir("signal/BasicMicArray") { - withTools(params.TOOLS_VX4_VERSION) {sh 'python -m pytest --level nightly --seed 12345 -k "0isr or OneStageFilter" -v'} + sh 'pytest --level nightly --seed 12345 -k "0isr or OneStageFilter" -v' + } + dir ("signal/profile") { + sh 'pytest test_measure_mips.py --APP_HW_TARGET=XK-EVK-XU416 -v' } + } // with tools } // withVenv }}} // stage('Run tests') } // stages diff --git a/tests/signal/BasicMicArray/CMakeLists.txt b/tests/signal/BasicMicArray/CMakeLists.txt index 39f596e0..a672cfd3 100644 --- a/tests/signal/BasicMicArray/CMakeLists.txt +++ b/tests/signal/BasicMicArray/CMakeLists.txt @@ -6,26 +6,29 @@ set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../..) 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 - -lxc - -Wno-fptrgroup - -Wno-format) +# Target configuration +if(NOT DEFINED APP_HW_TARGET OR APP_HW_TARGET STREQUAL "XK-EVK-XU316") + set(APP_HW_TARGET XK-EVK-XU316) + set(ARCH_COMPILER_FLAGS -O2 + -g + -report + -mcmodel=large + -Wno-xcore-fptrgroup + -Wno-unknown-pragmas + -Wno-format) +else() + if(APP_HW_TARGET STREQUAL "XK-EVK-XU416") + set(ARCH_COMPILER_FLAGS -Os + -g + -lxc + -Wno-fptrgroup + -Wno-format) + else() + message(FATAL_ERROR "Unsupported target ${APP_HW_TARGET}.") + endif() endif() +# Params set_property(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/test_params.json") @@ -54,7 +57,7 @@ math(EXPR NUM_SAMP_FREQ "${NUM_SAMP_FREQ} - 1") math(EXPR NUM_ONE_MIC_OVERRIDE "${NUM_ONE_MIC_OVERRIDE} - 1") # Remove ISR if vx4 as it's not supported - if (APP_HW_TARGET STREQUAL "XK-EVK-XU416") +if(APP_HW_TARGET STREQUAL "XK-EVK-XU416") set(USE_ISR_LIST "[0]") set(NUM_USE_ISR 0) endif() @@ -113,7 +116,7 @@ foreach(l RANGE 0 ${NUM_SAMP_FREQ}) set(CONFIG "${N_MICS}ch_${FRAME_SIZE}smp_${USE_ISR}isr_${USE_1MIC_OVERRIDE}mo_${samp_freq_str}") message(${CONFIG}) - set(APP_COMPILER_FLAGS_${CONFIG} ${COMMON_COMPILER_FLAGS} + set(APP_COMPILER_FLAGS_${CONFIG} ${ARCH_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} @@ -131,7 +134,7 @@ endforeach() set(APP_INCLUDES src ${AUTOGEN_OUT_DIR}) # One stage filter (small_768k_to_12k_filter.h) + 1-mic override -set(APP_COMPILER_FLAGS_1stg_filter_1mic_override ${COMMON_COMPILER_FLAGS} +set(APP_COMPILER_FLAGS_1stg_filter_1mic_override ${ARCH_COMPILER_FLAGS} -DMIC_ARRAY_CONFIG_USE_PDM_ISR=0 -DMIC_ARRAY_CONFIG_SAMPLES_PER_FRAME=2 -DMIC_ARRAY_CONFIG_MIC_COUNT=2 # Set to something other than 1 to test 1-mic overriding @@ -142,7 +145,7 @@ set(APP_COMPILER_FLAGS_1stg_filter_1mic_override ${COMMON_COMPILER_FLAGS} ) # One stage filter (small_768k_to_12k_filter.h) -set(APP_COMPILER_FLAGS_1stg_filter ${COMMON_COMPILER_FLAGS} +set(APP_COMPILER_FLAGS_1stg_filter ${ARCH_COMPILER_FLAGS} -DMIC_ARRAY_CONFIG_USE_PDM_ISR=0 -DMIC_ARRAY_CONFIG_SAMPLES_PER_FRAME=2 -DMIC_ARRAY_CONFIG_MIC_COUNT=2 # Set to something other than 1 to test 1-mic overriding diff --git a/tests/signal/BasicMicArray/src/app.c b/tests/signal/BasicMicArray/src/app.c index 093cc6d4..9aa077fb 100644 --- a/tests/signal/BasicMicArray/src/app.c +++ b/tests/signal/BasicMicArray/src/app.c @@ -57,6 +57,8 @@ #define DATA_OUT (1) #endif +#define FIFO_ENTRIES (8) + typedef chanend_t streaming_chanend_t; DECLARE_JOB(app_output_task, (chanend_t, chanend_t)); @@ -378,9 +380,9 @@ void app_mic( // Sometimes xscope doesn't keep up causing backpressure so add a FIFO to decouple this, at least up to 8 frames. // We can buffer up to 8 chars in a same tile chanend. -const unsigned fifo_entries = 8; + typedef int32_t ma_frame_t[APP_MIC_COUNT][MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME]; -ma_frame_t frame_fifo[fifo_entries]; +ma_frame_t frame_fifo[FIFO_ENTRIES]; void app_output_task(chanend_t c_frames_in, chanend_t c_fifo) @@ -414,7 +416,7 @@ void app_output_task(chanend_t c_frames_in, chanend_t c_fifo) if(t1 - t0 > 10){ printstrln("ERROR - Timing fail"); } - if(fifo_idx == fifo_entries){ + if(fifo_idx == FIFO_ENTRIES){ fifo_idx = 0; } } diff --git a/tests/signal/BasicMicArray/test_mic_array.py b/tests/signal/BasicMicArray/test_mic_array.py index bfc1678d..f4b63bd4 100644 --- a/tests/signal/BasicMicArray/test_mic_array.py +++ b/tests/signal/BasicMicArray/test_mic_array.py @@ -37,6 +37,8 @@ from pathlib import Path from mic_array_shared import MicArraySharedBase +MAX_DIFF_TH = 12 + with open(Path(__file__).parent / "test_params.json") as f: params = json.load(f) @@ -149,10 +151,15 @@ def test_BasicMicArray(self, request, chans, frame_size, use_isr, one_mic_overri # not always, because the 64-bit partial products of the inner product # (i.e. filter_state[:] * filter_coef[:]) have a rounding-right-shift # applied to them prior to being summed. - result_diff = np.max(np.abs(expected - device_output)) - print(f"result_diff = {result_diff}") - threshold = 12 - assert result_diff <= threshold, f"max diff between python and xcore mic array output ({result_diff}) exceeds threshold ({threshold})" + + # compare shape + exp = expected + dev = device_output + assert exp.shape == dev.shape, f"shapes differ: {exp.shape} vs {dev.shape}" + + # compare max difference + result_diff = np.max(np.abs(exp - dev)) + assert result_diff <= MAX_DIFF_TH, f"result_diff = {result_diff} exceeds MAX_DIFF_TH = {MAX_DIFF_TH}" @pytest.mark.parametrize("chans", [1, 2], ids=["1mic_override", "2mic"]) @@ -227,12 +234,11 @@ def test_BasicMicArrayOneStageFilter(self, request, chans): end = -device_output_delay_samps or None start = device_output_delay_samps - result_diff = np.max(np.abs(expected[:, :end] - device_output[:, start:])) - - print(f"result_diff = {result_diff}") + # compare shape + exp = expected[:, :end] + dev = device_output[:, start:] + assert exp.shape == dev.shape, f"shapes differ: {exp.shape} vs {dev.shape}" - threshold = 12 - assert result_diff <= threshold, ( - f"max diff between python and xcore mic array output ({result_diff}) " - f"exceeds threshold ({threshold})" - ) \ No newline at end of file + # compare max difference + result_diff = np.max(np.abs(exp - dev)) + assert result_diff <= MAX_DIFF_TH, f"result_diff = {result_diff} exceeds MAX_DIFF_TH = {MAX_DIFF_TH}" diff --git a/tests/signal/pdmrx_isr/CMakeLists.txt b/tests/signal/pdmrx_isr/CMakeLists.txt index 3878863d..1dc5b63e 100644 --- a/tests/signal/pdmrx_isr/CMakeLists.txt +++ b/tests/signal/pdmrx_isr/CMakeLists.txt @@ -12,10 +12,15 @@ set(APP_COMPILER_FLAGS -Os -DMIC_ARRAY_CONFIG_USE_PDM_ISR=1 ) -if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL "3.6.0") # XS3 (XTC 15.3.1) -set(APP_HW_TARGET XK-EVK-XU316) -else() # VX4 -set(APP_HW_TARGET XK-EVK-XU416) +# Default target +if(NOT DEFINED APP_HW_TARGET) + set(APP_HW_TARGET XK-EVK-XU316) +endif() + +# Supported targets +set(SUPPORTED_TARGETS "XK-EVK-XU316;XK-EVK-XU416") +if(NOT APP_HW_TARGET IN_LIST SUPPORTED_TARGETS) + message(FATAL_ERROR "Unsupported target ${APP_HW_TARGET}. Supported: ${SUPPORTED_TARGETS}") endif() XMOS_REGISTER_APP() diff --git a/tests/signal/profile/app_memory/CMakeLists.txt b/tests/signal/profile/app_memory/CMakeLists.txt index c9410a47..94080d27 100644 --- a/tests/signal/profile/app_memory/CMakeLists.txt +++ b/tests/signal/profile/app_memory/CMakeLists.txt @@ -6,6 +6,10 @@ set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../../..) set(APP_DEPENDENT_MODULES "lib_mic_array") +if(APP_HW_TARGET STREQUAL "XK-EVK-XU416") +message(FATAL_ERROR "Unsupported target ${APP_HW_TARGET}. This test is only supported on XK-VOICE-L71.xn.") +endif() + set(APP_HW_TARGET XK-VOICE-L71.xn) set(NAME_MAP custom;default) diff --git a/tests/signal/profile/app_mips/CMakeLists.txt b/tests/signal/profile/app_mips/CMakeLists.txt index 068f602e..bd755dcd 100644 --- a/tests/signal/profile/app_mips/CMakeLists.txt +++ b/tests/signal/profile/app_mips/CMakeLists.txt @@ -8,11 +8,20 @@ set(APP_DEPENDENT_MODULES "lib_mic_array") set(AUTOGEN_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/autogen") -if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL "3.6.0") # XS3 (XTC 15.3.1) -set(ISR_LIST 0 1) # ISR and thread-based PDM handling -else() # VX4 -set(ISR_LIST 0) # Only thread-based PDM handling supported on VX4 +# Target specific +if(NOT DEFINED APP_HW_TARGET) + set(APP_HW_TARGET src/XK-VOICE-L71.xn) + set(ISR_LIST 0 1) # ISR and thread-based PDM handling + list(APPEND APP_DEPENDENT_MODULES "lib_board_support(1.5.0)") +else() + if(APP_HW_TARGET STREQUAL "XK-EVK-XU416") + set(ISR_LIST 0) # Only thread-based PDM handling supported on VX4 + set(APP_XC_SRCS "") # prevents including xc + else() + message(FATAL_ERROR "Unsupported target ${APP_HW_TARGET}.") + endif() endif() +message(STATUS "Building for target ${APP_HW_TARGET} with ISR_LIST=${ISR_LIST}") set(NAME_MAP thread;isr) # Exactly one custom filter (.pkl) may be listed alongside numeric sample rates. @@ -71,16 +80,6 @@ endforeach() set(APP_INCLUDES src src/mips/ ${AUTOGEN_OUT_DIR}) -# ---- Target specific ---- -if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL "3.6.0") # XS3 (XTC 15.3.1) -set(APP_HW_TARGET src/XK-VOICE-L71.xn) -list(APPEND APP_DEPENDENT_MODULES "lib_board_support(1.5.0)") -else() # VX4 -set(APP_HW_TARGET XK-EVK-XU416) -set(APP_XC_SRCS "") # prevents including xc -endif() - - XMOS_REGISTER_APP() foreach(target ${APP_BUILD_TARGETS}) diff --git a/tests/signal/profile/app_mips/src/app_pll.c b/tests/signal/profile/app_mips/src/app_pll.c index c9d266c4..1b8b4731 100644 --- a/tests/signal/profile/app_mips/src/app_pll.c +++ b/tests/signal/profile/app_mips/src/app_pll.c @@ -57,11 +57,11 @@ void app_pll_init(void) // print reg values printf("PLL Configuration:\n"); - printf("PLL DISABLE: 0x%08lX\n", DEVICE_PLL_DISABLE); - printf("PLL MUX VAL: 0x%08lX\n", DEVICE_PLL_MUX_VAL); - printf("PLL CTL VAL: 0x%08lX\n", DEVICE_PLL_CTL_VAL); - printf("PLL DIV VAL: 0x%08lX\n", DEVICE_PLL_DIV_0); - printf("PLL FRAC_NOM: 0x%08lX\n", DEVICE_PLL_FRAC_NOM); + printf("PLL DISABLE: 0x%08X\n", DEVICE_PLL_DISABLE); + printf("PLL MUX VAL: 0x%08X\n", DEVICE_PLL_MUX_VAL); + printf("PLL CTL VAL: 0x%08X\n", DEVICE_PLL_CTL_VAL); + printf("PLL DIV VAL: 0x%08X\n", DEVICE_PLL_DIV_0); + printf("PLL FRAC_NOM: 0x%08X\n", DEVICE_PLL_FRAC_NOM); // CONFIGURE sswitch_reg_try_write(tileid, VX_SSB_CSR_PLL1_CTRL_NUM, DEVICE_PLL_DISABLE); // disable PLL before configuration diff --git a/tests/signal/profile/conftest.py b/tests/signal/profile/conftest.py index 3a895863..ae63f9ad 100644 --- a/tests/signal/profile/conftest.py +++ b/tests/signal/profile/conftest.py @@ -8,3 +8,8 @@ def pytest_addoption(parser): help=("Overwrite mic_array_memory.json and regenerate mic_array_memory_table.rst. " "The comparison check which flags mips/memory being out of range doesn't run in this case.") ) + parser.addoption( + "--APP_HW_TARGET", + default="XK-EVK-XU316", + help="Hardware target board. Supported: XK-EVK-XU316 (default, xs3), XK-EVK-XU416 (vx4)" + ) diff --git a/tests/signal/profile/mic_array_mips_vx4.json b/tests/signal/profile/mic_array_mips_vx4.json index d0f837b8..5cc650cc 100644 --- a/tests/signal/profile/mic_array_mips_vx4.json +++ b/tests/signal/profile/mic_array_mips_vx4.json @@ -1,8 +1,8 @@ { - "1mic_thread_16000fs": 10.81, - "1mic_thread_32000fs": 13.16, - "1mic_thread_48000fs": 16.50, - "2mic_thread_16000fs": 22.74, - "2mic_thread_32000fs": 26.94, - "2mic_thread_48000fs": 33.10 -} + "1mic_thread_16000fs": 11.6162, + "1mic_thread_32000fs": 13.9999, + "1mic_thread_48000fs": 17.3681, + "2mic_thread_16000fs": 23.6923, + "2mic_thread_32000fs": 27.9639, + "2mic_thread_48000fs": 34.2039 +} \ No newline at end of file diff --git a/tests/signal/profile/test_measure_mips.py b/tests/signal/profile/test_measure_mips.py index bedfeffa..15f5565a 100644 --- a/tests/signal/profile/test_measure_mips.py +++ b/tests/signal/profile/test_measure_mips.py @@ -9,31 +9,32 @@ cwd = Path(__file__).parent -def get_xcc_version() -> str: - output = subprocess.check_output(["xcc", "--version"], text=True) - for line in output.splitlines(): - if line.startswith("XTC version:"): - return line.split(":")[1].strip() - raise RuntimeError("XTC version not found") - -def get_mips_file() -> Path: - xcc_version = get_xcc_version() - if "15.3.1" in xcc_version: - mips_file = cwd / "mic_array_mips_xs3.json" - elif "99.99.99" in xcc_version: - mips_file = cwd / "mic_array_mips_vx4.json" - else: - raise RuntimeError(f"Unsupported XCC version: {xcc_version}") - return mips_file - -def get_isr_list(): - xcc_version = get_xcc_version() - if "15.3.1" in xcc_version: - return ["isr", "thread"] - elif "99.99.99" in xcc_version: - return ["thread"] # Only thread-based PDM handling supported on VX4 - else: - raise RuntimeError(f"Unsupported XCC version: {xcc_version}") +MIC_LIST = [1, 2] +FS_LIST = [16000, 32000, 48000] + +def get_isr_list(hw_target: str = "XK-EVK-XU316"): + options = { + "XK-EVK-XU316": ["isr", "thread"], + "XK-EVK-XU416": ["thread"], + } + isr_list = options[hw_target] + return isr_list + +def get_mips_file(hw_target: str = "XK-EVK-XU316") -> Path: + options = { + "XK-EVK-XU316": "mic_array_mips_xs3.json", + "XK-EVK-XU416": "mic_array_mips_vx4.json", + } + mips_file = options[hw_target] + return cwd / mips_file + +def get_rst_file(hw_target: str = "XK-EVK-XU316") -> Path: + options = { + "XK-EVK-XU316": "mic_array_mips_table.rst", + "XK-EVK-XU416": "mic_array_mips_table_vx4.rst", + } + rst_file = options[hw_target] + return cwd / rst_file def max_mips(lines): mips_values = [] @@ -102,9 +103,10 @@ def test_measure_mips(pytestconfig): mic_array_mips_table.rst - autogenerated RST table of results """ update = pytestconfig.getoption("--update") - mics = [1, 2] - pdmrx = get_isr_list() - fs = [16000, 32000, 48000] + hw_target = pytestconfig.getoption("--APP_HW_TARGET") or "XK-EVK-XU316" + pdmrx = get_isr_list(hw_target) + mics = MIC_LIST + fs = FS_LIST results = {} print("\n\n") for chans, pdmrx_type, samp_freq in itertools.product(mics, pdmrx, fs): @@ -117,7 +119,8 @@ def test_measure_mips(pytestconfig): # Compare against mic_array_mips.json that's already there to ensure MIPS # number are in the same ballpark, before overwriting mic_array_mips.json - outfile = get_mips_file() + outfile = get_mips_file(hw_target) + outfile_rst = get_rst_file(hw_target) with outfile.open("r") as f: ref_data = json.load(f) for cfg in ref_data: @@ -125,7 +128,7 @@ def test_measure_mips(pytestconfig): assert cfg in results, f"cfg {cfg} not found in results.\nresults = {results}" test_mips = results[cfg] if not update: - threshold = 0.50 #TODO replace by 0.05 once stable + threshold = 0.25 #TODO replace by 0.05 once stable assert abs(test_mips - ref_mips) < threshold, (f"For cfg {cfg}, test_mips {test_mips} differ " f"from ref_mips {ref_mips} by more than the allowed threshold of {threshold}.\n" f"If this is expected, run test with pytest test_measure_mips --update " @@ -136,5 +139,5 @@ def test_measure_mips(pytestconfig): json.dump(results, f, indent=2) # RST table output - rst_out = cwd / "mic_array_mips_table.rst" + rst_out = outfile_rst write_rst_table(results, rst_out) diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 9352cc9a..3f93e0f9 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -7,16 +7,9 @@ set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) set(APP_INCLUDES src) 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) +if(NOT DEFINED APP_HW_TARGET OR APP_HW_TARGET STREQUAL "XK-EVK-XU316") + set(APP_HW_TARGET XK-EVK-XU316) set(APP_COMPILER_FLAGS -O2 -g -report @@ -28,12 +21,15 @@ if(__XS3__) # xs3 -fxscope -DUNITY_INCLUDE_CONFIG_H=1) else() # vx4 - set(APP_HW_TARGET XK-EVK-XU416) + if(APP_HW_TARGET STREQUAL "XK-EVK-XU416") set(APP_COMPILER_FLAGS -Os -g -Wno-fptrgroup -DUNITY_INCLUDE_CONFIG_H=1) + else() + message(FATAL_ERROR "Unsupported target ${APP_HW_TARGET}.") + endif() endif() XMOS_REGISTER_APP() diff --git a/tests/unit/src/test_ChannelFrameTransmitter.cpp b/tests/unit/src/test_ChannelFrameTransmitter.cpp index 45729fc8..8551e286 100644 --- a/tests/unit/src/test_ChannelFrameTransmitter.cpp +++ b/tests/unit/src/test_ChannelFrameTransmitter.cpp @@ -52,8 +52,8 @@ extern "C" { } - static unsigned __attribute__((aligned (8))) stack[8000]; // dword alignment requirement. see comment in test_ma_frame_tx_rx.cpp - static void* stack_start = stack_base(stack, 8000); + static unsigned __attribute__((aligned (8))) th_stack[8000]; // dword alignment requirement. see comment in test_ma_frame_tx_rx.cpp + static void* stack_start = stack_base(th_stack, 8000); } From 6c9a6c3ae9aa5740bcf48c2c8c810c17e04b8fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Fri, 22 May 2026 15:52:37 +0100 Subject: [PATCH 15/17] Update lib_unity version in CMakeLists.txt to v2.7.0 --- tests/unit/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 3f93e0f9..fd0d6399 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -5,7 +5,7 @@ project(tests-unit) set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) set(APP_INCLUDES src) -set(APP_DEPENDENT_MODULES "lib_mic_array" "lib_unity(main)") #TODO release lib_unity +set(APP_DEPENDENT_MODULES "lib_mic_array" "lib_unity(v2.7.0)") # Target specific compiler flags if(NOT DEFINED APP_HW_TARGET OR APP_HW_TARGET STREQUAL "XK-EVK-XU316") From f02cbcd9d1623dc2b8ef85b6f77eb0d14945cdd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Wed, 27 May 2026 08:35:56 +0100 Subject: [PATCH 16/17] Update links to lib_xcore_math documentation --- doc/rst/src/custom_filters.rst | 4 ++-- doc/rst/src/decimator_stages.rst | 6 +++--- examples/app_par_decimator/src/app_decimator.hpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/rst/src/custom_filters.rst b/doc/rst/src/custom_filters.rst index 145f2577..502a1683 100644 --- a/doc/rst/src/custom_filters.rst +++ b/doc/rst/src/custom_filters.rst @@ -184,7 +184,7 @@ to convert floating-point coefficients to the required format. If a second stage is included, it must meet these requirements: -- Implementation: Must be compatible with the 32-bit FIR filter from `lib_xcore_math `_, +- Implementation: Must be compatible with the 32-bit FIR filter from `lib_xcore_math `_, specifically :c:func:`xs3_filter_fir_s32()` as described in :ref:`stage_2_filter_impl` - Tap count: Configurable (no fixed constraint) - Decimation factor: Configurable integer value @@ -193,7 +193,7 @@ Use the Python helper script ``python/stage2.py`` to convert floating-point coef **Stage 3 (optional)** -A third stage, if included, must also be compatible with the 32-bit FIR filter from `lib_xcore_math `_. +A third stage, if included, must also be compatible with the 32-bit FIR filter from `lib_xcore_math `_. It has the same flexibility as stage 2: - Tap count: Configurable diff --git a/doc/rst/src/decimator_stages.rst b/doc/rst/src/decimator_stages.rst index 75a83d54..9703bd5c 100644 --- a/doc/rst/src/decimator_stages.rst +++ b/doc/rst/src/decimator_stages.rst @@ -10,7 +10,7 @@ to convert a high sample rate stream of (1-bit) PDM samples into a lower sample rate stream of (32-bit) PCM samples. The default and most widely used configuration is a two-stage decimation -pipeline, and that two-stage case is the focus of this page. This is shown in +pipeline, and that two-stage case is the focus of this section. This is shown in :ref:`decimator_stages_simplified`. .. _decimator_stages_simplified: @@ -262,7 +262,7 @@ sample rate (the sample rate received by the main application code) is 3.072 MHz / (32*6) = 16 kHz The second stage filter uses the 32-bit FIR filter implementation from -`lib_xcore_math `_. See +`lib_xcore_math `_. See ``xs3_filter_fir_s32()`` in that library for more implementation details. The filter state (delay line) consists of as many 32-bit samples as there are taps in the stage-2 filter, @@ -272,6 +272,6 @@ and requires that many 32-bit words for storage. If providing stage-2 custom coefficients, they must remain compatible with the underlying 32-bit FIR implementation from - `lib_xcore_math `_ + `lib_xcore_math `_ (for example, tap/shift/state configuration must match the ``xs3_filter_fir_s32()`` function requirements). For details on supplying custom coefficients via configuration structures, see :ref:`custom_filters`. diff --git a/examples/app_par_decimator/src/app_decimator.hpp b/examples/app_par_decimator/src/app_decimator.hpp index 754fd704..83464d54 100644 --- a/examples/app_par_decimator/src/app_decimator.hpp +++ b/examples/app_par_decimator/src/app_decimator.hpp @@ -127,7 +127,7 @@ class MyTwoStageDecimator * * `s2_filter_shr` is the final right-shift applied to the stage 2 filter's * accumulator prior to output. See - * lib_xcore_math's + * lib_xcore_math's * documentation of `filter_fir_s32_t` for more details. * * @param s1_filter_coef @parblock From 186698c14e9cec136edda56669fb04f6d989aab2 Mon Sep 17 00:00:00 2001 From: xalbertoisorna <124703429+xalbertoisorna@users.noreply.github.com> Date: Thu, 28 May 2026 13:44:52 +0100 Subject: [PATCH 17/17] Add vx4b docs and fix typos (#314) * Updating reqs version * Add tests/src/audio-test-tools to .gitignore * Adding docs for vx4 and minor typos * Update documentation and tests for vx4 architecture support * Update version number to 7.1.0 in README * Update doc/rst/src/resource_usage.rst Co-authored-by: shuchitak <38428600+shuchitak@users.noreply.github.com> --------- Co-authored-by: shuchitak <38428600+shuchitak@users.noreply.github.com> --- CHANGELOG.rst | 11 ++++++ CMakeLists.txt | 2 +- README.rst | 2 +- doc/exclude_patterns.inc | 4 +++ doc/rst/src/getting_started.rst | 2 +- doc/rst/src/overview.rst | 4 +-- doc/rst/src/resource_usage.rst | 8 ++++- .../CMakeLists.txt | 0 .../convert.py | 0 .../src/app.c | 0 .../src/app_config.h | 0 .../src/config.xscope | 0 .../src/device_pll_ctrl.c | 0 .../src/device_pll_ctrl.h | 0 .../src/mapfile.c | 0 .../src/small_768k_to_12k_filter.h | 0 lib_mic_array/lib_build_info.cmake | 4 +-- settings.yml | 2 +- .../profile/mic_array_mips_table_vx4.rst | 34 +++++++++++++++++++ tests/signal/profile/mic_array_mips_vx4.json | 4 +-- tests/signal/profile/test_measure_mips.py | 16 ++++++--- 21 files changed, 78 insertions(+), 15 deletions(-) rename examples/{app_mic_array_basic => app_mic_array_vx4b}/CMakeLists.txt (100%) rename examples/{app_mic_array_basic => app_mic_array_vx4b}/convert.py (100%) rename examples/{app_mic_array_basic => app_mic_array_vx4b}/src/app.c (100%) rename examples/{app_mic_array_basic => app_mic_array_vx4b}/src/app_config.h (100%) rename examples/{app_mic_array_basic => app_mic_array_vx4b}/src/config.xscope (100%) rename examples/{app_mic_array_basic => app_mic_array_vx4b}/src/device_pll_ctrl.c (100%) rename examples/{app_mic_array_basic => app_mic_array_vx4b}/src/device_pll_ctrl.h (100%) rename examples/{app_mic_array_basic => app_mic_array_vx4b}/src/mapfile.c (100%) rename examples/{app_mic_array_basic => app_mic_array_vx4b}/src/small_768k_to_12k_filter.h (100%) create mode 100644 tests/signal/profile/mic_array_mips_table_vx4.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 082f6cc7..f5c56ff0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,17 @@ lib_mic_array change log ======================== +7.1.0 +----- + + * ADDED: Initial support for vx4 targets. + * ADDED: Support for 1-, 2-, or 3-stage decimation pipelines. + * ADDED: dynamic 1-mic switching support (`mic_array_enable_1mic_override`). + + * Changes to dependencies: + + - lib_xcore_math: 2.4.1 -> 3.0.0 + 7.0.0 ----- diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cab4dfb..0a3f30d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,7 @@ if(${DEV_LIB_MIC_ARRAY}) CPMAddPackage( NAME lib_xcore_math GIT_REPOSITORY https://github.com/xmos/lib_xcore_math - GIT_TAG v2.1.0 + GIT_TAG v3.0.0 ) set(USING_CUSTOM_CMAKE TRUE) add_subdirectory( tests ) diff --git a/README.rst b/README.rst index 3d87d928..bd7c00cd 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ lib_mic_array: PDM microphone array library ########################################### :vendor: XMOS -:version: 7.0.0 +:version: 7.1.0 :scope: General Use :description: PDM microphone array library :category: Audio diff --git a/doc/exclude_patterns.inc b/doc/exclude_patterns.inc index f5a5bea5..af6dfc73 100644 --- a/doc/exclude_patterns.inc +++ b/doc/exclude_patterns.inc @@ -8,3 +8,7 @@ build.xcore tests/**/.pytest_cache/*.md tests/.pytest_cache/*.md **/app_mic_array_basic*/*.md +**/audio-test-tools +tests/signal/profile/mic_array_mips_table.rst +tests/signal/profile/mic_array_mips_table_vx4.rst +tests/signal/profile/mic_array_memory_table.rst diff --git a/doc/rst/src/getting_started.rst b/doc/rst/src/getting_started.rst index 608d8a54..365607b7 100644 --- a/doc/rst/src/getting_started.rst +++ b/doc/rst/src/getting_started.rst @@ -337,7 +337,7 @@ following constraints: instantiation of :cpp:class:`MicArray ` object. This overhead is primarily from wrapper code and inclusion of all provided filter coefficient sets, even when only a subset is used (see - :ref:`mic_array_memory_usage`). For memory‑constrained systems a custom + :ref:`mic_array_memory_usage`). For memory-constrained systems a custom configuration might be preferable. For custom usage involving creating a :cpp:class:`MicArray ` diff --git a/doc/rst/src/overview.rst b/doc/rst/src/overview.rst index 536013cf..e8b7b6b6 100644 --- a/doc/rst/src/overview.rst +++ b/doc/rst/src/overview.rst @@ -22,8 +22,8 @@ Capabilities (3.072 MHz typical) * Supports :ref:`1-, 2-, or 3-stage decimation FIR filters ` - * First stage is compulsary and has a fixed tap count of 256 and decimation factor of 32 - * Further stages are optional and hav fully configurable tap count and decimation factor + * First stage is compulsory and has a fixed tap count of 256 and decimation factor of 32 + * Further stages are optional and have fully configurable tap count and decimation factor * Custom filter coefficients can be used for any stage * Pre-designed two-stage reference filters with total decimation factor of 192, 96 and 64 are provided (16 kHz, 32 kHz and 48 kHz output sample rates with 3.072 MHz input PDM clock). diff --git a/doc/rst/src/resource_usage.rst b/doc/rst/src/resource_usage.rst index 9d8838c2..f392224d 100644 --- a/doc/rst/src/resource_usage.rst +++ b/doc/rst/src/resource_usage.rst @@ -107,7 +107,9 @@ and no more than `CORE_CLOCK_MHZ/5` millions of issue slots per second, where With a core clock rate of 600 MHz, that means that each core should expect at least 75 MIPS. Table :ref:`mic_array_mips` shows the mic array MIPS by profiling an application that includes the -mic array. The application used to generate the MIPS numbers runs the :ref:`default ` mic +mic array. Table :ref:`mic_array_mips_vx4` shows the same for the vx4 architecture. + +The application used to generate the MIPS numbers runs the :ref:`default ` mic array API (so the decimator running in a single hardware thread) with all defines set to their default values as listed in :ref:`mic_array_default_model_defines` except for ``MIC_ARRAY_CONFIG_MIC_COUNT`` and ``MIC_ARRAY_CONFIG_USE_PDM_ISR``. These two (along with the output sampling rate) are varied to build the @@ -115,6 +117,8 @@ different configurations that are profiled. .. include:: ../../../tests/signal/profile/mic_array_mips_table.rst +.. include:: ../../../tests/signal/profile/mic_array_mips_table_vx4.rst + .. note:: The MIPS numbers scale approximately linearly with the number of microphones. Although the table lists values only @@ -124,7 +128,9 @@ different configurations that are profiled. :ref:`default ` mic array API. An example of a custom multi-threaded decimator implementation can be found in :ref:`mic_array_par_decimator`. +.. note:: + In vx4 configuration, running PDM RX in ISR mode is currently not supported, so the application will use at least two threads for running the mic array unit. Memory ====== diff --git a/examples/app_mic_array_basic/CMakeLists.txt b/examples/app_mic_array_vx4b/CMakeLists.txt similarity index 100% rename from examples/app_mic_array_basic/CMakeLists.txt rename to examples/app_mic_array_vx4b/CMakeLists.txt diff --git a/examples/app_mic_array_basic/convert.py b/examples/app_mic_array_vx4b/convert.py similarity index 100% rename from examples/app_mic_array_basic/convert.py rename to examples/app_mic_array_vx4b/convert.py diff --git a/examples/app_mic_array_basic/src/app.c b/examples/app_mic_array_vx4b/src/app.c similarity index 100% rename from examples/app_mic_array_basic/src/app.c rename to examples/app_mic_array_vx4b/src/app.c diff --git a/examples/app_mic_array_basic/src/app_config.h b/examples/app_mic_array_vx4b/src/app_config.h similarity index 100% rename from examples/app_mic_array_basic/src/app_config.h rename to examples/app_mic_array_vx4b/src/app_config.h diff --git a/examples/app_mic_array_basic/src/config.xscope b/examples/app_mic_array_vx4b/src/config.xscope similarity index 100% rename from examples/app_mic_array_basic/src/config.xscope rename to examples/app_mic_array_vx4b/src/config.xscope diff --git a/examples/app_mic_array_basic/src/device_pll_ctrl.c b/examples/app_mic_array_vx4b/src/device_pll_ctrl.c similarity index 100% rename from examples/app_mic_array_basic/src/device_pll_ctrl.c rename to examples/app_mic_array_vx4b/src/device_pll_ctrl.c diff --git a/examples/app_mic_array_basic/src/device_pll_ctrl.h b/examples/app_mic_array_vx4b/src/device_pll_ctrl.h similarity index 100% rename from examples/app_mic_array_basic/src/device_pll_ctrl.h rename to examples/app_mic_array_vx4b/src/device_pll_ctrl.h diff --git a/examples/app_mic_array_basic/src/mapfile.c b/examples/app_mic_array_vx4b/src/mapfile.c similarity index 100% rename from examples/app_mic_array_basic/src/mapfile.c rename to examples/app_mic_array_vx4b/src/mapfile.c diff --git a/examples/app_mic_array_basic/src/small_768k_to_12k_filter.h b/examples/app_mic_array_vx4b/src/small_768k_to_12k_filter.h similarity index 100% rename from examples/app_mic_array_basic/src/small_768k_to_12k_filter.h rename to examples/app_mic_array_vx4b/src/small_768k_to_12k_filter.h diff --git a/lib_mic_array/lib_build_info.cmake b/lib_mic_array/lib_build_info.cmake index 77d907b3..c2fd0474 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 7.0.0) -set(LIB_DEPENDENT_MODULES "lib_xcore_math(develop)") +set(LIB_VERSION 7.1.0) +set(LIB_DEPENDENT_MODULES "lib_xcore_math(3.0.0)") set(LIB_INCLUDES api api/mic_array diff --git a/settings.yml b/settings.yml index ee9907fa..80d8c6a9 100644 --- a/settings.yml +++ b/settings.yml @@ -3,7 +3,7 @@ lib_name: lib_mic_array project: '{{lib_name}}' title: '{{lib_name}}: PDM microphone array library' -version: 7.0.0 +version: 7.1.0 documentation: exclude_patterns_path: doc/exclude_patterns.inc diff --git a/tests/signal/profile/mic_array_mips_table_vx4.rst b/tests/signal/profile/mic_array_mips_table_vx4.rst new file mode 100644 index 00000000..762436af --- /dev/null +++ b/tests/signal/profile/mic_array_mips_table_vx4.rst @@ -0,0 +1,34 @@ +.. _mic_array_mips_vx4: + +.. list-table:: Estimated MIPS (per configuration) + :header-rows: 1 + :widths: 6 6 8 8 + + * - mic count + - PDM RX + - output samp freq + - MIPS + * - 1 + - THREAD + - 16000 + - 11.616 + * - 1 + - THREAD + - 32000 + - 14.000 + * - 1 + - THREAD + - 48000 + - 17.368 + * - 2 + - THREAD + - 16000 + - 23.692 + * - 2 + - THREAD + - 32000 + - 27.964 + * - 2 + - THREAD + - 48000 + - 34.204 \ No newline at end of file diff --git a/tests/signal/profile/mic_array_mips_vx4.json b/tests/signal/profile/mic_array_mips_vx4.json index 5cc650cc..5b135f97 100644 --- a/tests/signal/profile/mic_array_mips_vx4.json +++ b/tests/signal/profile/mic_array_mips_vx4.json @@ -1,7 +1,7 @@ { - "1mic_thread_16000fs": 11.6162, + "1mic_thread_16000fs": 11.6161, "1mic_thread_32000fs": 13.9999, - "1mic_thread_48000fs": 17.3681, + "1mic_thread_48000fs": 17.368, "2mic_thread_16000fs": 23.6923, "2mic_thread_32000fs": 27.9639, "2mic_thread_48000fs": 34.2039 diff --git a/tests/signal/profile/test_measure_mips.py b/tests/signal/profile/test_measure_mips.py index 15f5565a..11c3330f 100644 --- a/tests/signal/profile/test_measure_mips.py +++ b/tests/signal/profile/test_measure_mips.py @@ -36,6 +36,13 @@ def get_rst_file(hw_target: str = "XK-EVK-XU316") -> Path: rst_file = options[hw_target] return cwd / rst_file +def get_table_reference(hw_target: str = "XK-EVK-XU316") -> str: + options = { + "XK-EVK-XU316": "_mic_array_mips", + "XK-EVK-XU416": "_mic_array_mips_vx4", + } + return options[hw_target] + def max_mips(lines): mips_values = [] for line in lines: @@ -46,7 +53,7 @@ def max_mips(lines): return max(mips_values) if mips_values else None -def write_rst_table(results: dict, outfile: Path): +def write_rst_table(results: dict, outfile: Path, reference: str): """ Write results dict to an RST list-table. cfg key format: '{mics}mic_{pdmrx}_{fs}fs' @@ -66,7 +73,7 @@ def write_rst_table(results: dict, outfile: Path): rows.append((mic_count, pdmrx_mode.upper(), fs_val, mips_str)) lines = [] - lines.append(".. _mic_array_mips:\n") + lines.append(f".. {reference}:\n") lines.append(".. list-table:: Estimated MIPS (per configuration)") lines.append(" :header-rows: 1") lines.append(" :widths: 6 6 8 8") @@ -121,6 +128,8 @@ def test_measure_mips(pytestconfig): # number are in the same ballpark, before overwriting mic_array_mips.json outfile = get_mips_file(hw_target) outfile_rst = get_rst_file(hw_target) + table_refence = get_table_reference(hw_target) + with outfile.open("r") as f: ref_data = json.load(f) for cfg in ref_data: @@ -139,5 +148,4 @@ def test_measure_mips(pytestconfig): json.dump(results, f, indent=2) # RST table output - rst_out = outfile_rst - write_rst_table(results, rst_out) + write_rst_table(results, outfile_rst, table_refence)