diff --git a/include/zephyr/dsp/macros.h b/include/zephyr/dsp/macros.h new file mode 100644 index 0000000000000..34f860ddb721f --- /dev/null +++ b/include/zephyr/dsp/macros.h @@ -0,0 +1,337 @@ +/* Copyright (c) 2024 tinyVision.ai Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef INCLUDE_ZEPHYR_DSP_MACROS_H_ +#define INCLUDE_ZEPHYR_DSP_MACROS_H_ + +#include + +#include +#include + +/** + * @brief Definition of the minimum and maximum values of the internal representation + * + * @note The value represented is always between -1.0 and 1.0 for any storage size. + * Scaling variables imported before using them in fixed-points is required. + * + * @{ + */ + +/** Minimum internal value for Q.7. */ +#define MINq7 INT8_MIN + +/** Minimum internal value for Q.15. */ +#define MINq15 INT16_MIN + +/** Minimum internal value for Q.31. */ +#define MINq31 INT32_MIN + +/** Maximum internal value for Q.7. */ +#define MAXq7 INT8_MAX + +/** Maximum internal value for Q.15. */ +#define MAXq15 INT16_MAX + +/** Maximum internal value for Q.31. */ +#define MAXq31 INT32_MAX + +/* @} + */ + +/** + * @brief Clamp a fixed-point value to -1.0 to 1.0. + * + * @note Useful for internal use, or when breaking the abstraction barrier. + * + * @{ + */ + +/** Enforce the a Q.7 fixed-point to be between -1.0 and 1.0 */ +#define CLAMPq7(q7) ((q7_t)CLAMP((q7), MINq7, MAXq7)) + +/** Enforce the a Q.15 fixed-point to be between -1.0 and 1.0 */ +#define CLAMPq15(q15) ((q15_t)CLAMP((q15), MINq15, MAXq15)) + +/** Enforce the a Q.31 fixed-point to be between -1.0 and 1.0 */ +#define CLAMPq31(q31) ((q31_t)CLAMP((q31), MINq31, MAXq31)) + +/* @} + */ + +/** + * @brief Construct a fixed-point number out of a floating point number. + * + * The input float can be an expression or a single number. + * If it is below -1.0 or above 1.0, it is adjusted to fit the -1.0 to 1.0 range. + * + * @example "Q15(-1.0) // Minimum value" + * @example "Q31(1.0) // Maximum value" + * @example "Q15(1.3) // Will become 1.0" + * @example "Q7(f - 1.0)" + * @example "Q15(2.0 / 3.1415)" + * + * @param f Floating-point number to convert into a fixed-point number. + * + * @{ + */ + +/** Construct a Q.7 fixed-point: 1 bit for the sign, 7 bits of fractional part */ +#define Q7f(f) CLAMPq7((f) * (1ll << 7)) + +/** Construct a Q.15 fixed-point: 1 bit for the sign, 15 bits of fractional part */ +#define Q15f(f) CLAMPq15((f) * (1ll << 15)) + +/** Construct a Q.31 fixed-point: 1 bit for the sign, 31 bits of fractional part */ +#define Q31f(f) CLAMPq31((f) * (1ll << 31)) + +/* @} + */ + +/** + * @brief Construct a fixed-point out of an integer and a scale. + * + * This permits to work with numbers above the -1.0 to 1.0 range: + * The minimum and maximum possible value of the input number are specified. + * The input number is then scaled so that the minimum value becomes -1.0 and + * maximum value becomes 1.0. + * Once the computation is done in fixed piotn, it is popssible to get an + * unscaled value with @ref INTq7, @ref INtq15 and @rev INTq31. + * + * @example "Q15i(temperature, -20, 200)" + * @example "Q15i(angle, 0, 360)" + * @example "Q15i(voltage, V_MIN, V_MAX)" + * + * @param i Input number to convert to a fixed-point representation + * @param min Minimum value that @p i can take. + * The same minimum value is used to convert the numbers back. + * @param max Maximum value that @p i can take. + * The same maximum value is used to convert the numbers back. + * + * @{ + */ + +/** Build a Q.7 number by scaling @p i range from [min, max] (at most [ , ]) to [-1.0, 1.0] */ +#define Q7i(i, min, max) CLAMPq7(SCALE((i), (min), (max), MINq7, MAXq7)) + +/** Build a Q.15 number by scaling @p i range from [min, max] to [-1.0, 1.0] */ +#define Q15i(i, min, max) CLAMPq15(SCALE((i), (min), (max), MINq15, MAXq15)) + +/** Build a Q.31 number by scaling @p i range from [min, max] to [-1.0, 1.0] */ +#define Q31i(i, min, max) CLAMPq31(SCALE((i), (min), (max), MINq31, MAXq31)) + +/* @} + */ + +/** + * @brief Convert fixed-points from one size to another + * + * This permits to increase or decrease the precision by switching between + * smaller and larger representation. + * The values represented does not change except due to loss of precision. + * The minimum value remains -1.0 and maximum value remains 1.0. + * Only the internal representation is scaled. + * + * @{ + */ + +/** Build a Q.7 number out of a Q.15 number, losing 8 bits of precision */ +#define Q7q15(q15) (q7_t)((q15) / (1 << 8)) + +/** Build a Q.7 number out of a Q.31 number, losing 24 bits of precision */ +#define Q7q31(q31) (q7_t)((q31) / (1 << 24)) + +/** Build a Q.15 number out of a Q.7 number, gaining 8 bits of precision */ +#define Q15q7(q7) CLAMPq15((q15_t)(q7) * (1 << 8)) + +/** Build a Q.15 number out of a Q.31 number, losing 16 bits of precision */ +#define Q15q31(q31) (q15_t)((q31) / (1 << 16)) + +/** Build a Q.31 number out of a Q.7 number, gaining 24 bits of precision */ +#define Q31q7(q7) CLAMPq31((q31_t)(q7) * (1 << 24)) + +/** Build a Q.31 number out of a Q.15 number, gaining 16 bits of precision */ +#define Q31q15(q15) CLAMPq31((q31_t)(q15) * (1 << 16)) + +/* @} + */ + +/** + * @brief Convert a fixed-point number back to an natural representation. + * + * This permits to extract a result value out of a fixed-point number, + * to reverse the effect of @ref Q7i, @ref Q15i and @ref Q31i. + * + * @param i The fixed-point number to convert to a natural number. + * @param min The minimum value specified to create the fixed-point. + * @param max The maximum value specified to create the fixed-point. + * + * @{ + */ + +/** Convert a Q.7 fixed-point number to a natural integer */ +#define INTq7(i, min, max) SCALE((i), MINq7, MAXq7, (min), (max)) + +/** Convert a Q.15 fixed-point number to a natural integer */ +#define INTq15(i, min, max) SCALE((i), MINq15, MAXq15, (min), (max)) + +/** Convert a Q.31 fixed-point number to a natural integer */ +#define INTq31(i, min, max) SCALE((i), MINq31, MAXq31, (min), (max)) + +/* @} + */ + +/** + * @brief Add two fixed-point numbers together. + * + * Saturation logic is applied, so number out of ranges will be converted + * to the minimum -1.0 or maximum 1.0 value instead of overflowing. + * + * @note If two fixed-point numbers are to be subtracted into a larger type + * such as Q.7 + Q.7 = Q.15, the C operator @c + can be used instead. + * + * @param a First number to add. + * @param b Second number to add. + * + * @{ + */ + +/** Sum two Q.7 numbers and produce a Q.7 result */ +#define ADDq7(a, b) CLAMPq7((int16_t)(a) + (int16_t)(b)) + +/** Sum two Q.15 numbers and produce a Q.15 result */ +#define ADDq15(a, b) CLAMPq15((int32_t)(a) + (int32_t)(b)) + +/** Sum two Q.31 numbers and produce a Q.31 result */ +#define ADDq31(a, b) CLAMPq31((int64_t)(a) + (int64_t)(b)) + +/* @} + */ + +/** + * @brief Subtract a fixed-point number to another. + * + * Saturation logic is applied, so number out of ranges will be converted + * to the minimum 1.0 or maximum -1.0 value instead of overflowing. + * + * @note If two fixed-point numbers are to be subtracted into a larger type + * such as Q.7 - Q.7 = Q.15, the C operator @c - can be used instead. + * + * @param a First number to add. + * @param a Second number to add. + * + * @{ + */ + +/** Subtract two Q.7 numbers and produce a Q.7 result */ +#define SUBq7(a, b) CLAMPq7((int16_t)(a) - (int16_t)(b)) + +/** Subtract two Q.15 numbers and produce a Q.15 result */ +#define SUBq15(a, b) CLAMPq15((int32_t)(a) - (int32_t)(b)) + +/** Subtract two Q.31 numbers and produce a Q.31 result */ +#define SUBq31(a, b) CLAMPq31((int64_t)(a) - (int64_t)(b)) + +/* @} + */ + +/** + * @brief Multiply two fixed-point numbers together. + * + * Saturation logic is applied, so number out of range will be converted + * to handle the edge case Q#f(-1.0) * Q#f(-1.0) = Q#f(1.0) + * + * @note This implementation does not perform rounding. + * + * @param a First number to add. + * @param b Second number to add. + * + * @{ + */ + +/** Multiply two Q.7 numbers and produce a Q.7 result */ +#define MULq7(a, b) CLAMPq7(((int16_t)(a) * (int16_t)(b)) / (1 << 7)) + +/** Multiply two Q.15 numbers and produce a Q.15 result */ +#define MULq15(a, b) CLAMPq15(((int32_t)(a) * (int32_t)(b)) / (1 << 15)) + +/** Multiply two Q.31 numbers and produce a Q.31 result */ +#define MULq31(a, b) CLAMPq31(((int64_t)(a) * (int64_t)(b)) / (1 << 31)) + +/* @} + */ + +/** + * @brief Divide two fixed-point numbers together. + * + * Saturation logic is applied, so number out of ranges will be converted + * to the minimum -1.0 or maximum 1.0 value instead of overflowing. + * + * @note This implementation does not perform rounding. + * + * @param a Numerator of the division. + * @param b Denominator of the division. + * + * @{ + */ + +/** Divide a Q.7 number and produce a Q.7 result */ +#define DIVq7(a, b) CLAMPq7((a) * (1 << 7) / (b)) + +/** Divide a Q.15 number and produce a Q.15 result */ +#define DIVq15(a, b) CLAMPq15((a) * (1 << 15) / (b)) + +/** Divide a Q.31 number and produce a Q.31 result */ +#define DIVq31(a, b) CLAMPq31((a) * (1 << 31) / (b)) + +/* @} + */ + +/** + * @brief Apply the opposite value of the fixed-point number. + * + * Saturation logic is applied, as the most negative number could + * overflow internally if converted to positive. + * + * @param a Number to get the opposite value for + * + * @{ + */ + +/** Get the negation of a Q.7 number and produce a Q.7 result */ +#define NEGq7(a) (q7_t)((a) == MINq7 ? MAXq7 : -(a)) + +/** Get the negation of a Q.15 number and produce a Q.15 result */ +#define NEGq15(a) (q15_t)((a) == MINq15 ? MAXq15 : -(a)) + +/** Get the negation of a Q.15 number and produce a Q.15 result */ +#define NEGq31(a) (q31_t)((a) == MINq31 ? MAXq31 : -(a)) + +/* @} + */ + +/** + * @brief Get the absolute value of a fixed-point number. + * + * Saturation logic is applied, as the most negative number overflows if + * converted to positive. + * + * @param a Number to get the absolute value for. + * + * @{ + */ + +/** Get the absolute value of a Q.7 number and produce a Q.7 result */ +#define ABSq7(a) (q7_t)((a) < 0 ? NEGq7(a) : (a)) + +/** Get the absolute value of a Q.15 number and produce a Q.15 result */ +#define ABSq15(a) (q15_t)((a) < 0 ? NEGq15(a) : (a)) + +/** Get the absolute value of a Q.31 number and produce a Q.31 result */ +#define ABSq31(a) (q31_t)((a) < 0 ? NEGq31(a) : (a)) + +/* @} + */ + +#endif /* INCLUDE_ZEPHYR_DSP_MACROS_H_ */ diff --git a/include/zephyr/sys/util.h b/include/zephyr/sys/util.h index 6fba21dc16ac6..6b95e0e5d6cc3 100644 --- a/include/zephyr/sys/util.h +++ b/include/zephyr/sys/util.h @@ -407,6 +407,30 @@ extern "C" { #define CLAMP(val, low, high) (((val) <= (low)) ? (low) : MIN(val, high)) #endif +/** + * @brief Linearly maps a value from one scale to another. + * + * A value is considered to fit on an input range, and is scaled to + * the same proportion of the output range. + * The ranges are described by their minimum and maximum values. + * + * @note To avoid an overflow, the number of bits of the input and output range + * must be equal or lower to 61. For instance, an input range with min and max + * values between INT8_MIN and INT8_MAX, then the output range must have + * values within INT53_MIN and INT53_MAX. + * + * @note The size of the output range can be [0, 0]. + * The size of the input range must be greater than 0 + * + * @param i The value to convert from the input range to the output range. + * @param imin The minimum value of the input range. + * @param imax The maximum value of the input range. + * @param omin The minimum value of the output range. + * @param omax The maximum value of the output range. + */ +#define SCALE(i, imin, imax, omin, omax) \ + (((int64_t)(i) - (imin)) * ((int64_t)(omax) - (omin)) / ((int64_t)(imax) - (imin)) + (omin)) + /** * @brief Checks if a value is within range. * diff --git a/tests/lib/sys_util/src/main.c b/tests/lib/sys_util/src/main.c index 02e6465e44786..4dcdeb8947acb 100644 --- a/tests/lib/sys_util/src/main.c +++ b/tests/lib/sys_util/src/main.c @@ -13,6 +13,73 @@ * @{ */ +ZTEST(sys_util, test_SCALE) +{ + /* Test for a few arbitrary values */ + zassert_equal(SCALE(3, 0, 10, 0, 100), 30); + zassert_equal(SCALE(-3, -10, 0, -100, 0), -30); + zassert_equal(SCALE(10, -100, 100, -10, 10), 1); + zassert_equal(SCALE(0, -10, 40, -50, 0), -40); + zassert_equal(SCALE(0, -128, 127, 0, 2), 1); + zassert_equal(SCALE(5, -50, 5000, -1000, 10), -989); + + /* Test for i = 1..60 and o = 60..1 */ + for (int i = 1; i < 61; i++) { + int o = 61 - i; + int64_t imin = -(1ll << i); + int64_t imax = (1ll << i); + int64_t omin = -(1ll << o); + int64_t omax = (1ll << o); + + /* Special case: the output range can be [0, 0] */ + + zassert_equal(SCALE(imin, imin, imax, 0, 0), 0); + zassert_equal(SCALE(0, imin, imax, 0, 0), 0); + zassert_equal(SCALE(imax, imin, imax, 0, 0), 0); + + zassert_equal(SCALE(0, 0, imax, 0, 0), 0); + zassert_equal(SCALE(imax, 0, imax, 0, 0), 0); + + zassert_equal(SCALE(imin, imin, 0, 0, 0), 0); + zassert_equal(SCALE(0, imin, 0, 0, 0), 0); + + /* Test the extreme cases */ + + zassert_equal(SCALE(imin, imin, imax, omin, omax), omin); + zassert_equal(SCALE(0, imin, imax, omin, omax), 0); + zassert_equal(SCALE(imax, imin, imax, omin, omax), omax); + + zassert_equal(SCALE(0, 0, imax, omin, omax), omin); + zassert_equal(SCALE(imax, 0, imax, omin, omax), omax); + + zassert_equal(SCALE(imin, imin, 0, omin, omax), omin); + zassert_equal(SCALE(0, imin, 0, omin, omax), omax); + + zassert_equal(SCALE(imin, imin, imax, 0, omax), 0); + zassert_equal(SCALE(0, imin, imax, 0, omax), omax / 2); + zassert_equal(SCALE(imax, imin, imax, 0, omax), omax); + + zassert_equal(SCALE(imin, imin, imax, omin, 0), omin); + zassert_equal(SCALE(0, imin, imax, omin, 0), omin / 2); + zassert_equal(SCALE(imax, imin, imax, omin, 0), 0); + + zassert_equal(SCALE(0, 0, imax, 0, omax), 0); + zassert_equal(SCALE(imax, 0, imax, 0, omax), omax); + + zassert_equal(SCALE(0, 0, imax, omin, 0), omin); + zassert_equal(SCALE(imax, 0, imax, omin, 0), 0); + + zassert_equal(SCALE(imin, imin, 0, 0, omax), 0); + zassert_equal(SCALE(0, imin, 0, 0, omax), omax); + + zassert_equal(SCALE(imin, imin, 0, omin, 0), omin); + zassert_equal(SCALE(0, imin, 0, omin, 0), 0); + + zassert_equal(SCALE(0, 0, imax, 0, omax), 0); + zassert_equal(SCALE(imax, 0, imax, 0, omax), omax); + } +} + /** * @brief Test wait_for works as expected with typical use cases * @@ -71,7 +138,6 @@ ZTEST(sys_util, test_NUM_VA_ARGS_LESS_1) * @} */ - /** * @defgroup sys_util_tests Sys Util Tests * @ingroup all_tests diff --git a/tests/subsys/dsp/macros/CMakeLists.txt b/tests/subsys/dsp/macros/CMakeLists.txt new file mode 100644 index 0000000000000..a71f0b95b0ffb --- /dev/null +++ b/tests/subsys/dsp/macros/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(cmsis_dsp_macros) + +target_sources(app PRIVATE + src/q7.c + ) diff --git a/tests/subsys/dsp/macros/prj.conf b/tests/subsys/dsp/macros/prj.conf new file mode 100644 index 0000000000000..a4dcdd10ccb4b --- /dev/null +++ b/tests/subsys/dsp/macros/prj.conf @@ -0,0 +1,2 @@ +CONFIG_ZTEST=y +CONFIG_DSP=y diff --git a/tests/subsys/dsp/macros/src/q15.c b/tests/subsys/dsp/macros/src/q15.c new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/subsys/dsp/macros/src/q31.c b/tests/subsys/dsp/macros/src/q31.c new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/subsys/dsp/macros/src/q7.c b/tests/subsys/dsp/macros/src/q7.c new file mode 100644 index 0000000000000..811891a894c12 --- /dev/null +++ b/tests/subsys/dsp/macros/src/q7.c @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2021 Stephanos Ioannidis + * Copyright (C) 2010-2021 ARM Limited or its affiliates. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include + +ZTEST(zdsp_macros_q7, test_zdsp_macros_int_to_q7) +{ + q7_t e = 1; + + /* Test a few arbitrary values */ + zassert_within(Q7i(12, 10, 100), -122, e); + zassert_within(Q7i(120, 10, 234), -3, e); + zassert_within(Q7i(-1232, -91875010, 129308519), -23, e); + zassert_within(Q7i(-100000000, -91875010, -129308519), -73, e); + + /* Test the 1:1 conversion scale factor */ + for (int i = INT8_MIN; i <= INT8_MAX; i++) { + zassert_equal(Q7i(i, INT8_MIN, INT8_MAX), i); + } + + /* Test edge cases on the output range */ + zassert_equal(Q7i(0, 0, 1), MINq7); + zassert_equal(Q7i(1, 0, 1), MAXq7); + zassert_within(Q7i(1, 0, 2), 0, e); + zassert_equal(Q7i(1000, 1000, 1001), MINq7); + zassert_equal(Q7i(1001, 1000, 1001), MAXq7); + zassert_within(Q7i(1001, 1000, 1002), 0, e); + zassert_equal(Q7i(-1001, -1001, -1000), MINq7); + zassert_equal(Q7i(-1000, -1001, -1000), MAXq7); + zassert_within(Q7i(-1001, -1002, -1000), 0, e); + + /* Test boundary cases on the input range */ + zassert_equal(Q7i(0, INT8_MIN, 0), MAXq7); + zassert_equal(Q7i(0, 0, INT8_MAX), MINq7); + zassert_equal(Q7i(0, 0, UINT8_MAX), MINq7); + zassert_equal(Q7i(0, INT16_MIN, 0), MAXq7); + zassert_equal(Q7i(0, 0, INT16_MAX), MINq7); + zassert_equal(Q7i(0, 0, UINT16_MAX), MINq7); + zassert_equal(Q7i(0, INT32_MIN, 0), MAXq7); + zassert_equal(Q7i(0, 0, INT32_MAX), MINq7); + zassert_equal(Q7i(0, 0, UINT32_MAX), MINq7); + zassert_equal(Q7i(0, -(1LL << 54), 0), MAXq7); + zassert_equal(Q7i(0, 0, (1LL << 54) - 1), MINq7); + + /* Test that saturation happens if aiming above the output range */ + zassert_equal(Q7i(-1, 10, 20), MINq7); + zassert_equal(Q7i(200, 10, 20), MAXq7); +} + +ZTEST(zdsp_macros_q7, test_zdsp_macros_float_to_q7) +{ + q7_t e = 1; + + /* Test floatability for well-known values */ + zassert_within(Q7f(1.0), MAXq7, e); + zassert_within(Q7f(0.5), MAXq7 / 2, e); + zassert_within(Q7f(0.25), MAXq7 / 4, e); + zassert_within(Q7f(0.125), MAXq7 / 8, e); + zassert_within(Q7f(0.0), 0, e); + zassert_within(Q7f(-0.125), MINq7 / 8, e); + zassert_within(Q7f(-0.25), MINq7 / 4, e); + zassert_within(Q7f(-0.5), MINq7 / 2, e); + zassert_within(Q7f(-1.0), MINq7, e); + + /* Test saturation */ + zassert_within(Q7f(-1.1), MINq7, e); + zassert_within(Q7f(-1000000), MINq7, e); + zassert_within(Q7f(1.1), MAXq7, e); + zassert_within(Q7f(1000000), MAXq7, e); +} + +ZTEST(zdsp_macros_q7, test_zdsp_macros_int_from_q7) +{ + q7_t e = 1; + + /* Test edge cases for selected output ranges */ + zassert_within(INTq7(MINq7, 10, 100), 10, e); + zassert_within(INTq7(MAXq7, 10, 100), 100, e); + zassert_within(INTq7(MINq7, -100, -10), -100, e); + zassert_within(INTq7(MAXq7, -100, -10), -10, e); + + /* Test the 1:1 conversion scale factor */ + for (int i = MINq7; i <= MAXq7; i++) { + zassert_within(INTq7(i, INT8_MIN, INT8_MAX), i, e); + } +} + +ZTEST(zdsp_macros_q7, test_zdsp_macros_equiv_q7) +{ + /* Acceptable precision related to the change of range */ + int64_t e = ((int64_t)INT32_MAX - (int64_t)INT32_MIN) / (MAXq7 - MINq7); + + /* Test a few selected values within [INT32_MIN, INT32_MAX] */ + zassert_within(INTq7(Q7i(INT32_MIN, INT32_MIN, INT32_MAX), INT32_MIN, INT32_MAX), INT32_MIN, e); + zassert_within(INTq7(Q7i(-1032, INT32_MIN, INT32_MAX), INT32_MIN, INT32_MAX), -1032, e); + zassert_within(INTq7(Q7i(0, INT32_MIN, INT32_MAX), INT32_MIN, INT32_MAX), 0, e); + zassert_within(INTq7(Q7i(1032, INT32_MIN, INT32_MAX), INT32_MIN, INT32_MAX), 1032, e); + zassert_within(INTq7(Q7i(INT32_MAX, INT32_MIN, INT32_MAX), INT32_MIN, INT32_MAX), INT32_MAX, e); + + /* Test a few selected values within arbitrary ranges */ + zassert_within(INTq7(Q7i(132, 0, 1000), 0, 1000), 132, e); + zassert_within(INTq7(Q7i(-132, -1000, 0), -1000, 0), -132, e); + zassert_within(INTq7(Q7i(132, -1000, 1000), -1000, 1000), 132, e); + + /* Test a much larger range */ + for (int64_t i = INT32_MIN; i <= INT32_MAX; i += 1000009) { + zassert_within(INTq7(Q7i(i, INT32_MIN, INT32_MAX), INT32_MIN, INT32_MAX), i, e); + zassert_within(INTq7(Q7i(i, i, INT32_MAX), i, INT32_MAX), i, e); + zassert_within(INTq7(Q7i(i, INT32_MIN, i), INT32_MIN, i), i, e); + } +} + +ZTEST(zdsp_macros_q7, test_zdsp_macros_q15_from_q7) +{ + q15_t e = 1 << 8; + + /* Test through selected values through a whole range for Q.15 */ + zassert_within(Q15q7(Q7f(-1.0)), Q15f(-1.0), e); + zassert_within(Q15q7(Q7f(-0.3)), Q15f(-0.3), e); + zassert_within(Q15q7(Q7f(0.0)), Q15f(0.0), e); + zassert_within(Q15q7(Q7f(0.7)), Q15f(0.7), e); + zassert_within(Q15q7(Q7f(1.0)), Q15f(1.0), e); +} + +ZTEST(zdsp_macros_q7, test_zdsp_macros_q31_from_q7) +{ + q31_t e = 1 << 24; + + /* Test through selected values through a whole range for Q.31 */ + zassert_within(Q31q7(Q7f(-1.0)), Q31f(-1.0), e); + zassert_within(Q31q7(Q7f(-0.3)), Q31f(-0.3), e); + zassert_within(Q31q7(Q7f(0.0)), Q31f(0.0), e); + zassert_within(Q31q7(Q7f(0.7)), Q31f(0.7), e); + zassert_within(Q31q7(Q7f(1.0)), Q31f(1.0), e); +} + +ZTEST(zdsp_macros_q7, test_zdsp_macros_add_q7) +{ + q7_t e = 1; + + /* Test arbitrary values in range */ + zassert_equal(ADDq7(Q7f(0.0), Q7f(0.0)), Q7f(0.0)); + zassert_within(ADDq7(Q7f(0.3), Q7f(0.3)), Q7f(0.6), e); + zassert_within(ADDq7(Q7f(0.3), Q7f(-0.3)), Q7f(0.0), e); + zassert_within(ADDq7(Q7f(0.1), Q7f(0.9)), Q7f(1.0), e); + zassert_within(ADDq7(Q7f(0.1), Q7f(0.2)), Q7f(0.3), e); + zassert_within(ADDq7(Q7f(0.3123), Q7f(0.4123)), Q7f(0.7246), e); + + /* Test saturation */ + zassert_equal(ADDq7(Q7f(0.9), Q7f(0.5)), Q7f(1.0)); + zassert_equal(ADDq7(Q7f(-0.9), Q7f(-0.5)), Q7f(-1.0)); + zassert_equal(ADDq7(Q7f(1.1), Q7f(1.2)), Q7f(1.0)); + zassert_equal(ADDq7(Q7f(-1.1), Q7f(-1.2)), Q7f(-1.0)); + zassert_within(ADDq7(Q7f(1.1), Q7f(-1.2)), Q7f(0.0), e); +} + +ZTEST(zdsp_macros_q7, test_zdsp_macros_sub_q7) +{ + q7_t e = 1; + + /* Test arbitrary values in range */ + zassert_equal(SUBq7(Q7f(0.0), Q7f(0.0)), Q7f(0.0)); + zassert_equal(SUBq7(Q7f(0.3), Q7f(0.3)), Q7f(0.0)); + zassert_within(SUBq7(Q7f(0.1), Q7f(0.9)), Q7f(-0.8), e); + zassert_within(SUBq7(Q7f(0.1), Q7f(0.2)), Q7f(-0.1), e); + zassert_within(SUBq7(Q7f(0.3123), Q7f(0.4123)), Q7f(-0.1), e); + + /* Test saturation */ + zassert_within(SUBq7(Q7f(-0.1), Q7f(1.5)), Q7f(-1.0), e); + zassert_within(SUBq7(Q7f(-1.0), Q7f(0.3)), Q7f(-1.0), e); + zassert_equal(SUBq7(Q7f(0.2), Q7f(-1.6)), Q7f(1.0)); + zassert_equal(SUBq7(Q7f(-1.0), Q7f(0.4)), Q7f(-1.0)); +} + +ZTEST(zdsp_macros_q7, test_zdsp_macros_mul_q7) +{ + q7_t e = 1; + + /* Test arbitrary values */ + zassert_within(MULq7(Q7f(0.1), Q7f(0.2)), Q7f(0.02), e); + zassert_within(MULq7(Q7f(0.2), Q7f(0.2)), Q7f(0.04), e); + zassert_within(MULq7(Q7f(-0.1), Q7f(-0.2)), Q7f(0.02), e); + zassert_within(MULq7(Q7f(-0.1), Q7f(-0.2)), Q7f(0.02), e); + + /* Test identity. Note that with fixed-points 1.0 * 1.0 is slightly smaller than 1.0 */ + for (double f = -1.0; f <= 1.0; f += 0.001) { + zassert_within(MULq7(Q7f(f), Q7f(1.0)), Q7f(f), e); + zassert_within(MULq7(Q7f(f), Q7f(-1.0)), Q7f(-f), e); + zassert_within(MULq7(Q7f(-f), Q7f(1.0)), Q7f(-f), e); + zassert_within(MULq7(Q7f(-f), Q7f(-1.0)), Q7f(f), e); + } +} + +ZTEST(zdsp_macros_q7, test_zdsp_macros_div_q7) +{ + q7_t e = 3; + + /* Test arbitrary values */ + zassert_within(DIVq7(Q7f(0.1), Q7f(0.1)), Q7f(1.0), e); + zassert_within(DIVq7(Q7f(0.1), Q7f(0.2)), Q7f(0.5), e); + zassert_within(DIVq7(Q7f(0.1), Q7f(0.4)), Q7f(0.25), e); + zassert_within(DIVq7(Q7f(0.4), Q7f(0.5)), Q7f(0.8), e); + + /* Test saturation */ + zassert_within(DIVq7(Q7f(1.0), Q7f(0.2)), Q7f(1.0), e); + zassert_within(DIVq7(Q7f(1.0), Q7f(-0.9)), Q7f(-1.0), e); + zassert_within(DIVq7(Q7f(-1.0), Q7f(0.6)), Q7f(-1.0), e); + zassert_within(DIVq7(Q7f(-0.9), Q7f(-0.6)), Q7f(1.0), e); + + /* Test identity */ + for (double f = -1.0; f <= 1.0; f += 0.001) { + zassert_within(DIVq7(Q7f(f), Q7f(1.0)), Q7f(f), e); + zassert_within(DIVq7(Q7f(f), Q7f(-1.0)), Q7f(-f), e); + zassert_within(DIVq7(Q7f(-f), Q7f(1.0)), Q7f(-f), e); + zassert_within(DIVq7(Q7f(-f), Q7f(-1.0)), Q7f(f), e); + } +} + +ZTEST(zdsp_macros_q7, test_zdsp_macros_neg_q7) +{ + q7_t e = 1; + + /* Test arbitrary values */ + zassert_equal(NEGq7(Q7f(-1.0)), Q7f(1.0)); + zassert_equal(NEGq7(Q7f(-0.3)), Q7f(0.3)); + zassert_equal(NEGq7(Q7f(0.0)), Q7f(0.0)); + zassert_equal(NEGq7(Q7f(0.5)), Q7f(-0.5)); + zassert_within(NEGq7(Q7f(1.0)), Q7f(-1.0), e); +} + +ZTEST(zdsp_macros_q7, test_zdsp_macros_abs_q7) +{ + /* Test arbitrary values */ + zassert_equal(ABSq7(Q7f(-1.0)), Q7f(1.0)); + zassert_equal(ABSq7(Q7f(-0.4)), Q7f(0.4)); + zassert_equal(ABSq7(Q7f(0.0)), Q7f(0.0)); + zassert_equal(ABSq7(Q7f(0.4)), Q7f(0.4)); + zassert_equal(ABSq7(Q7f(1.0)), Q7f(1.0)); +} + +ZTEST(zdsp_macros_q7, test_zdsp_macros_complex_q7) +{ + /* */ + zassert_equal(ABSq7(Q7f(-1.0)), Q7f(1.0)); +} + +ZTEST_SUITE(zdsp_macros_q7, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/subsys/dsp/macros/testcase.yaml b/tests/subsys/dsp/macros/testcase.yaml new file mode 100644 index 0000000000000..af877788739e2 --- /dev/null +++ b/tests/subsys/dsp/macros/testcase.yaml @@ -0,0 +1,7 @@ +tests: + zdsp.macros: + integration_platforms: + - native_sim + tags: zdsp + min_flash: 64 + min_ram: 32