diff --git a/components/esp_common/CMakeLists.txt b/components/esp_common/CMakeLists.txt index 44cc0a62bce..38d1af29178 100644 --- a/components/esp_common/CMakeLists.txt +++ b/components/esp_common/CMakeLists.txt @@ -1,12 +1,18 @@ idf_build_get_property(target IDF_TARGET) -if(${target} STREQUAL "linux") +# TODO(IDF-15945): potentially remove __ULP_BUILD handling from esp_common +if(${target} STREQUAL "linux" OR __ULP_BUILD) + # ULP sources include esp_err.h from components/ulp public and shared headers. set(ldfragments) else() set(ldfragments common.lf soc.lf linker.lf) endif() -set(srcs "src/esp_err_to_name.c") +set(srcs) + +if(NOT __ULP_BUILD) + set(srcs "src/esp_err_to_name.c") +endif() idf_component_register(SRCS "${srcs}" INCLUDE_DIRS include diff --git a/components/esp_driver_uart/CMakeLists.txt b/components/esp_driver_uart/CMakeLists.txt index 7bb23130f53..971e0dc42ec 100644 --- a/components/esp_driver_uart/CMakeLists.txt +++ b/components/esp_driver_uart/CMakeLists.txt @@ -2,6 +2,24 @@ idf_build_get_property(target IDF_TARGET) set(srcs) set(public_include "include") + +# TODO(IDF-15946): potentially remove __ULP_BUILD handling from esp_driver_uart +if(__ULP_BUILD) + # LP-core UART wakeup shared code includes driver/uart_wakeup.h from + # ulp/lp_core/shared/include/ulp_lp_core_lp_uart_shared.h. + set(ulp_srcs) + if(ULP_TYPE STREQUAL "lp_core" AND + CONFIG_SOC_UART_SUPPORTED AND + (CONFIG_SOC_DEEP_SLEEP_SUPPORTED OR CONFIG_SOC_LIGHT_SLEEP_SUPPORTED)) + list(APPEND ulp_srcs "src/uart_wakeup.c") + endif() + + idf_component_register(SRCS ${ulp_srcs} + INCLUDE_DIRS ${public_include} + REQUIRES esp_common esp_hal_uart esp_hw_support log soc) + return() +endif() + if(CONFIG_SOC_UART_SUPPORTED) list(APPEND srcs "src/uart.c") diff --git a/components/esp_hal_clock/CMakeLists.txt b/components/esp_hal_clock/CMakeLists.txt index 8066ee34ffc..54d8e166c18 100644 --- a/components/esp_hal_clock/CMakeLists.txt +++ b/components/esp_hal_clock/CMakeLists.txt @@ -5,14 +5,27 @@ endif() set(srcs) set(includes "include") +# Public clock HAL headers include soc/ and hal/ headers directly, and some +# target clk_tree_ll.h headers include hal/regi2c_ctrl.h. +set(requires soc hal esp_hal_regi2c) # Target-specific include directory if present if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${target}/include") list(APPEND includes "${target}/include") endif() +# TODO(IDF-15947): potentially remove __ULP_BUILD handling from esp_hal_clock +if(__ULP_BUILD) + # LP-core timer shared code includes hal/clk_tree_ll.h from + # ulp/lp_core/shared/ulp_lp_core_lp_timer_shared.c. Some target + # clk_tree_ll.h headers include hal/regi2c_ctrl.h. + idf_component_register(INCLUDE_DIRS ${includes} + REQUIRES ${requires}) + return() +endif() + list(APPEND srcs "${target}/clk_tree_hal.c") idf_component_register(SRCS ${srcs} INCLUDE_DIRS ${includes} - REQUIRES soc hal) + REQUIRES ${requires}) diff --git a/components/esp_hal_gpio/CMakeLists.txt b/components/esp_hal_gpio/CMakeLists.txt index f7bde5ea3c8..66d9a3df4b5 100644 --- a/components/esp_hal_gpio/CMakeLists.txt +++ b/components/esp_hal_gpio/CMakeLists.txt @@ -7,19 +7,23 @@ if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${target}/include") list(APPEND includes "${target}/include") endif() -if(CONFIG_SOC_GPIO_PORT GREATER 0) +# ULP GPIO helpers include GPIO/RTC IO HAL headers from +# ulp/ulp_riscv/ulp_core/include/ulp_riscv_gpio.h and +# ulp/lp_core/lp_core/include/ulp_lp_core_gpio.h. +# TODO(IDF-15948): potentially remove __ULP_BUILD source exclusions from esp_hal_gpio +if(CONFIG_SOC_GPIO_PORT GREATER 0 AND NOT __ULP_BUILD) list(APPEND srcs "gpio_hal.c") endif() -if(CONFIG_SOC_RTCIO_PIN_COUNT GREATER 0) +if(CONFIG_SOC_RTCIO_PIN_COUNT GREATER 0 AND NOT __ULP_BUILD) list(APPEND srcs "rtc_io_hal.c" "${target}/rtc_io_periph.c") endif() -if(CONFIG_SOC_DEDICATED_GPIO_SUPPORTED) +if(CONFIG_SOC_DEDICATED_GPIO_SUPPORTED AND NOT __ULP_BUILD) list(APPEND srcs "${target}/dedic_gpio_periph.c") endif() -if(CONFIG_SOC_SDM_SUPPORTED) +if(CONFIG_SOC_SDM_SUPPORTED AND NOT __ULP_BUILD) list(APPEND srcs "sdm_hal.c" "${target}/sdm_periph.c") endif() diff --git a/components/esp_hal_pmu/CMakeLists.txt b/components/esp_hal_pmu/CMakeLists.txt index 0e517d68cc9..5dfee33c531 100644 --- a/components/esp_hal_pmu/CMakeLists.txt +++ b/components/esp_hal_pmu/CMakeLists.txt @@ -5,6 +5,19 @@ endif() set(srcs) set(public_include "include" "${target}/include") +# Public PMU/RTC control HAL headers include hal/regi2c_ctrl.h on several +# targets, so CMake v2 builds need esp_hal_regi2c explicitly. +set(requires soc hal esp_rom esp_hal_regi2c) + +# TODO(IDF-15949): potentially remove __ULP_BUILD handling from esp_hal_pmu +if(__ULP_BUILD) + # LP-core runtime code includes hal/pmu_ll.h from + # ulp/lp_core/lp_core/lp_core_utils.c. Some target brownout/VBAT headers + # include hal/regi2c_ctrl.h. + idf_component_register(INCLUDE_DIRS ${public_include} + REQUIRES ${requires}) + return() +endif() # New chips with PMU peripheral if(CONFIG_SOC_PMU_SUPPORTED) @@ -31,5 +44,5 @@ endif() idf_component_register(SRCS ${srcs} INCLUDE_DIRS ${public_include} - REQUIRES soc hal esp_rom + REQUIRES ${requires} LDFRAGMENTS linker.lf) diff --git a/components/esp_hal_regi2c/CMakeLists.txt b/components/esp_hal_regi2c/CMakeLists.txt index cf2529d8e23..777dc4c7d2b 100644 --- a/components/esp_hal_regi2c/CMakeLists.txt +++ b/components/esp_hal_regi2c/CMakeLists.txt @@ -11,6 +11,15 @@ if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${target}/include") list(APPEND includes "${target}/include") endif() +# TODO(IDF-15950): potentially remove __ULP_BUILD handling from esp_hal_regi2c +if(__ULP_BUILD) + # ULP ADC support reaches REGI2C headers through ADC HAL includes used by + # ulp/ulp_riscv/ulp_core/ulp_riscv_adc.c. + idf_component_register(INCLUDE_DIRS ${includes} + REQUIRES soc hal esp_rom) + return() +endif() + # Per-target regi2c implementation sources (replaces ROM implementation when needed) if(NOT CONFIG_ESP_ROM_HAS_REGI2C_IMPL) list(APPEND srcs "${target}/regi2c_impl.c") diff --git a/components/esp_hal_security/.build-test-rules.yml b/components/esp_hal_security/.build-test-rules.yml index e10f0a47a91..8d7598acef4 100644 --- a/components/esp_hal_security/.build-test-rules.yml +++ b/components/esp_hal_security/.build-test-rules.yml @@ -8,5 +8,8 @@ components/esp_hal_security/test_apps/crypto: - esp_security components/esp_hal_security/test_apps/tee: + disable+: + - if: IDF_BUILD_V2 == "1" + reason: Legacy ULP apps are covered by CMake v1; buildv2 covers full_subproject ULP apps. disable: - if: IDF_TARGET not in ["esp32c6", "esp32h2", "esp32c5", "esp32c61", "esp32p4"] diff --git a/components/esp_hw_support/CMakeLists.txt b/components/esp_hw_support/CMakeLists.txt index 731b6d3648a..4760f2181f4 100644 --- a/components/esp_hw_support/CMakeLists.txt +++ b/components/esp_hw_support/CMakeLists.txt @@ -12,6 +12,34 @@ if(${target} STREQUAL "linux") return() endif() +set(public_include_dirs "include" "include/soc" + "ldo/include" "debug_probe/include" "etm/include" + "mspi/mspi_timing_tuning/include" "mspi/mspi_timing_tuning/tuning_scheme_impl/include" + "mspi/mspi_intr/include" + "power_supply/include" "modem/include") + +if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/include/soc/${target}") + list(APPEND public_include_dirs + "include/soc/${target}" + ) +endif() + +if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/port/${target}/include") + list(APPEND public_include_dirs + "port/${target}/include" + ) +endif() + +# TODO(IDF-15951): potentially remove __ULP_BUILD handling from esp_hw_support +if(__ULP_BUILD) + # ULP headers and LP-core runtime include esp_intr_alloc.h and esp_cpu.h from + # ulp/ulp_riscv/include/ulp_riscv.h and + # ulp/lp_core/lp_core/lp_core_ubsan.c. + idf_component_register(INCLUDE_DIRS ${public_include_dirs} + REQUIRES esp_common esp_hal_gpio hal soc) + return() +endif() + set(requires esp_hal_gpio esp_hal_usb esp_hal_pmu esp_hal_regi2c esp_hal_uart) set(priv_requires efuse # only esp_hw_support/adc_share_hw_ctrl.c requires efuse component spi_flash @@ -180,18 +208,6 @@ else() list(APPEND priv_requires "esp_system") endif() -set(public_include_dirs "include" "include/soc" - "ldo/include" "debug_probe/include" "etm/include" - "mspi/mspi_timing_tuning/include" "mspi/mspi_timing_tuning/tuning_scheme_impl/include" - "mspi/mspi_intr/include" - "power_supply/include" "modem/include") - -if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/include/soc/${target}") - list(APPEND public_include_dirs - "include/soc/${target}" - ) -endif() - if(${target} STREQUAL "esp32p4") # for mipi_csi_share_hw_ctrl.c list(APPEND priv_requires esp_hal_cam) diff --git a/components/esp_hw_support/test_apps/.build-test-rules.yml b/components/esp_hw_support/test_apps/.build-test-rules.yml index d7b2f899479..9949960095f 100644 --- a/components/esp_hw_support/test_apps/.build-test-rules.yml +++ b/components/esp_hw_support/test_apps/.build-test-rules.yml @@ -103,6 +103,8 @@ components/esp_hw_support/test_apps/vad_wakeup: - ulp - soc disable: + - if: IDF_BUILD_V2 == "1" + reason: Legacy ULP apps are covered by CMake v1; buildv2 covers full_subproject ULP apps. - if: SOC_LP_VAD_SUPPORTED != 1 components/esp_hw_support/test_apps/wakeup_tests: diff --git a/components/esp_libc/project_include.cmake b/components/esp_libc/project_include.cmake index a3cb2042663..71dba137c5c 100644 --- a/components/esp_libc/project_include.cmake +++ b/components/esp_libc/project_include.cmake @@ -1,3 +1,9 @@ +# Sub-projects with a custom toolchain (e.g. ULP) do not use the IDF +# toolchain response file machinery. Skip flag manipulation. +if(IDF_CUSTOM_TOOLCHAIN) + return() +endif() + if(CONFIG_IDF_TOOLCHAIN_GCC) if(CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND) idf_toolchain_add_flags(COMPILE_OPTIONS "-mdisable-hardware-atomics") diff --git a/components/esp_pm/test_apps/esp_pm/main/CMakeLists.txt b/components/esp_pm/test_apps/esp_pm/main/CMakeLists.txt index c58652fb512..38ec64772db 100644 --- a/components/esp_pm/test_apps/esp_pm/main/CMakeLists.txt +++ b/components/esp_pm/test_apps/esp_pm/main/CMakeLists.txt @@ -10,4 +10,5 @@ endif() idf_component_register(SRCS ${sources} INCLUDE_DIRS "." PRIV_REQUIRES unity esp_pm ulp esp_timer esp_psram esp_driver_gptimer vfs esp_hal_gpio + esp_driver_gpio WHOLE_ARCHIVE) diff --git a/components/esp_psram/project_include.cmake b/components/esp_psram/project_include.cmake index aa9ff60758f..dad3cf29ba1 100644 --- a/components/esp_psram/project_include.cmake +++ b/components/esp_psram/project_include.cmake @@ -1,3 +1,7 @@ +if(IDF_CUSTOM_TOOLCHAIN) + return() +endif() + if(CONFIG_IDF_TOOLCHAIN_GCC) # Remove all "-mfix-esp32-psram-cache*" from toolchain flags # that may have appeared during configuration changes. diff --git a/components/esp_rom/CMakeLists.txt b/components/esp_rom/CMakeLists.txt index eed0702b9fb..33f372fab66 100644 --- a/components/esp_rom/CMakeLists.txt +++ b/components/esp_rom/CMakeLists.txt @@ -7,6 +7,22 @@ set(include_dirs "include" "${target_folder}/include/${target_folder}" "${target_folder}") +# TODO(IDF-15952): potentially remove __ULP_BUILD handling from esp_rom +if(__ULP_BUILD) + # LP-core runtime and print code include ROM headers from + # ulp/lp_core/lp_core/lp_core_startup.c and + # ulp/lp_core/lp_core/lp_core_print.c. + idf_component_register(INCLUDE_DIRS ${include_dirs}) + if(ULP_TYPE STREQUAL "lp_core" AND CONFIG_ESP_ROM_HAS_LP_ROM) + target_linker_script(${COMPONENT_LIB} INTERFACE + "${target_folder}/ld/${target}lp.rom.ld" + "${target_folder}/ld/${target}lp.rom.newlib.ld" + "${target_folder}/ld/${target}lp.rom.version.ld" + "${target_folder}/ld/${target}lp.rom.api.ld") + endif() + return() +endif() + set(private_required_comp "") set(sources "patches/esp_rom_sys.c" diff --git a/components/hal/CMakeLists.txt b/components/hal/CMakeLists.txt index e5c599455e2..b7bd8ad5871 100644 --- a/components/hal/CMakeLists.txt +++ b/components/hal/CMakeLists.txt @@ -1,7 +1,7 @@ idf_build_get_property(target IDF_TARGET) idf_build_get_property(esp_tee_build ESP_TEE_BUILD) -set(srcs "hal_utils.c") +set(srcs) set(includes "platform_port/include") set(priv_include) set(requires) @@ -12,6 +12,18 @@ list(APPEND includes "${target}/include") list(APPEND includes "include") +# ULP runtime code includes generic HAL headers, for example hal/lp_core_ll.h +# from ulp/lp_core/lp_core/lp_core_interrupt.c. +# TODO(IDF-15953): potentially remove __ULP_BUILD handling from hal +if(__ULP_BUILD) + idf_component_register(INCLUDE_DIRS ${includes} + PRIV_INCLUDE_DIRS ${priv_include} + REQUIRES esp_common ${requires}) + return() +endif() + +list(APPEND srcs "hal_utils.c") + if(CONFIG_SOC_EFUSE_SUPPORTED) list(APPEND srcs "efuse_hal.c" "${target}/efuse_hal.c") endif() @@ -46,11 +58,13 @@ if(NOT esp_tee_build AND NOT BOOTLOADER_BUILD) endif() endif() +set(ldfragments linker.lf) + idf_component_register(SRCS ${srcs} INCLUDE_DIRS ${includes} PRIV_INCLUDE_DIRS ${priv_include} REQUIRES ${requires} - LDFRAGMENTS linker.lf) + LDFRAGMENTS ${ldfragments}) if(CONFIG_HAL_DEFAULT_ASSERTION_LEVEL EQUAL 1) target_link_libraries(${COMPONENT_LIB} INTERFACE "-u abort") diff --git a/components/log/CMakeLists.txt b/components/log/CMakeLists.txt index 353055521ab..ae7eb5258bd 100644 --- a/components/log/CMakeLists.txt +++ b/components/log/CMakeLists.txt @@ -1,6 +1,16 @@ idf_build_get_property(target IDF_TARGET) idf_build_get_property(non_os_build NON_OS_BUILD) +# TODO(IDF-15954): potentially remove __ULP_BUILD handling from log +if(__ULP_BUILD) + # LP-core UART wakeup support builds components/esp_driver_uart/src/uart_wakeup.c, + # which includes ESP log headers. + idf_component_register(INCLUDE_DIRS "include" + PRIV_INCLUDE_DIRS "include/esp_private" + REQUIRES esp_rom) + return() +endif() + if(non_os_build) set(system_target "noos") else() diff --git a/components/riscv/CMakeLists.txt b/components/riscv/CMakeLists.txt index 12872530fe6..3c93a824cd7 100644 --- a/components/riscv/CMakeLists.txt +++ b/components/riscv/CMakeLists.txt @@ -2,10 +2,20 @@ idf_build_get_property(target IDF_TARGET) idf_build_get_property(arch IDF_TARGET_ARCH) idf_build_get_property(esp_tee_build ESP_TEE_BUILD) -if(NOT "${arch}" STREQUAL "riscv") +# TODO(IDF-15955): potentially remove __ULP_BUILD handling from riscv +if(NOT "${arch}" STREQUAL "riscv" AND NOT (__ULP_BUILD AND "${ULP_TYPE}" STREQUAL "riscv")) return() endif() +if(__ULP_BUILD AND "${ULP_TYPE}" STREQUAL "riscv") + # RISC-V ULP interrupt support includes riscv/interrupt.h from + # ulp/ulp_riscv/ulp_core/include/ulp_riscv_interrupt.h. + idf_component_register(INCLUDE_DIRS "include") + return() +endif() + +# When __ULP_BUILD and ULP_TYPE is lp_core, keep the normal RISC-V helper sources for headers +# used by ulp/lp_core/lp_core/lp_core_pmp.c and vector.S. if(BOOTLOADER_BUILD) set(priv_requires soc hal) diff --git a/components/soc/CMakeLists.txt b/components/soc/CMakeLists.txt index 9ba726fe800..a1e36e530a0 100644 --- a/components/soc/CMakeLists.txt +++ b/components/soc/CMakeLists.txt @@ -4,14 +4,6 @@ set(target_folder "${target}") set(srcs) -# On Linux the soc component is a simple wrapper, without much functionality -if(NOT ${target} STREQUAL "linux") - set(srcs "lldesc.c" - "dport_access_common.c" - "${target_folder}/interrupts.c" - "${target_folder}/gpio_periph.c") -endif() - set(includes "include" "${target_folder}") if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${target_folder}/include") @@ -39,6 +31,33 @@ else() endif() endif() +# TODO(IDF-15956): potentially remove __ULP_BUILD handling from soc +if(__ULP_BUILD) + idf_component_register(INCLUDE_DIRS ${includes} + REQUIRES esp_common) + + # For an embedded system, the MMU page size should always be defined statically + # For IDF, we define it according to the Flash size that user selects + # Replace this value in an adaptive way, if Kconfig isn't available on your platform + target_compile_definitions(${COMPONENT_LIB} INTERFACE SOC_MMU_PAGE_SIZE=CONFIG_MMU_PAGE_SIZE) + + target_compile_definitions(${COMPONENT_LIB} INTERFACE SOC_XTAL_FREQ_MHZ=CONFIG_XTAL_FREQ) + + if(ULP_TYPE STREQUAL "lp_core") + target_linker_script(${COMPONENT_LIB} INTERFACE "${target_folder}/ld/${target}.peripherals.ld") + endif() + + return() +endif() + +# On Linux the soc component is a simple wrapper, without much functionality. +if(NOT ${target} STREQUAL "linux") + set(srcs "lldesc.c" + "dport_access_common.c" + "${target_folder}/interrupts.c" + "${target_folder}/gpio_periph.c") +endif() + if(target STREQUAL "esp32") list(APPEND srcs "${target_folder}/dport_access.c") endif() @@ -55,9 +74,11 @@ if(CONFIG_SOC_BOD_SUPPORTED) list(APPEND srcs "${target_folder}/power_supply_periph.c") endif() +set(ldfragments "linker.lf") + idf_component_register(SRCS ${srcs} INCLUDE_DIRS ${includes} - LDFRAGMENTS "linker.lf") + LDFRAGMENTS ${ldfragments}) # For an embedded system, the MMU page size should always be defined statically # For IDF, we define it according to the Flash size that user selects diff --git a/components/soc/project_include.cmake b/components/soc/project_include.cmake index 847891dbfca..3f2c6c36a23 100644 --- a/components/soc/project_include.cmake +++ b/components/soc/project_include.cmake @@ -1,3 +1,7 @@ +if(IDF_CUSTOM_TOOLCHAIN) + return() +endif() + if(CONFIG_IDF_TOOLCHAIN_GCC) # Common flags idf_toolchain_add_flags(LINK_OPTIONS "-nostartfiles") diff --git a/components/ulp/CMakeLists.txt b/components/ulp/CMakeLists.txt index b55067b5801..99ab6beb777 100644 --- a/components/ulp/CMakeLists.txt +++ b/components/ulp/CMakeLists.txt @@ -4,6 +4,11 @@ if(${target} STREQUAL "linux") return() # This component is not supported by the POSIX/Linux simulator endif() +if(IDF_BUILD_V2) + include(${CMAKE_CURRENT_LIST_DIR}/CMakeLists_v2.txt) + return() +endif() + set(srcs "") set(includes "") diff --git a/components/ulp/CMakeLists_v2.txt b/components/ulp/CMakeLists_v2.txt new file mode 100644 index 00000000000..2b187bcf39d --- /dev/null +++ b/components/ulp/CMakeLists_v2.txt @@ -0,0 +1,183 @@ +set(srcs "") +set(includes "") +set(requires "") +set(priv_requires "") + +if(__ULP_BUILD) + if(ULP_TYPE STREQUAL "riscv") + list(APPEND includes + ulp_riscv/shared/include + ulp_riscv/ulp_core/include) + list(APPEND srcs + "ulp_riscv/ulp_core/ulp_riscv_vectors.S" + "ulp_riscv/ulp_core/start.S" + "ulp_riscv/ulp_core/ulp_riscv_utils.c") + list(APPEND requires + riscv + soc) + elseif(ULP_TYPE STREQUAL "lp_core") + list(APPEND includes + lp_core/lp_core/include + lp_core/shared/include) + list(APPEND srcs + "lp_core/lp_core/start.S" + "lp_core/lp_core/vector.S" + "lp_core/lp_core/port/${target}/vector_table.S" + "lp_core/shared/ulp_lp_core_memory_shared.c" + "lp_core/lp_core/lp_core_startup.c" + "lp_core/lp_core/lp_core_pmp.c" + "lp_core/lp_core/lp_core_utils.c" + "lp_core/lp_core/lp_core_panic.c" + "lp_core/lp_core/lp_core_interrupt.c" + "lp_core/shared/ulp_lp_core_lp_uart_shared.c" + "lp_core/lp_core/lp_core_uart.c" + "lp_core/shared/ulp_lp_core_lp_timer_shared.c" + "lp_core/shared/ulp_lp_core_critical_section_shared.c") + + list(APPEND requires + esp_common + esp_driver_uart + esp_hal_gpio + esp_hal_uart + esp_rom + hal + riscv + soc) + list(APPEND priv_requires + esp_hal_clock + esp_hal_i2s + esp_hal_rtc_timer + esp_hal_pmu + esp_hw_support) + + elseif(ULP_TYPE STREQUAL "fsm") + list(APPEND includes + ulp_common/include + ulp_fsm/include + ulp_fsm/include/${target}) + list(APPEND requires soc) + else() + message(FATAL_ERROR "__ULP_BUILD requires ULP_TYPE to be one of: riscv, lp_core, fsm.") + endif() +else() + if(CONFIG_ULP_COPROC_ENABLED) + list(APPEND includes + ulp_common/include) + list(APPEND requires + esp_common + soc) + endif() + + if(CONFIG_ULP_COPROC_TYPE_FSM) + list(APPEND includes + ulp_fsm/include + ulp_fsm/include/${target}) + list(APPEND srcs + "ulp_common/ulp_common.c" + "ulp_fsm/ulp.c" + "ulp_fsm/ulp_macro.c") + list(APPEND requires esp_hw_support) + endif() + + if(CONFIG_ULP_COPROC_TYPE_RISCV) + list(APPEND includes + ulp_riscv/include + ulp_riscv/shared/include + ulp_riscv/ulp_core/include) + list(APPEND srcs + "ulp_common/ulp_common.c" + "ulp_riscv/ulp_riscv.c") + list(APPEND requires + esp_hw_support + esp_hal_pmu) + endif() + + if(CONFIG_ULP_COPROC_TYPE_LP_CORE) + list(APPEND includes + lp_core/include + lp_core/lp_core/include + lp_core/shared/include) + list(APPEND srcs + "lp_core/lp_core.c" + "lp_core/shared/ulp_lp_core_memory_shared.c" + "lp_core/shared/ulp_lp_core_lp_timer_shared.c" + "lp_core/shared/ulp_lp_core_critical_section_shared.c") + list(APPEND requires + esp_hw_support + esp_hal_clock + esp_hal_pmu + esp_hal_rtc_timer + esp_rom + hal + riscv + soc) + endif() +endif() + +if(requires) + list(REMOVE_DUPLICATES requires) +endif() + +if(priv_requires) + list(REMOVE_DUPLICATES priv_requires) +endif() + +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS ${includes} + REQUIRES ${requires} + PRIV_REQUIRES ${priv_requires}) + +# In the parent app build, the ulp component only provides the host-side API. +# The executable/linker setup below is for native ULP child projects. +if(NOT __ULP_BUILD) + idf_define_esp_err_codes(HEADERS ulp_fsm/include/ulp_fsm_common.h) + return() +endif() + +# Preprocessor flags shared by every ULP memory-layout template. The template +# is a ".in" file, so the standard target_linker_script flow runs the C +# preprocessor on it. -D__ASSEMBLER__ keeps SoC headers to their macro-only +# form, and the component's own ld directory is available for relative +# includes. +set(ulp_ld_flags "-D__ASSEMBLER__ -I\"${CMAKE_CURRENT_SOURCE_DIR}/ld\"") + +# Select the linker template and target-specific link options used by the ULP +# executable created in the child project. The memory-layout template is +# registered with target_linker_script so the standard build system +# preprocesses and attaches it; per-type FLAGS supply the include directories +# its #include lines need. +if(ULP_TYPE STREQUAL "riscv") + target_linker_script(${COMPONENT_LIB} INTERFACE "ld/ulp_riscv.ld.in" MEMORY + FLAGS "${ulp_ld_flags}") + target_linker_script(${COMPONENT_LIB} INTERFACE "ld/${target}.peripherals.ld") + target_link_options(${COMPONENT_LIB} INTERFACE + -nostartfiles + -Wl,--gc-sections + -Wl,--no-warn-rwx-segments + LINKER:-u,main) + target_compile_definitions(${COMPONENT_LIB} PUBLIC + IS_ULP_COCPU + ULP_RISCV_REGISTER_OPS) +elseif(ULP_TYPE STREQUAL "lp_core") + # lp_core_riscv.ld includes soc/soc.h and esp_common headers, which are + # resolved automatically from the linked component graph. It also includes + # the esp_system ld snippets (ld.common, ld.hp_mem_defs); those live in the + # component's ld/ directory, which is not a public include dir, so pass it + # via FLAGS. + idf_component_get_property(esp_system_dir esp_system COMPONENT_DIR) + set(lp_core_ld_flags "${ulp_ld_flags} -I\"${esp_system_dir}/ld\" -I\"${esp_system_dir}/ld/${target}\"") + target_linker_script(${COMPONENT_LIB} INTERFACE "ld/lp_core_riscv.ld.in" MEMORY + FLAGS "${lp_core_ld_flags}") + + target_link_options(${COMPONENT_LIB} INTERFACE + -nostartfiles + -Wl,--gc-sections + -Wl,--no-warn-rwx-segments + LINKER:-u,main) + target_compile_definitions(${COMPONENT_LIB} PUBLIC IS_ULP_COCPU) + target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-implicit-fallthrough) +elseif(ULP_TYPE STREQUAL "fsm") + target_linker_script(${COMPONENT_LIB} INTERFACE "ld/ulp_fsm.ld.in" MEMORY + FLAGS "${ulp_ld_flags}") + target_link_options(${COMPONENT_LIB} INTERFACE "SHELL:-u entry") +endif() diff --git a/components/ulp/cmake/IDFULPProject.cmake b/components/ulp/cmake/IDFULPProject.cmake index 9c5353cff1c..5c447ffb925 100644 --- a/components/ulp/cmake/IDFULPProject.cmake +++ b/components/ulp/cmake/IDFULPProject.cmake @@ -1,35 +1,56 @@ +# This is the CMake v1 (legacy) ULP child entry point, used by ulp_embed_binary. +# CMake v2 full-subproject builds include components/ulp/cmake/ulp_project.cmake +# instead. +# +# Legacy ULP child projects are plain CMake projects, so the parent-provided +# sdkconfig.cmake has to be loaded here before the common ULP helpers inspect +# CONFIG_* values. CMake v1 callers pass this path explicitly. include(${SDKCONFIG_CMAKE}) +include(${CMAKE_CURRENT_LIST_DIR}/IDFULPProjectCommon.cmake) enable_language(C ASM) set(CMAKE_EXECUTABLE_SUFFIX ".elf") -# Logic to determine ULP type and set reusable flags -set(BUILD_RISCV OFF) -set(BUILD_FSM OFF) -set(BUILD_LP_CORE OFF) - -# If ULP_TYPE is explicitly set, use it; otherwise fall back to CONFIG checks -if(ULP_TYPE) - string(TOLOWER "${ULP_TYPE}" ulp_type_lower) - if(ulp_type_lower STREQUAL "riscv") - set(BUILD_RISCV ON) - elseif(ulp_type_lower STREQUAL "fsm") - set(BUILD_FSM ON) - elseif(ulp_type_lower STREQUAL "lp_core") - set(BUILD_LP_CORE ON) - endif() -elseif(CONFIG_ULP_COPROC_TYPE_RISCV) - set(BUILD_RISCV ON) -elseif(CONFIG_ULP_COPROC_TYPE_LP_CORE) - set(BUILD_LP_CORE ON) -elseif(CONFIG_ULP_COPROC_TYPE_FSM) - set(BUILD_FSM ON) -endif() +ulp_detect_build_type() +ulp_apply_build_type_options() # Check the supported assembler version if(BUILD_FSM) check_expected_tool_version("esp32ulp-elf" ${CMAKE_ASM_COMPILER}) endif() +# CMake v1-only helpers that preprocess the ULP memory-layout linker template +# with the C preprocessor. The CMake v2 path attaches the template through +# target_linker_script(... FLAGS) and does not use these. +function(__ulp_create_arg_file arguments output_file) + # Escape all spaces + list(TRANSFORM arguments REPLACE " " "\\\\ ") + # Create a single string with all args separated by space + list(JOIN arguments " " arguments) + # Generate the response file late enough for target generator expressions + # in include directories to resolve to their final build-system values. + file(GENERATE OUTPUT "${output_file}" CONTENT "${arguments}") +endfunction() + +function(__ulp_add_preprocessed_linker_script ulp_app_name ld_template ld_script ld_script_target) + # Use the C preprocessor so sdkconfig and SoC constants can shape the + # linker template before the ULP executable is linked. + set(preprocessor_args -D__ASSEMBLER__ -E -P -xc -o ${ld_script} ${ARGN} ${ld_template}) + set(compiler_arguments_file ${CMAKE_CURRENT_BINARY_DIR}/${ld_script}_args.txt) + __ulp_create_arg_file("${preprocessor_args}" "${compiler_arguments_file}") + + add_custom_command(OUTPUT ${ld_script} + COMMAND ${CMAKE_C_COMPILER} @${compiler_arguments_file} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + MAIN_DEPENDENCY ${ld_template} + DEPENDS ${SDKCONFIG_HEADER} + COMMENT "Generating ${ld_script} linker script..." + VERBATIM) + + add_custom_target(${ld_script_target} DEPENDS ${ld_script}) + add_dependencies(${ulp_app_name} ${ld_script_target}) + target_link_options(${ulp_app_name} PRIVATE SHELL:-T ${CMAKE_CURRENT_BINARY_DIR}/${ld_script}) +endfunction() + function(ulp_apply_default_options ulp_app_name) if(BUILD_RISCV) target_link_options(${ulp_app_name} PRIVATE "-nostartfiles") @@ -47,16 +68,6 @@ function(ulp_apply_default_options ulp_app_name) endfunction() function(ulp_apply_default_sources ulp_app_name) - - function(create_arg_file arguments output_file) - # Escape all spaces - list(TRANSFORM arguments REPLACE " " "\\\\ ") - # Create a single string with all args separated by space - list(JOIN arguments " " arguments) - # Write it into the response file - file(WRITE ${output_file} ${arguments}) - endfunction() - message(STATUS "Building ULP app ${ulp_app_name}") get_filename_component(sdkconfig_dir ${SDKCONFIG_HEADER} DIRECTORY) @@ -76,32 +87,19 @@ function(ulp_apply_default_sources ulp_app_name) # Pre-process the linker script if(BUILD_RISCV) - set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/ulp_riscv.ld) + set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/ulp_riscv.ld.in) elseif(BUILD_LP_CORE) - set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/lp_core_riscv.ld) + set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/lp_core_riscv.ld.in) elseif(BUILD_FSM) - set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/ulp_fsm.ld) + set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/ulp_fsm.ld.in) else() message(FATAL_ERROR "Unable to determine ULP type. ") endif() - get_filename_component(ULP_LD_SCRIPT ${ULP_LD_TEMPLATE} NAME) - - # Put all arguments to the list - set(preprocessor_args -D__ASSEMBLER__ -E -P -xc -o ${ULP_LD_SCRIPT} ${ULP_PREPRO_ARGS} ${ULP_LD_TEMPLATE}) - set(compiler_arguments_file ${CMAKE_CURRENT_BINARY_DIR}/${ULP_LD_SCRIPT}_args.txt) - create_arg_file("${preprocessor_args}" "${compiler_arguments_file}") - - add_custom_command(OUTPUT ${ULP_LD_SCRIPT} - COMMAND ${CMAKE_C_COMPILER} @${compiler_arguments_file} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - MAIN_DEPENDENCY ${ULP_LD_TEMPLATE} - DEPENDS ${SDKCONFIG_HEADER} - COMMENT "Generating ${ULP_LD_SCRIPT} linker script..." - VERBATIM) - add_custom_target(ld_script DEPENDS ${ULP_LD_SCRIPT}) - add_dependencies(${ulp_app_name} ld_script) - target_link_options(${ulp_app_name} PRIVATE SHELL:-T ${CMAKE_CURRENT_BINARY_DIR}/${ULP_LD_SCRIPT}) + # Strip the .in suffix so the generated script keeps its .ld name. + get_filename_component(ULP_LD_SCRIPT ${ULP_LD_TEMPLATE} NAME_WLE) + __ulp_add_preprocessed_linker_script(${ulp_app_name} ${ULP_LD_TEMPLATE} ${ULP_LD_SCRIPT} + ld_script ${ULP_PREPRO_ARGS}) # To avoid warning "Manually-specified variables were not used by the project" set(bypassWarning "${IDF_TARGET}") @@ -146,7 +144,7 @@ function(ulp_apply_default_sources ulp_app_name) set(preprocessor_args -D__ASSEMBLER__ -E -P -xc -o ${ulp_ps_output} ${ULP_PREPRO_ARGS} ${ulp_s_source}) set(compiler_arguments_file ${CMAKE_CURRENT_BINARY_DIR}/${ulp_ps_source}_args.txt) - create_arg_file("${preprocessor_args}" "${compiler_arguments_file}") + __ulp_create_arg_file("${preprocessor_args}" "${compiler_arguments_file}") # Generate preprocessed assembly files. add_custom_command(OUTPUT ${ulp_ps_output} @@ -237,59 +235,3 @@ function(ulp_apply_default_sources ulp_app_name) endif() endfunction() - -function(ulp_add_build_binary_targets ulp_app_name) - cmake_parse_arguments(ULP "" "PREFIX" "" ${ARGN}) - if(NOT ULP_PREFIX) - set(ULP_PREFIX "ulp_") - endif() - - if(ADD_PICOLIBC_SPECS) - target_compile_options(${ulp_app_name} PRIVATE $<$:-specs=picolibc.specs>) - target_compile_options(${ulp_app_name} PRIVATE $<$:-specs=picolibcpp.specs>) - endif() - - if(BUILD_LP_CORE) - set(ULP_BASE_ADDR "0x0") - else() - set(ULP_BASE_ADDR "0x50000000") - endif() - - set(ULP_MAP_GEN ${PYTHON} ${IDF_PATH}/components/ulp/esp32ulp_mapgen.py) - - # Dump the list of global symbols in a convenient format - add_custom_command(OUTPUT ${ULP_APP_NAME}.sym - COMMAND ${CMAKE_READELF} -sW $ > ${ulp_app_name}.sym - DEPENDS ${ulp_app_name} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - - # Dump the binary for inclusion into the project - if(BUILD_LP_CORE AND CONFIG_ULP_COPROC_RUN_FROM_HP_MEM) - add_custom_command(OUTPUT ${ulp_app_name}.bin - COMMAND ${PYTHON} -m esptool --chip ${IDF_TARGET} elf2image - --output ${ulp_app_name}.bin $ - DEPENDS ${ulp_app_name} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - else() - add_custom_command(OUTPUT ${ulp_app_name}.bin - COMMAND ${CMAKE_OBJCOPY} -O binary $ ${ulp_app_name}.bin - DEPENDS ${ulp_app_name} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - endif() - - add_custom_command(OUTPUT ${ulp_app_name}.ld ${ulp_app_name}.h - COMMAND ${ULP_MAP_GEN} -s ${ulp_app_name}.sym -o ${ulp_app_name} - --base ${ULP_BASE_ADDR} --prefix ${ULP_PREFIX} - --target ${IDF_TARGET} - DEPENDS ${ulp_app_name}.sym - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - - # Building the component separately from the project should result in - # ULP files being built. - add_custom_target(build - DEPENDS ${ulp_app_name} ${ulp_app_name}.bin ${ulp_app_name}.sym - ${CMAKE_CURRENT_BINARY_DIR}/${ulp_app_name}.ld - ${CMAKE_CURRENT_BINARY_DIR}/${ulp_app_name}.h - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - -endfunction() diff --git a/components/ulp/cmake/IDFULPProjectCommon.cmake b/components/ulp/cmake/IDFULPProjectCommon.cmake new file mode 100644 index 00000000000..8579ccbd396 --- /dev/null +++ b/components/ulp/cmake/IDFULPProjectCommon.cmake @@ -0,0 +1,96 @@ +macro(ulp_detect_build_type) + # Logic to determine ULP type and set reusable flags + set(BUILD_RISCV OFF) + set(BUILD_FSM OFF) + set(BUILD_LP_CORE OFF) + + # If ULP_TYPE is explicitly set, use it; otherwise fall back to CONFIG checks + if(ULP_TYPE) + string(TOLOWER "${ULP_TYPE}" ulp_type_lower) + if(ulp_type_lower STREQUAL "riscv") + set(BUILD_RISCV ON) + elseif(ulp_type_lower STREQUAL "fsm") + set(BUILD_FSM ON) + elseif(ulp_type_lower STREQUAL "lp_core") + set(BUILD_LP_CORE ON) + endif() + elseif(CONFIG_ULP_COPROC_TYPE_RISCV) + set(BUILD_RISCV ON) + elseif(CONFIG_ULP_COPROC_TYPE_LP_CORE) + set(BUILD_LP_CORE ON) + elseif(CONFIG_ULP_COPROC_TYPE_FSM) + set(BUILD_FSM ON) + endif() +endmacro() + +macro(ulp_apply_build_type_options) + # CMake initializes GNU ASM definitions as --defsym during enable_language(). + # Native full-subproject FSM sources are preprocessed by the toolchain rule, + # so use C preprocessor definitions for generated ASM rules. + if(BUILD_FSM AND ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN) + set(CMAKE_ASM_DEFINE_FLAG "-D") + endif() +endmacro() + +function(ulp_add_build_binary_targets ulp_app_name) + cmake_parse_arguments(ULP "" "PREFIX" "" ${ARGN}) + if(NOT ULP_PREFIX) + set(ULP_PREFIX "ulp_") + endif() + + if(ADD_PICOLIBC_SPECS) + target_compile_options(${ulp_app_name} PRIVATE $<$:-specs=picolibc.specs>) + target_compile_options(${ulp_app_name} PRIVATE $<$:-specs=picolibcpp.specs>) + endif() + + if(BUILD_LP_CORE) + set(ULP_BASE_ADDR "0x0") + else() + set(ULP_BASE_ADDR "0x50000000") + endif() + + set(ULP_MAP_GEN ${PYTHON} ${IDF_PATH}/components/ulp/esp32ulp_mapgen.py) + + # Dump the list of global symbols in a convenient format + add_custom_command(OUTPUT ${ulp_app_name}.sym + COMMAND ${CMAKE_READELF} -sW $ > ${ulp_app_name}.sym + DEPENDS ${ulp_app_name} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + # Dump the binary for inclusion into the project + if(BUILD_LP_CORE AND CONFIG_ULP_COPROC_RUN_FROM_HP_MEM) + # LP-core images running from HP memory use the normal ESP image format; + # RTC-memory ULP binaries are raw object-copy output. + add_custom_command(OUTPUT ${ulp_app_name}.bin + COMMAND ${PYTHON} -m esptool --chip ${IDF_TARGET} elf2image + --output ${ulp_app_name}.bin $ + DEPENDS ${ulp_app_name} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + else() + add_custom_command(OUTPUT ${ulp_app_name}.bin + COMMAND ${CMAKE_OBJCOPY} -O binary $ ${ulp_app_name}.bin + DEPENDS ${ulp_app_name} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + endif() + + add_custom_command(OUTPUT ${ulp_app_name}.ld ${ulp_app_name}.h + COMMAND ${ULP_MAP_GEN} -s ${ulp_app_name}.sym -o ${ulp_app_name} + --base ${ULP_BASE_ADDR} --prefix ${ULP_PREFIX} + --target ${IDF_TARGET} + DEPENDS ${ulp_app_name}.sym + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + add_custom_target(${ulp_app_name}_binary_artifacts + DEPENDS ${ulp_app_name} ${ulp_app_name}.bin ${ulp_app_name}.sym + ${CMAKE_CURRENT_BINARY_DIR}/${ulp_app_name}.ld + ${CMAKE_CURRENT_BINARY_DIR}/${ulp_app_name}.h + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + # Building the component separately from the project should result in all + # ULP files being built. Multiple ULP executables in one child project all + # contribute their artifact targets here. + if(NOT TARGET build) + add_custom_target(build) + endif() + add_dependencies(build ${ulp_app_name}_binary_artifacts) +endfunction() diff --git a/components/ulp/cmake/preprocess_fsm_asm.cmake b/components/ulp/cmake/preprocess_fsm_asm.cmake new file mode 100644 index 00000000000..7084bfb54e8 --- /dev/null +++ b/components/ulp/cmake/preprocess_fsm_asm.cmake @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +foreach(required_var ULP_FSM_PREPROCESSOR ULP_FSM_ASSEMBLER ULP_FSM_TARGET ULP_FSM_SOURCE ULP_FSM_OBJECT) + if(NOT DEFINED ${required_var} OR "${${required_var}}" STREQUAL "") + message(FATAL_ERROR "${required_var} is required") + endif() +endforeach() + +set(extra_args) +set(extra_args_started OFF) +math(EXPR last_arg_index "${CMAKE_ARGC} - 1") +foreach(arg_index RANGE 0 ${last_arg_index}) + if("${CMAKE_ARGV${arg_index}}" STREQUAL "--") + set(extra_args_started ON) + continue() + endif() + + if(extra_args_started) + list(APPEND extra_args "${CMAKE_ARGV${arg_index}}") + endif() +endforeach() + +set(preprocessed_source "${ULP_FSM_OBJECT}.ulp.S") +set(listing_file "${ULP_FSM_OBJECT}.lst") + +execute_process( + COMMAND "${ULP_FSM_PREPROCESSOR}" + -D__ASSEMBLER__ -E -P -x c + ${extra_args} + -o "${preprocessed_source}" + "${ULP_FSM_SOURCE}" + RESULT_VARIABLE preprocess_result +) +if(NOT preprocess_result EQUAL 0) + message(FATAL_ERROR "Failed to preprocess ${ULP_FSM_SOURCE}") +endif() + +execute_process( + COMMAND "${ULP_FSM_ASSEMBLER}" + "--mcpu=${ULP_FSM_TARGET}" + "-al=${listing_file}" + -o "${ULP_FSM_OBJECT}" + -c "${preprocessed_source}" + RESULT_VARIABLE assemble_result +) +if(NOT assemble_result EQUAL 0) + message(FATAL_ERROR "Failed to assemble ${preprocessed_source}") +endif() diff --git a/components/ulp/cmake/toolchain-esp32-ulp.cmake b/components/ulp/cmake/toolchain-esp32-ulp.cmake index 599e939addc..c16ba8f9736 100644 --- a/components/ulp/cmake/toolchain-esp32-ulp.cmake +++ b/components/ulp/cmake/toolchain-esp32-ulp.cmake @@ -9,9 +9,28 @@ set(CMAKE_ASM_COMPILER "esp32ulp-elf-as") set(CMAKE_OBJCOPY "esp32ulp-elf-objcopy") set(CMAKE_LINKER "esp32ulp-elf-ld") +# Native CMake v2 ULP projects include cmakev2/idf.cmake before project(), +# making idf_project_init visible when this toolchain is loaded. Legacy child +# projects, even when launched by a CMake v2 parent, include the old IDF CMake +# entrypoint and keep the historical explicit preprocessing path. +set(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN OFF) +if(IDF_BUILD_V2 AND COMMAND idf_project_init) + set(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN ON) +endif() -set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT "${CMAKE_ASM${ASM_DIALECT}_COMPILER} \ - --mcpu=esp32 -o -c ") +if(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN) + set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT + "${CMAKE_COMMAND} \ + -DULP_FSM_PREPROCESSOR=${CMAKE_C_COMPILER} \ + -DULP_FSM_ASSEMBLER=${CMAKE_ASM${ASM_DIALECT}_COMPILER} \ + -DULP_FSM_TARGET=esp32 \ + -DULP_FSM_SOURCE= \ + -DULP_FSM_OBJECT= \ + -P ${CMAKE_CURRENT_LIST_DIR}/preprocess_fsm_asm.cmake -- ") +else() + set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT "${CMAKE_ASM${ASM_DIALECT}_COMPILER} \ + --mcpu=esp32 -o -c ") +endif() set(CMAKE_EXE_LINKER_FLAGS "-A elf32-esp32ulp -nostdlib" CACHE STRING "ULP Linker Base Flags") set(CMAKE_ASM_LINK_EXECUTABLE "${CMAKE_LINKER} \ -o ") diff --git a/components/ulp/cmake/toolchain-esp32s2-ulp.cmake b/components/ulp/cmake/toolchain-esp32s2-ulp.cmake index 7eedc02982d..ca3a0bb5d4a 100644 --- a/components/ulp/cmake/toolchain-esp32s2-ulp.cmake +++ b/components/ulp/cmake/toolchain-esp32s2-ulp.cmake @@ -9,8 +9,28 @@ set(CMAKE_ASM_COMPILER "esp32ulp-elf-as") set(CMAKE_OBJCOPY "esp32ulp-elf-objcopy") set(CMAKE_LINKER "esp32ulp-elf-ld") -set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT "${CMAKE_ASM${ASM_DIALECT}_COMPILER} \ - --mcpu=esp32s2 -o -c ") +# Native CMake v2 ULP projects include cmakev2/idf.cmake before project(), +# making idf_project_init visible when this toolchain is loaded. Legacy child +# projects, even when launched by a CMake v2 parent, include the old IDF CMake +# entrypoint and keep the historical explicit preprocessing path. +set(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN OFF) +if(IDF_BUILD_V2 AND COMMAND idf_project_init) + set(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN ON) +endif() + +if(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN) + set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT + "${CMAKE_COMMAND} \ + -DULP_FSM_PREPROCESSOR=${CMAKE_C_COMPILER} \ + -DULP_FSM_ASSEMBLER=${CMAKE_ASM${ASM_DIALECT}_COMPILER} \ + -DULP_FSM_TARGET=esp32s2 \ + -DULP_FSM_SOURCE= \ + -DULP_FSM_OBJECT= \ + -P ${CMAKE_CURRENT_LIST_DIR}/preprocess_fsm_asm.cmake -- ") +else() + set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT "${CMAKE_ASM${ASM_DIALECT}_COMPILER} \ + --mcpu=esp32s2 -o -c ") +endif() set(CMAKE_EXE_LINKER_FLAGS "-A elf32-esp32s2ulp -nostdlib" CACHE STRING "ULP Linker Base Flags") set(CMAKE_ASM_LINK_EXECUTABLE "${CMAKE_LINKER} \ -o ") diff --git a/components/ulp/cmake/toolchain-esp32s3-ulp.cmake b/components/ulp/cmake/toolchain-esp32s3-ulp.cmake index 61e0a85c315..3b02407ad01 100644 --- a/components/ulp/cmake/toolchain-esp32s3-ulp.cmake +++ b/components/ulp/cmake/toolchain-esp32s3-ulp.cmake @@ -9,8 +9,28 @@ set(CMAKE_ASM_COMPILER "esp32ulp-elf-as") set(CMAKE_OBJCOPY "esp32ulp-elf-objcopy") set(CMAKE_LINKER "esp32ulp-elf-ld") -set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT "${CMAKE_ASM${ASM_DIALECT}_COMPILER} \ - --mcpu=esp32s3 -o -c ") +# Native CMake v2 ULP projects include cmakev2/idf.cmake before project(), +# making idf_project_init visible when this toolchain is loaded. Legacy child +# projects, even when launched by a CMake v2 parent, include the old IDF CMake +# entrypoint and keep the historical explicit preprocessing path. +set(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN OFF) +if(IDF_BUILD_V2 AND COMMAND idf_project_init) + set(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN ON) +endif() + +if(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN) + set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT + "${CMAKE_COMMAND} \ + -DULP_FSM_PREPROCESSOR=${CMAKE_C_COMPILER} \ + -DULP_FSM_ASSEMBLER=${CMAKE_ASM${ASM_DIALECT}_COMPILER} \ + -DULP_FSM_TARGET=esp32s3 \ + -DULP_FSM_SOURCE= \ + -DULP_FSM_OBJECT= \ + -P ${CMAKE_CURRENT_LIST_DIR}/preprocess_fsm_asm.cmake -- ") +else() + set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT "${CMAKE_ASM${ASM_DIALECT}_COMPILER} \ + --mcpu=esp32s3 -o -c ") +endif() set(CMAKE_EXE_LINKER_FLAGS "-A elf32-esp32s3ulp -nostdlib" CACHE STRING "ULP Linker Base Flags") set(CMAKE_ASM_LINK_EXECUTABLE "${CMAKE_LINKER} \ -o ") diff --git a/components/ulp/cmake/ulp_project.cmake b/components/ulp/cmake/ulp_project.cmake new file mode 100644 index 00000000000..072d46522f7 --- /dev/null +++ b/components/ulp/cmake/ulp_project.cmake @@ -0,0 +1,188 @@ +# ULP subproject entry point for CMake v2 full-subproject builds. +# +# A ULP child project includes this file instead of tools/cmakev2/idf.cmake +# directly. It pulls in the cmakev2 build system and layers the ULP subproject +# API on top: ulp_project_init, ulp_build_executable, ulp_build_binary and +# ulp_project_default, the ULP analogs of idf_project_init, +# idf_build_executable, idf_build_binary and idf_project_default. + +include(${IDF_PATH}/tools/cmakev2/idf.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/IDFULPProjectCommon.cmake) + +# Reset the compile/link state inherited from the parent app build and select +# ULP-specific build behavior. Idempotent within a child configure. +function(__ulp_prepare_build) + idf_build_get_property(ulp_build_prepared __ULP_BUILD_PREPARED) + if(ulp_build_prepared) + return() + endif() + + if(BUILD_FSM) + check_expected_tool_version("esp32ulp-elf" ${CMAKE_ASM_COMPILER}) + # FSM links with esp32ulp-elf-ld directly, not through GCC, so avoid + # GCC-driver link grouping flags from the generic executable helper. + idf_build_set_property(LINKER_TYPE ULP_FSM) + endif() + + # ULP projects use their own toolchain files and component-provided options, + # so discard compile/link options added for normal app builds. + foreach(property IN ITEMS + COMPILE_OPTIONS + C_COMPILE_OPTIONS + CXX_COMPILE_OPTIONS + ASM_COMPILE_OPTIONS + COMPILE_DEFINITIONS + LINK_OPTIONS) + idf_build_set_property(${property} "") + endforeach() + + # Do not apply the default app component closure to ULP child projects. + # ULP components should contribute dependencies explicitly. + idf_build_set_property(__COMPONENT_REQUIRES_COMMON "") + idf_build_set_property(__COMMON_COMPONENT_INTERFACES "") + idf_build_set_property(__COMMON_COMPONENTS_INITIALIZED YES) + + idf_build_set_property(__ULP_BUILD_PREPARED YES) +endfunction() + +#[[api +.. cmakev2:macro:: ulp_project_init + + .. code-block:: cmake + + ulp_project_init() + + Initialize a ULP full-subproject build. Calls :cmakev2:ref:`idf_project_init` + and then applies the ULP-specific setup: it detects the ULP type (RISC-V, LP + core or FSM) from the ``ULP_TYPE`` variable or the project configuration and + resets the compile and link options inherited from the parent application + build, so the ULP executable uses only its own toolchain and + component-provided options. + + This is the ULP analog of :cmakev2:ref:`idf_project_init` and is the entry + point for projects that build more than one ULP executable, followed by + :cmakev2:ref:`ulp_build_executable` and :cmakev2:ref:`ulp_build_binary` + calls. For the common single-executable case use + :cmakev2:ref:`ulp_project_default` instead. +#]] +macro(ulp_project_init) + idf_project_init() + ulp_detect_build_type() + ulp_apply_build_type_options() + __ulp_prepare_build() +endmacro() + +#[[api +.. cmakev2:function:: ulp_build_executable + + .. code-block:: cmake + + ulp_build_executable( + [COMPONENTS ...] + [MAPFILE_TARGET ]) + + *executable[in]* + + Name of the ULP executable target to create. + + Build a ULP executable. This is the ULP analog of + :cmakev2:ref:`idf_build_executable` and forwards its arguments to + :cmakev2:ref:`idf_build_executable`. Call :cmakev2:ref:`ulp_build_binary` + for each executable that should produce embeddable ULP artifacts. +#]] +function(ulp_build_executable executable) + if("PREFIX" IN_LIST ARGN) + message(FATAL_ERROR "Use ulp_build_binary(${executable} PREFIX ...) to set ULP symbol prefixes.") + endif() + + idf_build_executable(${executable} ${ARGN}) +endfunction() + +#[[api +.. cmakev2:function:: ulp_build_binary + + .. code-block:: cmake + + ulp_build_binary( + [PREFIX ]) + + *executable[in]* + + ULP executable target for which to generate embeddable artifacts. + + *PREFIX[in,opt]* + + Symbol name prefix for the generated symbol header and linker export + files. Defaults to ``ulp_``. Use a distinct prefix per executable when a + project builds several ULP binaries. + + Generate the ``.bin`` payload and symbol header/linker export files for a + ULP executable. This is the ULP analog of :cmakev2:ref:`idf_build_binary`. +#]] +function(ulp_build_binary executable) + cmake_parse_arguments(_ULP "" "PREFIX" "" ${ARGN}) + if(_ULP_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments to ulp_build_binary(): ${_ULP_UNPARSED_ARGUMENTS}") + endif() + + if(DEFINED _ULP_PREFIX) + ulp_add_build_binary_targets(${executable} PREFIX "${_ULP_PREFIX}") + elseif(ULP_PREFIX_APPEND_BIN_NAME) + set(prefix "ulp_") + if(DEFINED ULP_VAR_PREFIX) + set(prefix "${ULP_VAR_PREFIX}") + endif() + string(MAKE_C_IDENTIFIER "${prefix}${executable}_" prefix) + ulp_add_build_binary_targets(${executable} PREFIX "${prefix}") + else() + ulp_add_build_binary_targets("${executable}") + endif() +endfunction() + +# Build the single default ULP executable from the main component. +function(__ulp_project_default) + set(ulp_app_name "${PROJECT_NAME}") + + set(executable_args COMPONENTS main) + set(binary_args) + if(NOT BUILD_FSM) + list(APPEND executable_args MAPFILE_TARGET ${ulp_app_name}_mapfile) + endif() + if(DEFINED ULP_VAR_PREFIX AND NOT "${ULP_VAR_PREFIX}" STREQUAL "") + list(APPEND binary_args PREFIX ${ULP_VAR_PREFIX}) + endif() + + ulp_build_executable(${ulp_app_name} ${executable_args}) + ulp_build_binary(${ulp_app_name} ${binary_args}) + + idf_build_generate_metadata(EXECUTABLE ${ulp_app_name}) + + if(TARGET "${ulp_app_name}_mapfile") + idf_create_size_report("${ulp_app_name}_mapfile" TARGET size) + endif() + + idf_build_generate_depgraph("${ulp_app_name}") +endfunction() + +#[[api +.. cmakev2:macro:: ulp_project_default + + .. code-block:: cmake + + ulp_project_default() + + Build a single ULP executable from the ``main`` component and its transitive + dependencies, the ULP analog of :cmakev2:ref:`idf_project_default`. Calls + :cmakev2:ref:`ulp_project_init`, then builds the executable (named after + ``PROJECT_NAME``) with :cmakev2:ref:`ulp_build_executable` and generates its + embeddable artifacts with :cmakev2:ref:`ulp_build_binary`. +#]] +macro(ulp_project_default) + ulp_project_init() + + # A single executable is built, so DEFERRED optional-requires resolution is + # safe and keeps the linked component set minimal. + idf_build_set_property(IDF_COMPONENT_OPTIONAL_REQUIRES_MODE DEFERRED) + + __ulp_project_default() +endmacro() diff --git a/components/ulp/ld/lp_core_riscv.ld b/components/ulp/ld/lp_core_riscv.ld.in similarity index 100% rename from components/ulp/ld/lp_core_riscv.ld rename to components/ulp/ld/lp_core_riscv.ld.in diff --git a/components/ulp/ld/ulp_fsm.ld b/components/ulp/ld/ulp_fsm.ld.in similarity index 100% rename from components/ulp/ld/ulp_fsm.ld rename to components/ulp/ld/ulp_fsm.ld.in diff --git a/components/ulp/ld/ulp_riscv.ld b/components/ulp/ld/ulp_riscv.ld.in similarity index 100% rename from components/ulp/ld/ulp_riscv.ld rename to components/ulp/ld/ulp_riscv.ld.in diff --git a/components/ulp/project_include.cmake b/components/ulp/project_include.cmake index bc9712fa26e..0d68833185c 100644 --- a/components/ulp/project_include.cmake +++ b/components/ulp/project_include.cmake @@ -1,10 +1,16 @@ +if(IDF_BUILD_V2 AND __ULP_BUILD) + return() +endif() + # ulp_embed_binary # # Create ULP binary and embed into the application. -function(__setup_ulp_project app_name project_path prefix type s_sources exp_dep_srcs) +function(__setup_ulp_project app_name project_path prefix prefix_append_bin_name type binary_names + s_sources exp_dep_srcs) if(NOT CMAKE_BUILD_EARLY_EXPANSION) + set(ulp_cmake_dir "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/cmake") set(sources "") spaces2list(s_sources) foreach(source ${s_sources}) @@ -12,28 +18,69 @@ function(__setup_ulp_project app_name project_path prefix type s_sources exp_dep list(APPEND sources ${source}) endforeach() + # In cmakev2, anchor the ULP sub-project's binary dir to the parent's + # BUILD_DIR so it is not nested under the component that happened to + # register it, and so that wiping the parent build dir removes the ULP + # artifacts together with any child-owned sdkconfig. + if(IDF_BUILD_V2) + idf_build_get_property(build_dir BUILD_DIR) + set(ulp_binary_dir "${build_dir}/subprojects/${app_name}") + + # Safety net for cases where the build directory is not wiped + # before changing target (e.g. someone reconfigures directly via + # `cmake -B build -DIDF_TARGET=...` instead of `idf.py set-target`, + # which would normally pull in `fullclean` and remove the whole + # build tree). When `_IDF_PY_SET_TARGET_ACTION=1` is set, mirror + # what the parent does to its own sdkconfig (cmakev1 renames it, + # cmakev2 relaxes the consistency check) and move the sub-project's + # sdkconfig aside. The ULP's externalproject_add configure is lazy + # and runs after the parent's configure exits, so by then + # _IDF_PY_SET_TARGET_ACTION is no longer in the environment. + # Without this, a stale sub-project sdkconfig from a previous + # target would trip the cross-check inside the ULP's + # __init_idf_target. + set(ulp_sdkconfig "${ulp_binary_dir}/sdkconfig") + if("$ENV{_IDF_PY_SET_TARGET_ACTION}" STREQUAL "1" AND EXISTS "${ulp_sdkconfig}") + file(RENAME "${ulp_sdkconfig}" "${ulp_sdkconfig}.old") + message(STATUS "Existing sub-project sdkconfig '${ulp_sdkconfig}' " + "renamed to '${ulp_sdkconfig}.old'.") + endif() + else() + set(ulp_binary_dir "${CMAKE_CURRENT_BINARY_DIR}/${app_name}") + endif() + foreach(source ${sources}) get_filename_component(ps_source ${source} NAME_WE) - set(ps_output ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/${ps_source}.ulp.S) + set(ps_output ${ulp_binary_dir}/${ps_source}.ulp.S) list(APPEND ps_sources ${ps_output}) endforeach() - set(ulp_artifacts_prefix ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/${app_name}) + if(NOT binary_names) + set(binary_names "${app_name}") + endif() - set(ulp_artifacts ${ulp_artifacts_prefix}.bin - ${ulp_artifacts_prefix}.ld - ${ulp_artifacts_prefix}.h) + # Full ULP subprojects may emit multiple binaries. Embed each declared + # output in the main app so runtime code can choose which ULP program + # to load. + foreach(binary_name IN LISTS binary_names) + set(ulp_artifacts_prefix ${ulp_binary_dir}/${binary_name}) - set(ulp_artifacts_extras ${ulp_artifacts_prefix}.map - ${ulp_artifacts_prefix}.sym - ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/esp32.ulp.ld) + list(APPEND ulp_artifacts + ${ulp_artifacts_prefix}.bin + ${ulp_artifacts_prefix}.ld + ${ulp_artifacts_prefix}.h) + + list(APPEND ulp_artifacts_extras + ${ulp_artifacts_prefix}.map + ${ulp_artifacts_prefix}.sym + ${ulp_binary_dir}/${binary_name}.elf) + endforeach() # Replace the separator for the list of ULP source files that will be passed to # the external ULP project. This is a workaround to the bug https://public.kitware.com/Bug/view.php?id=16137. string(REPLACE ";" "|" ulp_s_sources "${ulp_s_sources}") - idf_build_get_property(sdkconfig_header SDKCONFIG_HEADER) - idf_build_get_property(sdkconfig_cmake SDKCONFIG_CMAKE) + idf_build_get_property(sdkconfig SDKCONFIG) idf_build_get_property(idf_path IDF_PATH) idf_build_get_property(idf_target IDF_TARGET) idf_build_get_property(python PYTHON) @@ -48,7 +95,7 @@ function(__setup_ulp_project app_name project_path prefix type s_sources exp_dep "Only FSM type is available for ULP on this target.") endif() endif() - set(TOOLCHAIN_FLAG ${idf_path}/components/ulp/cmake/toolchain-${idf_target}-ulp.cmake) + set(TOOLCHAIN_FLAG ${ulp_cmake_dir}/toolchain-${idf_target}-ulp.cmake) set(ULP_IS_RISCV OFF) elseif(IDF_TARGET STREQUAL "esp32s2" OR IDF_TARGET STREQUAL "esp32s3") # If both FSM and RISC-V are enabled in sdkconfig and a TYPE was @@ -58,63 +105,118 @@ function(__setup_ulp_project app_name project_path prefix type s_sources exp_dep message(STATUS "Both RISCV and FSM are enabled, using '${type}' toolchain for ${app_name} ULP project.") string(TOLOWER "${type}" type_lower) if(type_lower STREQUAL "riscv") - set(TOOLCHAIN_FLAG ${idf_path}/components/ulp/cmake/toolchain-ulp-riscv.cmake) + set(TOOLCHAIN_FLAG ${ulp_cmake_dir}/toolchain-ulp-riscv.cmake) elseif(type_lower STREQUAL "fsm") - set(TOOLCHAIN_FLAG ${idf_path}/components/ulp/cmake/toolchain-${idf_target}-ulp.cmake) + set(TOOLCHAIN_FLAG ${ulp_cmake_dir}/toolchain-${idf_target}-ulp.cmake) else() message(FATAL_ERROR "Invalid ULP_TYPE '${type}'; expected 'fsm' or 'riscv'.") endif() else() if(CONFIG_ULP_COPROC_TYPE_RISCV STREQUAL "y") - set(TOOLCHAIN_FLAG ${idf_path}/components/ulp/cmake/toolchain-ulp-riscv.cmake) + set(TOOLCHAIN_FLAG ${ulp_cmake_dir}/toolchain-ulp-riscv.cmake) else() - set(TOOLCHAIN_FLAG ${idf_path}/components/ulp/cmake/toolchain-${idf_target}-ulp.cmake) + set(TOOLCHAIN_FLAG ${ulp_cmake_dir}/toolchain-${idf_target}-ulp.cmake) endif() endif() elseif(CONFIG_ULP_COPROC_TYPE_LP_CORE) - set(TOOLCHAIN_FLAG ${idf_path}/components/ulp/cmake/toolchain-lp-core-riscv.cmake) + set(TOOLCHAIN_FLAG ${ulp_cmake_dir}/toolchain-lp-core-riscv.cmake) + endif() + + set(ulp_project_args) + if(IDF_BUILD_V2) + set(ulp_project_args + # The parent passes a superset of variables that different ULP + # subproject styles consume (e.g. ULP_VAR_PREFIX only by + # ulp_project_default, COMPONENT_INCLUDES only by hand-written + # subprojects). Do not warn about the ones a given child ignores. + --no-warn-unused-cli + -DIDF_DEFAULT_PROJECT_NAME=${app_name} + -DIDF_BUILD_V2=y + # Internal marker for the ULP child project component graph. + -D__ULP_BUILD=1 + -DIDF_PARENT_BUILD_DIR=${build_dir} + -DULP_PREFIX_APPEND_BIN_NAME=${prefix_append_bin_name} + -DSDKCONFIG=${sdkconfig} + -DSDKCONFIG_HEADER=${SDKCONFIG_HEADER} + -DSDKCONFIG_CMAKE=${SDKCONFIG_CMAKE} + -DSDKCONFIG_DEFAULTS= + -DGENERATE_SDKCONFIG=0 + -DDEPENDENCIES_LOCK=${ulp_binary_dir}/dependencies.lock + # All ULP child builds use ULP-specific toolchain files today. + # LP core and ULP RISC-V are GCC-based and could eventually + # share more of the normal IDF app toolchain response-file + # setup, but keep them under IDF_CUSTOM_TOOLCHAIN for now. + -DIDF_CUSTOM_TOOLCHAIN=1) + if(s_sources) + list(APPEND ulp_project_args + -DULP_S_SOURCES=$) + endif() + if(DEFINED COMPONENT_TARGET AND TARGET ${COMPONENT_TARGET}) + # Backward compatibility for existing ULP subprojects that + # include headers from the parent component. New full + # subprojects should declare their own component include + # directories and dependencies instead. + list(APPEND ulp_project_args + -DCOMPONENT_INCLUDES=$) + endif() + if(COMPONENT_DIR) + list(APPEND ulp_project_args + -DCOMPONENT_DIR=${COMPONENT_DIR}) + endif() + else() + list(APPEND ulp_project_args + -DULP_S_SOURCES=$ + -DULP_APP_NAME=${app_name} + -DCOMPONENT_DIR=${COMPONENT_DIR} + -DCOMPONENT_INCLUDES=$ + -DSDKCONFIG_HEADER=${SDKCONFIG_HEADER} + -DSDKCONFIG_CMAKE=${SDKCONFIG_CMAKE} + # The v1 ULP child resolves include(IDFULPProject) via the module path. + -DCMAKE_MODULE_PATH=${ulp_cmake_dir}) endif() externalproject_add(${app_name} - SOURCE_DIR ${project_path} - BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${app_name} - INSTALL_COMMAND "" - CMAKE_ARGS -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - -DCMAKE_GENERATOR=${CMAKE_GENERATOR} - -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FLAG} - -DULP_S_SOURCES=$ - -DULP_APP_NAME=${app_name} - -DADD_PICOLIBC_SPECS=${CONFIG_LIBC_PICOLIBC} - -DULP_VAR_PREFIX=${prefix} - -DULP_TYPE=${type} - -DCOMPONENT_DIR=${COMPONENT_DIR} - -DCOMPONENT_INCLUDES=$ - -DIDF_TARGET=${idf_target} - -DIDF_PATH=${idf_path} - -DSDKCONFIG_HEADER=${SDKCONFIG_HEADER} - -DSDKCONFIG_CMAKE=${SDKCONFIG_CMAKE} - -DPYTHON=${python} - -DCMAKE_MODULE_PATH=${idf_path}/components/ulp/cmake/ - ${extra_cmake_args} - BUILD_COMMAND ${CMAKE_COMMAND} --build ${CMAKE_CURRENT_BINARY_DIR}/${app_name} --target build - BUILD_BYPRODUCTS ${ulp_artifacts} ${ulp_artifacts_extras} ${ulp_ps_sources} - ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/${app_name} - BUILD_ALWAYS 1 - ) + SOURCE_DIR ${project_path} + BINARY_DIR ${ulp_binary_dir} + INSTALL_COMMAND "" + CMAKE_ARGS -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FLAG} + -DADD_PICOLIBC_SPECS=${CONFIG_LIBC_PICOLIBC} + -DULP_VAR_PREFIX=${prefix} + -DULP_TYPE=${type} + ${ulp_project_args} + -DIDF_TARGET=${idf_target} + -DIDF_PATH=${idf_path} + -DPYTHON=${python} + ${extra_cmake_args} + BUILD_COMMAND ${CMAKE_COMMAND} --build ${ulp_binary_dir} --target build + BUILD_BYPRODUCTS ${ulp_artifacts} ${ulp_artifacts_extras} ${ulp_ps_sources} + ${ulp_binary_dir}/${app_name} + BUILD_ALWAYS 1 + ) + # Only the CMake v1 path reads this property when passing ULP_S_SOURCES + # to the child project. Leaving it set for v2 is harmless and keeps the + # post-ExternalProject artifact wiring common. Can be dropped when + # v1 support is dropped. set_property(TARGET ${app_name} PROPERTY ULP_SOURCES "${sources}") spaces2list(exp_dep_srcs) - set_source_files_properties(${exp_dep_srcs} PROPERTIES OBJECT_DEPENDS ${ulp_artifacts}) + if(exp_dep_srcs) + set_source_files_properties(${exp_dep_srcs} PROPERTIES OBJECT_DEPENDS ${ulp_artifacts}) + endif() - include_directories(${CMAKE_CURRENT_BINARY_DIR}/${app_name}) + include_directories(${ulp_binary_dir}) add_custom_target(${app_name}_artifacts DEPENDS ${app_name}) add_dependencies(${COMPONENT_LIB} ${app_name}_artifacts) - target_linker_script(${COMPONENT_LIB} INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/${app_name}.ld) - target_add_binary_data(${COMPONENT_LIB} ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/${app_name}.bin BINARY) + foreach(binary_name IN LISTS binary_names) + target_linker_script(${COMPONENT_LIB} INTERFACE ${ulp_binary_dir}/${binary_name}.ld) + target_add_binary_data(${COMPONENT_LIB} ${ulp_binary_dir}/${binary_name}.bin BINARY) + endforeach() endif() endfunction() @@ -139,6 +241,23 @@ function(validate_ulp_type ULP_TYPE) endif() endfunction() +function(__ulp_resolve_type out_var type) + # CMake v2 ULP subprojects receive an explicit ULP_TYPE argument because the + # child project uses it to choose the component graph. CMake v1 legacy builds + # keep the historical empty/uppercase TYPE handling in __setup_ulp_project. + if(type) + string(TOLOWER "${type}" resolved_type) + elseif(CONFIG_ULP_COPROC_TYPE_RISCV) + set(resolved_type "riscv") + elseif(CONFIG_ULP_COPROC_TYPE_LP_CORE) + set(resolved_type "lp_core") + elseif(CONFIG_ULP_COPROC_TYPE_FSM) + set(resolved_type "fsm") + endif() + + set(${out_var} "${resolved_type}" PARENT_SCOPE) +endfunction() + function(ulp_embed_binary app_name s_sources exp_dep_srcs) cmake_parse_arguments(ULP "" "PREFIX;TYPE" "" ${ARGN}) if(NOT ULP_PREFIX) @@ -146,18 +265,38 @@ function(ulp_embed_binary app_name s_sources exp_dep_srcs) endif() validate_ulp_type("${ULP_TYPE}") + if(IDF_BUILD_V2) + __ulp_resolve_type(ULP_TYPE "${ULP_TYPE}") + endif() + set(ulp_cmake_dir "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/cmake") - __setup_ulp_project("${app_name}" "${idf_path}/components/ulp/cmake" - "${ULP_PREFIX}" "${ULP_TYPE}" "${s_sources}" "${exp_dep_srcs}") + __setup_ulp_project("${app_name}" "${ulp_cmake_dir}" + "${ULP_PREFIX}" FALSE "${ULP_TYPE}" + "${app_name}" + "${s_sources}" "${exp_dep_srcs}") endfunction() function(ulp_add_project app_name project_path) - cmake_parse_arguments(ULP "" "PREFIX;TYPE" "" ${ARGN}) + cmake_parse_arguments(ULP "" "PREFIX;TYPE" "BINARIES" ${ARGN}) + if(NOT ULP_BINARIES) + set(ULP_BINARIES "${app_name}") + endif() + + list(LENGTH ULP_BINARIES ULP_BINARY_COUNT) + set(ULP_PREFIX_APPEND_BIN_NAME FALSE) + if(IDF_BUILD_V2 AND ULP_BINARY_COUNT GREATER 1) + set(ULP_PREFIX_APPEND_BIN_NAME TRUE) + endif() + if(NOT ULP_PREFIX) set(ULP_PREFIX "ulp_") endif() validate_ulp_type("${ULP_TYPE}") + if(IDF_BUILD_V2) + __ulp_resolve_type(ULP_TYPE "${ULP_TYPE}") + endif() - __setup_ulp_project("${app_name}" "${project_path}" "${ULP_PREFIX}" "${ULP_TYPE}" "" "") + __setup_ulp_project("${app_name}" "${project_path}" "${ULP_PREFIX}" + "${ULP_PREFIX_APPEND_BIN_NAME}" "${ULP_TYPE}" "${ULP_BINARIES}" "" "") endfunction() diff --git a/components/ulp/test_apps/.build-test-rules.yml b/components/ulp/test_apps/.build-test-rules.yml index 8d8d7163435..dc57d4a67ac 100644 --- a/components/ulp/test_apps/.build-test-rules.yml +++ b/components/ulp/test_apps/.build-test-rules.yml @@ -1,6 +1,12 @@ # Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps +.ulp_legacy_buildv2_disable: &ulp_legacy_buildv2_disable + if: IDF_BUILD_V2 == "1" + reason: Legacy ULP apps are covered by CMake v1; buildv2 covers full_subproject ULP apps. + components/ulp/test_apps/lp_core/lp_core_basic_tests: + disable+: + - *ulp_legacy_buildv2_disable disable: - if: SOC_LP_CORE_SUPPORTED != 1 - if: CONFIG_NAME == "xtal" and SOC_CLK_LP_FAST_SUPPORT_XTAL != 1 @@ -20,6 +26,8 @@ components/ulp/test_apps/lp_core/lp_core_basic_tests: components/ulp/test_apps/lp_core/lp_core_gpio_wakeup_tests: + disable: + - *ulp_legacy_buildv2_disable enable: - if: (SOC_LP_CORE_SUPPORTED == 1) and (SOC_RTCIO_PIN_COUNT > 0) and (SOC_DEEP_SLEEP_SUPPORTED == 1) and (SOC_RTC_GPIO_EDGE_WAKEUP_SUPPORTED == 1) depends_components: @@ -31,6 +39,8 @@ components/ulp/test_apps/lp_core/lp_core_gpio_wakeup_tests: components/ulp/test_apps/lp_core/lp_core_hp_mem: + disable+: + - *ulp_legacy_buildv2_disable disable: - if: SOC_LP_CORE_SUPPORTED != 1 depends_components: @@ -41,6 +51,8 @@ components/ulp/test_apps/lp_core/lp_core_hp_mem: components/ulp/test_apps/lp_core/lp_core_hp_uart: + disable+: + - *ulp_legacy_buildv2_disable disable: - if: SOC_LP_CORE_SUPPORTED != 1 depends_components: @@ -51,6 +63,8 @@ components/ulp/test_apps/lp_core/lp_core_hp_uart: components/ulp/test_apps/ulp_fsm: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_ULP_FSM_SUPPORTED == 1 depends_components: @@ -61,6 +75,8 @@ components/ulp/test_apps/ulp_fsm: components/ulp/test_apps/ulp_riscv: + disable+: + - *ulp_legacy_buildv2_disable disable: - if: SOC_RISCV_COPROC_SUPPORTED != 1 depends_components: diff --git a/components/xtensa/project_include.cmake b/components/xtensa/project_include.cmake index 3ed44c2f59a..acdb7d66f29 100644 --- a/components/xtensa/project_include.cmake +++ b/components/xtensa/project_include.cmake @@ -21,6 +21,12 @@ endif() message(STATUS "Compiler supported targets: ${dump_machine}") if(NOT (${CMAKE_SYSTEM_NAME} STREQUAL "Generic" AND ${dump_machine} MATCHES xtensa)) + # Sub-projects (e.g. ULP RISC-V on an xtensa target) may use a + # non-IDF toolchain provided by the parent build. Skip validation + # when a custom toolchain is explicitly declared. + if(IDF_CUSTOM_TOOLCHAIN) + return() + endif() message(FATAL_ERROR "Internal error, toolchain has not been set correctly by project " "(or an invalid CMakeCache.txt file has been generated somehow)") endif() diff --git a/examples/system/ulp/.build-test-rules.yml b/examples/system/ulp/.build-test-rules.yml index c74d218d22c..da7398c0585 100644 --- a/examples/system/ulp/.build-test-rules.yml +++ b/examples/system/ulp/.build-test-rules.yml @@ -6,12 +6,20 @@ - esp_hw_support - esp_hal_pmu +.ulp_legacy_buildv2_disable: &ulp_legacy_buildv2_disable + if: IDF_BUILD_V2 == "1" + reason: Legacy ULP apps are covered by CMake v1; buildv2 covers full_subproject ULP apps. + examples/system/ulp/lp_core/build_system: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_LP_CORE_SUPPORTED == 1 <<: *ulp_default_depends examples/system/ulp/lp_core/debugging: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_LP_CORE_SUPPORTED == 1 disable_test: @@ -21,6 +29,8 @@ examples/system/ulp/lp_core/debugging: <<: *ulp_default_depends examples/system/ulp/lp_core/gpio: + disable+: + - *ulp_legacy_buildv2_disable disable: - if: SOC_DEEP_SLEEP_SUPPORTED != 1 enable: @@ -30,6 +40,8 @@ examples/system/ulp/lp_core/gpio: - esp_hal_gpio examples/system/ulp/lp_core/gpio_intr_pulse_counter: + disable: + - *ulp_legacy_buildv2_disable enable: - if: (SOC_LP_CORE_SUPPORTED == 1) and (SOC_ULP_LP_UART_SUPPORTED == 1 and SOC_DEEP_SLEEP_SUPPORTED == 1) <<: *ulp_default_depends @@ -37,6 +49,8 @@ examples/system/ulp/lp_core/gpio_intr_pulse_counter: - esp_hal_gpio examples/system/ulp/lp_core/gpio_wakeup: + disable: + - *ulp_legacy_buildv2_disable enable: - if: (SOC_LP_CORE_SUPPORTED == 1) and (SOC_RTCIO_PIN_COUNT > 0) and (SOC_DEEP_SLEEP_SUPPORTED == 1) and (SOC_RTC_GPIO_EDGE_WAKEUP_SUPPORTED == 1) # TODO: [ESP32S31] IDF-15884 SOC_RTC_GPIO_EDGE_WAKEUP_SUPPORTED not declared yet <<: *ulp_default_depends @@ -45,16 +59,22 @@ examples/system/ulp/lp_core/gpio_wakeup: - esp_hal_gpio examples/system/ulp/lp_core/inter_cpu_critical_section/: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_LP_CORE_SUPPORTED == 1 <<: *ulp_default_depends examples/system/ulp/lp_core/interrupt: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_LP_CORE_SUPPORTED == 1 <<: *ulp_default_depends examples/system/ulp/lp_core/lp_adc: + disable+: + - *ulp_legacy_buildv2_disable disable: - if: (SOC_LP_ADC_SUPPORTED != 1) <<: *ulp_default_depends @@ -64,6 +84,8 @@ examples/system/ulp/lp_core/lp_adc: - esp_hal_uart examples/system/ulp/lp_core/lp_i2c: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_LP_CORE_SUPPORT_I2C == 1 and (SOC_DEEP_SLEEP_SUPPORTED == 1 and SOC_LP_CORE_SUPPORTED == 1) <<: *ulp_default_depends @@ -71,11 +93,15 @@ examples/system/ulp/lp_core/lp_i2c: - esp_hal_i2c examples/system/ulp/lp_core/lp_mailbox: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_LP_CORE_SUPPORTED == 1 <<: *ulp_default_depends examples/system/ulp/lp_core/lp_spi: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_LP_SPI_SUPPORTED == 1 and SOC_DEEP_SLEEP_SUPPORTED == 1 <<: *ulp_default_depends @@ -83,11 +109,15 @@ examples/system/ulp/lp_core/lp_spi: - esp_hal_gpspi examples/system/ulp/lp_core/lp_timer_interrupt: + disable+: + - *ulp_legacy_buildv2_disable disable: - if: (SOC_LP_CORE_SUPPORTED != 1) or (SOC_LP_TIMER_SUPPORTED != 1) <<: *ulp_default_depends examples/system/ulp/lp_core/lp_touch: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_TOUCH_SENSOR_SUPPORTED == 1 and (SOC_DEEP_SLEEP_SUPPORTED == 1 and SOC_LP_CORE_SUPPORTED == 1) <<: *ulp_default_depends @@ -96,6 +126,8 @@ examples/system/ulp/lp_core/lp_touch: - esp_hal_touch_sens examples/system/ulp/lp_core/lp_uart/lp_uart_char_seq_wakeup: + disable+: + - *ulp_legacy_buildv2_disable disable: - if: (SOC_ULP_LP_UART_SUPPORTED != 1) or (SOC_DEEP_SLEEP_SUPPORTED != 1) <<: *ulp_default_depends @@ -103,6 +135,8 @@ examples/system/ulp/lp_core/lp_uart/lp_uart_char_seq_wakeup: - esp_hal_uart examples/system/ulp/lp_core/lp_uart/lp_uart_echo: + disable+: + - *ulp_legacy_buildv2_disable disable: - if: (SOC_ULP_LP_UART_SUPPORTED != 1) or (SOC_DEEP_SLEEP_SUPPORTED != 1) <<: *ulp_default_depends @@ -110,6 +144,8 @@ examples/system/ulp/lp_core/lp_uart/lp_uart_echo: - esp_hal_uart examples/system/ulp/lp_core/lp_uart/lp_uart_print: + disable+: + - *ulp_legacy_buildv2_disable disable: - if: (SOC_ULP_LP_UART_SUPPORTED != 1) or (SOC_DEEP_SLEEP_SUPPORTED != 1) <<: *ulp_default_depends @@ -117,11 +153,15 @@ examples/system/ulp/lp_core/lp_uart/lp_uart_print: - esp_hal_uart examples/system/ulp/ulp_fsm/ulp: + disable+: + - *ulp_legacy_buildv2_disable disable: - if: SOC_ULP_FSM_SUPPORTED != 1 <<: *ulp_default_depends examples/system/ulp/ulp_fsm/ulp_adc: + disable: + - *ulp_legacy_buildv2_disable enable: - if: IDF_TARGET in ["esp32", "esp32s3"] temporary: true @@ -132,6 +172,8 @@ examples/system/ulp/ulp_fsm/ulp_adc: - esp_hal_ana_conv examples/system/ulp/ulp_riscv/adc: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_RISCV_COPROC_SUPPORTED == 1 <<: *ulp_default_depends @@ -140,6 +182,8 @@ examples/system/ulp/ulp_riscv/adc: - esp_hal_ana_conv examples/system/ulp/ulp_riscv/ds18b20_onewire: + disable: + - *ulp_legacy_buildv2_disable enable: - if: IDF_TARGET == "esp32s2" temporary: true @@ -149,6 +193,8 @@ examples/system/ulp/ulp_riscv/ds18b20_onewire: - esp_hal_gpio examples/system/ulp/ulp_riscv/gpio: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_RISCV_COPROC_SUPPORTED == 1 <<: *ulp_default_depends @@ -156,6 +202,8 @@ examples/system/ulp/ulp_riscv/gpio: - esp_hal_gpio examples/system/ulp/ulp_riscv/gpio_interrupt: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_RISCV_COPROC_SUPPORTED == 1 <<: *ulp_default_depends @@ -164,6 +212,8 @@ examples/system/ulp/ulp_riscv/gpio_interrupt: - esp_hal_gpio examples/system/ulp/ulp_riscv/gpio_pulse_counter: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_RISCV_COPROC_SUPPORTED == 1 <<: *ulp_default_depends @@ -171,6 +221,8 @@ examples/system/ulp/ulp_riscv/gpio_pulse_counter: - esp_hal_gpio examples/system/ulp/ulp_riscv/i2c: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_RISCV_COPROC_SUPPORTED == 1 <<: *ulp_default_depends @@ -178,11 +230,15 @@ examples/system/ulp/ulp_riscv/i2c: - esp_hal_i2c examples/system/ulp/ulp_riscv/interrupts: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_RISCV_COPROC_SUPPORTED == 1 <<: *ulp_default_depends examples/system/ulp/ulp_riscv/touch: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_RISCV_COPROC_SUPPORTED == 1 <<: *ulp_default_depends @@ -191,6 +247,8 @@ examples/system/ulp/ulp_riscv/touch: - esp_hal_touch_sens examples/system/ulp/ulp_riscv/uart_print: + disable: + - *ulp_legacy_buildv2_disable enable: - if: SOC_RISCV_COPROC_SUPPORTED == 1 <<: *ulp_default_depends diff --git a/examples/system/ulp/ulp_fsm_riscv_combined/counter/.build-test-rules.yml b/examples/system/ulp/ulp_fsm_riscv_combined/counter/.build-test-rules.yml index 4eb9be1fb97..da40905d51c 100644 --- a/examples/system/ulp/ulp_fsm_riscv_combined/counter/.build-test-rules.yml +++ b/examples/system/ulp/ulp_fsm_riscv_combined/counter/.build-test-rules.yml @@ -1,4 +1,7 @@ examples/system/ulp/ulp_fsm_riscv_combined/counter: + disable: + - if: IDF_BUILD_V2 == "1" + reason: Legacy ULP apps are covered by CMake v1; buildv2 covers full_subproject ULP apps. enable: - if: SOC_ULP_FSM_SUPPORTED == 1 and SOC_RISCV_COPROC_SUPPORTED == 1 and IDF_TARGET in ["esp32s2", "esp32s3"] temporary: false diff --git a/tools/ci/check_examples_documented.py b/tools/ci/check_examples_documented.py index 9d2b2efb790..f80527439e1 100644 --- a/tools/ci/check_examples_documented.py +++ b/tools/ci/check_examples_documented.py @@ -125,6 +125,10 @@ KNOWN_MISSING = { 'system/ulp/lp_core/lp_spi', 'system/ulp/lp_core/lp_timer_interrupt', 'system/ulp/lp_core/lp_touch', + 'system/ulp/ulp_fsm/ulp/main/ulp', + 'system/ulp/ulp_fsm_riscv_combined/counter/main/ulp_fsm', + 'system/ulp/ulp_fsm_riscv_combined/counter/main/ulp_riscv', + 'system/ulp/ulp_riscv/gpio/main/ulp', 'system/ulp/ulp_riscv/gpio_pulse_counter', 'system/unit_test/test', # TODO IDF-15382: add :example: references for peripherals examples diff --git a/tools/ci/dynamic_pipelines/scripts/patch_buildv2_child_pipeline.py b/tools/ci/dynamic_pipelines/scripts/patch_buildv2_child_pipeline.py index 901742242db..7536a3fdbf9 100644 --- a/tools/ci/dynamic_pipelines/scripts/patch_buildv2_child_pipeline.py +++ b/tools/ci/dynamic_pipelines/scripts/patch_buildv2_child_pipeline.py @@ -6,8 +6,8 @@ The buildv2 build child pipeline is generated by ``idf-ci gitlab build-child-pip from the same manifest as the default pipeline. This script post-processes that generated YAML so the cmakev2 path is exercised end to end: -1. Inject ``IDF_BUILD_V2`` into each child build job so cmake activates the - cmakev2 shim. +1. Point child build jobs at the buildv2 generator artifacts, then inject + ``IDF_BUILD_V2`` so cmake activates the cmakev2 shim. 2. Inject ``PIPELINE_COMMIT_SHA: ${PIPELINE_COMMIT_SHA}_buildv2`` into each child build job. idf-ci reads ``PIPELINE_COMMIT_SHA`` when computing the s3 @@ -27,6 +27,13 @@ import argparse import yaml PIPELINE_COMMIT_SHA_V2 = '${PIPELINE_COMMIT_SHA}_buildv2' +BUILDV2_GENERATOR_JOB = 'generate_build_child_pipeline_buildv2' + + +def _use_buildv2_generator_artifacts(job: dict) -> None: + for need in job.get('needs', []): + if isinstance(need, dict) and need.get('job') == 'generate_build_child_pipeline': + need['job'] = BUILDV2_GENERATOR_JOB def patch(path: str) -> None: @@ -36,6 +43,7 @@ def patch(path: str) -> None: injected = [] for k, v in d.items(): if isinstance(v, dict) and 'extends' in v: + _use_buildv2_generator_artifacts(v) v.setdefault('variables', {})['IDF_BUILD_V2'] = '1' v['variables']['PIPELINE_COMMIT_SHA'] = PIPELINE_COMMIT_SHA_V2 injected.append(k) diff --git a/tools/cmake/linker_script_preprocessor.cmake b/tools/cmake/linker_script_preprocessor.cmake index df06494a702..c70cccd7b2b 100644 --- a/tools/cmake/linker_script_preprocessor.cmake +++ b/tools/cmake/linker_script_preprocessor.cmake @@ -4,7 +4,11 @@ # Note: Paths are expected to be quoted in the CFLAGS variable separate_arguments(CFLAGS_LIST UNIX_COMMAND "${CFLAGS}") -execute_process(COMMAND "${CC}" "-C" "-P" "-x" "c" "-E" ${CFLAGS_LIST} "${SOURCE}" +# Comment handling is left to the caller via CFLAGS. The default linker-script +# CFLAGS keep comments (-C) to preserve historical output. Callers whose +# templates include headers with C++-style "//" comments, which ld rejects, +# e.g. the ULP memory layout including soc/soc.h, omit -C in their flags. +execute_process(COMMAND "${CC}" "-P" "-x" "c" "-E" ${CFLAGS_LIST} "${SOURCE}" RESULT_VARIABLE RET_CODE OUTPUT_VARIABLE PREPROCESSED_LINKER_SCRIPT ERROR_VARIABLE ERROR_VAR) diff --git a/tools/cmake/utilities.cmake b/tools/cmake/utilities.cmake index 4580c30e2a9..ddbbbe149a3 100644 --- a/tools/cmake/utilities.cmake +++ b/tools/cmake/utilities.cmake @@ -214,13 +214,16 @@ function(preprocess_linker_file cmake_target script_in output_var preserve_suffi set(extra_cflags "-I\"${dir_to_include}\"") endif() + # Keep comments (-C): historical behavior for cmakev1 linker scripts. It was + # previously hardcoded in linker_script_preprocessor.cmake, which now leaves + # comment handling to the caller. add_custom_command( OUTPUT ${script_out} COMMAND ${CMAKE_COMMAND} "-DCC=${CMAKE_C_COMPILER}" "-DSOURCE=${script_in}" "-DTARGET=${script_out}" - "-DCFLAGS=-I\"${config_dir}\" ${extra_cflags}" + "-DCFLAGS=-C -I\"${config_dir}\" ${extra_cflags}" -P "${linker_script_generator}" MAIN_DEPENDENCY ${script_in} DEPENDS ${sdkconfig_header} diff --git a/tools/cmakev2/build.cmake b/tools/cmakev2/build.cmake index 9dbe3a04576..f434adfd018 100644 --- a/tools/cmakev2/build.cmake +++ b/tools/cmakev2/build.cmake @@ -473,8 +473,33 @@ function(idf_build_library library) string(MAKE_C_IDENTIFIER "_${library}" suffix) idf_library_get_property(component_interfaces_linked "${library}" LIBRARY_COMPONENT_INTERFACES_LINKED) + + # Build -I arguments for the public include dirs of every component in the + # linked closure. These are appended to every linker-script preprocess so a + # template can #include any component header (e.g. the ULP memory layout + # including soc/soc.h) without the component having to know about it. + # INCLUDE_DIRS is stored relative to each component's COMPONENT_DIR. + set(component_include_flags "") + foreach(ci IN LISTS component_interfaces_linked) + idf_component_get_property(ci_dir "${ci}" COMPONENT_DIR) + idf_component_get_property(ci_includes "${ci}" INCLUDE_DIRS) + foreach(ci_include IN LISTS ci_includes) + get_filename_component(ci_include_abs "${ci_include}" ABSOLUTE BASE_DIR "${ci_dir}") + # Quote the path: the preprocessor invocation splits CFLAGS with + # separate_arguments(UNIX_COMMAND), so unquoted paths containing + # spaces would fall apart. + string(APPEND component_include_flags " -I\"${ci_include_abs}\"") + endforeach() + endforeach() + + # Linker scripts marked MEMORY (the memory-layout base for the link) are + # emitted as -T before the rest, so section-placement scripts from any + # component can reference their MEMORY regions and REGION_ALIASes. Collect + # across all components into two ordered lists and emit memory ones first. + set(memory_scripts "") + set(other_scripts "") + foreach(component_interface IN LISTS component_interfaces_linked) - set(scripts) idf_component_get_property(component_dir "${component_interface}" COMPONENT_DIR) idf_component_get_property(component_build_dir "${component_interface}" COMPONENT_BUILD_DIR) idf_component_get_property(preprocessed "${component_interface}" __LINKER_SCRIPTS_PREPROCESSED) @@ -483,6 +508,9 @@ function(idf_build_library library) idf_component_get_property(scripts_static "${component_interface}" LINKER_SCRIPTS) __get_absolute_paths(PATHS "${scripts_static}" BASE_DIR "${component_dir}" OUTPUT scripts_static_abs) foreach(script IN LISTS scripts_static_abs) + __linker_script_key("${script}" script_key) + idf_component_get_property(script_is_memory "${component_interface}" + "LINKER_SCRIPT_MEMORY_${script_key}") if(script MATCHES "\\.in$") # If the linker script file name ends with the ".in" extension, # preprocess it with the C preprocessor. @@ -492,27 +520,37 @@ function(idf_build_library library) if(NOT preprocessed) file(MAKE_DIRECTORY "${component_build_dir}/ld") - __preprocess_linker_script("${script}" "${script_out}") + idf_component_get_property(script_flags "${component_interface}" + "LINKER_SCRIPT_FLAGS_${script_key}") + __preprocess_linker_script("${script}" "${script_out}" + "${script_flags}" "${component_include_flags}") # Add a custom target for the preprocessed script. add_custom_target(${script_target} DEPENDS "${script_out}") endif() # Add dependency for the library interface to ensure the script is # generated before linking. add_dependencies("${library}" ${script_target}) - list(APPEND scripts "${script_out}") + set(emit_script "${script_out}") else() - list(APPEND scripts "${script}") + set(emit_script "${script}") + endif() + if(script_is_memory) + list(APPEND memory_scripts "${emit_script}") + else() + list(APPEND other_scripts "${emit_script}") endif() endforeach() - # Generate linker scripts from templates. - # LINKER_SCRIPTS_TEMPLATE and LINKER_SCRIPTS_GENERATED are parallel - # lists. The first holds the template linker script path, and the - # second holds the generated linker script path. + # Generate linker scripts from templates. The generated output path and + # the optional preprocessor flags for each template are stored as + # component properties keyed by the template, so no second list has to + # stay index-aligned with LINKER_SCRIPTS_TEMPLATE. idf_component_get_property(template_scripts "${component_interface}" LINKER_SCRIPTS_TEMPLATE) - idf_component_get_property(generated_scripts "${component_interface}" LINKER_SCRIPTS_GENERATED) - foreach(template script IN ZIP_LISTS template_scripts generated_scripts) + foreach(template IN LISTS template_scripts) + __linker_script_key("${template}" template_key) + idf_component_get_property(script "${component_interface}" + "LINKER_SCRIPT_GENERATED_${template_key}") if(template MATCHES "\\.in$") # If the linker script file name ends with the ".in" extension, # preprocess it with the C preprocessor. @@ -520,7 +558,10 @@ function(idf_build_library library) set(template_out "${component_build_dir}/ld/${template_name}") if(NOT preprocessed) file(MAKE_DIRECTORY "${component_build_dir}/ld") - __preprocess_linker_script("${template}" "${template_out}") + idf_component_get_property(template_flags "${component_interface}" + "LINKER_SCRIPT_FLAGS_${template_key}") + __preprocess_linker_script("${template}" "${template_out}" + "${template_flags}" "${component_include_flags}") endif() set(template "${template_out}") endif() @@ -529,7 +570,13 @@ function(idf_build_library library) TEMPLATE "${template}" SUFFIX "${suffix}" OUTPUT "${script}") - list(APPEND scripts "${script}") + idf_component_get_property(template_is_memory "${component_interface}" + "LINKER_SCRIPT_MEMORY_${template_key}") + if(template_is_memory) + list(APPEND memory_scripts "${script}") + else() + list(APPEND other_scripts "${script}") + endif() # Add a custom target for the generated script and include it as a # dependency for the library interface to ensure the script is # generated before linking. @@ -545,21 +592,29 @@ function(idf_build_library library) # property to indicate that the linker scripts have already been # preprocessed. idf_component_set_property("${component_interface}" __LINKER_SCRIPTS_PREPROCESSED YES) + endforeach() - # Finally, add all preprocessed and ldgen-generated linker scripts to - # the library interface. - foreach(script IN LISTS scripts) - get_filename_component(script_dir "${script}" DIRECTORY) - get_filename_component(script_name "${script}" NAME) - # Add linker script directory to the linker search path. - target_link_directories("${library}" INTERFACE "${script_dir}") - # Add linker script to link. Regarding the usage of SHELL, see - # https://cmake.org/cmake/help/latest/command/target_link_options.html#option-de-duplication - target_link_options("${library}" INTERFACE "SHELL:-T ${script_name}") - # Add the linker script as a dependency to ensure the executable is - # re-linked if the script changes. - set_property(TARGET "${library}" APPEND PROPERTY INTERFACE_LINK_DEPENDS "${script}") - endforeach() + # Emit the collected linker scripts as -T on the library interface, with + # memory-layout scripts first so their MEMORY regions and REGION_ALIASes are + # seen before any section-placement script references them. + foreach(script IN LISTS memory_scripts other_scripts) + get_filename_component(script_dir "${script}" DIRECTORY) + # Add the linker script directory to the linker search path so INCLUDE + # directives inside a script can resolve sibling scripts by name. + target_link_directories("${library}" INTERFACE "${script_dir}") + # Add the linker script to the link by absolute path. Passing the full + # path (rather than a bare name resolved via -L) keeps this working for + # direct linker drivers such as the ULP FSM esp32ulp-elf-ld link, where + # the -L search directories follow the -T options on the command line + # and GNU ld therefore does not use them to locate the -T script. + # Regarding the usage of SHELL, see + # https://cmake.org/cmake/help/latest/command/target_link_options.html#option-de-duplication + # Quote the path: SHELL: strings are re-split on spaces, so an unquoted + # path containing spaces would fall apart. + target_link_options("${library}" INTERFACE "SHELL:-T \"${script}\"") + # Add the linker script as a dependency to ensure the executable is + # re-linked if the script changes. + set_property(TARGET "${library}" APPEND PROPERTY INTERFACE_LINK_DEPENDS "${script}") endforeach() # Validate components linked to this library @@ -625,15 +680,33 @@ function(idf_build_executable executable) set(ARG_SUFFIX ".elf") endif() + get_property(enabled_languages GLOBAL PROPERTY ENABLED_LANGUAGES) + if(C IN_LIST enabled_languages) + set(stub_language C) + set(stub_extension c) + elseif(CXX IN_LIST enabled_languages) + set(stub_language CXX) + set(stub_extension c) + elseif(ASM IN_LIST enabled_languages) + set(stub_language ASM) + set(stub_extension S) + else() + message(FATAL_ERROR "idf_build_executable() requires C, CXX, or ASM to be enabled.") + endif() + idf_build_get_property(build_dir BUILD_DIR) set(library "library_${executable}") idf_build_library(${library} COMPONENTS "${ARG_COMPONENTS}") - set(executable_src ${CMAKE_BINARY_DIR}/executable_${executable}.c) + set(executable_src ${CMAKE_BINARY_DIR}/executable_${executable}.${stub_extension}) if(NOT EXISTS "${executable_src}") file(TOUCH "${executable_src}") endif() + if(stub_language STREQUAL "CXX") + set_source_files_properties("${executable_src}" PROPERTIES LANGUAGE CXX) + endif() + add_executable(${executable} "${executable_src}") set_target_properties(${executable} PROPERTIES OUTPUT_NAME ${ARG_NAME}) @@ -642,6 +715,10 @@ function(idf_build_executable executable) set_target_properties(${executable} PROPERTIES SUFFIX ${ARG_SUFFIX}) endif() + if(NOT stub_language STREQUAL "C") + set_target_properties(${executable} PROPERTIES LINKER_LANGUAGE ${stub_language}) + endif() + target_link_libraries(${executable} PRIVATE ${library}) idf_build_get_property(linker_type LINKER_TYPE) diff --git a/tools/cmakev2/compat.cmake b/tools/cmakev2/compat.cmake index c6100ab9dfb..e1e19158356 100644 --- a/tools/cmakev2/compat.cmake +++ b/tools/cmakev2/compat.cmake @@ -165,6 +165,25 @@ endfunction() ``linkerscript`` template. The ``linkerscript`` is processed with ldgen to produce the ``output``. + *FLAGS[in,opt]* + + Explicit preprocessor flags for the linker script(s) registered by this + call, for example ``-D`` defines or extra ``-I`` include directories. + When given, they replace the default parent-dir==target include + heuristic while a ``.in`` script or template is preprocessed. + ``-I`` and the linked components' include directories are + always added regardless. Applies to every ``scriptfile`` in the same + call. + + *MEMORY[opt]* + + Marks the script(s) as the memory-layout base for the link. Such + scripts are emitted as ``-T`` before all other linker scripts, so that + section-placement scripts from other components can reference their + ``MEMORY`` regions and ``REGION_ALIAS`` names. Needed when the memory + layout lives in a different component than the section scripts that + depend on it. + This function adds one or more linker scripts to the specified component target, incorporating the linker script into the linking process. @@ -178,23 +197,76 @@ function(target_linker_script target deptype scriptfiles) # The linker script files, templates, and their output filenames are stored # only as component properties. The script files are generated and added to # the library link interface in the idf_build_library function. - set(options) - set(one_value PROCESS) + set(options MEMORY) + set(one_value PROCESS FLAGS) set(multi_value) cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN}) foreach(scriptfile ${scriptfiles}) get_filename_component(scriptfile "${scriptfile}" ABSOLUTE) idf_msg("Adding linker script ${scriptfile}") + __linker_script_key("${scriptfile}" script_key) if(ARG_PROCESS) get_filename_component(output "${ARG_PROCESS}" ABSOLUTE) idf_component_set_property("${target}" LINKER_SCRIPTS_TEMPLATE "${scriptfile}" APPEND) - idf_component_set_property("${target}" LINKER_SCRIPTS_GENERATED "${output}" APPEND) + # Key the generated output path by the template instead of keeping a + # second list index-aligned with LINKER_SCRIPTS_TEMPLATE. + idf_component_set_property("${target}" "LINKER_SCRIPT_GENERATED_${script_key}" "${output}") else() - idf_component_set_property("${target}" LINKER_SCRIPTS ${scriptfile} APPEND) + idf_component_set_property("${target}" LINKER_SCRIPTS "${scriptfile}" APPEND) + endif() + # Memory-layout scripts are emitted before other scripts in the link, + # so section-placement scripts can reference their MEMORY regions. + if(ARG_MEMORY) + idf_component_set_property("${target}" "LINKER_SCRIPT_MEMORY_${script_key}" TRUE) + endif() + # Per-script preprocessor flags, consumed by __preprocess_linker_script + # when a ".in" script or template is built. The "__DEFAULT__" sentinel + # means FLAGS was not given (use the parent-dir==target heuristic); any + # other value, including empty, means FLAGS was given and replaces it. + # This distinguishes "not given" from "given empty", which the property + # value alone cannot (both would read back as empty). + if(DEFINED ARG_FLAGS) + idf_component_set_property("${target}" "LINKER_SCRIPT_FLAGS_${script_key}" "${ARG_FLAGS}") + else() + idf_component_set_property("${target}" "LINKER_SCRIPT_FLAGS_${script_key}" "__DEFAULT__") endif() endforeach() endfunction() +function(__idf_component_link_optional_requirement component target link_type req_interface output) + # Config-only cmakev1 components are represented by INTERFACE libraries. + # CMake cannot apply PRIVATE links to an INTERFACE library, so optional + # private requirements are ignored for those components. + idf_component_get_property(component_target_type "${component}" COMPONENT_REAL_TARGET_TYPE) + if("${component_target_type}" STREQUAL "INTERFACE_LIBRARY") + if("${link_type}" STREQUAL "PRIVATE") + set(${output} FALSE PARENT_SCOPE) + return() + endif() + target_link_libraries("${target}" INTERFACE "${req_interface}") + else() + target_link_libraries("${target}" "${link_type}" "${req_interface}") + endif() + + if("${link_type}" STREQUAL "PRIVATE") + set(req_type PRIV_REQUIRES) + elseif("${link_type}" STREQUAL "PUBLIC" OR "${link_type}" STREQUAL "INTERFACE") + set(req_type REQUIRES) + else() + set(req_type "") + endif() + + if(req_type) + idf_component_get_property(req_name "${req_interface}" COMPONENT_NAME) + idf_component_get_property(existing "${component}" ${req_type}) + if(NOT "${req_name}" IN_LIST existing) + idf_component_set_property("${component}" ${req_type} "${req_name}" APPEND) + endif() + endif() + + set(${output} TRUE PARENT_SCOPE) +endfunction() + #[[ __idf_component_process_optional_requires() @@ -269,22 +341,10 @@ function(__idf_component_process_optional_requires) endif() # Link the caller's real target to the requirement's interface target. - target_link_libraries("${caller_target}" "${link_type}" "${req_interface}") - - # Update the caller's REQUIRES or PRIV_REQUIRES property. - idf_component_get_property(req_name "${req_interface}" COMPONENT_NAME) - if("${link_type}" STREQUAL "PRIVATE") - idf_component_get_property(existing "${caller_interface}" PRIV_REQUIRES) - if(NOT "${req_name}" IN_LIST existing) - idf_component_set_property("${caller_interface}" PRIV_REQUIRES - "${req_name}" APPEND) - endif() - elseif("${link_type}" STREQUAL "PUBLIC" OR "${link_type}" STREQUAL "INTERFACE") - idf_component_get_property(existing "${caller_interface}" REQUIRES) - if(NOT "${req_name}" IN_LIST existing) - idf_component_set_property("${caller_interface}" REQUIRES - "${req_name}" APPEND) - endif() + __idf_component_link_optional_requirement("${caller_interface}" "${caller_target}" + "${link_type}" "${req_interface}" linked) + if(NOT linked) + continue() endif() # Mark this pair as done so it is not processed again for subsequent @@ -372,24 +432,8 @@ function(idf_component_optional_requires type) endif() idf_component_include("${req}") - if("${type}" STREQUAL "PRIVATE") - set(req_type PRIV_REQUIRES) - elseif("${type}" STREQUAL "PUBLIC") - set(req_type REQUIRES) - else() - set(req_type "") - endif() - - if(req_type) - idf_component_get_property(req_name "${req_interface}" COMPONENT_NAME) - idf_component_get_property(target_reqs "${COMPONENT_NAME}" ${req_type}) - if(NOT "${req_name}" IN_LIST target_reqs) - idf_component_set_property("${COMPONENT_NAME}" ${req_type} "${req_name}" APPEND) - target_link_libraries(${COMPONENT_TARGET} ${type} ${req_interface}) - endif() - else() - target_link_libraries(${COMPONENT_TARGET} ${type} ${req_interface}) - endif() + __idf_component_link_optional_requirement("${COMPONENT_NAME}" "${COMPONENT_TARGET}" + "${type}" "${req_interface}" linked) endforeach() endif() endfunction() @@ -700,7 +744,10 @@ function(idf_component_register) target_include_directories("${COMPONENT_TARGET}" PRIVATE "${include_dir}") endforeach() - set_target_properties(${COMPONENT_TARGET} PROPERTIES OUTPUT_NAME ${COMPONENT_NAME} LINKER_LANGUAGE C) + __idf_component_get_linker_language(component_linker_language) + set_target_properties(${COMPONENT_TARGET} PROPERTIES + OUTPUT_NAME ${COMPONENT_NAME} + LINKER_LANGUAGE ${component_linker_language}) set(component_type LIBRARY) @@ -819,6 +866,14 @@ function(idf_component_register) idf_component_set_property("${COMPONENT_NAME}" PRIV_REQUIRES "${__merged_priv_requires}") idf_component_set_property("${COMPONENT_NAME}" REQUIRED_IDF_TARGETS "${ARG_REQUIRED_IDF_TARGETS}") idf_component_set_property("${COMPONENT_NAME}" COMPONENT_TYPE "${component_type}") + idf_component_set_property("${COMPONENT_NAME}" COMPONENT_REAL_TARGET "${COMPONENT_TARGET}") + # Optional requirements need to know whether the backward-compatible + # component target is STATIC or INTERFACE before deciding how to link it. + if("${component_type}" STREQUAL "CONFIG_ONLY") + idf_component_set_property("${COMPONENT_NAME}" COMPONENT_REAL_TARGET_TYPE INTERFACE_LIBRARY) + else() + idf_component_set_property("${COMPONENT_NAME}" COMPONENT_REAL_TARGET_TYPE STATIC_LIBRARY) + endif() endfunction() #[[ diff --git a/tools/cmakev2/component.cmake b/tools/cmakev2/component.cmake index b8ec4c22577..9c4fae0bb6f 100644 --- a/tools/cmakev2/component.cmake +++ b/tools/cmakev2/component.cmake @@ -88,6 +88,20 @@ function(__idf_component_get_property_unchecked variable component_interface pro set(${variable} ${value} PARENT_SCOPE) endfunction() +function(__idf_component_get_linker_language variable) + get_property(enabled_languages GLOBAL PROPERTY ENABLED_LANGUAGES) + if(C IN_LIST enabled_languages) + set(language C) + elseif(CXX IN_LIST enabled_languages) + set(language CXX) + elseif(ASM IN_LIST enabled_languages) + set(language ASM) + else() + idf_die("No supported language is enabled for component targets") + endif() + set(${variable} ${language} PARENT_SCOPE) +endfunction() + #[[api .. cmakev2:function:: idf_component_get_property @@ -1197,7 +1211,10 @@ function(idf_component_include name) endif() # Set the component archive file name. - set_target_properties(${component_real_target} PROPERTIES OUTPUT_NAME ${component_name} LINKER_LANGUAGE C) + __idf_component_get_linker_language(component_linker_language) + set_target_properties(${component_real_target} PROPERTIES + OUTPUT_NAME ${component_name} + LINKER_LANGUAGE ${component_linker_language}) idf_build_get_property(include_directories INCLUDE_DIRECTORIES GENERATOR_EXPRESSION) target_include_directories("${component_real_target}" BEFORE PRIVATE "${include_directories}") diff --git a/tools/cmakev2/idf.cmake b/tools/cmakev2/idf.cmake index dfe29b70dee..f4fc09ec99b 100644 --- a/tools/cmakev2/idf.cmake +++ b/tools/cmakev2/idf.cmake @@ -5,6 +5,10 @@ include_guard(GLOBAL) cmake_minimum_required(VERSION 3.22) +if(NOT DEFINED IDF_DEFAULT_PROJECT_NAME) + get_filename_component(IDF_DEFAULT_PROJECT_NAME "${CMAKE_CURRENT_SOURCE_DIR}" NAME) +endif() + # Update the CMAKE_MODULE_PATH to ensure that additional cmakev2 build system # modules can be included. The third_party directory from cmakev1 is also # included for third-party modules shared with cmakev1. @@ -313,6 +317,16 @@ function(__init_toolchain) set(cache_toolchain $CACHE{IDF_TOOLCHAIN}) set(cache_toolchain_file $CACHE{CMAKE_TOOLCHAIN_FILE}) + # When IDF_CUSTOM_TOOLCHAIN is set, the toolchain was provided + # externally by the parent build (e.g. ULP sub-project via + # externalproject_add -DCMAKE_TOOLCHAIN_FILE=...). Skip + # IDF_TARGET-based resolution and validation, and trust the + # caller-provided CMAKE_TOOLCHAIN_FILE. + if(IDF_CUSTOM_TOOLCHAIN AND cache_toolchain_file) + idf_build_set_property(IDF_TOOLCHAIN_FILE "${cache_toolchain_file}") + return() + endif() + idf_build_get_property(idf_path IDF_PATH) idf_build_get_property(idf_target IDF_TARGET) diff --git a/tools/cmakev2/kconfig.cmake b/tools/cmakev2/kconfig.cmake index 624f28ae3b6..1beaddc4f30 100644 --- a/tools/cmakev2/kconfig.cmake +++ b/tools/cmakev2/kconfig.cmake @@ -51,7 +51,10 @@ function(__init_kconfig) # include(project.cmake) but before project(). __resolve_sdkconfig_defaults() - idf_build_set_property(GENERATE_SDKCONFIG 1) + __get_default_value(VARIABLE GENERATE_SDKCONFIG + DEFAULT 1 + OUTPUT generate_sdkconfig) + idf_build_set_property(GENERATE_SDKCONFIG "${generate_sdkconfig}") # Setup ESP-IDF root Kconfig and sdkconfig.rename files. idf_build_set_property(__ROOT_KCONFIG "${idf_path}/Kconfig") @@ -994,6 +997,97 @@ function(idf_create_menuconfig executable) endif() endfunction() +#[[ +.. cmakev2:function:: idf_register_menuconfig + + .. code-block:: cmake + + idf_register_menuconfig(NAME + TARGET ) + + *NAME[in]* + + Human-readable label shown in the dispatcher picker (e.g. ``"Main application"``, + ``"ULP (lp_core)"``). + + *TARGET[in]* + + Name of an existing CMake target that, when built, launches a menuconfig + session for some configuration. May be a target created by + ``idf_create_menuconfig`` or any other custom target (for example a proxy + target forwarding to a sub-project's ``menuconfig`` via + ``externalproject_add``). + + Register a menuconfig target with the cmakev2 dispatcher. When exactly one + menuconfig is registered (the common case), ``idf.py menuconfig`` behaves + identically to today and runs that target directly. When two or more are + registered, ``idf.py menuconfig`` presents a small picker so the user can + choose which configuration to edit. +#]] +function(idf_register_menuconfig) + set(options) + set(one_value NAME TARGET) + set(multi_value) + cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN}) + + if(NOT DEFINED ARG_NAME) + idf_die("idf_register_menuconfig: NAME option is required") + endif() + + if(NOT DEFINED ARG_TARGET) + idf_die("idf_register_menuconfig: TARGET option is required") + endif() + + idf_build_get_property(names __MENUCONFIG_NAMES) + idf_build_get_property(targets __MENUCONFIG_TARGETS) + list(APPEND names "${ARG_NAME}") + list(APPEND targets "${ARG_TARGET}") + idf_build_set_property(__MENUCONFIG_NAMES "${names}") + idf_build_set_property(__MENUCONFIG_TARGETS "${targets}") +endfunction() + +# Create the user-facing `menuconfig` target based on what has been registered +# via idf_register_menuconfig(). With a single registration, `menuconfig` is a +# thin alias for that target. With two or more, it runs the dispatcher. +function(__finalize_menuconfig) + idf_build_get_property(names __MENUCONFIG_NAMES) + idf_build_get_property(targets __MENUCONFIG_TARGETS) + list(LENGTH names num_entries) + + if(num_entries EQUAL 0) + return() + endif() + + if(num_entries EQUAL 1) + list(GET targets 0 target) + add_custom_target(menuconfig) + add_dependencies(menuconfig ${target}) + return() + endif() + + # Two or more registrations: generate entries file and create dispatcher target + idf_build_get_property(build_dir BUILD_DIR) + idf_build_get_property(python PYTHON) + idf_build_get_property(idf_path IDF_PATH) + + set(entries_file "${build_dir}/menuconfig_entries.txt") + set(content "") + math(EXPR last_idx "${num_entries} - 1") + foreach(i RANGE ${last_idx}) + list(GET names ${i} name) + list(GET targets ${i} target) + string(APPEND content "${name}|${target}\n") + endforeach() + file(WRITE "${entries_file}" "${content}") + + add_custom_target(menuconfig + COMMAND ${python} "${idf_path}/tools/kconfig_new/menuconfig_dispatcher.py" + "${entries_file}" "${build_dir}" + USES_TERMINAL + VERBATIM + ) +endfunction() + #[[api .. cmakev2:function:: idf_create_confserver diff --git a/tools/cmakev2/project.cmake b/tools/cmakev2/project.cmake index bc5ae404200..c8e229e7064 100644 --- a/tools/cmakev2/project.cmake +++ b/tools/cmakev2/project.cmake @@ -90,6 +90,17 @@ function(__init_project_configuration) idf_build_get_property(build_dir BUILD_DIR) idf_build_get_property(project_dir PROJECT_DIR) idf_build_get_property(project_name PROJECT_NAME) + get_property(enabled_languages GLOBAL PROPERTY ENABLED_LANGUAGES) + + set(c_enabled OFF) + if(C IN_LIST enabled_languages) + set(c_enabled ON) + endif() + + set(cxx_enabled OFF) + if(CXX IN_LIST enabled_languages) + set(cxx_enabled ON) + endif() # Set the LINKER_TYPE build property. Different linkers may have varying # options, so it's important to identify the linker type to configure the @@ -144,38 +155,43 @@ function(__init_project_configuration) if(IDF_TARGET STREQUAL "linux") # Building for Linux target, fall back to an older version of the standard # if the preferred one is not supported by the compiler. - set(preferred_c_versions gnu23 gnu17 gnu11 gnu99) - set(ver_found FALSE) - foreach(c_version ${preferred_c_versions}) - check_c_compiler_flag("-std=${c_version}" ver_${c_version}_supported) - if(ver_${c_version}_supported) - set(c_std ${c_version}) - set(ver_found TRUE) - break() + if(c_enabled) + set(preferred_c_versions gnu23 gnu17 gnu11 gnu99) + set(ver_found FALSE) + foreach(c_version ${preferred_c_versions}) + check_c_compiler_flag("-std=${c_version}" ver_${c_version}_supported) + if(ver_${c_version}_supported) + set(c_std ${c_version}) + set(ver_found TRUE) + break() + endif() + endforeach() + if(NOT ver_found) + idf_die("Failed to set C language standard to one of the supported versions: " + "${preferred_c_versions}. Please upgrade the host compiler.") endif() - endforeach() - if(NOT ver_found) - idf_die("Failed to set C language standard to one of the supported versions: " - "${preferred_c_versions}. Please upgrade the host compiler.") + + list(APPEND c_compile_options "-std=${c_std}") endif() - set(preferred_cxx_versions gnu++26 gnu++2b gnu++20 gnu++2a gnu++17 gnu++14) - set(ver_found FALSE) - foreach(cxx_version ${preferred_cxx_versions}) - check_cxx_compiler_flag("-std=${cxx_version}" ver_${cxx_version}_supported) - if(ver_${cxx_version}_supported) - set(cxx_std ${cxx_version}) - set(ver_found TRUE) - break() + if(cxx_enabled) + set(preferred_cxx_versions gnu++26 gnu++2b gnu++20 gnu++2a gnu++17 gnu++14) + set(ver_found FALSE) + foreach(cxx_version ${preferred_cxx_versions}) + check_cxx_compiler_flag("-std=${cxx_version}" ver_${cxx_version}_supported) + if(ver_${cxx_version}_supported) + set(cxx_std ${cxx_version}) + set(ver_found TRUE) + break() + endif() + endforeach() + if(NOT ver_found) + idf_die("Failed to set C++ language standard to one of the supported versions: " + "${preferred_cxx_versions}. Please upgrade the host compiler.") endif() - endforeach() - if(NOT ver_found) - idf_die("Failed to set C++ language standard to one of the supported versions: " - "${preferred_cxx_versions}. Please upgrade the host compiler.") - endif() - list(APPEND c_compile_options "-std=${c_std}") - list(APPEND cxx_compile_options "-std=${cxx_std}") + list(APPEND cxx_compile_options "-std=${cxx_std}") + endif() else() # Building for chip targets: we use a known version of the toolchain. # Use latest supported versions. @@ -183,8 +199,12 @@ function(__init_project_configuration) # function, which must be called after project(). # Please update docs/en/api-guides/c.rst, docs/en/api-guides/cplusplus.rst and # tools/test_apps/system/cxx_build_test/main/test_cxx_standard.cpp when changing this. - list(APPEND c_compile_options "-std=gnu23") - list(APPEND cxx_compile_options "-std=gnu++26") + if(c_enabled) + list(APPEND c_compile_options "-std=gnu23") + endif() + if(cxx_enabled) + list(APPEND cxx_compile_options "-std=gnu++26") + endif() endif() # Subprojects that handle their own compiler optimization flags can set the @@ -215,17 +235,19 @@ function(__init_project_configuration) endif() endif() - if(CONFIG_COMPILER_CXX_EXCEPTIONS) - list(APPEND cxx_compile_options "-fexceptions") - else() - list(APPEND cxx_compile_options "-fno-exceptions") - endif() + if(cxx_enabled) + if(CONFIG_COMPILER_CXX_EXCEPTIONS) + list(APPEND cxx_compile_options "-fexceptions") + else() + list(APPEND cxx_compile_options "-fno-exceptions") + endif() - if(CONFIG_COMPILER_CXX_RTTI) - list(APPEND cxx_compile_options "-frtti") - else() - list(APPEND cxx_compile_options "-fno-rtti") - list(APPEND link_options "-fno-rtti") # used to invoke correct multilib variant (no-rtti) during linking + if(CONFIG_COMPILER_CXX_RTTI) + list(APPEND cxx_compile_options "-frtti") + else() + list(APPEND cxx_compile_options "-fno-rtti") + list(APPEND link_options "-fno-rtti") # used to invoke correct multilib variant (no-rtti) during linking + endif() endif() if(CONFIG_COMPILER_SAVE_RESTORE_LIBCALLS) @@ -236,10 +258,12 @@ function(__init_project_configuration) list(APPEND c_compile_options "-Wno-old-style-declaration") endif() - # TODO IDF-15784: remove in IDF v7.0 ? - check_c_compiler_flag("-Wunused-but-set-variable=1" compiler_supports_wunused_but_set_variable_eq_1) - if(compiler_supports_wunused_but_set_variable_eq_1) - list(APPEND compile_options "-Wunused-but-set-variable=1") + if(c_enabled) + # TODO IDF-15784: remove in IDF v7.0 ? + check_c_compiler_flag("-Wunused-but-set-variable=1" compiler_supports_wunused_but_set_variable_eq_1) + if(compiler_supports_wunused_but_set_variable_eq_1) + list(APPEND compile_options "-Wunused-but-set-variable=1") + endif() endif() if(CONFIG_COMPILER_CXX_TRIVIAL_AUTO_VAR_INIT_UNINITIALIZED) @@ -249,7 +273,7 @@ function(__init_project_configuration) elseif(CONFIG_COMPILER_CXX_TRIVIAL_AUTO_VAR_INIT_ZERO) set(compiler_cxx_trivial_auto_var_init "zero") endif() - if(compiler_cxx_trivial_auto_var_init) + if(NOT IDF_CUSTOM_TOOLCHAIN AND cxx_enabled AND compiler_cxx_trivial_auto_var_init) idf_toolchain_remove_flags(CXX_COMPILE_OPTIONS "-ftrivial-auto-var-init") check_cxx_compiler_flag("-ftrivial-auto-var-init=${compiler_cxx_trivial_auto_var_init}" compiler_supports_ftrivial_auto_var_init) @@ -886,7 +910,10 @@ function(__project_default) endif() idf_create_menuconfig("${executable}" - TARGET menuconfig) + TARGET menuconfig-app) + idf_register_menuconfig(NAME "Main application" + TARGET menuconfig-app) + __finalize_menuconfig() idf_create_confserver("${executable}" TARGET confserver) diff --git a/tools/cmakev2/utilities.cmake b/tools/cmakev2/utilities.cmake index 5be6325b6ff..d44a4cd5b28 100644 --- a/tools/cmakev2/utilities.cmake +++ b/tools/cmakev2/utilities.cmake @@ -1182,7 +1182,30 @@ function(fail_at_build_time target_name message_line0) endfunction() #[[ - __preprocess_linker_script( ) + __linker_script_key( ) + + *path[in]* + + Linker script or template path. + + *output[out]* + + Variable set to a stable key derived from the absolute ``path``. + + Compute a collision-resistant key for a linker script. It is used to store + per-script metadata such as the generated output path and preprocessor + flags as component properties. Both the producer ``target_linker_script`` + and the consumer ``idf_build_library`` must derive the key identically, so + the absolute-path normalization lives here and nowhere else. +#]] +function(__linker_script_key path output) + get_filename_component(path_abs "${path}" ABSOLUTE) + string(MD5 hash "${path_abs}") + set(${output} "${hash}" PARENT_SCOPE) +endfunction() + +#[[ + __preprocess_linker_script( ) *script_in[in]* @@ -1192,29 +1215,47 @@ endfunction() Path where the preprocessed linker script will be saved. + *flags[in]* + + Explicit preprocessor flags from ``target_linker_script``'s ``FLAGS`` + option, or the literal ``__DEFAULT__`` when no ``FLAGS`` was given. + ``__DEFAULT__`` keeps comments (``-C``) and selects the parent-dir==target + include heuristic; any other value, including an empty string, replaces + that whole set, so a caller can drop ``-C``. + + *component_includes[in]* + + Pre-built ``-I`` arguments for the linked component graph, always + appended so a template can include any component header. + Run the C preprocessor on ``script_in`` and store the result in - ``script_out``. + ``script_out``. The preprocessor always receives ``-I`` so + ``sdkconfig.h`` resolves, plus ``component_includes``. #]] -function(__preprocess_linker_script script_in script_out) +function(__preprocess_linker_script script_in script_out flags component_includes) idf_build_get_property(sdkconfig_header __SDKCONFIG_HEADER) idf_build_get_property(idf_path IDF_PATH) idf_build_get_property(config_dir CONFIG_DIR) idf_build_get_property(idf_target IDF_TARGET) - # This approach is not ideal, but it is the current method used in cmakev1 - # for the esp_system component. The linker script files for specific - # targets within the esp_system component include the ld.common file. This - # adds the ld.common directory to the search path for the C preprocessor. - # This method works for the specific directory layout of esp_common, but if - # other components with different layouts adopt this approach, adjustments - # will be necessary. It might be better to include the files using relative - # paths in the linker scripts. - get_filename_component(script_parent_dir "${script_in}" DIRECTORY) - get_filename_component(script_parent_name "${script_parent_dir}" NAME) - set(extra_cflags "") - if(script_parent_name STREQUAL idf_target) - get_filename_component(dir_to_include "${script_parent_dir}" DIRECTORY) - set(extra_cflags "-I\"${dir_to_include}\"") + if(flags STREQUAL "__DEFAULT__") + # No FLAGS were given: keep comments (-C, the historical default) and + # apply the parent-dir==target include heuristic. esp_system's + # target-specific scripts live in ld// and include the sibling + # ld.common file, so add the parent directory to the C preprocessor + # search path. Works for that layout; a component with a different + # layout, or one that must drop -C, should pass FLAGS instead. + set(base_flags "-C") + get_filename_component(script_parent_dir "${script_in}" DIRECTORY) + get_filename_component(script_parent_name "${script_parent_dir}" NAME) + if(script_parent_name STREQUAL idf_target) + get_filename_component(dir_to_include "${script_parent_dir}" DIRECTORY) + string(APPEND base_flags " -I\"${dir_to_include}\"") + endif() + else() + # Explicit FLAGS replace the whole default set, including -C. config_dir + # and the component include dirs are always added regardless. + set(base_flags "${flags}") endif() set(linker_script_generator "${idf_path}/tools/cmake/linker_script_preprocessor.cmake") @@ -1225,7 +1266,7 @@ function(__preprocess_linker_script script_in script_out) "-DCC=${CMAKE_C_COMPILER}" "-DSOURCE=${script_in}" "-DTARGET=${script_out}" - "-DCFLAGS=-I\"${config_dir}\" ${extra_cflags}" + "-DCFLAGS=-I\"${config_dir}\" ${base_flags} ${component_includes}" -P "${linker_script_generator}" MAIN_DEPENDENCY "${script_in}" DEPENDS "${sdkconfig_header}" diff --git a/tools/kconfig_new/menuconfig_dispatcher.py b/tools/kconfig_new/menuconfig_dispatcher.py new file mode 100644 index 00000000000..6431e5c8c3c --- /dev/null +++ b/tools/kconfig_new/menuconfig_dispatcher.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import subprocess +import sys +from pathlib import Path + + +def _read_entries(entries_file: Path) -> list[tuple[str, str]]: + entries: list[tuple[str, str]] = [] + for line in entries_file.read_text().splitlines(): + line = line.strip() + if not line: + continue + name, _, target = line.partition('|') + entries.append((name, target)) + return entries + + +def _prompt(entries: list[tuple[str, str]]) -> str | None: + width = max(len(name) for name, _ in entries) + print('\nMultiple menuconfig configurations available:\n') + for i, (name, target) in enumerate(entries, 1): + print(f' [{i}] {name:<{width}} (idf.py {target})') + print() + + while True: + try: + choice = input(f'Select [1-{len(entries)}, q to quit]: ').strip().lower() + except EOFError: + return None + if choice in ('q', 'quit', ''): + return None + if choice.isdigit(): + idx = int(choice) + if 1 <= idx <= len(entries): + return entries[idx - 1][1] + print('Invalid choice.') + + +def main() -> int: + if len(sys.argv) != 3: + print(f'Usage: {sys.argv[0]} ', file=sys.stderr) + return 2 + + entries_file = Path(sys.argv[1]) + build_dir = sys.argv[2] + + entries = _read_entries(entries_file) + if not entries: + print(f'No menuconfig entries registered in {entries_file}', file=sys.stderr) + return 1 + + target = _prompt(entries) + if target is None: + return 0 + + return subprocess.run(['cmake', '--build', build_dir, '--target', target]).returncode + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/test_apps/system/.build-test-rules.yml b/tools/test_apps/system/.build-test-rules.yml index ba63e747e23..11f2dd1c26c 100644 --- a/tools/test_apps/system/.build-test-rules.yml +++ b/tools/test_apps/system/.build-test-rules.yml @@ -1,5 +1,17 @@ # Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps +.ulp_full_subproject_child_project_disable: &ulp_full_subproject_child_project_disable + disable: + - if: IDF_TARGET in ["esp32", "esp32s2", "esp32s3", "esp32c3", "esp32c2", "esp32c6", "esp32c5", "esp32h2", "esp32p4", "esp32c61", "esp32h21", "esp32h4", "esp32s31"] + reason: ULP child project is built by its parent test app + +.ulp_full_subproject_default_depends: &ulp_full_subproject_default_depends + depends_components: + - ulp + - esp_hw_support + - esp_hal_pmu + + tools/test_apps/system/bootloader_sections: disable: - if: CONFIG_NAME == "rtc_retain" and SOC_RTC_FAST_MEM_SUPPORTED != 1 @@ -182,6 +194,9 @@ tools/test_apps/system/ram_loadable_app: reason: cannot pass # TODO: IDF-15525 tools/test_apps/system/rtc_mem_reserve: + disable: + - if: IDF_BUILD_V2 == "1" + reason: Legacy ULP apps are covered by CMake v1; buildv2 covers full_subproject ULP apps. enable: - if: IDF_TARGET in ["esp32p4"] reason: only P4 has a potential conflict due to using rtc mem for lp rom data/stack @@ -199,6 +214,48 @@ tools/test_apps/system/test_watchpoint: enable: - if: IDF_TARGET in ["esp32", "esp32c3"] # Just one test per architecture +tools/test_apps/system/ulp/full_subproject/combined: + enable: + - if: (SOC_ULP_FSM_SUPPORTED == 1) and (SOC_RISCV_COPROC_SUPPORTED == 1) + <<: *ulp_full_subproject_default_depends + +tools/test_apps/system/ulp/full_subproject/combined/main/ulp_fsm: + <<: *ulp_full_subproject_child_project_disable + +tools/test_apps/system/ulp/full_subproject/combined/main/ulp_riscv: + <<: *ulp_full_subproject_child_project_disable + +tools/test_apps/system/ulp/full_subproject/fsm: + enable: + - if: SOC_ULP_FSM_SUPPORTED == 1 + <<: *ulp_full_subproject_default_depends + +tools/test_apps/system/ulp/full_subproject/fsm/main/ulp: + <<: *ulp_full_subproject_child_project_disable + +tools/test_apps/system/ulp/full_subproject/lp_core: + enable: + - if: SOC_LP_CORE_SUPPORTED == 1 + <<: *ulp_full_subproject_default_depends + +tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp: + <<: *ulp_full_subproject_child_project_disable + +tools/test_apps/system/ulp/full_subproject/multi_binary: + enable: + - if: SOC_LP_CORE_SUPPORTED == 1 + <<: *ulp_full_subproject_default_depends + +tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp: + <<: *ulp_full_subproject_child_project_disable + +tools/test_apps/system/ulp/full_subproject/riscv: + enable: + - if: SOC_RISCV_COPROC_SUPPORTED == 1 + <<: *ulp_full_subproject_default_depends + +tools/test_apps/system/ulp/full_subproject/riscv/main/ulp: + <<: *ulp_full_subproject_child_project_disable tools/test_apps/system/unicore_bootloader: disable: - if: SOC_CPU_CORES_NUM == 1 diff --git a/tools/test_apps/system/ulp/full_subproject/combined/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/combined/CMakeLists.txt new file mode 100644 index 00000000000..fee86eb2ffb --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/combined/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake) + +project(combined C CXX ASM) + +idf_project_default() diff --git a/tools/test_apps/system/ulp/full_subproject/combined/README.md b/tools/test_apps/system/ulp/full_subproject/combined/README.md new file mode 100644 index 00000000000..c5101b052b1 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/combined/README.md @@ -0,0 +1,26 @@ +| Supported Targets | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | + +# ULP FSM and RISC-V Full Subproject Example + +This example demonstrates two separate ULP full subprojects in one application. The main application starts a ULP FSM subproject first, then starts a ULP RISC-V subproject after the FSM counter completes. + +The ULP FSM program is built from `main/ulp_fsm`, and the ULP RISC-V program is built from `main/ulp_riscv`. The parent app registers them independently with `ulp_add_project()`: + +```cmake +ulp_add_project("ulp_fsm_main" "${CMAKE_CURRENT_LIST_DIR}/ulp_fsm/" TYPE fsm) +ulp_add_project("ulp_riscv_main" "${CMAKE_CURRENT_LIST_DIR}/ulp_riscv/" TYPE riscv) +``` + +At runtime, the FSM ULP increments `fsm_counter` to 5 and wakes the main CPU from light sleep. The main CPU then loads the RISC-V ULP, initializes `riscv_counter` from the FSM result, and lets the RISC-V ULP continue counting to 10. + +## Example Output + +``` +ULP FSM and RISC-V full subproject example +Starting ULP FSM counter +ULP FSM counter: 5 +Starting ULP RISC-V counter +ULP RISC-V counter: 10 +Example finished +``` diff --git a/tools/test_apps/system/ulp/full_subproject/combined/main/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/combined/main/CMakeLists.txt new file mode 100644 index 00000000000..a295e2adc89 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/combined/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS "ulp_combined_build_system_example_main.c" + REQUIRES ulp) + +ulp_add_project("ulp_fsm_main" "${CMAKE_CURRENT_LIST_DIR}/ulp_fsm/" TYPE fsm) +ulp_add_project("ulp_riscv_main" "${CMAKE_CURRENT_LIST_DIR}/ulp_riscv/" TYPE riscv) diff --git a/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_combined_build_system_example_main.c b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_combined_build_system_example_main.c new file mode 100644 index 00000000000..21b3608bc15 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_combined_build_system_example_main.c @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include "esp_sleep.h" +#include "ulp.h" +#include "ulp_riscv.h" +#include "ulp_fsm_main.h" +#include "ulp_riscv_main.h" + +extern const uint8_t ulp_fsm_main_bin_start[] asm("_binary_ulp_fsm_main_bin_start"); +extern const uint8_t ulp_fsm_main_bin_end[] asm("_binary_ulp_fsm_main_bin_end"); + +extern const uint8_t ulp_riscv_main_bin_start[] asm("_binary_ulp_riscv_main_bin_start"); +extern const uint8_t ulp_riscv_main_bin_end[] asm("_binary_ulp_riscv_main_bin_end"); + +static void init_ulp_fsm_program(void); +static void init_ulp_riscv_program(uint32_t initial_count); + +void app_main(void) +{ + printf("ULP FSM and RISC-V full subproject example\n"); + + ESP_ERROR_CHECK(esp_sleep_enable_ulp_wakeup()); + + printf("Starting ULP FSM counter\n"); + init_ulp_fsm_program(); + ESP_ERROR_CHECK(esp_light_sleep_start()); + ulp_timer_stop(); + + uint32_t fsm_count = ulp_fsm_counter & UINT16_MAX; + printf("ULP FSM counter: %" PRIu32 "\n", fsm_count); + + printf("Starting ULP RISC-V counter\n"); + init_ulp_riscv_program(fsm_count); + ESP_ERROR_CHECK(esp_light_sleep_start()); + ulp_riscv_timer_stop(); + + uint32_t riscv_count = ulp_riscv_counter; + printf("ULP RISC-V counter: %" PRIu32 "\n", riscv_count); + printf("Example finished\n"); +} + +static void init_ulp_fsm_program(void) +{ + ESP_ERROR_CHECK(ulp_load_binary(0, ulp_fsm_main_bin_start, + (ulp_fsm_main_bin_end - ulp_fsm_main_bin_start) / sizeof(uint32_t))); + + ulp_fsm_counter = 0; + ulp_set_wakeup_period(0, 10000); + ESP_ERROR_CHECK(ulp_run(&ulp_entry - RTC_SLOW_MEM)); +} + +static void init_ulp_riscv_program(uint32_t initial_count) +{ + ESP_ERROR_CHECK(ulp_riscv_load_binary(ulp_riscv_main_bin_start, + ulp_riscv_main_bin_end - ulp_riscv_main_bin_start)); + + ulp_riscv_counter = initial_count; + ulp_set_wakeup_period(0, 10000); + ESP_ERROR_CHECK(ulp_riscv_run()); +} diff --git a/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_fsm/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_fsm/CMakeLists.txt new file mode 100644 index 00000000000..b48a7286f3e --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_fsm/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.22) + +include(${IDF_PATH}/components/ulp/cmake/ulp_project.cmake) + +project(${IDF_DEFAULT_PROJECT_NAME} ASM) + +ulp_project_default() diff --git a/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_fsm/README.md b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_fsm/README.md new file mode 100644 index 00000000000..85fb18b5570 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_fsm/README.md @@ -0,0 +1,3 @@ +# ULP FSM Subproject + +This ULP FSM subproject is built by the parent combined example. It increments `fsm_counter` on each timer wakeup and wakes the main CPU when the counter reaches 5. diff --git a/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_fsm/main/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_fsm/main/CMakeLists.txt new file mode 100644 index 00000000000..5021b10db9f --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_fsm/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.S" + REQUIRES ulp) diff --git a/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_fsm/main/main.S b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_fsm/main/main.S new file mode 100644 index 00000000000..290363fe17d --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_fsm/main/main.S @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + + .bss + .global fsm_counter +fsm_counter: + .long 0 + + .text + .global entry +entry: + move r1, fsm_counter + ld r0, r1, 0 + add r0, r0, 1 + st r0, r1, 0 + jumpr wake_cpu, 5, ge + halt + +wake_cpu: + wake + halt diff --git a/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_riscv/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_riscv/CMakeLists.txt new file mode 100644 index 00000000000..ec8b058fc29 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_riscv/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.22) + +include(${IDF_PATH}/components/ulp/cmake/ulp_project.cmake) + +project(${IDF_DEFAULT_PROJECT_NAME} C CXX ASM) + +ulp_project_default() diff --git a/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_riscv/README.md b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_riscv/README.md new file mode 100644 index 00000000000..ba9e9884dab --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_riscv/README.md @@ -0,0 +1,3 @@ +# ULP RISC-V Subproject + +This ULP RISC-V subproject is built by the parent combined example. It continues from the FSM counter value and wakes the main CPU when `riscv_counter` reaches 10. diff --git a/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_riscv/main/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_riscv/main/CMakeLists.txt new file mode 100644 index 00000000000..9d04d40931e --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_riscv/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.c" + REQUIRES ulp) diff --git a/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_riscv/main/main.c b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_riscv/main/main.c new file mode 100644 index 00000000000..73d9118835b --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/combined/main/ulp_riscv/main/main.c @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include "ulp_riscv_utils.h" + +uint32_t riscv_counter = 0; + +int main(void) +{ + riscv_counter++; + if (riscv_counter >= 10) { + ulp_riscv_wakeup_main_processor(); + } + + return 0; +} diff --git a/tools/test_apps/system/ulp/full_subproject/combined/pytest_ulp_combined.py b/tools/test_apps/system/ulp/full_subproject/combined/pytest_ulp_combined.py new file mode 100644 index 00000000000..c819e2ca54c --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/combined/pytest_ulp_combined.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@idf_parametrize('target', ['esp32s2', 'esp32s3'], indirect=['target']) +def test_ulp_combined(dut: Dut) -> None: + dut.expect_exact('ULP FSM and RISC-V full subproject example') + dut.expect_exact('Starting ULP FSM counter') + dut.expect_exact('ULP FSM counter: 5', timeout=5) + dut.expect_exact('Starting ULP RISC-V counter') + dut.expect_exact('ULP RISC-V counter: 10', timeout=5) + dut.expect_exact('Example finished') diff --git a/tools/test_apps/system/ulp/full_subproject/combined/sdkconfig.defaults b/tools/test_apps/system/ulp/full_subproject/combined/sdkconfig.defaults new file mode 100644 index 00000000000..96f91847646 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/combined/sdkconfig.defaults @@ -0,0 +1,11 @@ +# Enable both ULP types +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_FSM=y +CONFIG_ULP_COPROC_TYPE_RISCV=y +CONFIG_ULP_COPROC_RESERVE_MEM=4096 + +# Set log level to Warning to produce clean output +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_BOOTLOADER_LOG_LEVEL=2 +CONFIG_LOG_DEFAULT_LEVEL_WARN=y +CONFIG_LOG_DEFAULT_LEVEL=2 diff --git a/tools/test_apps/system/ulp/full_subproject/fsm/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/fsm/CMakeLists.txt new file mode 100644 index 00000000000..c5610df3b41 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/fsm/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake) + +project(fsm C CXX ASM) + +idf_project_default() diff --git a/tools/test_apps/system/ulp/full_subproject/fsm/README.md b/tools/test_apps/system/ulp/full_subproject/fsm/README.md new file mode 100644 index 00000000000..369a23954fa --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/fsm/README.md @@ -0,0 +1,25 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | + +# ULP FSM Build System Example + +This example demonstrates a separate ULP FSM project built with the CMake v2 ULP project helpers. It programs the ULP FSM coprocessor to increment a counter while the main CPUs are in deep sleep, then wake the main CPU after a fixed number of timer iterations. + +ULP program written in assembly can be found across `main/ulp/main/counter.S` and `main/ulp/main/wake_up.S` (demonstrating multiple ULP source files). The build system assembles and links this program, converts it into binary format, and embeds it into the .rodata section of the ESP-IDF application. + +At runtime, the main code running on the ESP (found in main.c) loads ULP program into the `RTC_SLOW_MEM` memory region using `ulp_load_binary` function. Main code configures the ULP program by setting up values of some variables and then starts it using `ulp_run`. Once the ULP program is started, it runs periodically, with the period set by the main program. The main program enables ULP wakeup source and puts the chip into deep sleep mode. + +On each timer iteration, the ULP program increments `iteration_count`. Once this counter reaches `wakeup_iteration` (set by the main program), the ULP triggers wake up from deep sleep. Upon wakeup, the main program stops the ULP timer, prints the final counter value, and exits the example. + +In this example, the `CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP` Kconfig option is used, which allows you to reduce the boot time of the bootloader during waking up from deep sleep. The bootloader stores in rtc memory the address of a running partition and uses it when it wakes up. This example allows you to skip all image checks and speed up the boot. + +## Example output + +``` +Not ULP wakeup, initializing ULP +Entering deep sleep + +ULP FSM woke up the main CPU +ULP FSM iteration count: 5 +Example finished +``` diff --git a/tools/test_apps/system/ulp/full_subproject/fsm/main/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/fsm/main/CMakeLists.txt new file mode 100644 index 00000000000..ae4ce259ff0 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/fsm/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "ulp_example_main.c" + REQUIRES ulp) + +ulp_add_project("ulp_main" "${CMAKE_CURRENT_LIST_DIR}/ulp/" TYPE fsm) diff --git a/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/CMakeLists.txt new file mode 100644 index 00000000000..b48a7286f3e --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.22) + +include(${IDF_PATH}/components/ulp/cmake/ulp_project.cmake) + +project(${IDF_DEFAULT_PROJECT_NAME} ASM) + +ulp_project_default() diff --git a/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/README.md b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/README.md new file mode 100644 index 00000000000..b88a9825d9f --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | diff --git a/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/components/ulp_wake/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/components/ulp_wake/CMakeLists.txt new file mode 100644 index 00000000000..694ddcfaa66 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/components/ulp_wake/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "wake_up.S" + REQUIRES ulp) diff --git a/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/components/ulp_wake/wake_up.S b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/components/ulp_wake/wake_up.S new file mode 100644 index 00000000000..da2dd78fc15 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/components/ulp_wake/wake_up.S @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* ULP assembly files are passed through C preprocessor first, so include directives + and C macros may be used in these files + */ +#include "soc/rtc_cntl_reg.h" +#include "soc/soc_ulp.h" +#include "sdkconfig.h" + + .global wake_up +wake_up: + /* Check if the system is in sleep mode */ +#if CONFIG_IDF_TARGET_ESP32 + READ_RTC_REG(RTC_CNTL_LOW_POWER_ST_REG, 27, 1) +#else + READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_MAIN_STATE_IN_IDLE) +#endif + move r1, r0 + /* Check if the system can be woken up */ + READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP) + /* If the system is in normal mode or if the system is in sleep mode with ready for wakeup set, we can signal the main CPU to wakeup */ + or r0, r0, r1 + jump wake_up, eq + + /* Wake up the SoC, end program */ + wake + halt diff --git a/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/main/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/main/CMakeLists.txt new file mode 100644 index 00000000000..81958e793d6 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/main/CMakeLists.txt @@ -0,0 +1,13 @@ +set(srcs + "counter.S") + +idf_component_register(SRCS ${srcs} + REQUIRES ulp ulp_wake + LDFRAGMENTS "ulp_fsm_sections.lf") + +target_compile_definitions(${COMPONENT_LIB} PRIVATE ULP_BUILD_SYSTEM_FSM_EXAMPLE=1) + +# Supplementary linker script template processed by ldgen. The mapping marker +# is resolved using ulp_fsm_sections.lf from this component. +target_linker_script(${COMPONENT_NAME} INTERFACE ulp_sections.ld.in + PROCESS "${CMAKE_BINARY_DIR}/ulp_sections.ld") diff --git a/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/main/counter.S b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/main/counter.S new file mode 100644 index 00000000000..bfaffc59d71 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/main/counter.S @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +/* ULP Example: timer counter + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. + + This file contains assembly code which runs on the ULP. On each timer wakeup, + the program increments a counter in RTC_SLOW_MEM. Once the counter reaches + the value set by the main program, the ULP wakes the main CPU from deep sleep. +*/ + +/* ULP assembly files are passed through C preprocessor first, so include directives + and C macros may be used in these files + */ +#include "sdkconfig.h" + +#ifndef ULP_BUILD_SYSTEM_FSM_EXAMPLE +#error "ULP FSM compile definitions were not passed to the assembly preprocessor" +#endif + + /* Define variables, which go into .bss section (zero-initialized data) */ + .bss + .global iteration_count +iteration_count: + .long 0 + + .global wakeup_iteration +wakeup_iteration: + .long 0 + + /* Ldgen-only marker section used by ulp_fsm_sections.lf. Keep it + non-allocated so it never becomes part of the ULP FSM load image. */ + .section .ulp_fsm_extra_data, "" +ldgen_section_marker: + .long 0x12345678 + + /* Code goes into .text section */ + .text + .global entry +entry: + /* Increment iteration_count. */ + move r3, iteration_count + ld r2, r3, 0 + add r2, r2, 1 + st r2, r3, 0 + + /* Compare iteration_count to wakeup_iteration. */ + move r3, wakeup_iteration + ld r3, r3, 0 + sub r3, r3, r2 + jump wake_up, eq + halt diff --git a/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/main/ulp_fsm_sections.lf b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/main/ulp_fsm_sections.lf new file mode 100644 index 00000000000..3af93816780 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/main/ulp_fsm_sections.lf @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 + +[sections:ulp_fsm_extra_data] +entries: + .ulp_fsm_extra_data+ + +[scheme:ulp_fsm_extra] +entries: + ulp_fsm_extra_data -> ulp_fsm_extra_data + +[mapping:main] +archive: libmain.a +entries: + counter (ulp_fsm_extra) diff --git a/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/main/ulp_sections.ld.in b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/main/ulp_sections.ld.in new file mode 100644 index 00000000000..8112034c5c6 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp/main/ulp_sections.ld.in @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +/* + * Supplementary ULP FSM linker script template processed by ldgen. + * The mapped marker is non-allocated, so it exercises ldgen without becoming + * part of the ULP FSM load image. The FSM loader validates the binary size + * against the .text/.data sizes recorded in the default linker-script header. + */ +SECTIONS +{ + .ulp_fsm_extra_data 0 (INFO) : + { + mapping[ulp_fsm_extra_data] + } +} diff --git a/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp_example_main.c b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp_example_main.c new file mode 100644 index 00000000000..b1f6c91add5 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/fsm/main/ulp_example_main.c @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* ULP Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include "esp_sleep.h" +#include "ulp.h" +#include "ulp_main.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start"); +extern const uint8_t ulp_main_bin_end[] asm("_binary_ulp_main_bin_end"); + +static void init_ulp_program(void); + +void app_main(void) +{ + /* If user is using USB-serial-jtag then idf monitor needs some time to + * re-connect to the USB port. We wait 1 sec here to allow for it to make the reconnection + * before we print anything. Otherwise the chip will go back to sleep again before the user + * has time to monitor any output. + */ + vTaskDelay(pdMS_TO_TICKS(1000)); + + uint32_t causes = esp_sleep_get_wakeup_causes(); + if (!(causes & BIT(ESP_SLEEP_WAKEUP_ULP))) { + printf("Not ULP wakeup, initializing ULP\n"); + init_ulp_program(); + } else { + ulp_timer_stop(); + printf("ULP FSM woke up the main CPU\n"); + printf("ULP FSM iteration count: %" PRIu32 "\n", ulp_iteration_count & UINT16_MAX); + printf("Example finished\n"); + vTaskDelay(portMAX_DELAY); + } + + printf("Entering deep sleep\n\n"); + ESP_ERROR_CHECK( esp_sleep_enable_ulp_wakeup() ); + esp_deep_sleep_start(); +} + +static void init_ulp_program(void) +{ + esp_err_t err = ulp_load_binary(0, ulp_main_bin_start, + (ulp_main_bin_end - ulp_main_bin_start) / sizeof(uint32_t)); + ESP_ERROR_CHECK(err); + + ulp_iteration_count = 0; + ulp_wakeup_iteration = 5; + + /* Set ULP wake up period to 20ms. */ + ulp_set_wakeup_period(0, 20000); + + /* Start the program */ + err = ulp_run(&ulp_entry - RTC_SLOW_MEM); + ESP_ERROR_CHECK(err); +} diff --git a/tools/test_apps/system/ulp/full_subproject/fsm/pytest_ulp_full_subproject_fsm.py b/tools/test_apps/system/ulp/full_subproject/fsm/pytest_ulp_full_subproject_fsm.py new file mode 100644 index 00000000000..531d2add191 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/fsm/pytest_ulp_full_subproject_fsm.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@idf_parametrize('target', ['esp32', 'esp32s2', 'esp32s3'], indirect=['target']) +def test_ulp_full_subproject_fsm(dut: Dut) -> None: + dut.expect_exact('Not ULP wakeup, initializing ULP') + dut.expect_exact('Entering deep sleep') + dut.expect_exact('ULP FSM woke up the main CPU', timeout=5) + count = int(dut.expect(r'ULP FSM iteration count:\s+(\d+)', timeout=5).group(1), 10) + assert count >= 5 + dut.expect_exact('Example finished') diff --git a/tools/test_apps/system/ulp/full_subproject/fsm/sdkconfig.defaults b/tools/test_apps/system/ulp/full_subproject/fsm/sdkconfig.defaults new file mode 100644 index 00000000000..1dffd2c78b2 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/fsm/sdkconfig.defaults @@ -0,0 +1,10 @@ +# Enable ULP +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_FSM=y +CONFIG_ULP_COPROC_RESERVE_MEM=1024 +# Set log level to Warning to produce clean output +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_BOOTLOADER_LOG_LEVEL=2 +CONFIG_LOG_DEFAULT_LEVEL_WARN=y +CONFIG_LOG_DEFAULT_LEVEL=2 +CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=y diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/lp_core/CMakeLists.txt new file mode 100644 index 00000000000..92afb8de861 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake) + +project(lp_core C CXX ASM) + +idf_project_default() diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/README.md b/tools/test_apps/system/ulp/full_subproject/lp_core/README.md new file mode 100644 index 00000000000..195279457e5 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-P4 | ESP32-S31 | +| ----------------- | -------- | -------- | -------- | --------- | diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/lp_core/main/CMakeLists.txt new file mode 100644 index 00000000000..c0cb4329097 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "lp_core_build_system_example_main.c" + REQUIRES ulp) + +ulp_add_project("ulp_build_system_example" "${CMAKE_CURRENT_LIST_DIR}/ulp/") diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/lp_core_build_system_example_main.c b/tools/test_apps/system/ulp/full_subproject/lp_core/main/lp_core_build_system_example_main.c new file mode 100644 index 00000000000..eaf341b5c37 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/lp_core_build_system_example_main.c @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include "esp_sleep.h" +#include "ulp_lp_core.h" +#include "ulp_build_system_example.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +extern const uint8_t ulp_build_system_example_bin_start[] asm("_binary_ulp_build_system_example_bin_start"); +extern const uint8_t ulp_build_system_example_bin_end[] asm("_binary_ulp_build_system_example_bin_end"); + +static void init_ulp_program(void); + +void app_main(void) +{ + vTaskDelay(pdMS_TO_TICKS(1000)); + + init_ulp_program(); + + /* Wait for ULP to complete. The ULP variables are in RTC memory and + * not declared volatile, so a delay is needed to prevent the compiler + * from optimizing the reads into a tight loop. */ + vTaskDelay(pdMS_TO_TICKS(100)); + + printf("Sum calculated by ULP (ulp_sum component): %" PRIi32 "\n", ulp_sum); + printf("Product calculated by ULP (ulp_math component): %" PRIi32 "\n", ulp_product); + + printf("Example finished\n"); + vTaskDelay(portMAX_DELAY); +} + +static void init_ulp_program(void) +{ + esp_err_t err = ulp_lp_core_load_binary(ulp_build_system_example_bin_start, + (ulp_build_system_example_bin_end - ulp_build_system_example_bin_start)); + ESP_ERROR_CHECK(err); + + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + err = ulp_lp_core_run(&cfg); + ESP_ERROR_CHECK(err); +} diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/CMakeLists.txt new file mode 100644 index 00000000000..ec8b058fc29 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.22) + +include(${IDF_PATH}/components/ulp/cmake/ulp_project.cmake) + +project(${IDF_DEFAULT_PROJECT_NAME} C CXX ASM) + +ulp_project_default() diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/CMakeLists.txt new file mode 100644 index 00000000000..51840ed3464 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "ulp_math.c" + INCLUDE_DIRS "include" + LDFRAGMENTS "ulp_math.lf") diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/Kconfig b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/Kconfig new file mode 100644 index 00000000000..7087c52ac5e --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/Kconfig @@ -0,0 +1,10 @@ +menu "ULP Math Component" + + config ULP_MATH_ENABLED + bool "Enable ULP math multiplication" + default y + help + When enabled, the ULP program will compute a product using the + ulp_math component. + +endmenu diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/include/ulp_math.h b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/include/ulp_math.h new file mode 100644 index 00000000000..e56462017cb --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/include/ulp_math.h @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +int ulp_math_multiply(int a, int b); diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/ulp_math.c b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/ulp_math.c new file mode 100644 index 00000000000..e126a2dfaed --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/ulp_math.c @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "ulp_math.h" + +/* Place in .ulp_math_fast section via the ldgen fragment (ulp_math.lf) */ +__attribute__((section(".ulp_math_fast"))) +int ulp_math_multiply(int a, int b) +{ + return a * b; +} diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/ulp_math.lf b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/ulp_math.lf new file mode 100644 index 00000000000..06984cb3561 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_math/ulp_math.lf @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 + +# Linker fragment file for the ulp_math ULP component. +# Demonstrates custom section placement via ldgen for ULP builds. + +[sections:ulp_fast_text] +entries: + .ulp_math_fast+ + +[scheme:ulp_fast] +entries: + ulp_fast_text -> ulp_math_fast + +[mapping:ulp_math] +archive: libulp_math.a +entries: + ulp_math (ulp_fast) diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/CMakeLists.txt new file mode 100644 index 00000000000..88f8577fdd8 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/CMakeLists.txt @@ -0,0 +1,4 @@ +# ulp_clamp is resolved by the component manager via idf_component.yml +idf_component_register(SRCS "ulp_sum.c" + INCLUDE_DIRS "include" + REQUIRES ulp_clamp) diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/Kconfig b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/Kconfig new file mode 100644 index 00000000000..b9bc989f0d1 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/Kconfig @@ -0,0 +1,10 @@ +menu "ULP Sum Component" + + config ULP_SUM_ENABLED + bool "Enable ULP sum function" + default y + help + When enabled, the ULP program will compute a sum using the + ulp_sum component. + +endmenu diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/idf_component.yml b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/idf_component.yml new file mode 100644 index 00000000000..f9d2a53baef --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + ulp_clamp: + path: ../../extra/ulp_clamp diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/include/ulp_sum.h b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/include/ulp_sum.h new file mode 100644 index 00000000000..05c225baec2 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/include/ulp_sum.h @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +int ulp_sum_func(int a, int b); diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/ulp_sum.c b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/ulp_sum.c new file mode 100644 index 00000000000..6e06514c915 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/components/ulp_sum/ulp_sum.c @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "sdkconfig.h" +#include "ulp_sum.h" +#include "ulp_clamp.h" + +int ulp_sum_func(int a, int b) +{ + /* Uses ulp_clamp() from the ulp_clamp managed component. + * CONFIG_ULP_CLAMP_MAX_VALUE comes from ulp_clamp's Kconfig. */ + return ulp_clamp(a + b, 0, CONFIG_ULP_CLAMP_MAX_VALUE); +} diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/extra/ulp_clamp/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/extra/ulp_clamp/CMakeLists.txt new file mode 100644 index 00000000000..9ece626b744 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/extra/ulp_clamp/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "ulp_clamp.c" + INCLUDE_DIRS "include") diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/extra/ulp_clamp/Kconfig b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/extra/ulp_clamp/Kconfig new file mode 100644 index 00000000000..48c8146f3f9 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/extra/ulp_clamp/Kconfig @@ -0,0 +1,9 @@ +menu "ULP Clamp Component" + + config ULP_CLAMP_MAX_VALUE + int "Maximum clamp value" + default 255 + help + The upper bound used by the ulp_clamp() function. + +endmenu diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/extra/ulp_clamp/include/ulp_clamp.h b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/extra/ulp_clamp/include/ulp_clamp.h new file mode 100644 index 00000000000..6e050b37a8a --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/extra/ulp_clamp/include/ulp_clamp.h @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +/* Clamp a value to the range [min, max]. */ +int ulp_clamp(int value, int min, int max); diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/extra/ulp_clamp/ulp_clamp.c b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/extra/ulp_clamp/ulp_clamp.c new file mode 100644 index 00000000000..85f026b5dcd --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/extra/ulp_clamp/ulp_clamp.c @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "ulp_clamp.h" + +int ulp_clamp(int value, int min, int max) +{ + if (value < min) { + return min; + } + if (value > max) { + return max; + } + return value; +} diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/main/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/main/CMakeLists.txt new file mode 100644 index 00000000000..09d8933709d --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/main/CMakeLists.txt @@ -0,0 +1,7 @@ +idf_component_register(SRCS "main.c" + REQUIRES ulp ulp_sum ulp_math) + +# Linker script template processed by ldgen. The mapping[ulp_math_fast] +# marker is resolved using the .lf fragment from the ulp_math component. +target_linker_script(${COMPONENT_NAME} INTERFACE ulp_sections.ld.in + PROCESS "${CMAKE_CURRENT_BINARY_DIR}/ulp_sections.ld") diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/main/main.c b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/main/main.c new file mode 100644 index 00000000000..fe1db5f8f33 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/main/main.c @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "ulp_sum.h" +#include "ulp_math.h" + +int sum; +int product; + +int main(void) +{ + /* ulp_sum_func internally uses ulp_clamp() from the ulp_clamp + * managed component (resolved via ulp_sum/idf_component.yml). */ + sum = ulp_sum_func(5, 6); + product = ulp_math_multiply(3, 7); + + /* ulp_lp_core_halt() is called automatically when main exits */ + return 0; +} diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/main/ulp_sections.ld.in b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/main/ulp_sections.ld.in new file mode 100644 index 00000000000..929fcda63d5 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/main/ulp/main/ulp_sections.ld.in @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +/* + * Supplementary ULP linker script template processed by ldgen. + * + * ldgen replaces the mapping[ulp_math_fast] marker with explicit symbol + * entries derived from the component .lf fragment files. The generated + * output is linked via -T alongside the main lp_core_riscv.ld. + */ +SECTIONS +{ + . = ADDR(.bss) + SIZEOF(.bss); + .ulp_math_fast ALIGN(4) : + { + mapping[ulp_math_fast] + *(.ulp_math_fast .ulp_math_fast.*) + } > default_app_seg +} diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/pytest_ulp_full_subproject_lp_core.py b/tools/test_apps/system/ulp/full_subproject/lp_core/pytest_ulp_full_subproject_lp_core.py new file mode 100644 index 00000000000..e917a47df5a --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/pytest_ulp_full_subproject_lp_core.py @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@idf_parametrize('target', ['esp32c5', 'esp32c6', 'esp32p4', 'esp32s31'], indirect=['target']) +def test_ulp_full_subproject_lp_core(dut: Dut) -> None: + dut.expect_exact('Sum calculated by ULP (ulp_sum component): 11') + dut.expect_exact('Product calculated by ULP (ulp_math component): 21') + dut.expect_exact('Example finished') diff --git a/tools/test_apps/system/ulp/full_subproject/lp_core/sdkconfig.defaults b/tools/test_apps/system/ulp/full_subproject/lp_core/sdkconfig.defaults new file mode 100644 index 00000000000..42aa360e61d --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/lp_core/sdkconfig.defaults @@ -0,0 +1,9 @@ +# Enable ULP +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_LP_CORE=y +CONFIG_ULP_COPROC_RESERVE_MEM=4096 +# Set log level to Warning to produce clean output +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_BOOTLOADER_LOG_LEVEL=2 +CONFIG_LOG_DEFAULT_LEVEL_WARN=y +CONFIG_LOG_DEFAULT_LEVEL=2 diff --git a/tools/test_apps/system/ulp/full_subproject/multi_binary/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/multi_binary/CMakeLists.txt new file mode 100644 index 00000000000..a5d2225c628 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/multi_binary/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake) + +project(multi_binary C CXX ASM) + +idf_project_default() diff --git a/tools/test_apps/system/ulp/full_subproject/multi_binary/README.md b/tools/test_apps/system/ulp/full_subproject/multi_binary/README.md new file mode 100644 index 00000000000..2468f274f13 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/multi_binary/README.md @@ -0,0 +1,31 @@ +| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-P4 | ESP32-S31 | +| ----------------- | -------- | -------- | -------- | --------- | + +# ULP LP-Core Multi-Binary Build System Example + +This example demonstrates a CMake v2 ULP full subproject that builds multiple +ULP binaries from one ULP child project. The ULP project uses the lower-level +ULP build APIs instead of `ulp_project_default()`: it calls +`ulp_project_init()`, creates two ULP executables with +`ulp_build_executable()`, and generates the binary artifacts for each +executable with `ulp_build_binary()`. + +The main application embeds both generated ULP binaries, runs the first one to +calculate an addition result, then loads and runs the second one to calculate a +multiplication result. + +Both ULP binaries define a global variable named `result`. Because no explicit +`PREFIX` is passed to `ulp_add_project()`, the build system gives each binary a +binary-scoped default symbol prefix, so the parent app can read +`ulp_add_result` and `ulp_multiply_result` without the generated linker exports +colliding. + +## Example Output + +``` +Running ULP add binary +ULP add result: 22 +Running ULP multiply binary +ULP multiply result: 42 +Example finished +``` diff --git a/tools/test_apps/system/ulp/full_subproject/multi_binary/main/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/CMakeLists.txt new file mode 100644 index 00000000000..5b9b5c17d1f --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS "ulp_multi_binary_example_main.c" + REQUIRES ulp) + +ulp_add_project("ulp_multi_binary" "${CMAKE_CURRENT_LIST_DIR}/ulp/" + BINARIES add multiply) diff --git a/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/CMakeLists.txt new file mode 100644 index 00000000000..1df774c07fb --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.22) + +include(${IDF_PATH}/components/ulp/cmake/ulp_project.cmake) + +project(${IDF_DEFAULT_PROJECT_NAME} C CXX ASM) + +ulp_project_init() + +ulp_build_executable(add + COMPONENTS ulp_add_main ulp_shared + MAPFILE_TARGET add_mapfile) +ulp_build_binary(add) + +ulp_build_executable(multiply + COMPONENTS ulp_multiply_main ulp_shared + MAPFILE_TARGET multiply_mapfile) +ulp_build_binary(multiply) diff --git a/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_add_main/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_add_main/CMakeLists.txt new file mode 100644 index 00000000000..9d04d40931e --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_add_main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.c" + REQUIRES ulp) diff --git a/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_add_main/main.c b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_add_main/main.c new file mode 100644 index 00000000000..1fbeb23387b --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_add_main/main.c @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +extern int ulp_shared_add_bias(int value); + +int result; + +int main(void) +{ + result = ulp_shared_add_bias(12); + return 0; +} diff --git a/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_multiply_main/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_multiply_main/CMakeLists.txt new file mode 100644 index 00000000000..9d04d40931e --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_multiply_main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.c" + REQUIRES ulp) diff --git a/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_multiply_main/main.c b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_multiply_main/main.c new file mode 100644 index 00000000000..fe8f7a102b0 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_multiply_main/main.c @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +extern int ulp_shared_multiply_by_two(int value); + +int result; + +int main(void) +{ + result = ulp_shared_multiply_by_two(21); + return 0; +} diff --git a/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_shared/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_shared/CMakeLists.txt new file mode 100644 index 00000000000..034f23db6df --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_shared/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS "ulp_shared.c") diff --git a/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_shared/ulp_shared.c b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_shared/ulp_shared.c new file mode 100644 index 00000000000..27823e73f3f --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp/components/ulp_shared/ulp_shared.c @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +int ulp_shared_add_bias(int value) +{ + return value + 10; +} + +int ulp_shared_multiply_by_two(int value) +{ + return value * 2; +} diff --git a/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp_multi_binary_example_main.c b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp_multi_binary_example_main.c new file mode 100644 index 00000000000..8eabd09020e --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/multi_binary/main/ulp_multi_binary_example_main.c @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "ulp_lp_core.h" +#include "add.h" +#include "multiply.h" + +extern const uint8_t add_bin_start[] asm("_binary_add_bin_start"); +extern const uint8_t add_bin_end[] asm("_binary_add_bin_end"); +extern const uint8_t multiply_bin_start[] asm("_binary_multiply_bin_start"); +extern const uint8_t multiply_bin_end[] asm("_binary_multiply_bin_end"); + +static void run_ulp_binary(const uint8_t *binary_start, const uint8_t *binary_end) +{ + ESP_ERROR_CHECK(ulp_lp_core_load_binary(binary_start, binary_end - binary_start)); + + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + ESP_ERROR_CHECK(ulp_lp_core_run(&cfg)); + + /* The LP core halts when the ULP program returns. */ + vTaskDelay(pdMS_TO_TICKS(100)); +} + +void app_main(void) +{ + printf("Running ULP add binary\n"); + run_ulp_binary(add_bin_start, add_bin_end); + printf("ULP add result: %" PRIi32 "\n", (int32_t)ulp_add_result); + + printf("Running ULP multiply binary\n"); + run_ulp_binary(multiply_bin_start, multiply_bin_end); + printf("ULP multiply result: %" PRIi32 "\n", (int32_t)ulp_multiply_result); + + printf("Example finished\n"); +} diff --git a/tools/test_apps/system/ulp/full_subproject/multi_binary/pytest_ulp_full_subproject_multi_binary.py b/tools/test_apps/system/ulp/full_subproject/multi_binary/pytest_ulp_full_subproject_multi_binary.py new file mode 100644 index 00000000000..f8c6fbaf74e --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/multi_binary/pytest_ulp_full_subproject_multi_binary.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@idf_parametrize('target', ['esp32c5', 'esp32c6', 'esp32p4', 'esp32s31'], indirect=['target']) +def test_ulp_full_subproject_multi_binary(dut: Dut) -> None: + dut.expect_exact('Running ULP add binary') + dut.expect_exact('ULP add result: 22') + dut.expect_exact('Running ULP multiply binary') + dut.expect_exact('ULP multiply result: 42') + dut.expect_exact('Example finished') diff --git a/tools/test_apps/system/ulp/full_subproject/multi_binary/sdkconfig.defaults b/tools/test_apps/system/ulp/full_subproject/multi_binary/sdkconfig.defaults new file mode 100644 index 00000000000..42aa360e61d --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/multi_binary/sdkconfig.defaults @@ -0,0 +1,9 @@ +# Enable ULP +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_LP_CORE=y +CONFIG_ULP_COPROC_RESERVE_MEM=4096 +# Set log level to Warning to produce clean output +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_BOOTLOADER_LOG_LEVEL=2 +CONFIG_LOG_DEFAULT_LEVEL_WARN=y +CONFIG_LOG_DEFAULT_LEVEL=2 diff --git a/tools/test_apps/system/ulp/full_subproject/riscv/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/riscv/CMakeLists.txt new file mode 100644 index 00000000000..25eeb0e22e6 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/riscv/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake) + +project(riscv C CXX ASM) + +idf_project_default() diff --git a/tools/test_apps/system/ulp/full_subproject/riscv/README.md b/tools/test_apps/system/ulp/full_subproject/riscv/README.md new file mode 100644 index 00000000000..3aca727b72c --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/riscv/README.md @@ -0,0 +1,23 @@ +| Supported Targets | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | + +# ULP RISC-V Build System Example + +This example demonstrates a separate ULP RISC-V project built with the CMake v2 ULP project helpers. It programs the ULP RISC-V coprocessor to increment a counter while the main CPUs are in deep sleep, then wake the main CPU after a fixed number of timer iterations. + +ULP program written in C can be found in `main/ulp/main/main.c`. The build system compiles and links this program, converts it into binary format, and embeds it into the .rodata section of the ESP-IDF application. + +At runtime, the application running inside the main CPU loads ULP program into the `RTC_SLOW_MEM` memory region using `ulp_riscv_load_binary` function. The main code then configures the ULP wakeup period and starts the coprocessor by using `ulp_riscv_run`. Once the ULP program is started, it runs periodically, with the period set by the main program. The main program enables ULP wakeup source and puts the chip into deep sleep mode. + +On each timer iteration, the ULP program increments `iteration_count`. Once this counter reaches `wakeup_iteration` (set by the main program), the ULP sends a wakeup signal to the main CPU. Upon wakeup, the main program stops the ULP timer, prints the final counter value, and exits the example. + +## Example output + +``` +Not a ULP-RISC-V wakeup, initializing it! +Entering deep sleep + +ULP-RISC-V woke up the main CPU! +ULP-RISC-V iteration count: 5 +Example finished +``` diff --git a/tools/test_apps/system/ulp/full_subproject/riscv/main/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/riscv/main/CMakeLists.txt new file mode 100644 index 00000000000..2ca270c01b5 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/riscv/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "ulp_riscv_example_main.c" + REQUIRES ulp) + +ulp_add_project("ulp_main" "${CMAKE_CURRENT_LIST_DIR}/ulp/" TYPE riscv) diff --git a/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/CMakeLists.txt new file mode 100644 index 00000000000..ec8b058fc29 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.22) + +include(${IDF_PATH}/components/ulp/cmake/ulp_project.cmake) + +project(${IDF_DEFAULT_PROJECT_NAME} C CXX ASM) + +ulp_project_default() diff --git a/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/README.md b/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/README.md new file mode 100644 index 00000000000..af8190f9146 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | diff --git a/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/main/CMakeLists.txt b/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/main/CMakeLists.txt new file mode 100644 index 00000000000..a6e1be0adc6 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/main/CMakeLists.txt @@ -0,0 +1,8 @@ +idf_component_register(SRCS "main.c" + REQUIRES ulp + LDFRAGMENTS "ulp_riscv_sections.lf") + +# Supplementary linker script template processed by ldgen. The mapping marker +# is resolved using ulp_riscv_sections.lf from this component. +target_linker_script(${COMPONENT_NAME} INTERFACE ulp_sections.ld.in + PROCESS "${CMAKE_CURRENT_BINARY_DIR}/ulp_sections.ld") diff --git a/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/main/main.c b/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/main/main.c new file mode 100644 index 00000000000..99e88767e30 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/main/main.c @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* ULP-RISC-V example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. + + This code runs on ULP-RISC-V coprocessor +*/ + +#include +#include +#include "ulp_riscv_utils.h" + +uint32_t iteration_count = 0; +uint32_t wakeup_iteration = 0; +__attribute__((used, section(".ulp_riscv_extra_data"))) uint32_t ldgen_section_marker = 0x12345678; + +int main(void) +{ + iteration_count++; + if (iteration_count >= wakeup_iteration) { + ulp_riscv_wakeup_main_processor(); + } + + /* ulp_riscv_halt() is called automatically when main exits */ + return 0; +} diff --git a/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/main/ulp_riscv_sections.lf b/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/main/ulp_riscv_sections.lf new file mode 100644 index 00000000000..f685f5e0f8c --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/main/ulp_riscv_sections.lf @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 + +[sections:ulp_riscv_extra_data] +entries: + .ulp_riscv_extra_data+ + +[scheme:ulp_riscv_extra] +entries: + ulp_riscv_extra_data -> ulp_riscv_extra_data + +[mapping:main] +archive: libmain.a +entries: + main (ulp_riscv_extra) diff --git a/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/main/ulp_sections.ld.in b/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/main/ulp_sections.ld.in new file mode 100644 index 00000000000..8f609ec6517 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp/main/ulp_sections.ld.in @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +/* + * Supplementary ULP RISC-V linker script template processed by ldgen. + * ldgen replaces mapping[ulp_riscv_extra_data] with entries from the + * component linker fragment file. + */ +SECTIONS +{ + .ulp_riscv_extra_data ALIGN(4) : + { + mapping[ulp_riscv_extra_data] + *(.ulp_riscv_extra_data .ulp_riscv_extra_data.*) + } > ram +} diff --git a/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp_riscv_example_main.c b/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp_riscv_example_main.c new file mode 100644 index 00000000000..62d8018ef06 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/riscv/main/ulp_riscv_example_main.c @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* ULP RISC-V build system example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include "esp_sleep.h" +#include "ulp_riscv.h" +#include "ulp_main.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start"); +extern const uint8_t ulp_main_bin_end[] asm("_binary_ulp_main_bin_end"); + +static void init_ulp_program(void); + +void app_main(void) +{ + /* If user is using USB-serial-jtag then idf monitor needs some time to + * re-connect to the USB port. We wait 1 sec here to allow for it to make the reconnection + * before we print anything. Otherwise the chip will go back to sleep again before the user + * has time to monitor any output. + */ + vTaskDelay(pdMS_TO_TICKS(1000)); + + uint32_t causes = esp_sleep_get_wakeup_causes(); + /* not a wakeup from ULP, load the firmware */ + if (!(causes & BIT(ESP_SLEEP_WAKEUP_ULP))) { + printf("Not a ULP-RISC-V wakeup, initializing it!\n"); + init_ulp_program(); + } else { + ulp_riscv_timer_stop(); + printf("ULP-RISC-V woke up the main CPU!\n"); + printf("ULP-RISC-V iteration count: %" PRIu32 "\n", ulp_iteration_count); + printf("Example finished\n"); + vTaskDelay(portMAX_DELAY); + } + + /* Go back to sleep, only the ULP Risc-V will run */ + printf("Entering deep sleep\n\n"); + + /* Small delay to ensure the messages are printed */ + vTaskDelay(100); + + ESP_ERROR_CHECK( esp_sleep_enable_ulp_wakeup()); + esp_deep_sleep_start(); +} + +static void init_ulp_program(void) +{ + esp_err_t err = ulp_riscv_load_binary(ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start)); + ESP_ERROR_CHECK(err); + + ulp_iteration_count = 0; + ulp_wakeup_iteration = 5; + + /* The first argument is the period index, which is not used by the ULP-RISC-V timer + * The second argument is the period in microseconds, which gives a wakeup time period of: 20ms + */ + ulp_set_wakeup_period(0, 20000); + + /* Start the program */ + err = ulp_riscv_run(); + ESP_ERROR_CHECK(err); +} diff --git a/tools/test_apps/system/ulp/full_subproject/riscv/pytest_ulp_full_subproject_riscv.py b/tools/test_apps/system/ulp/full_subproject/riscv/pytest_ulp_full_subproject_riscv.py new file mode 100644 index 00000000000..1048b6c9390 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/riscv/pytest_ulp_full_subproject_riscv.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@idf_parametrize('target', ['esp32s2', 'esp32s3'], indirect=['target']) +def test_ulp_full_subproject_riscv(dut: Dut) -> None: + dut.expect_exact('Not a ULP-RISC-V wakeup, initializing it!') + dut.expect_exact('Entering deep sleep') + dut.expect_exact('ULP-RISC-V woke up the main CPU!', timeout=5) + count = int(dut.expect(r'ULP-RISC-V iteration count:\s+(\d+)', timeout=5).group(1), 10) + assert count >= 5 + dut.expect_exact('Example finished') diff --git a/tools/test_apps/system/ulp/full_subproject/riscv/sdkconfig.defaults b/tools/test_apps/system/ulp/full_subproject/riscv/sdkconfig.defaults new file mode 100644 index 00000000000..e3745e50576 --- /dev/null +++ b/tools/test_apps/system/ulp/full_subproject/riscv/sdkconfig.defaults @@ -0,0 +1,9 @@ +# Enable ULP +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_RISCV=y +CONFIG_ULP_COPROC_RESERVE_MEM=4096 +# Set log level to Warning to produce clean output +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_BOOTLOADER_LOG_LEVEL=2 +CONFIG_LOG_DEFAULT_LEVEL_WARN=y +CONFIG_LOG_DEFAULT_LEVEL=2 diff --git a/tools/test_build_system/buildv2/test_ulp.py b/tools/test_build_system/buildv2/test_ulp.py new file mode 100644 index 00000000000..9a0198e01a8 --- /dev/null +++ b/tools/test_build_system/buildv2/test_ulp.py @@ -0,0 +1,158 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +from pathlib import Path + +import pytest +from test_build_system_helpers import IdfPyFunc +from test_build_system_helpers import replace_in_file + + +@pytest.mark.test_app_copy('tools/test_apps/system/ulp/full_subproject/lp_core') +def test_ulp_full_project_regenerates_config_from_parent_sdkconfig(idf_py: IdfPyFunc, test_app_copy: Path) -> None: + # Full ULP subprojects use the parent sdkconfig as their source of truth. + # Changing the parent config should regenerate the child's sdkconfig.cmake. + assert test_app_copy.exists() + + sdkconfig = Path('build/sdkconfig') + + idf_py('-DIDF_TARGET=esp32c6', '-DSDKCONFIG=build/sdkconfig', 'build') + ulp_sdkconfig_cmake_files = sorted(Path('build/subprojects').glob('*/config/sdkconfig.cmake')) + assert len(ulp_sdkconfig_cmake_files) == 1 + ulp_sdkconfig_cmake = ulp_sdkconfig_cmake_files[0] + assert 'set(CONFIG_ULP_COPROC_RESERVE_MEM "4096")' in ulp_sdkconfig_cmake.read_text() + + replace_in_file(sdkconfig, 'CONFIG_ULP_COPROC_RESERVE_MEM=4096', 'CONFIG_ULP_COPROC_RESERVE_MEM=8192') + + idf_py('build') + assert 'set(CONFIG_ULP_COPROC_RESERVE_MEM "8192")' in ulp_sdkconfig_cmake.read_text() + + +@pytest.mark.parametrize( + 'target,subproject_dir,linked_artifact,custom_section,mapped_entry,symbol', + ( + pytest.param( + 'esp32', + 'ulp_main', + 'ulp_main.sym', + '.ulp_fsm_extra_data', + '*libmain.a:counter.*(.ulp_fsm_extra_data .ulp_fsm_extra_data.*)', + 'ldgen_section_marker', + marks=[pytest.mark.test_app_copy('tools/test_apps/system/ulp/full_subproject/fsm')], + id='fsm', + ), + pytest.param( + 'esp32s2', + 'ulp_main', + 'ulp_main.map', + '.ulp_riscv_extra_data', + '*libmain.a:main.*(.ulp_riscv_extra_data .ulp_riscv_extra_data.*)', + 'ldgen_section_marker', + marks=[pytest.mark.test_app_copy('tools/test_apps/system/ulp/full_subproject/riscv')], + id='riscv', + ), + pytest.param( + 'esp32c6', + 'ulp_build_system_example', + 'ulp_build_system_example.map', + '.ulp_math_fast', + '*libulp_math.a:ulp_math.*(.ulp_math_fast .ulp_math_fast.*)', + 'ulp_math_multiply', + marks=[pytest.mark.test_app_copy('tools/test_apps/system/ulp/full_subproject/lp_core')], + id='lp_core', + ), + ), +) +def test_ulp_full_project_applies_component_ldgen_mapping( + idf_py: IdfPyFunc, + test_app_copy: Path, + target: str, + subproject_dir: str, + linked_artifact: str, + custom_section: str, + mapped_entry: str, + symbol: str, +) -> None: + # These examples declare component LDFRAGMENTS and target_linker_script() + # templates. Build the ULP child project, then check that ldgen expanded the + # template mapping and the custom section/symbol survived the ULP link. + assert test_app_copy.exists() + idf_py(f'-DIDF_TARGET={target}', '-DSDKCONFIG=build/sdkconfig', 'build') + + ulp_project_dir = Path('build/subprojects') / subproject_dir + processed_scripts = sorted(ulp_project_dir.glob('**/ulp_sections.ld_library_*')) + assert processed_scripts + assert any(mapped_entry in script.read_text(encoding='utf-8') for script in processed_scripts) + + linked_output = (ulp_project_dir / linked_artifact).read_text(encoding='utf-8') + assert custom_section in linked_output + assert symbol in linked_output + + +@pytest.mark.parametrize( + 'target,ulp_project_cmake,subproject_dir,project_replacement,enabled_languages,disabled_languages', + ( + pytest.param( + 'esp32', + 'main/ulp/CMakeLists.txt', + 'ulp_main', + None, + ('ASM',), + ('C', 'CXX'), + marks=[pytest.mark.test_app_copy('tools/test_apps/system/ulp/full_subproject/fsm')], + id='fsm_asm_only', + ), + pytest.param( + 'esp32s2', + 'main/ulp/CMakeLists.txt', + 'ulp_main', + ('project(${IDF_DEFAULT_PROJECT_NAME} C CXX ASM)', 'project(${IDF_DEFAULT_PROJECT_NAME} C ASM)'), + ('C', 'ASM'), + ('CXX',), + marks=[pytest.mark.test_app_copy('tools/test_apps/system/ulp/full_subproject/riscv')], + id='riscv_c_asm', + ), + pytest.param( + 'esp32c6', + 'main/ulp/CMakeLists.txt', + 'ulp_build_system_example', + ('project(${IDF_DEFAULT_PROJECT_NAME} C CXX ASM)', 'project(${IDF_DEFAULT_PROJECT_NAME} C ASM)'), + ('C', 'ASM'), + ('CXX',), + marks=[pytest.mark.test_app_copy('tools/test_apps/system/ulp/full_subproject/lp_core')], + id='lp_core_c_asm', + ), + ), +) +def test_ulp_full_project_respects_child_project_enabled_languages( + idf_py: IdfPyFunc, + test_app_copy: Path, + target: str, + ulp_project_cmake: str, + subproject_dir: str, + project_replacement: tuple[str, str] | None, + enabled_languages: tuple[str, ...], + disabled_languages: tuple[str, ...], +) -> None: + # The ULP child project should use the languages declared by its own + # project() call. FSM can build as an ASM-only project, while RISC-V and + # LP-core ULP projects do not need CXX when their child project only enables + # C and ASM. + if project_replacement is not None: + replace_in_file(test_app_copy / ulp_project_cmake, project_replacement[0], project_replacement[1]) + ulp_project_cmake_path = test_app_copy / ulp_project_cmake + ulp_project_cmake_path.write_text( + ulp_project_cmake_path.read_text(encoding='utf-8') + + '\nget_property(ulp_enabled_languages GLOBAL PROPERTY ENABLED_LANGUAGES)\n' + + 'file(WRITE "${CMAKE_BINARY_DIR}/enabled_languages.txt" "${ulp_enabled_languages}\\n")\n', + encoding='utf-8', + ) + + idf_py(f'-DIDF_TARGET={target}', '-DSDKCONFIG=build/sdkconfig', 'build') + + child_languages = ( + (Path('build/subprojects') / subproject_dir / 'enabled_languages.txt').read_text(encoding='utf-8').strip() + ) + for language in enabled_languages: + assert language in child_languages.split(';') + for language in disabled_languages: + assert language not in child_languages.split(';')