diff --git a/tests/boards/nrf/coresight_stm/CMakeLists.txt b/tests/boards/nrf/coresight_stm/CMakeLists.txt new file mode 100644 index 000000000000000..facbc05da926132 --- /dev/null +++ b/tests/boards/nrf/coresight_stm/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +if(NOT (CONFIG_BOARD_NRF54H20DK_NRF54H20_CPUAPP)) + message(FATAL_ERROR "${BOARD}${BOARD_QUALIFIERS} is not supported for this sample") +endif() + +project(nrf_coresight_stm) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/boards/nrf/coresight_stm/Kconfig.sysbuild b/tests/boards/nrf/coresight_stm/Kconfig.sysbuild new file mode 100644 index 000000000000000..819201cab0881be --- /dev/null +++ b/tests/boards/nrf/coresight_stm/Kconfig.sysbuild @@ -0,0 +1,31 @@ +# Copyright 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +source "share/sysbuild/Kconfig" + +config CORESIGHT_STM_USE_REMOTE_1 + bool "Use first remote core" + default "y" if "$(BOARD)${BOARD_QUALIFIERS}" = "nrf54h20dk/nrf54h20/cpuapp" + +config CORESIGHT_STM_USE_REMOTE_2 + bool "Use second remote core" + +config CORESIGHT_STM_USE_REMOTE_3 + bool "Use third remote core" + + +config REMOTE_1_BOARD +string + default "nrf54h20dk/nrf54h20/cpurad" if "$(BOARD)${BOARD_QUALIFIERS}" = "nrf54h20dk/nrf54h20/cpuapp" + depends on CORESIGHT_STM_USE_REMOTE_1 + +config REMOTE_2_BOARD +string + default "nrf54h20dk/nrf54h20/cpuppr" if "$(BOARD)${BOARD_QUALIFIERS}" = "nrf54h20dk/nrf54h20/cpuapp" + depends on CORESIGHT_STM_USE_REMOTE_2 + +config REMOTE_3_BOARD +string + default "nrf54h20dk/nrf54h20/cpuflpr" if "$(BOARD)${BOARD_QUALIFIERS}" = "nrf54h20dk/nrf54h20/cpuapp" + depends on CORESIGHT_STM_USE_REMOTE_3 diff --git a/tests/boards/nrf/coresight_stm/boards/nrf54h20dk_nrf54h20_cpuapp.overlay b/tests/boards/nrf/coresight_stm/boards/nrf54h20dk_nrf54h20_cpuapp.overlay new file mode 100644 index 000000000000000..5adc64f8771636b --- /dev/null +++ b/tests/boards/nrf/coresight_stm/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor + * SPDX-License-Identifier: Apache-2.0 + */ + +&cpuppr_vpr { + status = "okay"; +}; + +&cpuppr_ram3x_region { + status = "okay"; +}; diff --git a/tests/boards/nrf/coresight_stm/prj.conf b/tests/boards/nrf/coresight_stm/prj.conf new file mode 100644 index 000000000000000..1e935e973c761b4 --- /dev/null +++ b/tests/boards/nrf/coresight_stm/prj.conf @@ -0,0 +1 @@ +CONFIG_LOG=y diff --git a/tests/boards/nrf/coresight_stm/pytest/test_stm.py b/tests/boards/nrf/coresight_stm/pytest/test_stm.py new file mode 100644 index 000000000000000..378defec1aa9604 --- /dev/null +++ b/tests/boards/nrf/coresight_stm/pytest/test_stm.py @@ -0,0 +1,343 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +import logging +import re +import subprocess +from dataclasses import dataclass +from pathlib import Path +from time import sleep +from typing import Optional + +import psutil +from twister_harness import DeviceAdapter + +logger = logging.getLogger(__name__) + + +@dataclass +class STMLimits: + log_0_arg: Optional[float] + log_1_arg: Optional[float] + log_2_arg: Optional[float] + log_3_arg: Optional[float] + log_str: Optional[float] + tracepoint: Optional[float] + tracepoint_d32: Optional[float] + tolerance: Optional[float] + + +# https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/misc/coresight/nrf_etr.c#L102 +STM_M_ID = { + "sec": 33, + "app": 34, + "rad": 35, + "mod": 36, + "sys": 44, + "flpr": 45, + "ppr": 46, + "hw": 128, +} + + +def _check_benchmark_results(output: str, core: str, constraints: STMLimits) -> None: + """ + Use regular expressions to parse 'output' string. + Search for benchmark results related to 'core' coprocessor. + Check that benchamrk results are lower than limits provided in 'constraints'. + """ + + latency_msg_0_str = re.search( + rf"{core}: Timing for log message with 0 arguments: (.+)us", output + ).group(1) + assert latency_msg_0_str is not None, "Timing for log message with 0 arguments NOT found" + # check value + latency_msg_0 = float(latency_msg_0_str) + threshold = (constraints.log_0_arg) * (1 + constraints.tolerance) + assert ( + latency_msg_0 < threshold + ), f"Timing for log message with 0 arguments {latency_msg_0} us exceeds threshold {threshold} us" + + + latency_msg_1_str = re.search( + rf"{core}: Timing for log message with 1 argument: (.+)us", output + ).group(1) + assert latency_msg_1_str is not None, "Timing for log message with 1 argument NOT found" + # check value + latency_msg_1 = float(latency_msg_1_str) + threshold = (constraints.log_1_arg) * (1 + constraints.tolerance) + assert ( + latency_msg_1 < threshold + ), f"Timing for log message with 1 argument {latency_msg_1} us exceeds threshold {threshold} us" + + + latency_msg_2_str = re.search( + rf"{core}: Timing for log message with 2 arguments: (.+)us", output + ).group(1) + assert latency_msg_2_str is not None, "Timing for log message with 2 arguments NOT found" + # check value + latency_msg_2 = float(latency_msg_2_str) + threshold = (constraints.log_2_arg) * (1 + constraints.tolerance) + assert ( + latency_msg_2 < threshold + ), f"Timing for log message with 2 arguments {latency_msg_2} us exceeds threshold {threshold} us" + + + latency_msg_3_str = re.search( + rf"{core}: Timing for log message with 3 arguments: (.+)us", output + ).group(1) + assert latency_msg_3_str is not None, "Timing for log message with 3 arguments NOT found" + # check value + latency_msg_3 = float(latency_msg_3_str) + threshold = (constraints.log_3_arg) * (1 + constraints.tolerance) + assert ( + latency_msg_3 < threshold + ), f"Timing for log message with 3 arguments {latency_msg_3} us exceeds threshold {threshold} us" + + + latency_msg_string_str = re.search( + rf"{core}: Timing for log_message with string: (.+)us", output + ).group(1) + assert latency_msg_string_str is not None, "Timing for log_message with string NOT found" + # check value + latency_msg_string = float(latency_msg_string_str) + threshold = (constraints.log_str) * (1 + constraints.tolerance) + assert ( + latency_msg_string < threshold + ), f"Timing for log message with string {latency_msg_string} us exceeds threshold {threshold} us" + + + latency_tracepoint_str = re.search( + rf"{core}: Timing for tracepoint: (.+)us", output + ).group(1) + assert latency_tracepoint_str is not None, "Timing for tracepoint NOT found" + # check value + latency_tracepoint = float(latency_tracepoint_str) + threshold = (constraints.tracepoint) * (1 + constraints.tolerance) + assert ( + latency_tracepoint < threshold + ), f"Timing for tracepoint {latency_tracepoint} us exceeds threshold {threshold} us" + + + latency_tracepoint_d32_str = re.search( + rf"{core}: Timing for tracepoint_d32: (.+)us", output + ).group(1) + assert latency_tracepoint_d32_str is not None, "Timing for tracepoint_d32 NOT found" + # check value + latency_tracepoint_d32 = float(latency_tracepoint_d32_str) + threshold = (constraints.tracepoint_d32) * (1 + constraints.tolerance) + assert ( + latency_tracepoint_d32 < threshold + ), f"Timing for tracepoint_d32 {latency_tracepoint_d32} us exceeds threshold {threshold} us" + + +# nrfutil starts children processes +# when subprocess.terminate(nrfutil_process) is executed, only the parent terminates +# this blocks serial port for other uses +def _kill(proc): + try: + for child in psutil.Process(proc.pid).children(recursive=True): + child.kill() + proc.kill() + except Exception as e: + logger.exception(f'Could not kill nrfutil - {e}') + + +def _nrfutil_dictionary_from_serial( + dut: DeviceAdapter, + decoded_file_name: str = "output.txt", + collect_time: float = 60.0, +) -> None: + UART_PATH = dut.device_config.serial + UART_BAUDRATE = dut.device_config.baud + dut.close() + + logger.debug(f"Using serial: {UART_PATH}") + + if Path(f"{decoded_file_name}").exists(): + logger.warning("Output file already exists!") + + # prepare database config string + BUILD_DIR = str(dut.device_config.build_dir) + logger.debug(f"{BUILD_DIR=}") + config_str = f"{STM_M_ID['app']}:{BUILD_DIR}/coresight_stm/zephyr/log_dictionary.json" + config_str = config_str + f",{STM_M_ID['rad']}:{BUILD_DIR}/remote_1/zephyr/log_dictionary.json" + config_str = config_str + f",{STM_M_ID['ppr']}:{BUILD_DIR}/remote_2/zephyr/log_dictionary.json" + logger.debug(f"{config_str=}") + + cmd = ( + "nrfutil trace stm --database-config " + f"{config_str} " + f"--input-serialport {UART_PATH} --baudrate {UART_BAUDRATE} " + f"--output-ascii {decoded_file_name}" + ) + try: + # run nrfutil trace in background non-blocking + logger.info(f"Executing:\n{cmd}") + proc = subprocess.Popen(cmd.split(), stdout=subprocess.DEVNULL) + except OSError as exc: + logger.error(f"Unable to start nrfutil trace:\n{cmd}\n{exc}") + try: + proc.wait(collect_time) + except subprocess.TimeoutExpired: + pass + finally: + _kill(proc) + + +def test_STM_decoded(dut: DeviceAdapter): + """ + Run sample.boards.nrf.coresight_stm from samples/boards/nrf/coresight_stm sample. + Both Application and Radio cores use STM for logging. + STM proxy (Application core) decodes logs from all domains. + After reset, coprocessors execute code in expected way and Application core + outputs STM traces on UART port. + """ + app_constraints = STMLimits( + # all values in us + log_0_arg=1.7, + log_1_arg=1.9, + log_2_arg=2.0, + log_3_arg=2.1, + log_str=4.5, + tracepoint=0.5, + tracepoint_d32=0.5, + tolerance=0.5, # 50 % + ) + rad_constraints = STMLimits( + # all values in us + log_0_arg=5.1, + log_1_arg=5.8, + log_2_arg=6.0, + log_3_arg=6.4, + log_str=7.1, + tracepoint=0.5, + tracepoint_d32=0.5, + tolerance=0.5, + ) + ppr_constraints = STMLimits( + # all values in us + log_0_arg=25.4, + log_1_arg=26.3, + log_2_arg=27.0, + log_3_arg=59.4, + log_str=111.8, + tracepoint=1.7, + tracepoint_d32=1.65, + tolerance=0.5, + ) + # nrf54h20 prints immediately after it is flashed. + # Wait a bit to skipp logs from previous test. + sleep(4) + + # Get output from serial port + output = "\n".join(dut.readlines()) + + # check results on Application core + _check_benchmark_results( + output=output, + core='app', + constraints=app_constraints + ) + + # check results on Radio core + _check_benchmark_results( + output=output, + core='rad', + constraints=rad_constraints + ) + + # check results on PPR core + _check_benchmark_results( + output=output, + core='ppr', + constraints=ppr_constraints + ) + + +def test_STM_dictionary_mode(dut: DeviceAdapter): + """ + Run sample.boards.nrf.coresight_stm.dict from samples/boards/nrf/coresight_stm sample. + Both Application and Radio cores use STM for logging. + STM proxy (Application core) prints on serial port raw logs from all domains. + Nrfutil trace is used to decode STM logs. + After reset, coprocessors execute code in expected way and Application core + outputs STM traces on UART port. + """ + BUILD_DIR = str(dut.device_config.build_dir) + test_filename = f"{BUILD_DIR}/coresight_stm_dictionary.txt" + COLLECT_TIMEOUT = 10.0 + app_constraints = STMLimits( + # all values in us + log_0_arg=0.7, + log_1_arg=0.8, + log_2_arg=0.9, + log_3_arg=1.5, + log_str=3.4, + tracepoint=0.5, + tracepoint_d32=0.5, + tolerance=0.5, # 50 % + ) + rad_constraints = STMLimits( + # all values in us + log_0_arg=0.8, + log_1_arg=0.9, + log_2_arg=1.0, + log_3_arg=1.9, + log_str=4.2, + tracepoint=0.5, + tracepoint_d32=0.5, + tolerance=0.5, + ) + + ppr_constraints = STMLimits( + # all values in us + log_0_arg=7.8, + log_1_arg=8.2, + log_2_arg=8.4, + log_3_arg=56.8, + log_str=82.9, + tracepoint=1.6, + tracepoint_d32=1.6, + tolerance=0.5, + ) + + # use nrfutil trace to decode logs + _nrfutil_dictionary_from_serial( + dut=dut, + decoded_file_name=f"{test_filename}", + collect_time=COLLECT_TIMEOUT, + ) + + # read decoded logs + with open(f"{test_filename}", errors="ignore") as decoded_file: + decoded_file_content = decoded_file.read() + + # if nothing in decoded_file, stop test + assert( + len(decoded_file_content) > 0 + ), f"File {test_filename} is empty" + + # check results on Application core + _check_benchmark_results( + output=decoded_file_content, + core='app', + constraints=app_constraints + ) + + # check results on Radio core + _check_benchmark_results( + output=decoded_file_content, + core='rad', + constraints=rad_constraints + ) + + # check results on PPR core + _check_benchmark_results( + output=decoded_file_content, + core='ppr', + constraints=ppr_constraints + ) diff --git a/tests/boards/nrf/coresight_stm/remote/CMakeLists.txt b/tests/boards/nrf/coresight_stm/remote/CMakeLists.txt new file mode 100644 index 000000000000000..a09e7c97631e128 --- /dev/null +++ b/tests/boards/nrf/coresight_stm/remote/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(stm_remote) + +target_sources(app PRIVATE ../src/main.c) diff --git a/tests/boards/nrf/coresight_stm/remote/boards/nrf54h20dk_nrf54h20_cpuppr.conf b/tests/boards/nrf/coresight_stm/remote/boards/nrf54h20dk_nrf54h20_cpuppr.conf new file mode 100644 index 000000000000000..fcc11b72c82d8f7 --- /dev/null +++ b/tests/boards/nrf/coresight_stm/remote/boards/nrf54h20dk_nrf54h20_cpuppr.conf @@ -0,0 +1,6 @@ +CONFIG_LOG=y +CONFIG_SERIAL=n +CONFIG_CONSOLE=n +CONFIG_UART_CONSOLE=n +CONFIG_BOOT_BANNER=n +CONFIG_PRINTK=n diff --git a/tests/boards/nrf/coresight_stm/remote/boards/nrf54h20dk_nrf54h20_cpuppr.overlay b/tests/boards/nrf/coresight_stm/remote/boards/nrf54h20dk_nrf54h20_cpuppr.overlay new file mode 100644 index 000000000000000..4c6b710d1247a5a --- /dev/null +++ b/tests/boards/nrf/coresight_stm/remote/boards/nrf54h20dk_nrf54h20_cpuppr.overlay @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor + * SPDX-License-Identifier: Apache-2.0 + */ + +/* uart135 is enabled by Radio core by default */ +&uart135 { + status = "disabled"; +}; diff --git a/tests/boards/nrf/coresight_stm/remote/prj.conf b/tests/boards/nrf/coresight_stm/remote/prj.conf new file mode 100644 index 000000000000000..1e935e973c761b4 --- /dev/null +++ b/tests/boards/nrf/coresight_stm/remote/prj.conf @@ -0,0 +1 @@ +CONFIG_LOG=y diff --git a/tests/boards/nrf/coresight_stm/src/main.c b/tests/boards/nrf/coresight_stm/src/main.c new file mode 100644 index 000000000000000..221633d8638c427 --- /dev/null +++ b/tests/boards/nrf/coresight_stm/src/main.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#ifdef CONFIG_LOG_FRONTEND_STMESP +#include +#endif + +LOG_MODULE_REGISTER(app); + +#define TEST_LOG(rpt, item) \ + ({ \ + uint32_t key = irq_lock(); \ + uint32_t t = k_cycle_get_32(); \ + for (uint32_t i = 0; i < rpt; i++) { \ + __DEBRACKET item; \ + } \ + t = k_cycle_get_32() - t; \ + irq_unlock(key); \ + k_msleep(400); \ + t; \ + }) + +static char *core_name = "unknown"; + +static void get_core_name(void) +{ + if (strstr(CONFIG_BOARD_TARGET, "cpuapp")) { + core_name = "app"; + } else if (strstr(CONFIG_BOARD_TARGET, "cpurad")) { + core_name = "rad"; + } else if (strstr(CONFIG_BOARD_TARGET, "cpuppr")) { + core_name = "ppr"; + } else if (strstr(CONFIG_BOARD_TARGET, "cpuflpr")) { + core_name = "flpr"; + } +} + +static uint32_t t_to_ns(uint32_t t, uint32_t rpt, uint32_t freq) +{ + return (uint32_t)(((uint64_t)t * 1000000000) / (uint64_t)(rpt * freq)); +} + +static void timing_report(uint32_t t, uint32_t rpt, const char *str) +{ + uint32_t ns = t_to_ns(t, rpt, CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC); + + LOG_RAW("%s: Timing for %s: %d.%dus\n", core_name, str, ns / 1000, (ns % 1000) / 10); +} + +int main(void) +{ + uint32_t t; + uint32_t delta; + uint32_t rpt = 10; + uint32_t t0, t1, t2, t3, t_s; + char str[] = "test string"; + + get_core_name(); + + t = k_cycle_get_32(); + delta = k_cycle_get_32() - t; + + t0 = TEST_LOG(rpt, (LOG_INF("test no arguments"))); + t0 -= delta; + + t1 = TEST_LOG(rpt, (LOG_INF("test with one argument %d", 100))); + t1 -= delta; + + t2 = TEST_LOG(rpt, (LOG_INF("test with two arguments %d %d", 100, 10))); + t2 -= delta; + + t3 = TEST_LOG(rpt, (LOG_INF("test with three arguments %d %d %d", 100, 10, 1))); + t3 -= delta; + + t_s = TEST_LOG(rpt, (LOG_INF("test with string %s", str))); + t_s -= delta; + +#ifdef CONFIG_LOG_FRONTEND_STMESP + uint32_t rpt_tp = 20; + uint32_t t_tp, t_tpd; + + t_tp = TEST_LOG(rpt_tp, (log_frontend_stmesp_tp(5))); + t_tp -= delta; + + t_tpd = TEST_LOG(rpt_tp, (log_frontend_stmesp_tp_d32(6, 10))); + t_tpd -= delta; +#endif + + timing_report(t0, rpt, "log message with 0 arguments"); + timing_report(t1, rpt, "log message with 1 argument"); + timing_report(t2, rpt, "log message with 2 arguments"); + timing_report(t3, rpt, "log message with 3 arguments"); + timing_report(t_s, rpt, "log_message with string"); + +#ifdef CONFIG_LOG_FRONTEND_STMESP + timing_report(t_tp, rpt_tp, "tracepoint"); + timing_report(t_tpd, rpt_tp, "tracepoint_d32"); +#endif + + return 0; +} diff --git a/tests/boards/nrf/coresight_stm/sysbuild.cmake b/tests/boards/nrf/coresight_stm/sysbuild.cmake new file mode 100644 index 000000000000000..54aa985af747dd8 --- /dev/null +++ b/tests/boards/nrf/coresight_stm/sysbuild.cmake @@ -0,0 +1,32 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +if("${SB_CONFIG_REMOTE_1_BOARD}" STREQUAL "") + message(FATAL_ERROR + "Target ${BOARD} not supported for this sample. " + "There is no remote board selected in Kconfig.sysbuild") +endif() + +ExternalZephyrProject_Add( + APPLICATION remote_1 + SOURCE_DIR ${APP_DIR}/remote + BOARD ${SB_CONFIG_REMOTE_1_BOARD} +) + +# There are sample configurations which do not use second remote board. +if(NOT "${SB_CONFIG_REMOTE_2_BOARD}" STREQUAL "") + ExternalZephyrProject_Add( + APPLICATION remote_2 + SOURCE_DIR ${APP_DIR}/remote + BOARD ${SB_CONFIG_REMOTE_2_BOARD} + ) +endif() + +# There are sample configurations which do not use third remote board. +if(NOT "${SB_CONFIG_REMOTE_3_BOARD}" STREQUAL "") + ExternalZephyrProject_Add( + APPLICATION remote_3 + SOURCE_DIR ${APP_DIR}/remote + BOARD ${SB_CONFIG_REMOTE_3_BOARD} + ) +endif() diff --git a/tests/boards/nrf/coresight_stm/testcase.yaml b/tests/boards/nrf/coresight_stm/testcase.yaml new file mode 100644 index 000000000000000..3e10f39ceec2e1f --- /dev/null +++ b/tests/boards/nrf/coresight_stm/testcase.yaml @@ -0,0 +1,69 @@ +sample: + name: Logging using Coresight STM on nrf54h20 + +common: + tags: stm + sysbuild: true + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + integration_platforms: + - nrf54h20dk/nrf54h20/cpuapp + +tests: + boards.nrf.coresight_stm.dict: + filter: not CONFIG_COVERAGE + harness: pytest + harness_config: + pytest_dut_scope: session + pytest_root: + - "pytest/test_stm.py::test_STM_dictionary_mode" + required_snippets: + - nordic-log-stm-dict + extra_args: + - SB_CONFIG_CORESIGHT_STM_USE_REMOTE_2=y + - SB_CONFIG_CORESIGHT_STM_USE_REMOTE_3=n + + boards.nrf.coresight_stm: + filter: not CONFIG_COVERAGE + harness: pytest + harness_config: + pytest_dut_scope: session + pytest_root: + - "pytest/test_stm.py::test_STM_decoded" + required_snippets: + - nordic-log-stm + extra_args: + - SB_CONFIG_CORESIGHT_STM_USE_REMOTE_2=y + - SB_CONFIG_CORESIGHT_STM_USE_REMOTE_3=n + + boards.nrf.coresight_stm.local_uart: + harness: console + harness_config: + type: multi_line + ordered: true + regex: + - "Timing for log message with 0 arguments:" + - "Timing for log message with 1 argument:" + - "Timing for log message with 2 arguments:" + - "Timing for log message with 3 arguments:" + - "Timing for log_message with string:" + + boards.nrf.coresight_stm.coverage: + filter: CONFIG_COVERAGE + harness: console + harness_config: + type: one_line + regex: + - "GCOV_COVERAGE_DUMP_END" + required_snippets: + - nordic-log-stm + + boards.nrf.coresight_stm.dict.coverage: + filter: CONFIG_COVERAGE + harness: console + harness_config: + type: one_line + regex: + - "GCOV_COVERAGE_DUMP_END" + required_snippets: + - nordic-log-stm-dict