From 95926d1e14ae152cc42fcc697bc982a6ef1d07cb Mon Sep 17 00:00:00 2001 From: "C.S.M" Date: Wed, 4 Mar 2026 15:33:02 +0800 Subject: [PATCH 1/3] feat(cordic): Add hal layer support for cordic --- components/esp_hal_cordic/CMakeLists.txt | 22 ++ components/esp_hal_cordic/README.md | 9 + components/esp_hal_cordic/cordic_hal.c | 87 +++++ .../esp_hal_cordic/esp32s31/cordic_periph.c | 23 ++ .../esp32s31/include/hal/cordic_ll.h | 301 ++++++++++++++++++ .../esp_hal_cordic/include/hal/cordic_hal.h | 109 +++++++ .../include/hal/cordic_periph.h | 30 ++ .../esp_hal_cordic/include/hal/cordic_types.h | 74 +++++ .../esp32s31/include/soc/Kconfig.soc_caps.in | 4 + .../soc/esp32s31/include/soc/clk_tree_defs.h | 14 + .../soc/esp32s31/include/soc/soc_caps.h | 1 + .../soc/esp32s31/register/soc/cordic_struct.h | 3 +- 12 files changed, 676 insertions(+), 1 deletion(-) create mode 100644 components/esp_hal_cordic/CMakeLists.txt create mode 100644 components/esp_hal_cordic/README.md create mode 100644 components/esp_hal_cordic/cordic_hal.c create mode 100644 components/esp_hal_cordic/esp32s31/cordic_periph.c create mode 100644 components/esp_hal_cordic/esp32s31/include/hal/cordic_ll.h create mode 100644 components/esp_hal_cordic/include/hal/cordic_hal.h create mode 100644 components/esp_hal_cordic/include/hal/cordic_periph.h create mode 100644 components/esp_hal_cordic/include/hal/cordic_types.h diff --git a/components/esp_hal_cordic/CMakeLists.txt b/components/esp_hal_cordic/CMakeLists.txt new file mode 100644 index 0000000000..e47779c40f --- /dev/null +++ b/components/esp_hal_cordic/CMakeLists.txt @@ -0,0 +1,22 @@ +idf_build_get_property(target IDF_TARGET) +if(${target} STREQUAL "linux") + return() # This component is not supported by the POSIX/Linux simulator +endif() + +set(includes) + +if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${target}/include") + list(APPEND includes "${target}/include") +endif() +list(APPEND includes "include") + +set(srcs) + +# Cordic related source files +if(CONFIG_SOC_CORDIC_SUPPORTED) + list(APPEND srcs "cordic_hal.c" "${target}/cordic_periph.c") +endif() + +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS ${includes} + REQUIRES soc hal) diff --git a/components/esp_hal_cordic/README.md b/components/esp_hal_cordic/README.md new file mode 100644 index 0000000000..67a3a0689b --- /dev/null +++ b/components/esp_hal_cordic/README.md @@ -0,0 +1,9 @@ +# `esp_hal_cordic` + +⚠️ This HAL component is still under heavy development at the moment, so we don't guarantee the stability and backward-compatibility among versions. + +The `esp_hal_cordic` component provides a **Hardware Abstraction Layer** of cordic for all targets supported by ESP-IDF. + +In a broad sense, the HAL layer consists of two sub-layers: HAL (upper) and Low-Level(bottom). The HAL layer defines the steps and data that is required to operate a peripheral (e.g. initialization, parameter settings). The low-level is a translation layer above the register files under the `soc` component, it only covers general conceptions to register configurations. + +The functions in this file mainly provide hardware abstraction for IDF peripheral drivers. For advanced developers, the HAL layer functions can also be directly used to assist in implementing their own drivers. However, it needs to be mentioned again that the interfaces here do not guarantee stability. diff --git a/components/esp_hal_cordic/cordic_hal.c b/components/esp_hal_cordic/cordic_hal.c new file mode 100644 index 0000000000..5bff3e8e66 --- /dev/null +++ b/components/esp_hal_cordic/cordic_hal.c @@ -0,0 +1,87 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "hal/cordic_hal.h" +#include "hal/cordic_ll.h" + +// Function pointer arrays for setting arguments +// Index: [format][argument_count] +// format: 0 = Q15, 1 = Q31 +// argument_count: 0 = one_arg, 1 = two_args +const cordic_hal_set_argument_func_t cordic_hal_set_argument_funcs[ESP_CORDIC_IQ_SIZE_MAX][2] = { + [ESP_CORDIC_FORMAT_Q15] = { + [0] = cordic_hal_set_argument_q15_one_arg, + [1] = cordic_hal_set_argument_q15_two_args, + }, + [ESP_CORDIC_FORMAT_Q31] = { + [0] = cordic_hal_set_argument_q31_one_arg, + [1] = cordic_hal_set_argument_q31_two_args, + }, +}; + +// Function pointer array for getting results +// Index: [format] +// format: 0 = Q15, 1 = Q31 +const cordic_hal_get_result_func_t cordic_hal_get_result_funcs[ESP_CORDIC_IQ_SIZE_MAX] = { + [ESP_CORDIC_FORMAT_Q15] = cordic_hal_get_result_q15, + [ESP_CORDIC_FORMAT_Q31] = cordic_hal_get_result_q31, +}; + +void cordic_hal_init(cordic_hal_context_t *hal) +{ + hal->dev = CORDIC_LL_GET_HW(); +} + +void cordic_hal_deinit(cordic_hal_context_t *hal) +{ + hal->dev = NULL; +} + +void cordic_hal_set_argument_q15_two_args(cordic_hal_context_t *hal, const cordic_input_buffer_desc_t *input_buffer, size_t index) +{ + // Pack two 16-bit values into one 32-bit register + cordic_ll_set_calculate_argument_1(hal->dev, (uint32_t)(input_buffer->p_data_arg2[index] << 16) | input_buffer->p_data_arg1[index]); +} + +// Q15 format with one argument +void cordic_hal_set_argument_q15_one_arg(cordic_hal_context_t *hal, const cordic_input_buffer_desc_t *input_buffer, size_t index) +{ + cordic_ll_set_calculate_argument_1(hal->dev, input_buffer->p_data_arg1[index]); +} + +void cordic_hal_set_argument_q31_two_args(cordic_hal_context_t *hal, const cordic_input_buffer_desc_t *input_buffer, size_t index) +{ + // Set two separate 32-bit registers + cordic_ll_set_calculate_argument_1(hal->dev, input_buffer->p_data_arg1[index]); + cordic_ll_set_calculate_argument_2(hal->dev, input_buffer->p_data_arg2[index]); +} + +void cordic_hal_set_argument_q31_one_arg(cordic_hal_context_t *hal, const cordic_input_buffer_desc_t *input_buffer, size_t index) +{ + cordic_ll_set_calculate_argument_1(hal->dev, input_buffer->p_data_arg1[index]); +} + +void cordic_hal_get_result_q15(cordic_hal_context_t *hal, cordic_output_buffer_desc_t *output_buffer, size_t index) +{ + // For Q15_SIZE: extract two 16-bit values from one 32-bit register + uint32_t res = cordic_ll_get_calculate_result_1(hal->dev); + output_buffer->p_data_res1[index] = (res & 0x0000FFFF); + if (output_buffer->p_data_res2 != NULL) { + output_buffer->p_data_res2[index] = (res >> 16); + } +} + +void cordic_hal_get_result_q31(cordic_hal_context_t *hal, cordic_output_buffer_desc_t *output_buffer, size_t index) +{ + // For Q31_SIZE: get two separate 32-bit results + uint32_t res = cordic_ll_get_calculate_result_1(hal->dev); + output_buffer->p_data_res1[index] = res; + if (output_buffer->p_data_res2 != NULL) { + uint32_t res2 = cordic_ll_get_calculate_result_2(hal->dev); + output_buffer->p_data_res2[index] = res2; + } +} diff --git a/components/esp_hal_cordic/esp32s31/cordic_periph.c b/components/esp_hal_cordic/esp32s31/cordic_periph.c new file mode 100644 index 0000000000..acfe4688c9 --- /dev/null +++ b/components/esp_hal_cordic/esp32s31/cordic_periph.c @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "hal/cordic_hal.h" +#include "hal/cordic_ll.h" + +// algorithm allowable scale, format [min, max] +uint16_t cordic_hal_algorithm_allowable_scale[ESP_CORDIC_FUNC_MAX][2] = { + {0, 0}, + {0, 0}, + {0, 15}, + {0, 15}, + {0, 15}, + {1, 1}, + {1, 1}, + {1, 1}, + {1, 1}, + {0, 4}, +}; diff --git a/components/esp_hal_cordic/esp32s31/include/hal/cordic_ll.h b/components/esp_hal_cordic/esp32s31/include/hal/cordic_ll.h new file mode 100644 index 0000000000..16a5811561 --- /dev/null +++ b/components/esp_hal_cordic/esp32s31/include/hal/cordic_ll.h @@ -0,0 +1,301 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include "hal/misc.h" +#include "hal/assert.h" +#include "soc/cordic_struct.h" +#include "hal/cordic_types.h" +#include "soc/hp_sys_clkrst_struct.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CORDIC_LL_GET_HW() (&CORDIC) +#define CORDIC_LL_PRECISION_MAX (0xF) + +/** + * @brief CORDIC operation mode + */ +typedef enum { + CORDIC_LL_MODE_REG = 0, ///< Register mode: data is transferred via registers + CORDIC_LL_MODE_DMA, ///< DMA mode: data is transferred via DMA +} cordic_ll_mode_t; + +/** + * @brief Enable the hardware clock for CORDIC module + * + * @param enable True to enable; false to disable + */ +static inline void cordic_ll_enable_bus_clock(bool enable) +{ + HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_apb_clk_en = enable; +} + +/** + * @brief Enable the CORDIC module clock + * + * @param enable True to enable; false to disable + */ +static inline void cordic_ll_enable_clock(bool enable) +{ + HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_clk_en = enable; +} + +/** + * @brief Reset the CORDIC module + */ +static inline void cordic_ll_reset_module_register(void) +{ + HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_apb_rst_en = 1; + HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_apb_rst_en = 0; +} + +/** + * @brief Set the clock source for CORDIC module + * + * @param source Clock source to use (XTAL, RC_FAST, or PLL_F160M) + */ +static inline void cordic_ll_set_clock_source(cordic_clock_source_t source) +{ + switch (source) { + case CORDIC_CLK_SRC_XTAL: + HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_clk_src_sel = 0; + break; + case CORDIC_CLK_SRC_RC_FAST: + HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_clk_src_sel = 1; + break; + case CORDIC_CLK_SRC_PLL_F160M: + HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_clk_src_sel = 2; + break; + default: + HAL_ASSERT(false && "unsupported cordic clock source"); + break; + } +} + +/** + * @brief Set the calculation function type (e.g., cosine, sine, arctan) + * + * @param hw Pointer to the CORDIC hardware register structure + * @param function Function type to calculate + */ +__attribute__((always_inline)) +static inline void cordic_ll_set_calculate_function(cordic_dev_t *hw, cordic_func_t function) +{ + switch (function) { + case ESP_CORDIC_FUNC_COS: + hw->csr_cfg.func = 0; + break; + case ESP_CORDIC_FUNC_SIN: + hw->csr_cfg.func = 1; + break; + case ESP_CORDIC_FUNC_PHASE: + hw->csr_cfg.func = 2; + break; + case ESP_CORDIC_FUNC_MODULUS: + hw->csr_cfg.func = 3; + break; + case ESP_CORDIC_FUNC_ARCTAN: + hw->csr_cfg.func = 4; + break; + case ESP_CORDIC_FUNC_COSH: + hw->csr_cfg.func = 5; + break; + case ESP_CORDIC_FUNC_SINH: + hw->csr_cfg.func = 6; + break; + case ESP_CORDIC_FUNC_ARCHTANH: + hw->csr_cfg.func = 7; + break; + case ESP_CORDIC_FUNC_LOGE: + hw->csr_cfg.func = 8; + break; + case ESP_CORDIC_FUNC_SQUARE_ROOT: + hw->csr_cfg.func = 9; + break; + default: + HAL_ASSERT(false); + } +} + +/** + * @brief Set the calculation mode (register mode or DMA mode) + * + * @param hw Pointer to the CORDIC hardware register structure + * @param mode Operation mode (REG_MODE or DMA_MODE) + */ +__attribute__((always_inline)) +static inline void cordic_ll_set_calculate_mode(cordic_dev_t *hw, cordic_ll_mode_t mode) +{ + if (mode == CORDIC_LL_MODE_REG) { + hw->csr_cfg.work_mode = 0; + } else if (mode == CORDIC_LL_MODE_DMA) { + hw->csr_cfg.work_mode = 1; + } else { + HAL_ASSERT(false); + } +} + +/** + * @brief Set the calculation precision (iteration count) + * + * @param hw Pointer to the CORDIC hardware register structure + * @param precision Precision value (number of iterations) + */ +__attribute__((always_inline)) +static inline void cordic_ll_set_calculate_precision(cordic_dev_t *hw, uint16_t precision) +{ + hw->csr_cfg.press = precision; +} + +/** + * @brief Set the input data scale factor + * + * @param hw Pointer to the CORDIC hardware register structure + * @param scale Scale factor for input data (range depends on function type) + */ +__attribute__((always_inline)) +static inline void cordic_ll_set_calculate_scale(cordic_dev_t *hw, uint16_t scale) +{ + hw->csr_cfg.scale = scale; +} + +/** + * @brief Set the number of result outputs (one or two) + * + * @param hw Pointer to the CORDIC hardware register structure + * @param number Number of result outputs (ONE_NUM or TWO_NUM) + */ +__attribute__((always_inline)) +static inline void cordic_ll_set_calculate_result_number(cordic_dev_t *hw, uint8_t number) +{ + hw->csr_cfg.res_num = number - 1; +} + +/** + * @brief Set the number of input arguments (one or two) + * + * @param hw Pointer to the CORDIC hardware register structure + * @param number Number of input arguments (ONE_NUM or TWO_NUM) + */ +__attribute__((always_inline)) +static inline void cordic_ll_set_calculate_argument_number(cordic_dev_t *hw, uint8_t number) +{ + hw->csr_cfg.arg_num = number - 1; +} + +/** + * @brief Set the result data format size (Q15 or Q31) + * + * @param hw Pointer to the CORDIC hardware register structure + * @param size Result data format (Q15_SIZE or Q31_SIZE) + */ +__attribute__((always_inline)) +static inline void cordic_ll_set_calculate_result_format(cordic_dev_t *hw, cordic_iq_format_t format) +{ + if (format == ESP_CORDIC_FORMAT_Q15) { + hw->csr_cfg.res_size = 0; + } else { + hw->csr_cfg.res_size = 1; + } +} + +/** + * @brief Set the argument data format size (Q15 or Q31) + * + * @param hw Pointer to the CORDIC hardware register structure + * @param size Argument data format (Q15_SIZE or Q31_SIZE) + */ +__attribute__((always_inline)) +static inline void cordic_ll_set_calculate_argument_format(cordic_dev_t *hw, cordic_iq_format_t format) +{ + if (format == ESP_CORDIC_FORMAT_Q15) { + hw->csr_cfg.arg_size = 0; + } else { + hw->csr_cfg.arg_size = 1; + } +} + +/** + * @brief Set the first input argument value + * + * @param hw Pointer to the CORDIC hardware register structure + * @param arg First argument value in fixed-point format + */ +__attribute__((always_inline)) +static inline void cordic_ll_set_calculate_argument_1(cordic_dev_t *hw, uint32_t arg) +{ + hw->arg1.arg1_data = arg; +} + +/** + * @brief Set the second input argument value (for two-argument functions) + * + * @param hw Pointer to the CORDIC hardware register structure + * @param arg Second argument value in fixed-point format + */ +__attribute__((always_inline)) +static inline void cordic_ll_set_calculate_argument_2(cordic_dev_t *hw, uint32_t arg) +{ + hw->arg2.arg2_data = arg; +} + +/** + * @brief Start the CORDIC calculation + * + * @param hw Pointer to the CORDIC hardware register structure + */ +__attribute__((always_inline)) +static inline void cordic_ll_start_calculate(cordic_dev_t *hw) +{ + hw->csr_cfg.update_flag = 1; +} + +/** + * @brief Get the first calculation result + * + * @param hw Pointer to the CORDIC hardware register structure + * @return First result value in fixed-point format + */ +__attribute__((always_inline)) +static inline uint32_t cordic_ll_get_calculate_result_1(cordic_dev_t *hw) +{ + return hw->res1.res1_data; +} + +/** + * @brief Get the second calculation result (for functions with two outputs) + * + * @param hw Pointer to the CORDIC hardware register structure + * @return Second result value in fixed-point format + */ +__attribute__((always_inline)) +static inline uint32_t cordic_ll_get_calculate_result_2(cordic_dev_t *hw) +{ + return hw->res2.res2_data; +} + +/** + * @brief Check if the calculation result is ready + * + * @param hw Pointer to the CORDIC hardware register structure + * @return True if result is ready, false otherwise + */ +__attribute__((always_inline)) +static inline bool cordic_ll_is_calculate_result_ready(cordic_dev_t *hw) +{ + return hw->csr_cfg.res_rdy_flag; +} + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hal_cordic/include/hal/cordic_hal.h b/components/esp_hal_cordic/include/hal/cordic_hal.h new file mode 100644 index 0000000000..109e7dc78d --- /dev/null +++ b/components/esp_hal_cordic/include/hal/cordic_hal.h @@ -0,0 +1,109 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "hal/cordic_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct cordic_dev_t *cordic_soc_handle_t; // cordic SOC layer handle + +// Forward declaration for function pointer types +typedef struct cordic_hal_context_t cordic_hal_context_t; + +// Function pointer type for setting CORDIC arguments +typedef void (*cordic_hal_set_argument_func_t)(cordic_hal_context_t *hal, const cordic_input_buffer_desc_t *input_buffer, size_t index); + +// Function pointer type for getting CORDIC results +typedef void (*cordic_hal_get_result_func_t)(cordic_hal_context_t *hal, cordic_output_buffer_desc_t *output_buffer, size_t index); + +// Function pointer arrays for setting arguments +extern const cordic_hal_set_argument_func_t cordic_hal_set_argument_funcs[ESP_CORDIC_IQ_SIZE_MAX][2]; +extern const cordic_hal_get_result_func_t cordic_hal_get_result_funcs[ESP_CORDIC_IQ_SIZE_MAX]; + +/** + * Context that should be maintained by both the driver and the HAL + */ +typedef struct cordic_hal_context_t { + cordic_soc_handle_t dev; // cordic SOC layer handle (i.e. register base address) +} cordic_hal_context_t; + +/** + * @brief Initialize the cordic codec HAL driver + * + * @param hal: cordic codec HAL context + */ +void cordic_hal_init(cordic_hal_context_t *hal); + +/** + * @brief Deinitialize the cordic codec HAL driver + * + * @param hal: cordic codec HAL context + */ +void cordic_hal_deinit(cordic_hal_context_t *hal); + +/** + * @brief Set CORDIC arguments for Q15 format with two arguments (PHASE_FUNC, MODULUS_FUNC) + * + * @param hal: cordic HAL context + * @param input_buffer: input buffer descriptor (HAL layer type) + * @param index: data index + */ +void cordic_hal_set_argument_q15_two_args(cordic_hal_context_t *hal, const cordic_input_buffer_desc_t *input_buffer, size_t index); + +/** + * @brief Set CORDIC arguments for Q15 format with one argument + * + * @param hal: cordic HAL context + * @param input_buffer: input buffer descriptor (HAL layer type) + * @param index: data index + */ +void cordic_hal_set_argument_q15_one_arg(cordic_hal_context_t *hal, const cordic_input_buffer_desc_t *input_buffer, size_t index); + +/** + * @brief Set CORDIC arguments for Q31 format with two arguments (PHASE_FUNC, MODULUS_FUNC) + * + * @param hal: cordic HAL context + * @param input_buffer: input buffer descriptor (HAL layer type) + * @param index: data index + */ +void cordic_hal_set_argument_q31_two_args(cordic_hal_context_t *hal, const cordic_input_buffer_desc_t *input_buffer, size_t index); + +/** + * @brief Set CORDIC arguments for Q31 format with one argument + * + * @param hal: cordic HAL context + * @param input_buffer: input buffer descriptor (HAL layer type) + * @param index: data index + */ +void cordic_hal_set_argument_q31_one_arg(cordic_hal_context_t *hal, const cordic_input_buffer_desc_t *input_buffer, size_t index); + +/** + * @brief Get CORDIC results for Q15 format + * + * @param hal: cordic HAL context + * @param output_buffer: output buffer descriptor (HAL layer type) + * @param index: data index + */ +void cordic_hal_get_result_q15(cordic_hal_context_t *hal, cordic_output_buffer_desc_t *output_buffer, size_t index); + +/** + * @brief Get CORDIC results for Q31 format + * + * @param hal: cordic HAL context + * @param output_buffer: output buffer descriptor (HAL layer type) + * @param index: data index + */ +void cordic_hal_get_result_q31(cordic_hal_context_t *hal, cordic_output_buffer_desc_t *output_buffer, size_t index); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hal_cordic/include/hal/cordic_periph.h b/components/esp_hal_cordic/include/hal/cordic_periph.h new file mode 100644 index 0000000000..046bce9e25 --- /dev/null +++ b/components/esp_hal_cordic/include/hal/cordic_periph.h @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "hal/cordic_types.h" + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#if SOC_CORDIC_SUPPORTED + +/** + * @brief Algorithm allowable scale, format [min, max] + * + * @param 1: function type + * @param 2: [min, max] scale + */ +extern uint16_t cordic_hal_algorithm_allowable_scale[ESP_CORDIC_FUNC_MAX][2]; + +#endif // SOC_CORDIC_SUPPORTED + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hal_cordic/include/hal/cordic_types.h b/components/esp_hal_cordic/include/hal/cordic_types.h new file mode 100644 index 0000000000..a2e1d91d7d --- /dev/null +++ b/components/esp_hal_cordic/include/hal/cordic_types.h @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "soc/soc_caps.h" +#include "soc/clk_tree_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if SOC_CORDIC_SUPPORTED +/** + * @brief CORDIC group clock source + */ +typedef soc_periph_cordic_clk_src_t cordic_clock_source_t; +#else +/** + * @brief default type + */ +typedef int cordic_clock_source_t; +#endif + +/** + * @brief Input buffer descriptor for CORDIC operations + */ +typedef struct { + uint32_t *p_data_arg1; /*!< Pointer to input data argument 1 buffer */ + uint32_t *p_data_arg2; /*!< Pointer to input data argument 2 buffer (can be NULL for single argument functions) */ +} cordic_input_buffer_desc_t; + +/** + * @brief Output buffer descriptor for CORDIC operations + */ +typedef struct { + uint32_t *p_data_res1; /*!< Pointer to output result 1 buffer */ + uint32_t *p_data_res2; /*!< Pointer to output result 2 buffer (can be NULL for single result) */ +} cordic_output_buffer_desc_t; + +/** + * @brief CORDIC calculation function type + */ +typedef enum { + ESP_CORDIC_FUNC_COS = 0, ///< Cosine function (can have both cos, sin results) + ESP_CORDIC_FUNC_SIN, ///< Sine function (can have both sin, cos results) + ESP_CORDIC_FUNC_PHASE, ///< Phase function (requires two arguments) (can have both phase, modulus results) + ESP_CORDIC_FUNC_MODULUS, ///< Modulus function (requires two arguments) (can have both phase, modulus results) + ESP_CORDIC_FUNC_ARCTAN, ///< Arctangent function + ESP_CORDIC_FUNC_COSH, ///< Hyperbolic cosine function (can have both cosh, sinh results) + ESP_CORDIC_FUNC_SINH, ///< Hyperbolic sine function (can have both sinh, cosh results) + ESP_CORDIC_FUNC_ARCHTANH, ///< Hyperbolic arctangent function + ESP_CORDIC_FUNC_LOGE, ///< Natural logarithm function + ESP_CORDIC_FUNC_SQUARE_ROOT, ///< Square root function + ESP_CORDIC_FUNC_MAX, ///< Maximum function type (for boundary checking) +} cordic_func_t; + +/** + * @brief CORDIC data format size + */ +typedef enum { + ESP_CORDIC_FORMAT_Q15 = 0, ///< Q15 format: 16-bit fixed-point (range: -1.0 to 1.0) + ESP_CORDIC_FORMAT_Q31 = 1, ///< Q31 format: 32-bit fixed-point (range: -1.0 to 1.0) + ESP_CORDIC_IQ_SIZE_MAX, ///< Maximum size type (for boundary checking) +} cordic_iq_format_t; + +#ifdef __cplusplus +} +#endif diff --git a/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in index 86f0e3b237..45431cf5b5 100644 --- a/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in @@ -71,6 +71,10 @@ config SOC_SPI_FLASH_SUPPORTED bool default y +config SOC_CORDIC_SUPPORTED + bool + default y + config SOC_XTAL_SUPPORT_40M bool default y diff --git a/components/soc/esp32s31/include/soc/clk_tree_defs.h b/components/soc/esp32s31/include/soc/clk_tree_defs.h index a0915003d3..9fcf550337 100644 --- a/components/soc/esp32s31/include/soc/clk_tree_defs.h +++ b/components/soc/esp32s31/include/soc/clk_tree_defs.h @@ -358,6 +358,20 @@ typedef enum { CLK_CAL_LP_PLL, /*!< Select to calculate frequency of LP_PLL_CLK */ } soc_clk_freq_calculation_src_t; +//////////////////////////////////////////////////CORDIC/////////////////////////////////////////////////////////// + +/** + * @brief Array initializer for all supported clock sources of CORDIC + */ +#define SOC_CORDIC_CLKS {SOC_MOD_CLK_XTAL, SOC_MOD_CLK_RC_FAST, SOC_MOD_CLK_PLL_F160M} + +typedef enum { + CORDIC_CLK_SRC_XTAL = SOC_MOD_CLK_XTAL, + CORDIC_CLK_SRC_PLL_F160M = SOC_MOD_CLK_PLL_F160M, + CORDIC_CLK_SRC_RC_FAST = SOC_MOD_CLK_RC_FAST, + CORDIC_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F160M, +} soc_periph_cordic_clk_src_t; + #ifdef __cplusplus } #endif diff --git a/components/soc/esp32s31/include/soc/soc_caps.h b/components/soc/esp32s31/include/soc/soc_caps.h index 3abb6f3db8..221b334713 100644 --- a/components/soc/esp32s31/include/soc/soc_caps.h +++ b/components/soc/esp32s31/include/soc/soc_caps.h @@ -96,6 +96,7 @@ // #define SOC_PM_SUPPORTED 1 // TODO: [ESP32S31] IDF-14648 // #define SOC_BITSCRAMBLER_SUPPORTED 1 // TODO: [ESP32S31] IDF-14714 // #define SOC_SIMD_INSTRUCTION_SUPPORTED 1 // TODO: [ESP32S31] IDF-14661 +#define SOC_CORDIC_SUPPORTED 1 /*-------------------------- XTAL CAPS ---------------------------------------*/ #define SOC_XTAL_SUPPORT_40M 1 diff --git a/components/soc/esp32s31/register/soc/cordic_struct.h b/components/soc/esp32s31/register/soc/cordic_struct.h index a589e28c85..58ddbf553a 100644 --- a/components/soc/esp32s31/register/soc/cordic_struct.h +++ b/components/soc/esp32s31/register/soc/cordic_struct.h @@ -303,7 +303,7 @@ typedef union { } cordic_version_reg_t; -typedef struct { +typedef struct cordic_dev_t { volatile cordic_csr_cfg_reg_t csr_cfg; volatile cordic_arg1_reg_t arg1; volatile cordic_arg2_reg_t arg2; @@ -319,6 +319,7 @@ typedef struct { volatile cordic_version_reg_t version; } cordic_dev_t; +extern cordic_dev_t CORDIC; #ifndef __cplusplus _Static_assert(sizeof(cordic_dev_t) == 0x44, "Invalid size of cordic_dev_t structure"); From e35306a02b4764e59000514dbde5d3be5205fcfe Mon Sep 17 00:00:00 2001 From: "C.S.M" Date: Wed, 4 Mar 2026 15:35:26 +0800 Subject: [PATCH 2/3] test(cordic): Add test cases for cordic --- .../test_apps/.build-test-rules.yml | 10 + .../test_apps/cordic_test_apps/CMakeLists.txt | 36 + .../test_apps/cordic_test_apps/README.md | 2 + .../cordic_test_apps/main/CMakeLists.txt | 13 + .../cordic_test_apps/main/idf_component.yml | 5 + .../cordic_test_apps/main/test_app_main.c | 41 + .../cordic_test_apps/main/test_cordic_q15.c | 768 ++++++++++++++++++ .../cordic_test_apps/main/test_cordic_q31.c | 682 ++++++++++++++++ .../cordic_test_apps/sdkconfig.ci.iram_safe | 9 + .../cordic_test_apps/sdkconfig.ci.release | 7 + .../cordic_test_apps/sdkconfig.defaults | 2 + 11 files changed, 1575 insertions(+) create mode 100644 components/esp_driver_cordic/test_apps/.build-test-rules.yml create mode 100644 components/esp_driver_cordic/test_apps/cordic_test_apps/CMakeLists.txt create mode 100644 components/esp_driver_cordic/test_apps/cordic_test_apps/README.md create mode 100644 components/esp_driver_cordic/test_apps/cordic_test_apps/main/CMakeLists.txt create mode 100644 components/esp_driver_cordic/test_apps/cordic_test_apps/main/idf_component.yml create mode 100644 components/esp_driver_cordic/test_apps/cordic_test_apps/main/test_app_main.c create mode 100644 components/esp_driver_cordic/test_apps/cordic_test_apps/main/test_cordic_q15.c create mode 100644 components/esp_driver_cordic/test_apps/cordic_test_apps/main/test_cordic_q31.c create mode 100644 components/esp_driver_cordic/test_apps/cordic_test_apps/sdkconfig.ci.iram_safe create mode 100644 components/esp_driver_cordic/test_apps/cordic_test_apps/sdkconfig.ci.release create mode 100644 components/esp_driver_cordic/test_apps/cordic_test_apps/sdkconfig.defaults diff --git a/components/esp_driver_cordic/test_apps/.build-test-rules.yml b/components/esp_driver_cordic/test_apps/.build-test-rules.yml new file mode 100644 index 0000000000..784bd94bd8 --- /dev/null +++ b/components/esp_driver_cordic/test_apps/.build-test-rules.yml @@ -0,0 +1,10 @@ +# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps + +components/esp_driver_cordic/test_apps/cordic_test_apps: + disable: + - if: SOC_CORDIC_SUPPORTED != 1 + depends_components: + - esp_driver_cordic + - esp_hal_cordic + - esp_hw_support + - soc diff --git a/components/esp_driver_cordic/test_apps/cordic_test_apps/CMakeLists.txt b/components/esp_driver_cordic/test_apps/cordic_test_apps/CMakeLists.txt new file mode 100644 index 0000000000..9455596772 --- /dev/null +++ b/components/esp_driver_cordic/test_apps/cordic_test_apps/CMakeLists.txt @@ -0,0 +1,36 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.22) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(cordic_test) + +idf_build_get_property(elf EXECUTABLE) +if(CONFIG_COMPILER_DUMP_RTL_FILES) + # Collect RTL directories in a variable for readability. Join them + # with commas so they are passed as a single --rtl-dirs argument to the script. + set(CORDIC_RTL_DIRS + ${CMAKE_BINARY_DIR}/esp-idf/esp_driver_cordic + ${CMAKE_BINARY_DIR}/esp-idf/hal + ${CMAKE_BINARY_DIR}/esp-idf/esp_hal_cordic + ) + string(JOIN "," CORDIC_RTL_DIRS_JOINED ${CORDIC_RTL_DIRS}) + + add_custom_target(check_test_app_sections ALL + COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py + --rtl-dirs ${CORDIC_RTL_DIRS_JOINED} + --elf-file ${CMAKE_BINARY_DIR}/cordic_test.elf + find-refs + --from-sections=.iram0.text + --to-sections=.flash.text,.flash.rodata + --exit-code + DEPENDS ${elf} + ) +endif() + +message(STATUS "Checking cordic registers are not read-write by half-word") +include($ENV{IDF_PATH}/tools/ci/check_register_rw_half_word.cmake) +check_register_rw_half_word(SOC_MODULES "cordic" + HAL_MODULES "cordic") diff --git a/components/esp_driver_cordic/test_apps/cordic_test_apps/README.md b/components/esp_driver_cordic/test_apps/cordic_test_apps/README.md new file mode 100644 index 0000000000..1ffc091273 --- /dev/null +++ b/components/esp_driver_cordic/test_apps/cordic_test_apps/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-S31 | +| ----------------- | --------- | \ No newline at end of file diff --git a/components/esp_driver_cordic/test_apps/cordic_test_apps/main/CMakeLists.txt b/components/esp_driver_cordic/test_apps/cordic_test_apps/main/CMakeLists.txt new file mode 100644 index 0000000000..922eb7f26e --- /dev/null +++ b/components/esp_driver_cordic/test_apps/cordic_test_apps/main/CMakeLists.txt @@ -0,0 +1,13 @@ +set(srcs "test_app_main.c" + ) + +if(CONFIG_SOC_CORDIC_SUPPORTED) + list(APPEND srcs + "test_cordic_q15.c" + "test_cordic_q31.c" +) +endif() + +idf_component_register(SRCS ${srcs} + PRIV_REQUIRES unity test_utils esp_driver_cordic ccomp_timer esp_driver_gptimer spi_flash + WHOLE_ARCHIVE) diff --git a/components/esp_driver_cordic/test_apps/cordic_test_apps/main/idf_component.yml b/components/esp_driver_cordic/test_apps/cordic_test_apps/main/idf_component.yml new file mode 100644 index 0000000000..19e4e0856d --- /dev/null +++ b/components/esp_driver_cordic/test_apps/cordic_test_apps/main/idf_component.yml @@ -0,0 +1,5 @@ +dependencies: + ccomp_timer: "^1.0.0" + iqmath: "^1.0.0" + test_utils: + path: ${IDF_PATH}/tools/test_apps/components/test_utils diff --git a/components/esp_driver_cordic/test_apps/cordic_test_apps/main/test_app_main.c b/components/esp_driver_cordic/test_apps/cordic_test_apps/main/test_app_main.c new file mode 100644 index 0000000000..2fa755ae54 --- /dev/null +++ b/components/esp_driver_cordic/test_apps/cordic_test_apps/main/test_app_main.c @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated in CORDIC driver, so we reserved this threshold when checking memory leak +// A better way to check a potential memory leak is running a same case by twice, for the second time, the memory usage delta should be zero +#define LEAKS (400) + +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +void tearDown(void) +{ + unity_utils_evaluate_leaks_direct(LEAKS); +} + +void app_main(void) +{ + // ,-----. ,-----. ,------. ,------. ,--. ,-----. + // ' .--./' .-. '| .--. '| .-. \ | |' .--./ + // | | | | | || '--'.'| | \ :| || | + // ' '--'\' '-' '| |\ \ | '--' /| |' '--' + // `-----' `-----' `--' '--'`-------' `--' `-----' + + printf(" ,-----. ,-----. ,------. ,------. ,--. ,-----. \n"); + printf("' .--./' .-. '| .--. '| .-. \\ | |' .--./ \n"); + printf("| | | | | || '--'.'| | \\ :| || | \n"); + printf("' '--'\\' '-' '| |\\ \\ | '--' /| |' '--'\\\n"); + printf(" `-----' `-----' `--' '--'`-------' `--' `-----'\n"); + + unity_run_menu(); +} diff --git a/components/esp_driver_cordic/test_apps/cordic_test_apps/main/test_cordic_q15.c b/components/esp_driver_cordic/test_apps/cordic_test_apps/main/test_cordic_q15.c new file mode 100644 index 0000000000..85e6ea0ebd --- /dev/null +++ b/components/esp_driver_cordic/test_apps/cordic_test_apps/main/test_cordic_q15.c @@ -0,0 +1,768 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "unity.h" +#include "soc/cordic_reg.h" +#include "hal/cordic_types.h" +#include "hal/cordic_ll.h" +#include "driver/cordic.h" +#include "driver/cordic_types.h" +#include "ccomp_timer.h" +#include "esp_random.h" +#include "test_utils.h" +#include "driver/gptimer.h" +#include "esp_private/cache_utils.h" +#define GLOBAL_IQ (15) +#include "IQmathLib.h" + +#define PI 3.14159265358979323846 +static uint32_t iq_pi_x = _IQ15(PI); + +TEST_CASE("cordic install-uninstall test", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, cordic_new_engine(&engine_config, &engine)); + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test cosine and sine q15", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_COS, + .iq_format = ESP_CORDIC_FORMAT_Q15, + .iteration_count = 4, + .scale_exp = 0, + }; + + // Test range: -π to π in Q15 format + // In Q15 format: -0x8000 = -π, 0x0000 = 0, 0x7FFF ≈ π + // Generate 32 test points uniformly distributed from -π to π + uint32_t data_x[32] = {}; + for (size_t i = 0; i < 32; i++) { + // Map i from [0, 31] to float range [-1.0, 1.0] + // Linear interpolation: float_value = -1.0 + (i * 2.0 / 31) + float float_value = -1.0f + (i * 2.0f / 31.0f); + data_x[i] = cordic_convert_float_to_fixed(float_value, calc_config.iq_format); + } + uint32_t res1[32] = {}; + uint32_t res2[32] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 32)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Q15 format, range: -π to π) ===\n"); + printf("Input(Q15) | Angle(rad) | SW_Cosine | SW_Sine | HW_Cosine | HW_Sine | Cos_Error | Sin_Error\n"); + printf("-----------|------------|------------|------------|------------|------------|------------|----------\n"); + + for (size_t i = 0; i < 32; i++) { + // Convert Q15 input angle to radians + // Q15 format: angle_rad = (int16_t)data_x / 32768.0 * π + // Range: -π (data_x = -0x8000) to π (data_x = 0x7FFF) + float angle_rad = cordic_convert_fixed_to_float(data_x[i], calc_config.iq_format) * M_PI; + + // Software calculation using standard math library + float sw_cosine = cosf(angle_rad); + float sw_sine = sinf(angle_rad); + // Convert hardware results to float using cordic_convert_fixed_to_float + float hw_cosine = cordic_convert_fixed_to_float(res1[i] & 0xFFFF, calc_config.iq_format); + float hw_sine = cordic_convert_fixed_to_float(res2[i] & 0xFFFF, calc_config.iq_format); + float cos_error = fabsf(sw_cosine - hw_cosine); + float sin_error = fabsf(sw_sine - hw_sine); + + // Print comparison results + printf("0x%04lx | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f", + (data_x[i] & 0xFFFF), angle_rad, sw_cosine, sw_sine, hw_cosine, hw_sine, cos_error, sin_error); + + TEST_ASSERT_FLOAT_WITHIN(0.01f, sw_cosine, hw_cosine); + TEST_ASSERT_FLOAT_WITHIN(0.01f, sw_sine, hw_sine); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test phase and module q15", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_PHASE, + .iq_format = ESP_CORDIC_FORMAT_Q15, + .iteration_count = 4, + .scale_exp = 0, + }; + + uint32_t data_x[60] = {}; + uint32_t data_y[60] = {}; + for (size_t i = 0; i < 60; i++) { + // Generate x and y values in range (-1, 1) + // Map i from [0, 59] to float range [-1.0, 1.0] + // Use different patterns for x and y to cover more combinations + float x_float = -1.0f + (i * 2.0f / 59.0f); + float y_float = -1.0f + (((i * 3) % 60) * 2.0f / 59.0f); + data_x[i] = cordic_convert_float_to_fixed(x_float, calc_config.iq_format); + data_y[i] = cordic_convert_float_to_fixed(y_float, calc_config.iq_format); + } + uint32_t res1[60] = {}; + uint32_t res2[60] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = data_y, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 60)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Phase and Module Q15 format, range: -1 to 1) ===\n"); + printf("Input_X(Q15) | Input_Y(Q15) | SW_Phase | SW_Module | HW_Phase | HW_Module | Phase_Err | Module_Err\n"); + printf("-------------|-------------|------------|------------|------------|------------|------------|-----------\n"); + + for (size_t i = 0; i < 60; i++) { + // Convert Q15 format inputs to float + // Q15 format: float_value = (int16_t)q15_value / 32768.0f + float x_float = cordic_convert_fixed_to_float(data_x[i], calc_config.iq_format); + float y_float = cordic_convert_fixed_to_float(data_y[i], calc_config.iq_format); + + // Software calculation: phase = atan2(y, x) / π, module = sqrt(x^2 + y^2) + float sw_phase = atan2f(y_float, x_float) / M_PI; + float sw_module = sqrtf(x_float * x_float + y_float * y_float); + // Convert hardware results to float using cordic_convert_fixed_to_float + uint16_t hw_module_q15 = (uint16_t)(res2[i] & 0xFFFF); + if (hw_module_q15 == 0x7FFF) { + continue; // Skip this entry, do not print + } + float hw_phase = cordic_convert_fixed_to_float(res1[i] & 0xFFFF, calc_config.iq_format); + float hw_module = cordic_convert_fixed_to_float(res2[i] & 0xFFFF, calc_config.iq_format); + float phase_error = fabsf(sw_phase - hw_phase); + float module_error = fabsf(sw_module - hw_module); + + // Print comparison results + printf("%10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f", + x_float, y_float, sw_phase, sw_module, hw_phase, hw_module, phase_error, module_error); + + TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_phase, hw_phase); + TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_module, hw_module); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test arctan q15", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_ARCTAN, + .iq_format = ESP_CORDIC_FORMAT_Q15, + .iteration_count = 4, + .scale_exp = 0, + }; + + // Test range: data_x in (-1, 1) in Q15 format + // In Q15 format: -0x8000 = -1.0, 0x0000 = 0.0, 0x7FFF ≈ 0.999969 + // Generate 32 test points uniformly distributed from -1 to 1 + uint32_t data_x[32] = {}; + for (size_t i = 0; i < 32; i++) { + // Map i from [0, 31] to float range [-1.0, 1.0] (avoiding exact -1 and 1) + float x_float = -1.0f + (i * 2.0f / 31.0f); + data_x[i] = cordic_convert_float_to_fixed(x_float, calc_config.iq_format); + } + uint32_t res1[32] = {}; + uint32_t res2[32] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 32)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Arctan Q15 format, input range: -1 to 1) ===\n"); + printf("Input(Q15) | X_Float | SW_Arctan | HW_Arctan | Error | Arctan(rad)\n"); + printf("-----------|------------|------------|------------|------------|------------\n"); + + for (size_t i = 0; i < 32; i++) { + float x_float = cordic_convert_fixed_to_float(data_x[i], calc_config.iq_format); + float sw_arctan_rad = atanf(x_float); + // Normalize to [-1, 1] range (divide by π for Q15 format output) + // Q15 arctan output is typically normalized: arctan(x) / π + float sw_arctan_normalized = sw_arctan_rad / M_PI; + // Convert hardware result to float using cordic_convert_fixed_to_float + float hw_arctan_normalized = cordic_convert_fixed_to_float(res1[i] & 0xFFFF, calc_config.iq_format); + float arctan_error = fabsf(sw_arctan_normalized - hw_arctan_normalized); + + // Print comparison results + printf("0x%04lx | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f", + data_x[i], x_float, sw_arctan_normalized, hw_arctan_normalized, arctan_error, sw_arctan_rad); + + // Highlight significant errors + TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_arctan_normalized, hw_arctan_normalized); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test hyperbolic cosine and sine q15", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_COSH, + .iq_format = ESP_CORDIC_FORMAT_Q15, + .iteration_count = 4, + .scale_exp = 1, + }; + + // Test range: data_x in (-0.559, 0.559) in Q15 format + // In Q15 format: -0.559 * 32768 = -18320.512, 0.559 * 32768 = 18320.512 + // Generate 32 test points uniformly distributed from -0.559 to 0.559 + uint32_t data_x[32] = {}; + for (size_t i = 0; i < 32; i++) { + // Map i from [0, 31] to float range [-0.559, 0.559] + float x_float = -0.559f + (i * (0.559f - (-0.559f)) / 31.0f); + data_x[i] = cordic_convert_float_to_fixed(x_float, calc_config.iq_format); + } + uint32_t res1[32] = {}; + uint32_t res2[32] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 32)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Hyperbolic Cosine and Sine Q15 format, input range: -0.559 to 0.559) ===\n"); + printf("X_Float | SW_Cosh | SW_Sinh | HW_Cosh | HW_Sinh | Cosh_Err | Sinh_Err\n"); + printf("-------------|------------|------------|------------|------------|------------|----------\n"); + + for (size_t i = 0; i < 32; i++) { + // Convert Q15 format input to float + // Q15 format: float_value = (int16_t)q15_value / 32768.0f + float x_float = cordic_convert_fixed_to_float(data_x[i], calc_config.iq_format) * 2.0f; // multi 2 + + // Software calculation using standard math library + float sw_cosh = cosh(x_float); + float sw_sinh = sinh(x_float); + // Convert hardware results to float using cordic_convert_fixed_to_float + float hw_cosh_temp = cordic_convert_fixed_to_float(res1[i] & 0xFFFF, calc_config.iq_format); + float hw_sinh_temp = cordic_convert_fixed_to_float(res2[i] & 0xFFFF, calc_config.iq_format); + float hw_cosh = hw_cosh_temp * 2.0f; // multi 2 + float hw_sinh = hw_sinh_temp * 2.0f; // multi 2 + + // Calculate errors + float cosh_error = fabsf(sw_cosh - hw_cosh); + float sinh_error = fabsf(sw_sinh - hw_sinh); + + // Print comparison results + printf("%10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f", + x_float, sw_cosh, sw_sinh, hw_cosh, hw_sinh, cosh_error, sinh_error); + + TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_cosh, hw_cosh); + TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_sinh, hw_sinh); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test arctanh q15", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_ARCHTANH, + .iq_format = ESP_CORDIC_FORMAT_Q15, + .iteration_count = 4, + .scale_exp = 1, + }; + + // Test range: data_x in (-0.403, 0.403) in Q15 format + // In Q15 format: -0.403 * 32768 = -13205.504, 0.403 * 32768 = 13205.504 + // Generate 32 test points uniformly distributed from -0.403 to 0.403 + uint32_t data_x[32] = {}; + for (size_t i = 0; i < 32; i++) { + // Map i from [0, 31] to float range [-0.403, 0.403] + float x_float = -0.403f + (i * (0.403f - (-0.403f)) / 31.0f); + data_x[i] = cordic_convert_float_to_fixed(x_float, calc_config.iq_format); + } + uint32_t res1[32] = {}; + uint32_t res2[32] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 32)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Arctanh Q15 format, input range: -0.403 to 0.403) ===\n"); + printf("Input(Q15) | X_Float | SW_Arctanh | HW_Arctanh | Error | Arctanh(rad)\n"); + printf("-----------|------------|------------|------------|------------|------------\n"); + + for (size_t i = 0; i < 32; i++) { + // Convert Q15 format input to float + // Q15 format: float_value = (int16_t)q15_value / 32768.0f + float x_float = cordic_convert_fixed_to_float(data_x[i], calc_config.iq_format) * 2.0f; // multi 2 + + // Software calculation: arctanh(x) in radians + float sw_arctanh_rad = atanh(x_float); + // Normalize to [-1, 1] range (divide by π for Q15 format output) + // Q15 arctanh output is typically normalized: arctanh(x) / π + float sw_arctanh_normalized = sw_arctanh_rad; + // Convert hardware result to float using cordic_convert_fixed_to_float + float hw_arctanh_temp = cordic_convert_fixed_to_float(res1[i] & 0xFFFF, calc_config.iq_format); + float hw_arctanh_normalized = hw_arctanh_temp * 2.0f; // multi 2 + float arctanh_error = fabsf(sw_arctanh_normalized - hw_arctanh_normalized); + + // Print comparison results + printf("0x%04lx | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f", + data_x[i] & 0xFFFF, x_float, sw_arctanh_normalized, hw_arctanh_normalized, arctanh_error, sw_arctanh_rad); + + TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_arctanh_normalized, hw_arctanh_normalized); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test natural logarithm q15", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_LOGE, + .iq_format = ESP_CORDIC_FORMAT_Q15, + .iteration_count = 4, + .scale_exp = 1, + }; + + // Test range: x in (0.1069, 9.3573) + // Hardware parameter: (x-1)/(x+1) * (2^-1), which should be in [-0.403, 0.403] + // Hardware output: (ln x) * (2^-2), so we need to multiply by 4 to get ln(x) + // Generate 32 test points uniformly distributed from 0.1069 to 9.3573 + float x_values[32] = {}; + uint32_t data_x[32] = {}; + for (size_t i = 0; i < 32; i++) { + // Map i from [0, 31] to x range [0.1069, 9.3573] + float x = 0.1069f + (i * (9.3573f - 0.1069f) / 31.0f); + x_values[i] = x; + + // Calculate hardware parameter: (x-1)/(x+1) * 0.5 + float hw_param = ((x - 1.0f) / (x + 1.0f)) * 0.5f; + + // Convert to Q15 format + data_x[i] = cordic_convert_float_to_fixed(hw_param, calc_config.iq_format); + } + uint32_t res1[32] = {}; + uint32_t res2[32] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 32)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Natural Logarithm Q15 format, x range: 0.1069 to 9.3573) ===\n"); + printf("X_Value | HW_Param | SW_LnX | HW_LnX | Error | LnX\n"); + printf("------------|------------|------------|------------|------------|------------\n"); + + for (size_t i = 0; i < 32; i++) { + float x = x_values[i]; + + // Calculate hardware parameter: (x-1)/(x+1) * 0.5 + float hw_param = ((x - 1.0f) / (x + 1.0f)) * 0.5f; + + // Software calculation: ln(x) + float sw_lnx = logf(x); + // Convert hardware result to float using cordic_convert_fixed_to_float + float hw_lnx_scaled = cordic_convert_fixed_to_float(res1[i] & 0xFFFF, calc_config.iq_format); + float hw_lnx = hw_lnx_scaled * 4.0f; + float lnx_error = fabsf(sw_lnx - hw_lnx); + + // Print comparison results + printf("%10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f", + x, hw_param, sw_lnx, hw_lnx, lnx_error, sw_lnx); + TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_lnx, hw_lnx); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test square root q15", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_SQUARE_ROOT, + .iq_format = ESP_CORDIC_FORMAT_Q15, + .iteration_count = 4, + .scale_exp = 0, + }; + + // Test range: x in (0.1069, 1) + // Hardware input: x (directly) + // Hardware output: sqrt(x) + // Generate 32 test points uniformly distributed from 0.1069 to 0.999 (avoiding exact 1) + float x_values[32] = {}; + uint32_t data_x[32] = {}; + for (size_t i = 0; i < 32; i++) { + // Map i from [0, 31] to x range (0.1069, 1) + float x = 0.1069f + (i * (0.999f - 0.1069f) / 31.0f); + x_values[i] = x; + + // Convert x to Q15 format + data_x[i] = cordic_convert_float_to_fixed(x, calc_config.iq_format); + } + uint32_t res1[32] = {}; + uint32_t res2[32] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 32)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Square Root Q15 format, x range: 0.1069 to 1) ===\n"); + printf("X_Value | SW_SqrtX | HW_SqrtX | Error\n"); + printf("------------|------------|------------|----------\n"); + + for (size_t i = 0; i < 32; i++) { + float x = x_values[i]; + + float sw_sqrtx = sqrtf(x); + // Convert hardware result to float using cordic_convert_fixed_to_float + float hw_sqrtx = cordic_convert_fixed_to_float(res1[i] & 0xFFFF, calc_config.iq_format); + float sqrtx_error = fabsf(sw_sqrtx - hw_sqrtx); + + // Print comparison results + printf("%10.6f | %10.6f | %10.6f | %10.6f", + x, sw_sqrtx, hw_sqrtx, sqrtx_error); + + TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_sqrtx, hw_sqrtx); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test arctan scale 2 q15", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_ARCTAN, + .iq_format = ESP_CORDIC_FORMAT_Q15, + .iteration_count = 4, + .scale_exp = 2, // Scale = 2: hardware input is (x) * 2^(-2) = x / 4 + }; + + // Test range: hardware input (x * 2^(-2)) should be in [-1, 1] + // So actual x range should be [-4, 4] in Q15 format + // Note: Q15 format range is [-1, 1], but we need to input values that represent [-4, 4] + // Hardware will scale: x_hw = x * 2^(-2) = x / 4, so x should be in [-4, 4] range + // Generate 32 test points uniformly distributed from -4 to 4 + uint32_t data_x[32] = {}; + for (size_t i = 0; i < 32; i++) { + // Map i from [0, 31] to float range [-4, 4] + float x_float = -4.0f + (i * 8.0f / 31.0f); + // Convert to Q15 format: x_float is in [-4, 4], but Q15 range is [-1, 1] + // So we need to represent x_float / 4 in Q15 format + float x_normalized = x_float / 4.0f; // Normalize to [-1, 1] range + data_x[i] = cordic_convert_float_to_fixed(x_normalized, calc_config.iq_format); + } + uint32_t res1[32] = {}; + uint32_t res2[32] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 32)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Arctan Q15 format, scale=2, input range: -4 to 4) ===\n"); + printf("Input(Q15) | X_Float | SW_Arctan | HW_Arctan | Error | Arctan(rad)\n"); + printf("-----------|------------|------------|------------|------------|------------\n"); + + for (size_t i = 0; i < 32; i++) { + // Convert Q15 input to float using cordic_convert_fixed_to_float + float x_normalized = cordic_convert_fixed_to_float(data_x[i], calc_config.iq_format); // This is in [-1, 1] range + // Reconstruct original x value: x_float = x_normalized * 4 + float x_float = x_normalized * 4.0f; // Original x in [-4, 4] range + // Hardware scales input: x_scaled = x_float * 2^(-2) = x_float / 4 = x_normalized + // float x_scaled = x_float / 4.0f; // This equals x_normalized + + // Software calculation: arctan(x_scaled) / π + float sw_arctan_rad = atanf(x_float); + float sw_arctan_normalized = sw_arctan_rad / M_PI; + + // Hardware output is already arctan(x * 2^(-2)) / π = arctan(x_scaled) / π + // Convert Q15 result to float using cordic_convert_fixed_to_float + // Note: res1[i] * 4 is used for scale adjustment, but we need to convert the raw value first + float hw_arctan_temp = cordic_convert_fixed_to_float(res1[i] & 0xFFFF, calc_config.iq_format); + float hw_arctan_normalized = hw_arctan_temp * 4; + + float arctan_error = fabsf(sw_arctan_normalized - hw_arctan_normalized); + + // Print comparison results + printf("0x%04lx | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f", + data_x[i] & 0xFFFF, x_float, sw_arctan_normalized, hw_arctan_normalized, arctan_error, sw_arctan_rad); + + // Highlight significant errors + TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_arctan_normalized, hw_arctan_normalized); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test cordic performance should quicker than software iqmath", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_COS, + .iq_format = ESP_CORDIC_FORMAT_Q15, + .iteration_count = 4, + .scale_exp = 0, + }; + + const int test_seed = 999; + srand(test_seed); + uint32_t iq_radians[36] = {}; + uint32_t data_x[36] = {}; + for (size_t i = 0; i < 36; i++) { + uint32_t rand_value = rand() % (0xFFFF + 1); + data_x[i] = rand_value; + iq_radians[i] = _IQmpy(rand_value, iq_pi_x); + } + + uint32_t res1[36] = {}; + uint32_t res2[36] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + ccomp_timer_start(); + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 36)); + + int64_t cordic_time = ccomp_timer_stop(); + printf("CORDIC time: %lld us\n", cordic_time); + + // Use volatile variables to prevent compiler optimization + volatile uint32_t dummy_cos = 0; + volatile uint32_t dummy_sin = 0; + + ccomp_timer_start(); + for (uint32_t i = 0; i < 36; i++) { + // Assign results to volatile variables to prevent compiler optimization + dummy_cos = _IQcos(iq_radians[i]); + dummy_sin = _IQsin(iq_radians[i]); + } + int64_t iqmath_time = ccomp_timer_stop(); + printf("IQMATH time: %lld us\n", iqmath_time); + + IDF_LOG_PERFORMANCE("CORDIC_TIME", "Cordic time sine cosine is %lld us", cordic_time); + TEST_ASSERT_LESS_THAN(iqmath_time, cordic_time); + + // Use the volatile variables to prevent dead code elimination + (void)dummy_cos; + (void)dummy_sin; + TEST_ESP_OK(cordic_delete_engine(engine)); + +} + +#if CONFIG_CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM + +/*--------------------------------------------------------------- + CORDIC work with ISR +---------------------------------------------------------------*/ +typedef struct { + TaskHandle_t task_handle; //Task handle + cordic_engine_handle_t engine_handle; // engine handle + uint32_t res1; + uint32_t res2; +} test_cordic_isr_ctx_t; + +static bool IRAM_ATTR s_alarm_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + test_cordic_isr_ctx_t *test_ctx = (test_cordic_isr_ctx_t *)user_data; + BaseType_t high_task_wakeup; + + /** + * This test would disable the cache, so must put code between cache disable and cache enable in IRAM + */ + uint32_t data_x = 0x123; + uint32_t res1 = 0; + uint32_t res2 = 0; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = &data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = &res1, + .p_data_res2 = &res2, + }; + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_COS, + .iq_format = ESP_CORDIC_FORMAT_Q15, + .iteration_count = 4, + .scale_exp = 0, + }; + spi_flash_disable_interrupts_caches_and_other_cpu(); + TEST_ESP_OK(cordic_calculate_polling(test_ctx->engine_handle, &calc_config, &input_buffer, &output_buffer, 1)); + spi_flash_enable_interrupts_caches_and_other_cpu(); + + test_ctx->res1 = res1; + test_ctx->res2 = res2; + + vTaskNotifyGiveFromISR(test_ctx->task_handle, &high_task_wakeup); + return high_task_wakeup == pdTRUE; +} + +TEST_CASE("cordic can calculation in ISR", "[cordic]") +{ + static test_cordic_isr_ctx_t isr_test_ctx = {}; + isr_test_ctx.engine_handle = NULL; + isr_test_ctx.task_handle = xTaskGetCurrentTaskHandle(); + + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &isr_test_ctx.engine_handle)); + + //-------------GPTimer Init & Config---------------// + gptimer_handle_t timer = NULL; + gptimer_config_t timer_config = { + .resolution_hz = 1 * 1000 * 1000, + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + }; + TEST_ESP_OK(gptimer_new_timer(&timer_config, &timer)); + + gptimer_event_callbacks_t cbs = { + .on_alarm = s_alarm_callback, + }; + gptimer_alarm_config_t alarm_config = { + .reload_count = 0, + .alarm_count = 100000, // 100ms + }; + TEST_ESP_OK(gptimer_set_alarm_action(timer, &alarm_config)); + TEST_ESP_OK(gptimer_register_event_callbacks(timer, &cbs, &isr_test_ctx)); + + printf("start timer\r\n"); + TEST_ESP_OK(gptimer_enable(timer)); + TEST_ESP_OK(gptimer_start(timer)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + esp_rom_printf(DRAM_STR("CORDIC calculation done, cos is 0x%lx, sine is 0x%lx\n"), isr_test_ctx.res1, isr_test_ctx.res2); + TEST_ESP_OK(gptimer_stop(timer)); + + TEST_ESP_OK(gptimer_disable(timer)); + TEST_ESP_OK(gptimer_del_timer(timer)); + TEST_ESP_OK(cordic_delete_engine(isr_test_ctx.engine_handle)); + +} + +#endif diff --git a/components/esp_driver_cordic/test_apps/cordic_test_apps/main/test_cordic_q31.c b/components/esp_driver_cordic/test_apps/cordic_test_apps/main/test_cordic_q31.c new file mode 100644 index 0000000000..9b81167ced --- /dev/null +++ b/components/esp_driver_cordic/test_apps/cordic_test_apps/main/test_cordic_q31.c @@ -0,0 +1,682 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "unity.h" +#include "soc/cordic_reg.h" +#include "hal/cordic_types.h" +#include "hal/cordic_ll.h" +#include "driver/cordic.h" +#include "driver/cordic_types.h" + +TEST_CASE("test cosine and sine q31", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_COS, + .iq_format = ESP_CORDIC_FORMAT_Q31, + .iteration_count = 4, + .scale_exp = 0, + }; + + uint32_t data_x[32] = {}; + for (size_t i = 0; i < 32; i++) { + // Map i from [0, 31] to float range [-1.0, 1.0] + float float_value = -1.0f + (i * 2.0f / 31.0f); + data_x[i] = cordic_convert_float_to_fixed(float_value, calc_config.iq_format); + } + uint32_t res1[32] = {}; + uint32_t res2[32] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 32)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Q31 format) ===\n"); + printf("Input(Q31) | SW_Cosine | SW_Sine | HW_Cosine | HW_Sine | Cos_Error | Sin_Error\n"); + printf("-------------|------------|------------|------------|------------|------------|----------\n"); + + for (size_t i = 0; i < 32; i++) { + float angle_rad = cordic_convert_fixed_to_float(data_x[i], calc_config.iq_format) * M_PI; + float sw_cosine = cosf(angle_rad); + float sw_sine = sinf(angle_rad); + // Convert hardware results to float using cordic_convert_fixed_to_float + float hw_cosine = cordic_convert_fixed_to_float(res1[i], calc_config.iq_format); + float hw_sine = cordic_convert_fixed_to_float(res2[i], calc_config.iq_format); + float cos_error = fabsf(sw_cosine - hw_cosine); + float sin_error = fabsf(sw_sine - hw_sine); + + // Print comparison results + printf("0x%08lx | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f", + data_x[i], sw_cosine, sw_sine, hw_cosine, hw_sine, cos_error, sin_error); + + TEST_ASSERT_FLOAT_WITHIN(0.0001f, sw_cosine, hw_cosine); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, sw_sine, hw_sine); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test phase and module q31", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_PHASE, + .iq_format = ESP_CORDIC_FORMAT_Q31, + .iteration_count = 4, + .scale_exp = 0, + }; + + // Test range: data_x and data_y in (-1, 1) in Q31 format + // In Q31 format: -0x80000000 = -1.0, 0x00000000 = 0.0, 0x7FFFFFFF ≈ 0.999999999 + // Generate 60 test points uniformly distributed in (-1, 1) x (-1, 1) + uint32_t data_x[60] = {}; + uint32_t data_y[60] = {}; + for (size_t i = 0; i < 60; i++) { + // Generate x and y values in range (-1, 1) + // Map i from [0, 59] to float range [-1.0, 1.0] (avoiding exact -1 and 1) + // Use different patterns for x and y to cover more combinations + float x_float = -1.0f + (i * 2.0f / 59.0f); + float y_float = -1.0f + (((i * 3) % 60) * 2.0f / 59.0f); + data_x[i] = cordic_convert_float_to_fixed(x_float, calc_config.iq_format); + data_y[i] = cordic_convert_float_to_fixed(y_float, calc_config.iq_format); + } + uint32_t res1[60] = {}; + uint32_t res2[60] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = data_y, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 60)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Phase and Module Q31 format, range: -1 to 1) ===\n"); + printf("X_Float | Y_Float | SW_Phase | SW_Module | HW_Phase | HW_Module | Phase_Err | Module_Err\n"); + printf("-------------|-------------|------------|------------|------------|------------|------------|-----------\n"); + + for (size_t i = 0; i < 60; i++) { + float x_float = cordic_convert_fixed_to_float(data_x[i], calc_config.iq_format); + float y_float = cordic_convert_fixed_to_float(data_y[i], calc_config.iq_format); + float sw_phase = atan2f(y_float, x_float) / M_PI; + float sw_module = sqrtf(x_float * x_float + y_float * y_float); + // Convert hardware results to float using cordic_convert_fixed_to_float + uint32_t hw_module_q31 = res2[i]; + if (hw_module_q31 == 0x7FFFFFFF) { + continue; // Skip this entry, do not print + } + float hw_phase = cordic_convert_fixed_to_float(res1[i], calc_config.iq_format); + float hw_module = cordic_convert_fixed_to_float(res2[i], calc_config.iq_format); + float phase_error = fabsf(sw_phase - hw_phase); + float module_error = fabsf(sw_module - hw_module); + + // Print comparison results + printf("%10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f", + x_float, y_float, sw_phase, sw_module, hw_phase, hw_module, phase_error, module_error); + + TEST_ASSERT_FLOAT_WITHIN(0.0001f, sw_phase, hw_phase); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, sw_module, hw_module); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test arctan q31", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_ARCTAN, + .iq_format = ESP_CORDIC_FORMAT_Q31, + .iteration_count = 4, + .scale_exp = 0, + }; + + // Test range: data_x in (-1, 1) in Q31 format + // In Q31 format: -0x80000000 = -1.0, 0x00000000 = 0.0, 0x7FFFFFFF ≈ 0.999999999 + // Generate 32 test points uniformly distributed from -1 to 1 + uint32_t data_x[32] = {}; + for (size_t i = 0; i < 32; i++) { + // Map i from [0, 31] to float range [-1.0, 1.0] (avoiding exact -1 and 1) + float x_float = -1.0f + (i * 2.0f / 31.0f); + data_x[i] = cordic_convert_float_to_fixed(x_float, calc_config.iq_format); + } + uint32_t res1[32] = {}; + uint32_t res2[32] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 32)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Arctan Q31 format, input range: -1 to 1) ===\n"); + printf("X_Float | SW_Arctan | HW_Arctan | Error | Arctan(rad)\n"); + printf("-------------|------------|------------|------------|------------\n"); + + for (size_t i = 0; i < 32; i++) { + float x_float = cordic_convert_fixed_to_float(data_x[i], calc_config.iq_format); + float sw_arctan_rad = atanf(x_float); + // Normalize to [-1, 1] range (divide by π for Q31 format output) + // Q31 arctan output is typically normalized: arctan(x) / π + float sw_arctan_normalized = sw_arctan_rad / M_PI; + // Convert hardware result to float using cordic_convert_fixed_to_float + float hw_arctan_normalized = cordic_convert_fixed_to_float(res1[i], calc_config.iq_format); + float arctan_error = fabsf(sw_arctan_normalized - hw_arctan_normalized); + + // Print comparison results + printf("%10.6f | %10.6f | %10.6f | %10.6f", + x_float, sw_arctan_normalized, hw_arctan_normalized, arctan_error); + + TEST_ASSERT_FLOAT_WITHIN(0.0001f, sw_arctan_normalized, hw_arctan_normalized); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test hyperbolic cosine and sine q31", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_COSH, + .iq_format = ESP_CORDIC_FORMAT_Q31, + .iteration_count = 4, + .scale_exp = 1, + }; + + // Test range: data_x in (-0.559, 0.559) in Q31 format + // In Q31 format: -0.559 * 2147483648 = -1200611353.6, 0.559 * 2147483648 = 1200611353.6 + // Generate 32 test points uniformly distributed from -0.559 to 0.559 + uint32_t data_x[32] = {}; + for (size_t i = 0; i < 32; i++) { + // Map i from [0, 31] to float range [-0.559, 0.559] + float x_float = -0.559f + (i * (0.559f - (-0.559f)) / 31.0f); + data_x[i] = cordic_convert_float_to_fixed(x_float, calc_config.iq_format); + } + uint32_t res1[32] = {}; + uint32_t res2[32] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 32)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Hyperbolic Cosine and Sine Q31 format, input range: -0.559 to 0.559) ===\n"); + printf("X_Float | SW_Cosh | SW_Sinh | HW_Cosh | HW_Sinh | Cosh_Err | Sinh_Err\n"); + printf("-------------|------------|------------|------------|------------|------------|----------\n"); + + for (size_t i = 0; i < 32; i++) { + float x_float = cordic_convert_fixed_to_float(data_x[i], calc_config.iq_format) * 2.0f; + float sw_cosh = cosh(x_float); + float sw_sinh = sinh(x_float); + // Convert hardware results to float using cordic_convert_fixed_to_float + float hw_cosh_temp = cordic_convert_fixed_to_float(res1[i], calc_config.iq_format); + float hw_sinh_temp = cordic_convert_fixed_to_float(res2[i], calc_config.iq_format); + float hw_cosh = hw_cosh_temp * 2.0f; + float hw_sinh = hw_sinh_temp * 2.0f; + float cosh_error = fabsf(sw_cosh - hw_cosh); + float sinh_error = fabsf(sw_sinh - hw_sinh); + + // Print comparison results + printf("%10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f", + x_float, sw_cosh, sw_sinh, hw_cosh, hw_sinh, cosh_error, sinh_error); + + TEST_ASSERT_FLOAT_WITHIN(0.0001f, sw_cosh, hw_cosh); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, sw_sinh, hw_sinh); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test arctanh q31", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_ARCHTANH, + .iq_format = ESP_CORDIC_FORMAT_Q31, + .iteration_count = 4, + .scale_exp = 1, + }; + + // Test range: data_x in (-0.403, 0.403) in Q31 format + // In Q31 format: -0.403 * 2147483648 ≈ -865536000, 0.403 * 2147483648 ≈ 865536000 + // Generate 32 test points uniformly distributed from -0.403 to 0.403 + uint32_t data_x[32] = {}; + for (size_t i = 0; i < 32; i++) { + // Map i from [0, 31] to float range [-0.403, 0.403] + float x_float = -0.403f + (i * (0.403f - (-0.403f)) / 31.0f); + data_x[i] = cordic_convert_float_to_fixed(x_float, calc_config.iq_format); + } + uint32_t res1[32] = {}; + uint32_t res2[32] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 32)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Arctanh Q31 format, input range: -0.403 to 0.403) ===\n"); + printf("X_Float | SW_Arctanh | HW_Arctanh | Error | Arctanh(rad)\n"); + printf("-------------|------------|------------|------------|------------\n"); + + for (size_t i = 0; i < 32; i++) { + float x_float = cordic_convert_fixed_to_float(data_x[i], calc_config.iq_format) * 2.0f; + float sw_arctanh_rad = atanh(x_float); + // Normalize to [-1, 1] range (divide by π for Q31 format output) + // Q31 arctanh output is typically normalized: arctanh(x) / π + float sw_arctanh_normalized = sw_arctanh_rad; + // Convert hardware result to float using cordic_convert_fixed_to_float + float hw_arctanh_temp = cordic_convert_fixed_to_float(res1[i], calc_config.iq_format); + float hw_arctanh_normalized = hw_arctanh_temp * 2.0f; + float arctanh_error = fabsf(sw_arctanh_normalized - hw_arctanh_normalized); + + // Print comparison results + printf("%10.6f | %10.6f | %10.6f | %10.6f | %10.6f", + x_float, sw_arctanh_normalized, hw_arctanh_normalized, arctanh_error, sw_arctanh_rad); + + TEST_ASSERT_FLOAT_WITHIN(0.0001f, sw_arctanh_normalized, hw_arctanh_normalized); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test natural logarithm q31", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_LOGE, + .iq_format = ESP_CORDIC_FORMAT_Q31, + .iteration_count = 4, + .scale_exp = 1, + }; + + // Test range: x in (0.1069, 9.3573) + // Hardware parameter: (x-1)/(x+1) * (2^-1), which should be in [-0.403, 0.403] + // Hardware output: (ln x) * (2^-2), so we need to multiply by 4 to get ln(x) + // Generate 32 test points uniformly distributed from 0.1069 to 9.3573 + float x_values[32] = {}; + uint32_t data_x[32] = {}; + for (size_t i = 0; i < 32; i++) { + // Map i from [0, 31] to x range [0.1069, 9.3573] + float x = 0.1069f + (i * (9.3573f - 0.1069f) / 31.0f); + x_values[i] = x; + + // Calculate hardware parameter: (x-1)/(x+1) * 0.5 + float hw_param = ((x - 1.0f) / (x + 1.0f)) * 0.5f; + + // Convert to Q31 format + data_x[i] = cordic_convert_float_to_fixed(hw_param, calc_config.iq_format); + } + uint32_t res1[32] = {}; + uint32_t res2[32] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 32)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Natural Logarithm Q31 format, x range: 0.1069 to 9.3573) ===\n"); + printf("X_Value | HW_Param | SW_LnX | HW_LnX | Error | LnX\n"); + printf("------------|------------|------------|------------|------------|------------\n"); + + for (size_t i = 0; i < 32; i++) { + float x = x_values[i]; + + // Calculate hardware parameter: (x-1)/(x+1) * 0.5 + float hw_param = ((x - 1.0f) / (x + 1.0f)) * 0.5f; + + // Software calculation: ln(x) + float sw_lnx = logf(x); + // Convert hardware result to float using cordic_convert_fixed_to_float + float hw_lnx_scaled = cordic_convert_fixed_to_float(res1[i], calc_config.iq_format); + float hw_lnx = hw_lnx_scaled * 4.0f; + float lnx_error = fabsf(sw_lnx - hw_lnx); + + // Print comparison results + printf("%10.6f | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f", + x, hw_param, sw_lnx, hw_lnx, lnx_error, sw_lnx); + TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_lnx, hw_lnx); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test square root q31", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_SQUARE_ROOT, + .iq_format = ESP_CORDIC_FORMAT_Q31, + .iteration_count = 4, + .scale_exp = 0, + }; + + // Test range: x in (0.1069, 1) + // Hardware input: x (directly) + // Hardware output: sqrt(x) + // Generate 32 test points uniformly distributed from 0.1069 to 0.999 (avoiding exact 1) + float x_values[32] = {}; + uint32_t data_x[32] = {}; + for (size_t i = 0; i < 32; i++) { + // Map i from [0, 31] to x range (0.1069, 1) + float x = 0.1069f + (i * (0.999f - 0.1069f) / 31.0f); + x_values[i] = x; + + // Convert x to Q31 format + data_x[i] = cordic_convert_float_to_fixed(x, calc_config.iq_format); + } + uint32_t res1[32] = {}; + uint32_t res2[32] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 32)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Square Root Q31 format, x range: 0.1069 to 1) ===\n"); + printf("X_Value | SW_SqrtX | HW_SqrtX | Error\n"); + printf("------------|------------|------------|----------\n"); + + for (size_t i = 0; i < 32; i++) { + float x = x_values[i]; + float sw_sqrtx = sqrtf(x); + // Convert hardware result to float using cordic_convert_fixed_to_float + float hw_sqrtx = cordic_convert_fixed_to_float(res1[i], calc_config.iq_format); + float sqrtx_error = fabsf(sw_sqrtx - hw_sqrtx); + + // Print comparison results + printf("%10.6f | %10.6f | %10.6f | %10.6f", + x, sw_sqrtx, hw_sqrtx, sqrtx_error); + TEST_ASSERT_FLOAT_WITHIN(0.00001f, sw_sqrtx, hw_sqrtx); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test arctan scale 2 q31", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config = { + .function = ESP_CORDIC_FUNC_ARCTAN, + .iq_format = ESP_CORDIC_FORMAT_Q31, + .iteration_count = 4, + .scale_exp = 2, // Scale = 2: hardware input is (x) * 2^(-2) = x / 4 + }; + + // Test range: hardware input (x * 2^(-2)) should be in [-1, 1] + // So actual x range should be [-4, 4] in Q31 format + // Note: Q31 format range is [-1, 1], but we need to input values that represent [-4, 4] + // Hardware will scale: x_hw = x * 2^(-2) = x / 4, so x should be in [-4, 4] range + // Generate 32 test points uniformly distributed from -4 to 4 + uint32_t data_x[32] = {}; + for (size_t i = 0; i < 32; i++) { + // Map i from [0, 31] to float range [-4, 4] + float x_float = -4.0f + (i * 8.0f / 31.0f); + // Convert to Q31 format: x_float is in [-4, 4], but Q31 range is [-1, 1] + // So we need to represent x_float / 4 in Q31 format + float x_normalized = x_float / 4.0f; // Normalize to [-1, 1] range + data_x[i] = cordic_convert_float_to_fixed(x_normalized, calc_config.iq_format); + } + uint32_t res1[32] = {}; + uint32_t res2[32] = {}; + + cordic_input_buffer_desc_t input_buffer = { + .p_data_arg1 = data_x, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer = { + .p_data_res1 = res1, + .p_data_res2 = res2, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, 32)); + + // Print header + printf("\n=== Comparison: Software vs Hardware (Arctan Q31 format, scale=2, input range: -4 to 4) ===\n"); + printf("Input(Q31) | X_Float | SW_Arctan | HW_Arctan | Error | Arctan(rad)\n"); + printf("-------------|------------|------------|------------|------------|------------\n"); + + for (size_t i = 0; i < 32; i++) { + // Convert Q31 input to float using cordic_convert_fixed_to_float + float x_normalized = cordic_convert_fixed_to_float(data_x[i], calc_config.iq_format); // This is in [-1, 1] range + // Reconstruct original x value: x_float = x_normalized * 4 + float x_float = x_normalized * 4.0f; // Original x in [-4, 4] range + // Hardware scales input: x_scaled = x_float * 2^(-2) = x_float / 4 = x_normalized + // float x_scaled = x_float / 4.0f; // This equals x_normalized + + // Software calculation: arctan(x_scaled) / π + float sw_arctan_rad = atanf(x_float); + float sw_arctan_normalized = sw_arctan_rad / M_PI; + + // Hardware output is already arctan(x * 2^(-2)) / π = arctan(x_scaled) / π + // Convert Q31 result to float using cordic_convert_fixed_to_float + float hw_arctan_temp = cordic_convert_fixed_to_float(res1[i], calc_config.iq_format); + float hw_arctan_normalized = hw_arctan_temp * 4; + + float arctan_error = fabsf(sw_arctan_normalized - hw_arctan_normalized); + + // Print comparison results + printf("0x%08lx | %10.6f | %10.6f | %10.6f | %10.6f | %10.6f", + data_x[i], x_float, sw_arctan_normalized, hw_arctan_normalized, arctan_error, sw_arctan_rad); + + // Highlight significant errors + TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_arctan_normalized, hw_arctan_normalized); + printf("\n"); + } + TEST_ESP_OK(cordic_delete_engine(engine)); +} + +TEST_CASE("test reconfig from cos q15 to log q31", "[cordic]") +{ + cordic_engine_handle_t engine = NULL; + + // Step 1: Create engine with COS function and Q15 format + cordic_engine_config_t engine_config = { + .clock_source = CORDIC_CLK_SRC_DEFAULT, + }; + TEST_ESP_OK(cordic_new_engine(&engine_config, &engine)); + + cordic_calculate_config_t calc_config_cos = { + .function = ESP_CORDIC_FUNC_COS, + .iq_format = ESP_CORDIC_FORMAT_Q15, + .iteration_count = 4, + .scale_exp = 0, + }; + + // Test COS function with Q15 format + uint32_t data_x_cos[16] = {}; + for (size_t i = 0; i < 16; i++) { + // Map i from [0, 15] to float range [-1.0, 1.0] + float float_value = -1.0f + (i * 2.0f / 15.0f); + data_x_cos[i] = cordic_convert_float_to_fixed(float_value, calc_config_cos.iq_format); + } + uint32_t res1_cos[16] = {}; + uint32_t res2_cos[16] = {}; + + cordic_input_buffer_desc_t input_buffer_cos = { + .p_data_arg1 = data_x_cos, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer_cos = { + .p_data_res1 = res1_cos, + .p_data_res2 = res2_cos, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config_cos, &input_buffer_cos, &output_buffer_cos, 16)); + + // Print header for COS test + printf("\n=== Step 1: COS function with Q15 format ===\n"); + printf("Input(Q15) | Angle(rad) | SW_Cosine | HW_Cosine | Cos_Error\n"); + printf("-----------|------------|------------|------------|----------\n"); + + for (size_t i = 0; i < 16; i++) { + // Convert Q15 input angle to radians + float angle_rad = cordic_convert_fixed_to_float(data_x_cos[i] & 0xFFFF, calc_config_cos.iq_format) * M_PI; + float sw_cosine = cosf(angle_rad); + float hw_cosine = cordic_convert_fixed_to_float(res1_cos[i] & 0xFFFF, calc_config_cos.iq_format); + float cos_error = fabsf(sw_cosine - hw_cosine); + + printf("0x%04lx | %10.6f | %10.6f | %10.6f | %10.6f", + data_x_cos[i] & 0xFFFF, angle_rad, sw_cosine, hw_cosine, cos_error); + TEST_ASSERT_FLOAT_WITHIN(0.01f, sw_cosine, hw_cosine); + printf("\n"); + } + + // Step 2: Use NATURAL_LOG function with Q31 format (no reconfig needed, just use new calc_config) + cordic_calculate_config_t calc_config_log = { + .function = ESP_CORDIC_FUNC_LOGE, + .iq_format = ESP_CORDIC_FORMAT_Q31, + .iteration_count = 4, + .scale_exp = 1, + }; + + // Test NATURAL_LOG function with Q31 format + // Test range: x in (0.1069, 9.3573) + float x_values[16] = {}; + uint32_t data_x_log[16] = {}; + for (size_t i = 0; i < 16; i++) { + // Map i from [0, 15] to x range [0.1069, 9.3573] + float x = 0.1069f + (i * (9.3573f - 0.1069f) / 15.0f); + x_values[i] = x; + + // Calculate hardware parameter: (x-1)/(x+1) * 0.5 + float hw_param = ((x - 1.0f) / (x + 1.0f)) * 0.5f; + + // Convert to Q31 format + data_x_log[i] = cordic_convert_float_to_fixed(hw_param, calc_config_log.iq_format); + } + uint32_t res1_log[16] = {}; + uint32_t res2_log[16] = {}; + + cordic_input_buffer_desc_t input_buffer_log = { + .p_data_arg1 = data_x_log, + .p_data_arg2 = NULL, + }; + cordic_output_buffer_desc_t output_buffer_log = { + .p_data_res1 = res1_log, + .p_data_res2 = res2_log, + }; + + TEST_ESP_OK(cordic_calculate_polling(engine, &calc_config_log, &input_buffer_log, &output_buffer_log, 16)); + + // Print header for LOG test + printf("\n=== Step 2: NATURAL_LOG function with Q31 format (after reconfig) ===\n"); + printf("X_Value | HW_Param | SW_LnX | HW_LnX | Error\n"); + printf("------------|------------|------------|------------|----------\n"); + + for (size_t i = 0; i < 16; i++) { + float x = x_values[i]; + + // Calculate hardware parameter: (x-1)/(x+1) * 0.5 + float hw_param = ((x - 1.0f) / (x + 1.0f)) * 0.5f; + + // Software calculation: ln(x) + float sw_lnx = logf(x); + // Convert hardware result to float using cordic_convert_fixed_to_float + float hw_lnx_scaled = cordic_convert_fixed_to_float(res1_log[i], calc_config_log.iq_format); + float hw_lnx = hw_lnx_scaled * 4.0f; + float lnx_error = fabsf(sw_lnx - hw_lnx); + + printf("%10.6f | %10.6f | %10.6f | %10.6f | %10.6f", + x, hw_param, sw_lnx, hw_lnx, lnx_error); + TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_lnx, hw_lnx); + printf("\n"); + } + + cordic_delete_engine(engine); +} diff --git a/components/esp_driver_cordic/test_apps/cordic_test_apps/sdkconfig.ci.iram_safe b/components/esp_driver_cordic/test_apps/cordic_test_apps/sdkconfig.ci.iram_safe new file mode 100644 index 0000000000..5b679b5048 --- /dev/null +++ b/components/esp_driver_cordic/test_apps/cordic_test_apps/sdkconfig.ci.iram_safe @@ -0,0 +1,9 @@ +CONFIG_COMPILER_DUMP_RTL_FILES=y +CONFIG_CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM=y +CONFIG_GPTIMER_ISR_CACHE_SAFE=y +CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +# silent the error check, as the error string are stored in rodata, causing RTL check failure +CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_HAL_ASSERTION_SILENT=y diff --git a/components/esp_driver_cordic/test_apps/cordic_test_apps/sdkconfig.ci.release b/components/esp_driver_cordic/test_apps/cordic_test_apps/sdkconfig.ci.release new file mode 100644 index 0000000000..1a87ebbb4a --- /dev/null +++ b/components/esp_driver_cordic/test_apps/cordic_test_apps/sdkconfig.ci.release @@ -0,0 +1,7 @@ +CONFIG_PM_ENABLE=y +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y +CONFIG_PM_DFS_INIT_AUTO=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/components/esp_driver_cordic/test_apps/cordic_test_apps/sdkconfig.defaults b/components/esp_driver_cordic/test_apps/cordic_test_apps/sdkconfig.defaults new file mode 100644 index 0000000000..fa8ac618b9 --- /dev/null +++ b/components/esp_driver_cordic/test_apps/cordic_test_apps/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT_EN=n From 0a096a46aded19b885946199aecf4a6a5ab081fe Mon Sep 17 00:00:00 2001 From: "C.S.M" Date: Wed, 4 Mar 2026 15:36:23 +0800 Subject: [PATCH 3/3] feat(cordic): Add cordic oneshot driver for esp32s31 --- components/esp_driver_cordic/CMakeLists.txt | 24 +++ components/esp_driver_cordic/Kconfig | 10 + components/esp_driver_cordic/cordic.c | 185 ++++++++++++++++++ components/esp_driver_cordic/cordic_private.h | 47 +++++ .../esp_driver_cordic/include/driver/cordic.h | 115 +++++++++++ .../include/driver/cordic_types.h | 24 +++ components/esp_driver_cordic/linker.lf | 19 ++ 7 files changed, 424 insertions(+) create mode 100644 components/esp_driver_cordic/CMakeLists.txt create mode 100644 components/esp_driver_cordic/Kconfig create mode 100644 components/esp_driver_cordic/cordic.c create mode 100644 components/esp_driver_cordic/cordic_private.h create mode 100644 components/esp_driver_cordic/include/driver/cordic.h create mode 100644 components/esp_driver_cordic/include/driver/cordic_types.h create mode 100644 components/esp_driver_cordic/linker.lf diff --git a/components/esp_driver_cordic/CMakeLists.txt b/components/esp_driver_cordic/CMakeLists.txt new file mode 100644 index 0000000000..3d4cdf611f --- /dev/null +++ b/components/esp_driver_cordic/CMakeLists.txt @@ -0,0 +1,24 @@ +idf_build_get_property(target IDF_TARGET) + +set(srcs) +set(public_include "include") + +set(requires esp_hal_cordic) +if(${target} STREQUAL "linux") + set(priv_requires "") +else() + set(priv_requires esp_pm) +endif() + +if(CONFIG_SOC_CORDIC_SUPPORTED) + list(APPEND srcs + "cordic.c" + ) +endif() + +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS ${public_include} + PRIV_REQUIRES "${priv_requires}" + REQUIRES "${requires}" + LDFRAGMENTS linker.lf + ) diff --git a/components/esp_driver_cordic/Kconfig b/components/esp_driver_cordic/Kconfig new file mode 100644 index 0000000000..363906e307 --- /dev/null +++ b/components/esp_driver_cordic/Kconfig @@ -0,0 +1,10 @@ +menu "ESP-Driver:Cordic Configurations" + depends on SOC_CORDIC_SUPPORTED + + config CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM + bool "Place cordic oneshot calculation function into IRAM" + default n + help + Place cordic oneshot function into IRAM so that cordic oneshot function + can be safely used in ISR even when cache is disabled. +endmenu diff --git a/components/esp_driver_cordic/cordic.c b/components/esp_driver_cordic/cordic.c new file mode 100644 index 0000000000..546cb39a19 --- /dev/null +++ b/components/esp_driver_cordic/cordic.c @@ -0,0 +1,185 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "esp_attr.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_err.h" +#include "cordic_private.h" +#include "driver/cordic_types.h" +#include "driver/cordic.h" +#include "hal/cordic_hal.h" +#include "hal/cordic_ll.h" +#include "soc/cordic_reg.h" +#include "soc/soc.h" +#include "hal/cordic_periph.h" + +ESP_LOG_ATTR_TAG(TAG, "cordic"); + +typedef struct cordic_platform_t { + _lock_t mutex; // platform level mutex lock. + cordic_engine_handle_t engine_handle; // array of cordic instances. +} cordic_platform_t; + +static cordic_platform_t s_cordic_platform = {}; // singleton platform + +static bool cordic_engine_occupied(void) +{ + return s_cordic_platform.engine_handle != NULL; +} + +esp_err_t cordic_new_engine(const cordic_engine_config_t *cordic_cfg, cordic_engine_handle_t *ret_engine) +{ + esp_err_t ret = ESP_OK; + cordic_engine_handle_t engine = NULL; + ESP_RETURN_ON_FALSE(cordic_cfg, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(ret_engine, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + engine = (cordic_engine_handle_t)heap_caps_calloc(1, sizeof(cordic_engine_t), CORDIC_MEM_ALLOC_CAPS); + ESP_RETURN_ON_FALSE(engine, ESP_ERR_NO_MEM, TAG, "no memory for cordic engine"); + + bool engine_found = false; + _lock_acquire(&s_cordic_platform.mutex); + if (cordic_engine_occupied() == false) { + s_cordic_platform.engine_handle = engine; + engine_found = true; + } + _lock_release(&s_cordic_platform.mutex); + ESP_GOTO_ON_FALSE(engine_found, ESP_ERR_NOT_FOUND, err, TAG, "no free controller"); + + cordic_hal_init(&engine->hal); + cordic_ll_enable_bus_clock(true); + cordic_ll_reset_module_register(); + cordic_ll_enable_clock(true); + + cordic_clock_source_t clk_src = cordic_cfg->clock_source ? cordic_cfg->clock_source : CORDIC_CLK_SRC_DEFAULT; + cordic_ll_set_clock_source(clk_src); + *ret_engine = engine; + + return ESP_OK; + +err: + cordic_delete_engine(engine); + return ret; +} + +esp_err_t cordic_calculate_polling(cordic_engine_handle_t engine, const cordic_calculate_config_t *calc_cfg, cordic_input_buffer_desc_t *input_buffer_desc, cordic_output_buffer_desc_t *output_buffer_desc, size_t buffer_depth) +{ + ESP_RETURN_ON_FALSE_ISR(engine && calc_cfg && input_buffer_desc && input_buffer_desc->p_data_arg1 && output_buffer_desc && output_buffer_desc->p_data_res1 && buffer_depth > 0, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE_ISR(calc_cfg->function < ESP_CORDIC_FUNC_MAX, ESP_ERR_INVALID_ARG, TAG, "invalid function"); + ESP_RETURN_ON_FALSE_ISR(calc_cfg->iq_format < ESP_CORDIC_IQ_SIZE_MAX, ESP_ERR_INVALID_ARG, TAG, "invalid iq_format"); + ESP_RETURN_ON_FALSE_ISR(calc_cfg->scale_exp >= cordic_hal_algorithm_allowable_scale[calc_cfg->function][0] && calc_cfg->scale_exp <= cordic_hal_algorithm_allowable_scale[calc_cfg->function][1], ESP_ERR_INVALID_ARG, TAG, "invalid scale_exp"); + ESP_RETURN_ON_FALSE_ISR(calc_cfg->iteration_count <= CORDIC_LL_PRECISION_MAX && calc_cfg->iteration_count > 0, ESP_ERR_INVALID_ARG, TAG, "invalid iteration_count"); + // Determine if function requires two arguments + bool is_two_args = (calc_cfg->function == ESP_CORDIC_FUNC_PHASE || calc_cfg->function == ESP_CORDIC_FUNC_MODULUS); + ESP_RETURN_ON_FALSE_ISR(is_two_args == (input_buffer_desc->p_data_arg2 != NULL), ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + BaseType_t in_isr = xPortInIsrContext(); + + // Convert timeout from milliseconds to ticks + uint32_t timeout_ms = (buffer_depth / 100 + 1) * CORDIC_ONESHOT_CALCULATE_TIMEOUT_MS; + TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms); + TickType_t start_tick = 0; + if (in_isr) { + start_tick = xTaskGetTickCountFromISR(); + } else { + start_tick = xTaskGetTickCount(); + } + + // Configure hardware registers based on calculation config + cordic_ll_set_calculate_function(engine->hal.dev, calc_cfg->function); + cordic_ll_set_calculate_mode(engine->hal.dev, CORDIC_LL_MODE_REG); + cordic_ll_set_calculate_precision(engine->hal.dev, calc_cfg->iteration_count - 1); + cordic_ll_set_calculate_scale(engine->hal.dev, calc_cfg->scale_exp); + cordic_ll_set_calculate_result_number(engine->hal.dev, 2); + + if (is_two_args) { + cordic_ll_set_calculate_argument_number(engine->hal.dev, 2); + } else { + cordic_ll_set_calculate_argument_number(engine->hal.dev, 1); + } + cordic_ll_set_calculate_result_format(engine->hal.dev, calc_cfg->iq_format); + cordic_ll_set_calculate_argument_format(engine->hal.dev, calc_cfg->iq_format); + + // Get function pointers from HAL layer arrays using format and argument count as indices + size_t arg_num = is_two_args ? 1 : 0; + cordic_set_argument_func_t set_argument_func = cordic_hal_set_argument_funcs[calc_cfg->iq_format][arg_num]; + cordic_get_result_func_t get_result_func = cordic_hal_get_result_funcs[calc_cfg->iq_format]; + + for (size_t i = 0; i < buffer_depth; i++) { + // Use function pointer to set arguments + set_argument_func(&engine->hal, input_buffer_desc, i); + + cordic_ll_start_calculate(engine->hal.dev); + + // Wait for result ready with timeout + while (cordic_ll_is_calculate_result_ready(engine->hal.dev) == 0) { + TickType_t current_tick = 0; + if (in_isr) { + current_tick = xTaskGetTickCountFromISR(); + } else { + current_tick = xTaskGetTickCount(); + } + // TickType_t is unsigned, so subtraction handles overflow correctly + TickType_t elapsed_ticks = current_tick - start_tick; + if (elapsed_ticks >= timeout_ticks) { + ESP_DRAM_LOGE(TAG, "CORDIC calculation timeout after %d ms", timeout_ms); + cordic_ll_reset_module_register(); + return ESP_ERR_TIMEOUT; + } + } + // Use function pointer to get results + get_result_func(&engine->hal, output_buffer_desc, i); + } + + return ESP_OK; +} + +esp_err_t cordic_delete_engine(cordic_engine_handle_t engine) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(engine, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + cordic_ll_enable_bus_clock(false); + cordic_ll_enable_clock(false); + cordic_hal_deinit(&engine->hal); + + _lock_acquire(&s_cordic_platform.mutex); + s_cordic_platform.engine_handle = NULL; + _lock_release(&s_cordic_platform.mutex); + + heap_caps_free(engine); + + return ret; +} + +float cordic_convert_fixed_to_float(uint32_t fixed_value, cordic_iq_format_t iq_format) +{ + if (iq_format == ESP_CORDIC_FORMAT_Q15) { + return (float)(int16_t)(fixed_value & 0xFFFF) / CORDIC_Q15_SCALE_FACTOR; + } else if (iq_format == ESP_CORDIC_FORMAT_Q31) { + return (float)(int32_t)fixed_value / CORDIC_Q31_SCALE_FACTOR; + } else { + return 0.0f; + } +} + +uint32_t cordic_convert_float_to_fixed(float float_value, cordic_iq_format_t iq_format) +{ + if (iq_format == ESP_CORDIC_FORMAT_Q15) { + return (uint32_t)(int16_t)(float_value * CORDIC_Q15_SCALE_FACTOR) & 0xFFFF; + } else if (iq_format == ESP_CORDIC_FORMAT_Q31) { + return (uint32_t)(int32_t)(float_value * CORDIC_Q31_SCALE_FACTOR); + } else { + return 0; + } +} diff --git a/components/esp_driver_cordic/cordic_private.h b/components/esp_driver_cordic/cordic_private.h new file mode 100644 index 0000000000..99f4134f79 --- /dev/null +++ b/components/esp_driver_cordic/cordic_private.h @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include "esp_attr.h" +#include "esp_heap_caps.h" +#include "hal/cordic_hal.h" +#include "esp_intr_types.h" +#include "sdkconfig.h" +#include "driver/cordic.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM +#define CORDIC_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#define CORDIC_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT +#endif + +#define CORDIC_ONESHOT_CALCULATE_TIMEOUT_MS (2) // 2ms is enough for cordic one shot calculation api +#define CORDIC_Q15_SCALE_FACTOR (32768.0f) // 2^15, scale factor for Q15 format conversion +#define CORDIC_Q31_SCALE_FACTOR (2147483648.0f) // 2^31, scale factor for Q31 format conversion + +typedef struct cordic_engine_t cordic_engine_t; + +// Function pointer type for setting CORDIC arguments +typedef void (*cordic_set_argument_func_t)(cordic_hal_context_t *hal, const cordic_input_buffer_desc_t *input_buffer, size_t index); + +// Function pointer type for getting CORDIC results +typedef void (*cordic_get_result_func_t)(cordic_hal_context_t *hal, cordic_output_buffer_desc_t *output_buffer, size_t index); + +struct cordic_engine_t { + cordic_hal_context_t hal; // HAL context +}; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_cordic/include/driver/cordic.h b/components/esp_driver_cordic/include/driver/cordic.h new file mode 100644 index 0000000000..03682bc6b1 --- /dev/null +++ b/components/esp_driver_cordic/include/driver/cordic.h @@ -0,0 +1,115 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "driver/cordic_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief CORDIC engine configuration structure + */ +typedef struct { + cordic_clock_source_t clock_source; /*!< CORDIC clock source */ +} cordic_engine_config_t; + +/** + * @brief CORDIC calculation configuration structure + */ +typedef struct { + cordic_func_t function; /*!< CORDIC calculation function type (e.g., cosine, sine, arctan) */ + cordic_iq_format_t iq_format; /*!< IQ data format */ + uint16_t iteration_count; /*!< Internal CORDIC iteration count */ + uint16_t scale_exp; /*!< Input scaling exponent n, effective scaling is 2^n (range depends on function type) */ +} cordic_calculate_config_t; + +/** + * @brief Create a new CORDIC engine instance + * + * This function creates and initializes a CORDIC engine with the specified configuration. + * The engine handle returned can be used for subsequent CORDIC calculations. + * + * @param[in] cordic_cfg Pointer to CORDIC engine configuration structure. Must not be NULL. + * @param[out] ret_engine Pointer to store the created CORDIC engine handle. Must not be NULL. + * + * @return + * - ESP_OK: CORDIC engine created successfully. + * - ESP_ERR_INVALID_ARG: Invalid argument (NULL pointer or invalid scale exponent). + * - ESP_ERR_NO_MEM: Failed to allocate memory for the engine. + */ +esp_err_t cordic_new_engine(const cordic_engine_config_t *cordic_cfg, cordic_engine_handle_t *ret_engine); + +/** + * @brief Start one-shot CORDIC calculation + * + * This function performs CORDIC calculations on a batch of data points. It processes each data point + * sequentially: sets arguments, starts calculation, waits for completion, and retrieves results. + * + * @note The function can be safely used in ISR, but for the ISR run in cache-safe, please + * enable ``CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM`` to place the control functions into IRAM. + * @note This function is not guaranteed to be thread-safe. + * + * @param[in] engine CORDIC engine handle created by cordic_new_engine(). Must not be NULL. + * @param[in] calc_cfg Pointer to CORDIC calculation configuration structure. Must not be NULL. + * @param[in] input_buffer_desc Pointer to input buffer descriptor containing input data. Must not be NULL. + * @param[out] output_buffer_desc Pointer to output buffer descriptor for storing results. Must not be NULL. + * @param[in] buffer_depth Number of data points to process. + * + * @return + * - ESP_OK: All calculations completed successfully. + * - ESP_ERR_INVALID_ARG: Invalid argument (NULL pointer, zero buffer_depth, or invalid timeout). + * - ESP_ERR_TIMEOUT: Calculation timeout. + */ +esp_err_t cordic_calculate_polling(cordic_engine_handle_t engine, const cordic_calculate_config_t *calc_cfg, cordic_input_buffer_desc_t *input_buffer_desc, cordic_output_buffer_desc_t *output_buffer_desc, size_t buffer_depth); + +/** + * @brief Delete a CORDIC engine instance + * + * This function deletes a CORDIC engine and frees all associated resources including the mutex. + * After calling this function, the engine handle becomes invalid and should not be used. + * + * @param[in] engine CORDIC engine handle to delete. Must not be NULL. + * + * @return + * - ESP_OK: Engine deleted successfully. + * - ESP_ERR_INVALID_ARG: Invalid argument (NULL pointer). + */ +esp_err_t cordic_delete_engine(cordic_engine_handle_t engine); + +/** + * @brief Convert CORDIC hex value to float + * + * This function converts a hexadecimal value from CORDIC hardware output to a floating-point number. + * The conversion depends on the IQ format specified by the iq_format parameter. + * + * @param[in] fixed_value Hexadecimal value from CORDIC hardware output. + * @param[in] iq_format IQ data format. + * + * @return Converted floating-point value. Returns 0.0f if iq_format is invalid. + */ +float cordic_convert_fixed_to_float(uint32_t fixed_value, cordic_iq_format_t iq_format); + +/** + * @brief Convert float to CORDIC hex value + * + * This function converts a floating-point number to a hexadecimal value suitable for CORDIC hardware input. + * The conversion depends on the IQ format specified by the iq_format parameter. + * + * @param[in] float_value Floating-point value to convert. Should be in the range [-1.0, 1.0]. + * @param[in] iq_format IQ data format. + * + * @return Converted hexadecimal value. Returns 0 if iq_format is invalid. + */ +uint32_t cordic_convert_float_to_fixed(float float_value, cordic_iq_format_t iq_format); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_cordic/include/driver/cordic_types.h b/components/esp_driver_cordic/include/driver/cordic_types.h new file mode 100644 index 0000000000..a75e9cd196 --- /dev/null +++ b/components/esp_driver_cordic/include/driver/cordic_types.h @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "soc/soc_caps.h" +#include "hal/cordic_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief CORDIC engine handle type + */ +typedef struct cordic_engine_t *cordic_engine_handle_t; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_cordic/linker.lf b/components/esp_driver_cordic/linker.lf new file mode 100644 index 0000000000..0655030326 --- /dev/null +++ b/components/esp_driver_cordic/linker.lf @@ -0,0 +1,19 @@ +[mapping:esp_driver_cordic] +archive: libesp_driver_cordic.a +entries: + if CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM = y: + cordic: cordic_calculate_polling (noflash) + +[mapping:esp_hal_cordic] +archive: libesp_hal_cordic.a +entries: + if CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM = y: + cordic_periph: cordic_hal_algorithm_allowable_scale (noflash) + cordic_hal: cordic_hal_set_argument_funcs (noflash) + cordic_hal: cordic_hal_get_result_funcs (noflash) + cordic_hal: cordic_hal_set_argument_q15_two_args (noflash) + cordic_hal: cordic_hal_set_argument_q15_one_arg (noflash) + cordic_hal: cordic_hal_set_argument_q31_two_args (noflash) + cordic_hal: cordic_hal_set_argument_q31_one_arg (noflash) + cordic_hal: cordic_hal_get_result_q15 (noflash) + cordic_hal: cordic_hal_get_result_q31 (noflash)