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
-----------------
: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
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)
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
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)
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.
# 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.
{% 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
)
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])
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 += [
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,
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,