_tools: extract most utility methods to a private package.
[nmigen.git] / nmigen / lib / cdc.py
1 from .._tools import deprecated
2 from .. import *
3
4
5 __all__ = ["FFSynchronizer", "ResetSynchronizer"]
6 # TODO(nmigen-0.2): remove this
7 __all__ += ["MultiReg"]
8
9
10 def _check_stages(stages):
11 if not isinstance(stages, int) or stages < 1:
12 raise TypeError("Synchronization stage count must be a positive integer, not {!r}"
13 .format(stages))
14 if stages < 2:
15 raise ValueError("Synchronization stage count may not safely be less than 2")
16
17
18 class FFSynchronizer(Elaboratable):
19 """Resynchronise a signal to a different clock domain.
20
21 Consists of a chain of flip-flops. Eliminates metastabilities at the output, but provides
22 no other guarantee as to the safe domain-crossing of a signal.
23
24 Parameters
25 ----------
26 i : Signal(n), in
27 Signal to be resynchronised.
28 o : Signal(n), out
29 Signal connected to synchroniser output.
30 o_domain : str
31 Name of output clock domain.
32 reset : int
33 Reset value of the flip-flops. On FPGAs, even if ``reset_less`` is True,
34 the :class:`FFSynchronizer` is still set to this value during initialization.
35 reset_less : bool
36 If ``True`` (the default), this :class:`FFSynchronizer` is unaffected by ``o_domain``
37 reset. See "Note on Reset" below.
38 stages : int
39 Number of synchronization stages between input and output. The lowest safe number is 2,
40 with higher numbers reducing MTBF further, at the cost of increased latency.
41 max_input_delay : None or float
42 Maximum delay from the input signal's clock to the first synchronization stage, in seconds.
43 If specified and the platform does not support it, elaboration will fail.
44
45 Platform override
46 -----------------
47 Define the ``get_ff_sync`` platform method to override the implementation of
48 :class:`FFSynchronizer`, e.g. to instantiate library cells directly.
49
50 Note on Reset
51 -------------
52 :class:`FFSynchronizer` is non-resettable by default. Usually this is the safest option;
53 on FPGAs the :class:`FFSynchronizer` will still be initialized to its ``reset`` value when
54 the FPGA loads its configuration.
55
56 However, in designs where the value of the :class:`FFSynchronizer` must be valid immediately
57 after reset, consider setting ``reset_less`` to False if any of the following is true:
58
59 - You are targeting an ASIC, or an FPGA that does not allow arbitrary initial flip-flop states;
60 - Your design features warm (non-power-on) resets of ``o_domain``, so the one-time
61 initialization at power on is insufficient;
62 - Your design features a sequenced reset, and the :class:`FFSynchronizer` must maintain
63 its reset value until ``o_domain`` reset specifically is deasserted.
64
65 :class:`FFSynchronizer` is reset by the ``o_domain`` reset only.
66 """
67 def __init__(self, i, o, *, o_domain="sync", reset=0, reset_less=True, stages=2,
68 max_input_delay=None):
69 _check_stages(stages)
70
71 self.i = i
72 self.o = o
73
74 self._reset = reset
75 self._reset_less = reset_less
76 self._o_domain = o_domain
77 self._stages = stages
78
79 self._max_input_delay = max_input_delay
80
81 def elaborate(self, platform):
82 if hasattr(platform, "get_ff_sync"):
83 return platform.get_ff_sync(self)
84
85 if self._max_input_delay is not None:
86 raise NotImplementedError("Platform '{}' does not support constraining input delay "
87 "for FFSynchronizer"
88 .format(type(platform).__name__))
89
90 m = Module()
91 flops = [Signal(self.i.shape(), name="stage{}".format(index),
92 reset=self._reset, reset_less=self._reset_less)
93 for index in range(self._stages)]
94 for i, o in zip((self.i, *flops), flops):
95 m.d[self._o_domain] += o.eq(i)
96 m.d.comb += self.o.eq(flops[-1])
97 return m
98
99
100 # TODO(nmigen-0.2): remove this
101 MultiReg = deprecated("instead of `MultiReg`, use `FFSynchronizer`")(FFSynchronizer)
102
103
104 class ResetSynchronizer(Elaboratable):
105 """Synchronize deassertion of a clock domain reset.
106
107 The reset of the clock domain driven by the :class:`ResetSynchronizer` is asserted
108 asynchronously and deasserted synchronously, eliminating metastability during deassertion.
109
110 The driven clock domain could use a reset that is asserted either synchronously or
111 asynchronously; a reset is always deasserted synchronously. A domain with an asynchronously
112 asserted reset is useful if the clock of the domain may be gated, yet the domain still
113 needs to be reset promptly; otherwise, synchronously asserted reset (the default) should
114 be used.
115
116 Parameters
117 ----------
118 arst : Signal(1), out
119 Asynchronous reset signal, to be synchronized.
120 domain : str
121 Name of clock domain to reset.
122 stages : int, >=2
123 Number of synchronization stages between input and output. The lowest safe number is 2,
124 with higher numbers reducing MTBF further, at the cost of increased deassertion latency.
125 max_input_delay : None or float
126 Maximum delay from the input signal's clock to the first synchronization stage, in seconds.
127 If specified and the platform does not support it, elaboration will fail.
128
129 Platform override
130 -----------------
131 Define the ``get_reset_sync`` platform method to override the implementation of
132 :class:`ResetSynchronizer`, e.g. to instantiate library cells directly.
133 """
134 def __init__(self, arst, *, domain="sync", stages=2, max_input_delay=None):
135 _check_stages(stages)
136
137 self.arst = arst
138
139 self._domain = domain
140 self._stages = stages
141
142 self._max_input_delay = None
143
144 def elaborate(self, platform):
145 if hasattr(platform, "get_reset_sync"):
146 return platform.get_reset_sync(self)
147
148 if self._max_input_delay is not None:
149 raise NotImplementedError("Platform '{}' does not support constraining input delay "
150 "for ResetSynchronizer"
151 .format(type(platform).__name__))
152
153 m = Module()
154 m.domains += ClockDomain("reset_sync", async_reset=True, local=True)
155 flops = [Signal(1, name="stage{}".format(index), reset=1)
156 for index in range(self._stages)]
157 for i, o in zip((0, *flops), flops):
158 m.d.reset_sync += o.eq(i)
159 m.d.comb += [
160 ClockSignal("reset_sync").eq(ClockSignal(self._domain)),
161 ResetSignal("reset_sync").eq(self.arst),
162 ResetSignal(self._domain).eq(flops[-1])
163 ]
164 return m