from .. import *
from ..asserts import *
from .._utils import log2_int, deprecated
-from .coding import GrayEncoder
-from .cdc import FFSynchronizer
+from .coding import GrayEncoder, GrayDecoder
+from .cdc import FFSynchronizer, AsyncFFSynchronizer
__all__ = ["FIFOInterface", "SyncFIFO", "SyncFIFOBuffered", "AsyncFIFO", "AsyncFIFOBuffered"]
Read and write interfaces are accessed from different clock domains, which can be set when
constructing the FIFO.
+ :class:`AsyncFIFO` can be reset from the write clock domain. When the write domain reset is
+ asserted, the FIFO becomes empty. When the read domain is reset, data remains in the FIFO - the
+ read domain logic should correctly handle this case.
+
:class:`AsyncFIFO` only supports power of 2 depths. Unless ``exact_depth`` is specified,
the ``depth`` parameter is rounded up to the next power of 2.
""".strip(),
m.d.comb += produce_w_nxt.eq(produce_w_bin + do_write)
m.d[self._w_domain] += produce_w_bin.eq(produce_w_nxt)
- consume_r_bin = Signal(self._ctr_bits)
+ # Note: Both read-domain counters must be reset_less (see comments below)
+ consume_r_bin = Signal(self._ctr_bits, reset_less=True)
consume_r_nxt = Signal(self._ctr_bits)
m.d.comb += consume_r_nxt.eq(consume_r_bin + do_read)
m.d[self._r_domain] += consume_r_bin.eq(consume_r_nxt)
m.d.comb += produce_enc.i.eq(produce_w_nxt),
m.d[self._w_domain] += produce_w_gry.eq(produce_enc.o)
- consume_r_gry = Signal(self._ctr_bits)
+ consume_r_gry = Signal(self._ctr_bits, reset_less=True)
consume_w_gry = Signal(self._ctr_bits)
consume_enc = m.submodules.consume_enc = \
GrayEncoder(self._ctr_bits)
self.r_rdy.eq(~r_empty),
]
+ # Reset handling to maintain FIFO and CDC invariants in the presence of a write-domain
+ # reset.
+ # There is a CDC hazard associated with resetting an async FIFO - Gray code counters which
+ # are reset to 0 violate their Gray code invariant. One way to handle this is to ensure
+ # that both sides of the FIFO are asynchronously reset by the same signal. We adopt a
+ # slight variation on this approach - reset control rests entirely with the write domain.
+ # The write domain's reset signal is used to asynchronously reset the read domain's
+ # counters and force the FIFO to be empty when the write domain's reset is asserted.
+ # This requires the two read domain counters to be marked as "reset_less", as they are
+ # reset through another mechanism. See https://github.com/nmigen/nmigen/issues/181 for the
+ # full discussion.
+ w_rst = ResetSignal(domain=self._w_domain, allow_reset_less=True)
+ r_rst = Signal()
+
+ # Async-set-sync-release synchronizer avoids CDC hazards
+ rst_cdc = m.submodules.rst_cdc = \
+ AsyncFFSynchronizer(w_rst, r_rst, domain=self._r_domain)
+
+ # Decode Gray code counter synchronized from write domain to overwrite binary
+ # counter in read domain.
+ rst_dec = m.submodules.rst_dec = \
+ GrayDecoder(self._ctr_bits)
+ m.d.comb += rst_dec.i.eq(produce_r_gry),
+ with m.If(r_rst):
+ m.d.comb += r_empty.eq(1)
+ m.d[self._r_domain] += consume_r_gry.eq(produce_r_gry)
+ m.d[self._r_domain] += consume_r_bin.eq(rst_dec.o)
+
if platform == "formal":
with m.If(Initial()):
m.d.comb += Assume(produce_w_gry == (produce_w_bin ^ produce_w_bin[1:]))