Merge branch 'refactor/enable_fsm_and_riscv_ulp_simultaneously' into 'master'

refactor(ulp): Allow both ULP-FSM and ULP-RISCV to enable at build time

Closes IDFGH-11916

See merge request espressif/esp-idf!45751
This commit is contained in:
Meet Patel
2026-03-17 19:29:43 +05:30
23 changed files with 587 additions and 77 deletions
@@ -18,4 +18,4 @@ set(ulp_exp_dep_srcs "ulp_adc_example_main.c")
#
# 4. Call function to build ULP binary and embed in project using the argument
# values above.
ulp_embed_binary(${ulp_app_name} "${ulp_s_sources}" "${ulp_exp_dep_srcs}")
ulp_embed_binary(${ulp_app_name} "${ulp_s_sources}" "${ulp_exp_dep_srcs}" TYPE fsm)
@@ -0,0 +1,7 @@
examples/system/ulp/ulp_fsm_riscv_combined/counter:
enable:
- if: SOC_ULP_FSM_SUPPORTED == 1 and SOC_RISCV_COPROC_SUPPORTED == 1 and IDF_TARGET in ["esp32s2", "esp32s3"]
temporary: false
reason: Only ESP32-S2 and ESP32-S3 support both FSM and RISC-V ULP coprocessors
depends_components:
- ulp
@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ulp_fsm_riscv_combined)
@@ -0,0 +1,119 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |
# ULP FSM and RISC-V Combined Example
This example demonstrates how to use both ULP FSM and ULP RISC-V coprocessors sequentially in the same application.
## Overview
The example implements a counter that is incremented in three stages:
1. **ULP FSM Stage**: The FSM coprocessor increments the counter from 0 to 100
2. **ULP RISC-V Stage**: The RISC-V coprocessor continues incrementing from 100 to 500
3. **Main CPU Stage**: The main processor completes the count from 500 to 1500
This demonstrates:
- Enabling both `CONFIG_ULP_COPROC_TYPE_FSM` and `CONFIG_ULP_COPROC_TYPE_RISCV` for sequential use
- Using the `TYPE` parameter in `ulp_embed_binary()` to specify which toolchain to use for each ULP program
- Coordinating execution between FSM ULP, RISC-V ULP, and the main CPU
- Sharing data between different coprocessors and the main CPU
## How to Use Example
### Hardware Required
* ESP32-S2 or ESP32-S3 development board
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT build flash monitor
```
(Replace PORT with the name of the serial port to use)
(To exit the serial monitor, type ``Ctrl-]``)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
ULP FSM and RISC-V Combined Example
====================================
Step 1: Starting ULP FSM to count from 0 to 100...
HP core going to sleep, will be woken by ULP FSM when counting completes...
HP core woken up by: ULP
ULP FSM completed. Counter value: 100
Step 2: Starting ULP RISC-V to count from 100 to 500...
HP core going to sleep, will be woken by ULP RISC-V when counting completes...
HP core woken up by: ULP
ULP RISC-V completed. Counter value: 500
Step 3: Main CPU counting from 500 to 1500...
Main CPU completed. Final counter value: 1500
====================================
All stages completed successfully!
FSM: 0 -> 100
RISC-V: 100 -> 500
Main CPU: 500 -> 1500
```
## Troubleshooting
* If you see an error about missing ULP type when building, ensure both `CONFIG_ULP_COPROC_TYPE_FSM` and `CONFIG_ULP_COPROC_TYPE_RISCV` are enabled in menuconfig.
* The `TYPE` parameter in `ulp_embed_binary()` is mandatory when both ULP types are enabled. Check `main/CMakeLists.txt` to see the correct syntax:
```cmake
ulp_embed_binary(ulp_fsm_main "${ulp_fsm_sources}" "" TYPE fsm)
ulp_embed_binary(ulp_riscv_main "${ulp_riscv_sources}" "" TYPE riscv)
```
## Implementation Details
### CMakeLists.txt
The main component's CMakeLists.txt shows how to embed both FSM and RISC-V ULP binaries:
```cmake
# ULP FSM
set(ulp_fsm_app_name ulp_fsm_main)
set(ulp_fsm_sources "ulp_fsm/main.S")
ulp_embed_binary(${ulp_fsm_app_name} "${ulp_fsm_sources}" "" TYPE fsm)
# ULP RISC-V
set(ulp_riscv_app_name ulp_riscv_main)
set(ulp_riscv_sources "ulp_riscv/main.c")
ulp_embed_binary(${ulp_riscv_app_name} "${ulp_riscv_sources}" "" TYPE riscv)
```
The `TYPE` parameter is crucial - it tells the build system which toolchain to use for each ULP program.
### ULP FSM Program
The FSM program (`ulp_fsm/main.S`) is written in assembly and:
- Maintains a counter in RTC slow memory
- Increments it on each wakeup
- Halts when reaching 100
### ULP RISC-V Program
The RISC-V program (`ulp_riscv/main.c`) is written in C and:
- Continues from where FSM left off
- Increments the counter on each wakeup
- Halts when reaching 500
### Main Application
The main application coordinates the three stages:
1. Loads and starts the FSM ULP program
2. Waits for FSM to complete
3. Transfers the counter value and starts the RISC-V ULP program
4. Waits for RISC-V to complete
5. Completes the final counting stage on the main CPU
@@ -0,0 +1,20 @@
idf_component_register(SRCS "ulp_fsm_riscv_combined_main.c"
INCLUDE_DIRS "")
#
# ULP FSM support additions to component CMakeLists.txt.
#
set(ulp_fsm_app_name ulp_fsm_main)
set(ulp_fsm_sources "ulp_fsm/main.S")
ulp_embed_binary(${ulp_fsm_app_name} "${ulp_fsm_sources}" "" TYPE fsm)
#
# ULP RISC-V support additions to component CMakeLists.txt.
#
set(ulp_riscv_app_name ulp_riscv_main)
set(ulp_riscv_sources "ulp_riscv/main.c")
ulp_embed_binary(${ulp_riscv_app_name} "${ulp_riscv_sources}" "" TYPE riscv)
@@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* ULP FSM assembly program to increment counter */
#include "soc/rtc_cntl_reg.h"
#include "soc/soc_ulp.h"
/* Define variables */
.bss
.global fsm_counter
fsm_counter:
.long 0
/* Code section */
.text
.global entry
entry:
/* Load counter address */
move r1, fsm_counter
/* Load current counter value */
ld r0, r1, 0
/* Check if counter >= 100 */
jumpr done, 100, ge
/* Increment counter */
add r0, r0, 1
/* Store incremented value */
st r0, r1, 0
/* Halt until next wakeup */
halt
done:
/* Counter reached 100, wake up the main processor */
wake
/* Halt - task complete */
halt
@@ -0,0 +1,124 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <inttypes.h>
#include "esp_sleep.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/sens_reg.h"
#include "soc/rtc_periph.h"
#include "driver/gpio.h"
#include "driver/rtc_io.h"
#include "ulp.h"
#include "ulp_riscv.h"
#include "ulp_fsm_main.h"
#include "ulp_riscv_main.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.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(void);
void app_main(void)
{
/* Initialize shared counter */
ulp_fsm_counter = 0;
ulp_riscv_counter = 0;
printf("ULP FSM and RISC-V Combined Example\n");
printf("====================================\n\n");
/* Enable ULP wakeup */
ESP_ERROR_CHECK(esp_sleep_enable_ulp_wakeup());
/* Step 1: Start ULP FSM to count from 0 to 100 */
printf("Step 1: Starting ULP FSM to count from 0 to 100...\n");
printf("HP core going to sleep, will be woken by ULP FSM when counting completes...\n");
init_ulp_fsm_program();
/* Enter light sleep - ULP FSM will wake us when done */
esp_light_sleep_start();
/* Woken up by ULP FSM */
uint32_t wakeup_causes = esp_sleep_get_wakeup_causes();
printf("HP core woken up by: %s\n", (wakeup_causes & (1U << ESP_SLEEP_WAKEUP_ULP)) ? "ULP" : "other");
/* Stop FSM ULP */
ulp_timer_stop();
uint32_t fsm_final_count = ulp_fsm_counter; // Save FSM result before RISC-V overwrites it
printf("ULP FSM completed. Counter value: %" PRIu32 "\n\n", fsm_final_count);
/* Small delay to ensure FSM is fully stopped before starting RISC-V */
vTaskDelay(pdMS_TO_TICKS(100));
/* Step 2: Start ULP RISC-V to count from 100 to 500 */
printf("Step 2: Starting ULP RISC-V to count from 100 to 500...\n");
printf("HP core going to sleep, will be woken by ULP RISC-V when counting completes...\n");
init_ulp_riscv_program();
/* Transfer counter value to RISC-V ULP (after loading binary, otherwise it will be overwritten) */
ulp_riscv_counter = fsm_final_count;
/* Enter light sleep - ULP RISC-V will wake us when done */
esp_light_sleep_start();
/* Woken up by ULP RISC-V */
wakeup_causes = esp_sleep_get_wakeup_causes();
printf("HP core woken up by: %s\n", (wakeup_causes & (1U << ESP_SLEEP_WAKEUP_ULP)) ? "ULP" : "other");
/* Stop RISC-V ULP */
ulp_riscv_timer_stop();
uint32_t riscv_final_count = ulp_riscv_counter; // Save RISC-V result
printf("ULP RISC-V completed. Counter value: %" PRIu32 "\n\n", riscv_final_count);
/* Stage 3: Main CPU counts from 500 to 1500 */
printf("Step 3: Main CPU counting from 500 to 1500...\n");
uint32_t counter = riscv_final_count;
while (counter < 1500) {
counter++;
}
printf("Main CPU completed. Final counter value: %" PRIu32 "\n\n", counter);
printf("====================================\n");
printf("All stages completed successfully!\n");
printf("FSM: 0 -> %" PRIu32 "\n", fsm_final_count);
printf("RISC-V: %" PRIu32 " -> %" PRIu32 "\n", fsm_final_count, riscv_final_count);
printf("Main CPU: %" PRIu32 " -> %" PRIu32 "\n", riscv_final_count, counter);
}
static void init_ulp_fsm_program(void)
{
esp_err_t err = ulp_load_binary(0, ulp_fsm_main_bin_start,
(ulp_fsm_main_bin_end - ulp_fsm_main_bin_start) / sizeof(uint32_t));
ESP_ERROR_CHECK(err);
/* Set ULP wake up period to 10ms */
ulp_set_wakeup_period(0, 10000);
/* Start the ULP FSM program */
err = ulp_run(&ulp_entry - RTC_SLOW_MEM);
ESP_ERROR_CHECK(err);
}
static void init_ulp_riscv_program(void)
{
esp_err_t err = ulp_riscv_load_binary(ulp_riscv_main_bin_start,
(ulp_riscv_main_bin_end - ulp_riscv_main_bin_start));
ESP_ERROR_CHECK(err);
/* Set ULP wake up period to 1ms */
ulp_set_wakeup_period(0, 1000);
/* Start the ULP RISC-V program */
err = ulp_riscv_run();
ESP_ERROR_CHECK(err);
}
@@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* ULP RISC-V program to increment counter */
#include <stdint.h>
#include "ulp_riscv.h"
#include "ulp_riscv_utils.h"
uint32_t riscv_counter = 0;
int main(void)
{
/* Check if counting is complete */
if (riscv_counter >= 500) {
/* Wake up the main processor and halt */
ulp_riscv_wakeup_main_processor();
ulp_riscv_halt();
return 0;
}
/* Increment counter once per wakeup */
riscv_counter++;
/* Continue - will be woken up again by timer */
return 0;
}
@@ -0,0 +1,35 @@
#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR 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_fsm_riscv_combined(dut: Dut) -> None:
dut.expect('ULP FSM and RISC-V Combined Example', timeout=10)
# Check Step 1: FSM counting
dut.expect('Step 1: Starting ULP FSM to count from 0 to 100', timeout=5)
dut.expect('HP core going to sleep, will be woken by ULP FSM when counting completes', timeout=5)
dut.expect('HP core woken up by: ULP', timeout=5)
dut.expect('ULP FSM completed. Counter value: 100', timeout=5)
# Check Step 2: RISC-V counting
dut.expect('Step 2: Starting ULP RISC-V to count from 100 to 500', timeout=5)
dut.expect('HP core going to sleep, will be woken by ULP RISC-V when counting completes', timeout=5)
dut.expect('HP core woken up by: ULP', timeout=5)
dut.expect('ULP RISC-V completed. Counter value: 500', timeout=5)
# Check Step 3: Main CPU counting
dut.expect('Step 3: Main CPU counting from 500 to 1500', timeout=5)
dut.expect('Main CPU completed. Final counter value: 1500', timeout=5)
# Verify success message
dut.expect('All stages completed successfully!', timeout=5)
dut.expect('FSM: 0 -> 100', timeout=5)
dut.expect('RISC-V: 100 -> 500', timeout=5)
dut.expect('Main CPU: 500 -> 1500', timeout=5)
@@ -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 Info to see output
CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y
CONFIG_BOOTLOADER_LOG_LEVEL=3
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
CONFIG_LOG_DEFAULT_LEVEL=3