riscv-formal-nmigen/nmigen/lib/cdc.py

253 lines
9.8 KiB
Python

from .._utils import deprecated
from .. import *
__all__ = ["FFSynchronizer", "AsyncFFSynchronizer", "ResetSynchronizer", "PulseSynchronizer"]
def _check_stages(stages):
if not isinstance(stages, int) or stages < 1:
raise TypeError("Synchronization stage count must be a positive integer, not {!r}"
.format(stages))
if stages < 2:
raise ValueError("Synchronization stage count may not safely be less than 2")
class FFSynchronizer(Elaboratable):
"""Resynchronise a signal to a different clock domain.
Consists of a chain of flip-flops. Eliminates metastabilities at the output, but provides
no other guarantee as to the safe domain-crossing of a signal.
Parameters
----------
i : Signal(n), in
Signal to be resynchronised.
o : Signal(n), out
Signal connected to synchroniser output.
o_domain : str
Name of output clock domain.
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.
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, in seconds.
If specified and the platform does not support it, elaboration will fail.
Platform override
-----------------
Define the ``get_ff_sync`` platform method to override the implementation of
:class:`FFSynchronizer`, e.g. to instantiate library cells directly.
Note on Reset
-------------
:class:`FFSynchronizer` is non-resettable by default. Usually this is the safest option;
on FPGAs the :class:`FFSynchronizer` will still be initialized to its ``reset`` value when
the FPGA loads its configuration.
However, in designs where the value of the :class:`FFSynchronizer` must be valid immediately
after reset, consider setting ``reset_less`` to False if any of the following is true:
- You are targeting an ASIC, or an FPGA that does not allow arbitrary initial flip-flop states;
- Your design features warm (non-power-on) resets of ``o_domain``, so the one-time
initialization at power on is insufficient;
- Your design features a sequenced reset, and the :class:`FFSynchronizer` must maintain
its reset value until ``o_domain`` reset specifically is deasserted.
: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,
max_input_delay=None):
_check_stages(stages)
self.i = i
self.o = o
self._reset = reset
self._reset_less = reset_less
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 '{}' does not support constraining input delay "
"for FFSynchronizer"
.format(type(platform).__name__))
m = Module()
flops = [Signal(self.i.shape(), name="stage{}".format(index),
reset=self._reset, reset_less=self._reset_less)
for index in range(self._stages)]
for i, o in zip((self.i, *flops), flops):
m.d[self._o_domain] += o.eq(i)
m.d.comb += self.o.eq(flops[-1])
return m
class AsyncFFSynchronizer(Elaboratable):
"""Synchronize deassertion of an asynchronous signal.
The signal driven by the :class:`AsyncFFSynchronizer` is asserted asynchronously and deasserted
synchronously, eliminating metastability during deassertion.
This synchronizer is primarily useful for resets and reset-like signals.
Parameters
----------
i : Signal(1), in
Asynchronous input signal, to be synchronized.
o : Signal(1), out
Synchronously released output signal.
domain : str
Name of clock domain to reset.
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.
async_edge : str
The edge of the input signal which causes the output to be set. Must be one of "pos" or "neg".
"""
def __init__(self, i, o, *, domain="sync", stages=2, async_edge="pos", max_input_delay=None):
_check_stages(stages)
self.i = i
self.o = o
self._domain = domain
self._stages = stages
if async_edge not in ("pos", "neg"):
raise ValueError("AsyncFFSynchronizer async edge must be one of 'pos' or 'neg', "
"not {!r}"
.format(async_edge))
self._edge = async_edge
self._max_input_delay = max_input_delay
def elaborate(self, platform):
if hasattr(platform, "get_async_ff_sync"):
return platform.get_async_ff_sync(self)
if self._max_input_delay is not None:
raise NotImplementedError("Platform '{}' does not support constraining input delay "
"for AsyncFFSynchronizer"
.format(type(platform).__name__))
m = Module()
m.domains += ClockDomain("async_ff", async_reset=True, local=True)
flops = [Signal(1, name="stage{}".format(index), reset=1)
for index in range(self._stages)]
for i, o in zip((0, *flops), flops):
m.d.async_ff += o.eq(i)
if self._edge == "pos":
m.d.comb += ResetSignal("async_ff").eq(self.i)
else:
m.d.comb += ResetSignal("async_ff").eq(~self.i)
m.d.comb += [
ClockSignal("async_ff").eq(ClockSignal(self._domain)),
self.o.eq(flops[-1])
]
return m
class ResetSynchronizer(Elaboratable):
"""Synchronize deassertion of a clock domain reset.
The reset of the clock domain driven by the :class:`ResetSynchronizer` is asserted
asynchronously and deasserted synchronously, eliminating metastability during deassertion.
The driven clock domain could use a reset that is asserted either synchronously or
asynchronously; a reset is always deasserted synchronously. A domain with an asynchronously
asserted reset is useful if the clock of the domain may be gated, yet the domain still
needs to be reset promptly; otherwise, synchronously asserted reset (the default) should
be used.
Parameters
----------
arst : Signal(1), in
Asynchronous reset signal, to be synchronized.
domain : str
Name of clock domain to reset.
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, in seconds.
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, max_input_delay=None):
_check_stages(stages)
self.arst = arst
self._domain = domain
self._stages = stages
self._max_input_delay = max_input_delay
def elaborate(self, platform):
return AsyncFFSynchronizer(self.arst, ResetSignal(self._domain), domain=self._domain,
stages=self._stages, max_input_delay=self._max_input_delay)
class PulseSynchronizer(Elaboratable):
"""A one-clock pulse on the input produces a one-clock pulse on the output.
If the output clock is faster than the input clock, then the input may be safely asserted at
100% duty cycle. Otherwise, if the clock ratio is n : 1, the input may be asserted at most once
in every n input clocks, else pulses may be dropped.
Other than this there is no constraint on the ratio of input and output clock frequency.
Parameters
----------
i_domain : str
Name of input clock domain.
o-domain : str
Name of output clock domain.
sync_stages : int
Number of synchronisation flops between the two clock domains. 2 is the default, and
minimum safe value. High-frequency designs may choose to increase this.
"""
def __init__(self, i_domain, o_domain, sync_stages=2):
if not isinstance(sync_stages, int) or sync_stages < 1:
raise TypeError("sync_stages must be a positive integer, not '{!r}'".format(sync_stages))
self.i = Signal()
self.o = Signal()
self.i_domain = i_domain
self.o_domain = o_domain
self.sync_stages = sync_stages
def elaborate(self, platform):
m = Module()
itoggle = Signal()
otoggle = Signal()
ff_sync = m.submodules.ff_sync = \
FFSynchronizer(itoggle, otoggle, o_domain=self.o_domain, stages=self.sync_stages)
otoggle_prev = Signal()
m.d[self.i_domain] += itoggle.eq(itoggle ^ self.i)
m.d[self.o_domain] += otoggle_prev.eq(otoggle)
m.d.comb += self.o.eq(otoggle ^ otoggle_prev)
return m