vendor.xilinx_7series: apply false path / max delay constraints.

This commit is contained in:
Darrell Harmon 2019-09-23 18:47:54 -06:00 committed by whitequark
parent da53048ad4
commit f3a8880cb8
5 changed files with 70 additions and 7 deletions

View file

@ -29,15 +29,18 @@ class FFSynchronizer(Elaboratable):
Signal connected to synchroniser output. Signal connected to synchroniser output.
o_domain : str o_domain : str
Name of output clock domain. 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 : int
Reset value of the flip-flops. On FPGAs, even if ``reset_less`` is True, 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. the :class:`FFSynchronizer` is still set to this value during initialization.
reset_less : bool reset_less : bool
If True (the default), this :class:`FFSynchronizer` is unaffected by ``o_domain`` reset. If ``True`` (the default), this :class:`FFSynchronizer` is unaffected by ``o_domain``
See "Note on Reset" below. 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 Platform override
----------------- -----------------
@ -61,7 +64,8 @@ class FFSynchronizer(Elaboratable):
:class:`FFSynchronizer` is reset by the ``o_domain`` reset only. :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) _check_stages(stages)
self.i = i self.i = i
@ -72,10 +76,16 @@ class FFSynchronizer(Elaboratable):
self._o_domain = o_domain self._o_domain = o_domain
self._stages = stages self._stages = stages
self._max_input_delay = max_input_delay
def elaborate(self, platform): def elaborate(self, platform):
if hasattr(platform, "get_ff_sync"): if hasattr(platform, "get_ff_sync"):
return platform.get_ff_sync(self) 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() m = Module()
flops = [Signal(self.i.shape(), name="stage{}".format(index), flops = [Signal(self.i.shape(), name="stage{}".format(index),
reset=self._reset, reset_less=self._reset_less) reset=self._reset, reset_less=self._reset_less)
@ -111,13 +121,16 @@ class ResetSynchronizer(Elaboratable):
stages : int, >=2 stages : int, >=2
Number of synchronization stages between input and output. The lowest safe number is 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. 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 Platform override
----------------- -----------------
Define the ``get_reset_sync`` platform method to override the implementation of Define the ``get_reset_sync`` platform method to override the implementation of
:class:`ResetSynchronizer`, e.g. to instantiate library cells directly. :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) _check_stages(stages)
self.arst = arst self.arst = arst
@ -125,10 +138,16 @@ class ResetSynchronizer(Elaboratable):
self._domain = domain self._domain = domain
self._stages = stages self._stages = stages
self._max_input_delay = None
def elaborate(self, platform): def elaborate(self, platform):
if hasattr(platform, "get_reset_sync"): if hasattr(platform, "get_reset_sync"):
return platform.get_reset_sync(self) 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 = Module()
m.domains += ClockDomain("reset_sync", async_reset=True, local=True) m.domains += ClockDomain("reset_sync", async_reset=True, local=True)
flops = [Signal(1, name="stage{}".format(index), reset=1) flops = [Signal(1, name="stage{}".format(index), reset=1)

View file

@ -533,3 +533,6 @@ class LatticeECP5Platform(TemplatedPlatform):
io_B=p_port[bit], io_B=p_port[bit],
) )
return m return m
# CDC primitives are not currently specialized for ECP5. While Diamond supports the necessary
# attributes (TBD); nextpnr-ecp5 does not.

View file

@ -564,3 +564,6 @@ class LatticeICE40Platform(TemplatedPlatform):
# Tristate and bidirectional buffers are not supported on iCE40 because it requires external # Tristate and bidirectional buffers are not supported on iCE40 because it requires external
# termination, which is incompatible for input and output differential I/Os. # 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.

View file

@ -78,6 +78,17 @@ class Xilinx7SeriesPlatform(TemplatedPlatform):
{% endfor %} {% endfor %}
{{get_override("script_after_read")|default("# (script_after_read placeholder)")}} {{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
synth_design -top {{name}} 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)")}} {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
report_timing_summary -file {{name}}_timing_synth.rpt report_timing_summary -file {{name}}_timing_synth.rpt
report_utilization -hierarchical -file {{name}}_utilization_hierachical_synth.rpt report_utilization -hierarchical -file {{name}}_utilization_hierachical_synth.rpt
@ -361,12 +372,27 @@ class Xilinx7SeriesPlatform(TemplatedPlatform):
) )
return m 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): def get_ff_sync(self, ff_sync):
m = Module() m = Module()
flops = [Signal(ff_sync.i.shape(), name="stage{}".format(index), flops = [Signal(ff_sync.i.shape(), name="stage{}".format(index),
reset=ff_sync._reset, reset_less=ff_sync._reset_less, reset=ff_sync._reset, reset_less=ff_sync._reset_less,
attrs={"ASYNC_REG": "TRUE"}) attrs={"ASYNC_REG": "TRUE"})
for index in range(ff_sync._stages)] 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): for i, o in zip((ff_sync.i, *flops), flops):
m.d[ff_sync._o_domain] += o.eq(i) m.d[ff_sync._o_domain] += o.eq(i)
m.d.comb += ff_sync.o.eq(flops[-1]) 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, flops = [Signal(1, name="stage{}".format(index), reset=1,
attrs={"ASYNC_REG": "TRUE"}) attrs={"ASYNC_REG": "TRUE"})
for index in range(reset_sync._stages)] 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): for i, o in zip((0, *flops), flops):
m.d.reset_sync += o.eq(i) m.d.reset_sync += o.eq(i)
m.d.comb += [ m.d.comb += [

View file

@ -412,6 +412,10 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform):
return m return m
def get_ff_sync(self, ff_sync): 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() m = Module()
flops = [Signal(ff_sync.i.shape(), name="stage{}".format(index), flops = [Signal(ff_sync.i.shape(), name="stage{}".format(index),
reset=ff_sync._reset, reset_less=ff_sync._reset_less, reset=ff_sync._reset, reset_less=ff_sync._reset_less,
@ -423,6 +427,10 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform):
return m return m
def get_reset_sync(self, reset_sync): 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 = Module()
m.domains += ClockDomain("reset_sync", async_reset=True, local=True) m.domains += ClockDomain("reset_sync", async_reset=True, local=True)
flops = [Signal(1, name="stage{}".format(index), reset=1, flops = [Signal(1, name="stage{}".format(index), reset=1,