1 from collections
import namedtuple
, OrderedDict
6 __all__
= ["PLL_LatticeECP5"]
9 class PLL_LatticeECP5(Elaboratable
):
11 class Output(namedtuple("Output", ["domain", "freq", "div", "cphase", "fphase"])):
12 """PLL output parameters."""
15 """PLL parameters for Lattice ECP5 FPGAs.
22 Input clock frequency, in Hz.
24 If `True`, the input clock domain does not use a reset signal. Defaults to `True`.
26 Primary output clock domain.
28 Primary output clock frequency, in Hz.
30 Internal feedback mode. Optional. Defaults to `False`.
37 Input clock frequency, in Hz.
39 If `True`, the input clock domain does not use a reset signal.
43 Primary output clock domain.
45 Primary output clock frequency, in Hz.
47 Internal feedback mode.
49 Feedback clock divisor.
50 op : :class:`PLL_LatticeECP5.Parameters.Output`
51 Primary output parameters.
52 os, os2, os3 : :class:`PLL_LatticeECP5.Parameters.Output` or None
53 Secondary output parameters, or `None` if absent.
55 def __init__(self
, *, i_domain
, i_freq
, o_domain
, o_freq
, i_reset_less
=True, fb_internal
=False):
56 if not isinstance(i_domain
, str):
57 raise TypeError("Input domain must be a string, not {!r}"
59 if not isinstance(i_freq
, (int, float)):
60 raise TypeError("Input frequency must be an integer or a float, not {!r}"
62 if not 8e6
<= i_freq
<= 400e6
:
63 raise ValueError("Input frequency must be between 8 and 400 MHz, not {} MHz"
64 .format(i_freq
/ 1e6
))
65 if not isinstance(o_domain
, str):
66 raise TypeError("Output domain must be a string, not {!r}"
68 if not isinstance(o_freq
, (int, float)):
69 raise TypeError("Output frequency must be an integer or a float, not {!r}"
71 if not 10e6
<= o_freq
<= 400e6
:
72 raise ValueError("Output frequency must be between 10 and 400 MHz, not {} MHz"
73 .format(o_freq
/ 1e6
))
75 self
.i_domain
= i_domain
76 self
.i_freq
= int(i_freq
)
77 self
.i_reset_less
= bool(i_reset_less
)
78 self
.o_domain
= o_domain
79 self
.o_freq
= int(o_freq
)
80 self
.fb_internal
= bool(fb_internal
)
88 self
._2nd
_outputs
= OrderedDict()
121 def add_secondary_output(self
, *, domain
, freq
, phase
=0.0):
122 """Add secondary PLL output.
129 Output clock frequency.
131 Output clock phase, in degrees. Optional. Defaults to 0.
134 raise ValueError("PLL parameters have already been computed. Other outputs cannot "
136 if not isinstance(domain
, str):
137 raise TypeError("Output domain must be a string, not {!r}"
139 if not isinstance(freq
, (int, float)):
140 raise TypeError("Output frequency must be an integer or a float, not {!r}"
142 if not 10e6
<= freq
<= 400e6
:
143 raise ValueError("Output frequency must be between 10 and 400 MHz, not {} MHz"
145 if not isinstance(phase
, (int, float)):
146 raise TypeError("Output phase must be an integer or a float, not {!r}"
148 if not 0 <= phase
<= 360:
149 raise ValueError("Output phase must be between 0 and 360 degrees, not {}"
151 if len(self
._2nd
_outputs
) == 3:
152 raise ValueError("This PLL can drive at most 3 secondary outputs")
153 if domain
in self
._2nd
_outputs
:
154 raise ValueError("Output domain '{}' has already been added".format(domain
))
156 self
._2nd
_outputs
[domain
] = freq
, phase
158 def _iter_variants(self
):
159 for i_div
in range(1, 128 + 1):
160 pfd_freq
= self
.i_freq
/ i_div
161 if not 3.125e6
<= pfd_freq
<= 400e6
:
163 for fb_div
in range(1, 80 + 1):
164 for op_div
in range(1, 128 + 1):
165 vco_freq
= pfd_freq
* fb_div
* op_div
166 if not 400e6
<= vco_freq
<= 800e6
:
168 op_freq
= vco_freq
/ op_div
169 if not 10e6
<= op_freq
<= 400e6
:
171 yield (i_div
, fb_div
, op_div
, pfd_freq
, op_freq
)
174 """Compute PLL parameters.
176 This method is idempotent. As a side-effect of its first call, the visible state of the
177 :class:`PLL_LatticeECP5.Parameters` instance becomes immutable (e.g. adding more PLL outputs
183 variants
= list(self
._iter
_variants
())
185 raise ValueError("Input ({} MHz) to primary output ({} MHz) constraint was not "
187 .format(self
.i_freq
/ 1e6
, self
.o_freq
/ 1e6
))
190 i_div
, fb_div
, op_div
, pfd_freq
, op_freq
= variant
191 vco_freq
= pfd_freq
* fb_div
* op_div
192 return abs(op_freq
- self
.o_freq
), abs(vco_freq
- 600e6
), abs(pfd_freq
- 200e6
)
194 i_div
, fb_div
, op_div
, pfd_freq
, op_freq
= min(variants
, key
=error
)
196 vco_freq
= pfd_freq
* fb_div
* op_div
197 op_shift
= (1 / op_freq
) * 0.5
200 self
._fb
_div
= fb_div
202 self
._op
= PLL_LatticeECP5
.Parameters
.Output(
203 domain
= self
.o_domain
,
206 cphase
= op_shift
* vco_freq
,
210 for i
, (os_domain
, (os_freq
, os_phase
)) in enumerate(self
._2nd
_outputs
.items()):
211 os_name
= "_os{}".format(i
+ 1 if i
> 0 else "")
212 os_shift
= (1 / os_freq
) * os_phase
/ 360.0
213 os_params
= PLL_LatticeECP5
.Parameters
.Output(
216 div
= vco_freq
// os_freq
,
217 cphase
= self
._op
.cphase
+ (os_shift
* vco_freq
),
220 setattr(self
, os_name
, os_params
)
224 """PLL for Lattice ECP5 FPGAs.
228 params : :class:`PLL_LatticeECP5.Parameters`
233 params : :class:`PLL_LatticeECP5.Parameters`
235 locked : Signal(), out
238 def __init__(self
, params
):
239 if not isinstance(params
, PLL_LatticeECP5
.Parameters
):
240 raise TypeError("PLL parameters must be an instance of PLL_LatticeECP5.Parameters, not {!r}"
245 self
.locked
= Signal()
247 def elaborate(self
, platform
):
249 "a_ICP_CURRENT" : 12,
250 "a_LPF_RESISTOR" : 8,
251 "a_MFG_ENABLE_FILTEROPAMP" : 1,
252 "a_MFG_GMCREF_SEL" : 2,
253 "p_INTFB_WAKE" : "DISABLED",
254 "p_STDBY_ENABLE" : "DISABLED",
255 "p_DPHASE_SOURCE" : "DISABLED",
256 "p_OUTDIVIDER_MUXA" : "DIVA",
257 "p_OUTDIVIDER_MUXB" : "DIVB",
258 "p_OUTDIVIDER_MUXC" : "DIVC",
259 "p_OUTDIVIDER_MUXD" : "DIVD",
261 "i_PHASESEL0" : Const(0),
262 "i_PHASESEL1" : Const(0),
263 "i_PHASEDIR" : Const(1),
264 "i_PHASESTEP" : Const(1),
265 "i_PHASELOADREG" : Const(1),
266 "i_PLLWAKESYNC" : Const(0),
267 "i_ENCLKOP" : Const(0),
269 "o_LOCK" : self
.locked
,
271 "a_FREQUENCY_PIN_CLKI" : int(self
.params
.i_freq
/ 1e6
),
272 "p_CLKI_DIV" : self
.params
.i_div
,
273 "i_CLKI" : ClockSignal(self
.params
.i_domain
),
275 "a_FREQUENCY_PIN_CLKOP" : int(self
.params
.op
.freq
/ 1e6
),
276 "p_CLKOP_ENABLE" : "ENABLED",
277 "p_CLKOP_DIV" : self
.params
.op
.div
,
278 "p_CLKOP_CPHASE" : self
.params
.op
.cphase
,
279 "p_CLKOP_FPHASE" : self
.params
.op
.fphase
,
280 "o_CLKOP" : ClockSignal(self
.params
.op
.domain
),
285 if self
.params
.os
is not None:
287 "a_FREQUENCY_PIN_CLKOS" : int(self
.params
.os
.freq
/ 1e6
),
288 "p_CLKOS_ENABLE" : "ENABLED",
289 "p_CLKOS_DIV" : self
.params
.os
.div
,
290 "p_CLKOS_CPHASE" : self
.params
.os
.cphase
,
291 "p_CLKOS_FPHASE" : self
.params
.os
.fphase
,
292 "o_CLKOS" : ClockSignal(self
.params
.os
.domain
),
296 "p_CLKOS_ENABLE" : "DISABLED",
299 if self
.params
.os2
is not None:
301 "a_FREQUENCY_PIN_CLKOS2" : int(self
.params
.os2
.freq
/ 1e6
),
302 "p_CLKOS2_ENABLE" : "ENABLED",
303 "p_CLKOS2_DIV" : self
.params
.os2
.div
,
304 "p_CLKOS2_CPHASE" : self
.params
.os2
.cphase
,
305 "p_CLKOS2_FPHASE" : self
.params
.os2
.fphase
,
306 "o_CLKOS2" : ClockSignal(self
.params
.os2
.domain
),
310 "p_CLKOS2_ENABLE" : "DISABLED",
313 if self
.params
.os3
is not None:
315 "a_FREQUENCY_PIN_CLKOS3" : int(self
.params
.os3
.freq
/ 1e6
),
316 "p_CLKOS3_ENABLE" : "ENABLED",
317 "p_CLKOS3_DIV" : self
.params
.os3
.div
,
318 "p_CLKOS3_CPHASE" : self
.params
.os3
.cphase
,
319 "p_CLKOS3_FPHASE" : self
.params
.os3
.fphase
,
320 "o_CLKOS3" : ClockSignal(self
.params
.os3
.domain
),
324 "p_CLKOS3_ENABLE" : "DISABLED",
329 if not self
.params
.i_reset_less
:
331 "p_PLLRST_ENA" : "ENABLED",
332 "i_RST" : ResetSignal(self
.params
.i_domain
),
336 "p_PLLRST_ENA" : "DISABLED",
343 "p_CLKFB_DIV" : int(self
.params
.fb_div
),
346 if self
.params
.fb_internal
:
349 "p_FEEDBK_PATH" : "INT_OP",
350 "i_CLKFB" : clkintfb
,
351 "o_CLKINTFB" : clkintfb
,
355 "p_FEEDBK_PATH" : "CLKOP",
356 "i_CLKFB" : ClockSignal(self
.params
.op
.domain
),
357 "o_CLKINTFB" : Signal(),
360 return Instance("EHXPLLL", **pll_kwargs
)