build/xilinx/vivado: enable xpm libraries
[litex.git] / litex / build / xilinx / vivado.py
index 032b78bbec673e77cab835fcb3267cc9bc66d83a..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
@@ -20,6 +21,8 @@ def _format_constraint(c):
         return "set_property DRIVE " + str(c.strength)
     elif isinstance(c, Misc):
         return "set_property " + c.misc.replace("=", " ")
+    elif isinstance(c, Inverted):
+        return None
     else:
         raise ValueError("unknown constraint {}".format(c))
 
@@ -31,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
 
 
@@ -56,40 +60,85 @@ def _run_vivado(build_name, vivado_path, source, ver=None):
         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)
-        r = subprocess.call([build_script_file])
+        command = build_script_file
     else:
         build_script_contents = "# Autogenerated by LiteX\nset -e\n"
-        settings = common.settings(vivado_path, ver)
+
+        # 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"
         tools.write_to_file(build_script_file, build_script_contents)
-        r = subprocess.call(["bash", build_script_file])
-
+        command = ["bash", build_script_file]
+    r = tools.subprocess_call_filtered(command, common.colors)
     if r != 0:
         raise OSError("Subprocess failed")
 
 
 class XilinxVivadoToolchain:
+    attr_translate = {
+        "keep": ("dont_touch", "true"),
+        "no_retiming": ("dont_touch", "true"),
+        "async_reg": ("async_reg", "true"),
+        "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
+    }
+
     def __init__(self):
         self.bitstream_commands = []
         self.additional_commands = []
         self.pre_synthesis_commands = []
         self.with_phys_opt = False
+        self.clocks = dict()
+        self.false_paths = set()
 
-    def _build_batch(self, platform, sources, build_name):
+    def _build_batch(self, platform, sources, edifs, ips, build_name):
         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]")
         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))
+        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)
-        tcl.append("synth_design -top top -part {} -include_dirs {{{}}}".format(platform.device, " ".join(platform.verilog_include_paths)))
+        # "-include_dirs {}" crashes Vivado 2016.4
+        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))
+        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")
         if self.with_phys_opt:
             tcl.append("phys_opt_design -directive AddRetime")
@@ -99,9 +148,10 @@ class XilinxVivadoToolchain:
         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("write_checkpoint -force {}_route.dcp".format(build_name))
         tcl.append("report_route_status -file {}_route_status.rpt".format(build_name))
         tcl.append("report_drc -file {}_drc.rpt".format(build_name))
-        tcl.append("report_timing_summary -max_paths 10 -file {}_timing.rpt".format(build_name))
+        tcl.append("report_timing_summary -datasheet -max_paths 10 -file {}_timing.rpt".format(build_name))
         tcl.append("report_power -file {}_power.rpt".format(build_name))
         for bitstream_command in self.bitstream_commands:
             tcl.append(bitstream_command.format(build_name=build_name))
@@ -111,21 +161,73 @@ class XilinxVivadoToolchain:
         tcl.append("quit")
         tools.write_to_file(build_name + ".tcl", "\n".join(tcl))
 
+    def _convert_clocks(self, platform):
+        for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid):
+            platform.add_platform_command(
+                "create_clock -name {clk} -period " + str(period) +
+                " [get_nets {clk}]", clk=clk)
+        for from_, to in sorted(self.false_paths,
+                                key=lambda x: (x[0].duid, x[1].duid)):
+            platform.add_platform_command(
+                "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
+        del self.clocks
+        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 "
+            "-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_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="/opt/Xilinx/Vivado", source=True, run=True, **kwargs):
-        tools.mkdir_noerror(build_dir)
+            toolchain_path=None, source=True, run=True, **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"
+        os.makedirs(build_dir, exist_ok=True)
         cwd = os.getcwd()
         os.chdir(build_dir)
 
         if not isinstance(fragment, _Fragment):
             fragment = fragment.get_fragment()
         platform.finalize(fragment)
+        self._convert_clocks(platform)
+        self._constrain(platform)
         v_output = platform.get_verilog(fragment, name=build_name, **kwargs)
         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")}
-        self._build_batch(platform, sources, build_name)
+        sources = platform.sources + [(v_file, "verilog", "work")]
+        edifs = platform.edifs
+        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)
@@ -135,5 +237,10 @@ class XilinxVivadoToolchain:
         return v_output.ns
 
     def add_period_constraint(self, platform, clk, period):
-        platform.add_platform_command("""create_clock -name {clk} -period """ + \
-            str(period) + """ [get_ports {clk}]""", clk=clk)
+        if clk in self.clocks:
+            raise ValueError("A period constraint already exists")
+        self.clocks[clk] = period
+
+    def add_false_path_constraint(self, platform, from_, to):
+        if (to, from_) not in self.false_paths:
+            self.false_paths.add((from_, to))