cores.pll: add PLL generators for Lattice ECP5 and Xilinx 7 Series.
[lambdasoc.git] / lambdasoc / cores / pll / lattice_ecp5.py
1 from collections import namedtuple, OrderedDict
2
3 from nmigen import *
4
5
6 __all__ = ["PLL_LatticeECP5"]
7
8
9 class PLL_LatticeECP5(Elaboratable):
10 class Parameters:
11 class Output(namedtuple("Output", ["domain", "freq", "div", "cphase", "fphase"])):
12 """PLL output parameters."""
13 __slots__ = ()
14
15 """PLL parameters for Lattice ECP5 FPGAs.
16
17 Parameters
18 ----------
19 i_domain : str
20 Input clock domain.
21 i_freq : int or float
22 Input clock frequency, in Hz.
23 i_reset_less : bool
24 If `True`, the input clock domain does not use a reset signal. Defaults to `True`.
25 o_domain : str
26 Primary output clock domain.
27 o_freq : int or float
28 Primary output clock frequency, in Hz.
29 internal_fb : bool
30 Internal feedback mode. Optional. Defaults to `False`.
31
32 Attributes
33 ----------
34 i_domain : str
35 Input clock domain.
36 i_freq : int
37 Input clock frequency, in Hz.
38 i_reset_less : bool
39 If `True`, the input clock domain does not use a reset signal.
40 i_div : int
41 Input clock divisor.
42 o_domain : str
43 Primary output clock domain.
44 o_freq : int
45 Primary output clock frequency, in Hz.
46 fb_internal : bool
47 Internal feedback mode.
48 fb_div : int
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.
54 """
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}"
58 .format(i_domain))
59 if not isinstance(i_freq, (int, float)):
60 raise TypeError("Input frequency must be an integer or a float, not {!r}"
61 .format(i_freq))
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}"
67 .format(o_domain))
68 if not isinstance(o_freq, (int, float)):
69 raise TypeError("Output frequency must be an integer or a float, not {!r}"
70 .format(o_freq))
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))
74
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)
81
82 self._i_div = None
83 self._fb_div = None
84 self._op = None
85 self._os = None
86 self._os2 = None
87 self._os3 = None
88 self._2nd_outputs = OrderedDict()
89 self._frozen = False
90
91 @property
92 def i_div(self):
93 self.compute()
94 return self._i_div
95
96 @property
97 def fb_div(self):
98 self.compute()
99 return self._fb_div
100
101 @property
102 def op(self):
103 self.compute()
104 return self._op
105
106 @property
107 def os(self):
108 self.compute()
109 return self._os
110
111 @property
112 def os2(self):
113 self.compute()
114 return self._os2
115
116 @property
117 def os3(self):
118 self.compute()
119 return self._os3
120
121 def add_secondary_output(self, *, domain, freq, phase=0.0):
122 """Add secondary PLL output.
123
124 Arguments
125 ---------
126 domain : str
127 Output clock domain.
128 freq : int
129 Output clock frequency.
130 phase : int or float
131 Output clock phase, in degrees. Optional. Defaults to 0.
132 """
133 if self._frozen:
134 raise ValueError("PLL parameters have already been computed. Other outputs cannot "
135 "be added")
136 if not isinstance(domain, str):
137 raise TypeError("Output domain must be a string, not {!r}"
138 .format(domain))
139 if not isinstance(freq, (int, float)):
140 raise TypeError("Output frequency must be an integer or a float, not {!r}"
141 .format(freq))
142 if not 10e6 <= freq <= 400e6:
143 raise ValueError("Output frequency must be between 10 and 400 MHz, not {} MHz"
144 .format(freq / 1e6))
145 if not isinstance(phase, (int, float)):
146 raise TypeError("Output phase must be an integer or a float, not {!r}"
147 .format(phase))
148 if not 0 <= phase <= 360:
149 raise ValueError("Output phase must be between 0 and 360 degrees, not {}"
150 .format(phase))
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))
155
156 self._2nd_outputs[domain] = freq, phase
157
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:
162 continue
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:
167 continue
168 op_freq = vco_freq / op_div
169 if not 10e6 <= op_freq <= 400e6:
170 continue
171 yield (i_div, fb_div, op_div, pfd_freq, op_freq)
172
173 def compute(self):
174 """Compute PLL parameters.
175
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
178 will fail).
179 """
180 if self._frozen:
181 return
182
183 variants = list(self._iter_variants())
184 if not variants:
185 raise ValueError("Input ({} MHz) to primary output ({} MHz) constraint was not "
186 "satisfied"
187 .format(self.i_freq / 1e6, self.o_freq / 1e6))
188
189 def error(variant):
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)
193
194 i_div, fb_div, op_div, pfd_freq, op_freq = min(variants, key=error)
195
196 vco_freq = pfd_freq * fb_div * op_div
197 op_shift = (1 / op_freq) * 0.5
198
199 self._i_div = i_div
200 self._fb_div = fb_div
201
202 self._op = PLL_LatticeECP5.Parameters.Output(
203 domain = self.o_domain,
204 freq = op_freq,
205 div = op_div,
206 cphase = op_shift * vco_freq,
207 fphase = 0,
208 )
209
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(
214 domain = os_domain,
215 freq = os_freq,
216 div = vco_freq // os_freq,
217 cphase = self._op.cphase + (os_shift * vco_freq),
218 fphase = 0,
219 )
220 setattr(self, os_name, os_params)
221
222 self._frozen = True
223
224 """PLL for Lattice ECP5 FPGAs.
225
226 Parameters
227 ----------
228 params : :class:`PLL_LatticeECP5.Parameters`
229 PLL parameters.
230
231 Attributes
232 ----------
233 params : :class:`PLL_LatticeECP5.Parameters`
234 PLL parameters.
235 locked : Signal(), out
236 PLL lock status.
237 """
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}"
241 .format(params))
242
243 params.compute()
244 self.params = params
245 self.locked = Signal()
246
247 def elaborate(self, platform):
248 pll_kwargs = {
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",
260
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),
268
269 "o_LOCK" : self.locked,
270
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),
274
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),
281 }
282
283 # Secondary outputs
284
285 if self.params.os is not None:
286 pll_kwargs.update({
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),
293 })
294 else:
295 pll_kwargs.update({
296 "p_CLKOS_ENABLE" : "DISABLED",
297 })
298
299 if self.params.os2 is not None:
300 pll_kwargs.update({
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),
307 })
308 else:
309 pll_kwargs.update({
310 "p_CLKOS2_ENABLE" : "DISABLED",
311 })
312
313 if self.params.os3 is not None:
314 pll_kwargs.update({
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),
321 })
322 else:
323 pll_kwargs.update({
324 "p_CLKOS3_ENABLE" : "DISABLED",
325 })
326
327 # Reset
328
329 if not self.params.i_reset_less:
330 pll_kwargs.update({
331 "p_PLLRST_ENA" : "ENABLED",
332 "i_RST" : ResetSignal(self.params.i_domain),
333 })
334 else:
335 pll_kwargs.update({
336 "p_PLLRST_ENA" : "DISABLED",
337 "i_RST" : Const(0),
338 })
339
340 # Feedback
341
342 pll_kwargs.update({
343 "p_CLKFB_DIV" : int(self.params.fb_div),
344 })
345
346 if self.params.fb_internal:
347 clkintfb = Signal()
348 pll_kwargs.update({
349 "p_FEEDBK_PATH" : "INT_OP",
350 "i_CLKFB" : clkintfb,
351 "o_CLKINTFB" : clkintfb,
352 })
353 else:
354 pll_kwargs.update({
355 "p_FEEDBK_PATH" : "CLKOP",
356 "i_CLKFB" : ClockSignal(self.params.op.domain),
357 "o_CLKINTFB" : Signal(),
358 })
359
360 return Instance("EHXPLLL", **pll_kwargs)