build: merge with migen.build 27beffe7
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Wed, 28 Feb 2018 15:45:34 +0000 (16:45 +0100)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Wed, 28 Feb 2018 15:49:12 +0000 (16:49 +0100)
litex/build/altera/programmer.py
litex/build/generic_platform.py
litex/build/generic_programmer.py
litex/build/lattice/icestorm.py
litex/build/tools.py
litex/build/xilinx/common.py
litex/build/xilinx/ise.py
litex/build/xilinx/platform.py
litex/build/xilinx/programmer.py
litex/build/xilinx/vivado.py

index 93561d227effc0b5b85458192b638ce6349d0310..43d6ff9cdea4509fe51812fe9161f809b06aa1ab 100644 (file)
@@ -6,8 +6,7 @@ from litex.build.generic_programmer import GenericProgrammer
 class USBBlaster(GenericProgrammer):
     needs_bitreverse = False
 
-    def load_bitstream(self, bitstream_file, port=0):
-        usb_port = "[USB-{}]".format(port)
+    def load_bitstream(self, bitstream_file, cable_suffix=""):
         subprocess.call(["quartus_pgm", "-m", "jtag", "-c",
-                         "USB-Blaster{}".format(usb_port), "-o",
+                         "USB-Blaster{}".format(cable_suffix), "-o",
                          "p;{}".format(bitstream_file)])
index 83c0825a729b313ba796d34c9d5f1aea3c2967dd..b082c3b32440f15db7c8fc9218f670d8b58ab1e4 100644 (file)
@@ -366,6 +366,12 @@ class GenericPlatform:
             self.constraint_manager.get_io_signals(),
             create_clock_domains=False, **kwargs)
 
+    def get_edif(self, fragment, cell_library, vendor, device, **kwargs):
+        return edif.convert(
+            fragment,
+            self.constraint_manager.get_io_signals(),
+            cell_library, vendor, device, **kwargs)
+
     def build(self, fragment):
         raise NotImplementedError("GenericPlatform.build must be overloaded")
 
index 23a9eb12f65237e8d1cf24db69d8d55ad2ae41d4..b9540415c2d240bbe3dcc736d6ba7db4e59747c9 100644 (file)
@@ -17,10 +17,7 @@ class GenericProgrammer:
             fullname = os.path.join(fulldir, self.flash_proxy_basename)
             if os.path.exists(fullname):
                 return fullname
-        raise OSError(
-            "Failed to find flash proxy bitstream %s, searched:\n    %s\n" % (
-                self.flash_proxy_basename,
-                "\n    ".join(self.flash_proxy_dirs)))
+        raise OSError("Failed to find flash proxy bitstream")
 
     # must be overloaded by specific programmer
     def load_bitstream(self, bitstream_file):
index 5fd21ff2381de7ff34ea68bf9b0c0e8a104d8525..c64634743098de57da634e43c5eba23a704b2c5f 100644 (file)
@@ -36,13 +36,11 @@ def _build_pcf(named_sc, named_pc):
 def _run_icestorm(source, build_template, build_name, pnr_pkg_opts,
                   icetime_pkg_opts, icetime_constraint):
     if sys.platform == "win32" or sys.platform == "cygwin":
-        source_cmd = "call "
         script_ext = ".bat"
         shell = ["cmd", "/c"]
         build_script_contents = "@echo off\nrem Autogenerated by LiteX\n"
         fail_stmt = " || exit /b"
     else:
-        source_cmd = "source "
         script_ext = ".sh"
         shell = ["bash"]
         build_script_contents = "# Autogenerated by LiteX\nset -e\n"
@@ -159,12 +157,13 @@ class LatticeIceStormToolchain:
             "lp8k": ["cm81", "cm81:4k", "cm121", "cm121:4k", "cm225",
                      "cm225:4k"],
             "hx8k": ["cb132", "cb132:4k", "tq144:4k", "cm225", "ct256"],
+            "up5k": ["sg48"],
         }
 
         (family, series_size, package) = device_str.split("-")
         if family not in ["ice40"]:
             raise ValueError("Unknown device family")
-        if series_size not in ["lp384", "lp1k", "hx1k", "lp8k", "hx8k"]:
+        if series_size not in ["lp384", "lp1k", "hx1k", "lp8k", "hx8k", "up5k"]:
             raise ValueError("Invalid device series/size")
         if package not in valid_packages[series_size]:
             raise ValueError("Invalid device package")
index 598c0ff94522eed2e2983b6480a2af613fd335c2..2bfc1e77b28df84c8f43605de352a3d9d9cf34a3 100644 (file)
@@ -19,9 +19,6 @@ def write_to_file(filename, contents, force_unix=False):
     newline = None
     if force_unix:
         newline = "\n"
-    if os.path.exists(filename):
-        if open(filename, "r", newline=newline).read() == contents:
-            return
     with open(filename, "w", newline=newline) as f:
         f.write(contents)
 
@@ -41,23 +38,20 @@ def versions(path):
             continue
 
 
-def sub_rules(lines, rules, max_matches=1):
-    for line in lines:
-        n = max_matches
-        for pattern, color in rules:
-            line, m = re.subn(pattern, color, line, n)
-            n -= m
-            if not n:
-                break
-        yield line
+def sub_rules(line, rules, max_matches=1):
+    for pattern, color in rules:
+        line, matches = re.subn(pattern, color, line, max_matches)
+        max_matches -= matches
+        if not max_matches:
+            break
+    return line
 
 
 def subprocess_call_filtered(command, rules, *, max_matches=1, **kwargs):
-    proc = subprocess.Popen(command, stdout=subprocess.PIPE,
-                            universal_newlines=True, bufsize=1,
-                            **kwargs)
-    with proc:
-        for line in sub_rules(iter(proc.stdout.readline, ""),
-                              rules, max_matches):
-            sys.stdout.write(line)
-    return proc.returncode
+    with subprocess.Popen(command, stdout=subprocess.PIPE,
+                          universal_newlines=True, bufsize=1,
+                          **kwargs) as proc:
+        with open(proc.stdout.fileno(), errors="ignore", closefd=False) as stdout:
+            for line in stdout:
+                print(sub_rules(line, rules, max_matches), end="")
+        return proc.wait()
index 57ae89310c63d853e58b388460af5e6f6d4bfecf..8b0986a57fd409eec261a85697c6278f66b0367d 100644 (file)
@@ -2,11 +2,7 @@ import os
 import sys
 try:
     import colorama
-    # install escape sequence translation on Windows
-    if os.getenv("COLORAMA", "") == "force":
-        colorama.init(strip=False)
-    else:
-        colorama.init()
+    colorama.init()  # install escape sequence translation on Windows
     _have_colorama = True
 except ImportError:
     _have_colorama = False
@@ -35,54 +31,43 @@ if _have_colorama:
     ]
 
 
-def settings(path, name=None, ver=None, first=None):
-    if first == "version":
-        if not ver:
-            vers = tools.versions(path)
-            ver = max(vers)
-
-        full = os.path.join(path, str(ver), name)
-
-    elif first == "name":
-        path = os.path.join(path, name)
+def settings(path, ver=None, sub=None):
+    if ver is None:
+        vers = list(tools.versions(path))
+        if not vers:
+            raise OSError("no version directory for Xilinx tools found in "
+                          + path)
+        ver = max(vers)
 
-        if not ver:
-            vers = tools.versions(path)
-            ver = max(vers)
-
-        full = os.path.join(path, str(ver))
-
-    if not vers:
-        raise OSError(
-            "no version directory for Xilinx tools found in {}".format(
-                path))
+    full = os.path.join(path, str(ver))
+    if sub:
+        full = os.path.join(full, sub)
 
     search = [64, 32]
     if tools.arch_bits() == 32:
-        search = [32]
+        search.reverse()
 
     if sys.platform == "win32" or sys.platform == "cygwin":
         script_ext = "bat"
     else:
         script_ext = "sh"
 
-    searched_in = []
     for b in search:
         settings = os.path.join(full, "settings{0}.{1}".format(b, script_ext))
         if os.path.exists(settings):
             return settings
-        searched_in.append(settings)
 
-    raise OSError(
-        "no Xilinx tools settings file found.\n"
-        "Looked in:\n"
-        "   " +
-        "\n   ".join(searched_in))
+    raise OSError("no Xilinx tools settings file found")
 
 
 class XilinxMultiRegImpl(MultiRegImpl):
     def __init__(self, *args, **kwargs):
         MultiRegImpl.__init__(self, *args, **kwargs)
+        i = self.i
+        if not hasattr(i, "attr"):
+            i0, i = i, Signal()
+            self.comb += i.eq(i0)
+        self.regs[0].attr.add("mr_ff")
         for r in self.regs:
             r.attr.add("async_reg")
             r.attr.add("no_shreg_extract")
@@ -103,12 +88,11 @@ class XilinxAsyncResetSynchronizerImpl(Module):
         self.specials += [
             Instance("FDPE", p_INIT=1, i_D=0, i_PRE=async_reset,
                 i_CE=1, i_C=cd.clk, o_Q=rst_meta,
-                attr={"async_reg", "ars_ff"}),
+                attr={"async_reg", "ars_ff1"}),
             Instance("FDPE", p_INIT=1, i_D=rst_meta, i_PRE=async_reset,
                 i_CE=1, i_C=cd.clk, o_Q=cd.rst,
-                attr={"async_reg", "ars_ff"})
+                attr={"async_reg", "ars_ff2"})
         ]
-        async_reset.attr.add("ars_false_path")
 
 
 class XilinxAsyncResetSynchronizer:
@@ -139,27 +123,31 @@ class XilinxDifferentialOutput:
         return XilinxDifferentialOutputImpl(dr.i, dr.o_p, dr.o_n)
 
 
-class XilinxDDROutputImpl(Module):
+xilinx_special_overrides = {
+    MultiReg:               XilinxMultiReg,
+    AsyncResetSynchronizer: XilinxAsyncResetSynchronizer,
+    DifferentialInput:      XilinxDifferentialInput,
+    DifferentialOutput:     XilinxDifferentialOutput
+}
+
+
+class XilinxDDROutputImplS6(Module):
     def __init__(self, i1, i2, o, clk):
         self.specials += Instance("ODDR2",
-                p_DDR_ALIGNMENT="NONE", p_INIT=0, p_SRTYPE="SYNC",
+                p_DDR_ALIGNMENT="C0", p_INIT=0, p_SRTYPE="SYNC",
                 i_C0=clk, i_C1=~clk, i_CE=1, i_S=0, i_R=0,
                 i_D0=i1, i_D1=i2, o_Q=o,
         )
 
 
-class XilinxDDROutput:
+class XilinxDDROutputS6:
     @staticmethod
     def lower(dr):
-        return XilinxDDROutputImpl(dr.i1, dr.i2, dr.o, dr.clk)
+        return XilinxDDROutputImplS6(dr.i1, dr.i2, dr.o, dr.clk)
 
 
-xilinx_special_overrides = {
-    MultiReg:               XilinxMultiReg,
-    AsyncResetSynchronizer: XilinxAsyncResetSynchronizer,
-    DifferentialInput:      XilinxDifferentialInput,
-    DifferentialOutput:     XilinxDifferentialOutput,
-    DDROutput:              XilinxDDROutput
+xilinx_s6_special_overrides = {
+    DDROutput:              XilinxDDROutputS6
 }
 
 
@@ -178,6 +166,60 @@ class XilinxDDROutputS7:
         return XilinxDDROutputImplS7(dr.i1, dr.i2, dr.o, dr.clk)
 
 
+class XilinxDDRInputImplS7(Module):
+    def __init__(self, i, o1, o2, clk):
+        self.specials += Instance("IDDR",
+                p_DDR_CLK_EDGE="SAME_EDGE_PIPELINED",
+                i_C=clk, i_CE=1, i_S=0, i_R=0,
+                o_D=i, i_Q1=o1, i_Q2=o2,
+        )
+
+
+class XilinxDDRInputS7:
+    @staticmethod
+    def lower(dr):
+        return XilinxDDRInputImplS7(dr.i, dr.o1, dr.o2, dr.clk)
+
+
 xilinx_s7_special_overrides = {
-    DDROutput:              XilinxDDROutputS7
+    DDROutput:              XilinxDDROutputS7,
+    DDRInput:               XilinxDDRInputS7
+}
+
+
+class XilinxDDROutputImplKU(Module):
+    def __init__(self, i1, i2, o, clk):
+        self.specials += Instance("ODDRE1",
+                i_C=clk, i_SR=0,
+                i_D1=i1, i_D2=i2, o_Q=o,
+        )
+
+
+class XilinxDDROutputKU:
+    @staticmethod
+    def lower(dr):
+        return XilinxDDROutputImplKU(dr.i1, dr.i2, dr.o, dr.clk)
+
+
+class XilinxDDRInputImplKU(Module):
+    def __init__(self, i, o1, o2, clk):
+        self.specials += Instance("IDDRE1",
+            p_DDR_CLK_EDGE="SAME_EDGE_PIPELINED",
+            p_IS_C_INVERTED=0,
+            i_D=i,
+            o_Q1=o1, o_Q2=o2,
+            i_C=clk, i_CB=~clk,
+            i_R=0
+        )
+
+
+class XilinxDDRInputKU:
+    @staticmethod
+    def lower(dr):
+        return XilinxDDRInputImplKU(dr.i, dr.o1, dr.o2, dr.clk)
+
+
+xilinx_ku_special_overrides = {
+    DDROutput:              XilinxDDROutputKU,
+    DDRInput:               XilinxDDRInputKU
 }
index 2d51e6950f68791289fc5facadfff80e129bf32d..10714248e4ea932b373aa8ff25eb64be86fa670a 100644 (file)
@@ -83,7 +83,7 @@ synth_xilinx -top top -edif {build_name}.edif""".format(build_name=build_name)
 
 
 def _run_ise(build_name, ise_path, source, mode, ngdbuild_opt,
-        bitgen_opt, ise_commands, map_opt, par_opt, ver=None):
+        toolchain, platform, ver=None):
     if sys.platform == "win32" or sys.platform == "cygwin":
         source_cmd = "call "
         script_ext = ".bat"
@@ -107,14 +107,24 @@ xst -ifn {build_name}.xst{fail_stmt}
 
     build_script_contents += """
 ngdbuild {ngdbuild_opt} -uc {build_name}.ucf {build_name}.{ext} {build_name}.ngd{fail_stmt}
+"""
+    if mode == "cpld":
+        build_script_contents += """
+cpldfit -ofmt verilog {par_opt} -p {device} {build_name}.ngd{fail_stmt}
+taengine -f {build_name}.vm6 -detail -iopath -l {build_name}.tim{fail_stmt}
+hprep6 -s IEEE1532 -i {build_name}.vm6{fail_stmt}
+"""
+    else:
+        build_script_contents += """
 map {map_opt} -o {build_name}_map.ncd {build_name}.ngd {build_name}.pcf{fail_stmt}
 par {par_opt} {build_name}_map.ncd {build_name}.ncd {build_name}.pcf{fail_stmt}
 bitgen {bitgen_opt} {build_name}.ncd {build_name}.bit{fail_stmt}
 """
     build_script_contents = build_script_contents.format(build_name=build_name,
-            ngdbuild_opt=ngdbuild_opt, bitgen_opt=bitgen_opt, ext=ext,
-            par_opt=par_opt, map_opt=map_opt, fail_stmt=fail_stmt)
-    build_script_contents += ise_commands.format(build_name=build_name)
+            ngdbuild_opt=ngdbuild_opt, bitgen_opt=toolchain.bitgen_opt, ext=ext,
+            par_opt=toolchain.par_opt, map_opt=toolchain.map_opt,
+            device=platform.device, fail_stmt=fail_stmt)
+    build_script_contents += toolchain.ise_commands.format(build_name=build_name)
     build_script_file = "build_" + build_name + script_ext
     tools.write_to_file(build_script_file, build_script_contents, force_unix=False)
     command = shell + [build_script_file]
@@ -128,8 +138,9 @@ class XilinxISEToolchain:
         "keep": ("keep", "true"),
         "no_retiming": ("register_balancing", "no"),
         "async_reg": None,
-        "ars_ff": None,
-        "ars_false_path": None,
+        "mr_ff": None,
+        "ars_ff1": None,
+        "ars_ff2": None,
         "no_shreg_extract": ("shreg_extract", "no")
     }
 
@@ -145,7 +156,7 @@ class XilinxISEToolchain:
         self.ise_commands = ""
 
     def build(self, platform, fragment, build_dir="build", build_name="top",
-            toolchain_path=None, source=None, run=True, mode="xst", **kwargs):
+            toolchain_path=None, source=True, run=True, mode="xst", **kwargs):
         if not isinstance(fragment, _Fragment):
             fragment = fragment.get_fragment()
         if toolchain_path is None:
@@ -155,8 +166,6 @@ class XilinxISEToolchain:
                 toolchain_path = "/cygdrive/c/Xilinx"
             else:
                 toolchain_path = "/opt/Xilinx"
-        if source is None:
-            source = sys.platform != "win32"
 
         platform.finalize(fragment)
         ngdbuild_opt = self.ngdbuild_opt
@@ -166,31 +175,46 @@ class XilinxISEToolchain:
         cwd = os.getcwd()
         os.chdir(build_dir)
         try:
-            if mode == "xst" or mode == "yosys":
+            if mode in ("xst", "yosys", "cpld"):
                 v_output = platform.get_verilog(fragment, name=build_name, **kwargs)
                 vns = v_output.ns
                 named_sc, named_pc = platform.resolve_signals(vns)
                 v_file = build_name + ".v"
                 v_output.write(v_file)
                 sources = platform.sources | {(v_file, "verilog", "work")}
-                if mode == "xst":
+                if mode in ("xst", "cpld"):
                     _build_xst_files(platform.device, sources, platform.verilog_include_paths, build_name, self.xst_opt)
-                    isemode = "xst"
+                    isemode = mode
                 else:
                     _run_yosys(platform.device, sources, platform.verilog_include_paths, build_name)
                     isemode = "edif"
                     ngdbuild_opt += "-p " + platform.device
 
+            if mode == "mist":
+                from mist import synthesize
+                synthesize(fragment, platform.constraint_manager.get_io_signals())
+
+            if mode == "edif" or mode == "mist":
+                e_output = platform.get_edif(fragment)
+                vns = e_output.ns
+                named_sc, named_pc = platform.resolve_signals(vns)
+                e_file = build_name + ".edif"
+                e_output.write(e_file)
+                isemode = "edif"
+
             tools.write_to_file(build_name + ".ucf", _build_ucf(named_sc, named_pc))
             if run:
                 _run_ise(build_name, toolchain_path, source, isemode,
-                         ngdbuild_opt, self.bitgen_opt, self.ise_commands,
-                         self.map_opt, self.par_opt)
+                         ngdbuild_opt, self, platform)
         finally:
             os.chdir(cwd)
 
         return vns
 
+    # ISE is broken and you must use *separate* TNM_NET objects for period
+    # constraints and other constraints otherwise it will be unable to trace
+    # them through clock objects like DCM and PLL objects.
+
     def add_period_constraint(self, platform, clk, period):
         platform.add_platform_command(
             """
index 64e9d97f221b172d82fb812a5eb5731213855b91..e17ad4f9e7659ccb8843bb6fe2e1a1e9344a2e0d 100644 (file)
@@ -1,5 +1,3 @@
-import os
-
 from litex.build.generic_platform import GenericPlatform
 from litex.build.xilinx import common, vivado, ise
 
@@ -22,12 +20,18 @@ class XilinxPlatform(GenericPlatform):
 
     def get_verilog(self, *args, special_overrides=dict(), **kwargs):
         so = dict(common.xilinx_special_overrides)
+        if self.device[:3] == "xc6":
+            so.update(common.xilinx_s6_special_overrides)
         if self.device[:3] == "xc7":
             so.update(common.xilinx_s7_special_overrides)
+        if self.device[:4] == "xcku":
+            so.update(common.xilinx_ku_special_overrides)
         so.update(special_overrides)
         return GenericPlatform.get_verilog(self, *args,
             special_overrides=so, attr_translate=self.toolchain.attr_translate, **kwargs)
 
+    def get_edif(self, fragment, **kwargs):
+        return GenericPlatform.get_edif(self, fragment, "UNISIMS", "Xilinx", self.device, **kwargs)
 
     def build(self, *args, **kwargs):
         return self.toolchain.build(self, *args, **kwargs)
index 9232a12e2bb63152a46105c84b5b36affd3909a5..d1ae01bcad8bab80f9a6d66dc71c65a7502e890c 100644 (file)
@@ -6,6 +6,42 @@ from litex.build.generic_programmer import GenericProgrammer
 from litex.build.xilinx import common
 
 
+def _run_urjtag(cmds):
+    with subprocess.Popen("jtag", stdin=subprocess.PIPE) as process:
+        process.stdin.write(cmds.encode("ASCII"))
+        process.communicate()
+
+
+class UrJTAG(GenericProgrammer):
+    needs_bitreverse = True
+
+    def __init__(self, cable, flash_proxy_basename=None):
+        GenericProgrammer.__init__(self, flash_proxy_basename)
+        self.cable = cable
+
+    def load_bitstream(self, bitstream_file):
+        cmds = """cable {cable}
+detect
+pld load {bitstream}
+quit
+""".format(bitstream=bitstream_file, cable=self.cable)
+        _run_urjtag(cmds)
+
+    def flash(self, address, data_file):
+        flash_proxy = self.find_flash_proxy()
+        cmds = """cable {cable}
+detect
+pld load "{flash_proxy}"
+initbus fjmem opcode=000010
+frequency 6000000
+detectflash 0
+endian big
+flashmem "{address}" "{data_file}" noverify
+""".format(flash_proxy=flash_proxy, address=address, data_file=data_file,
+           cable=self.cable)
+        _run_urjtag(cmds)
+
+
 class XC3SProg(GenericProgrammer):
     needs_bitreverse = False
 
index 5201dc5f6b78a7a89291956165275cf75469c2f1..0b7fbdb1a4bd47caf227aa1b40198c7a5fa50809 100644 (file)
@@ -79,8 +79,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
     }
 
@@ -94,8 +95,7 @@ class XilinxVivadoToolchain:
 
     def _build_batch(self, platform, sources, edifs, 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))
         for filename, language, library in sources:
             filename_tcl = "{" + filename + "}"
             tcl.append("add_files " + filename_tcl)
@@ -111,7 +111,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))
@@ -119,11 +118,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")
@@ -147,12 +144,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
@@ -160,30 +156,30 @@ 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"
-            elif sys.platform == "cygwin":
-                toolchain_path = "/cygdrive/c/Xilinx"
-            else:
-                toolchain_path = "/opt/Xilinx"
+            toolchain_path="/opt/Xilinx/Vivado", source=True, run=True, **kwargs):
         os.makedirs(build_dir, exist_ok=True)
         cwd = os.getcwd()
         os.chdir(build_dir)
@@ -214,4 +210,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))