build/xilinx/vivado: improve directive support
[litex.git] / litex / build / xilinx / vivado.py
index 974958c9c56ac24fba70f971f075db78bd2ba614..244288e050dd524f4951c9a629f5efc85cb92abc 100644 (file)
@@ -1,9 +1,11 @@
-# This file is Copyright (c) 2014 Florent Kermarrec <florent@enjoy-digital.fr>
+# This file is Copyright (c) 2014-2019 Florent Kermarrec <florent@enjoy-digital.fr>
 # License: BSD
 
 import os
 import subprocess
 import sys
+import math
+from distutils.spawn import find_executable
 
 from migen.fhdl.structure import _Fragment
 
@@ -56,28 +58,29 @@ def _build_xdc(named_sc, named_pc):
 
 def _run_vivado(build_name, vivado_path, source, ver=None):
     if sys.platform == "win32" or sys.platform == "cygwin":
-        build_script_contents = "REM Autogenerated by LiteX\n"
+        build_script_contents = "REM Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n"
         build_script_contents += "vivado -mode batch -source " + build_name + ".tcl\n"
         build_script_file = "build_" + build_name + ".bat"
         tools.write_to_file(build_script_file, build_script_contents)
         command = build_script_file
     else:
-        build_script_contents = "# Autogenerated by LiteX\nset -e\n"
-
-        # For backwards compatibility with ISE paths, also
-        # look for a version in a subdirectory named "Vivado"
-        # under the current directory.
-        paths_to_try = [vivado_path, os.path.join(vivado_path, "Vivado")]
-        for p in paths_to_try:
-            try:
-                settings = common.settings(p, ver)
-            except OSError:
-                continue
-            break
-        else:
-            raise OSError("Unable to locate Vivado directory or settings.")
+        build_script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
+        # Only source Vivado settings if not already in our $PATH
+        if not find_executable("vivado"):
+            # For backwards compatibility with ISE paths, also
+            # look for a version in a subdirectory named "Vivado"
+            # under the current directory.
+            paths_to_try = [vivado_path, os.path.join(vivado_path, "Vivado")]
+            for p in paths_to_try:
+                try:
+                    settings = common.settings(p, ver)
+                except OSError:
+                    continue
+                break
+            else:
+                raise OSError("Unable to locate Vivado directory or settings.")
+            build_script_contents += "source " + settings + "\n"
 
-        build_script_contents += "source " + settings + "\n"
         build_script_contents += "vivado -mode batch -source " + build_name + ".tcl\n"
         build_script_file = "build_" + build_name + ".sh"
         tools.write_to_file(build_script_file, build_script_contents)
@@ -102,21 +105,32 @@ class XilinxVivadoToolchain:
         self.bitstream_commands = []
         self.additional_commands = []
         self.pre_synthesis_commands = []
-        self.with_phys_opt = False
+        self.with_phys_opt = False  # deprecated -> vivado_post_place_phys_opt_directive
+        self.incremental_implementation = False
+        self.vivado_synth_directive = 'default'
+        self.opt_directive = 'default'
+        self.vivado_place_directive = 'default'
+        self.vivado_post_place_phys_opt_directive = None
+        self.vivado_route_directive = 'default'
+        self.vivado_post_route_phys_opt_directive = 'default'
         self.clocks = dict()
         self.false_paths = set()
 
-    def _build_batch(self, platform, sources, edifs, ips, build_name, synth_mode="vivado"):
+    def _build_batch(self, platform, sources, edifs, ips, build_name, synth_mode, enable_xpm):
+        assert synth_mode in ["vivado", "yosys"]
         tcl = []
         tcl.append("create_project -force -name {} -part {}".format(build_name, platform.device))
-        tcl.append("set_property XPM_LIBRARIES {XPM_CDC XPM_MEMORY} [current_project]")
+        tcl.append("set_msg_config -id {Common 17-55} -new_severity {Warning}")
+        if enable_xpm:
+            tcl.append("set_property XPM_LIBRARIES {XPM_CDC XPM_MEMORY} [current_project]")
         if synth_mode == "vivado":
             # "-include_dirs {}" crashes Vivado 2016.4
             for filename, language, library in sources:
                 filename_tcl = "{" + filename + "}"
                 tcl.append("add_files " + filename_tcl)
-                tcl.append("set_property library {} [get_files {}]"
-                           .format(library, filename_tcl))
+                if language == "vhdl":
+                    tcl.append("set_property library {} [get_files {}]"
+                               .format(library, filename_tcl))
         for filename in edifs:
             filename_tcl = "{" + filename + "}"
             tcl.append("read_edif " + filename_tcl)
@@ -134,10 +148,11 @@ class XilinxVivadoToolchain:
         tcl.extend(c.format(build_name=build_name) for c in self.pre_synthesis_commands)
 
         if synth_mode == "vivado":
+            synth_cmd = "synth_design -directive {} -top {} -part {}".format(self.vivado_synth_directive,
+                                                                             build_name, platform.device)
             if platform.verilog_include_paths:
-                tcl.append("synth_design -top {} -part {} -include_dirs {{{}}}".format(build_name, platform.device, " ".join(platform.verilog_include_paths)))
-            else:
-                tcl.append("synth_design -top {} -part {}".format(build_name, platform.device))
+                synth_cmd += " -include_dirs {{{}}}".format(" ".join(platform.verilog_include_paths))
+            tcl.append(synth_cmd)
         elif synth_mode == "yosys":
             tcl.append("read_edif {}.edif".format(build_name))
             tcl.append("link_design -top {} -part {}".format(build_name, platform.device))
@@ -147,17 +162,22 @@ class XilinxVivadoToolchain:
         tcl.append("report_timing_summary -file {}_timing_synth.rpt".format(build_name))
         tcl.append("report_utilization -hierarchical -file {}_utilization_hierarchical_synth.rpt".format(build_name))
         tcl.append("report_utilization -file {}_utilization_synth.rpt".format(build_name))
-        tcl.append("opt_design")
-        tcl.append("place_design")
+        tcl.append("opt_design -directive {}".format(self.opt_directive))
+        if self.incremental_implementation:
+            tcl.append("read_checkpoint -incremental {}_route.dcp".format(build_name))
+        tcl.append("place_design -directive {}".format(self.vivado_place_directive))
         if self.with_phys_opt:
-            tcl.append("phys_opt_design -directive AddRetime")
+            tools.deprecated_warning('with_phys_opt -> vivado_post_place_phys_opt_directive')
+            self.vivado_post_place_phys_opt_directive = 'AddRetime'
+        if self.vivado_post_place_phys_opt_directive:
+            tcl.append("phys_opt_design -directive {}".format(self.vivado_post_place_phys_opt_directive))
         tcl.append("report_utilization -hierarchical -file {}_utilization_hierarchical_place.rpt".format(build_name))
         tcl.append("report_utilization -file {}_utilization_place.rpt".format(build_name))
         tcl.append("report_io -file {}_io.rpt".format(build_name))
         tcl.append("report_control_sets -verbose -file {}_control_sets.rpt".format(build_name))
         tcl.append("report_clock_utilization -file {}_clock_utilization.rpt".format(build_name))
-        tcl.append("route_design")
-        tcl.append("phys_opt_design")
+        tcl.append("route_design -directive {}".format(self.vivado_route_directive))
+        tcl.append("phys_opt_design -directive {}".format(self.vivado_post_route_phys_opt_directive))
         tcl.append("report_timing_summary -no_header -no_detailed_paths")
         tcl.append("write_checkpoint -force {}_route.dcp".format(build_name))
         tcl.append("report_route_status -file {}_route_status.rpt".format(build_name))
@@ -194,35 +214,30 @@ class XilinxVivadoToolchain:
         # The asynchronous input to a MultiReg is a false path
         platform.add_platform_command(
             "set_false_path -quiet "
-            "-to [get_nets -filter {{mr_ff == TRUE}}]"
+            "-to [get_nets -quiet -filter {{mr_ff == TRUE}}]"
         )
         # The asychronous reset input to the AsyncResetSynchronizer is a false
         # path
         platform.add_platform_command(
             "set_false_path -quiet "
-            "-to [get_pins -filter {{REF_PIN_NAME == PRE}} "
-                "-of [get_cells -filter {{ars_ff1 == TRUE || ars_ff2 == TRUE}}]]"
+            "-to [get_pins -quiet -filter {{REF_PIN_NAME == PRE}} "
+                "-of [get_cells -quiet -filter {{ars_ff1 == TRUE || ars_ff2 == TRUE}}]]"
         )
         # clock_period-2ns to resolve metastability on the wire between the
         # AsyncResetSynchronizer FFs
         platform.add_platform_command(
             "set_max_delay 2 -quiet "
-            "-from [get_pins -filter {{REF_PIN_NAME == Q}} "
-                "-of [get_cells -filter {{ars_ff1 == TRUE}}]] "
-            "-to [get_pins -filter {{REF_PIN_NAME == D}} "
-                "-of [get_cells -filter {{ars_ff2 == TRUE}}]]"
+            "-from [get_pins -quiet -filter {{REF_PIN_NAME == Q}} "
+                "-of [get_cells -quiet -filter {{ars_ff1 == TRUE}}]] "
+            "-to [get_pins -quiet -filter {{REF_PIN_NAME == D}} "
+                "-of [get_cells -quiet -filter {{ars_ff2 == TRUE}}]]"
         )
 
     def build(self, platform, fragment, build_dir="build", build_name="top",
-            toolchain_path=None, source=True, run=True, **kwargs):
-        synth_mode = kwargs.get('synth_mode', 'yosys')
+            toolchain_path="/opt/Xilinx/Vivado", source=True, run=True,
+            synth_mode="vivado", enable_xpm=False, **kwargs):
         if toolchain_path is None:
-            if sys.platform == "win32":
-                toolchain_path = "C:\\Xilinx\\Vivado"
-            elif sys.platform == "cygwin":
-                toolchain_path = "/cygdrive/c/Xilinx/Vivado"
-            else:
-                toolchain_path = "/opt/Xilinx/Vivado"
+            toolchain_path = "/opt/Xilinx/Vivado"
         os.makedirs(build_dir, exist_ok=True)
         cwd = os.getcwd()
         os.chdir(build_dir)
@@ -236,16 +251,14 @@ class XilinxVivadoToolchain:
         named_sc, named_pc = platform.resolve_signals(v_output.ns)
         v_file = build_name + ".v"
         v_output.write(v_file)
-        sources = platform.sources + [(v_file, "verilog", "work")]
+        sources = platform.sources | {(v_file, "verilog", "work")}
         edifs = platform.edifs
         ips = platform.ips
-        self._build_batch(platform, sources, edifs, ips, build_name, synth_mode=synth_mode)
+        self._build_batch(platform, sources, edifs, ips, build_name, synth_mode, enable_xpm)
         tools.write_to_file(build_name + ".xdc", _build_xdc(named_sc, named_pc))
         if run:
             if synth_mode == "yosys":
                 common._run_yosys(platform.device, sources, platform.verilog_include_paths, build_name)
-            else:
-                raise OSError("Error!")
             _run_vivado(build_name, toolchain_path, source)
 
         os.chdir(cwd)
@@ -255,6 +268,7 @@ class XilinxVivadoToolchain:
     def add_period_constraint(self, platform, clk, period):
         if clk in self.clocks:
             raise ValueError("A period constraint already exists")
+        period = math.floor(period*1e3)/1e3 # round to lowest picosecond
         self.clocks[clk] = period
 
     def add_false_path_constraint(self, platform, from_, to):