vendor.xilinx_7series: apply false path / max delay constraints.
authorDarrell Harmon <dlharmon@users.noreply.github.com>
Tue, 24 Sep 2019 00:47:54 +0000 (18:47 -0600)
committerwhitequark <cz@m-labs.hk>
Tue, 24 Sep 2019 00:47:54 +0000 (00:47 +0000)
nmigen/lib/cdc.py
nmigen/vendor/lattice_ecp5.py
nmigen/vendor/lattice_ice40.py
nmigen/vendor/xilinx_7series.py
nmigen/vendor/xilinx_spartan_3_6.py

index d21df79ae7d0609eca11f1117e694dbb806de626..452814deb6fcc836fe345e3a31711ab5b032fb94 100644 (file)
@@ -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)
index f18e87c270eb42fe271bf6512844da716d108b27..89143ab049796e1726e488a07cbb4bf9e94444a4 100644 (file)
@@ -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.
index bc1df8f3ce2cb8f655e3e560ea3e02b60699c551..ffbbd0c99792aedd9eb1021799c79c50389e42c4 100644 (file)
@@ -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.
index 8102fde08f491139558a52d9e28e086b8d136815..da7fd2b2569d72374cc17b1e32dd31edc0327180 100644 (file)
@@ -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 += [
index 284cfab1934e9b2d16dd0d6bf55266f7990cf881..eb0aac017ac40b060f525ebefb14c92be2420ef1 100644 (file)
@@ -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,