diff --git a/.gitignore b/.gitignore index e2c52ea3..ce409554 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,47 @@ -next_steps.* -build/ -output* -list/ -transcript +# Environment & secrets +.env +.env.* +.env.local + +# IDE & editor .vscode/ -gvsoc_config.json + +# python +__pycache__/ +*.pyc + +# Build & output +build/ +output/ +output_*.txt +next_steps.* power_report.csv + +# Logs & traces +log.txt trace_file.txt insn.txt debug_* -compile_commands.json + +# CMake CMakeCache.txt cmake_install.cmake -CMakeFiles +CMakeFiles/ +compile_commands.json /*/Makefile /*/*/Makefile /*/*/*/Makefile /*/*/*/*/Makefile -log.txt -gvsoc/ + +# Test & CI testsuite.py -test.conf testsuite* +test.conf testlogs/ + +# Project-specific +gvsoc/ +gvsoc_config.json +power_report.csv +list/ +transcript diff --git a/CMakeLists.txt b/CMakeLists.txt index 385bf533..868a45f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) set(CMAKE_VERBOSE_MAKEFILE TRUE) set(CMAKE_EXE_LINKER_FLAGS_INIT "--specs=nosys.specs") -set(CMAKE_LINK_LIBRARY_USING_WHOLE_ARCHIVE +set(CMAKE_LINK_LIBRARY_USING_WHOLE_ARCHIVE "-Wl,--whole-archive -Wl,--no-whole-archive" ) set(CMAKE_LINK_LIBRARY_USING_WHOLE_ARCHIVE_SUPPORTED True) @@ -80,6 +80,7 @@ add_subdirectory(targets) add_subdirectory(hal) # add_subdirectory(devices) add_subdirectory(drivers) +add_subdirectory(kernels) ################################################################################ # Testing # diff --git a/Makefile b/Makefile index 4aacc409..1aa99664 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # Copyright (C) 2025 ETH Zurich and University of Bologna # -# Licensed under the Solderpad Hardware License, Version 0.51 -# (the "License"); you may not use this file except in compliance +# Licensed under the Solderpad Hardware License, Version 0.51 +# (the "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 @@ -15,7 +15,7 @@ # # Authors: Victor Isachi # Alberto Dequino -# +# # Magia-sdk Makefile SHELL := /bin/bash @@ -47,6 +47,14 @@ ISA ?= rv32imcxgap9 gui ?= 0 tiles ?= 2 +LLVM_CMAKE ?= cmake +LLVM_DIR ?= llvm +LLVM_REPO ?= git@github.com:pulp-platform/llvm-project.git +LLVM_COMMIT ?= b494f2d8dde88723026db8ec16ac6c7ee1e140ca +LLVM_INSTALL_DIR ?= $(CURR_DIR)/llvm/install +LLVM_BUILD_DIR ?= $(LLVM_DIR)/llvm-project/build +LLVM_JOBS ?= 8 + tiles_2 := $(shell echo $$(( $(tiles) * $(tiles) ))) tiles_log := $(shell awk 'BEGIN { printf "%.0f", log($(tiles_2))/log(2) }') tiles_log_real := $(shell awk 'BEGIN { printf "%.0f", log($(tiles))/log(2) }') @@ -85,7 +93,7 @@ endif set_mesh: ifeq ($(tiles), 1) $(eval mesh_dv=0) -endif +endif run: set_mesh @echo 'Magia is available at https://github.com/pulp-platform/MAGIA.git' @@ -107,10 +115,10 @@ else ifeq ($(platform), rtl) cp ./build/bin/$(test) $(BUILD_DIR)/build/verif objcopy --srec-len 1 --output-target=srec $(BIN) $(BIN).s19 scripts/parse_s19.pl $(BIN).s19 > $(BIN).txt - python3 scripts/s19tomem.py $(BIN).txt $(BUILD_DIR)/build/stim_instr.txt $(BUILD_DIR)/build/stim_data.txt + python3 scripts/s19tomem.py $(BIN).txt $(BUILD_DIR)/build/stim_instr.txt $(BUILD_DIR)/build/stim_data.txt cd $(BUILD_DIR) && \ cp -sf ../../../sim/modelsim.ini modelsim.ini && \ - ln -sfn ../../../sim/work work + ln -sfn ../../../sim/work work riscv32-unknown-elf-objdump -d -S -Mmarch=$(ISA) $(BIN) > $(BIN).dump riscv32-unknown-elf-objdump -d -l -s -Mmarch=$(ISA) $(BIN) > $(BIN).objdump python3 scripts/objdump2itb.py $(BIN).objdump > $(BIN).itb @@ -171,8 +179,44 @@ gvsoc_init: cd $(GVSOC_DIR) && \ git submodule update --init --recursive && \ cd core && \ - git checkout lz/magia-v2-core && \ + git checkout master && \ cd ../pulp && \ - git checkout lz/magia-v2-pulp + git checkout master +llvm: + mkdir -p $(LLVM_DIR) + if [ ! -d "$(LLVM_DIR)/llvm-project/.git" ]; then \ + cd $(LLVM_DIR) && git clone $(LLVM_REPO); \ + fi + cd $(LLVM_DIR)/llvm-project && \ + git checkout $(LLVM_COMMIT) && \ + git submodule update --init --recursive --jobs=$(LLVM_JOBS) . + mkdir -p $(LLVM_INSTALL_DIR) + cd $(LLVM_DIR)/llvm-project && mkdir -p build && cd build && \ + $(LLVM_CMAKE) \ + -DCMAKE_INSTALL_PREFIX=$(LLVM_INSTALL_DIR) \ + -DCMAKE_CXX_COMPILER=${CXX} \ + -DCMAKE_C_COMPILER=${CC} \ + -DLLVM_OPTIMIZED_TABLEGEN=True \ + -DLLVM_ENABLE_PROJECTS="clang;lld" \ + -DLLVM_TARGETS_TO_BUILD="RISCV" \ + -DLLVM_DEFAULT_TARGET_TRIPLE=riscv32-unknown-elf \ + -DLLVM_ENABLE_LLD=False \ + -DLLVM_APPEND_VC_REV=ON \ + -DCMAKE_BUILD_TYPE=Release \ + ../llvm && \ + make -j$(LLVM_JOBS) all && \ + make install +deploy: +ifndef test + $(error Proper formatting is: make deploy test= platform=rtl|gvsoc) +endif +ifndef platform + $(error Proper formatting is: make run test= platform=rtl|gvsoc) +endif + python deployment/generate.py \ + -s ./deployment/tests/$(test) \ + -d ./tests/magia/mesh/$(test) && \ + make clean build tiles=$(tiles) compiler=$(compiler) eval=$(eval) && \ + make run test=test_$(test) platform=$(platform) diff --git a/README.md b/README.md index 0ff77d26..3917b9de 100644 --- a/README.md +++ b/README.md @@ -51,9 +51,9 @@ The following *optional* parameters can be specified when running the make comma 1. Initialize the GVSoC submodule: `make gvsoc_init` - + 2. Build the Magia architecture (*this command may take time and return an error, please be patient.*): - + `make MAGIA ` And/Or the GVSoC module: @@ -90,7 +90,7 @@ The following *optional* parameters can be specified when running the make comma `make run test= ` -***WARNING: YOU HAVE TO REBUILD BOTH RTL/GVSOC AND THE TEST BINARY EACH TIME YOU WANT TO TEST A MAGIA MESH WITH A DIFFERENT NUMBER OF TILES.*** +***WARNING: YOU HAVE TO REBUILD BOTH RTL/GVSOC AND THE TEST BINARY EACH TIME YOU WANT TO TEST A MAGIA MESH WITH A DIFFERENT NUMBER OF TILES.*** If you want to run gvsoc or a binary from outside the magia-sdk directory you can edit the **GVSOC_ABS_PATH** and **BIN_ABS_PATH** option in Makefile or directly on the *run* command line. @@ -122,7 +122,7 @@ To add your own test, you have to integrate a new test folder inside the **tests 4. Add to the *\* directory: 1. A new CMakeList.txt file following this template: - + set(TEST_NAME ) file(GLOB_RECURSE TEST_SRCS @@ -142,11 +142,71 @@ To add your own test, you have to integrate a new test folder inside the **tests TARGET ${TEST_NAME} POST_BUILD COMMAND ${CMAKE_OBJDUMP} -dhS -Mmarch=${ISA} $ > $.s) - + 2. An **src** directory containing your test's source (.c) files 3. An **include** directory containing your test's header (.h) files + +## Deployment with Deeploy + +[Deeploy](https://github.com/pulp-platform/Deeploy) is a code generation framework that takes an ONNX model and produces C inference code targeting a specific hardware platform. In this SDK it is used to automatically generate mesh tests from a network description. + +### User: generating and running a test + +Each deployable test lives under `deployment/tests//` and must contain three files: +``` +deployment/tests// +├── network.onnx # ONNX model +├── inputs.npz # reference input tensors +└── outputs.npz # reference output tensors +``` + +To generate the corresponding test, build it, and run it on the simulator, use: + +``` +make deploy test= platform=rtl|gvsoc [tiles=] [compiler=] [eval=] +``` + +This will: +1. Generate a complete test under `tests/magia/mesh//` (C sources, headers, `CMakeLists.txt`) +2. Build the test binary (`make clean build`) +3. Run it on the selected platform (`make run`) + +The available `tiles`, `compiler`, and `eval` parameters are the same as described in the [Getting started](#getting-started-and-usage) section. + +### Developer: how the pipeline works + +The code generation is driven by `deployment/generate.py`, which can also be invoked directly: + +``` +python deployment/generate.py -s deployment/tests/ -d tests/magia/mesh/ [-v] +``` + +The script performs the following steps: + +1. **Load** the ONNX model and the reference I/O tensors from the source directory. +2. **Deploy** the model usign the Deeploy Target for Magia (defined in `deployment/MagiaDeeployTarget/`) +3. **Generate** the following files in the destination directory: + - `include/network.h` — buffer declarations and `InitNetwork`/`RunNetwork` prototypes + - `src/network.c` — generated inference code calling into `kernels/` + - `include/test.h` — C arrays with the reference inputs and outputs + - `src/test.c` — test harness (copied from `deployment/test.c`) + - `CMakeLists.txt` — build file linking against `runtime`, `hal`, and `kernels` + + +#### Adding support for a new operator + +Supporting a new ONNX operator requires four coordinated additions: + +1. **Kernel** — implement the C kernel in `kernels/src/` and expose it in `kernels/include/`. +2. **Template** — add a Deeploy `NodeTemplate` in `deployment/MagiaDeeployTarget/Templates/` that renders the C call to the kernel. +3. **Bindings** — add the type bindings for the operator in `deployment/MagiaDeeployTarget/Bindings.py`. +4. **Registration** — add a `NodeMapper` using the appropriate Deeploy parser and the new bindings, then register it in the `MagiaMapping` dict inside `deployment/MagiaDeeployTarget/Platform.py`. + +See the `Add` operator (`AddTemplate.py`, `Bindings.py`, `kernels/src/add.c`) as a reference implementation. + + ## Folder Structure ### README.md @@ -171,7 +231,7 @@ Contains the weak definitions of this SDK APIs. These are the API instruction th Contains the architecture-specific implementation and source code for the HAL APIs. Despite each implementation having different names, thanks to an aliasing system the programmer can use the same name for the same API instruction on different architectures. ### devices -Nothing there. +Nothing there. If MAGIA ever evolves to have a host-offload mechanism, this folder will contain the trampoline functions. diff --git a/deployment/MagiaDeeployTarget/Bindings.py b/deployment/MagiaDeeployTarget/Bindings.py new file mode 100644 index 00000000..117832de --- /dev/null +++ b/deployment/MagiaDeeployTarget/Bindings.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2024 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + + +from Deeploy.AbstractDataTypes import PointerClass +from Deeploy.CommonExtensions.DataTypes import int8_t, int32_t +from Deeploy.DeeployTypes import CodeTransformation, NodeBinding +from Deeploy.Targets.Generic.TypeCheckers import AddChecker +from MagiaDeeployTarget.Templates import AddTemplate + +BasicTransformer = CodeTransformation([]) + +MagiaAddBindings = [ + NodeBinding( + AddChecker( + [PointerClass(int8_t), PointerClass(int8_t)], + [PointerClass(int32_t)] + ), + AddTemplate.referenceTemplate, + BasicTransformer, + ), +] \ No newline at end of file diff --git a/deployment/MagiaDeeployTarget/Deployer.py b/deployment/MagiaDeeployTarget/Deployer.py new file mode 100644 index 00000000..68e5f678 --- /dev/null +++ b/deployment/MagiaDeeployTarget/Deployer.py @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2026 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +from typing import Callable, Dict, List, Type + +import numpy as np +import onnx_graphsurgeon as gs + +from Deeploy.AbstractDataTypes import Pointer +from Deeploy.CommonExtensions.NetworkDeployers.SignPropDeployer import SignPropDeployer +from Deeploy.DeeployTypes import ConstantBuffer, DeploymentPlatform, NodeTemplate, TopologyOptimizer, VariableBuffer + + +class MagiaDeployer(SignPropDeployer): + + def __init__( + self, + graph: gs.Graph, + deploymentPlatform: DeploymentPlatform, + inputTypes: Dict[str, Type[Pointer]], + loweringOptimizer: TopologyOptimizer, + scheduler: Callable = lambda x: x, + name: str = 'DeeployNetwork', + default_channels_first = False, + deeployStateDir: str = "DeeployStateDir", + inputOffsets: Dict[str, int] = {} + ): + + super().__init__( + graph, + deploymentPlatform, + inputTypes, + loweringOptimizer, + scheduler, + name, + default_channels_first = default_channels_first, + deeployStateDir = deeployStateDir, + inputOffsets = inputOffsets, + ) + + self.loweringOptimizer.passes += [ + # Extra optimizer passes on the lowering optimization pass. + # It seems to be different than the "normal" optimization passes + # defined on the Platform. + ] diff --git a/deployment/MagiaDeeployTarget/Platform.py b/deployment/MagiaDeeployTarget/Platform.py new file mode 100644 index 00000000..397a9e76 --- /dev/null +++ b/deployment/MagiaDeeployTarget/Platform.py @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2026 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +import numpy as np +import onnx_graphsurgeon as gs + +from Deeploy.DeeployTypes import ConstantBuffer, DeploymentEngine, DeploymentPlatform, NetworkContext, NodeMapper, \ + NodeTemplate, StructBuffer, TopologyOptimizer, TransientBuffer, VariableBuffer +from Deeploy.Targets.Generic.Layers import AddLayer +from Deeploy.Targets.Generic.Parsers import AddParser +from Deeploy.Targets.Generic.Templates import AllocateTemplate as BasicAllocateTemplate +from MagiaDeeployTarget.Bindings import MagiaAddBindings +from MagiaDeeployTarget.Templates import AllocateTemplate, FreeTemplate + + +AddMapper = NodeMapper(AddParser(), MagiaAddBindings) + +MagiaMapping = {'Add': AddLayer([AddMapper])} + + +class MagiaVariableBuffer(VariableBuffer): + + initTemplate = AllocateTemplate.magiaInitTemplate + allocTemplate = AllocateTemplate.magiaAllocateTemplate + deallocTemplate = FreeTemplate.magiaFreeTemplate + + def _bufferRepresentation(self): + buffRepr = { + "type": self._instance, + "name": self.name, + "size": int(np.prod(self.shape)), + "_memoryLevel": getattr(self, "_memoryLevel", None), + } + return buffRepr + + +class MagiaTransientBuffer(TransientBuffer): + + initTemplate = AllocateTemplate.magiaInitTemplate + allocTemplate = AllocateTemplate.magiaAllocateTemplate + deallocTemplate = FreeTemplate.magiaFreeTemplate + + def _bufferRepresentation(self): + buffRepr = { + "type": self._type, + "name": self.name, + "size": self.size, + "_memoryLevel": getattr(self, "_memoryLevel", None), + } + return buffRepr + + +class MagiaConstantBuffer(ConstantBuffer): + + initTemplate = AllocateTemplate.magiaGlobalInitTemplate + allocTemplate = AllocateTemplate.magiaGlobalAllocateTemplate + deallocTemplate = FreeTemplate.magiaGlobalTemplate + + def _bufferRepresentation(self): + buffRepr = super()._bufferRepresentation() + buffRepr["_memoryLevel"] = getattr(self, "_memoryLevel", None) + return buffRepr + + +class MagiaStructBuffer(StructBuffer): + + initTemplate = BasicAllocateTemplate.referenceStructInitTemplate + allocTemplate = BasicAllocateTemplate.referenceStructAllocateTemplate + deallocTemplate = NodeTemplate("") + + +MagiaOptimizer = TopologyOptimizer( + [ + # Insert here the ONNX optimization passes. + ], + name = "MagiaOptimizer") + +_includeList = ["tile.h", "idma.h", "redmule.h", "eventunit.h"] + + +class MagiaMeshEngine(DeploymentEngine): + + def __init__(self, + name: str, + Mapping = MagiaMapping, + initCode: str = "", + includeList: list[str] = _includeList, + n_tiles: int = 4) -> None: + super().__init__(name, Mapping, initCode, includeList) + self.n_tiles = n_tiles + + +class MagiaPlatform(DeploymentPlatform): + + def __init__(self, + engines = [MagiaMeshEngine("MagiaMesh")], + variableBuffer = MagiaVariableBuffer, + constantBuffer = MagiaConstantBuffer, + structBuffer = MagiaStructBuffer, + transientBuffer = MagiaTransientBuffer) -> None: + super().__init__(engines, variableBuffer, constantBuffer, structBuffer, transientBuffer) diff --git a/deployment/MagiaDeeployTarget/Templates/AddTemplate.py b/deployment/MagiaDeeployTarget/Templates/AddTemplate.py new file mode 100644 index 00000000..c31148fe --- /dev/null +++ b/deployment/MagiaDeeployTarget/Templates/AddTemplate.py @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2024 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +from typing import Dict, List, Tuple + +from Deeploy.DeeployTypes import NetworkContext, NodeTemplate, OperatorRepresentation + + +class _MagiaAddTemplate(NodeTemplate): + + def alignToContext(self, ctxt: NetworkContext, + operatorRepresentation: OperatorRepresentation) -> Tuple[NetworkContext, Dict, List[str]]: + + data_in_1 = ctxt.lookup(operatorRepresentation['data_in_1']) + data_in_2 = ctxt.lookup(operatorRepresentation['data_in_2']) + data_out = ctxt.lookup(operatorRepresentation['data_out']) + + input_1_offset = 0 + if hasattr(data_in_1, "_signed") and hasattr(data_in_1, "nLevels"): + input_1_offset = (data_in_1._signed == 0) * int(data_in_1.nLevels / 2) + input_2_offset = 0 + if hasattr(data_in_2, "_signed") and hasattr(data_in_2, "nLevels"): + input_2_offset = (data_in_2._signed == 0) * int(data_in_2.nLevels / 2) + output_offset = 0 + if hasattr(data_out, "_signed") and hasattr(data_out, "nLevels"): + output_offset = -(data_out._signed == 0) * int(data_out.nLevels // 2) + + operatorRepresentation['offset'] = input_1_offset + input_2_offset + output_offset + + return ctxt, operatorRepresentation, [] + + +referenceTemplate = _MagiaAddTemplate(""" +// Magia Add (Name: ${nodeName}, Op: ${nodeOp}) +MAGIA_add(${data_in_1}, ${data_in_2}, ${data_out}, ${size}, ${offset}); +""") diff --git a/deployment/MagiaDeeployTarget/Templates/AllocateTemplate.py b/deployment/MagiaDeeployTarget/Templates/AllocateTemplate.py new file mode 100644 index 00000000..9f208156 --- /dev/null +++ b/deployment/MagiaDeeployTarget/Templates/AllocateTemplate.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2021 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +from Deeploy.DeeployTypes import NodeTemplate + +magiaInitTemplate = NodeTemplate("${type.typeName} ${name};\n") + +magiaAllocateTemplate = NodeTemplate(""" +% if _memoryLevel == "L1": +${name} = (${type.typeName}) magia_l1_malloc(sizeof(${type.referencedType.typeName}) * ${size});\n +% elif _memoryLevel == "L2" or _memoryLevel is None: +${name} = (${type.typeName}) magia_l2_malloc(sizeof(${type.referencedType.typeName}) * ${size});\n +% endif +""") +# magiaAllocateTemplate = NodeTemplate("") + +magiaGlobalInitTemplate = NodeTemplate(""" +% if _memoryLevel == "L1": +static ${type.referencedType.typeName} ${name}[${size}] = {${values}};\n +% elif _memoryLevel == "L2" or _memoryLevel is None: +extern ${type.referencedType.typeName} ${name}[${size}] = {${values}};\n +% endif +""") + +magiaGlobalAllocateTemplate = NodeTemplate("") \ No newline at end of file diff --git a/deployment/MagiaDeeployTarget/Templates/FreeTemplate.py b/deployment/MagiaDeeployTarget/Templates/FreeTemplate.py new file mode 100644 index 00000000..9ea665b5 --- /dev/null +++ b/deployment/MagiaDeeployTarget/Templates/FreeTemplate.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2023 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +from Deeploy.DeeployTypes import NodeTemplate + +magiaFreeTemplate = NodeTemplate(""" +% if _memoryLevel == "L1": +magia_l1_free(${name}, sizeof(${type.referencedType.typeName}) * ${size}); +% elif _memoryLevel == "L2" or _memoryLevel is None: +magia_l2_free(${name}, sizeof(${type.referencedType.typeName}) * ${size}); +% endif +""") + +magiaGlobalTemplate = NodeTemplate("magia_l2_free(${name}, sizeof(${type.referencedType.typeName}) * ${size});") \ No newline at end of file diff --git a/deployment/MagiaDeeployTarget/Templates/__init__.py b/deployment/MagiaDeeployTarget/Templates/__init__.py new file mode 100644 index 00000000..aa296246 --- /dev/null +++ b/deployment/MagiaDeeployTarget/Templates/__init__.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2024 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +from . import * \ No newline at end of file diff --git a/deployment/MagiaDeeployTarget/__init__.py b/deployment/MagiaDeeployTarget/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/deployment/generate.py b/deployment/generate.py new file mode 100644 index 00000000..a7b12452 --- /dev/null +++ b/deployment/generate.py @@ -0,0 +1,366 @@ + +import os +import shutil +import re +from pathlib import Path +from typing import Optional, Sequence +from argparse import ArgumentParser +import logging + +import numpy as np +from numpy.typing import NDArray + +import onnx +import onnx_graphsurgeon as gs + +import Deeploy +from Deeploy.AbstractDataTypes import Pointer, PointerClass +from Deeploy.CommonExtensions.NetworkDeployers.SignPropDeployer import SignPropDeployer +from Deeploy.CommonExtensions import DataTypes +from Deeploy.CommonExtensions.DataTypes import FloatDataTypes, IntegerDataTypes + +from MagiaDeeployTarget.Deployer import MagiaDeployer +from MagiaDeeployTarget.Platform import MagiaPlatform, MagiaOptimizer + + +copyright_str = """ +Copyright 2026 Fondazione ChipsIT. +Licensed under the Apache License, Version 2.0, see LICENSE for details. +SPDX-License-Identifier: Apache-2.0 + +Alex Marchioni +""" + + +def copyright_comment(comment_symbol:str = "//") -> str: + text = ''.join([f'{comment_symbol} {row}\n' + for row in copyright_str.split("\n")[1:-1]]) + return text + + +def defaultScheduler(graph: gs.Graph): + return graph.nodes + + +def load_npz(path: Path) -> list[NDArray]: + npz = np.load(path) + arrays = [npz[key] for key in npz.files] + return arrays + + +def generate_array_size(array: NDArray, name: Optional[str] = None) -> str: + if name is None: + name = "array" + name = name.upper() # ensure upper case + _str = f"#define {name}_NDIM {array.ndim}\n" + for i, dim in enumerate(array.shape): + _str += f"#define {name}_DIM{i} {dim}\n" + _str += f"#define {name}_SIZE {np.prod(array.shape)}\n" + _str += f"#define {name}_TYPE {array.dtype}_t\n" + return _str + +def generate_arrays_size( + arrays: Sequence[NDArray], + name: Optional[str] = None, + ) -> str: + if name is None: + name = "array" + _str = f"#define {name.upper()}S_NUM {len(arrays)}\n\n" + _str += "".join([ + generate_array_size(array, f"{name}{i}") + "\n" + for i, array in enumerate(arrays) + ]) + return _str + + +def generate_array(array: NDArray, name: Optional[str] = None) -> str: + if name is None: + name = "array" + array_flat = array.reshape(-1) # reshape as a 1D array + _str = f"{array.dtype}_t {name}[{name.upper()}_SIZE] = " + "{" + if np.issubdtype(array.dtype, np.integer): # integers + _str += ", ".join([str(x) for x in array_flat]) + elif np.issubdtype(array.dtype, np.float): # floats + _str += ", ".join( + [f'{x}f' if np.isfinite(x) else str(x) for x in array_flat]) + else: + raise ValueError(f"array type {array.dtype} not supported.") + _str += "};\n" + return _str + + +def generate_arrays(arrays: list[NDArray], name: Optional[str] = None) -> str: + if name is None: + name = "array" + names = [f"{name}{i}" for i in range(len(arrays))] + + # generate each array + _str = "".join([ + generate_array(array.reshape(-1), _name) + "\n" + for _name, array in zip(names, arrays) + ]) + + # generate the array containing all arrays + _str += f"void* {name}s[{name.upper()}S_NUM] = {{{', '.join(names)}}};\n" + + # generate the array containing the size of each array + size_list = [f"{_name.upper()}_SIZE" for _name in names] + _str += f"uint32_t {name}s_size[{name.upper()}S_NUM] = "\ + f"{{{', '.join(size_list)}}};\n" + + # generate the array containing the element size of each array + elem_size_list = [f"sizeof({_name.upper()}_TYPE)" for _name in names] + _str += f"uint32_t {name}s_elem_size[{name.upper()}S_NUM] = "\ + f"{{{', '.join(elem_size_list)}}};\n" + return _str + + +def generate_test_header(inputs: list[NDArray], outputs: list[NDArray]) -> str: + + guard_str = f"_TEST_INCLUDE_GUARD_" + + text = copyright_comment('//') + text += "\n" + text += f"#ifndef {guard_str}\n" + text += f"#define {guard_str}\n" + text += "\n" + text += "#include \n" + text += "\n" + text += generate_arrays_size(inputs, "input") + text += "\n" + text += generate_arrays_size(outputs, "output") + text += "\n" + text += generate_arrays(inputs, "input") + text += "\n" + text += generate_arrays(outputs, "output") + text += "\n" + text += f"#endif // {guard_str}\n" + return text + +def generate_network_header(deployer: MagiaDeployer) -> str: + + guard_str = f"_NETWORK_INCLUDE_GUARD_" + + text = copyright_comment('//') + text += "\n" + text += f"#ifndef {guard_str}\n" + text += f"#define {guard_str}\n" + text += "\n" + text += "#include \n" + text += "\n" + text += "void RunNetwork();\n" + text += "void InitNetwork();\n" + text += "\n" + text += deployer.generateIOBufferInitializationCode() + "\n" + text += "\n" + text += f"#endif // {guard_str}\n" + return text + +def generate_network_source(deployer: MagiaDeployer) -> str: + + text = copyright_comment('//') + text += "\n" + text += deployer.generateIncludeString() +"\n" + text += '#include "network.h"\n' + text += "\n" + text += deployer.generateBufferInitializationCode() + text += "\n" + text += deployer.generateGlobalDefinitionCode() + text += "\n" + text += "void RunNetwork() {\n" + text += deployer.generateInferenceInitializationCode() + "\n" + text += deployer.generateFunction() + "\n" + text += "}\n" + text += "\n" + text += "void InitNetwork() {\n" + text += deployer.generateEngineInitializationCode() + text += deployer.generateBufferAllocationCode() + text += "}\n" + + return text + +def generate_cmakelist(test: str) -> str: + + text = copyright_comment('#') + text += "\n" + text += f"set(TEST_NAME test_{test})\n" + text += "\n" + text += 'file(GLOB_RECURSE TEST_SRCS "src/*.c")\n' + text += "\n" + text += "add_executable(${TEST_NAME} ${TEST_SRCS})\n" + text += "target_include_directories(${TEST_NAME} PUBLIC include)\n" + text += "\n" + text += "target_compile_options(${TEST_NAME} PRIVATE -O2)\n" + text += "target_link_libraries(${TEST_NAME} PUBLIC runtime hal kernels)\n" + text += "\n" + text += "add_custom_command(\n" + text += " TARGET ${TEST_NAME}\n" + text += " POST_BUILD\n" + text += " COMMAND ${CMAKE_OBJDUMP} -dhS -Mmarch=${ISA} " + text += " $ > $.s\n" + text += ")\n" + return text + + +def allocator_patch( + code: str, + inputs: Sequence[NDArray], + outputs: Sequence[NDArray], + ) -> str: + for i, _input in enumerate(inputs): + code = code.replace( + f'*DeeployNetwork_input_{i};', + f'DeeployNetwork_input_{i}[{_input.size}];', + ) + code = code.replace( + f'* DeeployNetwork_input_{i};', + f' DeeployNetwork_input_{i}[{_input.size}];', + ) + code = code.replace( + f'DeeployNetwork_input_{i} =', + f'// DeeployNetwork_input_{i} =', + ) + for i, _output in enumerate(outputs): + code = code.replace( + f'*DeeployNetwork_output_{i};', + f'DeeployNetwork_output_{i}[{_output.size}];', + ) + code = code.replace( + f'* DeeployNetwork_output_{i};', + f' DeeployNetwork_output_{i}[{_output.size}];', + ) + code = code.replace( + f'DeeployNetwork_output_{i} =', + f'// DeeployNetwork_output_{i} =', + ) + return code + +def main(src_dir: Path, dst_dir: Path) -> None: + """ + src_dir + ├── inputs.npz + ├── outputs.npz + └── network.onnx + + dst_dir + ├── include + | ├── network.h + │ └── test.h + ├── src + | ├── network.c + │ └── test.c + └── CMakeLists.txt + """ + + # load inputs, outputs, and network + logger.debug("loading inputs and outputs data") + inputs = load_npz(src_dir / 'inputs.npz') + outputs = load_npz(src_dir / 'outputs.npz') + + logger.debug("loading onnx model") + onnx_graph = onnx.load_model(src_dir / 'network.onnx') + graph = gs.import_onnx(onnx_graph) + + # get input types from inputs numpy arrays + inputs_type = {} + for i, array in enumerate(inputs): + _type = f'{np.dtype(array.dtype).name}_t' + inputs_type[f"input_{i}"] = PointerClass(getattr(DataTypes, _type)) + + # Magia deployer + deployer = MagiaDeployer( + graph=graph, + deploymentPlatform=MagiaPlatform(), + inputTypes=inputs_type, + loweringOptimizer=MagiaOptimizer, + scheduler=defaultScheduler, + name="DeeployNetwork", + default_channels_first=False, + deeployStateDir="states", + ) + + # run deployment process to be ready to generate code + logger.debug("run deployment process") + deployer.prepare() + + # create destination folders + dst_dir.mkdir(parents=True, exist_ok=True) + dst_inc_dir = dst_dir / 'include' + dst_inc_dir.mkdir(parents=True, exist_ok=True) + dst_src_dir = dst_dir / 'src' + dst_src_dir.mkdir(parents=True, exist_ok=True) + + # prepare formatting code command + clang_format = "{BasedOnStyle: llvm, IndentWidth: 4, ColumnLimit: 80}" + clang_cmd = lambda path: f'clang-format -i --style="{clang_format}" {path}' + + # header for test inputs and outputs + test_header_path = dst_inc_dir / 'test.h' + logger.debug(f"generate {test_header_path}") + test_header = generate_test_header(inputs, outputs) + with open(test_header_path, "w") as f: + f.write(test_header) + os.system(clang_cmd(test_header_path)) + + # header for network + network_header_path = dst_inc_dir / 'network.h' + logger.debug(f"generate {network_header_path}") + network_header = generate_network_header(deployer) + network_header = allocator_patch(network_header, inputs, outputs) # patch + with open(network_header_path, "w") as f: + f.write(network_header) + os.system(clang_cmd(network_header_path)) + + + # source for network + network_source_path = dst_src_dir / 'network.c' + logger.debug(f"generate {network_source_path}") + network_source = generate_network_source(deployer) + network_source = allocator_patch(network_source, inputs, outputs) # patch + with open(network_source_path, "w") as f: + f.write(network_source) + os.system(clang_cmd(network_source_path)) + + # test main + test_path = dst_src_dir / 'test.c' + logger.debug(f"generate {test_path}") + shutil.copyfile(src_dir.parents[1] / 'test.c', test_path) + + # CMakeLists + cmakelists_path = dst_dir / 'CMakeLists.txt' + logger.debug(f"generate {cmakelists_path}") + cmakelist = generate_cmakelist(dst_dir.name) + with open(cmakelists_path, "w") as f: + f.write(cmakelist) + + +if __name__ == "__main__": + + parser = ArgumentParser() + parser.add_argument('-s', '--source', type=str, required=True) + parser.add_argument('-d', '--destination', type=str, required=True) + parser.add_argument('-v', '--verbose', action='count', default=0) + + args = parser.parse_args() + + + # logger + if args.verbose == 0: + log_level = logging.WARNING + elif args.verbose == 1: + log_level = logging.INFO + else: + log_level = logging.DEBUG + + logger = logging.getLogger(__name__) + logger.setLevel(log_level) + + formatter = logging.Formatter("%(asctime)s %(levelname)-8s %(message)s") + + stream_handler = logging.StreamHandler() + stream_handler.setFormatter(formatter) + logger.addHandler(stream_handler) + + # generate code + logger.debug(f"args: {args}") + main(Path(args.source), Path(args.destination)) \ No newline at end of file diff --git a/deployment/test.c b/deployment/test.c new file mode 100644 index 00000000..bd3cea01 --- /dev/null +++ b/deployment/test.c @@ -0,0 +1,134 @@ +// Copyright 2025 University of Bologna and Fondazione Chips-IT. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// Alberto Dequino +// Alex Marchioni + +#include +#include "network.h" +#include "test.h" +#include "eventunit.h" +#include "idma.h" +#include "redmule.h" +#include "tile.h" +#include "fsync.h" + +#define WAIT_MODE WFE + + +// TODO: replace with the function commented below when memcmp is available +// this only works if the output vectors are int32_t +uint32_t arrays_equal(const int32_t *expected, const int32_t *computed, + uint32_t elem_size, uint32_t length) { + uint32_t errors = 0; + for (uint32_t i = 0; i < length; i++) { + if (computed[i] != expected[i]) { + printf("Expected %d computed: %d (at index %u)\n", expected[i], + computed[i], i); + errors++; + } + } + return errors; +} +// uint32_t arrays_equal(const void *expected, const void *computed, +// uint32_t elem_size, uint32_t length) { +// uint32_t errors = 0; +// for (uint32_t i = 0; i < length; i++) { +// const void *_expected = (const char *)expected + i * elem_size; +// const void *_computed = (const char *)computed + i * elem_size; +// if (memcmp(_expected, _computed, elem_size) != 0) { +// printf("Expected %d computed: %d (at index %u)\n", expected, +// computed, i); +// errors++; +// } +// return errors; +// } + + +int main(void) { + + uint32_t hartid = get_hartid(); + uint32_t l1_tile_base = get_l1_base(hartid); + uint32_t cycle_start, cycle_stop; + + /* Init tile's iDMA, Redmule, fsync, event-unit */ + idma_config_t idma_cfg = {.hartid = hartid}; + idma_controller_t idma_ctrl = { + .base = NULL, + .cfg = &idma_cfg, + .api = &idma_api, + }; + + redmule_config_t redmule_cfg = {.hartid = hartid}; + redmule_controller_t redmule_ctrl = { + .base = NULL, + .cfg = &redmule_cfg, + .api = &redmule_api, + }; + + fsync_config_t fsync_cfg = {.hartid = hartid}; + fsync_controller_t fsync_ctrl = { + .base = NULL, + .cfg = &fsync_cfg, + .api = &fsync_api, + }; + + fsync_init(&fsync_ctrl); + idma_init(&idma_ctrl); + redmule_init(&redmule_ctrl); + + eu_config_t eu_cfg = {.hartid = hartid}; + eu_controller_t eu_ctrl = { + .base = NULL, + .cfg = &eu_cfg, + .api = &eu_api, + }; + eu_init(&eu_ctrl); + eu_fsync_init(&eu_ctrl, 0); + eu_redmule_init(&eu_ctrl, 0); + eu_idma_init(&eu_ctrl, 0); + + /* initialization */ + if (hartid == 0) { + InitNetwork(); + } + + fsync_sync_level(&fsync_ctrl, MAX_SYNC_LVL - 1, 0); + eu_fsync_wait(&eu_ctrl, WAIT_MODE); + + /* input copy */ + // TODO: check if memcopy is necessary!!! + if (hartid == 0) { + for (uint32_t buf = 0; buf < DeeployNetwork_num_inputs; buf++) { + memcpy(DeeployNetwork_inputs[buf], inputs[buf], + DeeployNetwork_inputs_bytes[buf]); + } + } + + fsync_sync_global(&fsync_ctrl); + eu_fsync_wait(&eu_ctrl, WAIT_MODE); + + /* execution */ + cycle_start = perf_get_cycles(); + RunNetwork(); + cycle_stop = perf_get_cycles(); + printf("id: %d, cycles: %d\n", hartid, cycle_stop - cycle_start); + + fsync_sync_global(&fsync_ctrl); + eu_fsync_wait(&eu_ctrl, WAIT_MODE); + + /* comparison */ + uint32_t total_errors = 0; + if (hartid == 0) { + for (uint32_t i = 0; i < OUTPUTS_NUM; i++) { + uint32_t errors = + arrays_equal(outputs[i], DeeployNetwork_outputs[i], + outputs_elem_size[i], outputs_size[i]); + total_errors += errors; + printf("output_%d -> number of errors: %d\n", i, errors); + } + } + + return total_errors; +} \ No newline at end of file diff --git a/deployment/tests/adder/inputs.npz b/deployment/tests/adder/inputs.npz new file mode 100644 index 00000000..69fff710 Binary files /dev/null and b/deployment/tests/adder/inputs.npz differ diff --git a/deployment/tests/adder/network.onnx b/deployment/tests/adder/network.onnx new file mode 100644 index 00000000..6c836a0f --- /dev/null +++ b/deployment/tests/adder/network.onnx @@ -0,0 +1,23 @@ +pytorch2.7.1:¡ +( + onnx::Add_0 + onnx::Add_12/Add"Add +main_graphZ% + onnx::Add_0 + + + + +Z% + onnx::Add_1 + + + + +b +2 + + + + +B \ No newline at end of file diff --git a/deployment/tests/adder/outputs.npz b/deployment/tests/adder/outputs.npz new file mode 100644 index 00000000..038cda4c Binary files /dev/null and b/deployment/tests/adder/outputs.npz differ diff --git a/kernels/CMakeLists.txt b/kernels/CMakeLists.txt new file mode 100644 index 00000000..a75e01b4 --- /dev/null +++ b/kernels/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright 2026 Fondazione ChipsIT. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Alex Marchioni + +file(GLOB_RECURSE SOURCES + src/*.c +) + +add_library(kernels STATIC ${SOURCES}) + +target_include_directories(kernels PUBLIC "include") + +target_link_libraries(kernels + PUBLIC + runtime + hal +) \ No newline at end of file diff --git a/kernels/include/add.h b/kernels/include/add.h new file mode 100644 index 00000000..e1dde25c --- /dev/null +++ b/kernels/include/add.h @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2020 ETH Zurich and University of Bologna + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __MAGIA_ADD_KERNEL_HEADER_ +#define __MAGIA_ADD_KERNEL_HEADER_ + + +void MAGIA_add(int8_t *pIn1, int8_t *pIn2, int32_t *pOut, uint32_t size, int32_t offset); + +#endif // __MAGIA_ADD_KERNEL_HEADER_ \ No newline at end of file diff --git a/kernels/src/add.c b/kernels/src/add.c new file mode 100644 index 00000000..a784dda2 --- /dev/null +++ b/kernels/src/add.c @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2024 ETH Zurich and University of Bologna + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "tile.h" + +void MAGIA_add(int8_t *pIn1, int8_t *pIn2, int32_t *pOut, uint32_t size, int32_t offset) { + + uint32_t hartid = get_hartid(); + uint32_t tile_size = (size + NUM_HARTS - 1) / NUM_HARTS; + uint32_t start = tile_size * hartid; + uint32_t stop = tile_size * (hartid + 1); + + if (stop > size) { + stop = size; + } + + for (uint32_t i = start; i < stop; i++) { + pOut[i] = (int32_t)(pIn1[i]) + (int32_t)(pIn2[i]) + offset; + } +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..77033a0d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[build-system] +requires = ["setuptools>=61"] +build-backend = "setuptools.build_meta" + +[project] +name = "magia-deployment" +version = "0.1.0" +description = "Deployment on MAGIA platform" +requires-python = ">=3.12" +authors = [ + {name="Alex Marchioni", email = "alex.marchioni@outlook.it"}, +] +dependencies = [ + "numpy", + "prettytable", + "pyelftools", + "rich", + "six", + "pexpect", + "psutil", + "typing_extensions", + "deeploy-pulp", +] + +[project.optional-dependencies] +nb = [ + "jupyter", + "ipykernel", +] + +[tool.setuptools.packages.find] +where = ["."] \ No newline at end of file diff --git a/targets/magia_v2/link.ld b/targets/magia_v2/link.ld index 207e3b23..b3a5fdce 100644 --- a/targets/magia_v2/link.ld +++ b/targets/magia_v2/link.ld @@ -122,7 +122,7 @@ SECTIONS _bss_end = .; } > dataram - .l2_heap : + /* .l2_heap : { . = ALIGN(4); sl2_heap = .; @@ -130,7 +130,7 @@ SECTIONS . = . + L2_HEAP_SIZE; el2_heap = .; _el2_heap = .; - } > dataram + } > dataram */ /* ensure there is enough room for stack */ .stack (NOLOAD): { diff --git a/targets/magia_v2/src/crt0.S b/targets/magia_v2/src/crt0.S index 678f3182..e1044281 100644 --- a/targets/magia_v2/src/crt0.S +++ b/targets/magia_v2/src/crt0.S @@ -37,6 +37,8 @@ _start: csrrs zero, mie, t0 # clear the bss segment + csrr t2, mhartid + bnez t2, skip_bss_clear la t0, _bss_start la t1, _bss_end 1: @@ -44,6 +46,8 @@ _start: addi t0, t0, 4 bltu t0, t1, 1b +skip_bss_clear: + /* Stack initialization */ la x2, stack diff --git a/tests/magia/mesh/CMakeLists.txt b/tests/magia/mesh/CMakeLists.txt index a79569cd..a5c2df3b 100644 --- a/tests/magia/mesh/CMakeLists.txt +++ b/tests/magia/mesh/CMakeLists.txt @@ -22,6 +22,7 @@ add_subdirectory(hello_world) # add_subdirectory(brah) # add_subdirectory(fsync_lr) # add_subdirectory(gemv) +add_subdirectory(adder) if (TARGET_PLATFORM STREQUAL "magia_v1") add_subdirectory(amo) add_subdirectory(amo_lock_global) diff --git a/tests/magia/mesh/adder/CMakeLists.txt b/tests/magia/mesh/adder/CMakeLists.txt new file mode 100644 index 00000000..c2e124d8 --- /dev/null +++ b/tests/magia/mesh/adder/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright 2026 Fondazione ChipsIT. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Alex Marchioni + +set(TEST_NAME test_adder) + +file(GLOB_RECURSE TEST_SRCS "src/*.c") + +add_executable(${TEST_NAME} ${TEST_SRCS}) +target_include_directories(${TEST_NAME} PUBLIC include) + +target_compile_options(${TEST_NAME} PRIVATE -O2) +target_link_libraries(${TEST_NAME} PUBLIC runtime hal kernels) + +add_custom_command( + TARGET ${TEST_NAME} + POST_BUILD + COMMAND ${CMAKE_OBJDUMP} -dhS -Mmarch=${ISA} $ > $.s +) diff --git a/tests/magia/mesh/adder/include/network.h b/tests/magia/mesh/adder/include/network.h new file mode 100644 index 00000000..c79e2fce --- /dev/null +++ b/tests/magia/mesh/adder/include/network.h @@ -0,0 +1,28 @@ +// Copyright 2026 Fondazione ChipsIT. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// Alex Marchioni + +#ifndef _NETWORK_INCLUDE_GUARD_ +#define _NETWORK_INCLUDE_GUARD_ + +#include + +void RunNetwork(); +void InitNetwork(); + +extern int8_t DeeployNetwork_input_0[125]; +static const uint32_t DeeployNetwork_input_0_len = 125; +extern int8_t DeeployNetwork_input_1[125]; +static const uint32_t DeeployNetwork_input_1_len = 125; +extern int32_t DeeployNetwork_output_0[125]; +static const uint32_t DeeployNetwork_output_0_len = 125; +static const uint32_t DeeployNetwork_num_inputs = 2; +static const uint32_t DeeployNetwork_num_outputs = 1; +extern void *DeeployNetwork_inputs[2]; +extern void *DeeployNetwork_outputs[1]; +static const uint32_t DeeployNetwork_inputs_bytes[2] = {125, 125}; +static const uint32_t DeeployNetwork_outputs_bytes[1] = {500}; + +#endif // _NETWORK_INCLUDE_GUARD_ diff --git a/tests/magia/mesh/adder/include/test.h b/tests/magia/mesh/adder/include/test.h new file mode 100644 index 00000000..04bdccc7 --- /dev/null +++ b/tests/magia/mesh/adder/include/test.h @@ -0,0 +1,84 @@ +// Copyright 2026 Fondazione ChipsIT. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// Alex Marchioni + +#ifndef _TEST_INCLUDE_GUARD_ +#define _TEST_INCLUDE_GUARD_ + +#include + +#define INPUTS_NUM 2 + +#define INPUT0_NDIM 4 +#define INPUT0_DIM0 1 +#define INPUT0_DIM1 5 +#define INPUT0_DIM2 5 +#define INPUT0_DIM3 5 +#define INPUT0_SIZE 125 +#define INPUT0_TYPE int8_t + +#define INPUT1_NDIM 4 +#define INPUT1_DIM0 1 +#define INPUT1_DIM1 5 +#define INPUT1_DIM2 5 +#define INPUT1_DIM3 5 +#define INPUT1_SIZE 125 +#define INPUT1_TYPE int8_t + +#define OUTPUTS_NUM 1 + +#define OUTPUT0_NDIM 4 +#define OUTPUT0_DIM0 1 +#define OUTPUT0_DIM1 5 +#define OUTPUT0_DIM2 5 +#define OUTPUT0_DIM3 5 +#define OUTPUT0_SIZE 125 +#define OUTPUT0_TYPE int32_t + +int8_t input0[INPUT0_SIZE] = { + -13, 39, 112, 111, -107, 125, 113, 120, -79, -38, 127, 103, -19, 74, + 23, 109, 59, 89, -69, -107, 124, -82, 8, -8, -72, 125, -83, 72, + -38, -5, -107, -113, 49, 24, -54, 94, -116, 18, 94, -126, -95, 7, + 39, 41, 98, 51, -49, -4, -92, 124, -106, 95, 0, -11, 9, 40, + 103, -90, -78, 78, 48, -118, 106, -5, -23, 107, -91, -30, 0, 63, + -41, -4, 100, 123, -6, 112, -44, 88, 26, -34, 83, -19, 54, 94, + 126, 27, 90, 24, -81, -117, 95, 109, -118, -33, -40, 65, 70, 96, + 105, -68, -14, -78, 2, -21, -96, 67, 26, -103, 69, -16, 29, 85, + 98, 50, -125, 8, 9, 2, -73, 122, 113, 78, -83, -52, -49}; + +int8_t input1[INPUT1_SIZE] = { + 111, 56, 114, 74, 86, 51, -12, -63, -50, -10, 65, -8, 64, + 64, -7, -44, -51, -72, 70, 9, 35, -40, 33, 69, -27, 94, + 30, 36, 98, -80, -95, -16, -79, -17, 102, 107, -18, -90, 71, + -127, -107, -12, -106, -66, -23, -106, 93, 95, 127, 30, -30, 84, + 31, -128, 47, 41, 94, 60, 83, 56, -40, -119, -9, 93, 70, + 37, 109, 63, 55, 3, 83, 94, 91, -64, 51, -120, 97, -49, + 105, 51, 86, 36, 102, -117, -53, 89, -17, 50, -71, 123, -114, + 15, -26, 92, -15, -80, -56, 83, 14, -95, 124, -44, -38, 69, + 110, 94, -70, -9, 120, 51, 38, 74, 63, -48, -12, 53, -107, + 50, -73, 48, 18, 112, 42, 62, 14}; + +void *inputs[INPUTS_NUM] = {input0, input1}; +uint32_t inputs_size[INPUTS_NUM] = {INPUT0_SIZE, INPUT1_SIZE}; +uint32_t inputs_elem_size[INPUTS_NUM] = {sizeof(INPUT0_TYPE), + sizeof(INPUT1_TYPE)}; + +int32_t output0[OUTPUT0_SIZE] = { + 98, 95, 226, 185, -21, 176, 101, 57, -129, -48, 192, 95, 45, + 138, 16, 65, 8, 17, 1, -98, 159, -122, 41, 61, -99, 219, + -53, 108, 60, -85, -202, -129, -30, 7, 48, 201, -134, -72, 165, + -253, -202, -5, -67, -25, 75, -55, 44, 91, 35, 154, -136, 179, + 31, -139, 56, 81, 197, -30, 5, 134, 8, -237, 97, 88, 47, + 144, 18, 33, 55, 66, 42, 90, 191, 59, 45, -8, 53, 39, + 131, 17, 169, 17, 156, -23, 73, 116, 73, 74, -152, 6, -19, + 124, -144, 59, -55, -15, 14, 179, 119, -163, 110, -122, -36, 48, + 14, 161, -44, -112, 189, 35, 67, 159, 161, 2, -137, 61, -98, + 52, -146, 170, 131, 190, -41, 10, -35}; + +void *outputs[OUTPUTS_NUM] = {output0}; +uint32_t outputs_size[OUTPUTS_NUM] = {OUTPUT0_SIZE}; +uint32_t outputs_elem_size[OUTPUTS_NUM] = {sizeof(OUTPUT0_TYPE)}; + +#endif // _TEST_INCLUDE_GUARD_ diff --git a/tests/magia/mesh/adder/src/network.c b/tests/magia/mesh/adder/src/network.c new file mode 100644 index 00000000..9159354d --- /dev/null +++ b/tests/magia/mesh/adder/src/network.c @@ -0,0 +1,38 @@ +// Copyright 2026 Fondazione ChipsIT. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// Alex Marchioni + +#include "network.h" +#include "eventunit.h" +#include "idma.h" +#include "redmule.h" +#include "tile.h" + +int8_t DeeployNetwork_input_0[125]; +int8_t DeeployNetwork_input_1[125]; +int32_t DeeployNetwork_output_0[125]; +void *DeeployNetwork_inputs[2]; +void *DeeployNetwork_outputs[1]; + +void RunNetwork() { + + // Magia Add (Name: Add, Op: Add) + MAGIA_add(DeeployNetwork_input_0, DeeployNetwork_input_1, + DeeployNetwork_output_0, 125, 0); +} + +void InitNetwork() { + + // DeeployNetwork_input_0 = (int8_t*) magia_l2_malloc(sizeof(int8_t) * 125); + + // DeeployNetwork_input_1 = (int8_t*) magia_l2_malloc(sizeof(int8_t) * 125); + + // DeeployNetwork_output_0 = (int32_t*) magia_l2_malloc(sizeof(int32_t) * + // 125); + + DeeployNetwork_inputs[0] = (void *)DeeployNetwork_input_0; + DeeployNetwork_inputs[1] = (void *)DeeployNetwork_input_1; + DeeployNetwork_outputs[0] = (void *)DeeployNetwork_output_0; +} diff --git a/tests/magia/mesh/adder/src/test.c b/tests/magia/mesh/adder/src/test.c new file mode 100644 index 00000000..bd3cea01 --- /dev/null +++ b/tests/magia/mesh/adder/src/test.c @@ -0,0 +1,134 @@ +// Copyright 2025 University of Bologna and Fondazione Chips-IT. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// Alberto Dequino +// Alex Marchioni + +#include +#include "network.h" +#include "test.h" +#include "eventunit.h" +#include "idma.h" +#include "redmule.h" +#include "tile.h" +#include "fsync.h" + +#define WAIT_MODE WFE + + +// TODO: replace with the function commented below when memcmp is available +// this only works if the output vectors are int32_t +uint32_t arrays_equal(const int32_t *expected, const int32_t *computed, + uint32_t elem_size, uint32_t length) { + uint32_t errors = 0; + for (uint32_t i = 0; i < length; i++) { + if (computed[i] != expected[i]) { + printf("Expected %d computed: %d (at index %u)\n", expected[i], + computed[i], i); + errors++; + } + } + return errors; +} +// uint32_t arrays_equal(const void *expected, const void *computed, +// uint32_t elem_size, uint32_t length) { +// uint32_t errors = 0; +// for (uint32_t i = 0; i < length; i++) { +// const void *_expected = (const char *)expected + i * elem_size; +// const void *_computed = (const char *)computed + i * elem_size; +// if (memcmp(_expected, _computed, elem_size) != 0) { +// printf("Expected %d computed: %d (at index %u)\n", expected, +// computed, i); +// errors++; +// } +// return errors; +// } + + +int main(void) { + + uint32_t hartid = get_hartid(); + uint32_t l1_tile_base = get_l1_base(hartid); + uint32_t cycle_start, cycle_stop; + + /* Init tile's iDMA, Redmule, fsync, event-unit */ + idma_config_t idma_cfg = {.hartid = hartid}; + idma_controller_t idma_ctrl = { + .base = NULL, + .cfg = &idma_cfg, + .api = &idma_api, + }; + + redmule_config_t redmule_cfg = {.hartid = hartid}; + redmule_controller_t redmule_ctrl = { + .base = NULL, + .cfg = &redmule_cfg, + .api = &redmule_api, + }; + + fsync_config_t fsync_cfg = {.hartid = hartid}; + fsync_controller_t fsync_ctrl = { + .base = NULL, + .cfg = &fsync_cfg, + .api = &fsync_api, + }; + + fsync_init(&fsync_ctrl); + idma_init(&idma_ctrl); + redmule_init(&redmule_ctrl); + + eu_config_t eu_cfg = {.hartid = hartid}; + eu_controller_t eu_ctrl = { + .base = NULL, + .cfg = &eu_cfg, + .api = &eu_api, + }; + eu_init(&eu_ctrl); + eu_fsync_init(&eu_ctrl, 0); + eu_redmule_init(&eu_ctrl, 0); + eu_idma_init(&eu_ctrl, 0); + + /* initialization */ + if (hartid == 0) { + InitNetwork(); + } + + fsync_sync_level(&fsync_ctrl, MAX_SYNC_LVL - 1, 0); + eu_fsync_wait(&eu_ctrl, WAIT_MODE); + + /* input copy */ + // TODO: check if memcopy is necessary!!! + if (hartid == 0) { + for (uint32_t buf = 0; buf < DeeployNetwork_num_inputs; buf++) { + memcpy(DeeployNetwork_inputs[buf], inputs[buf], + DeeployNetwork_inputs_bytes[buf]); + } + } + + fsync_sync_global(&fsync_ctrl); + eu_fsync_wait(&eu_ctrl, WAIT_MODE); + + /* execution */ + cycle_start = perf_get_cycles(); + RunNetwork(); + cycle_stop = perf_get_cycles(); + printf("id: %d, cycles: %d\n", hartid, cycle_stop - cycle_start); + + fsync_sync_global(&fsync_ctrl); + eu_fsync_wait(&eu_ctrl, WAIT_MODE); + + /* comparison */ + uint32_t total_errors = 0; + if (hartid == 0) { + for (uint32_t i = 0; i < OUTPUTS_NUM; i++) { + uint32_t errors = + arrays_equal(outputs[i], DeeployNetwork_outputs[i], + outputs_elem_size[i], outputs_size[i]); + total_errors += errors; + printf("output_%d -> number of errors: %d\n", i, errors); + } + } + + return total_errors; +} \ No newline at end of file