lib.cdc: MultiReg→FFSynchronizer.

Fixes #229.
This commit is contained in:
whitequark 2019-09-23 14:17:44 +00:00
parent b227352258
commit 8deb13cea3
7 changed files with 71 additions and 57 deletions

View file

@ -1,11 +1,11 @@
from nmigen import * from nmigen import *
from nmigen.lib.cdc import MultiReg from nmigen.lib.cdc import FFSynchronizer
from nmigen.cli import main from nmigen.cli import main
i, o = Signal(name="i"), Signal(name="o") i, o = Signal(name="i"), Signal(name="o")
m = Module() m = Module()
m.submodules += MultiReg(i, o) m.submodules += FFSynchronizer(i, o)
if __name__ == "__main__": if __name__ == "__main__":
main(m, ports=[i, o]) main(m, ports=[i, o])

View file

@ -1,7 +1,7 @@
import warnings import warnings
from ...tools import deprecated from ...tools import deprecated
from ...lib.cdc import MultiReg as NativeMultiReg from ...lib.cdc import FFSynchronizer as NativeFFSynchronizer
from ...hdl.ast import * from ...hdl.ast import *
from ..fhdl.module import CompatModule from ..fhdl.module import CompatModule
from ..fhdl.structure import If from ..fhdl.structure import If
@ -10,14 +10,20 @@ from ..fhdl.structure import If
__all__ = ["MultiReg", "GrayCounter", "GrayDecoder"] __all__ = ["MultiReg", "GrayCounter", "GrayDecoder"]
class MultiReg(NativeMultiReg): class MultiReg(NativeFFSynchronizer):
def __init__(self, i, o, odomain="sync", n=2, reset=0): def __init__(self, i, o, odomain="sync", n=2, reset=0):
old_opts = []
new_opts = []
if odomain != "sync": if odomain != "sync":
warnings.warn("instead of `MultiReg(..., odomain={!r})`, " old_opts.append(", odomain={!r}".format(odomain))
"use `MultiReg(..., o_domain={!r})`" new_opts.append(", o_domain={!r}".format(odomain))
.format(odomain, 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) DeprecationWarning, stacklevel=2)
super().__init__(i, o, o_domain=odomain, n=n, reset=reset) super().__init__(i, o, o_domain=odomain, stages=n, reset=reset)
self.odomain = odomain self.odomain = odomain

View file

@ -1,10 +1,13 @@
from ..tools import deprecated
from .. import * 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. """Resynchronise a signal to a different clock domain.
Consists of a chain of flip-flops. Eliminates metastabilities at the output, but provides Consists of a chain of flip-flops. Eliminates metastabilities at the output, but provides
@ -12,70 +15,75 @@ class MultiReg(Elaboratable):
Parameters Parameters
---------- ----------
i : Signal(), in i : Signal, in
Signal to be resynchronised Signal to be resynchronised.
o : Signal(), out o : Signal, out
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.
n : int stages : int
Number of flops between input and output. 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, the MultiReg is Reset value of the flip-flops. On FPGAs, even if ``reset_less`` is True,
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 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. See "Note on Reset" below.
Platform override 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. e.g. to instantiate library cells directly.
Note on Reset Note on Reset
------------- -------------
MultiReg is non-resettable by default. Usually this is the safest option; on FPGAs :class:`FFSynchronizer` is non-resettable by default. Usually this is the safest option;
the MultiReg will still be initialized to its ``reset`` value when the FPGA loads its on FPGAs the :class:`FFSynchronizer` will still be initialized to its ``reset`` value when
configuration. the FPGA loads its configuration.
However, in designs where the value of the MultiReg must be valid immediately after reset, However, in designs where the value of the :class:`FFSynchronizer` must be valid immediately
consider setting ``reset_less`` to False if any of the following is true: 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; - 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 - Your design features warm (non-power-on) resets of ``o_domain``, so the one-time
initialization at power on is insufficient; initialization at power on is insufficient;
- Your design features a sequenced reset, and the MultiReg must maintain its reset value until - Your design features a sequenced reset, and the :class:`FFSynchronizer` must maintain
``o_domain`` reset specifically is deasserted. 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.i = i
self.o = o self.o = o
self._o_domain = o_domain self._o_domain = o_domain
self._regs = [Signal(self.i.shape(), name="cdc{}".format(i), reset=reset, self._stages = [Signal(self.i.shape(), name="stage{}".format(index),
reset_less=reset_less) reset=reset, reset_less=reset_less)
for i in range(n)] for index in range(stages)]
def elaborate(self, platform): def elaborate(self, platform):
if hasattr(platform, "get_multi_reg"): if hasattr(platform, "get_ff_sync"):
return platform.get_multi_reg(self) return platform.get_ff_sync(self)
m = Module() 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[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 return m
# TODO(nmigen-0.2): remove this
MultiReg = deprecated("instead of `MultiReg`, use `FFSynchronizer`")(FFSynchronizer)
class ResetSynchronizer(Elaboratable): class ResetSynchronizer(Elaboratable):
def __init__(self, arst, *, domain="sync", n=2): def __init__(self, arst, *, domain="sync", stages=2):
self.arst = arst self.arst = arst
self._domain = domain self._domain = domain
self._regs = [Signal(1, name="arst{}".format(i), reset=1) self._stages = [Signal(1, name="stage{}".format(i), reset=1)
for i in range(n)] for i in range(stages)]
def elaborate(self, platform): def elaborate(self, platform):
if hasattr(platform, "get_reset_sync"): if hasattr(platform, "get_reset_sync"):
@ -83,11 +91,11 @@ class ResetSynchronizer(Elaboratable):
m = Module() m = Module()
m.domains += ClockDomain("reset_sync", async_reset=True, local=True) 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.reset_sync += o.eq(i)
m.d.comb += [ m.d.comb += [
ClockSignal("reset_sync").eq(ClockSignal(self._domain)), ClockSignal("reset_sync").eq(ClockSignal(self._domain)),
ResetSignal("reset_sync").eq(self.arst), ResetSignal("reset_sync").eq(self.arst),
ResetSignal(self._domain).eq(self._regs[-1]) ResetSignal(self._domain).eq(self._stages[-1])
] ]
return m return m

View file

@ -4,7 +4,7 @@ from .. import *
from ..asserts import * from ..asserts import *
from ..tools import log2_int, deprecated from ..tools import log2_int, deprecated
from .coding import GrayEncoder from .coding import GrayEncoder
from .cdc import MultiReg from .cdc import FFSynchronizer
__all__ = ["FIFOInterface", "SyncFIFO", "SyncFIFOBuffered", "AsyncFIFO", "AsyncFIFOBuffered"] __all__ = ["FIFOInterface", "SyncFIFO", "SyncFIFOBuffered", "AsyncFIFO", "AsyncFIFOBuffered"]
@ -399,7 +399,7 @@ class AsyncFIFO(Elaboratable, FIFOInterface):
produce_enc = m.submodules.produce_enc = \ produce_enc = m.submodules.produce_enc = \
GrayEncoder(self._ctr_bits) GrayEncoder(self._ctr_bits)
produce_cdc = m.submodules.produce_cdc = \ 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.comb += produce_enc.i.eq(produce_w_nxt),
m.d[self._w_domain] += produce_w_gry.eq(produce_enc.o) m.d[self._w_domain] += produce_w_gry.eq(produce_enc.o)
@ -408,7 +408,7 @@ class AsyncFIFO(Elaboratable, FIFOInterface):
consume_enc = m.submodules.consume_enc = \ consume_enc = m.submodules.consume_enc = \
GrayEncoder(self._ctr_bits) GrayEncoder(self._ctr_bits)
consume_cdc = m.submodules.consume_cdc = \ 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.comb += consume_enc.i.eq(consume_r_nxt)
m.d[self._r_domain] += consume_r_gry.eq(consume_enc.o) m.d[self._r_domain] += consume_r_gry.eq(consume_enc.o)

View file

@ -4,11 +4,11 @@ from ..back.pysim import *
from ..lib.cdc import * from ..lib.cdc import *
class MultiRegTestCase(FHDLTestCase): class FFSynchronizerTestCase(FHDLTestCase):
def test_basic(self): def test_basic(self):
i = Signal() i = Signal()
o = Signal() o = Signal()
frag = MultiReg(i, o) frag = FFSynchronizer(i, o)
with Simulator(frag) as sim: with Simulator(frag) as sim:
sim.add_clock(1e-6) sim.add_clock(1e-6)
def process(): def process():
@ -26,7 +26,7 @@ class MultiRegTestCase(FHDLTestCase):
def test_reset_value(self): def test_reset_value(self):
i = Signal(reset=1) i = Signal(reset=1)
o = Signal() o = Signal()
frag = MultiReg(i, o, reset=1) frag = FFSynchronizer(i, o, reset=1)
with Simulator(frag) as sim: with Simulator(frag) as sim:
sim.add_clock(1e-6) sim.add_clock(1e-6)
def process(): def process():

View file

@ -361,10 +361,10 @@ class Xilinx7SeriesPlatform(TemplatedPlatform):
) )
return m return m
def get_multi_reg(self, multireg): def get_ff_sync(self, ff_sync):
m = Module() 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" o.attrs["ASYNC_REG"] = "TRUE"
m.d[multireg._o_domain] += o.eq(i) m.d[ff_sync._o_domain] += o.eq(i)
m.d.comb += multireg.o.eq(multireg._regs[-1]) m.d.comb += ff_sync.o.eq(ff_sync._stages[-1])
return m return m

View file

@ -411,12 +411,12 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform):
) )
return m return m
def get_multi_reg(self, multireg): def get_ff_sync(self, ff_sync):
m = Module() 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" o.attrs["ASYNC_REG"] = "TRUE"
m.d[multireg._o_domain] += o.eq(i) m.d[ff_sync._o_domain] += o.eq(i)
m.d.comb += multireg.o.eq(multireg._regs[-1]) m.d.comb += ff_sync.o.eq(multireg._stages[-1])
return m return m