From 11b0dab8720336cb8a9e1363463290ca2b8ec71b Mon Sep 17 00:00:00 2001 From: Victor Jung Date: Wed, 21 Jan 2026 14:46:40 +0100 Subject: [PATCH 01/12] Add option to deploy on the board for the GAP9 platform --- CMakeLists.txt | 12 ++- DeeployTest/Platforms/GAP9/CMakeLists.txt | 1 + DeeployTest/Platforms/GAP9/sdk_board.config | 34 +++++++++ .../GAP9/{sdk.config => sdk_gvsoc.config} | 3 +- DeeployTest/testUtils/testRunner.py | 6 +- cmake/gap9/gap9_board.cmake | 76 +++++++++++++++++++ cmake/gap9/gap9_gvsoc.cmake | 4 +- 7 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 DeeployTest/Platforms/GAP9/sdk_board.config rename DeeployTest/Platforms/GAP9/{sdk.config => sdk_gvsoc.config} (92%) create mode 100644 cmake/gap9/gap9_board.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c8a024c15..aa36c60ee8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,16 @@ elseif(platform STREQUAL PULPOpen) elseif(platform STREQUAL GAP9) message(STATUS "Building for platform 'GAP9'") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) - set(ENV{KCONFIG_CONFIG} DeeployTest/Platforms/GAP9/sdk.config) + + # Select SDK config based on simulator type + if(SIMULATOR STREQUAL "board") + set(ENV{KCONFIG_CONFIG} DeeployTest/Platforms/GAP9/sdk_board.config) + message(STATUS "[GAP9] Using board SDK configuration") + else() + set(ENV{KCONFIG_CONFIG} DeeployTest/Platforms/GAP9/sdk_gvsoc.config) + message(STATUS "[GAP9] Using GVSoC SDK configuration") + endif() + include($ENV{GAP_SDK_HOME}/utils/cmake/setup.cmake) elseif(platform STREQUAL Generic) message(STATUS "Building for platform 'Generic'") @@ -225,6 +234,7 @@ endif() if(platform STREQUAL GAP9) project(${TESTNAME} LANGUAGES C ASM) include(${CMAKE_CURRENT_LIST_DIR}/cmake/gap9/gap9_gvsoc.cmake) + include(${CMAKE_CURRENT_LIST_DIR}/cmake/gap9/gap9_board.cmake) add_compile_options( -Wno-error=unknown-pragmas ) diff --git a/DeeployTest/Platforms/GAP9/CMakeLists.txt b/DeeployTest/Platforms/GAP9/CMakeLists.txt index db06a4e38f..2f36cdedff 100644 --- a/DeeployTest/Platforms/GAP9/CMakeLists.txt +++ b/DeeployTest/Platforms/GAP9/CMakeLists.txt @@ -20,6 +20,7 @@ target_include_directories(${ProjectId} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/inc) target_link_libraries(${ProjectId} PRIVATE network deeploylib) target_compile_options(${ProjectId} INTERFACE network) add_gvsoc_emulation(${ProjectId} "gap9.evk") +add_board_deployment(${ProjectId} "gap9.evk") # RW: Waive sign comparison warnings from pulp_nn_utils.h target_compile_options(network PRIVATE diff --git a/DeeployTest/Platforms/GAP9/sdk_board.config b/DeeployTest/Platforms/GAP9/sdk_board.config new file mode 100644 index 0000000000..9629eb9f66 --- /dev/null +++ b/DeeployTest/Platforms/GAP9/sdk_board.config @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: 2025 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_BOARD_GAP9MOD_V1_0_B=y +CONFIG_BOARD_GAP9EVK_V1_3=y + +CONFIG_DRIVER_CLUSTER=y +CONFIG_DRIVER_CLUSTER_CONF_PROPERTY_ICACHE_CONF=0x7FF + +CONFIG_DRIVER_TYPE_FLASH=y +CONFIG_DRIVER_TYPE_RAM=y + +CONFIG_DRIVER_MRAM=y +CONFIG_READFS_FLASH_TYPE_OSPI=y + +CONFIG_DRIVER_READFS=y +# CONFIG_READFS_FLASH_TYPE_MRAM=y + +CONFIG_DRIVER_APS256XXN=y +CONFIG_DRIVER_RAM_API=y + +CONFIG_ENABLE_LIBMATH=y + +# OS float printf +CONFIG_IO_PRINTF_FLOAT_ENABLE=y +CONFIG_IO_PRINTF_FLOAT_EXPONENT_ENABLE=y + +# CONFIG_PLATFORM_GVSOC=y +CONFIG_PLATFORM_BOARD=y +# CONFIG_DRIVER_CLUSTERDECOMPRESSOR=n + +#CONFIG_CL_MASTER_CORE_STACK_SIZE=14000 +#CONFIG_CL_SLAVE_CORE_STACK_SIZE=1000 diff --git a/DeeployTest/Platforms/GAP9/sdk.config b/DeeployTest/Platforms/GAP9/sdk_gvsoc.config similarity index 92% rename from DeeployTest/Platforms/GAP9/sdk.config rename to DeeployTest/Platforms/GAP9/sdk_gvsoc.config index 8d6fb6b178..2f5bdf053c 100644 --- a/DeeployTest/Platforms/GAP9/sdk.config +++ b/DeeployTest/Platforms/GAP9/sdk_gvsoc.config @@ -27,9 +27,10 @@ CONFIG_IO_PRINTF_FLOAT_ENABLE=y CONFIG_IO_PRINTF_FLOAT_EXPONENT_ENABLE=y CONFIG_PLATFORM_GVSOC=y +# CONFIG_PLATFORM_BOARD=y # CONFIG_DRIVER_CLUSTERDECOMPRESSOR=n # GAP9 cluster stack size configuration # Uncomment and adjust these values if you need to modify stack sizes: # CONFIG_CL_MASTER_CORE_STACK_SIZE=14000 -# CONFIG_CL_SLAVE_CORE_STACK_SIZE=1000 \ No newline at end of file +# CONFIG_CL_SLAVE_CORE_STACK_SIZE=1000 diff --git a/DeeployTest/testUtils/testRunner.py b/DeeployTest/testUtils/testRunner.py index 9578c2f26c..71c35c5b55 100644 --- a/DeeployTest/testUtils/testRunner.py +++ b/DeeployTest/testUtils/testRunner.py @@ -298,7 +298,7 @@ def __init__(self, gen_args: str = "", cmake_args: str = ""): - if simulator not in ['gvsoc', 'banshee', 'qemu', 'vsim', 'vsim.gui', 'host', 'none']: + if simulator not in ['gvsoc', 'banshee', 'qemu', 'vsim', 'vsim.gui', 'host', 'board' ,'none']: raise ValueError( f"Invalid emulator {simulator} (valid options are 'gvsoc', 'banshee', 'qemu', 'vsim', 'vsim.gui', 'host', 'none')!" ) @@ -381,7 +381,7 @@ def configure_cmake_project(self): else: self.cmake_args += " -D gvsoc_simulation=OFF" - command = f"$CMAKE -D TOOLCHAIN={self._args.toolchain} -D GVSOC_INSTALL_DIR={self._dir_gvsoc} -D TOOLCHAIN_INSTALL_DIR={self._dir_toolchain} -D GENERATED_SOURCE={self._dir_gen} -D platform={self._platform} {self.cmake_args} -B {self._dir_build} -D TESTNAME={self._name_test} .." + command = f"$CMAKE -D TOOLCHAIN={self._args.toolchain} -D GVSOC_INSTALL_DIR={self._dir_gvsoc} -D TOOLCHAIN_INSTALL_DIR={self._dir_toolchain} -D GENERATED_SOURCE={self._dir_gen} -D platform={self._platform} -D SIMULATOR={self._simulator} {self.cmake_args} -B {self._dir_build} -D TESTNAME={self._name_test} .." if self._args.verbose >= 3: command = "VERBOSE=1 " + command + " --log-level debug" @@ -414,6 +414,8 @@ def run_simulation(self, out_file = 'out.txt'): if self._simulator == 'host': command = f"{self._dir_build}/bin/{self._name_test}" + elif self._simulator == 'board': + command = f"$CMAKE --build {self._dir_build} --target board_{self._name_test}" else: command = f"$CMAKE --build {self._dir_build} --target {self._simulator}_{self._name_test}" diff --git a/cmake/gap9/gap9_board.cmake b/cmake/gap9/gap9_board.cmake new file mode 100644 index 0000000000..8c34398abf --- /dev/null +++ b/cmake/gap9/gap9_board.cmake @@ -0,0 +1,76 @@ +# SPDX-FileCopyrightText: 2025 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + + +macro(add_board_deployment name target) + + if(NOT DEFINED GVSOC_INSTALL_DIR) + message(FATAL_ERROR "Environment variable GVSOC_INSTALL_DIR not set") + endif() + + message(STATUS "[Deeploy GAP9] Running on Board") + + set(BOARD_WORKDIR ${CMAKE_BINARY_DIR}/board_workdir) + set(DEEPLOY_BINARY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${name}") + set(GAP9_SDK_HOME $ENV{GAP_SDK_HOME}) + set(GAPY "${GAP9_SDK_HOME}/utils/gapy_v2/bin/gapy") + set(FLASH_LAYOUT "${GAP9_SDK_HOME}/utils/layouts/default_layout_multi_readfs.json") + set(FSBL_BINARY "${GAP9_SDK_HOME}/install/target/bin/fsbl") + set(SSBL_BINARY "${GAP9_SDK_HOME}/install/target/bin/ssbl") + + make_directory(${BOARD_WORKDIR}) + + if(NOT DEFINED GAP9_SDK_HOME) + message(FATAL_ERROR "Environment variable GAP_SDK_HOME not set") + endif() + + # Command to run on board + set(GAPY_CMD + ${GAPY} + --target=gap9.evk + --platform=board + --target-property=boot.flash_device=mram + --target-property=boot.mode=flash + --target-dir=${GAP9_SDK_HOME}/utils/gapy_v2/targets + --openocd-cable=${GAP9_SDK_HOME}/utils/openocd_tools/tcl/gapuino_ftdi.cfg + --openocd-script=${GAP9_SDK_HOME}/utils/openocd_tools/tcl/gap9revb.tcl + --openocd-tools=${GAP9_SDK_HOME}/utils/openocd_tools + --binary=${DEEPLOY_BINARY} + --work-dir=${BOARD_WORKDIR} + --multi-flash-content=${FLASH_LAYOUT} + --flash-size=67108864 + --flash-property=${FSBL_BINARY}@mram:fsbl:binary + --flash-property=${SSBL_BINARY}@mram:ssbl:binary + --flash-property=${DEEPLOY_BINARY}@mram:app:binary run + --py-stack + ) + + # Add readfs files if provided + if(GAPY_RUNNER_ARGS) + list(LENGTH GAPY_RUNNER_ARGS num_readfs_files) + message(STATUS "[Deeploy GAP9] Adding ${num_readfs_files} readfs file(s)") + list(APPEND GAPY_CMD ${GAPY_RUNNER_ARGS}) + endif() + + # Convert list to string for printing + string(REPLACE ";" " " GAPY_CMD_STR "${GAPY_CMD}") + + add_custom_target(board_${name} + DEPENDS ${name} + WORKING_DIRECTORY ${BOARD_WORKDIR} + COMMAND bash -c "${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/*.bin ${BOARD_WORKDIR}/ 2>/dev/null || true" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${GAP9_SDK_HOME}/utils/efuse/GAP9/efuse_hyper_preload.data + ${BOARD_WORKDIR}/chip.efuse_preload.data + COMMAND ${CMAKE_COMMAND} -E echo "==========================================" + COMMAND ${CMAKE_COMMAND} -E echo "[Deeploy GAP9] Executing gapy command to run on board:" + COMMAND ${CMAKE_COMMAND} -E echo "${GAPY_CMD_STR}" + COMMAND ${CMAKE_COMMAND} -E echo "==========================================" + COMMAND ${GAPY_CMD} + COMMENT "Running ${name} with gapy on GAP9 board" + POST_BUILD + USES_TERMINAL + VERBATIM + ) +endmacro() \ No newline at end of file diff --git a/cmake/gap9/gap9_gvsoc.cmake b/cmake/gap9/gap9_gvsoc.cmake index 5a6b205376..408a541e95 100644 --- a/cmake/gap9/gap9_gvsoc.cmake +++ b/cmake/gap9/gap9_gvsoc.cmake @@ -2,8 +2,6 @@ # # SPDX-License-Identifier: Apache-2.0 -# Mark that GAP9-specific gvsoc emulation is defined -set(GAP9_GVSOC_DEFINED TRUE) macro(add_gvsoc_emulation name target) @@ -19,6 +17,7 @@ macro(add_gvsoc_emulation name target) # Check if GAPY_RUNNER_ARGS is defined and non-empty (indicates L3 with readfs files) if(GAPY_RUNNER_ARGS) + # L3 mode: Use gapy with flash layout and readfs message(STATUS "[Deeploy GAP9] L3 mode: using gapy with readfs") @@ -81,7 +80,6 @@ macro(add_gvsoc_emulation name target) USES_TERMINAL VERBATIM ) - else() # L2 mode: Use traditional gvsoc command directly (no flash/readfs) message(STATUS "[Deeploy GAP9] L2 mode: using traditional gvsoc without flash") From 55f9902245599ade6dc25d33238fec96577fff1a Mon Sep 17 00:00:00 2001 From: Victor Jung Date: Mon, 23 Feb 2026 14:06:40 +0100 Subject: [PATCH 02/12] Add proper D flag for GAP9 board --- DeeployTest/testUtils/deeployRunner.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DeeployTest/testUtils/deeployRunner.py b/DeeployTest/testUtils/deeployRunner.py index a5a8d70ef3..9ee7c75677 100644 --- a/DeeployTest/testUtils/deeployRunner.py +++ b/DeeployTest/testUtils/deeployRunner.py @@ -395,7 +395,6 @@ def main(default_platform: Optional[str] = None, # Extract platform-specific CMake args from parsed args if available if platform_specific_cmake_args is None: platform_specific_cmake_args = [] - # Check for platform-specific arguments in args object and build CMake args if hasattr(args, 'cores'): platform_specific_cmake_args.append(f"-DNUM_CORES={args.cores}") @@ -405,6 +404,9 @@ def main(default_platform: Optional[str] = None, if hasattr(args, 'num_clusters'): platform_specific_cmake_args.append(f"-DNUM_CLUSTERS={args.num_clusters}") + if platform == 'GAP9': + platform_specific_cmake_args.append("-D SIMULATOR=" + simulator) + config = create_config_from_args(args, platform, simulator, tiling_enabled, platform_specific_cmake_args) print_configuration(config) From fb29c4a7468c9741fcfdcde7b7d16f4c12905845 Mon Sep 17 00:00:00 2001 From: "victor.jung" Date: Fri, 20 Mar 2026 12:50:22 +0100 Subject: [PATCH 03/12] Make pane name agnostic of the config --- scripts/gap9-run.sh | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/scripts/gap9-run.sh b/scripts/gap9-run.sh index af2dc20e0a..0d5644a373 100755 --- a/scripts/gap9-run.sh +++ b/scripts/gap9-run.sh @@ -305,7 +305,7 @@ cmd_attach_usbip() { cmd_detach_usbip || true log_info "Attaching USB device to usbip-devmgr..." - docker exec -it usbip-devmgr /bin/sh -lc 'nsenter -t1 -m sh -lc " + docker exec -i usbip-devmgr /bin/sh -lc 'nsenter -t1 -m sh -lc " usbip list -r \"$USBIP_HOST\" || { echo \"usbip list failed\"; exit 1; } BUSID=\$(usbip list -r \"$USBIP_HOST\" \ | grep \"$USBIP_VENDOR:$USBIP_PRODUCT\" \ @@ -457,28 +457,28 @@ cmd_start_tmux() { log_info "Creating tmux session: $session_name" - # Create new session with three panes (usbip-host, usbip-daemon, gap9) - tmux new-session -d -s "$session_name" -x 200 -y 50 - - # First pane: run pyusbip server - - # Second pane: run main orchestration (with delay to let server start) - tmux split-window -t "$session_name:0" -h - tmux split-window -t "$session_name:0" -v - tmux send-keys -t "$session_name:0.1" "alias stop='$script_path$opts_escaped stop'" Enter - tmux send-keys -t "$session_name:0.1" "$script_path$opts_escaped start-usbip-host" Enter - tmux send-keys -t "$session_name:0.2" "alias stop='$script_path$opts_escaped stop'" Enter - tmux send-keys -t "$session_name:0.2" "$script_path$opts_escaped start-usbip-daemon" Enter - tmux send-keys -t "$session_name:0.2" "$script_path$opts_escaped attach-usbip" Enter - tmux send-keys -t "$session_name:0.0" "alias stop='$script_path$opts_escaped stop'" Enter - tmux send-keys -t "$session_name:0.0" "$script_path$opts_escaped start-gap9" Enter - - # Select the first pane - tmux select-pane -t "$session_name:0.0" + # Create session and capture pane IDs (agnostic of base-index / pane-base-index) + # Layout: gap9 (left) | usbip-host (top-right) + # | usbip-daemon (bottom-right) + local pane_gap9 pane_usbip_host pane_usbip_daemon + pane_gap9=$(tmux new-session -d -s "$session_name" -x 200 -y 50 -P -F '#{pane_id}') + pane_usbip_host=$(tmux split-window -t "$pane_gap9" -h -P -F '#{pane_id}') + pane_usbip_daemon=$(tmux split-window -t "$pane_usbip_host" -v -P -F '#{pane_id}') + + tmux send-keys -t "$pane_usbip_host" "alias stop='$script_path$opts_escaped stop'" Enter + tmux send-keys -t "$pane_usbip_host" "$script_path$opts_escaped start-usbip-host" Enter + tmux send-keys -t "$pane_usbip_daemon" "alias stop='$script_path$opts_escaped stop'" Enter + tmux send-keys -t "$pane_usbip_daemon" "$script_path$opts_escaped start-usbip-daemon" Enter + tmux send-keys -t "$pane_usbip_daemon" "$script_path$opts_escaped attach-usbip" Enter + tmux send-keys -t "$pane_gap9" "alias stop='$script_path$opts_escaped stop'" Enter + tmux send-keys -t "$pane_gap9" "$script_path$opts_escaped start-gap9" Enter + + # Focus the main GAP9 pane + tmux select-pane -t "$pane_gap9" log_success "tmux session created: $session_name" log_info "Attaching to session..." - log_info "To detach: Ctrl+B then D" + log_info "To detach: prefix + D" log_info "To kill session: tmux kill-session -t $session_name" # Attach to the session From 2b4f4d41fa0d6d2805993c559b8e65ccda9af262 Mon Sep 17 00:00:00 2001 From: Victor Jung Date: Mon, 23 Mar 2026 10:27:21 +0100 Subject: [PATCH 04/12] Fix usbip host resolve for Linux platforms --- scripts/gap9-run.sh | 49 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/scripts/gap9-run.sh b/scripts/gap9-run.sh index 0d5644a373..5247b1e40c 100755 --- a/scripts/gap9-run.sh +++ b/scripts/gap9-run.sh @@ -25,7 +25,7 @@ set -euo pipefail ######################################################################### # Image and container names -GAP9_IMAGE="ghcr.io/pulp-platform/deeploy-gap9" +GAP9_IMAGE="deeploy-gap9" USBIP_IMAGE="jonathanberi/devmgr" DOCKER_PLATFORM="auto" @@ -68,6 +68,40 @@ log_success() { echo -e "\033[0;32m[SUCCESS]\033[0m $*" } +# Resolve USBIP_HOST to an IP usable from the host network namespace. +# On Linux, host.docker.internal doesn't resolve natively (no Docker Desktop DNS). +resolve_usbip_host() { + local host="$1" + # Already an IP address — return as-is + if [[ "$host" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "$host" + return + fi + # Try standard DNS resolution (works on macOS/Windows with Docker Desktop) + local ip + ip=$(getent hosts "$host" 2>/dev/null | awk '{print $1; exit}') + if [[ -n "$ip" ]]; then + echo "$ip" + return + fi + # Fallback for host.docker.internal on native Linux Docker: + # use the docker0 bridge interface or Docker bridge network gateway + if [[ "$host" == "host.docker.internal" ]]; then + ip=$(ip -4 addr show docker0 2>/dev/null | sed -n 's/.*inet \([0-9.]*\).*/\1/p' | head -1) + if [[ -n "$ip" ]]; then + echo "$ip" + return + fi + ip=$(docker network inspect bridge -f '{{range .IPAM.Config}}{{.Gateway}}{{end}}' 2>/dev/null | head -1) + if [[ -n "$ip" ]]; then + echo "$ip" + return + fi + fi + # Last resort: return hostname as-is (works on macOS/Windows) + echo "$host" +} + # Display help message show_help() { cat < Date: Mon, 23 Mar 2026 10:58:45 +0100 Subject: [PATCH 05/12] Fix hostname resolution for Macos --- scripts/gap9-run.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/gap9-run.sh b/scripts/gap9-run.sh index 5247b1e40c..4ae3bfcd59 100755 --- a/scripts/gap9-run.sh +++ b/scripts/gap9-run.sh @@ -77,7 +77,12 @@ resolve_usbip_host() { echo "$host" return fi - # Try standard DNS resolution (works on macOS/Windows with Docker Desktop) + # On non-Linux (macOS/Windows), Docker Desktop resolves host.docker.internal natively + if [[ "$(uname -s)" != "Linux" ]]; then + echo "$host" + return + fi + # Linux: try standard DNS resolution local ip ip=$(getent hosts "$host" 2>/dev/null | awk '{print $1; exit}') if [[ -n "$ip" ]]; then @@ -98,7 +103,7 @@ resolve_usbip_host() { return fi fi - # Last resort: return hostname as-is (works on macOS/Windows) + # Last resort: return hostname as-is echo "$host" } From 776969704f3fe4f4fa7a4d9f76da0e0c665f4e10 Mon Sep 17 00:00:00 2001 From: Victor Jung Date: Mon, 23 Feb 2026 14:07:22 +0100 Subject: [PATCH 06/12] Live print of the simulator cmd --- DeeployTest/testUtils/core/execution.py | 37 ++++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/DeeployTest/testUtils/core/execution.py b/DeeployTest/testUtils/core/execution.py index 1dcddeea62..a58f645a14 100644 --- a/DeeployTest/testUtils/core/execution.py +++ b/DeeployTest/testUtils/core/execution.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 import os +import re import shutil import subprocess import sys @@ -12,6 +13,7 @@ from .config import DeeployTestConfig from .output_parser import TestResult, parse_test_output +import threading def generate_network(config: DeeployTestConfig, skip: bool = False) -> None: @@ -147,6 +149,10 @@ def build_binary(config: DeeployTestConfig) -> None: log.error(f"Build failed with return code {result.returncode}") raise RuntimeError(f"Build failed for {config.test_name}") +# Source: https://stackoverflow.com/a/38662876 +def escapeAnsi(line): + ansi_escape = re.compile(r'(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]') + return ansi_escape.sub('', line) def run_simulation(config: DeeployTestConfig, skip: bool = False) -> TestResult: """ @@ -191,15 +197,32 @@ def run_simulation(config: DeeployTestConfig, skip: bool = False) -> TestResult: log.debug(f"[Execution] Simulation command: {' '.join(cmd)}") - result = subprocess.run(cmd, capture_output = True, text = True, env = env) - - if result.stdout: - print(result.stdout, end = '') - if result.stderr: - print(result.stderr, end = '', file = sys.stderr) + cmd_str = " ".join(cmd) + process = subprocess.Popen(cmd_str, + stdout = subprocess.PIPE, + stderr = subprocess.STDOUT, + shell = True, + encoding = 'utf-8') + + fileHandle = open('out.txt', 'a', encoding = 'utf-8') + fileHandle.write( + f"################## Testing {config.test_dir} on {config.platform} Platform ##################\n") + + result = "" + while True: + output = process.stdout.readline() + if output == '' and process.poll() is not None: + break + if output: + print(output.strip()) + result += output + fileHandle.write(f"{escapeAnsi(output)}") + + fileHandle.write("") + fileHandle.close() # Parse output for error count and cycles - test_result = parse_test_output(result.stdout, result.stderr) + test_result = parse_test_output(result, "") if not test_result.success and test_result.error_count == -1: log.warning(f"Could not parse error count from output") From 69f90e651a492404378ff7a5624d060a1f13abd0 Mon Sep 17 00:00:00 2001 From: "victor.jung" Date: Mon, 23 Mar 2026 11:33:42 +0100 Subject: [PATCH 07/12] Revert gap9 docker link --- scripts/gap9-run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gap9-run.sh b/scripts/gap9-run.sh index 4ae3bfcd59..f3ad70c2dd 100755 --- a/scripts/gap9-run.sh +++ b/scripts/gap9-run.sh @@ -25,7 +25,7 @@ set -euo pipefail ######################################################################### # Image and container names -GAP9_IMAGE="deeploy-gap9" +GAP9_IMAGE="ghcr.io/pulp-platform/deeploy-gap9" USBIP_IMAGE="jonathanberi/devmgr" DOCKER_PLATFORM="auto" From 461bd5ce11a30fc1a2cc6ec7544a25f7e88f20a8 Mon Sep 17 00:00:00 2001 From: "victor.jung" Date: Mon, 23 Mar 2026 11:46:18 +0100 Subject: [PATCH 08/12] Add optional GPIO toggling for power measurements for GAP9 --- DeeployTest/Platforms/GAP9/CMakeLists.txt | 4 ++++ DeeployTest/Platforms/GAP9/src/deeploytest.c | 22 ++++++++++++++++++++ DeeployTest/deeployRunner_gap9.py | 1 + DeeployTest/deeployRunner_tiled_gap9.py | 1 + DeeployTest/testUtils/deeployRunner.py | 3 +++ 5 files changed, 31 insertions(+) diff --git a/DeeployTest/Platforms/GAP9/CMakeLists.txt b/DeeployTest/Platforms/GAP9/CMakeLists.txt index 2f36cdedff..cbb6382329 100644 --- a/DeeployTest/Platforms/GAP9/CMakeLists.txt +++ b/DeeployTest/Platforms/GAP9/CMakeLists.txt @@ -22,6 +22,10 @@ target_compile_options(${ProjectId} INTERFACE network) add_gvsoc_emulation(${ProjectId} "gap9.evk") add_board_deployment(${ProjectId} "gap9.evk") +if(POWER_MEASUREMENT) + target_compile_definitions(${ProjectId} PRIVATE POWER_MEASUREMENT) +endif() + # RW: Waive sign comparison warnings from pulp_nn_utils.h target_compile_options(network PRIVATE -Wno-sign-compare diff --git a/DeeployTest/Platforms/GAP9/src/deeploytest.c b/DeeployTest/Platforms/GAP9/src/deeploytest.c index f4818e0104..e1c8abc5f9 100644 --- a/DeeployTest/Platforms/GAP9/src/deeploytest.c +++ b/DeeployTest/Platforms/GAP9/src/deeploytest.c @@ -16,6 +16,11 @@ // RW: Remove MAINSTACKSIZE because gap9-sdk does not use it #define SLAVESTACKSIZE 3800 +#ifdef POWER_MEASUREMENT + unsigned int GPIOs = 89; + #define WRITE_GPIO(x) pi_gpio_pin_write(GPIOs,x) +#endif + struct pi_device cluster_dev; uint32_t total_cycles = 0; @@ -78,6 +83,14 @@ void RunNetworkWrapper(void *args) { } int main(void) { + +#ifdef POWER_MEASUREMENT + pi_pad_function_set(GPIOs, 1); + pi_gpio_pin_configure(GPIOs, PI_GPIO_OUTPUT); + pi_gpio_pin_write(GPIOs, 0); + WRITE_GPIO(0); +#endif + #ifndef CI uint32_t core_id = pi_core_id(), cluster_id = pi_cluster_id(); printf("[%d %d] Hello World!\n", cluster_id, core_id); @@ -119,8 +132,17 @@ int main(void) { pi_cluster_task(&cluster_task, RunNetworkWrapper, NULL); cluster_task.slave_stack_size = SLAVESTACKSIZE; + +#ifdef POWER_MEASUREMENT + WRITE_GPIO(1); +#endif + pi_cluster_send_task_to_cl(&cluster_dev, &cluster_task); +#ifdef POWER_MEASUREMENT + WRITE_GPIO(0); +#endif + #ifndef CI printf("Output:\r\n"); #endif diff --git a/DeeployTest/deeployRunner_gap9.py b/DeeployTest/deeployRunner_gap9.py index ace2d8eb25..d35f8a50bd 100644 --- a/DeeployTest/deeployRunner_gap9.py +++ b/DeeployTest/deeployRunner_gap9.py @@ -12,6 +12,7 @@ # Define parser setup callback to add GAP9-specific arguments def setup_parser(parser): parser.add_argument('--cores', type = int, default = 8, help = 'Number of cores (default: 8)\n') + parser.add_argument('--powerMeasurement', action = 'store_true', default = False, help = 'Enable power measurement GPIO toggling\n') sys.exit( main(default_platform = "GAP9", diff --git a/DeeployTest/deeployRunner_tiled_gap9.py b/DeeployTest/deeployRunner_tiled_gap9.py index cde8e1e1d8..43ebe83997 100644 --- a/DeeployTest/deeployRunner_tiled_gap9.py +++ b/DeeployTest/deeployRunner_tiled_gap9.py @@ -12,6 +12,7 @@ # Define parser setup callback to add GAP9-specific arguments def setup_parser(parser): parser.add_argument('--cores', type = int, default = 8, help = 'Number of cores (default: 8)\n') + parser.add_argument('--powerMeasurement', action = 'store_true', default = False, help = 'Enable power measurement GPIO toggling\n') sys.exit( main(default_platform = "GAP9", diff --git a/DeeployTest/testUtils/deeployRunner.py b/DeeployTest/testUtils/deeployRunner.py index 9ee7c75677..e94de4250c 100644 --- a/DeeployTest/testUtils/deeployRunner.py +++ b/DeeployTest/testUtils/deeployRunner.py @@ -404,6 +404,9 @@ def main(default_platform: Optional[str] = None, if hasattr(args, 'num_clusters'): platform_specific_cmake_args.append(f"-DNUM_CLUSTERS={args.num_clusters}") + if hasattr(args, 'powerMeasurement') and args.powerMeasurement: + platform_specific_cmake_args.append("-DPOWER_MEASUREMENT=ON") + if platform == 'GAP9': platform_specific_cmake_args.append("-D SIMULATOR=" + simulator) From 880254aecbcfd569af5ecc3914a620813487ea7a Mon Sep 17 00:00:00 2001 From: "victor.jung" Date: Mon, 23 Mar 2026 11:52:08 +0100 Subject: [PATCH 09/12] Format --- CMakeLists.txt | 4 +-- DeeployTest/Platforms/GAP9/src/deeploytest.c | 6 ++--- DeeployTest/deeployRunner_gap9.py | 5 +++- DeeployTest/deeployRunner_tiled_gap9.py | 5 +++- DeeployTest/testUtils/core/execution.py | 15 ++++++----- DeeployTest/testUtils/testRunner.py | 2 +- cmake/gap9/gap9_board.cmake | 26 ++++++++++---------- 7 files changed, 34 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aa36c60ee8..0e07d64a9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,7 @@ elseif(platform STREQUAL PULPOpen) elseif(platform STREQUAL GAP9) message(STATUS "Building for platform 'GAP9'") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) - + # Select SDK config based on simulator type if(SIMULATOR STREQUAL "board") set(ENV{KCONFIG_CONFIG} DeeployTest/Platforms/GAP9/sdk_board.config) @@ -45,7 +45,7 @@ elseif(platform STREQUAL GAP9) set(ENV{KCONFIG_CONFIG} DeeployTest/Platforms/GAP9/sdk_gvsoc.config) message(STATUS "[GAP9] Using GVSoC SDK configuration") endif() - + include($ENV{GAP_SDK_HOME}/utils/cmake/setup.cmake) elseif(platform STREQUAL Generic) message(STATUS "Building for platform 'Generic'") diff --git a/DeeployTest/Platforms/GAP9/src/deeploytest.c b/DeeployTest/Platforms/GAP9/src/deeploytest.c index e1c8abc5f9..903ede8ae2 100644 --- a/DeeployTest/Platforms/GAP9/src/deeploytest.c +++ b/DeeployTest/Platforms/GAP9/src/deeploytest.c @@ -17,10 +17,10 @@ #define SLAVESTACKSIZE 3800 #ifdef POWER_MEASUREMENT - unsigned int GPIOs = 89; - #define WRITE_GPIO(x) pi_gpio_pin_write(GPIOs,x) +unsigned int GPIOs = 89; +#define WRITE_GPIO(x) pi_gpio_pin_write(GPIOs, x) #endif - + struct pi_device cluster_dev; uint32_t total_cycles = 0; diff --git a/DeeployTest/deeployRunner_gap9.py b/DeeployTest/deeployRunner_gap9.py index d35f8a50bd..79446ae3ba 100644 --- a/DeeployTest/deeployRunner_gap9.py +++ b/DeeployTest/deeployRunner_gap9.py @@ -12,7 +12,10 @@ # Define parser setup callback to add GAP9-specific arguments def setup_parser(parser): parser.add_argument('--cores', type = int, default = 8, help = 'Number of cores (default: 8)\n') - parser.add_argument('--powerMeasurement', action = 'store_true', default = False, help = 'Enable power measurement GPIO toggling\n') + parser.add_argument('--powerMeasurement', + action = 'store_true', + default = False, + help = 'Enable power measurement GPIO toggling\n') sys.exit( main(default_platform = "GAP9", diff --git a/DeeployTest/deeployRunner_tiled_gap9.py b/DeeployTest/deeployRunner_tiled_gap9.py index 43ebe83997..394522d7b8 100644 --- a/DeeployTest/deeployRunner_tiled_gap9.py +++ b/DeeployTest/deeployRunner_tiled_gap9.py @@ -12,7 +12,10 @@ # Define parser setup callback to add GAP9-specific arguments def setup_parser(parser): parser.add_argument('--cores', type = int, default = 8, help = 'Number of cores (default: 8)\n') - parser.add_argument('--powerMeasurement', action = 'store_true', default = False, help = 'Enable power measurement GPIO toggling\n') + parser.add_argument('--powerMeasurement', + action = 'store_true', + default = False, + help = 'Enable power measurement GPIO toggling\n') sys.exit( main(default_platform = "GAP9", diff --git a/DeeployTest/testUtils/core/execution.py b/DeeployTest/testUtils/core/execution.py index a58f645a14..abacf29c87 100644 --- a/DeeployTest/testUtils/core/execution.py +++ b/DeeployTest/testUtils/core/execution.py @@ -6,14 +6,12 @@ import re import shutil import subprocess -import sys from pathlib import Path from Deeploy.Logging import DEFAULT_LOGGER as log from .config import DeeployTestConfig from .output_parser import TestResult, parse_test_output -import threading def generate_network(config: DeeployTestConfig, skip: bool = False) -> None: @@ -149,11 +147,13 @@ def build_binary(config: DeeployTestConfig) -> None: log.error(f"Build failed with return code {result.returncode}") raise RuntimeError(f"Build failed for {config.test_name}") + # Source: https://stackoverflow.com/a/38662876 def escapeAnsi(line): ansi_escape = re.compile(r'(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]') return ansi_escape.sub('', line) + def run_simulation(config: DeeployTestConfig, skip: bool = False) -> TestResult: """ Run simulation and parse output. @@ -199,14 +199,13 @@ def run_simulation(config: DeeployTestConfig, skip: bool = False) -> TestResult: cmd_str = " ".join(cmd) process = subprocess.Popen(cmd_str, - stdout = subprocess.PIPE, - stderr = subprocess.STDOUT, - shell = True, - encoding = 'utf-8') + stdout = subprocess.PIPE, + stderr = subprocess.STDOUT, + shell = True, + encoding = 'utf-8') fileHandle = open('out.txt', 'a', encoding = 'utf-8') - fileHandle.write( - f"################## Testing {config.test_dir} on {config.platform} Platform ##################\n") + fileHandle.write(f"################## Testing {config.test_dir} on {config.platform} Platform ##################\n") result = "" while True: diff --git a/DeeployTest/testUtils/testRunner.py b/DeeployTest/testUtils/testRunner.py index 71c35c5b55..2d38cf6581 100644 --- a/DeeployTest/testUtils/testRunner.py +++ b/DeeployTest/testUtils/testRunner.py @@ -298,7 +298,7 @@ def __init__(self, gen_args: str = "", cmake_args: str = ""): - if simulator not in ['gvsoc', 'banshee', 'qemu', 'vsim', 'vsim.gui', 'host', 'board' ,'none']: + if simulator not in ['gvsoc', 'banshee', 'qemu', 'vsim', 'vsim.gui', 'host', 'board', 'none']: raise ValueError( f"Invalid emulator {simulator} (valid options are 'gvsoc', 'banshee', 'qemu', 'vsim', 'vsim.gui', 'host', 'none')!" ) diff --git a/cmake/gap9/gap9_board.cmake b/cmake/gap9/gap9_board.cmake index 8c34398abf..d8db011578 100644 --- a/cmake/gap9/gap9_board.cmake +++ b/cmake/gap9/gap9_board.cmake @@ -18,24 +18,24 @@ macro(add_board_deployment name target) set(FLASH_LAYOUT "${GAP9_SDK_HOME}/utils/layouts/default_layout_multi_readfs.json") set(FSBL_BINARY "${GAP9_SDK_HOME}/install/target/bin/fsbl") set(SSBL_BINARY "${GAP9_SDK_HOME}/install/target/bin/ssbl") - + make_directory(${BOARD_WORKDIR}) if(NOT DEFINED GAP9_SDK_HOME) message(FATAL_ERROR "Environment variable GAP_SDK_HOME not set") - endif() - + endif() + # Command to run on board - set(GAPY_CMD + set(GAPY_CMD ${GAPY} --target=gap9.evk --platform=board - --target-property=boot.flash_device=mram - --target-property=boot.mode=flash - --target-dir=${GAP9_SDK_HOME}/utils/gapy_v2/targets - --openocd-cable=${GAP9_SDK_HOME}/utils/openocd_tools/tcl/gapuino_ftdi.cfg - --openocd-script=${GAP9_SDK_HOME}/utils/openocd_tools/tcl/gap9revb.tcl - --openocd-tools=${GAP9_SDK_HOME}/utils/openocd_tools + --target-property=boot.flash_device=mram + --target-property=boot.mode=flash + --target-dir=${GAP9_SDK_HOME}/utils/gapy_v2/targets + --openocd-cable=${GAP9_SDK_HOME}/utils/openocd_tools/tcl/gapuino_ftdi.cfg + --openocd-script=${GAP9_SDK_HOME}/utils/openocd_tools/tcl/gap9revb.tcl + --openocd-tools=${GAP9_SDK_HOME}/utils/openocd_tools --binary=${DEEPLOY_BINARY} --work-dir=${BOARD_WORKDIR} --multi-flash-content=${FLASH_LAYOUT} @@ -55,13 +55,13 @@ macro(add_board_deployment name target) # Convert list to string for printing string(REPLACE ";" " " GAPY_CMD_STR "${GAPY_CMD}") - + add_custom_target(board_${name} DEPENDS ${name} WORKING_DIRECTORY ${BOARD_WORKDIR} COMMAND bash -c "${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/*.bin ${BOARD_WORKDIR}/ 2>/dev/null || true" - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${GAP9_SDK_HOME}/utils/efuse/GAP9/efuse_hyper_preload.data + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${GAP9_SDK_HOME}/utils/efuse/GAP9/efuse_hyper_preload.data ${BOARD_WORKDIR}/chip.efuse_preload.data COMMAND ${CMAKE_COMMAND} -E echo "==========================================" COMMAND ${CMAKE_COMMAND} -E echo "[Deeploy GAP9] Executing gapy command to run on board:" From 629fe13ef225bb639d74d23db3a83e5d52106a63 Mon Sep 17 00:00:00 2001 From: "victor.jung" Date: Mon, 23 Mar 2026 14:32:53 +0100 Subject: [PATCH 10/12] Cleanup file handles to avoid unhandled exeception in pytest --- DeeployTest/testUtils/core/execution.py | 39 ++++++++++++------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/DeeployTest/testUtils/core/execution.py b/DeeployTest/testUtils/core/execution.py index abacf29c87..987c6ea034 100644 --- a/DeeployTest/testUtils/core/execution.py +++ b/DeeployTest/testUtils/core/execution.py @@ -198,27 +198,24 @@ def run_simulation(config: DeeployTestConfig, skip: bool = False) -> TestResult: log.debug(f"[Execution] Simulation command: {' '.join(cmd)}") cmd_str = " ".join(cmd) - process = subprocess.Popen(cmd_str, - stdout = subprocess.PIPE, - stderr = subprocess.STDOUT, - shell = True, - encoding = 'utf-8') - - fileHandle = open('out.txt', 'a', encoding = 'utf-8') - fileHandle.write(f"################## Testing {config.test_dir} on {config.platform} Platform ##################\n") - - result = "" - while True: - output = process.stdout.readline() - if output == '' and process.poll() is not None: - break - if output: - print(output.strip()) - result += output - fileHandle.write(f"{escapeAnsi(output)}") - - fileHandle.write("") - fileHandle.close() + with subprocess.Popen(cmd_str, + stdout = subprocess.PIPE, + stderr = subprocess.STDOUT, + shell = True, + encoding = 'utf-8') as process: + + with open('out.txt', 'a', encoding = 'utf-8') as fileHandle: + fileHandle.write(f"################## Testing {config.test_dir} on {config.platform} Platform ##################\n") + + result = "" + while True: + output = process.stdout.readline() + if output == '' and process.poll() is not None: + break + if output: + print(output.strip()) + result += output + fileHandle.write(f"{escapeAnsi(output)}") # Parse output for error count and cycles test_result = parse_test_output(result, "") From 0959f0e74dcdcb5ac4b92f3300861a7ad690b085 Mon Sep 17 00:00:00 2001 From: "victor.jung" Date: Mon, 23 Mar 2026 14:33:09 +0100 Subject: [PATCH 11/12] format --- DeeployTest/testUtils/core/execution.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DeeployTest/testUtils/core/execution.py b/DeeployTest/testUtils/core/execution.py index 987c6ea034..4c6c972679 100644 --- a/DeeployTest/testUtils/core/execution.py +++ b/DeeployTest/testUtils/core/execution.py @@ -205,7 +205,8 @@ def run_simulation(config: DeeployTestConfig, skip: bool = False) -> TestResult: encoding = 'utf-8') as process: with open('out.txt', 'a', encoding = 'utf-8') as fileHandle: - fileHandle.write(f"################## Testing {config.test_dir} on {config.platform} Platform ##################\n") + fileHandle.write( + f"################## Testing {config.test_dir} on {config.platform} Platform ##################\n") result = "" while True: From 312c6b916cf40b0738a9ece2269a528261d691d7 Mon Sep 17 00:00:00 2001 From: Victor Jung Date: Tue, 24 Mar 2026 09:29:23 +0100 Subject: [PATCH 12/12] Remove unused GPIO and update gitignore --- .gitignore | 2 ++ DeeployTest/Platforms/GAP9/src/deeploytest.c | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d9e4faace3..bf976c1f64 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,5 @@ CHANGELOG_GEN.md # Container Artifacts .pyusbip/ .cache/ + +CLAUDE.md \ No newline at end of file diff --git a/DeeployTest/Platforms/GAP9/src/deeploytest.c b/DeeployTest/Platforms/GAP9/src/deeploytest.c index 903ede8ae2..77fe46a4e9 100644 --- a/DeeployTest/Platforms/GAP9/src/deeploytest.c +++ b/DeeployTest/Platforms/GAP9/src/deeploytest.c @@ -88,7 +88,6 @@ int main(void) { pi_pad_function_set(GPIOs, 1); pi_gpio_pin_configure(GPIOs, PI_GPIO_OUTPUT); pi_gpio_pin_write(GPIOs, 0); - WRITE_GPIO(0); #endif #ifndef CI