diff --git a/litex/build/altera/programmer.py b/litex/build/altera/programmer.py index 93561d227..43d6ff9cd 100644 --- a/litex/build/altera/programmer.py +++ b/litex/build/altera/programmer.py @@ -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)]) diff --git a/litex/build/generic_platform.py b/litex/build/generic_platform.py index 83c0825a7..b082c3b32 100644 --- a/litex/build/generic_platform.py +++ b/litex/build/generic_platform.py @@ -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") diff --git a/litex/build/generic_programmer.py b/litex/build/generic_programmer.py index 23a9eb12f..b9540415c 100644 --- a/litex/build/generic_programmer.py +++ b/litex/build/generic_programmer.py @@ -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): diff --git a/litex/build/lattice/icestorm.py b/litex/build/lattice/icestorm.py index 5fd21ff23..c64634743 100644 --- a/litex/build/lattice/icestorm.py +++ b/litex/build/lattice/icestorm.py @@ -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") diff --git a/litex/build/tools.py b/litex/build/tools.py index 598c0ff94..2bfc1e77b 100644 --- a/litex/build/tools.py +++ b/litex/build/tools.py @@ -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() diff --git a/litex/build/xilinx/common.py b/litex/build/xilinx/common.py index 57ae89310..8b0986a57 100644 --- a/litex/build/xilinx/common.py +++ b/litex/build/xilinx/common.py @@ -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) +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) - full = os.path.join(path, str(ver), name) - - elif first == "name": - path = os.path.join(path, name) - - 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 } diff --git a/litex/build/xilinx/ise.py b/litex/build/xilinx/ise.py index 2d51e6950..10714248e 100644 --- a/litex/build/xilinx/ise.py +++ b/litex/build/xilinx/ise.py @@ -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( """ diff --git a/litex/build/xilinx/platform.py b/litex/build/xilinx/platform.py index 64e9d97f2..e17ad4f9e 100644 --- a/litex/build/xilinx/platform.py +++ b/litex/build/xilinx/platform.py @@ -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) diff --git a/litex/build/xilinx/programmer.py b/litex/build/xilinx/programmer.py index 9232a12e2..d1ae01bca 100644 --- a/litex/build/xilinx/programmer.py +++ b/litex/build/xilinx/programmer.py @@ -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 diff --git a/litex/build/xilinx/vivado.py b/litex/build/xilinx/vivado.py index 5201dc5f6..0b7fbdb1a 100644 --- a/litex/build/xilinx/vivado.py +++ b/litex/build/xilinx/vivado.py @@ -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))