diff --git a/src/finn/core/utils.py b/src/finn/core/utils.py index e036c91348510e8708d86bc5b5070fb421cae6f9..794661e4d7b25ac54875c52a1e85ef6718a88009 100644 --- a/src/finn/core/utils.py +++ b/src/finn/core/utils.py @@ -1,5 +1,6 @@ import random import string +import subprocess import numpy as np import onnx @@ -195,7 +196,7 @@ def gen_finn_dt_tensor(finn_dt, tensor_shape): return tensor_values.astype(np.float32) -class CallCppCompiler: +class CppBuilder: def __init__(self): self.include_paths = [] self.cpp_files = [] @@ -207,56 +208,27 @@ class CallCppCompiler: def append_includes(self, library_path): self.include_paths.append(library_path) - def prepare_cpp_files(self, node): - if not self.code_gen_dir: - raise ValueError( - """There is no generated code to compile - for node of op type {}""".format( - node.op_type - ) - ) - else: - self.cpp_files.append( - str(self.code_gen_dir) + "/execute_" + str(node.op_type) + ".cpp" - ) - for lib in self.include_paths: - if "cnpy" in lib: - self.cpp_files.append("/workspace/cnpy/cnpy.cpp") - self.append_includes("-lz") - - def set_executable_path(self, node): - if not self.code_gen_dir: - raise ValueError( - """There is no generated code to compile - for node of op type {}""".format( - node.op_type - ) - ) - else: - self.executable_path = ( - str(self.code_gen_dir) + "/execute_" + str(node.op_type) - ) + def append_sources(self, cpp_file): + self.cpp_files.append(cpp_file) - def build(self, node): + def set_executable_path(self, path): + self.executable_path = path + + def build(self, code_gen_dir): # raise error if includes are empty - self.code_gen_dir = (get_by_name(node.attribute, "code_gen_dir")).s.decode( - "UTF-8" - ) - self.prepare_cpp_files(node) - self.set_executable_path(node) + self.code_gen_dir = code_gen_dir self.compile_components.append("g++ -o " + str(self.executable_path)) for cpp_file in self.cpp_files: self.compile_components.append(cpp_file) for lib in self.include_paths: self.compile_components.append(lib) - bash_compile = "" - for component in self.compile_components: bash_compile += str(component) + " " - self.compile_script = str(self.code_gen_dir) + "/compile.sh" - f = open(self.compile_script, "w") - f.write("#!/bin/sh \n") - f.write(bash_compile) - f.close() + with open(self.compile_script, "w") as f: + f.write("#!/bin/bash \n") + f.write(bash_compile + "\n") + bash_command = ["bash", self.compile_script] + process_compile = subprocess.Popen(bash_command, stdout=subprocess.PIPE) + process_compile.communicate() diff --git a/src/finn/custom_op/fpgadataflow/__init__.py b/src/finn/custom_op/fpgadataflow/__init__.py index ae8d8aa692d29c8514bf98a9dc5c60ced7d08486..0d9a306baa85149f2294ec8b0f6ec1ccdd603dfe 100644 --- a/src/finn/custom_op/fpgadataflow/__init__.py +++ b/src/finn/custom_op/fpgadataflow/__init__.py @@ -1,6 +1,7 @@ from abc import abstractmethod import os from finn.custom_op import CustomOp +from finn.core.utils import CppBuilder class HLSCustomOp(CustomOp): @@ -62,6 +63,21 @@ class HLSCustomOp(CustomOp): f.write(template) f.close() + def compile_singlenode_code(self): + code_gen_dir = self.get_nodeattr("code_gen_dir") + builder = CppBuilder() + builder.append_includes("-I/workspace/finn/src/finn/data/cpp") + builder.append_includes("-I/workspace/cnpy/") + builder.append_includes("-I/workspace/finn-hlslib") + builder.append_includes("-I/workspace/vivado-hlslib") + builder.append_includes("--std=c++11") + builder.append_sources(code_gen_dir + "/*.cpp") + builder.append_sources("/workspace/cnpy/cnpy.cpp") + builder.append_includes("-lz") + builder.set_executable_path(code_gen_dir + "/node_model") + builder.build(code_gen_dir) + self.set_nodeattr("executable_path", builder.executable_path) + @abstractmethod def generate_weights(self, context): pass diff --git a/src/finn/custom_op/fpgadataflow/streamingfclayer_batch.py b/src/finn/custom_op/fpgadataflow/streamingfclayer_batch.py index 97831485dafe1b66d962f44330fed37ef35d903b..fdc66ce2b1fc370e01f70b132e2c64502dc42476 100644 --- a/src/finn/custom_op/fpgadataflow/streamingfclayer_batch.py +++ b/src/finn/custom_op/fpgadataflow/streamingfclayer_batch.py @@ -247,7 +247,7 @@ class StreamingFCLayer_Batch(HLSCustomOp): in_ind += 1 # execute precompiled executable executable_path = self.get_nodeattr("executable_path") - # TODO sanity check executable + assert executable_path != "" process_execute = subprocess.Popen(executable_path, stdout=subprocess.PIPE) process_execute.communicate() # load output npy file diff --git a/src/finn/custom_op/fpgadataflow/streamingmaxpool_batch.py b/src/finn/custom_op/fpgadataflow/streamingmaxpool_batch.py index 56d43cac7e8383505c0b31ba41a28f88aee35175..8f497168ee4b9db81cc508474699aa706e84b3f1 100644 --- a/src/finn/custom_op/fpgadataflow/streamingmaxpool_batch.py +++ b/src/finn/custom_op/fpgadataflow/streamingmaxpool_batch.py @@ -38,7 +38,7 @@ class StreamingMaxPool_Batch(HLSCustomOp): in_ind += 1 # execute precompiled executable executable_path = self.get_nodeattr("executable_path") - # TODO sanity check executable + assert executable_path != "" process_execute = subprocess.Popen(executable_path, stdout=subprocess.PIPE) process_execute.communicate() # load output npy file diff --git a/src/finn/transformation/fpgadataflow/compilation_transformation.py b/src/finn/transformation/fpgadataflow/compilation_transformation.py index d06af8682a81ed951c6f9e3554bf63dd1c3e457a..8d158c4a5561bfeec4c17b9e5fa5c3df5a7a96bd 100644 --- a/src/finn/transformation/fpgadataflow/compilation_transformation.py +++ b/src/finn/transformation/fpgadataflow/compilation_transformation.py @@ -1,7 +1,5 @@ -import subprocess - import finn.core.utils as util -from finn.core.utils import CallCppCompiler +import finn.custom_op.registry as registry from finn.transformation import Transformation @@ -10,40 +8,26 @@ class Compilation(Transformation): def __init__(self): super().__init__() - self.compiler_call = CallCppCompiler() - - def get_includes(self): - # step by step addition of include paths to ensure easy extension - self.compiler_call.append_includes("-I/workspace/finn/src/finn/data/cpp") - self.compiler_call.append_includes("-I/workspace/cnpy/") - self.compiler_call.append_includes("-I/workspace/finn-hlslib") - self.compiler_call.append_includes("-I/workspace/vivado-hlslib") - self.compiler_call.append_includes("--std=c++11") - - def prepare_bash_command(self, node): - self.get_includes() - self.compiler_call.build(node) - bash_command = "chmod +x " + str(self.compiler_call.compile_script) - process_compile = subprocess.Popen(bash_command.split(), stdout=subprocess.PIPE) - process_compile.communicate() - print(self.compiler_call.code_gen_dir) def apply(self, model): - for node in model.graph.node: + op_type = node.op_type if node.domain == "finn": backend_attribute = util.get_by_name(node.attribute, "backend") backend_value = backend_attribute.s.decode("UTF-8") if backend_value == "fpgadataflow": - self.prepare_bash_command(node) - bash_command = self.compiler_call.compile_script - process_compile = subprocess.Popen( - bash_command.split(), stdout=subprocess.PIPE - ) - process_compile.communicate() - - model.set_attribute( - node, "executable_path", self.compiler_call.executable_path - ) - + try: + # lookup op_type in registry of CustomOps + inst = registry.custom_op[op_type](node) + # ensure that code is generated + assert inst.get_nodeattr("code_gen_dir") != "" + # call the compilation function for this node + inst.compile_singlenode_code() + # ensure that executable path is now set + assert inst.get_nodeattr("executable_path") != "" + except KeyError: + # exception if op_type is not supported + raise Exception( + "Custom op_type %s is currently not supported." % op_type + ) return (model, False)