Skip to content

Commit

Permalink
lib.cdc: MultiReg→FFSynchronizer.
Browse files Browse the repository at this point in the history
Fixes #229.
  • Loading branch information
whitequark committed Sep 23, 2019
1 parent b227352 commit 8deb13c
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 57 deletions.
4 changes: 2 additions & 2 deletions examples/basic/cdc.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from nmigen import *
from nmigen.lib.cdc import MultiReg
from nmigen.lib.cdc import FFSynchronizer
from nmigen.cli import main


i, o = Signal(name="i"), Signal(name="o")
m = Module()
m.submodules += MultiReg(i, o)
m.submodules += FFSynchronizer(i, o)

if __name__ == "__main__":
main(m, ports=[i, o])
20 changes: 13 additions & 7 deletions nmigen/compat/genlib/cdc.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import warnings

from ...tools import deprecated
from ...lib.cdc import MultiReg as NativeMultiReg
from ...lib.cdc import FFSynchronizer as NativeFFSynchronizer
from ...hdl.ast import *
from ..fhdl.module import CompatModule
from ..fhdl.structure import If
Expand All @@ -10,14 +10,20 @@
__all__ = ["MultiReg", "GrayCounter", "GrayDecoder"]


class MultiReg(NativeMultiReg):
class MultiReg(NativeFFSynchronizer):
def __init__(self, i, o, odomain="sync", n=2, reset=0):
old_opts = []
new_opts = []
if odomain != "sync":
warnings.warn("instead of `MultiReg(..., odomain={!r})`, "
"use `MultiReg(..., o_domain={!r})`"
.format(odomain, odomain),
DeprecationWarning, stacklevel=2)
super().__init__(i, o, o_domain=odomain, n=n, reset=reset)
old_opts.append(", odomain={!r}".format(odomain))
new_opts.append(", o_domain={!r}".format(odomain))
if n != 2:
old_opts.append(", n={!r}".format(n))
new_opts.append(", stages={!r}".format(n))
warnings.warn("instead of `MultiReg(...{})`, use `FFSynchronizer(...{})`"
.format("".join(old_opts), "".join(new_opts)),
DeprecationWarning, stacklevel=2)
super().__init__(i, o, o_domain=odomain, stages=n, reset=reset)
self.odomain = odomain


Expand Down
76 changes: 42 additions & 34 deletions nmigen/lib/cdc.py
Original file line number Diff line number Diff line change
@@ -1,93 +1,101 @@
from ..tools import deprecated
from .. import *


__all__ = ["MultiReg", "ResetSynchronizer"]
__all__ = ["FFSynchronizer", "ResetSynchronizer"]
# TODO(nmigen-0.2): remove this
__all__ += ["MultiReg"]


class MultiReg(Elaboratable):
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(), in
Signal to be resynchronised
o : Signal(), out
Signal connected to synchroniser output
i : Signal, in
Signal to be resynchronised.
o : Signal, out
Signal connected to synchroniser output.
o_domain : str
Name of output clock domain
n : int
Number of flops between input and output.
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 MultiReg is
still set to this value during initialization.
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 MultiReg is unaffected by ``o_domain`` reset.
If True (the default), this :class:`FFSynchronizer` is unaffected by ``o_domain`` reset.
See "Note on Reset" below.
Platform override
-----------------
Define the ``get_multi_reg`` platform method to override the implementation of MultiReg,
Define the ``get_ff_sync`` platform method to override the implementation of :class:`FFSynchronizer`,
e.g. to instantiate library cells directly.
Note on Reset
-------------
MultiReg is non-resettable by default. Usually this is the safest option; on FPGAs
the MultiReg will still be initialized to its ``reset`` value when the FPGA loads its
configuration.
: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 MultiReg must be valid immediately after reset,
consider setting ``reset_less`` to False if any of the following is true:
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 MultiReg must maintain its reset value until
``o_domain`` reset specifically is deasserted.
- Your design features a sequenced reset, and the :class:`FFSynchronizer` must maintain
its reset value until ``o_domain`` reset specifically is deasserted.
MultiReg 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", n=2, reset=0, reset_less=True):
def __init__(self, i, o, *, o_domain="sync", stages=2, reset=0, reset_less=True):
self.i = i
self.o = o

self._o_domain = o_domain
self._regs = [Signal(self.i.shape(), name="cdc{}".format(i), reset=reset,
reset_less=reset_less)
for i in range(n)]
self._stages = [Signal(self.i.shape(), name="stage{}".format(index),
reset=reset, reset_less=reset_less)
for index in range(stages)]

def elaborate(self, platform):
if hasattr(platform, "get_multi_reg"):
return platform.get_multi_reg(self)
if hasattr(platform, "get_ff_sync"):
return platform.get_ff_sync(self)

m = Module()
for i, o in zip((self.i, *self._regs), self._regs):
for i, o in zip((self.i, *self._stages), self._stages):
m.d[self._o_domain] += o.eq(i)
m.d.comb += self.o.eq(self._regs[-1])
m.d.comb += self.o.eq(self._stages[-1])
return m


# TODO(nmigen-0.2): remove this
MultiReg = deprecated("instead of `MultiReg`, use `FFSynchronizer`")(FFSynchronizer)


class ResetSynchronizer(Elaboratable):
def __init__(self, arst, *, domain="sync", n=2):
def __init__(self, arst, *, domain="sync", stages=2):
self.arst = arst

self._domain = domain
self._regs = [Signal(1, name="arst{}".format(i), reset=1)
for i in range(n)]
self._stages = [Signal(1, name="stage{}".format(i), reset=1)
for i in range(stages)]

def elaborate(self, platform):
if hasattr(platform, "get_reset_sync"):
return platform.get_reset_sync(self)

m = Module()
m.domains += ClockDomain("reset_sync", async_reset=True, local=True)
for i, o in zip((0, *self._regs), self._regs):
for i, o in zip((0, *self._stages), self._stages):
m.d.reset_sync += o.eq(i)
m.d.comb += [
ClockSignal("reset_sync").eq(ClockSignal(self._domain)),
ResetSignal("reset_sync").eq(self.arst),
ResetSignal(self._domain).eq(self._regs[-1])
ResetSignal(self._domain).eq(self._stages[-1])
]
return m
6 changes: 3 additions & 3 deletions nmigen/lib/fifo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ..asserts import *
from ..tools import log2_int, deprecated
from .coding import GrayEncoder
from .cdc import MultiReg
from .cdc import FFSynchronizer


__all__ = ["FIFOInterface", "SyncFIFO", "SyncFIFOBuffered", "AsyncFIFO", "AsyncFIFOBuffered"]
Expand Down Expand Up @@ -399,7 +399,7 @@ def elaborate(self, platform):
produce_enc = m.submodules.produce_enc = \
GrayEncoder(self._ctr_bits)
produce_cdc = m.submodules.produce_cdc = \
MultiReg(produce_w_gry, produce_r_gry, o_domain=self._r_domain)
FFSynchronizer(produce_w_gry, produce_r_gry, o_domain=self._r_domain)
m.d.comb += produce_enc.i.eq(produce_w_nxt),
m.d[self._w_domain] += produce_w_gry.eq(produce_enc.o)

Expand All @@ -408,7 +408,7 @@ def elaborate(self, platform):
consume_enc = m.submodules.consume_enc = \
GrayEncoder(self._ctr_bits)
consume_cdc = m.submodules.consume_cdc = \
MultiReg(consume_r_gry, consume_w_gry, o_domain=self._w_domain)
FFSynchronizer(consume_r_gry, consume_w_gry, o_domain=self._w_domain)
m.d.comb += consume_enc.i.eq(consume_r_nxt)
m.d[self._r_domain] += consume_r_gry.eq(consume_enc.o)

Expand Down
6 changes: 3 additions & 3 deletions nmigen/test/test_lib_cdc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
from ..lib.cdc import *


class MultiRegTestCase(FHDLTestCase):
class FFSynchronizerTestCase(FHDLTestCase):
def test_basic(self):
i = Signal()
o = Signal()
frag = MultiReg(i, o)
frag = FFSynchronizer(i, o)
with Simulator(frag) as sim:
sim.add_clock(1e-6)
def process():
Expand All @@ -26,7 +26,7 @@ def process():
def test_reset_value(self):
i = Signal(reset=1)
o = Signal()
frag = MultiReg(i, o, reset=1)
frag = FFSynchronizer(i, o, reset=1)
with Simulator(frag) as sim:
sim.add_clock(1e-6)
def process():
Expand Down
8 changes: 4 additions & 4 deletions nmigen/vendor/xilinx_7series.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,10 +361,10 @@ def get_diff_input_output(self, pin, p_port, n_port, attrs, invert):
)
return m

def get_multi_reg(self, multireg):
def get_ff_sync(self, ff_sync):
m = Module()
for i, o in zip((multireg.i, *multireg._regs), multireg._regs):
for i, o in zip((ff_sync.i, *ff_sync._stages), ff_sync._stages):
o.attrs["ASYNC_REG"] = "TRUE"
m.d[multireg._o_domain] += o.eq(i)
m.d.comb += multireg.o.eq(multireg._regs[-1])
m.d[ff_sync._o_domain] += o.eq(i)
m.d.comb += ff_sync.o.eq(ff_sync._stages[-1])
return m
8 changes: 4 additions & 4 deletions nmigen/vendor/xilinx_spartan_3_6.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,12 +411,12 @@ def get_diff_input_output(self, pin, p_port, n_port, attrs, invert):
)
return m

def get_multi_reg(self, multireg):
def get_ff_sync(self, ff_sync):
m = Module()
for i, o in zip((multireg.i, *multireg._regs), multireg._regs):
for i, o in zip((ff_sync.i, *ff_sync._stages), ff_sync._stages):
o.attrs["ASYNC_REG"] = "TRUE"
m.d[multireg._o_domain] += o.eq(i)
m.d.comb += multireg.o.eq(multireg._regs[-1])
m.d[ff_sync._o_domain] += o.eq(i)
m.d.comb += ff_sync.o.eq(multireg._stages[-1])
return m


Expand Down

0 comments on commit 8deb13c

Please sign in to comment.