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
adijif/plls/adf4382.py ADDED
@@ -0,0 +1,581 @@
1
+ """ADF4382 Microwave Wideband Synthesizer with Integrated VCO model."""
2
+
3
+ from typing import Dict, List, Union
4
+
5
+ from docplex.cp.modeler import if_then
6
+ from docplex.cp.solution import CpoSolveResult # type: ignore
7
+
8
+ from adijif.plls.pll import pll
9
+ from adijif.solvers import CpoExpr, GK_Intermediate, integer_var
10
+
11
+
12
+ def to_int(value: Union[int, float, List[int], List[float]]) -> Union[int, List[int]]:
13
+ """Convert value to int or list of ints."""
14
+ if isinstance(value, (int, float)):
15
+ return int(value)
16
+ elif isinstance(value, list):
17
+ return [int(v) for v in value]
18
+ else:
19
+ raise TypeError(f"Unsupported type: {type(value)}")
20
+
21
+
22
+ class adf4382(pll):
23
+ """ADF4382 PLL model.
24
+
25
+ This model does not support fractional mode
26
+
27
+ https://www.analog.com/media/en/technical-documentation/data-sheets/adf4382.pdf
28
+ """
29
+
30
+ name = "adf4382"
31
+
32
+ input_freq_min = int(10e6)
33
+ input_freq_max = int(4.5e9)
34
+
35
+ # pfd_freq_max_frac = int(160e6)
36
+ """Input reference doubler range"""
37
+ freq_doubler_input_min = int(10e6)
38
+ freq_doubler_input_max = int(2000e6)
39
+
40
+ """PFD frequency ranges"""
41
+ # Integer
42
+ pfd_freq_min_int_n_wide = int(5.4e6)
43
+ pfd_freq_max_int_n_wide = int(625e6)
44
+ # n full [*range(4, 4095+1)] minus 15, 28, 31
45
+ _n_int_wide = (
46
+ [*range(4, 15)] + [*range(16, 28)] + [*range(29, 31)] + [*range(32, 4095 + 1)]
47
+ )
48
+ pfd_freq_min_int_n_narrow = int(5.4e6)
49
+ pfd_freq_max_int_n_narrow = int(540e6)
50
+ _n_int_narrow = [15, 28, 31]
51
+ # Fractional
52
+ # n full [*range(19, 4095+1)]
53
+ pfd_freq_min_frac_modes_0_4 = int(5.4e6)
54
+ pfd_freq_max_frac_modes_0_4 = int(250e6)
55
+ _n_frac_modes_0_4 = {
56
+ "mode_0": to_int([*range(10, 4095 + 1)]),
57
+ "mode_4": to_int([*range(23, 4095 + 1)]),
58
+ }
59
+ pfd_freq_min_frac_modes_5 = int(5.4e6)
60
+ pfd_freq_max_frac_modes_5 = int(220e6)
61
+ _n_frac_modes_5 = to_int([*range(27, 4095 + 1)])
62
+
63
+ vco_freq_min = int(11e9)
64
+ vco_freq_max = int(22e9)
65
+
66
+ _d = to_int([1, 2])
67
+ d_available = [1, 2]
68
+
69
+ @property
70
+ def d(self) -> Union[int, List[int]]:
71
+ """REF-in doubler.
72
+
73
+ Valid values are 1,2
74
+
75
+ Returns:
76
+ int: Current allowable setting
77
+ """
78
+ return self._d
79
+
80
+ @d.setter
81
+ def d(self, value: Union[int, List[int]]) -> None:
82
+ """REF-in double.
83
+
84
+ Valid values are 1,2
85
+
86
+ Args:
87
+ value (int, list[int]): Current allowable setting
88
+
89
+ """
90
+ self._check_in_range(value, self.d_available, "d")
91
+ self._d = value
92
+
93
+ _r = [*range(1, 63 + 1)]
94
+ r_available = [*range(1, 63 + 1)]
95
+
96
+ @property
97
+ def r(self) -> Union[int, List[int]]:
98
+ """Reference divider.
99
+
100
+ Valid values are 1->(2^6-1)
101
+
102
+ Returns:
103
+ int: Current allowable setting
104
+ """
105
+ return self._r
106
+
107
+ @r.setter
108
+ def r(self, value: Union[int, List[int]]) -> None:
109
+ """Reference divider.
110
+
111
+ Valid values are 1->63
112
+
113
+ Args:
114
+ value (int, list[int]): Current allowable setting
115
+
116
+ """
117
+ self._check_in_range(value, self.r_available, "r")
118
+ self._r = value
119
+
120
+ _o = [1, 2, 4]
121
+ o_available = [1, 2, 4]
122
+
123
+ @property
124
+ def o(self) -> Union[int, List[int]]:
125
+ """Output RF divider.
126
+
127
+ Valid dividers are 1,2,4
128
+
129
+ Returns:
130
+ int: Current allowable dividers
131
+ """
132
+ return self._o
133
+
134
+ @o.setter
135
+ def o(self, value: Union[int, List[int]]) -> None:
136
+ """Output RF divider.
137
+
138
+ Valid dividers are 1,2,4
139
+
140
+ Args:
141
+ value (int, list[int]): Allowable values for divider
142
+
143
+ """
144
+ self._check_in_range(value, self.o_available, "o")
145
+ self._o = value
146
+
147
+ _n = [*range(4, 2**12)]
148
+ n_available = [*range(4, 2**12)]
149
+
150
+ @property
151
+ def n(self) -> Union[int, List[int]]:
152
+ """Feedback divider.
153
+
154
+ Valid dividers are 1->4096
155
+
156
+ Returns:
157
+ int: Current allowable dividers
158
+ """
159
+ return self._n
160
+
161
+ @n.setter
162
+ def n(self, value: Union[int, List[int]]) -> None:
163
+ """Feedback divider.
164
+
165
+ Valid dividers are 1->4096
166
+
167
+ Args:
168
+ value (int, list[int]): Allowable values for divider
169
+
170
+ """
171
+ self._check_in_range(value, self.n_available, "n")
172
+ self._n = value
173
+
174
+ _mode = ["integer"] # Dont use fractional mode by default
175
+ mode_available = ["integer", "fractional"]
176
+
177
+ @property
178
+ def mode(self) -> Union[str, List[str]]:
179
+ """Set operational mode.
180
+
181
+ Options are: fractional, integer or [fractional, integer]
182
+
183
+ Returns:
184
+ str: Current allowable modes
185
+ """
186
+ return self._mode
187
+
188
+ @mode.setter
189
+ def mode(self, value: Union[str, List[str]]) -> None:
190
+ """Set operational mode.
191
+
192
+ Options are: fractional, integer or [fractional, integer]
193
+
194
+ Args:
195
+ value (str, list[str]): Allowable values for mode
196
+
197
+ """
198
+ self._check_in_range(value, self.mode_available, "mode")
199
+ self._mode = value
200
+
201
+ # These are too large for user to set
202
+ _frac1_min_max = [0, 2**25 - 1]
203
+ _frac2_min_max = [0, 2**24 - 1]
204
+ _MOD1 = 33554432 # 2^25
205
+ _MOD2_PHASE_SYNC_min_max = [1, 2**17 - 1]
206
+ _MOD2_NO_PHASE_SYNC_min_max = [1, 2**24 - 1]
207
+
208
+ _phase_sync = True
209
+
210
+ @property
211
+ def require_phase_sync(self) -> bool:
212
+ """Determine if phase sync is required.
213
+
214
+ Returns:
215
+ bool: True if phase sync is required
216
+ """
217
+ return self._phase_sync
218
+
219
+ @require_phase_sync.setter
220
+ def require_phase_sync(self, value: bool) -> None:
221
+ """Determine if phase sync is required.
222
+
223
+ Args:
224
+ value (bool): True if phase sync is required
225
+ """
226
+ self._check_in_range(value, [True, False], "require_phase_sync")
227
+ self._phase_sync = value
228
+
229
+ _EFM3_MODE = [0, 4, 5]
230
+ EFM3_MODE_available = [0, 4, 5]
231
+
232
+ @property
233
+ def EFM3_MODE(self) -> Union[int, List[int]]:
234
+ """Set EFM3 optimization mode.
235
+
236
+ Options are: 0, 4, 5
237
+
238
+ Returns:
239
+ int: Current allowable modes
240
+ """
241
+ return self._EFM3_MODE
242
+
243
+ @EFM3_MODE.setter
244
+ def EFM3_MODE(self, value: Union[int, List[int]]) -> None:
245
+ """Set EFM3 optimization mode.
246
+
247
+ Options are: 0, 4, 5
248
+
249
+ Args:
250
+ value (int, list[int]): Allowable values for mode
251
+
252
+ """
253
+ self._check_in_range(value, self.EFM3_MODE_available, "EFM3_MODE")
254
+ self._EFM3_MODE = value
255
+
256
+ def get_config(self, solution: CpoSolveResult = None) -> Dict:
257
+ """Extract configurations from solver results.
258
+
259
+ Collect internal clock chip configuration and output clock definitions
260
+ leading to connected devices (converters, FPGAs)
261
+
262
+ Args:
263
+ solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver
264
+
265
+ Returns:
266
+ Dict: Dictionary of clocking rates and dividers for configuration
267
+
268
+ Raises:
269
+ Exception: If solver is not called first
270
+ """
271
+ if not self._clk_names:
272
+ raise Exception("set_requested_clocks must be called before get_config")
273
+
274
+ if solution:
275
+ self.solution = solution
276
+
277
+ config: Dict = {
278
+ "d": self._get_val(self.config["d"]),
279
+ # "n": self.solution.get_kpis()["n"],
280
+ "o": self._get_val(self.config["o"]),
281
+ "r": self._get_val(self.config["r"]),
282
+ }
283
+ if isinstance(self._n, list):
284
+ config["n"] = self.solution.get_kpis()["n"]
285
+ else:
286
+ config["n"] = self._n
287
+
288
+ if self._mode == "integer" or self._mode == ["integer"]:
289
+ mode = "integer"
290
+ elif self._mode == "fractional" or self._mode == ["fractional"]:
291
+ mode = "fractional"
292
+ else:
293
+ if self._get_val(self.config["frac_0_int_1"]) == 1:
294
+ mode = "integer"
295
+ else:
296
+ mode = "fractional"
297
+
298
+ if mode == "integer":
299
+ config["mode"] = "integer"
300
+ else:
301
+ config["mode"] = "fractional"
302
+ config["n_frac1w"] = self._get_val(self.config["n_frac1w"])
303
+ config["n_frac2w"] = self._get_val(self.config["n_frac2w"])
304
+ config["MOD1"] = self._MOD1
305
+ config["MOD2"] = self._get_val(self.config["MOD2"])
306
+ config["n_int"] = self._get_val(self.config["n_int"])
307
+ config["EFM3_MODE"] = self._get_val(self.config["EFM3_MODE"])
308
+
309
+ vco = self.solution.get_kpis()["vco"]
310
+ config["rf_out_frequency"] = vco / config["o"]
311
+
312
+ return config
313
+
314
+ def _setup_solver_constraints(
315
+ self, input_ref: Union[int, float, CpoExpr, GK_Intermediate]
316
+ ) -> None:
317
+ """Apply constraints to solver model.
318
+
319
+ Args:
320
+ input_ref (int, float, CpoExpr, GK_Intermediate): Input reference
321
+ frequency in hertz
322
+
323
+ Raises:
324
+ NotImplementedError: If solver is not CPLEX
325
+ """
326
+ self.config = {}
327
+
328
+ if self.solver != "CPLEX":
329
+ raise NotImplementedError("Only CPLEX solver is supported")
330
+
331
+ # if not isinstance(input_ref, (int, float)):
332
+ # self.config["input_ref_set"] = input_ref(self.model) # type: ignore
333
+ # input_ref = self.config["input_ref_set"]["range"]
334
+ self.input_ref = input_ref
335
+
336
+ # PFD
337
+ self.config["d"] = self._convert_input(self.d, name="d")
338
+ self.config["r"] = self._convert_input(self.r, name="r")
339
+ self.config["o"] = self._convert_input(self.o, name="o")
340
+
341
+ if self._mode == "integer":
342
+ self.config["frac_0_int_1"] = 1
343
+ elif self._mode == "fractional":
344
+ self.config["frac_0_int_1"] = 0
345
+ else:
346
+ self.config["frac_0_int_1"] = self._convert_input(
347
+ [0, 1], name="frac_0_int_1"
348
+ )
349
+
350
+ self.config["EFM3_MODE"] = self._convert_input(self.EFM3_MODE, name="EFM3_MODE")
351
+ if isinstance(self.EFM3_MODE, list):
352
+ self.model.add_kpi(self.config["EFM3_MODE"], "EFM3_MODE")
353
+
354
+ # N which supports fractional modes
355
+ if "fractional" in self._mode:
356
+ self.config["n_int"] = self._convert_input(self._n, name="n_int")
357
+ self.config["n_frac1w"] = integer_var(
358
+ min=self._frac1_min_max[0],
359
+ max=self._frac1_min_max[1],
360
+ name="n_frac1w",
361
+ )
362
+ self.config["n_frac2w"] = integer_var(
363
+ min=self._frac2_min_max[0],
364
+ max=self._frac2_min_max[1],
365
+ name="n_frac2w",
366
+ )
367
+ _MOD2_min_max = (
368
+ self._MOD2_PHASE_SYNC_min_max
369
+ if self._phase_sync
370
+ else self._MOD2_NO_PHASE_SYNC_min_max
371
+ )
372
+ self.config["MOD2"] = integer_var(
373
+ min=_MOD2_min_max[0],
374
+ max=_MOD2_min_max[1],
375
+ name="MOD2",
376
+ )
377
+ self.config["n_frac"] = self._add_intermediate(
378
+ (
379
+ self.config["n_frac1w"]
380
+ + self.config["n_frac2w"] / self.config["MOD2"]
381
+ )
382
+ / self._MOD1
383
+ )
384
+ # Constraints
385
+ self._add_equation(
386
+ if_then(
387
+ self.config["frac_0_int_1"] == 1,
388
+ self.config["n_frac"] == 0,
389
+ )
390
+ )
391
+ self._add_equation(
392
+ if_then(
393
+ self.config["frac_0_int_1"] == 0,
394
+ 100000 * self.config["n_frac"] < 100000,
395
+ )
396
+ )
397
+ self._add_equation(
398
+ if_then(
399
+ self.config["frac_0_int_1"] == 0,
400
+ self.config["n_frac1w"] + self.config["n_frac2w"] > 0,
401
+ )
402
+ )
403
+ self._add_equation(
404
+ if_then(
405
+ self.config["frac_0_int_1"] == 0,
406
+ self.config["MOD2"] > self.config["n_frac2w"],
407
+ )
408
+ )
409
+
410
+ self.config["n"] = self._add_intermediate(
411
+ self.config["n_int"] + self.config["n_frac"]
412
+ )
413
+ else:
414
+ self.config["n"] = self._convert_input(self._n, name="n_int")
415
+
416
+ if isinstance(self._mode, list):
417
+ # self.model.maximize(self.config["frac_0_int_1"])
418
+ self._add_objective(self.config["frac_0_int_1"])
419
+
420
+ # Add PFD frequency dependent on N
421
+ if isinstance(self._n, list) and len(self._n) > 1:
422
+ self.model.add_kpi(
423
+ self.config["n"],
424
+ "n",
425
+ )
426
+
427
+ # Add EFM3 mode constraints on N
428
+ self._add_equation(
429
+ [
430
+ if_then(
431
+ self.config["EFM3_MODE"] == 0,
432
+ self.config["n"] >= min(self._n_frac_modes_0_4["mode_0"]),
433
+ ),
434
+ if_then(
435
+ self.config["EFM3_MODE"] == 4,
436
+ self.config["n"] >= min(self._n_frac_modes_0_4["mode_4"]),
437
+ ),
438
+ if_then(
439
+ self.config["EFM3_MODE"] == 5,
440
+ self.config["n"] >= min(self._n_frac_modes_5),
441
+ ),
442
+ ]
443
+ )
444
+
445
+ # Clocking rates
446
+ self.config["f_pfd"] = self._add_intermediate(
447
+ input_ref * self.config["d"] / self.config["r"]
448
+ )
449
+
450
+ self.config["vco"] = self._add_intermediate(
451
+ self.config["f_pfd"] * self.config["n"] * self.config["o"]
452
+ )
453
+ self.model.add_kpi(
454
+ self.config["f_pfd"] * self.config["n"] * self.config["o"],
455
+ "vco",
456
+ )
457
+
458
+ # Add PFD frequency constraints for integer mode
459
+ self._add_equation(
460
+ [
461
+ if_then(
462
+ self.config["frac_0_int_1"] == 1,
463
+ if_then(
464
+ self.config["n"] == 15,
465
+ self.config["f_pfd"] <= self.pfd_freq_max_int_n_narrow,
466
+ ),
467
+ ),
468
+ if_then(
469
+ self.config["frac_0_int_1"] == 1,
470
+ if_then(
471
+ self.config["n"] == 28,
472
+ self.config["f_pfd"] <= self.pfd_freq_max_int_n_narrow,
473
+ ),
474
+ ),
475
+ if_then(
476
+ self.config["frac_0_int_1"] == 1,
477
+ if_then(
478
+ self.config["n"] == 31,
479
+ self.config["f_pfd"] <= self.pfd_freq_max_int_n_narrow,
480
+ ),
481
+ ),
482
+ # Wide is a looser upper bound but applies to all cases
483
+ if_then(
484
+ self.config["frac_0_int_1"] == 1,
485
+ self.config["f_pfd"] <= self.pfd_freq_max_int_n_wide,
486
+ ),
487
+ ]
488
+ )
489
+
490
+ # Add PFD frequency constraints for fractional mode
491
+ self._add_equation(
492
+ [
493
+ if_then(
494
+ self.config["frac_0_int_1"] == 0,
495
+ if_then(
496
+ self.config["EFM3_MODE"] == 0,
497
+ self.config["f_pfd"] <= self.pfd_freq_max_frac_modes_0_4,
498
+ ),
499
+ ),
500
+ if_then(
501
+ self.config["frac_0_int_1"] == 0,
502
+ if_then(
503
+ self.config["EFM3_MODE"] == 4,
504
+ self.config["f_pfd"] <= self.pfd_freq_max_frac_modes_0_4,
505
+ ),
506
+ ),
507
+ if_then(
508
+ self.config["frac_0_int_1"] == 0,
509
+ if_then(
510
+ self.config["EFM3_MODE"] == 5,
511
+ self.config["f_pfd"] <= self.pfd_freq_max_frac_modes_5,
512
+ ),
513
+ ),
514
+ ]
515
+ )
516
+
517
+ # Global min for PFD
518
+ self._add_equation(
519
+ [
520
+ self.config["f_pfd"] >= self.pfd_freq_min_int_n_wide,
521
+ ]
522
+ )
523
+
524
+ # Add remaining constraints
525
+ self._add_equation(
526
+ [
527
+ input_ref <= self.input_freq_max,
528
+ input_ref >= self.input_freq_min,
529
+ input_ref * self.config["d"] <= self.freq_doubler_input_max,
530
+ input_ref * self.config["d"] >= self.freq_doubler_input_min,
531
+ self.config["vco"] <= self.vco_freq_max,
532
+ self.config["vco"] >= self.vco_freq_min,
533
+ ]
534
+ )
535
+
536
+ # Minimize feedback divider to reduce jitter
537
+ self._add_objective(1 / self.config["n"])
538
+ # self.model.minimize(self.config['n'])
539
+
540
+ def _setup(self, input_ref: int) -> None:
541
+ if isinstance(input_ref, (float, int)):
542
+ assert (
543
+ self.input_freq_max >= input_ref >= self.input_freq_min
544
+ ), "Input frequency out of range"
545
+
546
+ # Setup clock chip internal constraints
547
+ self._setup_solver_constraints(input_ref)
548
+
549
+ def _get_clock_constraint(
550
+ self, clk_name: str
551
+ ) -> Union[int, float, CpoExpr, GK_Intermediate]:
552
+ """Get abstract clock output.
553
+
554
+ Args:
555
+ clk_name (str): Reference clock name
556
+
557
+ Returns:
558
+ (int or float or CpoExpr or GK_Intermediate): Abstract
559
+ or concrete clock reference
560
+ """
561
+ self._clk_names = ["clk_name"]
562
+
563
+ assert "o" in self.config, "_setup must be called first to set PLL internals"
564
+
565
+ return self.config["vco"] / self.config["o"]
566
+
567
+ def set_requested_clocks(
568
+ self, ref_in: Union[int, float, CpoExpr, GK_Intermediate], out_freq: int
569
+ ) -> None:
570
+ """Define necessary clocks to be generated in model.
571
+
572
+ Args:
573
+ ref_in (int, float, CpoExpr, GK_Intermediate): Reference frequency in hertz
574
+ out_freq (int): list of required clocks to be output
575
+
576
+ """
577
+ self._setup(ref_in)
578
+ self._clk_names = ["rf_out"]
579
+ print(f"Output: {out_freq}")
580
+
581
+ self._add_equation([self.config["o"] * out_freq == self.config["vco"]])
adijif/plls/pll.py ADDED
@@ -0,0 +1,103 @@
1
+ """PLL parent metaclass to maintain consistency for all pll chips."""
2
+
3
+ from abc import ABCMeta
4
+ from typing import Union
5
+
6
+ from docplex.cp.solution import CpoSolveResult # type: ignore
7
+
8
+ from adijif.common import core
9
+ from adijif.gekko_trans import gekko_translation
10
+
11
+
12
+ class pll(core, gekko_translation, metaclass=ABCMeta):
13
+ """Parent metaclass for all pll chip classes."""
14
+
15
+ # @property
16
+ # @abstractmethod
17
+ # def find_dividers(self) -> Dict:
18
+ # """Find all possible divider settings that validate config.
19
+
20
+ # Raises:
21
+ # NotImplementedError: Method not implemented
22
+ # """
23
+ # raise NotImplementedError # pragma: no cover
24
+
25
+ # @property
26
+ # @abstractmethod
27
+ # def list_available_references(self) -> List[int]:
28
+ # """Determine all references that can be generated.
29
+
30
+ # Based on config list possible references that can be generated
31
+ # based on VCO and output dividers
32
+
33
+ # Raises:
34
+ # NotImplementedError: Method not implemented
35
+ # """
36
+ # raise NotImplementedError # pragma: no cover
37
+
38
+ _connected_to_output = ""
39
+ _connected_to_input = ""
40
+
41
+ def _solve_gekko(self) -> bool:
42
+ """Local solve method for clock model.
43
+
44
+ Call model solver with correct arguments.
45
+
46
+ Returns:
47
+ bool: Always False
48
+ """
49
+ self.model.options.SOLVER = 1 # APOPT solver
50
+ self.model.solver_options = [
51
+ "minlp_maximum_iterations 1000", # minlp iterations with integer solution
52
+ "minlp_max_iter_with_int_sol 100", # treat minlp as nlp
53
+ "minlp_as_nlp 0", # nlp sub-problem max iterations
54
+ "nlp_maximum_iterations 500", # 1 = depth first, 2 = breadth first
55
+ "minlp_branch_method 1", # maximum deviation from whole number
56
+ "minlp_integer_tol 0", # covergence tolerance (MUST BE 0 TFC)
57
+ "minlp_gap_tol 0.1",
58
+ ]
59
+
60
+ self.model.solve(disp=False)
61
+ self.model.cleanup()
62
+ return False
63
+
64
+ # def _add_objective(self, sysrefs: List) -> None:
65
+ # pass
66
+
67
+ def _solve_cplex(self) -> CpoSolveResult:
68
+ if self._objectives:
69
+ if len(self._objectives) == 1:
70
+ self.model.maximize(self._objectives[0])
71
+ else:
72
+ self.model.add(self.model.maximize_static_lex(self._objectives))
73
+ self.model.export_model()
74
+ self.solution = self.model.solve(
75
+ # Workers=1,
76
+ # agent="local",
77
+ # SearchType="DepthFirst",
78
+ LogVerbosity="Verbose",
79
+ # OptimalityTolerance=1e-12,
80
+ # RelativeOptimalityTolerance=1e-12,
81
+ )
82
+ if self.solution.solve_status not in ["Feasible", "Optimal"]:
83
+ raise Exception("Solution Not Found")
84
+ return self.solution
85
+
86
+ def solve(self) -> Union[None, CpoSolveResult]:
87
+ """Local solve method for clock model.
88
+
89
+ Call model solver with correct arguments.
90
+
91
+ Returns:
92
+ [None,CpoSolveResult]: When cplex solver is used CpoSolveResult is returned
93
+
94
+ Raises:
95
+ Exception: If solver is not valid
96
+
97
+ """
98
+ if self.solver == "gekko":
99
+ return self._solve_gekko()
100
+ elif self.solver == "CPLEX":
101
+ return self._solve_cplex()
102
+ else:
103
+ raise Exception(f"Unknown solver {self.solver}")