diff --git a/CMakeLists.txt b/CMakeLists.txt index 20a85bccf8..ac707b16ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,8 +20,8 @@ if(TOOLCHAIN STREQUAL GCC) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) endif() -set(platform MemPool CACHE STRING "Platform (MemPool, SoftHier, QEMU, Siracusa, Siracusa_w_neureka, PULP-Open, GAP9, Generic, Snitch)") -set_property(CACHE platform PROPERTY STRINGS MemPool SoftHier QEMU Siracusa Siracusa_w_neureka PULP-Open GAP9 Generic Snitch) +set(platform MemPool CACHE STRING "Platform (MemPool, SoftHier, QEMU, Siracusa, Siracusa_w_neureka, PULP-Open, GAP9, Generic, Snitch, Xheep)") +set_property(CACHE platform PROPERTY STRINGS MemPool SoftHier QEMU Siracusa Siracusa_w_neureka PULP-Open GAP9 Generic Snitc Xheep) if(platform STREQUAL MemPool) message(STATUS "Building for platform 'MemPool'") @@ -57,6 +57,8 @@ elseif(platform STREQUAL Chimera) message(STATUS "Building for platform 'Chimera'") elseif(platform STREQUAL XDNA2) message(STATUS "Building for platform 'XDNA2'") +elseif(platform STREQUAL Xheep) + message(STATUS "Building for platform 'X-HEEP'") else() message(FATAL_ERROR "Invalid platform '${platform}' specified!") endif() @@ -326,5 +328,30 @@ if(platform STREQUAL XDNA2) endif() +if(platform STREQUAL Xheep) + if(TOOLCHAIN STREQUAL LLVM) + set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/cmake/xheep/toolchain_llvm.cmake) + else() + set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/cmake/xheep/toolchain_gcc.cmake) + endif() + + include(${CMAKE_CURRENT_LIST_DIR}/cmake/xheep/xheep.cmake) + include(${CMAKE_CURRENT_LIST_DIR}/cmake/xheep/xheep_verilator.cmake) + + project(deeploy LANGUAGES C ASM) + + message(STATUS "============================= X-HEEP Configuration ============================") + message(STATUS "[cMake ] GENERATED_SOURCE = " ${GENERATED_SOURCE}) + message(STATUS "[cMake ] TESTNAME = " ${TESTNAME}) + message(STATUS "==============================================================================") + message(STATUS "") + + add_subdirectory(TargetLibraries/Generic) + add_subdirectory(TargetLibraries/xheep) + add_subdirectory(DeeployTest) + + target_link_libraries(deeploylib INTERFACE deeploybasic deeployxheep) +endif() + print_simulation_config() diff --git a/Deeploy/Targets/Generic/Bindings.py b/Deeploy/Targets/Generic/Bindings.py index 21cf01e52a..596717b238 100644 --- a/Deeploy/Targets/Generic/Bindings.py +++ b/Deeploy/Targets/Generic/Bindings.py @@ -22,12 +22,14 @@ FloatSwishTemplate, GatherTemplate, GemmTemplate, IntegerDivTemplate, ITAMaxTemplate, ITAPartialMaxTemplate, \ MatMulTemplate, MaxPoolTemplate, MulTemplate, PadTemplate, QuantTemplate, ReduceMeanTemplate, ReduceSumTemplate, \ RequantShiftTemplate, ReshapeTemplate, RQIntegerDivTemplate, RQSiGELUTemplate, SliceTemplate, SubTemplate, \ - TransposeTemplate, iGELUTemplate, iLayernormTemplate, iRMSNormTemplate, iSoftmaxTemplate + TransposeTemplate, iGELUTemplate, iLayernormTemplate, iRMSNormTemplate, iSoftmaxTemplate, \ + TanhTemplate, ReduceMaxTemplate from Deeploy.Targets.Generic.TypeCheckers import AddChecker, BatchNormChecker, ConcatChecker, ConvChecker, \ DebugPrintChecker, DequantChecker, DivChecker, DummyChecker, GatherChecker, GELUChecker, GEMMChecker, \ LayerNormChecker, MatMulChecker, MaxPoolChecker, MulChecker, PadChecker, QuantChecker, ReduceMeanChecker, \ ReduceSumChecker, ReluChecker, RequantShiftChecker, ReshapeChecker, RQIntegerDivChecker, SliceChecker, \ - SoftmaxChecker, TransposeChecker + SoftmaxChecker, TransposeChecker, \ + ReduceMaxChecker, FloatConcatChecker, TanhChecker BasicTransformer = CodeTransformation([ArgumentStructGeneration(), MemoryManagementGeneration(), FutureGeneration()]) @@ -420,3 +422,28 @@ NodeBinding(DummyChecker([PointerClass(float32_t)], [PointerClass(float32_t)]), FloatGlobalMaxPoolTemplate.referenceTemplate, BasicTransformer) ] + + +### NEWLY ADDED LAYERS: +BasicTanhBindings = [ + NodeBinding(TanhChecker([PointerClass(float32_t), PointerClass(float32_t)], [PointerClass(float32_t)]), + TanhTemplate.referenceTemplate, BasicTransformer) +] + + +BasicReduceMaxBindings = [ + NodeBinding(ReduceMaxChecker([PointerClass(type1), PointerClass(type2)], [PointerClass(int32_t)]), + ReduceMaxTemplate.referenceTemplate, BasicTransformer) + for type1 in IntegerDataTypes + for type2 in IntegerDataTypes +] + [ + NodeBinding(ReduceMaxChecker([PointerClass(float32_t), PointerClass(float32_t)], [PointerClass(float32_t)]), + ReduceMaxTemplate.referenceTemplate, BasicTransformer) +] + +BasicConcatBindings += [NodeBinding( + FloatConcatChecker([PointerClass(float32_t), PointerClass(float32_t)], + [PointerClass(float32_t)]), + ConcatTemplate.referenceTemplate, + BasicTransformer +)] \ No newline at end of file diff --git a/Deeploy/Targets/Generic/Layers.py b/Deeploy/Targets/Generic/Layers.py index d0a1e1db3c..206f8b2dd5 100644 --- a/Deeploy/Targets/Generic/Layers.py +++ b/Deeploy/Targets/Generic/Layers.py @@ -792,3 +792,17 @@ def computeOps(self): opRep = self.mapper.parser.operatorRepresentation # (spatial_size - 1) comparisons per output channel return int(opRep['batch_size'] * opRep['num_channels'] * (opRep['spatial_size'] - 1)) + + +### NEWLY ADDED LAYERS : + +class TanhLayer(ONNXLayer): + + def __init__(self, maps: List[NodeMapper]): + super().__init__(maps) + +class ReduceMaxLayer(ONNXLayer): + + def __init__(self, maps: List[NodeMapper]): + super().__init__(maps) + diff --git a/Deeploy/Targets/Generic/Parsers.py b/Deeploy/Targets/Generic/Parsers.py index aa8bd8724a..19bc06e912 100644 --- a/Deeploy/Targets/Generic/Parsers.py +++ b/Deeploy/Targets/Generic/Parsers.py @@ -3124,3 +3124,76 @@ class GlobalMaxPoolParser(GlobalPoolParser): def parseNode(self, node: gs.Node) -> bool: return super().parseNode(node) and node.op == 'GlobalMaxPool' + + +### NEWLY ADDED LAYERS : + +class TanhParser(NodeParser): + + def __init__(self): + super().__init__() + + def parseNode(self, node: gs.Node) -> bool: + + ret = all([len(node.inputs) == 1, len(node.outputs) == 1]) + + return ret + + def parseNodeCtxt(self, + ctxt: NetworkContext, + node: gs.Node, + channels_first: bool = True) -> Tuple[NetworkContext, bool]: + + data_in = ctxt.lookup(node.inputs[0].name) + data_out = ctxt.lookup(node.outputs[0].name) + self.operatorRepresentation['data_in'] = data_in.name + self.operatorRepresentation['data_out'] = data_out.name + self.operatorRepresentation['size'] = np.prod(data_in.shape) + + return ctxt, True + + + +class ReduceMaxParser(NodeParser): + """ + Reduce Max Parser. + Only supports maximizing through one dimension. (axes.shape == 1) + """ + def __init__(self): + super().__init__() + + def parseNode(self, node: gs.Node) -> bool: + + ret = all(['axes' in node.attrs, len(node.inputs) <= 2, len(node.outputs) == 1]) + + if ret: + axes = node.attrs.get('axes', 0) + if len(axes) > 1: + return False + else: + self.operatorRepresentation['axes'] = axes[0] + return True + + return False + + def parseNodeCtxt(self, + ctxt: NetworkContext, + node: gs.Node, + channels_first: bool = True) -> Tuple[NetworkContext, bool]: + + data_in = ctxt.lookup(node.inputs[0].name) + data_out = ctxt.lookup(node.outputs[0].name) + self.operatorRepresentation['data_in'] = data_in.name + self.operatorRepresentation['data_out'] = data_out.name + + axes = self.operatorRepresentation['axes'] + + inner_size = int(np.prod(data_in.shape[axes+1:])) + outer_size = int(np.prod(data_in.shape[:axes])) + + self.operatorRepresentation['inner_size'] = inner_size + self.operatorRepresentation['output_size'] = inner_size * outer_size + self.operatorRepresentation['d_axes'] = int(data_in.shape[axes]) + self.operatorRepresentation['outer_step'] = int(data_in.shape[axes] * inner_size) + + return ctxt, True \ No newline at end of file diff --git a/Deeploy/Targets/Generic/Platform.py b/Deeploy/Targets/Generic/Platform.py index 2aa1ef1c38..cf979ca372 100644 --- a/Deeploy/Targets/Generic/Platform.py +++ b/Deeploy/Targets/Generic/Platform.py @@ -17,13 +17,15 @@ BasicPowBindings, BasicQuantBindings, BasicReduceMeanBindings, BasicReduceSumBindings, BasicReluBinding, \ BasicReshapeBindings, BasicRQIntegerDivBinding, BasicRQSBindings, BasicRQSGELUBinding, BasicSigmoidBindings, \ BasicSliceBindings, BasicSoftmaxBindings, BasicSqrtBindings, BasicSubBindings, BasicSwishBindings, \ - BasicTransposeBindings, DummyBinding + BasicTransposeBindings, DummyBinding, \ + BasicTanhBindings, BasicReduceMaxBindings from Deeploy.Targets.Generic.Layers import AddLayer, AveragePoolLayer, BatchNormalizationLayer, CeilLayer, ClipLayer, \ ConcatLayer, ConvLayer, ConvTransposeLayer, DebugPrintLayer, DequantLayer, DivLayer, ExpLayer, FloorLayer, \ GatherLayer, GELULayer, GEMMLayer, GlobalAveragePoolLayer, GlobalMaxPoolLayer, GroupNormLayer, InstanceNormLayer, \ ITAMaxLayer, LayerNormLayer, MatMulLayer, MaxPoolLayer, MulLayer, PadLayer, PowLayer, QuantLayer, ReduceMeanLayer, \ ReduceSumLayer, ReluLayer, RequantShiftLayer, ReshapeLayer, RQIntegerDivLayer, RQSiGELULayer, SigmoidLayer, \ - SliceLayer, SoftmaxLayer, SqrtLayer, SubLayer, SwishLayer, TransposeLayer + SliceLayer, SoftmaxLayer, SqrtLayer, SubLayer, SwishLayer, TransposeLayer, \ + TanhLayer, ReduceMaxLayer from Deeploy.Targets.Generic.Parsers import AddParser, AveragePool1DParser, AveragePool2DParser, BatchNormParser, \ CeilParser, ClipParser, ConcatParser, ConvTranspose1DParser, DebugParser, DequantParser, DivParser, DummyParser, \ ExpParser, FlattenParser, FloorParser, GatherParser, GELUParser, GenericConv1DParser, GenericConv2DParser, \ @@ -32,11 +34,12 @@ ITAMaxParser, ITAPartialMaxParser, LayerNormParser, MatMulParser, MaxPool1DParser, MulParser, Pad1DParser, \ Pad2DParser, PowParser, QuantParser, ReduceMeanParser, ReduceSumParser, ReluParser, RequantShiftParser, \ ReshapeParser, RQIntegerDivParser, RQSiGELUParser, SigmoidParser, SliceParser, SoftmaxParser, SqrtParser, \ - SubParser, SwishParser, TransposeParser, UnsqueezeParser, iLayerNormParser, iSoftmaxParser + SubParser, SwishParser, TransposeParser, UnsqueezeParser, iLayerNormParser, iSoftmaxParser, \ + TanhParser, ReduceMaxParser from Deeploy.Targets.Generic.Templates import AllocateTemplate, FreeTemplate from Deeploy.Targets.Generic.TopologyOptimizationPasses.Passes import DequantPatternPass, ExtractPaddingFromConvPass, \ ExtractPaddingFromPoolPass, MatMulAddMergePass, MergeConstAddAndRequantPass, QuantPatternPass, \ - iGELURequantMergePass + iGELURequantMergePass, UnrollConcatPass AddMapper = NodeMapper(AddParser(), BasicAddBindings) SubMapper = NodeMapper(SubParser(), BasicSubBindings) @@ -99,6 +102,11 @@ # They should always generate compiler errors to not accidentally end up in production code DummyMapper = NodeMapper(DummyParser(), [DummyBinding]) +### NEWLY ADDED LAYERS: +TanhMapper = NodeMapper(TanhParser(), BasicTanhBindings) +ReduceMaxMapper = NodeMapper(ReduceMaxParser(), BasicReduceMaxBindings) + + GenericMapping = { 'Add': AddLayer([AddMapper]), 'Sub': SubLayer([SubMapper]), @@ -158,6 +166,10 @@ # # deployment or optimizations with GlobalAveragePool nodes but did not yet # # implement the corresponding kernel # 'GlobalAveragePool': ConvLayer([DummyMapper]), + + ### NEWLY ADDED LAYERS: + 'Tanh' : TanhLayer([TanhMapper]), + 'ReduceMax' : ReduceMaxLayer([ReduceMaxMapper]) } @@ -198,6 +210,7 @@ class GenericStructBuffer(StructBuffer): MergeConstAddAndRequantPass(), ExtractPaddingFromConvPass(), ExtractPaddingFromPoolPass(), + UnrollConcatPass(), RemoveEmptyConvBiasPass(), RemoveOnlySingletonReduceMeanPass(), # DebugPrintPass(r'.*[Mm]at[Mm]ul.*', position = 'after'), diff --git a/Deeploy/Targets/Generic/Templates/ReduceMaxTemplate.py b/Deeploy/Targets/Generic/Templates/ReduceMaxTemplate.py new file mode 100644 index 0000000000..4ca50c2d3f --- /dev/null +++ b/Deeploy/Targets/Generic/Templates/ReduceMaxTemplate.py @@ -0,0 +1,50 @@ +# Copyright (C) 2026 EPFL. +# Solderpad Hardware License, Version 2.1, see LICENSE.md for details. +# SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +# +# File: ReduceMaxTemplate.py +# Author: Mohammad Hossein Nikkhah +# Description: + +from typing import Dict, List, Tuple + +from Deeploy.DeeployTypes import NetworkContext, NodeTemplate, OperatorRepresentation + + +referenceTemplate = NodeTemplate(""" +// ReduceMax (Name: ${nodeName}, Op: ${nodeOp}) +BEGIN_SINGLE_CORE + uint32_t outer_base = 0; + uint32_t inner_index = 0; + uint32_t input_base; + + + for (uint32_t i=0;i<${output_size};i++){ + input_base = outer_base + inner_index; + uint32_t input_offset = input_base; + + + ${data_in_type.referencedType.typeName} max_value = ${data_in}[input_offset]; + + + + for (uint32_t i_a = 0; i_a < ${d_axes}; i_a++) { + // Max operation : + if (max_value < ${data_in}[input_offset]) + max_value = ${data_in}[input_offset]; + + input_offset += ${inner_size}; + } + + ${data_out}[i] = max_value; + + inner_index ++; + + if (inner_index >= ${inner_size}) { + inner_index = 0; + outer_base += ${outer_step}; + } + + } +END_SINGLE_CORE +""") \ No newline at end of file diff --git a/Deeploy/Targets/Generic/Templates/TanhTemplate.py b/Deeploy/Targets/Generic/Templates/TanhTemplate.py new file mode 100644 index 0000000000..b2e9f6063e --- /dev/null +++ b/Deeploy/Targets/Generic/Templates/TanhTemplate.py @@ -0,0 +1,22 @@ +# Copyright (C) 2026 EPFL. +# Solderpad Hardware License, Version 2.1, see LICENSE.md for details. +# SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +# +# File: Tanh.py +# Author: Mohammad Hossein Nikkhah +# Description: + + +from typing import Dict, List, Tuple + +from Deeploy.DeeployTypes import NetworkContext, NodeTemplate, OperatorRepresentation + + +referenceTemplate = NodeTemplate(""" +// Tan (Name: ${nodeName}, Op: ${nodeOp}) +BEGIN_SINGLE_CORE + for (uint32_t i=0;i<${size};i++){ + ${data_out}[i] = tanh(${data_in}[i]); + } +END_SINGLE_CORE +""") diff --git a/Deeploy/Targets/Generic/TopologyOptimizationPasses/Passes.py b/Deeploy/Targets/Generic/TopologyOptimizationPasses/Passes.py index 146bcf699e..fa122f7130 100644 --- a/Deeploy/Targets/Generic/TopologyOptimizationPasses/Passes.py +++ b/Deeploy/Targets/Generic/TopologyOptimizationPasses/Passes.py @@ -1177,3 +1177,65 @@ def __init__(self): name = "_RECOGNIZE_DEQUANT_PASS" super().__init__(graph, _recognize_dequant_fun, name) + + +### EXTRA TOPOLOGY LOWERING PASSES + + +## TODO: for now only replaces 3 input concat layer with 2-2input concat layers +def _unroll_concat_layer_fun(graph: gs.Graph, match: Match, name: str): + """ + This function only works for concat layers with 3 inputs + """ + + matched_nodes = [m for k, m in match.nodes_map.items()] + concat_node = matched_nodes[0] + + if len(concat_node.inputs) != 3: + return graph + + if 'axis' not in concat_node.attrs: + return graph + + axis = concat_node.attrs['axis'] + firstInputShape = copy.deepcopy(concat_node.inputs[0].shape) + if firstInputShape is not None: + shapeAxis = axis if axis >= 0 else axis + len(firstInputShape) + firstInputShape[shapeAxis] += concat_node.inputs[1].shape[shapeAxis] + + intermediate = gs.Variable(name + '_out_0', dtype = concat_node.outputs[0].dtype, shape = firstInputShape) + originalOutputs = list(concat_node.outputs) + + firstConcat = gs.Node(op = 'Concat', + name = name + '_0', + attrs = copy.copy(concat_node.attrs), + inputs = list(concat_node.inputs[:2]), + outputs = [intermediate]) + secondConcat = gs.Node(op = 'Concat', + name = name + '_1', + attrs = copy.copy(concat_node.attrs), + inputs = [intermediate, concat_node.inputs[2]], + outputs = originalOutputs) + + graph.nodes.append(firstConcat) + graph.nodes.append(secondConcat) + + concat_node.inputs.clear() + concat_node.outputs.clear() + graph.cleanup().toposort() + + return graph + + +@contextagnostic +class UnrollConcatPass(ReplaceSequentialPatternPass): + + def __init__(self): + graph = gs.Graph() + inputs = [gs.Variable(name = f'input_{i}') for i in range(3)] + concat_output = graph.layer(inputs = inputs, outputs = ['concat_out'], op = 'Concat', name = 'concat') + graph.outputs.append(concat_output) + graph.inputs = inputs + + name = "_UNROLL_CONCAT_PASS" + super().__init__(graph, _unroll_concat_layer_fun, name) diff --git a/Deeploy/Targets/Generic/TypeCheckers.py b/Deeploy/Targets/Generic/TypeCheckers.py index c2c8d436f8..b4dd65dfc9 100644 --- a/Deeploy/Targets/Generic/TypeCheckers.py +++ b/Deeploy/Targets/Generic/TypeCheckers.py @@ -610,3 +610,41 @@ def _inferNumLevels(self, inputs: List[VariableBuffer], def _inferSignedness(self, inputs: List[VariableBuffer], operatorRepresentation: OperatorRepresentation) -> List[bool]: return [True] + + +### NEWLY ADDED LAYERS: + +class ReduceMaxChecker(SignPropTypeChecker): + + def __init__(self, input_types: Sequence[Type[Pointer]], output_types: Sequence[Type[Pointer]]): + super().__init__(input_types, output_types) + + def _inferNumLevels(self, inputs: List[VariableBuffer], + operatorRepresentation: OperatorRepresentation) -> List[int]: + return [2**(self.input_types[0].referencedType.typeWidth)] + + def _inferSignedness(self, inputs: List[VariableBuffer], + operatorRepresentation: OperatorRepresentation) -> List[bool]: + return [True] + +class TanhChecker(SignPropTypeChecker): + + def __init__(self, input_types: Sequence[Type[Pointer]], output_types: Sequence[Type[Pointer]]): + super().__init__(input_types, output_types) + + def _inferNumLevels(self, inputs: List[VariableBuffer], + operatorRepresentation: OperatorRepresentation) -> List[int]: + return [2**(self.input_types[0].referencedType.typeWidth)] + + def _inferSignedness(self, inputs: List[VariableBuffer], + operatorRepresentation: OperatorRepresentation) -> List[bool]: + return [True] + + + +class FloatConcatChecker(SignPropTypeChecker): + def _inferNumLevels(self, inputs, operatorRepresentation): + return None + + def _inferSignedness(self, inputs, operatorRepresentation): + return None \ No newline at end of file diff --git a/Deeploy/Targets/xheep/Bindings.py b/Deeploy/Targets/xheep/Bindings.py new file mode 100644 index 0000000000..b76518e166 --- /dev/null +++ b/Deeploy/Targets/xheep/Bindings.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2024 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +import itertools + +from Deeploy.AbstractDataTypes import PointerClass +from Deeploy.CommonExtensions.CodeTransformationPasses.MemoryAllocation import ArgumentStructGeneration, \ + MemoryManagementGeneration, MemoryPassthroughGeneration +from Deeploy.CommonExtensions.DataTypes import FloatDataTypes, IntegerDataTypes, SignedIntegerDataTypes, float32_t, \ + int8_t, int32_t, uint8_t +from Deeploy.DeeployTypes import CodeTransformation, NodeBinding +from Deeploy.FutureExtension.CodeTransformationPasses.FutureCodeTransformation import FutureGeneration +from Deeploy.Targets.Generic.Templates import AddTemplate, BatchNormalizationTemplate, ConcatTemplate, ConvTemplate, \ + ConvTransposeTemplate, DebugPrintTemplate, DequantTemplate, DummyTemplate, DWConvTemplate, FloatAddTemplate, \ + FloatAveragePoolTemplate, FloatCeilTemplate, FloatClipTemplate, FloatConvTemplate, FloatDivTemplate, \ + FloatDWConvTemplate, FloatExpTemplate, FloatFloorTemplate, FloatGELUTemplate, FloatGemmTemplate, \ + FloatGlobalAveragePoolTemplate, FloatGlobalMaxPoolTemplate, FloatGroupNormTemplate, FloatHardSigmoidTemplate, \ + FloatHardSwishTemplate, FloatInstanceNormTemplate, FloatLayernormTemplate, FloatMatMulTemplate, \ + FloatMaxPoolTemplate, FloatMulTemplate, FloatPadTemplate, FloatPowTemplate, FloatReduceMeanTemplate, \ + FloatReluTemplate, FloatSigmoidTemplate, FloatSoftmaxTemplate, FloatSqrtTemplate, FloatSubTemplate, \ + FloatSwishTemplate, GatherTemplate, GemmTemplate, IntegerDivTemplate, ITAMaxTemplate, ITAPartialMaxTemplate, \ + MatMulTemplate, MaxPoolTemplate, MulTemplate, PadTemplate, QuantTemplate, ReduceMeanTemplate, ReduceSumTemplate, \ + RequantShiftTemplate, ReshapeTemplate, RQIntegerDivTemplate, RQSiGELUTemplate, SliceTemplate, SubTemplate, \ + TransposeTemplate, iGELUTemplate, iLayernormTemplate, iRMSNormTemplate, iSoftmaxTemplate +from Deeploy.Targets.Generic.TypeCheckers import AddChecker, BatchNormChecker, ConcatChecker, ConvChecker, \ + DebugPrintChecker, DequantChecker, DivChecker, DummyChecker, GatherChecker, GELUChecker, GEMMChecker, \ + LayerNormChecker, MatMulChecker, MaxPoolChecker, MulChecker, PadChecker, QuantChecker, ReduceMeanChecker, \ + ReduceSumChecker, ReluChecker, RequantShiftChecker, ReshapeChecker, RQIntegerDivChecker, SliceChecker, \ + SoftmaxChecker, TransposeChecker + + diff --git a/Deeploy/Targets/xheep/Layers.py b/Deeploy/Targets/xheep/Layers.py new file mode 100644 index 0000000000..af4d00c081 --- /dev/null +++ b/Deeploy/Targets/xheep/Layers.py @@ -0,0 +1,794 @@ +# # SPDX-FileCopyrightText: 2021 ETH Zurich and University of Bologna +# # +# # SPDX-License-Identifier: Apache-2.0 + +# import copy +# from typing import List, Tuple + +# import numpy as np + +# from Deeploy.DeeployTypes import NodeMapper, ONNXLayer, OperatorRepresentation, Shape + + +# class SingleOperationPerElementLayer(ONNXLayer): + +# def computeOps(self): +# return self.mapper.parser.operatorRepresentation['size'] + + +# class ConcatLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class iRMSNormLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class SliceLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class ReshapeLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class GatherLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class GELULayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeOps(self): +# size = self.mapper.parser.operatorRepresentation['size'] +# # RW: Sigmoid approximation +# mul1 = size # Multiply by 1.702 +# neg = size # Negate the result +# exp = size # Compute exponential +# add = size # Add 1 +# div = size # Division for sigmoid +# mul2 = size # Final multiplication by x + +# return mul1 + neg + exp + add + div + mul2 + + +# class GELUGradLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeOps(self): +# size = self.mapper.parser.operatorRepresentation['size'] +# ops_per_element = 9 +# gelu_grad_ops = size * ops_per_element +# return gelu_grad_ops + + +# class iHardswishLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class iNoNormLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeOps(self): +# return self.mapper.parser.operatorRepresentation['size'] * 4 # 2 mul, 1 add, 1 right shift + +# def computeShapes(self, inputShapes: Shape, outputShapes: Shape, operatorRepresentation: OperatorRepresentation, +# channels_first: bool) -> Tuple[Shape]: + +# # JUNGVI: Broadcast the weights and bias to have as many dimensions as the inputs +# inputShapes[1] = [1] * (len(inputShapes[0]) - len(inputShapes[1])) + list(inputShapes[1]) +# inputShapes[2] = inputShapes[1] +# return (inputShapes, outputShapes) + + +# class RQSiGELULayer(GELULayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class RQSiHardswishLayer(iHardswishLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class SoftmaxLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeOps(self): + +# size = self.mapper.parser.operatorRepresentation['size'] +# last_dim_length = self.mapper.parser.operatorRepresentation['lastDimLength'] +# batch_size = size // last_dim_length + +# max_ops = last_dim_length - 1 +# exp_ops = last_dim_length * 2 +# sum_ops = last_dim_length - 1 +# div_ops = last_dim_length +# ops_per_batch = max_ops + exp_ops + sum_ops + div_ops +# total_ops = ops_per_batch * batch_size + +# return total_ops + + +# class SoftmaxGradLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeOps(self): +# input_size = self.mapper.parser.operatorRepresentation['size'] + +# # SoftmaxGrad operation: dy * (y - (y * sum(dy * y))) +# mul_ops = input_size +# sum_ops = input_size +# broadcast_mul_ops = input_size +# sub_ops = input_size +# final_mul_ops = input_size + +# return mul_ops + sum_ops + broadcast_mul_ops + sub_ops + final_mul_ops + + +# class ITAMaxLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class RequantShiftLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeShapes(self, inputShapes: List[Shape], outputShapes: Shape, operatorRepresentation, +# channels_first) -> Tuple[Shape, Shape]: + +# channel_dim = inputShapes[0][1] +# inputShapes[2] = [inputShapes[0][0], channel_dim] + list(inputShapes[2][1:]) +# inputShapes[1] = [inputShapes[0][0], channel_dim] + list(inputShapes[1][1:]) + +# return (inputShapes, outputShapes) + +# def computeOps(self): +# return self.mapper.parser.operatorRepresentation['size'] * 3 # One add, one mul, one div + + +# class AddLayer(SingleOperationPerElementLayer): + +# def computeShapes(self, inputShapes: Shape, outputShapes: Shape, operatorRepresentation, +# channels_first) -> Tuple[Shape, Shape]: + +# if len(inputShapes[0]) > len(inputShapes[1]): +# inputShapes[1] = inputShapes[0] +# else: +# inputShapes[0] = inputShapes[1] + +# outputShapes = [inputShapes[0]] +# return (inputShapes, outputShapes) + + +# SubLayer = AddLayer + + +# class MatMulLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeOps(self): +# return 2 * self.mapper.parser.operatorRepresentation['M'] * self.mapper.parser.operatorRepresentation[ +# 'N'] * self.mapper.parser.operatorRepresentation['O'] * self.mapper.parser.operatorRepresentation['batch'] + +# def computeShapes(self, inputShapes: Tuple[Shape, Shape], outputShapes: Shape, operatorRepresentation, +# channels_first) -> Tuple[Tuple[Shape, Shape], Shape]: + +# A_shape, B_shape = inputShapes +# if len(A_shape) < 2: +# A_shape = [1] * (2 - len(A_shape)) + A_shape + +# if len(B_shape) < 2: +# B_shape = B_shape + [1] * (2 - len(B_shape)) + +# if A_shape[-1] != B_shape[-2]: +# raise ValueError(f"MatMul requires A.shape[-1] == B.shape[-2], but got {A_shape} and {B_shape}") + +# if len(A_shape) > len(B_shape): +# B_shape = [1] * (len(A_shape) - len(B_shape)) + list(B_shape) + +# elif len(A_shape) < len(B_shape): +# A_shape = [1] * (len(B_shape) - len(A_shape)) + list(A_shape) + +# return [A_shape, B_shape], outputShapes + + +# class RQMatMulLayer(MatMulLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeShapes(self, inputShapes: List[Shape], outputShapes: Shape, operatorRepresentation, +# channels_first) -> Tuple[Shape, Shape]: + +# channel_dim = inputShapes[0][1] +# inputShapes[3] = [inputShapes[0][0]] + list(inputShapes[3][1:]) +# inputShapes[2] = [inputShapes[0][0]] + list(inputShapes[2][1:]) + +# return (inputShapes, outputShapes) + +# def computeOps(self): +# matmul = super().computeOps() +# rqs = self.mapper.parser.operatorRepresentation['size'] * 3 +# return matmul + rqs + + +# class PowLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class SqrtLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class DivLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class RQIntegerDivLayer(DivLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class GEMMLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeShapes(self, inputShapes: Shape, outputShapes: Shape, operatorRepresentation, +# channels_first) -> Tuple[Shape, Shape]: +# if operatorRepresentation['transA']: +# M = inputShapes[0][-1] +# else: +# M = inputShapes[0][-2] + +# if operatorRepresentation['transB']: +# N = inputShapes[1][-2] +# else: +# N = inputShapes[1][-1] + +# if len(inputShapes) == 3: +# inputShapes[2] = [M, N] + +# return (inputShapes, outputShapes) + +# def computeOps(self): +# matmul = 2 * self.mapper.parser.operatorRepresentation['M'] * self.mapper.parser.operatorRepresentation[ +# 'N'] * self.mapper.parser.operatorRepresentation['O'] * self.mapper.parser.operatorRepresentation['batch'] +# gemm = matmul + 3 * self.mapper.parser.operatorRepresentation['M'] * self.mapper.parser.operatorRepresentation[ +# 'O'] * self.mapper.parser.operatorRepresentation['batch'] + +# return gemm + + +# class RQGEMMLayer(GEMMLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeShapes(self, inputShapes: List[Shape], outputShapes: Shape, operatorRepresentation, +# channels_first) -> Tuple[Shape, Shape]: +# if operatorRepresentation['transA']: +# M = inputShapes[0][-1] +# else: +# M = inputShapes[0][-2] + +# if operatorRepresentation['transB']: +# N = inputShapes[1][-2] +# else: +# N = inputShapes[1][-1] + +# if len(inputShapes) == 5: +# inputShapes[2] = [M, N] +# inputShapes[4] = [inputShapes[0][0]] + list(inputShapes[4][1:]) +# inputShapes[3] = [inputShapes[0][0]] + list(inputShapes[3][1:]) +# else: +# inputShapes[3] = [inputShapes[0][0]] + list(inputShapes[3][1:]) +# inputShapes[2] = [ +# inputShapes[0][0], +# ] + list(inputShapes[2][1:]) + +# return (inputShapes, outputShapes) + +# def computeOps(self): +# gemm = super().computeOps() +# rqs = self.mapper.parser.operatorRepresentation['size'] * 3 +# return gemm + rqs + + +# class MulLayer(SingleOperationPerElementLayer): + +# def computeShapes(self, inputShapes: Shape, outputShapes: Shape, operatorRepresentation, +# channels_first) -> Tuple[Shape, Shape]: + +# if inputShapes[1] == () or inputShapes[1] == []: +# inputShapes[1] = (1,) + +# if len(inputShapes[0]) > len(inputShapes[1]): +# inputShapes[1] = inputShapes[0] +# else: +# inputShapes[0] = inputShapes[1] +# return (inputShapes, outputShapes) + + +# class ConvLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeShapes(self, inputShapes: Shape, outputShapes: Shape, operatorRepresentation, +# channels_first) -> Tuple[Shape, Shape]: +# if len(inputShapes) == 3: +# inputShapes[2] = inputShapes[1][0] +# return (inputShapes, outputShapes) + +# def computeOps(self): +# if "group" in self.mapper.parser.operatorRepresentation: +# groups = self.mapper.parser.operatorRepresentation['group'] +# else: +# groups = 1 +# opsPerPx = int( +# np.prod(self.mapper.parser.operatorRepresentation['kernel_shape']) * +# self.mapper.parser.operatorRepresentation['ch_im_in'] * +# self.mapper.parser.operatorRepresentation['ch_im_out'] / groups) * 2 +# if 'dim_im_out_y' in self.mapper.parser.operatorRepresentation: +# numPx = self.mapper.parser.operatorRepresentation[ +# 'dim_im_out_x'] * self.mapper.parser.operatorRepresentation['dim_im_out_y'] +# else: +# numPx = self.mapper.parser.operatorRepresentation['dim_im_out_x'] +# return numPx * opsPerPx + + +# class RQSConvLayer(ConvLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeOps(self): +# conv = super().computeOps() + +# if 'dim_im_out_y' in self.mapper.parser.operatorRepresentation: +# rqs = self.mapper.parser.operatorRepresentation['dim_im_out_x'] * self.mapper.parser.operatorRepresentation[ +# 'dim_im_out_y'] * 3 +# else: +# rqs = self.mapper.parser.operatorRepresentation['dim_im_out_x'] * 3 + +# return conv + rqs + + +# class PadLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class MaxPoolLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeOps(self): +# kernel_shape = self.mapper.parser.operatorRepresentation['kernel_shape'] +# elements_per_window = int(np.prod(kernel_shape)) +# data_out_size = self.mapper.parser.operatorRepresentation['data_out_size'] +# comparisons_per_window = elements_per_window - 1 +# total_ops = data_out_size * comparisons_per_window +# return total_ops + + +# class ReduceMeanLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class ReduceSumLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeShapes(self, inputShapes: Shape, outputShapes: Shape, operatorRepresentation, +# channels_first) -> Tuple[Shape, Shape]: +# outputShapes = copy.deepcopy(inputShapes) +# axis = operatorRepresentation['axes'][0] + +# if operatorRepresentation['keepdims']: +# outputShapes[0][axis] = 1 +# else: +# outputShapes[0] = outputShapes[0][:axis] + outputShapes[0][axis + 1:] +# return (inputShapes, outputShapes) + + +# class ReluLayer(SingleOperationPerElementLayer): +# pass + + +# class LayerNormLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeOps(self): +# compAverage = self.mapper.parser.operatorRepresentation['size'] +# compNormalize = self.mapper.parser.operatorRepresentation['size'] +# compSqr = self.mapper.parser.operatorRepresentation['size'] +# compSum = self.mapper.parser.operatorRepresentation['size'] +# compSqrt = self.mapper.parser.operatorRepresentation['size'] +# compDiv = self.mapper.parser.operatorRepresentation['size'] +# return compAverage + compNormalize + compSqr + compSum + compSqrt + compDiv + + +# class LayerNormGradLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class TransposeLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class SoftmaxCrossEntropyLossLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class SoftmaxCrossEntropyLossGradLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class SGDLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class LinearAttentionLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeShapes(self, inputShapes: Shape, outputShapes: Shape, operatorRepresentation, +# channels_first) -> Tuple[Shape, Shape]: +# inputShapes[4] = inputShapes[3][0] +# inputShapes[6] = inputShapes[5][0] +# inputShapes[8] = inputShapes[7][0] +# inputShapes[10] = inputShapes[9][0] + +# return (inputShapes, outputShapes) + +# def computeOps(self): +# # seqLen = self.mapper.parser.operatorRepresentation['in_C'] +# # dim = self.mapper.parser.operatorRepresentation['dim'] +# # dim_head = self.mapper.parser.operatorRepresentation['dim_head'] +# # heads = self.mapper.parser.operatorRepresentation['heads'] +# # QOps = seqLen * dim * dim_head * heads * 2 +# # # WQ * Q (H ) +# # KOps = seqLen * dim * dim_head * heads * 2 +# # # WK * K +# # VOps = seqLen * dim * dim_head * heads * 2 +# # # WV * V +# # KVOps = seqLen * dim_head * dim_head * heads * 2 +# # # Q * KT +# # QKVOps = seqLen * dim_head * dim_head * heads * 2 +# # # N H S S * N H S D -> N H S D +# # OutOps = seqLen * dim_head * heads * dim * 2 +# # # WO * O +# # totOps = QOps + KOps + VOps + KVOps + QKVOps + OutOps +# # return totOps + +# return 0 + + +# class CLCALayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeShapes(self, inputShapes: Shape, outputShapes: Shape, operatorRepresentation, +# channels_first) -> Tuple[Shape, Shape]: +# inputShapes[3] = inputShapes[2][0] +# inputShapes[5] = inputShapes[4][0] +# inputShapes[7] = inputShapes[6][0] +# # WQ Requant +# inputShapes[8] = [operatorRepresentation['dim_head'] * operatorRepresentation['heads'], 1] +# inputShapes[9] = [operatorRepresentation['dim_head'] * operatorRepresentation['heads'], 1] +# inputShapes[10] = [operatorRepresentation['dim_head'] * operatorRepresentation['heads'], 1] +# # WK Requant +# inputShapes[11] = [1, 1] +# inputShapes[12] = [1, 1] +# inputShapes[13] = [1, 1] +# # WV Requant +# inputShapes[14] = [operatorRepresentation['dim_head'] * operatorRepresentation['heads'], 1] +# inputShapes[15] = [operatorRepresentation['dim_head'] * operatorRepresentation['heads'], 1] +# inputShapes[16] = [operatorRepresentation['dim_head'] * operatorRepresentation['heads'], 1] +# # Kdiv Requanat +# inputShapes[17] = [1, 1] +# inputShapes[18] = [1, 1] +# inputShapes[19] = [1, 1] +# # Preattn Requant +# inputShapes[20] = [1, 1] +# inputShapes[21] = [1, 1] +# inputShapes[22] = [1, 1] +# # Postattn Requant +# inputShapes[23] = [1, 1] +# inputShapes[24] = [1, 1] +# inputShapes[25] = [1, 1] +# # WO Requant +# inputShapes[26] = [operatorRepresentation['out_dim'], 1] +# inputShapes[27] = [operatorRepresentation['out_dim'], 1] +# inputShapes[28] = [operatorRepresentation['out_dim'], 1] +# return (inputShapes, outputShapes) + +# def computeOps(self): + +# qLen = self.mapper.parser.operatorRepresentation['q_shape'][-1] +# kLen = self.mapper.parser.operatorRepresentation['kv_shape'][-1] +# inDim = self.mapper.parser.operatorRepresentation['q_shape'][-2] +# heads = self.mapper.parser.operatorRepresentation['heads'] +# dim_head = self.mapper.parser.operatorRepresentation['dim_head'] +# out_dim = self.mapper.parser.operatorRepresentation['out_dim'] + +# # q -> Q +# QOps = qLen * 1 * inDim * heads * dim_head * 2 +# # v -> V +# VOps = kLen * 1 * inDim * heads * dim_head * 2 +# # V -> K +# KOps = kLen * heads * dim_head * 2 +# # KOps = 0 + +# EOps = heads * kLen * heads * dim_head + +# MMKTV = heads * dim_head * kLen * dim_head * 2 +# MMQA = heads * qLen * dim_head * dim_head * 2 +# MMQE = heads * qLen * dim_head * 1 * 2 + +# # Divs, Adds(eps), muls(delta, eps) +# DivOps = heads * qLen * dim_head + heads * qLen + 2 * heads * qLen * dim_head + +# OOps = (heads * dim_head) * qLen * out_dim * 1 * 2 + +# return QOps + VOps + KOps + EOps + MMKTV + MMQA + MMQE + DivOps + OOps + + +# class MHSALayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeShapes(self, inputShapes: Shape, outputShapes: Shape, operatorRepresentation, +# channels_first) -> Tuple[Shape, Shape]: +# outputShapes = [[inputShapes[0][0], operatorRepresentation['heads']] + inputShapes[0][1:]] + +# return (inputShapes, outputShapes) + +# def computeOps(self): +# seqLen = self.mapper.parser.operatorRepresentation['S'] +# dim = self.mapper.parser.operatorRepresentation['dim'] +# dim_head = self.mapper.parser.operatorRepresentation['dim_head'] +# heads = self.mapper.parser.operatorRepresentation['heads'] +# QOps = seqLen * dim * dim_head * heads * 2 +# # WQ * Q (H ) +# KOps = seqLen * dim * dim_head * heads * 2 +# # WK * K +# VOps = seqLen * dim * dim_head * heads * 2 +# # WV * V +# QKOps = seqLen * seqLen * dim_head * heads * 2 +# # Q * KT +# AVOps = seqLen * seqLen * dim_head * heads * 2 +# # N H S S * N H S D -> N H S D +# OutOps = seqLen * dim_head * heads * dim * 2 +# # WO * O +# totOps = QOps + KOps + VOps + QKOps + AVOps + OutOps +# return totOps + + +# class DebugPrintLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class QuantLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class DequantLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + + +# class BatchNormalizationLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeOps(self): +# # 5 operations per element: sub, mul, add, sqrt, div +# B = self.mapper.parser.operatorRepresentation['batch_size'] +# C = self.mapper.parser.operatorRepresentation['channel_size'] +# W = self.mapper.parser.operatorRepresentation['window_size'] +# return B * C * W * 5 + + +# class ConvTransposeLayer(ONNXLayer): + +# def __init__(self, maps: List[NodeMapper]): +# super().__init__(maps) + +# def computeShapes(self, inputShapes: Shape, outputShapes: Shape, operatorRepresentation, +# channels_first) -> Tuple[Shape, Shape]: +# """ +# Infers output shapes for ConvTranspose using only static info. +# - inputShapes[0]: input tensor shape (e.g., [N, C_in, W] for 1D, [N, C_in, H, W] for 2D) +# - inputShapes[1]: weight tensor shape (e.g., [C_in, C_out // group, kW] for 1D) +# - outputShapes[0]: output tensor shape (to be updated) +# """ +# newInputShapes = list(inputShapes) +# newOutputShapes = list(outputShapes) +# group = operatorRepresentation.get('group', 1) +# weight_shape = inputShapes[1] + +# if newOutputShapes and len(newOutputShapes[0]) >= 2: +# # For 1D: weight_shape = [C_in, C_out // group, kW] +# # For 2D: weight_shape = [C_in, C_out // group, kH, kW] +# ch_out = weight_shape[1] * group +# if channels_first: +# newOutputShapes[0][1] = ch_out +# else: +# newOutputShapes[0][-1] = ch_out + +# return newInputShapes, newOutputShapes + +# def computeOps(self): +# opRep = self.mapper.parser.operatorRepresentation + +# groups = opRep.get('group', 1) +# kernel_shape = np.prod(opRep['kernel_shape']) # es. [3, 3] -> 9 +# ch_in = opRep['ch_im_in'] +# ch_out = opRep['ch_im_out'] + +# opsPerPx = int(kernel_shape * ch_in * ch_out / groups) * 2 + +# # ConvTranspose upscales spatial dims, quindi num pixel viene da output +# if 'dim_im_out_y' in opRep: +# numPx = opRep['dim_im_out_x'] * opRep['dim_im_out_y'] +# else: +# numPx = opRep['dim_im_out_x'] + +# return numPx * opsPerPx + + +# class CeilLayer(SingleOperationPerElementLayer): +# pass + + +# class FloorLayer(SingleOperationPerElementLayer): +# pass + + +# class ClipLayer(ONNXLayer): + +# def computeOps(self): +# # compare vs min and max +# return self.mapper.parser.operatorRepresentation['size'] * 2 + + +# class ExpLayer(SingleOperationPerElementLayer): +# pass + + +# class SigmoidLayer(ONNXLayer): + +# def computeOps(self): +# # sigmoid(x) = 1 / (1 + exp(-x)): neg, exp, add, div +# return self.mapper.parser.operatorRepresentation['size'] * 4 + + +# class SwishLayer(ONNXLayer): + +# def computeOps(self): +# # x * sigmoid(x): 4 ops for sigmoid + 1 mul +# return self.mapper.parser.operatorRepresentation['size'] * 5 + + +# class HardSigmoidLayer(ONNXLayer): + +# def computeOps(self): +# # max(0, min(1, alpha*x + beta)): mul, add, clip(min), clip(max) +# return self.mapper.parser.operatorRepresentation['size'] * 4 + + +# class HardSwishLayer(ONNXLayer): + +# def computeOps(self): +# # x * HardSigmoid(x): 4 ops for hard sigmoid + 1 mul +# return self.mapper.parser.operatorRepresentation['size'] * 5 + + +# class InstanceNormLayer(ONNXLayer): + +# def computeOps(self): +# # per element: mean-sum(1) + variance(sub+sq+add=3) + normalize(sub+div=2) + affine(mul+add=2) = 8 +# # per (batch, channel): mean(div=1) + variance(sqrt+div=2) = 3 +# opRep = self.mapper.parser.operatorRepresentation +# B, C, S = int(opRep['batch_size']), int(opRep['num_channels']), int(opRep['spatial']) +# return B * C * (S * 8 + 3) + + +# class GroupNormLayer(ONNXLayer): + +# def computeOps(self): +# # same structure as InstanceNorm: 8 ops/element + 3 ops per (batch, channel) +# opRep = self.mapper.parser.operatorRepresentation +# B, C, S = int(opRep['batch_size']), int(opRep['num_channels']), int(opRep['spatial']) +# return B * C * (S * 8 + 3) + + +# class AveragePoolLayer(ONNXLayer): + +# def computeOps(self): +# opRep = self.mapper.parser.operatorRepresentation +# kernel_elements = int(np.prod(opRep['kernel_shape'])) +# # (kernel_elements - 1) additions + 1 division per output element +# return opRep['data_out_size'] * kernel_elements + + +# class GlobalAveragePoolLayer(ONNXLayer): + +# def computeOps(self): +# opRep = self.mapper.parser.operatorRepresentation +# # (spatial_size - 1) additions + 1 division per output channel +# return int(opRep['batch_size'] * opRep['num_channels'] * opRep['spatial_size']) + + +# class GlobalMaxPoolLayer(ONNXLayer): + +# def computeOps(self): +# opRep = self.mapper.parser.operatorRepresentation +# # (spatial_size - 1) comparisons per output channel +# return int(opRep['batch_size'] * opRep['num_channels'] * (opRep['spatial_size'] - 1)) diff --git a/Deeploy/Targets/xheep/Parsers.py b/Deeploy/Targets/xheep/Parsers.py new file mode 100644 index 0000000000..861666bfff --- /dev/null +++ b/Deeploy/Targets/xheep/Parsers.py @@ -0,0 +1,16 @@ +# Copyright (C) 2026 EPFL. +# Solderpad Hardware License, Version 2.1, see LICENSE.md for details. +# SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +# +# File: Parsers.py +# Author: Mohammad Hossein Nikkhah +# Description: + +import math +from typing import Tuple + +import numpy as np +import onnx_graphsurgeon as gs + +from Deeploy.DeeployTypes import ConstantBuffer, NetworkContext, NodeParser, VariableBuffer + diff --git a/Deeploy/Targets/xheep/Platform.py b/Deeploy/Targets/xheep/Platform.py new file mode 100644 index 0000000000..dba7689dbf --- /dev/null +++ b/Deeploy/Targets/xheep/Platform.py @@ -0,0 +1,241 @@ +# Copyright (C) 2026 EPFL. +# Solderpad Hardware License, Version 2.1, see LICENSE.md for details. +# SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +# +# File: Platform.py +# Author: Mohammad Hossein Nikkhah +# Description: + + +from Deeploy.CommonExtensions.OptimizationPasses.TopologyOptimizationPasses.LoweringOptimizationPasses import \ + RemoveEmptyConvBiasPass, RemoveOnlySingletonReduceMeanPass +from Deeploy.DeeployTypes import ConstantBuffer, DeploymentEngine, DeploymentPlatform, NodeMapper, NodeTemplate, \ + StructBuffer, TopologyOptimizer, TransientBuffer, VariableBuffer +from Deeploy.Targets.Generic.Bindings import BasicAddBindings, BasicAveragePool1DBindings, BasicAveragePool2DBindings, \ + BasicBatchNormBindings, BasicCeilBindings, BasicClipBindings, BasicConcatBindings, BasicConv1DBindings, \ + BasicConv2DBindings, BasicConvTransposeBindings, BasicDebugPrintBindings, BasicDequantBindings, BasicDivBindings, \ + BasicDWConv1DBinding, BasicDWConv2DBindings, BasicExpBindings, BasicFloorBindings, BasicGatherBindings, \ + BasicGELUBindings, BasicGEMMBindings, BasicGlobalAveragePoolBindings, BasicGlobalMaxPoolBindings, \ + BasicGroupNormBindings, BasicHardSigmoidBindings, BasicHardSwishBindings, BasicInstanceNormBindings, \ + BasicITAPartialSoftmaxBinding, BasicITASoftmaxBinding, BasicLayerNormBindings, BasicMatMulBindings, \ + BasicMaxPool1DBindings, BasicMaxPool2DBindings, BasicMulBindings, BasicPad1DBindings, BasicPad2DBindings, \ + BasicPowBindings, BasicQuantBindings, BasicReduceMeanBindings, BasicReduceSumBindings, BasicReluBinding, \ + BasicReshapeBindings, BasicRQIntegerDivBinding, BasicRQSBindings, BasicRQSGELUBinding, BasicSigmoidBindings, \ + BasicSliceBindings, BasicSoftmaxBindings, BasicSqrtBindings, BasicSubBindings, BasicSwishBindings, \ + BasicTransposeBindings, DummyBinding, \ + BasicTanhBindings, BasicReduceMaxBindings +from Deeploy.Targets.Generic.Layers import AddLayer, AveragePoolLayer, BatchNormalizationLayer, CeilLayer, ClipLayer, \ + ConcatLayer, ConvLayer, ConvTransposeLayer, DebugPrintLayer, DequantLayer, DivLayer, ExpLayer, FloorLayer, \ + GatherLayer, GELULayer, GEMMLayer, GlobalAveragePoolLayer, GlobalMaxPoolLayer, GroupNormLayer, InstanceNormLayer, \ + ITAMaxLayer, LayerNormLayer, MatMulLayer, MaxPoolLayer, MulLayer, PadLayer, PowLayer, QuantLayer, ReduceMeanLayer, \ + ReduceSumLayer, ReluLayer, RequantShiftLayer, ReshapeLayer, RQIntegerDivLayer, RQSiGELULayer, SigmoidLayer, \ + SliceLayer, SoftmaxLayer, SqrtLayer, SubLayer, SwishLayer, TransposeLayer, \ + TanhLayer, ReduceMaxLayer +from Deeploy.Targets.Generic.Parsers import AddParser, AveragePool1DParser, AveragePool2DParser, BatchNormParser, \ + CeilParser, ClipParser, ConcatParser, ConvTranspose1DParser, DebugParser, DequantParser, DivParser, DummyParser, \ + ExpParser, FlattenParser, FloorParser, GatherParser, GELUParser, GenericConv1DParser, GenericConv2DParser, \ + GenericDWConv1DParser, GenericDWConv2DParser, GenericGEMMParser, GenericMaxPool2DParser, GlobalAveragePoolParser, \ + GlobalMaxPoolParser, GroupNormParser, HardSigmoidParser, HardSwishParser, InstanceNormParser, IntegerDivParser, \ + ITAMaxParser, ITAPartialMaxParser, LayerNormParser, MatMulParser, MaxPool1DParser, MulParser, Pad1DParser, \ + Pad2DParser, PowParser, QuantParser, ReduceMeanParser, ReduceSumParser, ReluParser, RequantShiftParser, \ + ReshapeParser, RQIntegerDivParser, RQSiGELUParser, SigmoidParser, SliceParser, SoftmaxParser, SqrtParser, \ + SubParser, SwishParser, TransposeParser, UnsqueezeParser, iLayerNormParser, iSoftmaxParser, \ + TanhParser, ReduceMaxParser +from Deeploy.Targets.Generic.Templates import AllocateTemplate, FreeTemplate + +from Deeploy.Targets.Generic.Platform import GenericVariableBuffer, GenericConstantBuffer, GenericTransientBuffer, GenericStructBuffer + +from Deeploy.Targets.Generic.TopologyOptimizationPasses.Passes import DequantPatternPass, ExtractPaddingFromConvPass, \ + ExtractPaddingFromPoolPass, MatMulAddMergePass, MergeConstAddAndRequantPass, QuantPatternPass, \ + iGELURequantMergePass, UnrollConcatPass + +AddMapper = NodeMapper(AddParser(), BasicAddBindings) +SubMapper = NodeMapper(SubParser(), BasicSubBindings) +Conv1DMapper = NodeMapper(GenericConv1DParser(), BasicConv1DBindings) +Conv2DMapper = NodeMapper(GenericConv2DParser(), BasicConv2DBindings) +ConcatMapper = NodeMapper(ConcatParser(), BasicConcatBindings) +DebugMapper = NodeMapper(DebugParser(), BasicDebugPrintBindings) +DWConv1DMapper = NodeMapper(GenericDWConv1DParser(), [BasicDWConv1DBinding]) +DWConv2DMapper = NodeMapper(GenericDWConv2DParser(), BasicDWConv2DBindings) +FlattenMapper = NodeMapper(FlattenParser(), BasicReshapeBindings) +GatherMapper = NodeMapper(GatherParser(), BasicGatherBindings) +GELUMapper = NodeMapper(GELUParser(), BasicGELUBindings) +GEMMMapper = NodeMapper(GenericGEMMParser(), BasicGEMMBindings) +LayerNormMapper = NodeMapper(LayerNormParser(), BasicLayerNormBindings) +iLayerNormMapper = NodeMapper(iLayerNormParser(), BasicLayerNormBindings) +DivMapper = NodeMapper(DivParser(), BasicDivBindings) +IntegerDivMapper = NodeMapper(IntegerDivParser(), BasicDivBindings) +ITAMaxMapper = NodeMapper(ITAMaxParser(), [BasicITASoftmaxBinding]) +ITAPartialMaxMapper = NodeMapper(ITAPartialMaxParser(), [BasicITAPartialSoftmaxBinding]) +MatMulMapper = NodeMapper(MatMulParser(), BasicMatMulBindings) +MaxPool2DMapper = NodeMapper(GenericMaxPool2DParser(), BasicMaxPool2DBindings) +MaxPool1DMapper = NodeMapper(MaxPool1DParser(), BasicMaxPool1DBindings) +MulMapper = NodeMapper(MulParser(), BasicMulBindings) +PowMapper = NodeMapper(PowParser(), BasicPowBindings) +SqrtMapper = NodeMapper(SqrtParser(), BasicSqrtBindings) +Pad1DMapper = NodeMapper(Pad1DParser(), BasicPad1DBindings) +Pad2DMapper = NodeMapper(Pad2DParser(), BasicPad2DBindings) +ReduceMeanMapper = NodeMapper(ReduceMeanParser(), BasicReduceMeanBindings) +ReduceSumMapper = NodeMapper(ReduceSumParser(), BasicReduceSumBindings) +ReluMapper = NodeMapper(ReluParser(), [BasicReluBinding]) +RequantShiftMapper = NodeMapper(RequantShiftParser(), BasicRQSBindings) +ReshapeMapper = NodeMapper(ReshapeParser(), BasicReshapeBindings) +RQGELUMapper = NodeMapper(RQSiGELUParser(), [BasicRQSGELUBinding]) +RQIntegerDivMapper = NodeMapper(RQIntegerDivParser(), [BasicRQIntegerDivBinding]) +SoftmaxMapper = NodeMapper(SoftmaxParser(), BasicSoftmaxBindings) +iSoftmaxMapper = NodeMapper(iSoftmaxParser(), BasicSoftmaxBindings) +TransposeMapper = NodeMapper(TransposeParser(), BasicTransposeBindings) +UnsqueezeMapper = NodeMapper(UnsqueezeParser(), BasicReshapeBindings) +QuantMapper = NodeMapper(QuantParser(), BasicQuantBindings) +DequantMapper = NodeMapper(DequantParser(), BasicDequantBindings) +BatchNormalizationMapper = NodeMapper(BatchNormParser(), BasicBatchNormBindings) +ConvTransposeMapper = NodeMapper(ConvTranspose1DParser(), BasicConvTransposeBindings) +SliceMapper = NodeMapper(SliceParser(), BasicSliceBindings) +CeilMapper = NodeMapper(CeilParser(), BasicCeilBindings) +FloorMapper = NodeMapper(FloorParser(), BasicFloorBindings) +ClipMapper = NodeMapper(ClipParser(), BasicClipBindings) +ExpMapper = NodeMapper(ExpParser(), BasicExpBindings) +SigmoidMapper = NodeMapper(SigmoidParser(), BasicSigmoidBindings) +SwishMapper = NodeMapper(SwishParser(), BasicSwishBindings) +HardSigmoidMapper = NodeMapper(HardSigmoidParser(), BasicHardSigmoidBindings) +HardSwishMapper = NodeMapper(HardSwishParser(), BasicHardSwishBindings) +InstanceNormMapper = NodeMapper(InstanceNormParser(), BasicInstanceNormBindings) +GroupNormMapper = NodeMapper(GroupNormParser(), BasicGroupNormBindings) +AveragePool1DMapper = NodeMapper(AveragePool1DParser(), BasicAveragePool1DBindings) +AveragePool2DMapper = NodeMapper(AveragePool2DParser(), BasicAveragePool2DBindings) +GlobalAveragePoolMapper = NodeMapper(GlobalAveragePoolParser(), BasicGlobalAveragePoolBindings) +GlobalMaxPoolMapper = NodeMapper(GlobalMaxPoolParser(), BasicGlobalMaxPoolBindings) + +# Dummy nodes are intended for development purposes only! +# They should always generate compiler errors to not accidentally end up in production code +DummyMapper = NodeMapper(DummyParser(), [DummyBinding]) + +### NEWLY ADDED LAYERS: +TanhMapper = NodeMapper(TanhParser(), BasicTanhBindings) +ReduceMaxMapper = NodeMapper(ReduceMaxParser(), BasicReduceMaxBindings) + + +XheepMapping = { + 'Add': AddLayer([AddMapper]), + 'Sub': SubLayer([SubMapper]), + 'Conv': ConvLayer([Conv2DMapper, DWConv2DMapper, Conv1DMapper, DWConv1DMapper]), + 'Concat': ConcatLayer([ConcatMapper]), + 'DebugPrint': DebugPrintLayer([DebugMapper]), + 'Div': DivLayer([DivMapper]), + 'Flatten': ReshapeLayer([FlattenMapper]), + 'Gather': GatherLayer([GatherMapper]), + 'Gemm': GEMMLayer([GEMMMapper]), + 'iGELU': GELULayer([GELUMapper]), + 'Gelu': GELULayer([GELUMapper]), + 'LayerNormalization': LayerNormLayer([LayerNormMapper]), + 'iLayerNorm': LayerNormLayer([iLayerNormMapper]), + 'IntegerDiv': DivLayer([IntegerDivMapper]), + 'IntegerMean': ReduceMeanLayer([ReduceMeanMapper]), + 'Softmax': SoftmaxLayer([SoftmaxMapper]), + 'iSoftmax': SoftmaxLayer([iSoftmaxMapper]), + 'ITAMax': ITAMaxLayer([ITAMaxMapper]), + 'ITAPartialMax': ITAMaxLayer([ITAPartialMaxMapper]), + 'MatMul': GEMMLayer([MatMulMapper]), + 'MatMulInteger': MatMulLayer([MatMulMapper]), + 'MaxPool': MaxPoolLayer([MaxPool1DMapper, MaxPool2DMapper]), + 'Mul': MulLayer([MulMapper]), + 'Pow': PowLayer([PowMapper]), + 'Sqrt': SqrtLayer([SqrtMapper]), + 'Pad': PadLayer([Pad1DMapper, Pad2DMapper]), + 'ReduceMean': ReduceMeanLayer([ReduceMeanMapper]), + 'ReduceSum': ReduceSumLayer([ReduceSumMapper]), + 'Relu': ReluLayer([ReluMapper]), + 'RequantizediGELU': RQSiGELULayer([RQGELUMapper]), + 'RequantShift': RequantShiftLayer([RequantShiftMapper]), + 'Reshape': ReshapeLayer([ReshapeMapper]), + 'RQIntegerDiv': RQIntegerDivLayer([RQIntegerDivMapper]), + 'Squeeze': ReshapeLayer([UnsqueezeMapper]), + 'Transpose': TransposeLayer([TransposeMapper]), + 'Unsqueeze': ReshapeLayer([UnsqueezeMapper]), + 'Slice': SliceLayer([SliceMapper]), + 'Quant': QuantLayer([QuantMapper]), + 'Dequant': DequantLayer([DequantMapper]), + 'BatchNormalization': BatchNormalizationLayer([BatchNormalizationMapper]), + 'ConvTranspose': ConvTransposeLayer([ConvTransposeMapper]), + 'Ceil': CeilLayer([CeilMapper]), + 'Floor': FloorLayer([FloorMapper]), + 'Clip': ClipLayer([ClipMapper]), + 'Exp': ExpLayer([ExpMapper]), + 'Sigmoid': SigmoidLayer([SigmoidMapper]), + 'Swish': SwishLayer([SwishMapper]), + 'HardSigmoid': SigmoidLayer([HardSigmoidMapper]), + 'HardSwish': SwishLayer([HardSwishMapper]), + 'InstanceNormalization': InstanceNormLayer([InstanceNormMapper]), + 'GroupNormalization': GroupNormLayer([GroupNormMapper]), + 'AveragePool': AveragePoolLayer([AveragePool1DMapper, AveragePool2DMapper]), + 'GlobalAveragePool': GlobalAveragePoolLayer([GlobalAveragePoolMapper]), + 'GlobalMaxPool': GlobalMaxPoolLayer([GlobalMaxPoolMapper]), + # # For example, you can use the DummpyMapper, in case you want to test + # # deployment or optimizations with GlobalAveragePool nodes but did not yet + # # implement the corresponding kernel + # 'GlobalAveragePool': ConvLayer([DummyMapper]), + + ### NEWLY ADDED LAYERS: + 'Tanh' : TanhLayer([TanhMapper]), + 'ReduceMax' : ReduceMaxLayer([ReduceMaxMapper]) +} + + +class XHeepVariableBuffer(GenericVariableBuffer): + def __init__(self, name = '', shape=..., aliases = None): + super().__init__(name, shape, aliases) + + +class XHeepTransientBuffer(GenericTransientBuffer): + def __init__(self, name = '', size=0): + super().__init__(name, size) + +class XHeepConstantBuffer(GenericConstantBuffer): + def __init__(self, name = '', shape=..., values=...): + super().__init__(name, shape, values) + + + +class XHeepStructBuffer(GenericStructBuffer): + def __init__(self, name, structDict): + super().__init__(name, structDict) + + +XHeepOptimizer = TopologyOptimizer( + [ + QuantPatternPass(), + DequantPatternPass(), + iGELURequantMergePass(), + MatMulAddMergePass(), + MergeConstAddAndRequantPass(), + ExtractPaddingFromConvPass(), + ExtractPaddingFromPoolPass(), + UnrollConcatPass(), + RemoveEmptyConvBiasPass(), + RemoveOnlySingletonReduceMeanPass(), + # DebugPrintPass(r'.*[Mm]at[Mm]ul.*', position = 'after'), + ], + name = "XHeepOptimizer") + + +## TODO: move to crt code +includeList = ["DeeployBasicMath.h", "csr.h"] +initCode = """ + CSR_SET_BITS(CSR_REG_MSTATUS, (0x1 << 13)); // ENABLES FP INSTRUCTIONS +""" + +class XHeepEngine(DeploymentEngine): + + def __init__(self, name: str, Mapping = XheepMapping, initCode: str = initCode, includeList = includeList) -> None: + super().__init__(name, Mapping, initCode, includeList) + + +class XHeepPlatform(DeploymentPlatform): + + def __init__(self, + engines = [XHeepEngine("XHeep")], + variableBuffer = XHeepVariableBuffer, + constantBuffer = XHeepConstantBuffer, + structBuffer = XHeepStructBuffer, + transientBuffer = XHeepTransientBuffer): + super().__init__(engines, variableBuffer, constantBuffer, structBuffer, transientBuffer) diff --git a/Deeploy/Targets/xheep/Templates/__init__.py b/Deeploy/Targets/xheep/Templates/__init__.py new file mode 100644 index 0000000000..be436b64a3 --- /dev/null +++ b/Deeploy/Targets/xheep/Templates/__init__.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2024 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +from . import * diff --git a/Deeploy/Targets/xheep/TileConstraints/__init__.py b/Deeploy/Targets/xheep/TileConstraints/__init__.py new file mode 100644 index 0000000000..be436b64a3 --- /dev/null +++ b/Deeploy/Targets/xheep/TileConstraints/__init__.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2024 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +from . import * diff --git a/Deeploy/Targets/xheep/TopologyOptimizationPasses/Passes.py b/Deeploy/Targets/xheep/TopologyOptimizationPasses/Passes.py new file mode 100644 index 0000000000..7b24a5346b --- /dev/null +++ b/Deeploy/Targets/xheep/TopologyOptimizationPasses/Passes.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2023 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +import copy +from collections import OrderedDict +from functools import partial +from typing import List + +import numpy as np +import onnx_graphsurgeon as gs + +from Deeploy.CommonExtensions.OptimizationPasses.Matchers import BranchingMatcher, Match, NonBranchingMatcher +from Deeploy.CommonExtensions.OptimizationPasses.PassClasses import ReplaceSequentialPatternPass, contextagnostic + diff --git a/Deeploy/Targets/xheep/TopologyOptimizationPasses/__init__.py b/Deeploy/Targets/xheep/TopologyOptimizationPasses/__init__.py new file mode 100644 index 0000000000..be436b64a3 --- /dev/null +++ b/Deeploy/Targets/xheep/TopologyOptimizationPasses/__init__.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2024 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +from . import * diff --git a/Deeploy/Targets/xheep/TypeCheckers.py b/Deeploy/Targets/xheep/TypeCheckers.py new file mode 100644 index 0000000000..1b6e49948a --- /dev/null +++ b/Deeploy/Targets/xheep/TypeCheckers.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2021 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +from typing import List, Optional, Sequence, Type + +import numpy as np + +from Deeploy.AbstractDataTypes import Pointer +from Deeploy.CommonExtensions.TypeCheckers.SignPropTypeChecker import SignPropTypeChecker +from Deeploy.DeeployTypes import ConstantBuffer, OperatorRepresentation, VariableBuffer + diff --git a/Deeploy/Targets/xheep/__init__.py b/Deeploy/Targets/xheep/__init__.py new file mode 100644 index 0000000000..be436b64a3 --- /dev/null +++ b/Deeploy/Targets/xheep/__init__.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2024 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +from . import * diff --git a/DeeployTest/CMakeLists.txt b/DeeployTest/CMakeLists.txt index b7f3535790..b3ecaab3c6 100644 --- a/DeeployTest/CMakeLists.txt +++ b/DeeployTest/CMakeLists.txt @@ -50,6 +50,8 @@ elseif(DEEPLOY_ARCH STREQUAL SNITCH) add_subdirectory(Platforms/Snitch) elseif(DEEPLOY_ARCH STREQUAL CHIMERA) add_subdirectory(Platforms/Chimera) +elseif(DEEPLOY_ARCH STREQUAL XHEEP) + add_subdirectory(Platforms/Xheep) elseif(platform STREQUAL GAP9) # Search for hex files generated by Python code generator diff --git a/DeeployTest/Platforms/Xheep/CMakeLists.txt b/DeeployTest/Platforms/Xheep/CMakeLists.txt new file mode 100644 index 0000000000..f016ac54ce --- /dev/null +++ b/DeeployTest/Platforms/Xheep/CMakeLists.txt @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2024 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +set(ProjectId ${TESTNAME}) + +file(GLOB_RECURSE SOURCES + main.c +) + +link_directories(${ProjectId}/../../${GENERATED_SOURCE}) + +add_deeploy_executable(${ProjectId} EXCLUDE_FROM_ALL ${SOURCES} ) +add_xheep_verilator_simulation(${ProjectId}) +target_link_libraries(${ProjectId} PRIVATE network deeploylib) +# RUN WANG: Link math Lib to Generic Target +target_link_libraries(${ProjectId} PRIVATE m) + +link_compile_dump(${TESTNAME}) diff --git a/DeeployTest/Platforms/Xheep/main.c b/DeeployTest/Platforms/Xheep/main.c new file mode 100644 index 0000000000..7cc26dbe28 --- /dev/null +++ b/DeeployTest/Platforms/Xheep/main.c @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2023 ETH Zurich and University of Bologna + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "Network.h" +#include "testinputs.h" +#include "testoutputs.h" +#include "timer_sdk.h" + +int main() { + + printf("Initializing network...\r\n"); + + + InitNetwork(0, 1); + + for (uint32_t buf = 0; buf < DeeployNetwork_num_inputs; buf++) { + memcpy(DeeployNetwork_inputs[buf], testInputVector[buf], + DeeployNetwork_inputs_bytes[buf]); + } + + uint32_t timer_val; + printf("Running network...\r\n"); + timer_cycles_init(); + timer_start(); + RunNetwork(0, 1); + + timer_val = timer_stop(); + + int32_t tot_err = 0; + uint32_t tot = 0; + OUTPUTTYPE diff; + OUTPUTTYPE expected, actual; + + for (uint32_t buf = 0; buf < DeeployNetwork_num_outputs; buf++) { + tot += DeeployNetwork_outputs_bytes[buf] / sizeof(OUTPUTTYPE); + for (uint32_t i = 0; + i < DeeployNetwork_outputs_bytes[buf] / sizeof(OUTPUTTYPE); i++) { + expected = ((OUTPUTTYPE *)testOutputVector[buf])[i]; + actual = ((OUTPUTTYPE *)DeeployNetwork_outputs[buf])[i]; + diff = expected - actual; + +#if ISOUTPUTFLOAT == 1 + // RUNWANG: Allow margin of error for float32_t + if ((diff < -1e-4) || (diff > 1e-4)) { + tot_err += 1; + printf("Expected: %10.6f ", (float)expected); + printf("Actual: %10.6f ", (float)actual); + printf("Diff: %10.6f at Index %12u in Output %u\r\n", (float)diff, i, + buf); + } +#else + // RUNWANG: No margin for integer comparison + if (diff != 0) { + tot_err += 1; + printf("Expected: %4d ", expected); + printf("Actual: %4d ", actual); + printf("Diff: %4d at Index %12u in Output %u\r\n", diff, i, buf); + } +#endif + } + } + + printf("Errors: %d out of %d \r\n", tot_err, tot); + printf("Model Execution time (cycles) : %i\n", timer_val); + + return tot_err; +} diff --git a/DeeployTest/deeployRunner_xheep.py b/DeeployTest/deeployRunner_xheep.py new file mode 100644 index 0000000000..26429d9555 --- /dev/null +++ b/DeeployTest/deeployRunner_xheep.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# SPDX-FileCopyrightText: 2025 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +import sys + +from testUtils.deeployRunner import main + + +def setup_xheep_defaults(parser): + """ + Setting up default values for X-HEEP platform + """ + parser.set_defaults( + toolchain="GCC", + toolchain_install_dir="/app/tools/riscv/", + skipsim=False, ## TODO : Remove this default after adding verilator simulation + cmake=[ + "-DXHEEP_HOME=/app/x-heep", + "-DXHEEP_TARGET=sim", + # "-DXHEEP_LINKER=flash_load", + "-DXHEEP_LINKER=on_chip", + ], + ) + + +if __name__ == "__main__": + #TODO : make the co-simulation with X-HEEP repository (Verilator) + sys.exit(main(default_platform = "Xheep", default_simulator = "verilator", tiling_enabled = False, parser_setup_callback=setup_xheep_defaults)) diff --git a/DeeployTest/testUtils/core/config.py b/DeeployTest/testUtils/core/config.py index e932c23962..75eb327495 100644 --- a/DeeployTest/testUtils/core/config.py +++ b/DeeployTest/testUtils/core/config.py @@ -13,7 +13,7 @@ class DeeployTestConfig: test_name: str test_dir: str platform: str - simulator: Literal['gvsoc', 'banshee', 'qemu', 'vsim', 'vsim.gui', 'host', 'none'] + simulator: Literal['gvsoc', 'banshee', 'qemu', 'vsim', 'vsim.gui', 'verilator', 'host', 'none'] tiling: bool gen_dir: str build_dir: str diff --git a/DeeployTest/testUtils/deeployRunner.py b/DeeployTest/testUtils/deeployRunner.py index bad25ee7f5..0f3f43e3e1 100644 --- a/DeeployTest/testUtils/deeployRunner.py +++ b/DeeployTest/testUtils/deeployRunner.py @@ -367,6 +367,7 @@ def main(default_platform: Optional[str] = None, "chimera": "Chimera", "softhier": "SoftHier", "xdna2": "XDNA2", + "xheep": "Xheep" } if args.platform: diff --git a/DeeployTest/testUtils/platformMapping.py b/DeeployTest/testUtils/platformMapping.py index 9155ed77ae..4809e4249d 100644 --- a/DeeployTest/testUtils/platformMapping.py +++ b/DeeployTest/testUtils/platformMapping.py @@ -29,8 +29,9 @@ from Deeploy.Targets.Snitch.Platform import SnitchOptimizer, SnitchPlatform from Deeploy.Targets.SoftHier.Deployer import SoftHierDeployer from Deeploy.Targets.SoftHier.Platform import SoftHierOptimizer, SoftHierPlatform +from Deeploy.Targets.xheep.Platform import XHeepOptimizer, XHeepPlatform -_SIGNPROP_PLATFORMS = ["Apollo3", "Apollo4", "QEMU-ARM", "Generic", "MemPool", "SoftHier"] +_SIGNPROP_PLATFORMS = ["Apollo3", "Apollo4", "QEMU-ARM", "Generic", "MemPool", "SoftHier", "Xheep"] _NONSIGNPROP_PLATFORMS = ["Siracusa", "Siracusa_w_neureka", "PULPOpen", "Snitch", "Chimera", "GAP9", "XDNA2"] _PLATFORMS = _SIGNPROP_PLATFORMS + _NONSIGNPROP_PLATFORMS @@ -79,6 +80,9 @@ def mapPlatform(platformName: str) -> Tuple[DeploymentPlatform, bool]: elif platformName == "XDNA2": from Deeploy.Targets.XDNA2.Platform import XDNA2Platform Platform = XDNA2Platform() + + elif platformName == "Xheep": + Platform = XHeepPlatform() else: raise RuntimeError(f"Deployment platform {platformName} is not implemented") @@ -173,7 +177,7 @@ def mapDeployer(platform: DeploymentPlatform, deeployStateDir = deeployStateDir, inputOffsets = inputOffsets) - elif isinstance(platform, GenericPlatform): + elif isinstance(platform, (GenericPlatform, XHeepPlatform)): # WIESEP: CMSIS performs add-multiply-divide and we normally do multiply-add-divide # Because these deployer were fine-tuned with a add-multiply-divide aware deployer can emulate this # behavior with the EmulateCMSISRequantPass @@ -276,7 +280,9 @@ def mapDeployer(platform: DeploymentPlatform, name = name, default_channels_first = default_channels_first, deeployStateDir = deeployStateDir) - + + # TODO: add a branch for X-HEEP if needed + else: # Lazy-import XDNA2 to avoid requiring mlir-aie on non-XDNA2 platforms try: diff --git a/DeeployTest/testUtils/pytestRunner.py b/DeeployTest/testUtils/pytestRunner.py index c0a597e587..b94ec488b1 100644 --- a/DeeployTest/testUtils/pytestRunner.py +++ b/DeeployTest/testUtils/pytestRunner.py @@ -31,7 +31,7 @@ def get_worker_id() -> str: def create_test_config( test_name: str, platform: str, - simulator: Literal['gvsoc', 'banshee', 'qemu', 'vsim', 'vsim.gui', 'host', 'board', 'none'], + simulator: Literal['gvsoc', 'banshee', 'qemu', 'vsim', 'vsim.gui', 'verilator', 'host', 'board', 'none'], deeploy_test_dir: str, toolchain: str, toolchain_dir: Optional[str], diff --git a/TargetLibraries/xheep/CMakeLists.txt b/TargetLibraries/xheep/CMakeLists.txt new file mode 100644 index 0000000000..d1a79855b4 --- /dev/null +++ b/TargetLibraries/xheep/CMakeLists.txt @@ -0,0 +1,38 @@ +# Copyright (C) 2026 EPFL. +# Solderpad Hardware License, Version 2.1, see LICENSE.md for details. +# SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +# +# File: CMakeLists.txt +# Author: Mohammad Hossein Nikkhah +# Description: + +if(NOT DEFINED XHEEP_CRT_SOURCES OR NOT DEFINED XHEEP_RUNTIME_SOURCES) + message(FATAL_ERROR "Include cmake/xheep/xheep.cmake before TargetLibraries/xheep.") +endif() + +if(NOT XHEEP_RUNTIME_SOURCES) + message(FATAL_ERROR "XHEEP_RUNTIME_SOURCES is empty. Check XHEEP_HOME and x-heep source collection.") +endif() + +set_source_files_properties(${XHEEP_CRT_SOURCES} PROPERTIES COMPILE_FLAGS -DLANGUAGE_ASSEMBLY) + +add_library(xheep-runtime OBJECT + ${XHEEP_CRT_SOURCES} + ${XHEEP_RUNTIME_SOURCES} +) + +# Keep X-HEEP startup/runtime objects out of project-wide LTO. +set_property(TARGET xheep-runtime PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE) + +target_include_directories(xheep-runtime SYSTEM PUBLIC + ${XHEEP_INCLUDE_DIRS} +) + +add_library(deeployxheep INTERFACE) + +target_include_directories(deeployxheep INTERFACE + ${XHEEP_INCLUDE_DIRS} +) + +target_link_libraries(deeployxheep INTERFACE xheep-runtime) +target_sources(deeployxheep INTERFACE $) diff --git a/cmake/Util.cmake b/cmake/Util.cmake index 1e54dc680b..2425603369 100644 --- a/cmake/Util.cmake +++ b/cmake/Util.cmake @@ -16,6 +16,19 @@ macro(add_deeploy_executable name) TARGET ${name} POST_BUILD COMMAND ${CMAKE_OBJDUMP} -dhS $ > $.s) + if(DEEPLOY_ARCH STREQUAL XHEEP) + if(XHEEP_LINKER STREQUAL flash_load OR XHEEP_LINKER STREQUAL flash_exec) + add_custom_command( + TARGET ${name} + POST_BUILD + COMMAND ${CMAKE_OBJCOPY} -O verilog --adjust-vma=-0x40000000 $ $/${name}.hex) + else() + add_custom_command( + TARGET ${name} + POST_BUILD + COMMAND ${CMAKE_OBJCOPY} -O verilog $ $/${name}.hex) + endif() + endif() endmacro() macro(link_compile_dump name) diff --git a/cmake/xheep/toolchain_gcc.cmake b/cmake/xheep/toolchain_gcc.cmake new file mode 100644 index 0000000000..19a0456bb7 --- /dev/null +++ b/cmake/xheep/toolchain_gcc.cmake @@ -0,0 +1,59 @@ +# Copyright (C) 2026 EPFL. +# Solderpad Hardware License, Version 2.1, see LICENSE.md for details. +# SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +# +# File: toolchain_gcc.cmake +# Author: Mohammad Hossein Nikkhah +# Description: + +set(TOOLCHAIN_PREFIX ${TOOLCHAIN_INSTALL_DIR}/bin/riscv32-unknown-elf) + +set(CMAKE_SYSTEM_NAME Generic) + +set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc) +set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++) +set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER}) +set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}-objcopy) +set(CMAKE_OBJDUMP ${TOOLCHAIN_PREFIX}-objdump) +set(CMAKE_AR ${TOOLCHAIN_PREFIX}-ar) +set(SIZE ${TOOLCHAIN_PREFIX}-size) + +set(ISA rv32imfc_zicsr CACHE STRING "X-HEEP RISC-V ISA") +# set(ISA rv32imc_zicsr CACHE STRING "X-HEEP RISC-V ISA") + + +set(ABI ilp32 CACHE STRING "X-HEEP RISC-V ABI") +set(CMAKE_SYSTEM_PROCESSOR ${ISA} CACHE STRING "X-HEEP RISC-V ISA") + + +set(CMAKE_EXECUTABLE_SUFFIX ".elf") + +add_compile_options( + -march=${ISA} + -mabi=${ABI} + -ffunction-sections + -fdata-sections + -O2 + -g + -MMD + -MP +) + +add_link_options( + -MMD + -MP + -march=${ISA} + -mabi=${ABI} + -nostartfiles + -nostdlib + -Wl,--print-memory-usage +) + +link_libraries( + -lc + -lm + -lgcc +) + +add_compile_definitions(__LINK_LD) +add_compile_definitions(__TOOLCHAIN_GCC__) diff --git a/cmake/xheep/toolchain_llvm.cmake b/cmake/xheep/toolchain_llvm.cmake new file mode 100644 index 0000000000..5aa73f883f --- /dev/null +++ b/cmake/xheep/toolchain_llvm.cmake @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2024 ETH Zurich and University of Bologna +# +# SPDX-License-Identifier: Apache-2.0 + +set(TOOLCHAIN_PREFIX ${TOOLCHAIN_INSTALL_DIR}/bin) + +set(CMAKE_SYSTEM_NAME Generic) + +set(LLVM_TAG llvm) + +set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}/clang) +set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}/clang++) +set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}/clang) +set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}/${LLVM_TAG}-objcopy) +set(CMAKE_OBJDUMP ${TOOLCHAIN_PREFIX}/${LLVM_TAG}-objdump) + + +set(ISA rv32imfc_zicsr CACHE STRING "X-HEEP RISC-V ISA") +set(ABI ilp32f CACHE STRING "X-HEEP RISC-V ABI") + +set(CMAKE_EXECUTABLE_SUFFIX ".elf") + +add_compile_options( + -target riscv32-unknown-elf + -march=${ISA} + -mabi=${ABI} + -ffunction-sections + -fdata-sections + -fomit-frame-pointer + -mno-relax + -O3 + -MP + --sysroot=${TOOLCHAIN_INSTALL_DIR}/picolibc/riscv/rv32imf + -fno-builtin-memcpy + -fno-builtin-memset +) + +add_link_options( + -target riscv32-unknown-elf + -MP + -nostartfiles + -march=${ISA} + -mabi=ilp32f + -L${TOOLCHAIN_INSTALL_DIR}/picolibc/riscv/rv32imf/lib + -L${TOOLCHAIN_INSTALL_DIR}/lib/clang/15.0.0/lib/baremetal/rv32imf/ + -z norelro + -fno-builtin-memcpy + -fno-builtin-memset +) + +link_libraries( + -lm +) + +add_compile_definitions(__LINK_LD) +add_compile_definitions(__TOOLCHAIN_LLVM__) diff --git a/cmake/xheep/xheep.cmake b/cmake/xheep/xheep.cmake new file mode 100644 index 0000000000..1b14eaa7b3 --- /dev/null +++ b/cmake/xheep/xheep.cmake @@ -0,0 +1,116 @@ +# Copyright (C) 2026 EPFL. +# Solderpad Hardware License, Version 2.1, see LICENSE.md for details. +# SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +# +# File: xheep.cmake +# Author: Mohammad Hossein Nikkhah +# Description: + +if(NOT XHEEP_HOME AND DEFINED ENV{XHEEP_HOME}) + set(XHEEP_HOME "$ENV{XHEEP_HOME}" CACHE PATH "Path to X-HEEP checkout" FORCE) +else() + set(XHEEP_HOME "" CACHE PATH "Path to X-HEEP checkout") +endif() + +if(NOT XHEEP_HOME) + message(FATAL_ERROR "XHEEP_HOME is not set. Pass -DXHEEP_HOME= or export XHEEP_HOME.") +endif() + +set(XHEEP_SW_DIR "${XHEEP_HOME}/sw") +set(XHEEP_LINKER_DIR "${XHEEP_SW_DIR}/linker") +set(XHEEP_CRT_DIR "${XHEEP_SW_DIR}/device/lib/crt") +set(XHEEP_RUNTIME_DIR "${XHEEP_SW_DIR}/device/lib/runtime") +set(XHEEP_DEVICE_DIR "${XHEEP_SW_DIR}/device") +set(XHEEP_TARGET sim CACHE STRING "X-HEEP software target") +set(XHEEP_LINKER on_chip CACHE STRING "X-HEEP linker mode") +set(XHEEP_COMPILER_PREFIX "riscv32-unknown-" CACHE STRING "X-HEEP GCC compiler prefix") +set_property(CACHE XHEEP_LINKER PROPERTY STRINGS on_chip flash_load flash_exec) + +if(XHEEP_LINKER STREQUAL on_chip) + set(XHEEP_LINKER_FILE link.ld) + set(XHEEP_CRT_TYPE ON_CHIP) +elseif(XHEEP_LINKER STREQUAL flash_load) + set(XHEEP_LINKER_FILE link_flash_load.ld) + set(XHEEP_CRT_TYPE FLASH_LOAD) +elseif(XHEEP_LINKER STREQUAL flash_exec) + set(XHEEP_LINKER_FILE link_flash_exec.ld) + set(XHEEP_CRT_TYPE FLASH_EXEC) +else() + message(FATAL_ERROR "Unsupported XHEEP_LINKER '${XHEEP_LINKER}'. Use on_chip, flash_load, or flash_exec.") +endif() + +set(XHEEP_LINKER_SCRIPT "${XHEEP_LINKER_DIR}/${XHEEP_LINKER_FILE}") +set(XHEEP_CRT_SOURCES + "${XHEEP_CRT_DIR}/crt0.S" + "${XHEEP_CRT_DIR}/vectors.S" +) + +file(GLOB_RECURSE XHEEP_DEVICE_SOURCES CONFIGURE_DEPENDS + "${XHEEP_DEVICE_DIR}/*.c" + "${XHEEP_DEVICE_DIR}/*.cpp" + "${XHEEP_DEVICE_DIR}/*.s" + "${XHEEP_DEVICE_DIR}/*.S" +) + +set(XHEEP_RUNTIME_SOURCES "") +foreach(XHEEP_DEVICE_SOURCE IN LISTS XHEEP_DEVICE_SOURCES) + string(FIND "${XHEEP_DEVICE_SOURCE}" "${XHEEP_CRT_DIR}/" XHEEP_IS_CRT_SOURCE) + if(XHEEP_IS_CRT_SOURCE EQUAL -1) + list(APPEND XHEEP_RUNTIME_SOURCES "${XHEEP_DEVICE_SOURCE}") + endif() +endforeach() +list(REMOVE_DUPLICATES XHEEP_RUNTIME_SOURCES) + +set(XHEEP_REQUIRED_FILES + "${XHEEP_LINKER_SCRIPT}" + ${XHEEP_CRT_SOURCES} + ${XHEEP_RUNTIME_SOURCES} + "${XHEEP_RUNTIME_DIR}/core_v_mini_mcu.h" + "${XHEEP_RUNTIME_DIR}/core_v_mini_mcu_memory.h" + "${XHEEP_DEVICE_DIR}/target/${XHEEP_TARGET}/x-heep.h" +) + +foreach(XHEEP_REQUIRED_FILE IN LISTS XHEEP_REQUIRED_FILES) + if(NOT EXISTS "${XHEEP_REQUIRED_FILE}") + message(FATAL_ERROR + "Required X-HEEP file not found: ${XHEEP_REQUIRED_FILE}\n" + "Run `make mcu-gen` in ${XHEEP_HOME}, then reconfigure Deeploy.") + endif() +endforeach() + +file(GLOB_RECURSE XHEEP_DEVICE_HEADERS CONFIGURE_DEPENDS "${XHEEP_DEVICE_DIR}/*.h") +set(XHEEP_INCLUDE_DIRS + "${XHEEP_SW_DIR}" + "${XHEEP_DEVICE_DIR}" + "${XHEEP_DEVICE_DIR}/target/${XHEEP_TARGET}" +) +foreach(XHEEP_HEADER IN LISTS XHEEP_DEVICE_HEADERS) + string(FIND "${XHEEP_HEADER}" "${XHEEP_DEVICE_DIR}/target/" XHEEP_IS_TARGET_HEADER) + string(FIND "${XHEEP_HEADER}" "${XHEEP_DEVICE_DIR}/target/${XHEEP_TARGET}/" XHEEP_IS_SELECTED_TARGET_HEADER) + if(XHEEP_IS_TARGET_HEADER EQUAL -1 OR XHEEP_IS_SELECTED_TARGET_HEADER EQUAL 0) + get_filename_component(XHEEP_HEADER_DIR "${XHEEP_HEADER}" DIRECTORY) + list(APPEND XHEEP_INCLUDE_DIRS "${XHEEP_HEADER_DIR}") + endif() +endforeach() +list(REMOVE_DUPLICATES XHEEP_INCLUDE_DIRS) +include_directories(SYSTEM ${XHEEP_INCLUDE_DIRS}) + +set(DEEPLOY_ARCH XHEEP) + +add_compile_definitions( + DEEPLOY_XHEEP_PLATFORM + DEEPLOY_GENERIC_PLATFORM + HOST_BUILD + ${XHEEP_CRT_TYPE} + INTERNAL_CRTO + portasmHANDLE_INTERRUPT=vSystemIrqHandler +) + +set(XHEEP_GCC_LIB_DIR "${TOOLCHAIN_INSTALL_DIR}/${XHEEP_COMPILER_PREFIX}elf/lib") +add_link_options( + -T "${XHEEP_LINKER_SCRIPT}" + -static + -Wl,--gc-sections + "-L${XHEEP_GCC_LIB_DIR}" + -specs=nano.specs +) diff --git a/cmake/xheep/xheep_verilator.cmake b/cmake/xheep/xheep_verilator.cmake new file mode 100644 index 0000000000..e075388ef8 --- /dev/null +++ b/cmake/xheep/xheep_verilator.cmake @@ -0,0 +1,79 @@ +# Copyright (C) 2026 EPFL. +# Solderpad Hardware License, Version 2.1, see LICENSE.md for details. +# SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +# +# File: xheep_verilator.cmake +# Author: Mohammad Hossein Nikkhah +# Description: Add Deeploy CMake targets for running X-HEEP Verilator simulation. + +set(XHEEP_VERILATOR_DIR "" CACHE PATH + "Path to X-HEEP sim-verilator directory containing Vtestharness") + +set(XHEEP_SIM_ARGS "" CACHE STRING + "Extra Verilator plusargs for X-HEEP, for example '+max_sim_time=750us'") + +function(add_xheep_verilator_simulation name) + if(XHEEP_VERILATOR_DIR) + set(_xheep_sim_dir "${XHEEP_VERILATOR_DIR}") + else() + file(GLOB _xheep_sim_dirs LIST_DIRECTORIES true + "${XHEEP_HOME}/build/openhwgroup.org_systems_core-v-mini-mcu_*/sim-verilator" + ) + + list(SORT _xheep_sim_dirs) + list(REVERSE _xheep_sim_dirs) + + if(_xheep_sim_dirs) + list(GET _xheep_sim_dirs 0 _xheep_sim_dir) + endif() + endif() + + if(NOT _xheep_sim_dir) + add_custom_target(verilator_${name} + COMMAND ${CMAKE_COMMAND} -E echo + "ERROR: Could not find X-HEEP sim-verilator directory under ${XHEEP_HOME}/build." + COMMAND ${CMAKE_COMMAND} -E echo + "Run: cd ${XHEEP_HOME} && make verilator-build" + COMMAND ${CMAKE_COMMAND} -E false + USES_TERMINAL + ) + return() + endif() + + set(_xheep_harness "${_xheep_sim_dir}/Vtestharness") + + if(NOT EXISTS "${_xheep_harness}") + add_custom_target(verilator_${name} + COMMAND ${CMAKE_COMMAND} -E echo + "ERROR: Missing X-HEEP Verilator executable: ${_xheep_harness}" + COMMAND ${CMAKE_COMMAND} -E echo + "Run: cd ${XHEEP_HOME} && make verilator-build" + COMMAND ${CMAKE_COMMAND} -E false + USES_TERMINAL + ) + return() + endif() + + get_filename_component(_xheep_firmware_hex + "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${name}.hex" + ABSOLUTE + ) + + set(_xheep_run_args "+firmware=${_xheep_firmware_hex}") + + if(XHEEP_SIM_ARGS) + separate_arguments(_xheep_extra_args UNIX_COMMAND "${XHEEP_SIM_ARGS}") + list(APPEND _xheep_run_args ${_xheep_extra_args}) + endif() + + add_custom_target(verilator_${name} + DEPENDS ${name} + WORKING_DIRECTORY "${_xheep_sim_dir}" + COMMAND ${CMAKE_COMMAND} -E rm -f uart0.log + COMMAND "${_xheep_harness}" ${_xheep_run_args} + COMMAND ${CMAKE_COMMAND} -E cat uart0.log + COMMENT "Simulating ${name} on X-HEEP Verilator" + USES_TERMINAL + VERBATIM + ) +endfunction()