From 91414d2eed036748f139136e476e90d8b61dea42 Mon Sep 17 00:00:00 2001 From: Darrell Harmon Date: Mon, 23 Sep 2019 18:47:54 -0600 Subject: [PATCH] vendor.xilinx_7series: apply false path / max delay constraints. --- nmigen/lib/cdc.py | 33 +++++++++++++++++++++++------ nmigen/vendor/lattice_ecp5.py | 3 +++ nmigen/vendor/lattice_ice40.py | 3 +++ nmigen/vendor/xilinx_7series.py | 30 ++++++++++++++++++++++++++ nmigen/vendor/xilinx_spartan_3_6.py | 8 +++++++ 5 files changed, 70 insertions(+), 7 deletions(-) diff --git a/nmigen/lib/cdc.py b/nmigen/lib/cdc.py index d21df79..452814d 100644 --- a/nmigen/lib/cdc.py +++ b/nmigen/lib/cdc.py @@ -29,15 +29,18 @@ class FFSynchronizer(Elaboratable): Signal connected to synchroniser output. o_domain : str Name of output clock domain. - stages : int - Number of synchronization stages between input and output. The lowest safe number is 2, - with higher numbers reducing MTBF further, at the cost of increased latency. reset : int Reset value of the flip-flops. On FPGAs, even if ``reset_less`` is True, the :class:`FFSynchronizer` is still set to this value during initialization. reset_less : bool - If True (the default), this :class:`FFSynchronizer` is unaffected by ``o_domain`` reset. - See "Note on Reset" below. + If ``True`` (the default), this :class:`FFSynchronizer` is unaffected by ``o_domain`` + reset. See "Note on Reset" below. + stages : int + Number of synchronization stages between input and output. The lowest safe number is 2, + with higher numbers reducing MTBF further, at the cost of increased latency. + max_input_delay : None or float + Maximum delay from the input signal's clock to the first synchronization stage. + If specified and the platform does not support it, elaboration will fail. Platform override ----------------- @@ -61,7 +64,8 @@ class FFSynchronizer(Elaboratable): :class:`FFSynchronizer` is reset by the ``o_domain`` reset only. """ - def __init__(self, i, o, *, o_domain="sync", reset=0, reset_less=True, stages=2): + def __init__(self, i, o, *, o_domain="sync", reset=0, reset_less=True, stages=2, + max_input_delay=None): _check_stages(stages) self.i = i @@ -72,10 +76,16 @@ class FFSynchronizer(Elaboratable): self._o_domain = o_domain self._stages = stages + self._max_input_delay = max_input_delay + def elaborate(self, platform): if hasattr(platform, "get_ff_sync"): return platform.get_ff_sync(self) + if self._max_input_delay is not None: + raise NotImplementedError("Platform {!r} does not support constraining input delay " + "for FFSynchronizer".format(platform)) + m = Module() flops = [Signal(self.i.shape(), name="stage{}".format(index), reset=self._reset, reset_less=self._reset_less) @@ -111,13 +121,16 @@ class ResetSynchronizer(Elaboratable): stages : int, >=2 Number of synchronization stages between input and output. The lowest safe number is 2, with higher numbers reducing MTBF further, at the cost of increased deassertion latency. + max_input_delay : None or float + Maximum delay from the input signal's clock to the first synchronization stage. + If specified and the platform does not support it, elaboration will fail. Platform override ----------------- Define the ``get_reset_sync`` platform method to override the implementation of :class:`ResetSynchronizer`, e.g. to instantiate library cells directly. """ - def __init__(self, arst, *, domain="sync", stages=2): + def __init__(self, arst, *, domain="sync", stages=2, max_input_delay=None): _check_stages(stages) self.arst = arst @@ -125,10 +138,16 @@ class ResetSynchronizer(Elaboratable): self._domain = domain self._stages = stages + self._max_input_delay = None + def elaborate(self, platform): if hasattr(platform, "get_reset_sync"): return platform.get_reset_sync(self) + if self._max_input_delay is not None: + raise NotImplementedError("Platform {!r} does not support constraining input delay " + "for ResetSynchronizer".format(platform)) + m = Module() m.domains += ClockDomain("reset_sync", async_reset=True, local=True) flops = [Signal(1, name="stage{}".format(index), reset=1) diff --git a/nmigen/vendor/lattice_ecp5.py b/nmigen/vendor/lattice_ecp5.py index f18e87c..89143ab 100644 --- a/nmigen/vendor/lattice_ecp5.py +++ b/nmigen/vendor/lattice_ecp5.py @@ -533,3 +533,6 @@ class LatticeECP5Platform(TemplatedPlatform): io_B=p_port[bit], ) return m + + # CDC primitives are not currently specialized for ECP5. While Diamond supports the necessary + # attributes (TBD); nextpnr-ecp5 does not. diff --git a/nmigen/vendor/lattice_ice40.py b/nmigen/vendor/lattice_ice40.py index bc1df8f..ffbbd0c 100644 --- a/nmigen/vendor/lattice_ice40.py +++ b/nmigen/vendor/lattice_ice40.py @@ -564,3 +564,6 @@ class LatticeICE40Platform(TemplatedPlatform): # Tristate and bidirectional buffers are not supported on iCE40 because it requires external # termination, which is incompatible for input and output differential I/Os. + + # CDC primitives are not currently specialized for iCE40. It is not known if iCECube2 supports + # the necessary attributes; nextpnr-ice40 does not. diff --git a/nmigen/vendor/xilinx_7series.py b/nmigen/vendor/xilinx_7series.py index 8102fde..da7fd2b 100644 --- a/nmigen/vendor/xilinx_7series.py +++ b/nmigen/vendor/xilinx_7series.py @@ -78,6 +78,17 @@ class Xilinx7SeriesPlatform(TemplatedPlatform): {% endfor %} {{get_override("script_after_read")|default("# (script_after_read placeholder)")}} synth_design -top {{name}} + foreach cell [get_cells -quiet -hier -filter {nmigen.vivado.false_path == "TRUE"}] { + set_false_path -to $cell + } + foreach cell [get_cells -quiet -hier -filter {nmigen.vivado.max_delay != ""}] { + set clock [get_clocks -of_objects \ + [all_fanin -flat -startpoints_only [get_pin $cell/D]]] + if {[llength $clock] != 0} { + set_max_delay -datapath_only -from $clock \ + -to [get_cells $cell] [get_property nmigen.vivado.max_delay $cell] + } + } {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}} report_timing_summary -file {{name}}_timing_synth.rpt report_utilization -hierarchical -file {{name}}_utilization_hierachical_synth.rpt @@ -361,12 +372,27 @@ class Xilinx7SeriesPlatform(TemplatedPlatform): ) return m + # The synchronizer implementations below apply two separate but related timing constraints. + # + # First, the ASYNC_REG attribute prevents inference of shift registers from synchronizer FFs, + # and constraints the FFs to be placed as close as possible, ideally in one CLB. This attribute + # only affects the synchronizer FFs themselves. + # + # Second, the nmigen.vivado.false_path or nmigen.vivado.max_delay attribute affects the path + # into the synchronizer. If maximum input delay is specified, a datapath-only maximum delay + # constraint is applied, limiting routing delay (and therefore skew) at the synchronizer input. + # Otherwise, a false path constraint is used to omit the input path from the timing analysis. + def get_ff_sync(self, ff_sync): m = Module() flops = [Signal(ff_sync.i.shape(), name="stage{}".format(index), reset=ff_sync._reset, reset_less=ff_sync._reset_less, attrs={"ASYNC_REG": "TRUE"}) for index in range(ff_sync._stages)] + if ff_sync._max_input_delay is None: + flops[0].attrs["nmigen.vivado.false_path"] = "TRUE" + else: + flops[0].attrs["nmigen.vivado.max_delay"] = ff_sync._max_input_delay for i, o in zip((ff_sync.i, *flops), flops): m.d[ff_sync._o_domain] += o.eq(i) m.d.comb += ff_sync.o.eq(flops[-1]) @@ -378,6 +404,10 @@ class Xilinx7SeriesPlatform(TemplatedPlatform): flops = [Signal(1, name="stage{}".format(index), reset=1, attrs={"ASYNC_REG": "TRUE"}) for index in range(reset_sync._stages)] + if reset_sync._max_input_delay is None: + flops[0].attrs["nmigen.vivado.false_path"] = "TRUE" + else: + flops[0].attrs["nmigen.vivado.max_delay"] = reset_sync._max_input_delay for i, o in zip((0, *flops), flops): m.d.reset_sync += o.eq(i) m.d.comb += [ diff --git a/nmigen/vendor/xilinx_spartan_3_6.py b/nmigen/vendor/xilinx_spartan_3_6.py index 284cfab..eb0aac0 100644 --- a/nmigen/vendor/xilinx_spartan_3_6.py +++ b/nmigen/vendor/xilinx_spartan_3_6.py @@ -412,6 +412,10 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform): return m def get_ff_sync(self, ff_sync): + if ff_sync._max_input_delay is not None: + raise NotImplementedError("Platform {!r} does not support constraining input delay " + "for FFSynchronizer".format(self)) + m = Module() flops = [Signal(ff_sync.i.shape(), name="stage{}".format(index), reset=ff_sync._reset, reset_less=ff_sync._reset_less, @@ -423,6 +427,10 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform): return m def get_reset_sync(self, reset_sync): + if reset_sync._max_input_delay is not None: + raise NotImplementedError("Platform {!r} does not support constraining input delay " + "for ResetSynchronizer".format(self)) + m = Module() m.domains += ClockDomain("reset_sync", async_reset=True, local=True) flops = [Signal(1, name="stage{}".format(index), reset=1, -- 2.30.2