diff --git a/include/zephyr/dsp/utils.h b/include/zephyr/dsp/utils.h new file mode 100644 index 00000000000..5787e1b3fe0 --- /dev/null +++ b/include/zephyr/dsp/utils.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2024 OWL Services LLC. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file zephyr/dsp/utils.h + * + * @brief Extra functions and macros for DSP + */ + +#ifndef INCLUDE_ZEPHYR_DSP_UTILS_H_ +#define INCLUDE_ZEPHYR_DSP_UTILS_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @ingroup math_dsp + * @defgroup math_dsp_utils_shifts Float/Fixed point shift conversion functions + */ + +/** + * @ingroup math_dsp_utils_shifts + * @addtogroup math_dsp_basic_conv_to_float Fixed to Float point conversions + * + * Convert number Q7/Q15/Q31 to Float or Double representation with shift. + * + * There are separate functions for floating-point, Q7, Q15, and Q31 data types. + * @{ + */ + +/** + * @brief Convert a Q7 fixed-point value to a floating-point (float32_t) value with a left shift. + * + * @param src The input Q7 fixed-point value. + * @param m The number of bits to left shift the input value (0 to 7). + * @return The converted floating-point (float32_t) value. + */ +#define Z_SHIFT_Q7_TO_F32(src, m) ((float32_t)(((src << m)) / (float32_t)(1U << 7))) + +/** + * @brief Convert a Q15 fixed-point value to a floating-point (float32_t) value with a left shift. + * + * @param src The input Q15 fixed-point value. + * @param m The number of bits to left shift the input value (0 to 15). + * @return The converted floating-point (float32_t) value. + */ +#define Z_SHIFT_Q15_TO_F32(src, m) ((float32_t)((src << m) / (float32_t)(1U << 15))) + +/** + * @brief Convert a Q31 fixed-point value to a floating-point (float32_t) value with a left shift. + * + * @param src The input Q31 fixed-point value. + * @param m The number of bits to left shift the input value (0 to 31). + * @return The converted floating-point (float32_t) value. + */ +#define Z_SHIFT_Q31_TO_F32(src, m) ((float32_t)(((int64_t)src) << m) / (float32_t)(1U << 31)) + +/** + * @brief Convert a Q7 fixed-point value to a floating-point (float64_t) value with a left shift. + * + * @param src The input Q7 fixed-point value. + * @param m The number of bits to left shift the input value (0 to 7). + * @return The converted floating-point (float64_t) value. + */ +#define Z_SHIFT_Q7_TO_F64(src, m) (((float64_t)(src << m)) / (1U << 7)) + +/** + * @brief Convert a Q15 fixed-point value to a floating-point (float64_t) value with a left shift. + * + * @param src The input Q15 fixed-point value. + * @param m The number of bits to left shift the input value (0 to 15). + * @return The converted floating-point (float64_t) value. + */ +#define Z_SHIFT_Q15_TO_F64(src, m) (((float64_t)(src << m)) / (1UL << 15)) + +/** + * @brief Convert a Q31 fixed-point value to a floating-point (float64_t) value with a left shift. + * + * @param src The input Q31 fixed-point value. + * @param m The number of bits to left shift the input value (0 to 31). + * @return The converted floating-point (float64_t) value. + */ +#define Z_SHIFT_Q31_TO_F64(src, m) ((float64_t)(((int64_t)src) << m) / (1ULL << 31)) + +/** + * @} + */ + +/** + * @ingroup math_dsp_utils_shifts + * @addtogroup math_dsp_basic_conv_to_fixed Float to Fixed point conversions + * + * Convert number representation in Float or Double to Q31/Q15/Q7. + * + * There are separate functions for floating-point, Q7, Q15, and Q31 data types. + * @{ + */ + +/** + * @brief Convert a floating-point (float32_t) value to a Q7 fixed-point value with a right shift. + * + * @param src The input floating-point (float32_t) value. + * @param m The number of bits to right shift the input value (0 to 7). + * @return The converted Q7 fixed-point value. + */ +#define Z_SHIFT_F32_TO_Q7(src, m) \ + ((q7_t)Z_CLAMP((int32_t)(src * (1U << 7)) >> m, INT8_MIN, INT8_MAX)) + +/** + * @brief Convert a floating-point (float32_t) value to a Q15 fixed-point value with a right shift. + * + * @param src The input floating-point (float32_t) value. + * @param m The number of bits to right shift the input value (0 to 15). + * @return The converted Q15 fixed-point value. + */ +#define Z_SHIFT_F32_TO_Q15(src, m) \ + ((q15_t)Z_CLAMP((int32_t)(src * (1U << 15)) >> m, INT16_MIN, INT16_MAX)) + +/** + * @brief Convert a floating-point (float32_t) value to a Q31 fixed-point value with a right shift. + * + * @param src The input floating-point (float32_t) value. + * @param m The number of bits to right shift the input value (0 to 31). + * @return The converted Q31 fixed-point value. + */ +#define Z_SHIFT_F32_TO_Q31(src, m) \ + ((q31_t)Z_CLAMP((int64_t)(src * (1U << 31)) >> m, INT32_MIN, INT32_MAX)) + +/** + * @brief Convert a floating-point (float64_t) value to a Q7 fixed-point value with a right shift. + * + * @param src The input floating-point (float64_t) value. + * @param m The number of bits to right shift the input value (0 to 7). + * @return The converted Q7 fixed-point value. + */ +#define Z_SHIFT_F64_TO_Q7(src, m) \ + ((q7_t)Z_CLAMP((int32_t)(src * (1U << 7)) >> m, INT8_MIN, INT8_MAX)) + +/** + * @brief Convert a floating-point (float64_t) value to a Q15 fixed-point value with a right shift. + * + * @param src The input floating-point (float64_t) value. + * @param m The number of bits to right shift the input value (0 to 15). + * @return The converted Q15 fixed-point value. + */ +#define Z_SHIFT_F64_TO_Q15(src, m) \ + ((q15_t)Z_CLAMP((int32_t)(src * (1U << 15)) >> m, INT16_MIN, INT16_MAX)) + +/** + * @brief Convert a floating-point (float64_t) value to a Q31 fixed-point value with a right shift. + * + * @param src The input floating-point (float64_t) value. + * @param m The number of bits to right shift the input value (0 to 31). + * @return The converted Q31 fixed-point value. + */ +#define Z_SHIFT_F64_TO_Q31(src, m) \ + ((q31_t)Z_CLAMP((int64_t)(src * (1U << 31)) >> m, INT32_MIN, INT32_MAX)) + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* INCLUDE_ZEPHYR_DSP_UTILS_H_ */ diff --git a/tests/subsys/dsp/utils/CMakeLists.txt b/tests/subsys/dsp/utils/CMakeLists.txt new file mode 100644 index 00000000000..fde5a545654 --- /dev/null +++ b/tests/subsys/dsp/utils/CMakeLists.txt @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(dsp_conversions) + +target_sources(app PRIVATE + src/q7.c + src/q15.c + src/q31.c + src/f32.c + src/f64.c + ) + +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/tests/lib/cmsis_dsp) diff --git a/tests/subsys/dsp/utils/prj.conf b/tests/subsys/dsp/utils/prj.conf new file mode 100644 index 00000000000..0e172417a3f --- /dev/null +++ b/tests/subsys/dsp/utils/prj.conf @@ -0,0 +1,6 @@ +CONFIG_ZTEST=y +CONFIG_DSP=y +CONFIG_DSP_BACKEND_CMSIS=y +CONFIG_CMSIS_DSP=y +CONFIG_CMSIS_DSP_SUPPORT=y +CONFIG_REQUIRES_FLOAT_PRINTF=y diff --git a/tests/subsys/dsp/utils/src/f32.c b/tests/subsys/dsp/utils/src/f32.c new file mode 100644 index 00000000000..3d03dfea2c8 --- /dev/null +++ b/tests/subsys/dsp/utils/src/f32.c @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 OWL Services LLC. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +#include "common/test_common.h" + +#define DEFINE_MULTIPLE_TEST_CASES(constructor, variant, array) \ + FOR_EACH_IDX_FIXED_ARG(constructor, (), variant, array) + +#define RE_DEFINE_TEST_VARIANT3(...) DEFINE_TEST_VARIANT3(__VA_ARGS__) + +#define DEFINE_SHIFT_F32_CASE(n, args, func) \ + RE_DEFINE_TEST_VARIANT3(shift_f32, func, n, __DEBRACKET args) + +#define TEST_CASES_SHIFT_F32_TO_Q7 (-1.0F, 0, -128), (1.0F, 0, 127), (1.0F, 7, 1), (-1.0F, 7, -1) + +#define TEST_CASES_SHIFT_F32_TO_Q15 \ + (-1.0F, 0, -32768), (1.0F, 0, 32767), (1.0F, 15, 1), (-1.0F, 15, -1) + +#define TEST_CASES_SHIFT_F32_TO_Q31 \ + (-1.0F, 0, -2147483648), (1.0F, 0, 2147483647), (1.0F, 31, 1), (-1.0F, 31, -1) + +static void test_shift_f32_to_q7(const float32_t data, const uint32_t shift, + const DSP_DATA q31_t expected) +{ + q7_t shifted_data = Z_SHIFT_F32_TO_Q7(data, shift); + + zassert_equal(shifted_data, expected, + "Conversion failed: %f shifted by %d = %d (expected %d)", (double)data, shift, + shifted_data, expected); +} + +static void test_shift_f32_to_q15(const float32_t data, const uint32_t shift, + const DSP_DATA q31_t expected) +{ + q15_t shifted_data = Z_SHIFT_F32_TO_Q15(data, shift); + + zassert_equal(shifted_data, expected, + "Conversion failed: %f shifted by %d = %d (expected %d)", (double)data, shift, + shifted_data, expected); +} + +static void test_shift_f32_to_q31(const float32_t data, const uint32_t shift, + const DSP_DATA q31_t expected) +{ + q31_t shifted_data = Z_SHIFT_F32_TO_Q31(data, shift); + + zassert_equal(shifted_data, expected, + "Conversion failed: %f shifted by %d = %d (expected %d)", (double)data, shift, + shifted_data, expected); +} + +DEFINE_MULTIPLE_TEST_CASES(DEFINE_SHIFT_F32_CASE, shift_f32_to_q7, TEST_CASES_SHIFT_F32_TO_Q7) +DEFINE_MULTIPLE_TEST_CASES(DEFINE_SHIFT_F32_CASE, shift_f32_to_q15, TEST_CASES_SHIFT_F32_TO_Q15) +DEFINE_MULTIPLE_TEST_CASES(DEFINE_SHIFT_F32_CASE, shift_f32_to_q31, TEST_CASES_SHIFT_F32_TO_Q31) + +ZTEST_SUITE(shift_f32, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/subsys/dsp/utils/src/f64.c b/tests/subsys/dsp/utils/src/f64.c new file mode 100644 index 00000000000..39c2a9772b4 --- /dev/null +++ b/tests/subsys/dsp/utils/src/f64.c @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 OWL Services LLC. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +#include "common/test_common.h" + +#define DEFINE_MULTIPLE_TEST_CASES(constructor, variant, array) \ + FOR_EACH_IDX_FIXED_ARG(constructor, (), variant, array) + +#define RE_DEFINE_TEST_VARIANT3(...) DEFINE_TEST_VARIANT3(__VA_ARGS__) + +#define DEFINE_SHIFT_F64_CASE(n, args, func) \ + RE_DEFINE_TEST_VARIANT3(shift_f64, func, n, __DEBRACKET args) + +#define TEST_CASES_SHIFT_F64_TO_Q7 (-1.0, 0, -128), (1.0, 0, 127), (1.0, 7, 1), (-1.0, 7, -1) + +#define TEST_CASES_SHIFT_F64_TO_Q15 (-1.0, 0, -32768), (1.0, 0, 32767), (1.0, 15, 1), (-1.0, 15, -1) + +#define TEST_CASES_SHIFT_F64_TO_Q31 \ + (-1.0, 0, INT32_MIN), (1.0, 0, INT32_MAX), (1.0, 31, 1), (-1.0, 31, -1) + +static void test_shift_f64_to_q7(const float64_t data, const uint32_t shift, + const DSP_DATA q7_t expected) +{ + const q7_t shifted_data = Z_SHIFT_F64_TO_Q7(data, shift); + + zassert_equal(shifted_data, expected, + "Conversion failed: %f shifted by %d = %d (expected %d)", data, shift, + shifted_data, expected); +} + +static void test_shift_f64_to_q15(const float64_t data, const uint32_t shift, + const DSP_DATA q15_t expected) +{ + const q15_t shifted_data = Z_SHIFT_F64_TO_Q15(data, shift); + + zassert_equal(shifted_data, expected, + "Conversion failed: %f shifted by %d = %d (expected %d)", (double)data, shift, + shifted_data, expected); +} + +static void test_shift_f64_to_q31(const float64_t data, const uint32_t shift, + const DSP_DATA q31_t expected) +{ + const q31_t shifted_data = Z_SHIFT_F64_TO_Q31(data, shift); + + zassert_equal(shifted_data, expected, + "Conversion failed: %f shifted by %d = %d (expected %d)", (double)data, shift, + shifted_data, expected); +} + +DEFINE_MULTIPLE_TEST_CASES(DEFINE_SHIFT_F64_CASE, shift_f64_to_q7, TEST_CASES_SHIFT_F64_TO_Q7) +DEFINE_MULTIPLE_TEST_CASES(DEFINE_SHIFT_F64_CASE, shift_f64_to_q15, TEST_CASES_SHIFT_F64_TO_Q15) +DEFINE_MULTIPLE_TEST_CASES(DEFINE_SHIFT_F64_CASE, shift_f64_to_q31, TEST_CASES_SHIFT_F64_TO_Q31) + +ZTEST_SUITE(shift_f64, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/subsys/dsp/utils/src/q15.c b/tests/subsys/dsp/utils/src/q15.c new file mode 100644 index 00000000000..46f44d725a3 --- /dev/null +++ b/tests/subsys/dsp/utils/src/q15.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 OWL Services LLC. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +#include "common/test_common.h" + +#define DEFINE_MULTIPLE_TEST_CASES(constructor, variant, array) \ + FOR_EACH_IDX_FIXED_ARG(constructor, (), variant, array) + +#define RE_DEFINE_TEST_VARIANT3(...) DEFINE_TEST_VARIANT3(__VA_ARGS__) + +#define DEFINE_SHIFT_Q15_CASE(n, args, func) \ + RE_DEFINE_TEST_VARIANT3(shift_q15, func, n, __DEBRACKET args) + +#define TEST_CORNER_CASES_SHIFT_Q15_TO_F32 \ + (-32768, 0, -1.0F), (32767, 0, 0.999969482421875F), (32767, 15, 32767.0F), \ + (-32768, 15, -32768.0F) + +#define TEST_CORNER_CASES_SHIFT_Q15_TO_F64 \ + (-32768, 0, -1), (32767, 0, 0.999969482421875), (32767, 15, 32767.0), (-32768, 15, -32768.0) + +static void test_shift_q15_to_f32(const q15_t data, const uint32_t shift, const float32_t expected) +{ + const float32_t shifted_data = Z_SHIFT_Q15_TO_F32(data, shift); + + zassert_equal(shifted_data, expected, + "Conversion failed: 0x%08x shifted by %d = %f (expected %f)", data, shift, + (double)shifted_data, (double)expected); +} + +static void test_shift_q15_to_f64(const q15_t data, const uint32_t shift, const float64_t expected) +{ + const float64_t shifted_data = Z_SHIFT_Q15_TO_F64(data, shift); + + zassert_equal(shifted_data, expected, + "Conversion failed: 0x%08x shifted by %d = %f (expected %f)", data, shift, + shifted_data, expected); +} + +DEFINE_MULTIPLE_TEST_CASES(DEFINE_SHIFT_Q15_CASE, shift_q15_to_f32, + TEST_CORNER_CASES_SHIFT_Q15_TO_F32) +DEFINE_MULTIPLE_TEST_CASES(DEFINE_SHIFT_Q15_CASE, shift_q15_to_f64, + TEST_CORNER_CASES_SHIFT_Q15_TO_F64) + +ZTEST_SUITE(shift_q15, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/subsys/dsp/utils/src/q31.c b/tests/subsys/dsp/utils/src/q31.c new file mode 100644 index 00000000000..157c0fbdcab --- /dev/null +++ b/tests/subsys/dsp/utils/src/q31.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 OWL Services LLC. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +#include "common/test_common.h" + +#define DEFINE_MULTIPLE_TEST_CASES(constructor, variant, array) \ + FOR_EACH_IDX_FIXED_ARG(constructor, (), variant, array) + +#define RE_DEFINE_TEST_VARIANT3(...) DEFINE_TEST_VARIANT3(__VA_ARGS__) + +#define DEFINE_SHIFT_Q31_CASE(n, args, func) \ + RE_DEFINE_TEST_VARIANT3(shift_q31, func, n, __DEBRACKET args) + +#define TEST_CORNER_CASES_SHIFT_Q31_TO_F32 \ + (2147483647, 0, 1.0F), (-2147483648, 0, -1.0F), (-2147483648, 31, -2147483648.0F), \ + (2147483647, 31, 2147483647.0F) + +#define TEST_CORNER_CASES_SHIFT_Q31_TO_F64 \ + (2147483647, 0, 0.9999999995343387), (-2147483648, 0, -1.0), \ + (-2147483648, 31, -2147483648.0), (2147483647, 31, 2147483647.0) + +static void test_shift_q31_to_f32(const q31_t data, const uint32_t shift, const float32_t expected) +{ + const float32_t shifted_data = Z_SHIFT_Q31_TO_F32(data, shift); + + zassert_equal(shifted_data, expected, + "Conversion failed: 0x%08x shifted by %d = %f (expected %f)", data, shift, + (double)shifted_data, (double)expected); +} + +static void test_shift_q31_to_f64(const q31_t data, const uint32_t shift, const float64_t expected) +{ + const float64_t shifted_data = Z_SHIFT_Q31_TO_F64(data, shift); + + zassert_equal(shifted_data, expected, + "Conversion failed: 0x%08x shifted by %d = %f (expected %f)", data, shift, + shifted_data, expected); +} + +DEFINE_MULTIPLE_TEST_CASES(DEFINE_SHIFT_Q31_CASE, shift_q31_to_f32, + TEST_CORNER_CASES_SHIFT_Q31_TO_F32) +DEFINE_MULTIPLE_TEST_CASES(DEFINE_SHIFT_Q31_CASE, shift_q31_to_f64, + TEST_CORNER_CASES_SHIFT_Q31_TO_F64) + +ZTEST_SUITE(shift_q31, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/subsys/dsp/utils/src/q7.c b/tests/subsys/dsp/utils/src/q7.c new file mode 100644 index 00000000000..0186eac2741 --- /dev/null +++ b/tests/subsys/dsp/utils/src/q7.c @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 OWL Services LLC. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +#include "common/test_common.h" + +#define DEFINE_MULTIPLE_TEST_CASES(constructor, variant, array) \ + FOR_EACH_IDX_FIXED_ARG(constructor, (), variant, array) + +#define RE_DEFINE_TEST_VARIANT3(...) DEFINE_TEST_VARIANT3(__VA_ARGS__) + +#define DEFINE_SHIFT_Q7_CASE(n, args, func) \ + RE_DEFINE_TEST_VARIANT3(shift_q7, func, n, __DEBRACKET args) + +#define TEST_CORNER_CASES_SHIFT_Q7_TO_F32 \ + (-128, 0, -1.0F), (127, 0, 0.9921875F), (127, 7, 127.0F), (-128, 7, -128.0F) + +#define TEST_CORNER_CASES_SHIFT_Q7_TO_F64 \ + (-128, 0, -1.0), (127, 0, 0.9921875), (127, 7, 127.0), (-128, 7, -128.0) + +static void test_shift_q7_to_f32(const q7_t data, const uint32_t shift, const float32_t expected) +{ + const float32_t shifted_data = Z_SHIFT_Q7_TO_F32(data, shift); + + zassert_equal(shifted_data, expected, + "Conversion failed: 0x%08x shifted by %d = %f (expected %f)", data, shift, + (double)shifted_data, (double)expected); +} + +static void test_shift_q7_to_f64(const q7_t data, const uint32_t shift, const float64_t expected) +{ + const float64_t shifted_data = Z_SHIFT_Q7_TO_F64(data, shift); + + zassert_equal(shifted_data, expected, + "Conversion failed: 0x%08x shifted by %d = %f (expected %f)", data, shift, + shifted_data, expected); +} + +DEFINE_MULTIPLE_TEST_CASES(DEFINE_SHIFT_Q7_CASE, shift_q7_to_f32, TEST_CORNER_CASES_SHIFT_Q7_TO_F32) +DEFINE_MULTIPLE_TEST_CASES(DEFINE_SHIFT_Q7_CASE, shift_q7_to_f64, TEST_CORNER_CASES_SHIFT_Q7_TO_F64) + +ZTEST_SUITE(shift_q7, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/subsys/dsp/utils/testcase.yaml b/tests/subsys/dsp/utils/testcase.yaml new file mode 100644 index 00000000000..eea746a733f --- /dev/null +++ b/tests/subsys/dsp/utils/testcase.yaml @@ -0,0 +1,6 @@ +tests: + zdsp.utils: + integration_platforms: + - native_sim + tags: + - zdsp