diff --git a/docker/Dockerfile.finn_ci b/docker/Dockerfile.finn_ci index 2668927602ebb8de5fdc3d7c25b20a0c8c4a2e55..5772b16abc8b927def1e2dfbbb8193a2f964f87d 100644 --- a/docker/Dockerfile.finn_ci +++ b/docker/Dockerfile.finn_ci @@ -37,7 +37,7 @@ WORKDIR /workspace RUN apt-get update RUN apt-get -y upgrade RUN apt-get install -y build-essential libglib2.0-0 libsm6 libxext6 libxrender-dev -RUN apt install verilator +RUN apt-get install -y verilator zsh RUN apt-get -y install sshpass RUN echo "StrictHostKeyChecking no" >> /etc/ssh/ssh_config @@ -52,6 +52,8 @@ RUN git clone https://github.com/Xilinx/finn-hlslib.git /workspace/finn-hlslib RUN git clone https://github.com/maltanar/pyverilator /workspace/pyverilator # PYNQ-HelloWorld RUN git clone https://github.com/maltanar/PYNQ-HelloWorld.git /workspace/PYNQ-HelloWorld +# oh-my-xilinx +RUN git clone https://bitbucket.org/maltanar/oh-my-xilinx.git /workspace/oh-my-xilinx # checkout desired FINN branch for testing RUN git clone --branch $FINN_CI_BRANCH https://github.com/Xilinx/finn /workspace/finn @@ -64,6 +66,8 @@ ENV PYTHONPATH "${PYTHONPATH}:/workspace/finn/src" ENV PYTHONPATH "${PYTHONPATH}:/workspace/pyverilator" ENV PYNQSHELL_PATH "/workspace/PYNQ-HelloWorld/boards" ENV VIVADO_IP_CACHE "$BUILD_PATH/vivado_ip_cache" +ENV PATH "${PATH}:/workspace/oh-my-xilinx" +ENV OHMYXILINX "/workspace/oh-my-xilinx" # colorful terminal output RUN echo "PS1='\[\033[1;36m\]\u\[\033[1;31m\]@\[\033[1;32m\]\h:\[\033[1;35m\]\w\[\033[1;31m\]\$\[\033[0m\] '" >> /root/.bashrc diff --git a/docker/Dockerfile.finn_dev b/docker/Dockerfile.finn_dev index 1200c7d5d15bbd62e15f19f84e70d5fe0b8aca28..22e3eb623c7a5da19a5e3ae2284557577898ad23 100644 --- a/docker/Dockerfile.finn_dev +++ b/docker/Dockerfile.finn_dev @@ -37,16 +37,12 @@ ARG PASSWD ARG JUPYTER_PORT ARG NETRON_PORT -EXPOSE $JUPYTER_PORT -EXPOSE $NETRON_PORT - WORKDIR /workspace RUN apt-get update RUN apt-get -y upgrade RUN apt-get install -y build-essential libglib2.0-0 libsm6 libxext6 libxrender-dev -RUN apt-get install verilator -RUN apt-get install nano +RUN apt-get install -y verilator nano zsh RUN apt-get -y install sshpass RUN echo "StrictHostKeyChecking no" >> /etc/ssh/ssh_config @@ -81,12 +77,16 @@ RUN git clone https://github.com/Xilinx/finn-hlslib.git /workspace/finn-hlslib RUN git clone https://github.com/maltanar/pyverilator /workspace/pyverilator # PYNQ-HelloWorld RUN git clone https://github.com/maltanar/PYNQ-HelloWorld.git /workspace/PYNQ-HelloWorld +# oh-my-xilinx +RUN git clone https://bitbucket.org/maltanar/oh-my-xilinx.git /workspace/oh-my-xilinx # for this developer-oriented Docker container we assume the FINN repo is cloned and mounted from the host # at /workspace/finn -- see run-docker.sh for an example of how to do this. ENV PYTHONPATH "${PYTHONPATH}:/workspace/finn/src" ENV PYTHONPATH "${PYTHONPATH}:/workspace/pyverilator" ENV PYNQSHELL_PATH "/workspace/PYNQ-HelloWorld/boards" +ENV PATH "${PATH}:/workspace/oh-my-xilinx" +ENV OHMYXILINX "/workspace/oh-my-xilinx" WORKDIR /home/$UNAME/finn RUN echo "PS1='\[\033[1;36m\]\u\[\033[1;31m\]@\[\033[1;32m\]\h:\[\033[1;35m\]\w\[\033[1;31m\]\$\[\033[0m\] '" >> /home/$UNAME/.bashrc @@ -100,5 +100,8 @@ RUN chmod 755 /usr/local/bin/finn_entrypoint.sh RUN chmod 755 /usr/local/bin/quicktest.sh USER $UNAME +EXPOSE $JUPYTER_PORT +EXPOSE $NETRON_PORT + ENTRYPOINT ["finn_entrypoint.sh"] CMD ["bash"] diff --git a/docker/finn_entrypoint.sh b/docker/finn_entrypoint.sh index 7e13e117859365531f459928b7c664edb3fbf4ce..e34b6ce9cc4488c806da8bcf3cc5cc8e500ae806 100644 --- a/docker/finn_entrypoint.sh +++ b/docker/finn_entrypoint.sh @@ -18,6 +18,7 @@ CNPY_COMMIT=4e8810b1a8637695171ed346ce68f6984e585ef4 HLSLIB_COMMIT=13e9b0772a27a3a1efc40c878d8e78ed09efb716 PYVERILATOR_COMMIT=c97a5ba41bbc7c419d6f25c74cdf3bdc3393174f PYNQSHELL_COMMIT=0c82a61b0ec1a07fa275a14146233824ded7a13d +OMX_COMMIT=1bae737669901e762f581af73348332b5c4b2ada gecho "Setting up known-good commit versions for FINN dependencies" @@ -42,6 +43,10 @@ git -C /workspace/pyverilator checkout $PYVERILATOR_COMMIT --quiet gecho "PYNQ shell @ $PYNQSHELL_COMMIT" git -C /workspace/PYNQ-HelloWorld pull --quiet git -C /workspace/PYNQ-HelloWorld checkout $PYNQSHELL_COMMIT --quiet +# oh-my-xilinx +gecho "oh-my-xilinx @ $OMX_COMMIT" +git -C /workspace/oh-my-xilinx pull --quiet +git -C /workspace/oh-my-xilinx checkout $OMX_COMMIT --quiet # source Vivado env.vars source $VIVADO_PATH/settings64.sh diff --git a/src/finn/transformation/fpgadataflow/synth_ooc.py b/src/finn/transformation/fpgadataflow/synth_ooc.py new file mode 100644 index 0000000000000000000000000000000000000000..1d49970c819961d1794cc89e998108639ca15593 --- /dev/null +++ b/src/finn/transformation/fpgadataflow/synth_ooc.py @@ -0,0 +1,64 @@ +# Copyright (c) 2020, Xilinx +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of FINN nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import os +from shutil import copy2 + +from finn.transformation import Transformation +from finn.util.vivado import out_of_context_synth +from finn.util.basic import make_build_dir + + +class SynthOutOfContext(Transformation): + """Run out-of-context Vivado synthesis on a stitched IP design.""" + + def __init__(self, part, clk_period_ns, clk_name="ap_clk_0"): + super().__init__() + self.part = part + self.clk_period_ns = clk_period_ns + self.clk_name = clk_name + + def apply(self, model): + def file_to_basename(x): + return os.path.basename(os.path.realpath(x)) + + vivado_stitch_proj_dir = model.get_metadata_prop("vivado_stitch_proj") + assert vivado_stitch_proj_dir is not None, "Need stitched IP to run." + top_module_name = model.get_metadata_prop("wrapper_filename") + top_module_name = file_to_basename(top_module_name).strip(".v") + build_dir = make_build_dir("synth_out_of_context_") + with open(vivado_stitch_proj_dir + "/all_verilog_srcs.txt", "r") as f: + all_verilog_srcs = f.read().split() + for file in all_verilog_srcs: + if file.endswith(".v"): + copy2(file, build_dir) + ret = out_of_context_synth( + build_dir, top_module_name, self.part, self.clk_name, self.clk_period_ns + ) + model.set_metadata_prop("res_total_ooc_synth", str(ret)) + return (model, False) diff --git a/src/finn/util/vivado.py b/src/finn/util/vivado.py new file mode 100644 index 0000000000000000000000000000000000000000..561bbc8c5642c14c0572d4365492f653cc507fcc --- /dev/null +++ b/src/finn/util/vivado.py @@ -0,0 +1,106 @@ +# Copyright (c) 2020, Xilinx +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of FINN nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import os +import subprocess + + +def which(program): + "Python equivalent of the shell cmd 'which'." + + # source: + # https://stackoverflow.com/questions/377017/test-if-executable-exists-in-python + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + + return None + + +def out_of_context_synth( + verilog_dir, + top_name, + fpga_part="xczu3eg-sbva484-1-e", + clk_name="ap_clk_0", + clk_period_ns=5.0, +): + "Run out-of-context Vivado synthesis, return resources and slack." + + # ensure that the OH_MY_XILINX envvar is set + if "OHMYXILINX" not in os.environ: + raise Exception("The environment variable OHMYXILINX is not defined.") + # ensure that vivado is in PATH: source $VIVADO_PATH/settings64.sh + if which("vivado") is None: + raise Exception("vivado is not in PATH, ensure settings64.sh is sourced.") + omx_path = os.environ["OHMYXILINX"] + script = "vivadocompile.sh" + # vivadocompile.sh <top-level-entity> <clock-name (optional)> <fpga-part (optional)> + call_omx = "zsh %s/%s %s %s %s %f" % ( + omx_path, + script, + top_name, + clk_name, + fpga_part, + float(clk_period_ns), + ) + call_omx = call_omx.split() + proc = subprocess.Popen( + call_omx, cwd=verilog_dir, stdout=subprocess.PIPE, env=os.environ + ) + proc.communicate() + + vivado_proj_folder = "%s/results_%s" % (verilog_dir, top_name) + res_counts_path = vivado_proj_folder + "/res.txt" + + with open(res_counts_path, "r") as myfile: + res_data = myfile.read().split("\n") + ret = {} + ret["vivado_proj_folder"] = vivado_proj_folder + for res_line in res_data: + res_fields = res_line.split("=") + print(res_fields) + try: + ret[res_fields[0]] = float(res_fields[1]) + except ValueError: + ret[res_fields[0]] = 0 + except IndexError: + ret[res_fields[0]] = 0 + if ret["WNS"] == 0: + ret["fmax_mhz"] = 0 + else: + ret["fmax_mhz"] = 1000.0 / (clk_period_ns - ret["WNS"]) + return ret diff --git a/tests/fpgadataflow/test_fpgadataflow_ip_stitch.py b/tests/fpgadataflow/test_fpgadataflow_ip_stitch.py index 16100522aa94fd25d234efa1d03edfdc866ca1bb..61dd81b728aafcd8ccc812cf0cb4c27eff00f471 100644 --- a/tests/fpgadataflow/test_fpgadataflow_ip_stitch.py +++ b/tests/fpgadataflow/test_fpgadataflow_ip_stitch.py @@ -53,6 +53,7 @@ from finn.transformation.general import GiveUniqueNodeNames from finn.util.basic import gen_finn_dt_tensor, pynq_part_map from finn.util.fpgadataflow import pyverilate_stitched_ip from finn.util.test import load_test_checkpoint_or_skip +from finn.transformation.fpgadataflow.synth_ooc import SynthOutOfContext test_pynq_board = os.getenv("PYNQ_BOARD", default="Pynq-Z1") test_fpga_part = pynq_part_map[test_pynq_board] @@ -281,6 +282,27 @@ def test_fpgadataflow_ipstitch_rtlsim(): assert (rtlsim_res == x).all() +@pytest.mark.vivado +@pytest.mark.slow +def test_fpgadataflow_ipstitch_synth_ooc(): + model = load_test_checkpoint_or_skip( + ip_stitch_model_dir + "/test_fpgadataflow_ip_stitch.onnx" + ) + model = model.transform(SynthOutOfContext(test_fpga_part, 5)) + ret = model.get_metadata_prop("res_total_ooc_synth") + assert ret is not None + # example expected output: (details may differ based on Vivado version etc) + # "{'vivado_proj_folder': ..., + # 'LUT': 708.0, 'FF': 1516.0, 'DSP': 0.0, 'BRAM': 0.0, 'WNS': 0.152, '': 0, + # 'fmax_mhz': 206.27062706270627}" + ret = eval(ret) + assert ret["LUT"] > 0 + assert ret["FF"] > 0 + assert ret["DSP"] == 0 + assert ret["BRAM"] == 0 + assert ret["fmax_mz"] > 100 + + @pytest.mark.vivado def test_fpgadataflow_ipstitch_pynq_projgen(): model = load_test_checkpoint_or_skip(