build/xilinx/vivado: enable xpm libraries
[litex.git] / litex / build / xilinx / vivado.py
index 4b9fedfa1a7a5f9b0f2f5adc8a395da7d6ca58b1..10a795bd7665533e1f88f5a63179b51621ddf803 100644 (file)
@@ -5,7 +5,8 @@ import os
 import subprocess
 import sys
 
-from litex.gen.fhdl.structure import _Fragment
+from migen.fhdl.structure import _Fragment
+
 from litex.build.generic_platform import *
 from litex.build import tools
 from litex.build.xilinx import common
@@ -21,7 +22,7 @@ def _format_constraint(c):
     elif isinstance(c, Misc):
         return "set_property " + c.misc.replace("=", " ")
     elif isinstance(c, Inverted):
-        return ""
+        return None
     else:
         raise ValueError("unknown constraint {}".format(c))
 
@@ -33,7 +34,8 @@ def _format_xdc(signame, resname, *constraints):
         fmt_r += "." + resname[2]
     r = " ## {}\n".format(fmt_r)
     for c in fmt_c:
-        r += c + " [get_ports " + signame + "]\n"
+        if c is not None:
+            r += c + " [get_ports " + signame + "]\n"
     return r
 
 
@@ -61,7 +63,20 @@ def _run_vivado(build_name, vivado_path, source, ver=None):
         command = build_script_file
     else:
         build_script_contents = "# Autogenerated by LiteX\nset -e\n"
-        settings = common.settings(vivado_path, "Vivado", ver, first="name")
+
+        # 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 += "vivado -mode batch -source " + build_name + ".tcl\n"
         build_script_file = "build_" + build_name + ".sh"
@@ -77,8 +92,9 @@ class XilinxVivadoToolchain:
         "keep": ("dont_touch", "true"),
         "no_retiming": ("dont_touch", "true"),
         "async_reg": ("async_reg", "true"),
-        "ars_ff": ("ars_ff", "true"),  # user-defined attribute
-        "ars_false_path": ("ars_false_path", "true"),  # user-defined attribute
+        "mr_ff": ("mr_ff", "true"),  # user-defined attribute
+        "ars_ff1": ("ars_ff1", "true"),  # user-defined attribute
+        "ars_ff2": ("ars_ff2", "true"),  # user-defined attribute
         "no_shreg_extract": None
     }
 
@@ -90,10 +106,10 @@ class XilinxVivadoToolchain:
         self.clocks = dict()
         self.false_paths = set()
 
-    def _build_batch(self, platform, sources, edifs, build_name):
+    def _build_batch(self, platform, sources, edifs, ips, build_name):
         tcl = []
-        tcl.append("create_property ars_ff cell")
-        tcl.append("create_property ars_false_path net")
+        tcl.append("create_project -force -name {} -part {}".format(build_name, platform.device))
+        tcl.append("set_property XPM_LIBRARIES {XPM_CDC XPM_MEMORY} [current_project]")
         for filename, language, library in sources:
             filename_tcl = "{" + filename + "}"
             tcl.append("add_files " + filename_tcl)
@@ -102,6 +118,16 @@ class XilinxVivadoToolchain:
         for filename in edifs:
             filename_tcl = "{" + filename + "}"
             tcl.append("read_edif " + filename_tcl)
+
+        for filename in ips:
+            filename_tcl = "{" + filename + "}"
+            ip = os.path.splitext(os.path.basename(filename))[0]
+            tcl.append("read_ip " + filename_tcl)
+            tcl.append("upgrade_ip [get_ips {}]".format(ip))
+            tcl.append("generate_target all [get_ips {}]".format(ip))
+            tcl.append("synth_ip [get_ips {}] -force".format(ip))
+            tcl.append("get_files -all -of_objects [get_files {}]".format(filename_tcl))
+
         tcl.append("read_xdc {}.xdc".format(build_name))
         tcl.extend(c.format(build_name=build_name) for c in self.pre_synthesis_commands)
         # "-include_dirs {}" crashes Vivado 2016.4
@@ -109,7 +135,6 @@ class XilinxVivadoToolchain:
             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))
-        tcl.append("write_checkpoint -force {}_synth.dcp".format(build_name))
         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))
@@ -117,11 +142,9 @@ class XilinxVivadoToolchain:
         tcl.append("place_design")
         if self.with_phys_opt:
             tcl.append("phys_opt_design -directive AddRetime")
-        tcl.append("write_checkpoint -force {}_place.dcp".format(build_name))
         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("write_csv -force {}_tracelength.csv".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")
@@ -145,12 +168,11 @@ class XilinxVivadoToolchain:
                 " [get_nets {clk}]", clk=clk)
         for from_, to in sorted(self.false_paths,
                                 key=lambda x: (x[0].duid, x[1].duid)):
-            if (from_ not in self.clocks
-                    or to not in self.clocks):
-                raise ValueError("Vivado requires period "
-                                 "constraints on all clocks used in false paths")
             platform.add_platform_command(
-                "set_false_path -from [get_clocks {from_}] -to [get_clocks {to}]",
+                "set_clock_groups "
+                "-group [get_clocks -include_generated_clocks -of [get_nets {from_}]] "
+                "-group [get_clocks -include_generated_clocks -of [get_nets {to}]] "
+                "-asynchronous",
                 from_=from_, to=to)
 
         # make sure add_*_constraint cannot be used again
@@ -158,30 +180,37 @@ class XilinxVivadoToolchain:
         del self.false_paths
 
     def _constrain(self, platform):
+        # 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}}]"
+        )
         # The asychronous reset input to the AsyncResetSynchronizer is a false
         # path
         platform.add_platform_command(
             "set_false_path -quiet "
-            "-through [get_nets -hier -filter {{ars_false_path==true}}] "
-            "-to [get_cells -hier -filter {{ars_ff==true}}]"
+            "-to [get_pins -filter {{REF_PIN_NAME == PRE}} "
+                "-of [get_cells -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_cells -hier -filter {{ars_ff==true}}] "
-            "-to [get_cells -hier -filter {{ars_ff==true}}]"
+            "-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}}]]"
         )
 
     def build(self, platform, fragment, build_dir="build", build_name="top",
             toolchain_path=None, source=True, run=True, **kwargs):
         if toolchain_path is None:
             if sys.platform == "win32":
-                toolchain_path = "C:\\Xilinx"
+                toolchain_path = "C:\\Xilinx\\Vivado"
             elif sys.platform == "cygwin":
-                toolchain_path = "/cygdrive/c/Xilinx"
+                toolchain_path = "/cygdrive/c/Xilinx/Vivado"
             else:
-                toolchain_path = "/opt/Xilinx"
+                toolchain_path = "/opt/Xilinx/Vivado"
         os.makedirs(build_dir, exist_ok=True)
         cwd = os.getcwd()
         os.chdir(build_dir)
@@ -195,9 +224,10 @@ 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
-        self._build_batch(platform, sources, edifs, build_name)
+        ips = platform.ips
+        self._build_batch(platform, sources, edifs, ips, build_name)
         tools.write_to_file(build_name + ".xdc", _build_xdc(named_sc, named_pc))
         if run:
             _run_vivado(build_name, toolchain_path, source)
@@ -212,4 +242,5 @@ class XilinxVivadoToolchain:
         self.clocks[clk] = period
 
     def add_false_path_constraint(self, platform, from_, to):
-        self.false_paths.add((from_, to))
+        if (to, from_) not in self.false_paths:
+            self.false_paths.add((from_, to))