vendor.xilinx_7series: apply false path / max delay constraints.
This commit is contained in:
parent
da53048ad4
commit
f3a8880cb8
|
@ -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)
|
||||||
|
|
3
nmigen/vendor/lattice_ecp5.py
vendored
3
nmigen/vendor/lattice_ecp5.py
vendored
|
@ -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.
|
||||||
|
|
3
nmigen/vendor/lattice_ice40.py
vendored
3
nmigen/vendor/lattice_ice40.py
vendored
|
@ -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.
|
||||||
|
|
30
nmigen/vendor/xilinx_7series.py
vendored
30
nmigen/vendor/xilinx_7series.py
vendored
|
@ -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 += [
|
||||||
|
|
8
nmigen/vendor/xilinx_spartan_3_6.py
vendored
8
nmigen/vendor/xilinx_spartan_3_6.py
vendored
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue