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..de4ed6e4 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) } } } @@ -97,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 @@ -106,7 +112,7 @@ pipeline { dir("tests") { createVenv(reqFile: "requirements.txt") withVenv { - xcoreBuild() + xcoreBuild(toolsVersion: params.TOOLS_XS3_VERSION, jobs:31) stash includes: '**/*.xe', name: 'test_bin', useDefaultExcludes: false } } @@ -124,7 +130,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 +142,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 +163,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 +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} " + sh "pytest -v --junitxml=pytest_basic_mic.xml --seed 12345 --level ${params.TEST_LEVEL}" } else { @@ -209,11 +213,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 "0isr or OneStageFilter" -v'} + } + } // withVenv + }}} // stage('Run tests') + } // stages + post { + cleanup {xcoreCleanSandbox()} + } //post + } // VX4 Tests + } // parallel } // stage('Tests') diff --git a/doc/exclude_patterns.inc b/doc/exclude_patterns.inc index 3a948a03..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/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/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..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:`TwoStageDecimator `, 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: @@ -24,8 +23,8 @@ 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 `, -both the first and second stage filters are implemented +Note that in :cpp:class:`Decimator `, +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 @@ -68,24 +67,9 @@ 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 - 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:`TwoStageDecimator ` 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 a994420f..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:`TwoStageDecimator `, +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. @@ -162,7 +167,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: @@ -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/examples.rst b/doc/rst/src/examples.rst index e5b81019..dab59996 100644 --- a/doc/rst/src/examples.rst +++ b/doc/rst/src/examples.rst @@ -158,3 +158,27 @@ 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 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. + +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/getting_started.rst b/doc/rst/src/getting_started.rst index 0c80f4b1..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 @@ -307,7 +312,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/mic_switching.rst b/doc/rst/src/mic_switching.rst new file mode 100644 index 00000000..f213c254 --- /dev/null +++ b/doc/rst/src/mic_switching.rst @@ -0,0 +1,48 @@ +.. _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`. + +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/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/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/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/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/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 784d8579..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 @@ -189,12 +184,12 @@ 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 -decimator implementation that uses a pair of decimating FIR filters to -accomplish this. +:cpp:class:`Decimator ` class is a +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/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_1mic_override/CMakeLists.txt b/examples/app_1mic_override/CMakeLists.txt new file mode 100644 index 00000000..8a5e31a5 --- /dev/null +++ b/examples/app_1mic_override/CMakeLists.txt @@ -0,0 +1,20 @@ +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(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) + +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; +} +} 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..1643c93a --- /dev/null +++ b/examples/app_mic_array_basic/src/app.c @@ -0,0 +1,149 @@ +// 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; +} + +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::I template void par_mic_array::MyTwoStageDecimator - ::ProcessBlock( + ::ProcessBlockTwoStage( int32_t sample_out[MIC_COUNT], uint32_t pdm_block[BLOCK_SIZE]) { diff --git a/examples/app_par_decimator/src/decimator_subtask.c b/examples/app_par_decimator/src/decimator_subtask.c index 3e0ee8c7..f54e23cd 100644 --- a/examples/app_par_decimator/src/decimator_subtask.c +++ b/examples/app_par_decimator/src/decimator_subtask.c @@ -119,5 +119,12 @@ void 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/Decimator.hpp b/lib_mic_array/api/mic_array/cpp/Decimator.hpp index 06003b59..7e785113 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: @@ -63,8 +64,16 @@ class TwoStageDecimator * Per-mic channel filter state (PDM history) size in 32-bit words for stage-1 filter. */ unsigned pdm_history_sz; + + /** + * Per-mic, 32-bit PDM output words from the PDM RX stage. + */ + unsigned pdm_out_words_per_mic; } stage1; + public: + constexpr Decimator() noexcept { } + /** * Stage 2 decimation configuration and state. */ @@ -79,26 +88,35 @@ class TwoStageDecimator unsigned decimation_factor; } stage2; - public: - - constexpr TwoStageDecimator() noexcept { } + /** + * 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 two-stage decimator from a configuration struct + * @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); + 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. @@ -123,9 +141,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 one block of PDM data through only the 1st stage decimation filter. + * + * 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 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; + }; } @@ -134,26 +185,40 @@ class TwoStageDecimator ////////////////////////////////////////////// template -void mic_array::TwoStageDecimator::Init( - mic_array_decimator_conf_t &decimator_conf) +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; + 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; + } + + 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; } - this->stage2.decimation_factor = decimator_conf.filter_conf[1].decimation_factor; } template -void mic_array::TwoStageDecimator - ::ProcessBlock( +void mic_array::Decimator + ::ProcessBlockTwoStage( int32_t sample_out[MIC_COUNT], uint32_t *pdm_block) { @@ -174,6 +239,61 @@ 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::Decimator + ::ProcessBlockSingleStage( + int32_t *sample_out, + uint32_t *pdm_block) +{ + // 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); + } + } +} static inline void mic_array::shift_buffer(uint32_t* buff) @@ -181,5 +301,12 @@ void mic_array::shift_buffer(uint32_t* buff) #if defined(__XS3A__) uint32_t* src = &buff[-1]; asm volatile("vldd %0[0]; vstd %1[0];" :: "r"(src), "r"(buff) : "memory" ); - #endif // __XS3A__ + #elif defined(__VX4B__) + uint32_t* src = &buff[-1]; + asm volatile("xm.vldd %0; xm.vstd %1;" :: "r"(src), "r"(buff) : "memory" ); + #else // C fallback + for (unsigned k = 7; k > 0; k--) { + buff[k] = buff[k-1]; + } + #endif } diff --git a/lib_mic_array/api/mic_array/cpp/MicArray.hpp b/lib_mic_array/api/mic_array/cpp/MicArray.hpp index f9bf9dbf..d722853f 100644 --- a/lib_mic_array/api/mic_array/cpp/MicArray.hpp +++ b/lib_mic_array/api/mic_array/cpp/MicArray.hpp @@ -5,14 +5,14 @@ #include #include -#include +#include #include +#include #include #include #include "PdmRx.hpp" #include "Decimator.hpp" -#include "ThreeStageDecimator.hpp" #include "SampleFilter.hpp" #include "OutputHandler.hpp" @@ -49,6 +49,11 @@ namespace mic_array { class MicArray { + private: + void ThreadEntryOneStage(); + void ThreadEntryTwoStage(); + void ThreadEntryThreeStage(); + public: /** * @brief The PDM rx service. @@ -78,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; @@ -177,6 +194,18 @@ namespace mic_array { * OutputHandler. */ void ThreadEntry(); + + /** + * @brief Maximum supported value for PDM RX output words per channel. Only relevant for single-stage decimator mode. + * + * 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. + */ + static constexpr unsigned MAX_PDM_OUT_WORDS_PER_CHANNEL = 10; }; } @@ -184,7 +213,34 @@ 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; +} // ThreadEntryOneStage + +// MicArray::ThreadEntryTwoStage() - Do not remove. Documentation anchor for literalinclude in software_structure.rst 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); } @@ -207,4 +263,51 @@ void mic_array::MicArray +void mic_array::MicArray::ThreadEntryThreeStage() +{ + int32_t sample_out[MIC_COUNT] = {0}; + volatile bool shutdown = false; + + while(!shutdown){ + 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; + +} // ThreadEntryThreeStage + + +// MicArray::ThreadEntry() - Do not remove. Documentation anchor for literalinclude in software_structure.rst +template +void mic_array::MicArray::ThreadEntry() +{ + if(Decimator.num_stages == 1) { + ThreadEntryOneStage(); + } + else if(Decimator.num_stages == 2) { + ThreadEntryTwoStage(); + } + else { + ThreadEntryThreeStage(); + } +} // ThreadEntry diff --git a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp index 938b1968..8c77ab3e 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,7 +326,6 @@ 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; /** @@ -350,21 +350,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. * @@ -465,8 +450,9 @@ namespace mic_array { * every iteration. */ void ThreadEntry(); - }; + uint32_t pdm_out_words_per_channel; // number of 32-sample subblocks per channel + }; } ////////////////////////////////////////////// @@ -488,7 +474,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 +482,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,22 +494,6 @@ 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) @@ -595,14 +565,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); @@ -611,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 < 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]; } @@ -641,7 +612,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 deleted file mode 100644 index 57cc436e..00000000 --- a/lib_mic_array/api/mic_array/cpp/ThreeStageDecimator.hpp +++ /dev/null @@ -1,199 +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; - } 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); - - /** - * @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) -{ - 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; - - 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/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_task.h b/lib_mic_array/api/mic_array/mic_array_task.h index 8b64e171..e79d1443 100644 --- a/lib_mic_array/api/mic_array/mic_array_task.h +++ b/lib_mic_array/api/mic_array/mic_array_task.h @@ -73,5 +73,19 @@ void mic_array_init_custom_filter(pdm_rx_resources_t* pdm_res, mic_array_conf_t* 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/api/mic_array/setup.h b/lib_mic_array/api/mic_array/setup.h index 479d2f2f..64746883 100644 --- a/lib_mic_array/api/mic_array/setup.h +++ b/lib_mic_array/api/mic_array/setup.h @@ -122,4 +122,4 @@ unsigned mic_array_mclk_divider( #include "mic_array/impl/setup_impl.h" -C_API_END \ No newline at end of file +C_API_END diff --git a/lib_mic_array/lib_build_info.cmake b/lib_mic_array/lib_build_info.cmake index b4c7e5ba..9a5cd3f2 100644 --- a/lib_mic_array/lib_build_info.cmake +++ b/lib_mic_array/lib_build_info.cmake @@ -1,6 +1,6 @@ set(LIB_NAME lib_mic_array) set(LIB_VERSION 6.0.0) -set(LIB_DEPENDENT_MODULES "lib_xcore_math(2.4.0)") +set(LIB_DEPENDENT_MODULES "lib_xcore_math(develop)") #TODO pin version set(LIB_INCLUDES api api/mic_array diff --git a/lib_mic_array/src/deinterleave16.S b/lib_mic_array/src/deinterleave16.S index efebb43e..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/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..3364ab31 --- /dev/null +++ b/lib_mic_array/src/mic_array_task.c @@ -0,0 +1,83 @@ +// 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_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; + 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)); + + init_mic_array_storage(); + init_mics_default_filter(pdm_res, channel_map, stg2_decimation_factor); + + 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(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) || + (mic_array_conf->decimator_conf.num_filter_stages == 2) || + (mic_array_conf->decimator_conf.num_filter_stages == 3)); + + 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; + 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(); +} + +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); + 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..3630c021 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,46 @@ #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 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 g_mics back to nullptr. +// then sets s_mics back to nullptr. + +#if !defined (__XS2A__) -#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_1mic_override(void) { - assert(g_mics == nullptr); // Mic array instance already initialised - - use_3_stg_decimator = false; - - 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); + // 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, - 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) +{ + 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); @@ -54,69 +58,97 @@ 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_default_filter(pdm_rx_resources_t* pdm_res, const unsigned* channel_map, unsigned stg2_dec_factor) { - 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); + static int32_t stg1_filter_state[MIC_ARRAY_CONFIG_MIC_COUNT][8]; + 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 + 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; + 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 = mic_array_conf.decimator_conf.filter_conf[1].num_taps; + filter_conf[1].state = stage_2_state_memory(stg2_dec_factor); + + 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; + + 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 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; + else { + init_from_conf(s_mics, 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 init_mics_custom_filter(pdm_rx_resources_t* pdm_res, mic_array_conf_t* mic_array_conf) +{ + 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); + } +} ///////////////////// // 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(); -} - -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 assert_mic_array_start_ready(void) { + if(s_1mic_override_active) { + assert(s_mics_1mic_override != nullptr); + } + else + { + assert(s_mics != nullptr); + } } -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(); +void set_output_channel(chanend_t c_frames_out) +{ + if(s_1mic_override_active) { + s_mics_1mic_override->OutputHandler.FrameTx.SetChannel(c_frames_out); + } + else { + s_mics->OutputHandler.FrameTx.SetChannel(c_frames_out); + } } #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 +156,72 @@ 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_1mic_override_active) { + start_mics_with_pdm_isr(s_mics_1mic_override, c_frames_out); } else { - start_mics_with_pdm_isr(g_mics, c_frames_out); + start_mics_with_pdm_isr(s_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))); +#endif +} + +// Helper functions for starting separate tasks +void start_pdm_task(void) +{ + if(s_1mic_override_active) { + s_mics_1mic_override->PdmRx.ThreadEntry(); } - 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))); + else { + s_mics->PdmRx.ThreadEntry(); } -#endif - // shutdown - if (use_3_stg_decimator) { - g_mics_3stg->~TMicArray_3stg_decimator(); - g_mics_3stg = nullptr; +} + +void start_decimator_task() +{ + if(s_1mic_override_active) { + s_mics_1mic_override->ThreadEntry(); + } + else { + s_mics->ThreadEntry(); + } +} + +//////////////////////// +// Mic array shutdown // +//////////////////////// + +void shutdown_mic_array(void) +{ + if (s_1mic_override_active) { + s_mics_1mic_override->~TMicArray1MicOverride(); } else { - g_mics->~TMicArray(); - g_mics = nullptr; + 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 (use_3_stg_decimator) { - assert(g_mics_3stg != nullptr); - g_mics_3stg->PdmRx.SetPort((port_t)c_pdm); - } else { - assert(g_mics != nullptr); - g_mics->PdmRx.SetPort((port_t)c_pdm); + if(s_1mic_override_active) { + s_mics_1mic_override->PdmRx.SetPort((port_t)c_pdm); + } + else { + 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..7dc34bbf 100644 --- a/lib_mic_array/src/mic_array_task_internal.hpp +++ b/lib_mic_array/src/mic_array_task_internal.hpp @@ -6,8 +6,9 @@ #include "mic_array.h" #include "mic_array/etc/filters_default.h" +#ifdef __cplusplus using TMicArray = mic_array::MicArray, + mic_array::Decimator, mic_array::StandardPdmRxService, // std::conditional uses USE_DCOE to determine which @@ -19,23 +20,15 @@ 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 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 +47,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 +82,34 @@ 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 +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); + +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_mic_array_pdm_isr(chanend_t c_frames_out); + +MA_C_API +void start_pdm_task(void); + +MA_C_API +void init_1mic_override(void); + +MA_C_API +void assert_mic_array_start_ready(void); 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__ 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/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): 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/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/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..39f596e0 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") @@ -19,18 +37,27 @@ 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") + 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) @@ -81,30 +108,48 @@ 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} -O2 - -g - -report - -mcmodel=large - -Wno-xcore-fptrgroup - -Wno-unknown-pragmas - -Wno-format - -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 + ) XMOS_REGISTER_APP() 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 00000000..54a52734 Binary files /dev/null and b/tests/signal/BasicMicArray/small_768k_to_12k_filter_int.pkl differ diff --git a/tests/signal/BasicMicArray/src/app.c b/tests/signal/BasicMicArray/src/app.c index f1bae818..093cc6d4 100644 --- a/tests/signal/BasicMicArray/src/app.c +++ b/tests/signal/BasicMicArray/src/app.c @@ -16,12 +16,46 @@ #include #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 -#define BUFF_SIZE (256) +#if APP_CONFIG_ONE_STAGE_DECIMATOR +#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; @@ -32,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; @@ -54,9 +89,16 @@ 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_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->stg1_coef_ptr = small_768k_to_12k_filter_stg1_coef; + cfg->num_stages = 1; +#elif !USE_CUSTOM_FILTER + cfg->num_stages = 2; cfg->stg1_tap_count = 256; cfg->stg1_decimation_factor = 32; @@ -86,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; @@ -94,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; @@ -129,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 @@ -180,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; } } @@ -194,7 +240,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); } @@ -223,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 @@ -246,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; @@ -257,8 +303,8 @@ 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; @@ -266,6 +312,36 @@ static void init_mic_conf(mic_array_conf_t *mic_array_conf, mic_array_filter_con } #endif +#if APP_CONFIG_ONE_STAGE_DECIMATOR +static +void init_mic_conf_one_stage_filter( + mic_array_conf_t *mic_array_conf, + mic_array_filter_conf_t *filter_conf, + unsigned *channel_map) +{ + static int32_t stg1_filter_state[APP_MIC_COUNT][8]; + 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[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; +} +#endif // ------------------------------- THREADS ------------------------------- @@ -273,14 +349,26 @@ void app_mic( chanend_t c_pdm_in, chanend_t c_frames_out) //non-streaming { -#if !USE_CUSTOM_FILTER - mic_array_init(&pdm_res, NULL, APP_SAMP_FREQ); -#else +#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]; +#if APP_CONFIG_ONE_STAGE_DECIMATOR + init_mic_conf_one_stage_filter(&mic_array_conf, filter_conf, NULL); +#else 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 @@ -291,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]; @@ -303,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); @@ -315,11 +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++); @@ -342,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]); } } @@ -382,7 +469,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..29ccc17f --- /dev/null +++ b/tests/signal/BasicMicArray/src/small_768k_to_12k_filter.h @@ -0,0 +1,38 @@ +// 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 ../tests/signal/BasicMicArray/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 NUM_DECIMATION_STAGES (1) + +#endif diff --git a/tests/signal/BasicMicArray/test_mic_array.py b/tests/signal/BasicMicArray/test_mic_array.py index 9e2a4fd9..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) @@ -126,3 +154,85 @@ 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("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") + + 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") + decimator_stgs = 1 + # --- num decimator stages dependent behaviour --- + stg1_only = (decimator_stgs == 1) + 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(chans, samp_total) + + expected = filter.Filter(sig.signal, stg1_only=stg1_only) + + if self.print_output: + print(f"Expected output: {expected}") + + 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}" + + with MicArrayDevice( + xe_path, + quiet_xgdb=not self.print_xgdb, + extra_xrun_args="--id 0" + ) as dev: + + assert dev.param["channels"] == chans + assert dev.param["s1.dec_factor"] == filter.s1.DecimationFactor + assert dev.param["s1.tap_count"] == filter.s1.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 + + 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..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"] } diff --git a/tests/signal/BasicMicArray/test_thdn.py b/tests/signal/BasicMicArray/test_thdn.py index fae62912..9d5038b6 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}") @@ -146,3 +146,118 @@ 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_OneStageFilter_uncollect(config, platform, decimator_stgs): + 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_OneStageFilter_uncollect) + @pytest.mark.parametrize("platform", ["python_only", "python_xcore"]) + @pytest.mark.parametrize("decimator_stgs", [1], ids=["1stg"]) + 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 = 4 + pdm_freq = 768_000 + 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") + + # --- 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"{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 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( + freq_hz, + [0.52]*len(freq_hz), + 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}") + + expected_output_float = self.to_float_array(expected) + + 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"] == chans + assert dev.param["s1.dec_factor"] == filter.s1.DecimationFactor + assert dev.param["s1.tap_count"] == filter.s1.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) + + 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}") + + 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 62% rename from tests/signal/TwoStageDecimator/src/app.cpp rename to tests/signal/TwoStageDecimator/src/run.cpp index 2b2690d5..53f95422 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) { @@ -69,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]; @@ -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); @@ -106,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]); } @@ -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/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/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..26f6cbfc 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" @@ -36,7 +36,6 @@ void app_pdm_rx_isr_setup( my_pdm_rx.AssertOnDroppedBlock(false); my_pdm_rx.InstallISR(); my_pdm_rx.UnmaskISR(); - } void test() @@ -90,7 +89,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 +108,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..ab6e287f 100644 --- a/tests/signal/profile/app_memory/src/app.cpp +++ b/tests/signal/profile/app_memory/src/app.cpp @@ -50,11 +50,15 @@ 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, + mic_array::Decimator, mic_array::StandardPdmRxService, typename std::conditional -#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..eadf0f58 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) @@ -104,7 +106,7 @@ void init_mic_conf( 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..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": 12572, + "used": 13308, "status": "OKAY", - "stack": 572, - "code": 8070, - "data": 3930 + "stack": 588, + "code": 8694, + "data": 4026 }, "1mic_default": { "available": 524288, - "used": 16580, + "used": 17236, "status": "OKAY", - "stack": 636, - "code": 9058, - "data": 6886 + "stack": 652, + "code": 9578, + "data": 7006 }, "2mic_custom": { "available": 524288, - "used": 13964, + "used": 14892, "status": "OKAY", - "stack": 580, - "code": 8278, - "data": 5106 + "stack": 604, + "code": 9078, + "data": 5210 }, "2mic_default": { "available": 524288, - "used": 18084, + "used": 20508, "status": "OKAY", - "stack": 636, - "code": 9378, - "data": 8070 + "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 dd73fb18..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 - - 12572 - - 572 - - 8070 - - 3930 + - 13308 + - 588 + - 8694 + - 4026 * - 1mic_default - 524288 - - 16580 - - 636 - - 9058 - - 6886 + - 17236 + - 652 + - 9578 + - 7006 * - 2mic_custom - 524288 - - 13964 - - 580 - - 8278 - - 5106 + - 14892 + - 604 + - 9078 + - 5210 * - 2mic_default - 524288 - - 18084 - - 636 - - 9378 - - 8070 \ 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.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..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 - - 14.146 + - 13.810 * - 1 - ISR - 32000 - - 17.234 + - 16.849 * - 1 - ISR - 48000 - - 21.305 + - 20.873 * - 1 - THREAD - 16000 - - 12.930 + - 12.514 * - 1 - THREAD - 32000 - - 15.954 + - 15.409 * - 1 - THREAD - 48000 - - 19.961 + - 19.290 * - 2 - ISR - 16000 - - 29.310 + - 28.685 * - 2 - ISR - 32000 - - 34.621 + - 34.013 * - 2 - ISR - 48000 - - 41.934 + - 41.358 * - 2 - THREAD - 16000 - - 27.006 + - 26.142 * - 2 - THREAD - 32000 - - 32.285 + - 31.421 * - 2 - THREAD - 48000 - - 39.533 \ No newline at end of file + - 38.670 \ 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..f3183261 --- /dev/null +++ b/tests/signal/profile/mic_array_mips_xs3.json @@ -0,0 +1,14 @@ +{ + "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 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) - - 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); +}