Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(esp_ulp): Add support for multiple ULP program embedding without name collision (IDFGH-14151) #14951

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/ulp/cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ include(IDFULPProject)

ulp_apply_default_options(${ULP_APP_NAME})
ulp_apply_default_sources(${ULP_APP_NAME})
ulp_add_build_binary_targets(${ULP_APP_NAME})
ulp_add_build_binary_targets(${ULP_APP_NAME} ${ULP_VAR_PREFIX})
7 changes: 3 additions & 4 deletions components/ulp/cmake/IDFULPProject.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ function(ulp_apply_default_sources ulp_app_name)
endif()
endfunction()

function(ulp_add_build_binary_targets ulp_app_name)
function(ulp_add_build_binary_targets ulp_app_name prefix)

if(CONFIG_ULP_COPROC_TYPE_LP_CORE)
set(ULP_BASE_ADDR "0x0")
Expand All @@ -190,7 +190,7 @@ function(ulp_add_build_binary_targets ulp_app_name)

# Dump the list of global symbols in a convenient format
add_custom_command(OUTPUT ${ULP_APP_NAME}.sym
COMMAND ${CMAKE_NM} -f posix -g $<TARGET_FILE:${ulp_app_name}> > ${ulp_app_name}.sym
COMMAND ${CMAKE_READELF} -sW $<TARGET_FILE:${ulp_app_name}> > ${ulp_app_name}.sym
DEPENDS ${ulp_app_name}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

Expand All @@ -201,15 +201,14 @@ function(ulp_add_build_binary_targets ulp_app_name)
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

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}
COMMAND ${ULP_MAP_GEN} -s ${ulp_app_name}.sym -o ${ulp_app_name} --base ${ULP_BASE_ADDR} --prefix ${prefix}
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})

Expand Down
97 changes: 61 additions & 36 deletions components/ulp/esp32ulp_mapgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,62 +11,87 @@
import os
import textwrap
import typing
import re

UTIL = os.path.basename(__file__)


def gen_ld_h_from_sym(f_sym: typing.TextIO, f_ld: typing.TextIO, f_h: typing.TextIO, base_addr: int) -> None:
f_ld.write(textwrap.dedent(
f"""
/* ULP variable definitions for the linker.
* This file is generated automatically by {UTIL} utility.
*/
"""
))
f_h.write(textwrap.dedent(
f"""
/* ULP variable definitions for the compiler.
* This file is generated automatically by {UTIL} utility.
*/
#pragma once
#ifdef __cplusplus
extern "C" {{
#endif
"""
))
def name_mangling(name: str) -> str:
# Simple and dumb name mangling for namespaced name following GCC algorithm
ns, n = name.split('::')
return '_ZN{0}{1}{2}{3}E'.format(len(ns), ns, len(n), n)

def gen_h_from_sym(f_sym: typing.TextIO, f_h: typing.TextIO, base_addr: int, prefix: str) -> None:
cpp_mode = False
if '::' in prefix:
# C++ mode, let's avoid the extern "C" type and instead use namespace
f_h.write(textwrap.dedent(
f"""
/* ULP variable definitions for the compiler.
* This file is generated automatically by {UTIL} utility.
*/
#pragma once
"""
))
f_h.write("namespace {0} {{\n".format(prefix.split('::')[0]))
cpp_mode = True
else:
f_h.write(textwrap.dedent(
f"""
/* ULP variable definitions for the compiler.
* This file is generated automatically by {UTIL} utility.
*/
#pragma once
#ifdef __cplusplus
extern "C" {{
#endif
"""
))

expr = re.compile("^\\s*\\d+: ([a-f0-9]{8})\\s+(\\d+) OBJECT\\s+GLOBAL\\s+DEFAULT\\s+[^ ]+ (.*)$")
for line in f_sym:
# NM "posix" format output has the following structure:
# symbol_name symbol_type addr_hex [size_hex]
parts = line.split()
name = parts[0]
addr = int(parts[2], 16) + base_addr
f_h.write('extern uint32_t ulp_{0};\n'.format(name))
f_ld.write('PROVIDE ( ulp_{0} = 0x{1:08x} );\n'.format(name, addr))
# readelf format output has the following structure:
# index: addr_hex size TYPE SCOPE DEFAULT junk symbol_name
# So match the line with a regular expression to parse it first
groups = expr.match(line)
if groups is None: # Ignore non global or non object
continue
addr = int(groups.group(1), 16) + base_addr
size = int(groups.group(2))
name = groups.group(3)
if not cpp_mode:
name = prefix + name
f_h.write('extern uint32_t {0}{1};'.format(name, '[{0}]'.format(int(size/4)) if size > 4 else ''))
# Then set the address to find the variable
f_h.write(' asm(".equ {0}, 0x{1:08X}");\n'.format(name_mangling(prefix+name) if cpp_mode else name, addr))

f_h.write(textwrap.dedent(
"""
#ifdef __cplusplus
}
#endif
"""
))
if cpp_mode:
f_h.write("}\n")
else:
f_h.write(textwrap.dedent(
"""
#ifdef __cplusplus
}
#endif
"""
))


def main() -> None:
description = ('This application generates .h and .ld files for symbols defined in input file. '
'The input symbols file can be generated using nm utility like this: '
'<PREFIX>nm -g -f posix <elf_file> > <symbols_file>')
'<PREFIX>readelf -sW <elf_file> > <symbols_file>')

parser = argparse.ArgumentParser(description=description)
parser.add_argument('-s', '--symfile', required=True, help='symbols file name', metavar='SYMFILE', type=argparse.FileType('r'))
parser.add_argument('-o', '--outputfile', required=True, help='destination .h and .ld files name prefix', metavar='OUTFILE')
parser.add_argument('--base-addr', required=True, help='base address of the ULP memory, to be added to each symbol')
parser.add_argument('-p', '--prefix', required=False, help='prefix for generated header file', default='ulp_')

args = parser.parse_args()

with open(args.outputfile + '.h', 'w') as f_h, open(args.outputfile + '.ld', 'w') as f_ld:
gen_ld_h_from_sym(args.symfile, f_ld, f_h, int(args.base_addr, 0))
with open(args.outputfile + '.h', 'w') as f_h:
gen_h_from_sym(args.symfile, f_h, int(args.base_addr, 0), args.prefix)


if __name__ == '__main__':
Expand Down
12 changes: 8 additions & 4 deletions components/ulp/project_include.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# Create ULP binary and embed into the application.

function(__setup_ulp_project app_name project_path s_sources exp_dep_srcs)
function(__setup_ulp_project app_name project_path prefix s_sources exp_dep_srcs)

if(NOT CMAKE_BUILD_EARLY_EXPANSION)
spaces2list(s_sources)
Expand Down Expand Up @@ -60,6 +60,7 @@ function(__setup_ulp_project app_name project_path s_sources exp_dep_srcs)
-DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FLAG}
-DULP_S_SOURCES=$<TARGET_PROPERTY:${app_name},ULP_SOURCES>
-DULP_APP_NAME=${app_name}
-DULP_VAR_PREFIX=${prefix}
-DCOMPONENT_DIR=${COMPONENT_DIR}
-DCOMPONENT_INCLUDES=$<TARGET_PROPERTY:${COMPONENT_TARGET},INTERFACE_INCLUDE_DIRECTORIES>
-DIDF_TARGET=${idf_target}
Expand All @@ -86,15 +87,18 @@ function(__setup_ulp_project app_name project_path s_sources exp_dep_srcs)

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)
endif()
endfunction()

function(ulp_embed_binary app_name s_sources exp_dep_srcs)
__setup_ulp_project("${app_name}" "${idf_path}/components/ulp/cmake" "${s_sources}" "${exp_dep_srcs}")
__setup_ulp_project("${app_name}" "${idf_path}/components/ulp/cmake" "ulp_" "${s_sources}" "${exp_dep_srcs}")
endfunction()

function(ulp_embed_binary_prefix app_name prefix s_sources exp_dep_srcs)
__setup_ulp_project("${app_name}" "${idf_path}/components/ulp/cmake" "${prefix}" "${s_sources}" "${exp_dep_srcs}")
endfunction()

function(ulp_add_project app_name project_path)
__setup_ulp_project("${app_name}" "${project_path}" "" "")
__setup_ulp_project("${app_name}" "${project_path}" "ulp_" "" "")
endfunction()
24 changes: 16 additions & 8 deletions docs/en/api-reference/system/ulp-lp-core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,21 @@ Using ``ulp_embed_binary``
ulp_embed_binary(${ulp_app_name} "${ulp_sources}" "${ulp_exp_dep_srcs}")

The first argument to ``ulp_embed_binary`` specifies the ULP binary name. The name specified here is also used by other generated artifacts such as the ELF file, map file, header file, and linker export file. The second argument specifies the ULP source files. Finally, the third argument specifies the list of component source files which include the header file to be generated. This list is needed to build the dependencies correctly and ensure that the generated header file is created before any of these files are compiled. See the section below for the concept of generated header files for ULP applications.
Variables in the ULP code will be prefixed with ``ulp_`` (default value) in this generated header file.

If you need to embed multiple ULP program, in order to avoid conflicting variable names, it's possible to modify the prefix like this:

.. code-block:: cmake

idf_component_register()

set(ulp_app_name ulp_${COMPONENT_NAME})
set(ulp_sources "ulp/ulp_c_source_file.c" "ulp/ulp_assembly_source_file.S")
set(ulp_exp_dep_srcs "ulp_c_source_file.c")

ulp_embed_binary_prefix(${ulp_app_name} "ULP::" "${ulp_sources}" "${ulp_exp_dep_srcs}")

The additional argument can be a C style prefix (like ``ulp2_``) or a C++ style prefix (like ``ULP::``).

Using a Custom CMake Project
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -140,13 +154,7 @@ The header file contains the declaration of the symbol:

extern uint32_t ulp_measurement_count;

Note that all symbols (variables, arrays, functions) are declared as ``uint32_t``. For functions and arrays, take the address of the symbol and cast it to the appropriate type.

The generated linker script file defines the locations of symbols in LP_MEM:

.. code-block:: none

PROVIDE ( ulp_measurement_count = 0x50000060 );
Note that all symbols (variables, functions) are declared as ``uint32_t``. Array are declared as ``uint32_t [SIZE]``. For functions, take the address of the symbol and cast it to the appropriate type.

To access the ULP LP core program variables from the main program, the generated header file should be included using an ``include`` statement. This allows the ULP LP core program variables to be accessed as regular variables.

Expand All @@ -161,7 +169,7 @@ To access the ULP LP core program variables from the main program, the generated
.. note::

Variables declared in the global scope of the LP core program reside in either the ``.bss`` or ``.data`` section of the binary. These sections are initialized when the LP core binary is loaded and executed. Accessing these variables from the main program on the HP-Core before the first LP core run may result in undefined behavior.

The ``ulp_`` prefix is the default value. You can specify the prefix to use with ``ulp_embed_binary_prefix`` to avoid name collusions for multiple ULP program.

Starting the ULP LP Core Program
--------------------------------
Expand Down
22 changes: 16 additions & 6 deletions docs/en/api-reference/system/ulp-risc-v.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,21 @@ Using ``ulp_embed_binary``
ulp_embed_binary(${ulp_app_name} "${ulp_sources}" "${ulp_exp_dep_srcs}")

The first argument to ``ulp_embed_binary`` specifies the ULP binary name. The name specified here is also used by other generated artifacts such as the ELF file, map file, header file, and linker export file. The second argument specifies the ULP source files. Finally, the third argument specifies the list of component source files which include the header file to be generated. This list is needed to build the dependencies correctly and ensure that the generated header file is created before any of these files are compiled. See the section below for the concept of generated header files for ULP applications.
Variables in the ULP code will be prefixed with ``ulp_`` (default value) in this generated header file.

If you need to embed multiple ULP program, in order to avoid conflicting variable names, it's possible to modify the prefix like this:

.. code-block:: cmake

idf_component_register()

set(ulp_app_name ulp_${COMPONENT_NAME})
set(ulp_sources "ulp/ulp_c_source_file.c" "ulp/ulp_assembly_source_file.S")
set(ulp_exp_dep_srcs "ulp_c_source_file.c")

ulp_embed_binary_prefix(${ulp_app_name} "ULP::" "${ulp_sources}" "${ulp_exp_dep_srcs}")

The additional argument can be a C style prefix (like ``ulp2_``) or a C++ style prefix (like ``ULP::``).

Using a Custom CMake Project
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -143,11 +157,7 @@ The header file contains the declaration of the symbol:

extern uint32_t ulp_measurement_count;

Note that all symbols (variables, arrays, functions) are declared as ``uint32_t``. For functions and arrays, take the address of the symbol and cast it to the appropriate type.

The generated linker script file defines the locations of symbols in RTC_SLOW_MEM::

PROVIDE ( ulp_measurement_count = 0x50000060 );
Note that all symbols (variables, functions) are declared as ``uint32_t``. Array are declared as ``uint32_t [SIZE]``. For functions, take the address of the symbol and cast it to the appropriate type.

To access the ULP RISC-V program variables from the main program, the generated header file should be included using an ``include`` statement. This will allow the ULP RISC-V program variables to be accessed as regular variables.

Expand All @@ -162,7 +172,7 @@ To access the ULP RISC-V program variables from the main program, the generated
.. note::

Variables declared in the global scope of the ULP RISC-V program reside in either the ``.bss`` or ``.data`` section of the binary. These sections are initialized when the ULP RISC-V binary is loaded and executed. Accessing these variables from the main program on the main CPU before the first ULP RISC-V run may result in undefined behavior.

The ``ulp_`` prefix is the default value. You can specify the prefix to use with ``ulp_embed_binary_prefix`` to avoid name collusions for multiple ULP program.

Mutual Exclusion
^^^^^^^^^^^^^^^^
Expand Down
15 changes: 15 additions & 0 deletions docs/en/api-reference/system/ulp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ To compile the ULP FSM code as part of the component, the following steps must b
ulp_embed_binary(${ulp_app_name} "${ulp_s_sources}" "${ulp_exp_dep_srcs}")

The first argument to ``ulp_embed_binary`` specifies the ULP FSM binary name. The name specified here will also be used by other generated artifacts such as the ELF file, map file, header file and linker export file. The second argument specifies the ULP FSM assembly source files. Finally, the third argument specifies the list of component source files which include the header file to be generated. This list is needed to build the dependencies correctly and ensure that the generated header file will be created before any of these files are compiled. See the section below for the concept of generated header files for ULP applications.
Variables in the ULP code will be prefixed with ``ulp_`` (default value) in this generated header file.

If you need to embed multiple ULP program, in order to avoid conflicting variable names, it's possible to modify the prefix like this:

.. code-block:: cmake

idf_component_register()

set(ulp_app_name ulp_${COMPONENT_NAME})
set(ulp_sources "ulp/ulp_c_source_file.c" "ulp/ulp_assembly_source_file.S")
set(ulp_exp_dep_srcs "ulp_c_source_file.c")

ulp_embed_binary_prefix(${ulp_app_name} "ULP::" "${ulp_sources}" "${ulp_exp_dep_srcs}")

The additional argument can be a C style prefix (like ``ulp2_``) or a C++ style prefix (like ``ULP::``).

3. Build the application as usual (e.g., ``idf.py app``).

Expand Down
Loading