pyadi-jif 0.1.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. adijif/__init__.py +32 -0
  2. adijif/adijif.py +1 -0
  3. adijif/cli.py +21 -0
  4. adijif/clocks/__init__.py +10 -0
  5. adijif/clocks/ad9523.py +321 -0
  6. adijif/clocks/ad9523_1_bf.py +91 -0
  7. adijif/clocks/ad9528.py +444 -0
  8. adijif/clocks/ad9528_bf.py +70 -0
  9. adijif/clocks/ad9545.py +553 -0
  10. adijif/clocks/clock.py +153 -0
  11. adijif/clocks/hmc7044.py +558 -0
  12. adijif/clocks/hmc7044_bf.py +68 -0
  13. adijif/clocks/ltc6952.py +624 -0
  14. adijif/clocks/ltc6952_bf.py +67 -0
  15. adijif/clocks/ltc6953.py +509 -0
  16. adijif/common.py +70 -0
  17. adijif/converters/__init__.py +3 -0
  18. adijif/converters/ad9081.py +679 -0
  19. adijif/converters/ad9081_dp.py +206 -0
  20. adijif/converters/ad9081_util.py +124 -0
  21. adijif/converters/ad9084.py +588 -0
  22. adijif/converters/ad9084_dp.py +111 -0
  23. adijif/converters/ad9084_draw.py +203 -0
  24. adijif/converters/ad9084_util.py +365 -0
  25. adijif/converters/ad9144.py +316 -0
  26. adijif/converters/ad9144_bf.py +44 -0
  27. adijif/converters/ad9680.py +201 -0
  28. adijif/converters/ad9680_bf.py +43 -0
  29. adijif/converters/ad9680_draw.py +184 -0
  30. adijif/converters/adc.py +83 -0
  31. adijif/converters/adrv9009.py +426 -0
  32. adijif/converters/adrv9009_bf.py +43 -0
  33. adijif/converters/adrv9009_util.py +89 -0
  34. adijif/converters/converter.py +399 -0
  35. adijif/converters/dac.py +85 -0
  36. adijif/converters/resources/AD9084_JTX_JRX.xlsx +0 -0
  37. adijif/converters/resources/ad9081_JRx_204B.csv +180 -0
  38. adijif/converters/resources/ad9081_JRx_204C.csv +411 -0
  39. adijif/converters/resources/ad9081_JTx_204B.csv +1488 -0
  40. adijif/converters/resources/ad9081_JTx_204C.csv +1064 -0
  41. adijif/converters/resources/full_rx_mode_table_ad9081.csv +1904 -0
  42. adijif/converters/resources/full_tx_mode_table_ad9081.csv +994 -0
  43. adijif/d2/__init__.py +26 -0
  44. adijif/d2/d2lib.h +81 -0
  45. adijif/draw.py +498 -0
  46. adijif/fpgas/__init__.py +1 -0
  47. adijif/fpgas/fpga.py +64 -0
  48. adijif/fpgas/xilinx/__init__.py +1143 -0
  49. adijif/fpgas/xilinx/bf.py +101 -0
  50. adijif/fpgas/xilinx/pll.py +232 -0
  51. adijif/fpgas/xilinx/sevenseries.py +531 -0
  52. adijif/fpgas/xilinx/ultrascaleplus.py +485 -0
  53. adijif/fpgas/xilinx/xilinx_draw.py +516 -0
  54. adijif/gekko_trans.py +295 -0
  55. adijif/jesd.py +760 -0
  56. adijif/plls/__init__.py +3 -0
  57. adijif/plls/adf4030.py +259 -0
  58. adijif/plls/adf4371.py +419 -0
  59. adijif/plls/adf4382.py +581 -0
  60. adijif/plls/pll.py +103 -0
  61. adijif/solvers.py +54 -0
  62. adijif/sys/__init__.py +1 -0
  63. adijif/sys/s_plls.py +185 -0
  64. adijif/system.py +567 -0
  65. adijif/system_draw.py +65 -0
  66. adijif/types.py +151 -0
  67. adijif/utils.py +191 -0
  68. pyadi_jif-0.1.0.dist-info/METADATA +62 -0
  69. pyadi_jif-0.1.0.dist-info/RECORD +73 -0
  70. pyadi_jif-0.1.0.dist-info/WHEEL +6 -0
  71. pyadi_jif-0.1.0.dist-info/licenses/AUTHORS.rst +13 -0
  72. pyadi_jif-0.1.0.dist-info/licenses/LICENSE +277 -0
  73. pyadi_jif-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,553 @@
1
+ """AD9545 clock chip model."""
2
+
3
+ from typing import Dict, List
4
+
5
+ import docplex.cp.catalog as ctg
6
+ import docplex.cp.expression as exp
7
+ import docplex.cp.modeler as mod
8
+
9
+ from adijif.clocks.clock import clock
10
+ from adijif.solvers import CpoSolveResult
11
+
12
+
13
+ class ad9545(clock):
14
+ """AD9545 clock chip model.
15
+
16
+ Currently this model supports only the internal zero delay mode
17
+
18
+ PLL_in_rate_0 = input_ref_0 / r0
19
+ PLL_in_rate_1 = input_ref_1 / r1
20
+ PLL_in_rate_2 = input_ref_2 / r2
21
+ PLL_in_rate_3 = input_ref_3 / r3
22
+
23
+ PLL_in_rate_0 < PLL_in_max
24
+ PLL_in_rate_1 < PLL_in_max
25
+ PLL_in_rate_2 < PLL_in_max
26
+ PLL_in_rate_3 < PLL_in_max
27
+
28
+ PLL0_rate = n0_profile_0 * PLL_in_rate_0
29
+ PLL0_rate = n0_profile_1 * PLL_in_rate_1
30
+ PLL0_rate = n0_profile_2 * PLL_in_rate_2
31
+ PLL0_rate = n0_profile_3 * PLL_in_rate_3
32
+
33
+ out_rate_0 = PLL0_rate / q0
34
+ out_rate_1 = PLL0_rate / q1
35
+ ...
36
+ out_rate_5 = PLL0_rate / q5
37
+ """
38
+
39
+ # Limits
40
+ """ Internal limits """
41
+ PLL_out_min = [1200e6, 1600e6]
42
+ PLL_out_max = [1600e6, 2000e6]
43
+
44
+ PLL_in_min = 1
45
+ PLL_in_max = 200000
46
+
47
+ APLL_PFD_MIN = int(162e6)
48
+ APLL_PFD_MAX = int(350e6)
49
+
50
+ """ Output dividers limits
51
+
52
+ Value is programmed on 32 bits but
53
+ 2 GHz is the maximum PLL output that feeds
54
+ directly to the output dividers.
55
+ """
56
+ Q_max = 2000000000
57
+ Q_min = 1
58
+
59
+ """ Input dividers limits
60
+
61
+ Value is programmed on 32 bits but
62
+ 750 MHz is the maximum input frequency anyway.
63
+ """
64
+ R_max = 750000000
65
+ R_min = 1
66
+
67
+ """ PLL N dividers limits
68
+
69
+ Value is programmed on 32 bits.
70
+
71
+ There are constraints on the input and output frequency.
72
+ This places lower and upper bounds on N too.
73
+
74
+ Maximum input frequency is 200 kHz.
75
+ """
76
+ N_max = 2000000000
77
+ N_min = 1
78
+
79
+ PLL_used = [False, False]
80
+
81
+ avoid_min_max_PLL_rates = True
82
+ minimize_input_dividers = True
83
+
84
+ """ Hitless mode
85
+
86
+ Output phase is alligned with the input phase.
87
+
88
+ If true, DPLL feedback path will no longer be
89
+ the one from APLL output. It will be from a designated
90
+ output divider.
91
+
92
+ These outputs that are used as feedback for DPLL are
93
+ assigned to a specific PLL profile.
94
+
95
+ f_out_fb_source * r_div = dpll_i_N * f_input_ref
96
+ """
97
+ profiles = {
98
+ "dpll_0_profile_0": {"hitless": False, "fb_source": 0},
99
+ "dpll_0_profile_1": {"hitless": False, "fb_source": 0},
100
+ "dpll_0_profile_2": {"hitless": False, "fb_source": 0},
101
+ "dpll_0_profile_3": {"hitless": False, "fb_source": 0},
102
+ "dpll_1_profile_0": {"hitless": False, "fb_source": 0},
103
+ "dpll_1_profile_1": {"hitless": False, "fb_source": 0},
104
+ "dpll_1_profile_2": {"hitless": False, "fb_source": 0},
105
+ "dpll_1_profile_3": {"hitless": False, "fb_source": 0},
106
+ }
107
+
108
+ config = {
109
+ "r0": 0,
110
+ "r1": 0,
111
+ "r2": 0,
112
+ "r3": 0,
113
+ "PLL0": {},
114
+ "PLL1": {},
115
+ "q0": 0,
116
+ "q1": 0,
117
+ "q2": 0,
118
+ "q3": 0,
119
+ "q4": 0,
120
+ "q5": 0,
121
+ "q6": 0,
122
+ "q7": 0,
123
+ "q8": 0,
124
+ "q9": 0,
125
+ }
126
+
127
+ def list_available_references(self) -> List[int]:
128
+ """List available references for a given divider set."""
129
+ return [self.ref0, self.ref1, self.ref2, self.ref3]
130
+
131
+ def find_dividers(self) -> None:
132
+ """Find dividers for a given input reference.
133
+
134
+ Not implemented for this model.
135
+
136
+ Raises:
137
+ NotImplementedError: Always
138
+ """
139
+ raise NotImplementedError
140
+
141
+ def get_config(self, solution: CpoSolveResult = None) -> Dict:
142
+ """Extract configurations from solver results.
143
+
144
+ Collect internal clock chip configuration and output clock definitions
145
+ leading to connected devices (converters, FPGAs)
146
+
147
+ Args:
148
+ solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver
149
+
150
+ Returns:
151
+ Dict: Dictionary of clocking rates and dividers for configuration
152
+ """
153
+ if solution:
154
+ self.solution = solution
155
+
156
+ config: Dict = {
157
+ "PLL0": {},
158
+ "PLL1": {},
159
+ }
160
+
161
+ for i in range(0, 4):
162
+ config["r" + str(i)] = self._get_val(self.config["r" + str(i)])
163
+
164
+ for i in range(0, 2):
165
+ for j in range(0, 4):
166
+ if self.input_refs[j] != 0 and self.PLL_used[i]:
167
+ n_name = "n" + str(i) + "_profile_" + str(j)
168
+ n_dpll_name = "n_dpll" + str(i) + "_profile_" + str(j)
169
+
170
+ config["PLL" + str(i)][n_dpll_name] = self._get_val(
171
+ self.config[n_dpll_name]
172
+ )
173
+
174
+ config["PLL" + str(i)][n_name] = self._get_val(self.config[n_name])
175
+
176
+ config["PLL" + str(i)]["rate_hz"] = self._get_val(
177
+ self.config["PLL" + str(i) + "_rate"]
178
+ )
179
+
180
+ for i in range(0, 2):
181
+ for j in range(0, 4):
182
+ dpll_profile_name = "dpll_" + str(i) + "_profile_" + str(j)
183
+
184
+ if self.PLL_used[i] and dpll_profile_name in self.profiles:
185
+ if self.profiles[dpll_profile_name]["hitless"]:
186
+ source_nr = int(self.profiles[dpll_profile_name]["fb_source"])
187
+
188
+ config["PLL" + str(i)]["hitless"] = {
189
+ "fb_source": source_nr,
190
+ "fb_source_rate": int(self.out_freqs[source_nr]),
191
+ }
192
+ break
193
+
194
+ for i in range(0, 10):
195
+ config["q" + str(i)] = self._get_val(self.config["q" + str(i)])
196
+
197
+ return config
198
+
199
+ def _setup_solver_constraints(
200
+ self, input_refs: List[int], out_freqs: List[int]
201
+ ) -> None:
202
+ """Apply constraints to solver model.
203
+
204
+ Args:
205
+ input_refs (List[int]): 4 references (frequency in hertz)
206
+ out_freqs (List[int]): 10 outputs (frequency in hertz)
207
+
208
+ Raises:
209
+ Exception: Invalid solver
210
+ """
211
+ self.input_refs = input_refs
212
+
213
+ self.PLL_used = [False, False]
214
+ for i in range(0, 10):
215
+ if out_freqs[i] != 0:
216
+ if i > 5:
217
+ self.PLL_used[1] = True
218
+ else:
219
+ self.PLL_used[0] = True
220
+
221
+ equations = []
222
+
223
+ if self.solver == "gekko":
224
+ """Add divider as variables to the model"""
225
+ for i in range(0, 4):
226
+ if input_refs[i] != 0:
227
+ """If the user does not set a specific r,
228
+ turn it into a variable"""
229
+ r_div_int = isinstance(self.config["r" + str(i)], int)
230
+ if r_div_int and self.config["r" + str(i)] != 0:
231
+ self.config["r" + str(i)] = self.model.Const(
232
+ int(self.config["r" + str(i)]),
233
+ name=("r" + str(i)),
234
+ )
235
+ else:
236
+ self.config["r" + str(i)] = self.model.Var(
237
+ integer=True,
238
+ lb=self.R_min,
239
+ ub=self.R_max,
240
+ name=("r" + str(i)),
241
+ )
242
+ self.config["input_ref_" + str(i)] = self.model.Const(
243
+ int(input_refs[i]),
244
+ name=("input_ref_" + str(i)),
245
+ )
246
+
247
+ """ Add PLL N as variables to the model for each PLL profile """
248
+ for i in range(0, 2):
249
+ if self.PLL_used[i]:
250
+ """Force PLL input rates and output rates
251
+ to be integer values"""
252
+ self.config["PLL" + str(i) + "_rate"] = self.model.Var(
253
+ integer=True,
254
+ lb=self.PLL_out_min[i],
255
+ ub=self.PLL_out_max[i],
256
+ name=("PLL" + str(i) + "_rate"),
257
+ )
258
+
259
+ for j in range(0, 4):
260
+ if input_refs[j] != 0:
261
+ n_name = "n" + str(i) + "_profile_" + str(j)
262
+ n_dpll_name = "n_dpll" + str(i) + "_profile_" + str(j)
263
+ m_apll_name = "m_apll" + str(i) + "_profile_" + str(j)
264
+
265
+ self.config[n_name] = self.model.Var(
266
+ integer=True,
267
+ lb=self.N_min,
268
+ ub=self.N_max,
269
+ name=n_name,
270
+ )
271
+
272
+ """ Internally the PLL block is composed of a
273
+ Digital PLL and an Analog PLL with different
274
+ constraints on dividers.
275
+ """
276
+ DPLL_N = self.model.Var(
277
+ integer=True, lb=1, ub=350e6, name=n_dpll_name
278
+ )
279
+ self.config[n_dpll_name] = DPLL_N
280
+
281
+ APLL_M = self.model.Var(
282
+ integer=True, lb=7, ub=255, name=m_apll_name
283
+ )
284
+ self.config[m_apll_name] = APLL_M
285
+
286
+ equations = equations + [
287
+ self.config[n_name] == DPLL_N * APLL_M
288
+ ]
289
+
290
+ elif self.solver == "CPLEX":
291
+ """Add divider as variables to the model"""
292
+ for i in range(0, 4):
293
+ if input_refs[i] != 0:
294
+ """If the user does not set a specific r,
295
+ turn it into a variable
296
+ """
297
+
298
+ r_div_int = isinstance(self.config["r" + str(i)], int)
299
+ if r_div_int and self.config["r" + str(i)] != 0:
300
+ self.config["input_ref_" + str(i)] = exp.CpoValue(
301
+ int(self.config["r" + str(i)]), type=ctg.Type_Int
302
+ )
303
+ else:
304
+ self.config["r" + str(i)] = exp.integer_var(
305
+ int(self.R_min), int(self.R_max), "r" + str(i)
306
+ )
307
+ self.config["input_ref_" + str(i)] = exp.CpoValue(
308
+ int(input_refs[i]), type=ctg.Type_Int
309
+ )
310
+
311
+ """ Add PLL N as variables to the model for each PLL profile """
312
+ for i in range(0, 2):
313
+ if self.PLL_used[i]:
314
+ self.config["PLL" + str(i) + "_rate"] = exp.integer_var(
315
+ int(self.PLL_out_min[i]),
316
+ int(self.PLL_out_max[i]),
317
+ "PLL" + str(i) + "_rate",
318
+ )
319
+
320
+ for j in range(0, 4):
321
+ if input_refs[j] != 0:
322
+ n_name = "n" + str(i) + "_profile_" + str(j)
323
+ n_dpll_name = "n_dpll" + str(i) + "_profile_" + str(j)
324
+ m_apll_name = "m_apll" + str(i) + "_profile_" + str(j)
325
+
326
+ self.config[n_name] = exp.integer_var(
327
+ int(self.N_min), int(self.N_max), n_name
328
+ )
329
+
330
+ """ Internally the PLL block is composed of a Digital
331
+ PLL and an Analog PLL with different constraints on
332
+ dividers
333
+ """
334
+ DPLL_N = exp.integer_var(int(1), int(350e6), n_dpll_name)
335
+ self.config[n_dpll_name] = DPLL_N
336
+ APLL_M = exp.integer_var(int(7), int(255), m_apll_name)
337
+ self.config[m_apll_name] = APLL_M
338
+
339
+ equations = equations + [
340
+ self.config[n_name] == DPLL_N * APLL_M
341
+ ]
342
+ else:
343
+ raise Exception("Unknown solver {}".format(self.solver))
344
+
345
+ for i in range(0, 4):
346
+ if self.input_refs[i] != 0:
347
+ if self.solver == "gekko":
348
+ self.config["PLL_in_rate_" + str(i)] = self.model.Var(
349
+ integer=True,
350
+ lb=self.PLL_in_min,
351
+ ub=self.PLL_in_max,
352
+ name=("PLL_in_rate_" + str(i)),
353
+ )
354
+ elif self.solver == "CPLEX":
355
+ self.config["PLL_in_rate_" + str(i)] = exp.integer_var(
356
+ int(self.PLL_in_min),
357
+ int(self.PLL_in_max),
358
+ "PLL_in_rate_" + str(i),
359
+ )
360
+ else:
361
+ raise Exception("Unknown solver {}".format(self.solver))
362
+
363
+ equations = equations + [
364
+ self.config["PLL_in_rate_" + str(i)] * self.config["r" + str(i)]
365
+ == self.config["input_ref_" + str(i)]
366
+ ]
367
+
368
+ for i in range(0, 2):
369
+ for j in range(0, 4):
370
+ if self.input_refs[j] != 0 and self.PLL_used[i]:
371
+ n_name = "n" + str(i) + "_profile_" + str(j)
372
+ n_dpll_name = "n_dpll" + str(i) + "_profile_" + str(j)
373
+ m_apll_name = "m_apll" + str(i) + "_profile_" + str(j)
374
+ dpll_profile_name = "dpll_" + str(i) + "_profile_" + str(j)
375
+
376
+ pll_rate = self.config["PLL" + str(i) + "_rate"]
377
+ pll_in_rate = self.config["PLL_in_rate_" + str(j)]
378
+ pll_m_div = self.config[m_apll_name]
379
+ pll_n_div = self.config[n_dpll_name]
380
+
381
+ if not self.profiles[dpll_profile_name]["hitless"]:
382
+ equations = equations + [
383
+ self.config[n_name] * pll_in_rate == pll_rate
384
+ ]
385
+
386
+ """ Limit APLL PFD input values """
387
+ equations = equations + [
388
+ (pll_n_div * pll_in_rate) >= self.APLL_PFD_MIN,
389
+ (pll_n_div * pll_in_rate) <= self.APLL_PFD_MAX,
390
+ ]
391
+ else:
392
+ """Limit APLL PFD input values"""
393
+ equations = equations + [
394
+ pll_rate >= self.APLL_PFD_MIN * pll_m_div,
395
+ pll_rate <= self.APLL_PFD_MAX * pll_m_div,
396
+ ]
397
+
398
+ self._add_equation(equations)
399
+
400
+ cplex_objectives = []
401
+
402
+ """ Instruct solver to try to avoid Minimum and Maximum PLL rates """
403
+ if self.avoid_min_max_PLL_rates:
404
+ for i in range(0, 2):
405
+ if self.PLL_used[i]:
406
+ average_PLL_rate = self.PLL_out_min[i] / 2 + self.PLL_out_max[i] / 2
407
+
408
+ if self.solver == "CPLEX":
409
+ cplex_objectives = cplex_objectives + [
410
+ mod.abs(
411
+ self.config["PLL" + str(i) + "_rate"] - average_PLL_rate
412
+ )
413
+ ]
414
+ elif self.solver == "gekko":
415
+ self.model.Minimize(
416
+ self.model.abs3(
417
+ self.config["PLL" + str(i) + "_rate"] - average_PLL_rate
418
+ )
419
+ )
420
+ else:
421
+ raise Exception("Unknown solver {}".format(self.solver))
422
+
423
+ """ Instruct solver to maximize PLL input rates """
424
+ if self.minimize_input_dividers:
425
+ for i in range(0, 4):
426
+ if self.input_refs[i] != 0:
427
+ if self.solver == "CPLEX":
428
+ cplex_objectives = cplex_objectives + [
429
+ self.config["r" + str(i)]
430
+ ]
431
+ elif self.solver == "gekko":
432
+ self.model.Maximize(self.config["PLL_in_rate_" + str(i)])
433
+ else:
434
+ raise Exception("Unknown solver {}".format(self.solver))
435
+
436
+ if self.solver == "CPLEX" and len(cplex_objectives) != 0:
437
+ self.model.add(mod.minimize_static_lex(cplex_objectives))
438
+
439
+ def _setup(self, input_refs: List[int], out_freqs: List[int]) -> None:
440
+ # Setup clock chip internal constraints
441
+ self.out_freqs = out_freqs
442
+
443
+ self._setup_solver_constraints(input_refs, out_freqs)
444
+
445
+ def set_requested_clocks(self, ins: List[int], outs: List[int]) -> None:
446
+ """Define necessary clocks to be generated in model.
447
+
448
+ Args:
449
+ ins (List[int]): list of input references rates
450
+ outs (List[int]): list of output rates required
451
+
452
+ Raises:
453
+ Exception: if the number of input references is not equal to the
454
+ number of output rates
455
+ """
456
+ input_refs = [0] * 4
457
+ out_freqs = [0] * 10
458
+
459
+ for in_ref in ins:
460
+ input_refs[in_ref[0]] = in_ref[1]
461
+
462
+ for out_ref in outs:
463
+ out_freqs[out_ref[0]] = out_ref[1]
464
+
465
+ self._setup(input_refs, out_freqs)
466
+
467
+ """ Add output dividers as variables to the model """
468
+ for i in range(0, 10):
469
+ if out_freqs[i] != 0:
470
+ if self.solver == "gekko":
471
+ self.config["q" + str(i)] = self.model.Var(
472
+ integer=True,
473
+ lb=self.Q_min,
474
+ ub=self.Q_max,
475
+ name=("q" + str(i)),
476
+ )
477
+ self.config["out_rate_" + str(i)] = self.model.Const(
478
+ int(out_freqs[i]), name=("out_rate_" + str(i))
479
+ )
480
+ elif self.solver == "CPLEX":
481
+ self.config["q" + str(i)] = exp.integer_var(
482
+ int(self.Q_min), int(self.Q_max), "q" + str(i)
483
+ )
484
+ self.config["out_rate_" + str(i)] = exp.CpoValue(
485
+ value=int(out_freqs[i]), type=ctg.Type_Int
486
+ )
487
+ else:
488
+ raise Exception("Unknown solver {}".format(self.solver))
489
+
490
+ """ Add hitless mode constraints for profiles that activate it """
491
+ for i in range(0, 2):
492
+ for j in range(0, 4):
493
+ dpll_profile_name = "dpll_" + str(i) + "_profile_" + str(j)
494
+
495
+ if self.PLL_used[i] and self.profiles[dpll_profile_name]["hitless"]:
496
+ source_nr = int(self.profiles[dpll_profile_name]["fb_source"])
497
+ n_dpll_name = "n_dpll" + str(i) + "_profile_" + str(j)
498
+
499
+ if out_freqs[source_nr] == 0:
500
+ raise Exception(
501
+ "Hitless profile: {}, has an invalid source.".format(
502
+ dpll_profile_name
503
+ )
504
+ )
505
+
506
+ input_ref = self.config["input_ref_" + str(j)]
507
+ pll_n_div = self.config[n_dpll_name]
508
+ out_rate = self.config["out_rate_" + str(source_nr)]
509
+ r_div = self.config["r" + str(j)]
510
+
511
+ """ Frequency translation factor:
512
+ N * input_ref_j == out_rate_x * r_div_j
513
+ """
514
+ self._add_equation([input_ref * pll_n_div == out_rate * r_div])
515
+
516
+ """ Hitless mode places a strict constraint on Q dividers """
517
+ self.config["q" + str(i)]
518
+ self._add_equation([self.config["q" + str(source_nr)] >= 8])
519
+
520
+ for i in range(0, 10):
521
+ if out_freqs[i] != 0:
522
+ if i > 5:
523
+ pll_number = 1
524
+ else:
525
+ pll_number = 0
526
+
527
+ self._add_equation(
528
+ [
529
+ self.config["PLL" + str(pll_number) + "_rate"]
530
+ == self.config["out_rate_" + str(i)] * self.config["q" + str(i)]
531
+ ]
532
+ )
533
+
534
+ def _solve_gekko(self) -> bool:
535
+ """Local solve method for clock model.
536
+
537
+ Call model solver with correct arguments.
538
+
539
+ Returns:
540
+ bool: Always False
541
+ """
542
+ self.model.options.SOLVER = 1 # APOPT solver
543
+ self.model.solver_options = [
544
+ "minlp_maximum_iterations 300000000",
545
+ "minlp_max_iter_with_int_sol 6000",
546
+ "minlp_as_nlp 0",
547
+ "minlp_branch_method 1",
548
+ "minlp_integer_tol 0.0001",
549
+ ]
550
+
551
+ self.model.solve(disp=False)
552
+
553
+ return False