Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions nmigen/lib/cdc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------------
Expand All @@ -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
Expand All @@ -72,10 +76,16 @@ def __init__(self, i, o, *, o_domain="sync", reset=0, reset_less=True, stages=2)
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)
Expand Down Expand Up @@ -111,24 +121,33 @@ 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

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)
Expand Down
3 changes: 3 additions & 0 deletions nmigen/vendor/lattice_ecp5.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,3 +533,6 @@ def get_diff_input_output(self, pin, p_port, n_port, attrs, invert):
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.
3 changes: 3 additions & 0 deletions nmigen/vendor/lattice_ice40.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,3 +564,6 @@ def get_diff_output(self, pin, p_port, n_port, attrs, invert):

# 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.
30 changes: 30 additions & 0 deletions nmigen/vendor/xilinx_7series.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -361,12 +372,27 @@ def get_diff_input_output(self, pin, p_port, n_port, attrs, invert):
)
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])
Expand All @@ -378,6 +404,10 @@ def get_reset_sync(self, reset_sync):
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 += [
Expand Down
8 changes: 8 additions & 0 deletions nmigen/vendor/xilinx_spartan_3_6.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,10 @@ def get_diff_input_output(self, pin, p_port, n_port, attrs, invert):
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,
Expand All @@ -423,6 +427,10 @@ def get_ff_sync(self, ff_sync):
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,
Expand Down