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,679 @@
1
+ """AD9081 high speed MxFE clocking model."""
2
+
3
+ from abc import ABCMeta, abstractmethod
4
+ from typing import Any, Dict, List, Union
5
+
6
+ import numpy as np
7
+
8
+ from ..solvers import GEKKO, CpoModel, CpoSolveResult # type: ignore
9
+ from .ad9081_dp import ad9081_dp_rx, ad9081_dp_tx
10
+ from .ad9081_util import _load_rx_config_modes, _load_tx_config_modes
11
+ from .adc import adc
12
+ from .converter import converter
13
+ from .dac import dac
14
+
15
+ from ..solvers import integer_var # type: ignore # isort: skip # noqa: I202
16
+
17
+
18
+ class ad9081_core(converter, metaclass=ABCMeta):
19
+ """AD9081 high speed MxFE model.
20
+
21
+ This model supports both direct clock configurations and on-board
22
+ generation
23
+
24
+ Clocking: AD9081 can internally generate or leverage external clocks. The
25
+ high speed clock within the system is referred to as the DAC clock and
26
+ the ADC clock will be a divided down version of the clock:
27
+ adc_clock == dac_clock / L, where L = 1,2,3,4
28
+
29
+
30
+ For internal generation, the DAC clock is generated through an integer PLL
31
+ through the following relation:
32
+ dac_clock == ((m_vco * n_vco) / R * ref_clock) / D
33
+
34
+ For external clocks, the clock must be provided at the DAC clock rate
35
+
36
+ Once we have the DAC clock the data rates can be directly evaluated into
37
+ each JESD framer:
38
+
39
+ rx_baseband_sample_rate = (dac_clock / L) / datapath_decimation
40
+ tx_baseband_sample_rate = dac_clock / datapath_interpolation
41
+
42
+ """
43
+
44
+ device_clock_available = None # FIXME
45
+ device_clock_ranges = None # FIXME
46
+
47
+ model: Union[GEKKO, CpoModel] = None
48
+
49
+ name = "AD9081"
50
+
51
+ # Integrated PLL constants
52
+ l_available = [1, 2, 3, 4]
53
+ l = 1 # pylint: disable=E741
54
+ m_vco_available = [5, 7, 8, 11] # 8 is nominal
55
+ m_vco = 8
56
+ n_vco_available = [*range(2, 50 + 1)]
57
+ n_vco = 2
58
+ r_available = [1, 2, 3, 4]
59
+ r = 1
60
+ d_available = [1, 2, 3, 4]
61
+ d = 1
62
+ # Integrated PLL limits
63
+ pfd_min = 25e6
64
+ pfd_max = 750e6
65
+ vco_min = 6e9
66
+ vco_max = 12e9
67
+
68
+ # JESD parameters
69
+ available_jesd_modes = ["jesd204b", "jesd204c"]
70
+ M_available = [1, 2, 3, 4, 6, 8, 12, 16]
71
+ L_available = [1, 2, 3, 4, 6, 8]
72
+ N_available = [12, 16]
73
+ Np_available = [12, 16, 24]
74
+ F_available = [1, 2, 3, 4, 6, 8, 12, 16, 24, 32]
75
+ S_available = [1, 2, 4, 8]
76
+ # FIXME
77
+ # K_available = [4, 8, 12, 16, 20, 24, 28, 32]
78
+ K_available = [16, 32, 64, 128, 256]
79
+ CS_available = [0, 1, 2, 3]
80
+ CF_available = [0]
81
+ # FIXME
82
+
83
+ # Clocking constraints
84
+ clocking_option_available = ["integrated_pll", "direct"]
85
+ _clocking_option = "integrated_pll"
86
+ bit_clock_min_available = {"jesd204b": 1.5e9, "jesd204c": 6e9}
87
+ bit_clock_max_available = {"jesd204b": 15.5e9, "jesd204c": 24.75e9}
88
+
89
+ config = {} # type: ignore
90
+
91
+ device_clock_max = 12e9
92
+ _lmfc_divisor_sysref_available = [
93
+ 1,
94
+ 2,
95
+ 4,
96
+ 8,
97
+ 16,
98
+ 32,
99
+ 64,
100
+ 128,
101
+ 256,
102
+ 512,
103
+ 1024,
104
+ ]
105
+
106
+ def _check_valid_internal_configuration(self) -> None:
107
+ # FIXME
108
+ pass
109
+
110
+ def get_config(self, solution: CpoSolveResult = None) -> Dict:
111
+ """Extract configurations from solver results.
112
+
113
+ Collect internal converter configuration and output clock definitions
114
+ leading to connected devices (clock chips, FPGAs)
115
+
116
+ Args:
117
+ solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver
118
+
119
+ Returns:
120
+ Dict: Dictionary of clocking rates and dividers for configuration
121
+ """
122
+ if solution:
123
+ self.solution = solution
124
+ if self.clocking_option == "integrated_pll":
125
+ pll_config: Dict = {
126
+ "m_vco": self._get_val(self.config["ad9081_m_vco"]),
127
+ "n_vco": self._get_val(self.config["ad9081_n_vco"]),
128
+ "r": self._get_val(self.config["ad9081_r"]),
129
+ "d": self._get_val(self.config["ad9081_d"]),
130
+ }
131
+ if "serdes_pll_div" in self.config:
132
+ pll_config["serdes_pll_div"] = self._get_val(
133
+ self.config["serdes_pll_div"]
134
+ )
135
+ return {
136
+ "clocking_option": self.clocking_option,
137
+ "pll_config": pll_config,
138
+ }
139
+ else:
140
+ if "serdes_pll_div" in self.config:
141
+ return {
142
+ "serdes_pll_div": self._get_val(self.config["serdes_pll_div"]),
143
+ "clocking_option": self.clocking_option,
144
+ }
145
+ return {"clocking_option": self.clocking_option}
146
+
147
+ def get_required_clock_names(self) -> List[str]:
148
+ """Get list of strings of names of requested clocks.
149
+
150
+ This list of names is for the clocks defined by get_required_clocks
151
+
152
+ Returns:
153
+ List[str]: List of strings of clock names in order
154
+ """
155
+ # clk = (
156
+ # "ad9081_dac_clock" if self.clocking_option == "direct" else "ad9081_pll_ref"
157
+ # )
158
+ clk = f"{self.name.lower()}_ref_clk"
159
+ sysref = f"{self.name.lower()}_sysref"
160
+ return [clk, sysref]
161
+
162
+ @property
163
+ @abstractmethod
164
+ def _converter_clock_config(self) -> None:
165
+ """Define source clocking relation based on ADC, DAC, or both.
166
+
167
+ Raises:
168
+ NotImplementedError: Method not implemented
169
+ """
170
+ raise NotImplementedError
171
+
172
+ def _pll_config(self, rxtx: bool = False) -> Dict:
173
+ self._converter_clock_config() # type: ignore
174
+
175
+ self.config["ad9081_m_vco"] = self._convert_input([5, 7, 8, 11], "ad9081_m_vco")
176
+ self.config["ad9081_n_vco"] = self._convert_input(
177
+ [*range(2, 51)], "ad9081_n_vco"
178
+ )
179
+ self.config["ad9081_r"] = self._convert_input([1, 2, 3, 4], "ad9081_r")
180
+ self.config["ad9081_d"] = self._convert_input([1, 2, 3, 4], "ad9081_d")
181
+
182
+ self.config["ad9081_ref_clk"] = self._add_intermediate(
183
+ self.config["converter_clk"]
184
+ * self.config["ad9081_d"]
185
+ * self.config["ad9081_r"]
186
+ / (self.config["ad9081_m_vco"] * self.config["ad9081_n_vco"])
187
+ )
188
+ # if self.solver == "gekko":
189
+ # self.config["ref_clk"] = self.model.Var(
190
+ # integer=True,
191
+ # lb=1e6,
192
+ # ub=self.device_clock_max,
193
+ # value=self.device_clock_max,
194
+ # )
195
+ # elif self.solver == "CPLEX":
196
+ # # self.config["ref_clk"] = integer_var(
197
+ # # int(1e6), int(self.device_clock_max), "ref_clk"
198
+ # # )
199
+ # self.config["ref_clk"] = (
200
+ # self.config["converter_clk"]
201
+ # * self.config["d"]
202
+ # * self.config["r"]
203
+ # / (self.config["m_vco"] * self.config["n_vco"])
204
+ # )
205
+ # else:
206
+ # raise Exception("Unknown solver")
207
+
208
+ self.config["ad9081_vco"] = self._add_intermediate(
209
+ self.config["ad9081_ref_clk"]
210
+ * self.config["ad9081_m_vco"]
211
+ * self.config["ad9081_n_vco"]
212
+ / self.config["ad9081_r"],
213
+ )
214
+
215
+ # if self.solver == "gekko":
216
+ # self.config["vco"] = self.model.Intermediate(
217
+ # self.config["ref_clk"]
218
+ # * self.config["m_vco"]
219
+ # * self.config["n_vco"]
220
+ # / self.config["r"],
221
+ # )
222
+ # elif self.solver == "CPLEX":
223
+ # self.config["vco"] = (
224
+ # self.config["ref_clk"]
225
+ # * self.config["m_vco"]
226
+ # * self.config["n_vco"]
227
+ # / self.config["r"]
228
+ # )
229
+ # else:
230
+ # raise Exception("Unknown solver: %s" % self.solver)
231
+
232
+ self._add_equation(
233
+ [
234
+ self.config["ad9081_vco"] >= self.vco_min,
235
+ self.config["ad9081_vco"] <= self.vco_max,
236
+ self.config["ad9081_ref_clk"] / self.config["ad9081_r"] <= self.pfd_max,
237
+ self.config["ad9081_ref_clk"] / self.config["ad9081_r"] >= self.pfd_min,
238
+ self.config["ad9081_ref_clk"] >= int(100e6),
239
+ self.config["ad9081_ref_clk"] <= int(2e9),
240
+ # self.config["converter_clk"] <= self.device_clock_max,
241
+ self.config["converter_clk"]
242
+ >= (
243
+ self.converter_clock_min
244
+ if not rxtx
245
+ else self.dac.converter_clock_min # type: ignore
246
+ ),
247
+ self.config["converter_clk"]
248
+ <= (
249
+ self.converter_clock_max
250
+ if not rxtx
251
+ else self.dac.converter_clock_max # type: ignore
252
+ ),
253
+ ]
254
+ )
255
+
256
+ # Make ref_clk an integer since API requires it
257
+ if self.solver == "CPLEX":
258
+ self.config["integer_ad9081_ref_clk"] = integer_var(
259
+ min=int(100e6), max=int(2e9), name="integer_ad9081_ref_clk"
260
+ )
261
+ self._add_equation(
262
+ [self.config["integer_ad9081_ref_clk"] == self.config["ad9081_ref_clk"]]
263
+ )
264
+ else:
265
+ raise Exception("Only CPLEX solver supported")
266
+
267
+ return self.config["ad9081_ref_clk"]
268
+
269
+ def get_required_clocks(self) -> List:
270
+ """Generate list required clocks.
271
+
272
+ For AD9081 this will contain [converter clock, sysref requirement SOS]
273
+
274
+ Returns:
275
+ List: List of solver variables, equations, and constants
276
+ """
277
+ # SYSREF
278
+ self.config = {}
279
+ self.config["lmfc_divisor_sysref"] = self._convert_input(
280
+ self._lmfc_divisor_sysref_available, "lmfc_divisor_sysref"
281
+ )
282
+
283
+ self.config["sysref"] = self._add_intermediate(
284
+ self.multiframe_clock / self.config["lmfc_divisor_sysref"]
285
+ )
286
+
287
+ # Device Clocking
288
+ if self.clocking_option == "direct":
289
+ # raise Exception("Not implemented yet")
290
+ clk = self.sample_clock * self.datapath_decimation
291
+ else:
292
+ clk = self._pll_config() # type: ignore
293
+
294
+ # Objectives
295
+ # self.model.Obj(self.config["sysref"]) # This breaks many searches
296
+ # self.model.Obj(-1*self.config["lmfc_divisor_sysref"])
297
+
298
+ return [clk, self.config["sysref"]]
299
+
300
+
301
+ class ad9081_rx(adc, ad9081_core):
302
+ """AD9081 Receive model."""
303
+
304
+ name = "AD9081_RX"
305
+ converter_type = "adc"
306
+
307
+ converter_clock_min = 1.45e9
308
+ converter_clock_max = 4e9
309
+
310
+ sample_clock_min = 312.5e6 / 16
311
+ sample_clock_max = 4e9
312
+
313
+ quick_configuration_modes = _load_rx_config_modes()
314
+
315
+ datapath = ad9081_dp_rx()
316
+ decimation_available = [
317
+ 1,
318
+ 2,
319
+ 3,
320
+ 4,
321
+ 6,
322
+ 8,
323
+ 9,
324
+ 12,
325
+ 16,
326
+ 18,
327
+ 24,
328
+ 32,
329
+ 36,
330
+ 48,
331
+ 64,
332
+ 72,
333
+ 96,
334
+ 144,
335
+ "auto",
336
+ ]
337
+
338
+ @property
339
+ def decimation(self) -> int:
340
+ """Decimation factor. This is the product of the coarse and fine decimation."""
341
+ return self.datapath.decimation_overall
342
+
343
+ @decimation.setter
344
+ def decimation(self, value: int) -> None:
345
+ raise Exception(
346
+ "Decimation is not writable and should be set by the properties\n"
347
+ + " datapath.cddc_decimations and datapath.fddc_decimations"
348
+ )
349
+
350
+ _adc_lmfc_divisor_sysref = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
351
+
352
+ def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401
353
+ """Initialize AD9081 clocking model for RX.
354
+
355
+ This is a common class used to handle RX constraints
356
+ together.
357
+
358
+ Args:
359
+ *args (Any): Pass through arguments
360
+ **kwargs (Any): Pass through keyword arguments
361
+ """
362
+ self.set_quick_configuration_mode("3.01", "jesd204b")
363
+ self.datapath.cddc_decimations = [2] * 4
364
+ self.datapath.fddc_decimations = [4] * 8
365
+ self.datapath.fddc_enabled = [True] * 8
366
+ assert self.decimation == 8
367
+ super().__init__(*args, **kwargs)
368
+
369
+ def _converter_clock_config(self) -> None:
370
+ """RX specific configuration of internal PLL config.
371
+
372
+ This method will update the config struct to include
373
+ the RX clocking constraints
374
+ """
375
+ adc_clk = self.decimation * self.sample_clock
376
+ self.config["l"] = self._convert_input([1, 2, 3, 4], "l")
377
+ self.config["adc_clk"] = self._convert_input(adc_clk)
378
+ self.config["converter_clk"] = self._add_intermediate(
379
+ self.config["adc_clk"] * self.config["l"]
380
+ )
381
+
382
+ def _check_valid_internal_configuration(self) -> None:
383
+ mode = self._check_valid_jesd_mode()
384
+ cfg = self.quick_configuration_modes[self.jesd_class][mode]
385
+
386
+ # Check decimation is valid
387
+ if isinstance(self.decimation, int) or isinstance(self.decimation, float):
388
+ found = False
389
+ combos = []
390
+ for dec in cfg["decimations"]:
391
+ found = found or dec["coarse"] * dec["fine"] == self.decimation
392
+ combos.append(f'{dec["coarse"]}/{dec["fine"]}')
393
+ assert found, (
394
+ f"Decimation {self.decimation} not valid for current JESD mode\n"
395
+ + f"Valid CDDC/FDDC {combos}"
396
+ )
397
+ elif self.decimation == "auto":
398
+ for dec in cfg["decimations"]:
399
+ dec = dec["coarse"] * dec["fine"]
400
+ # Check
401
+ cc = dec * self.sample_clock
402
+ # if dec == 64:
403
+ # print("dec", dec, cc, cfg["coarse"], cfg["fine"])
404
+ if cc <= self.converter_clock_max and cc >= self.converter_clock_min:
405
+ self.decimation = dec
406
+ print("Decimation automatically determined:", dec)
407
+ return
408
+ raise Exception("No valid decimation found")
409
+ else:
410
+ raise Exception("Decimation not valid")
411
+
412
+
413
+ class ad9081_tx(dac, ad9081_core):
414
+ """AD9081 Transmit model."""
415
+
416
+ name = "AD9081_TX"
417
+ converter_type = "dac"
418
+
419
+ converter_clock_min = 2.9e9
420
+ converter_clock_max = 12e9
421
+
422
+ sample_clock_min = 2.9e9 / (6 * 24) # with max interpolation
423
+ sample_clock_max = 12e9
424
+
425
+ quick_configuration_modes = _load_tx_config_modes()
426
+
427
+ datapath = ad9081_dp_tx()
428
+ interpolation_available = [
429
+ 1,
430
+ 2,
431
+ 3,
432
+ 4,
433
+ 6,
434
+ 8,
435
+ 9,
436
+ 12,
437
+ 16,
438
+ 18,
439
+ 24,
440
+ 32,
441
+ 36,
442
+ 48,
443
+ 64,
444
+ 72,
445
+ 96,
446
+ 144,
447
+ ]
448
+
449
+ @property
450
+ def interpolation(self) -> int:
451
+ """Interpolation factor.
452
+
453
+ This is the product of the coarse and fine interpolation.
454
+
455
+ Returns:
456
+ int: Interpolation factor
457
+ """
458
+ return self.datapath.interpolation_overall
459
+
460
+ @interpolation.setter
461
+ def interpolation(self, value: int) -> None:
462
+ raise Exception(
463
+ "Interpolation is not writable and should be set by the properties\n"
464
+ + " datapath.cduc_interpolation and datapath.fduc_interpolation"
465
+ )
466
+
467
+ _dac_lmfc_divisor_sysref = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
468
+
469
+ def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401
470
+ """Initialize AD9081 clocking model for TX.
471
+
472
+ This is a common class used to handle TX constraints
473
+ together.
474
+
475
+ Args:
476
+ *args (Any): Pass through arguments
477
+ **kwargs (Any): Pass through keyword arguments
478
+ """
479
+ self.set_quick_configuration_mode("0", "jesd204c")
480
+ self.datapath.cduc_interpolation = 6
481
+ self.datapath.fduc_interpolation = 4
482
+ self.datapath.fduc_enabled = [True] * 8
483
+ super().__init__(*args, **kwargs)
484
+
485
+ def _converter_clock_config(self) -> None:
486
+ """TX specific configuration of internall PLL config.
487
+
488
+ This method will update the config struct to include
489
+ the TX clocking constraints
490
+ """
491
+ dac_clk = self.interpolation * self.sample_clock
492
+ self.config["dac_clk"] = self._convert_input(dac_clk)
493
+ self.config["converter_clk"] = self._add_intermediate(self.config["dac_clk"])
494
+
495
+
496
+ class ad9081(ad9081_core):
497
+ """AD9081 combined transmit and receive model."""
498
+
499
+ converter_clock_min = ad9081_rx.converter_clock_min
500
+ converter_clock_max = ad9081_rx.converter_clock_max
501
+ quick_configuration_modes: Dict[str, Any] = {}
502
+ _nested = ["adc", "dac"]
503
+ converter_type = "adc_dac"
504
+
505
+ def __init__(
506
+ self, model: Union[GEKKO, CpoModel] = None, solver: str = None
507
+ ) -> None:
508
+ """Initialize AD9081 clocking model for TX and RX.
509
+
510
+ This is a common class used to handle TX and RX constraints
511
+ together.
512
+
513
+ Args:
514
+ model (GEKKO,CpoModel): Solver model
515
+ solver (str): Solver name (gekko or CPLEX)
516
+ """
517
+ if solver:
518
+ self.solver = solver
519
+ self.adc = ad9081_rx(model, solver=self.solver)
520
+ self.dac = ad9081_tx(model, solver=self.solver)
521
+ self.model = model
522
+
523
+ def validate_config(self) -> None:
524
+ """Validate device configurations including JESD and clocks of both ADC and DAC.
525
+
526
+ This check only is for static configuration that does not include
527
+ variables which are solved.
528
+ """
529
+ self.adc.validate_config()
530
+ self.dac.validate_config()
531
+
532
+ def _get_converters(self) -> List[Union[converter, converter]]:
533
+ return [self.adc, self.dac]
534
+
535
+ def get_required_clock_names(self) -> List[str]:
536
+ """Get list of strings of names of requested clocks.
537
+
538
+ This list of names is for the clocks defined by get_required_clocks
539
+
540
+ Returns:
541
+ List[str]: List of strings of clock names in order
542
+ """
543
+ clk = (
544
+ "ad9081_dac_clock"
545
+ if self.adc.clocking_option == "direct"
546
+ else "ad9081_pll_ref"
547
+ )
548
+ return [clk, "ad9081_adc_sysref", "ad9081_dac_sysref"]
549
+
550
+ def _converter_clock_config(self) -> None:
551
+ adc_clk = self.adc.decimation * self.adc.sample_clock
552
+ dac_clk = self.dac.interpolation * self.dac.sample_clock
553
+ l = dac_clk / adc_clk
554
+ if np.abs(l - round(l)) > 1e-6:
555
+ raise Exception(f"Sample clock ratio is not integer {adc_clk} {dac_clk}")
556
+ else:
557
+ l = int(round(l))
558
+ if l not in self.adc.l_available:
559
+ raise Exception(
560
+ f"ADC clock must be DAC clock/L where L={self.adc.l_available}."
561
+ + f" Got {l} ({dac_clk}/{adc_clk})"
562
+ )
563
+
564
+ self.config["dac_clk"] = self._convert_input(dac_clk)
565
+ self.config["adc_clk"] = self._convert_input(adc_clk)
566
+ self.config["converter_clk"] = self._add_intermediate(self.config["dac_clk"])
567
+
568
+ # Add single PLL constraint
569
+ # JESD204B/C transmitter is a power of 2 divisor of the lane rate of
570
+ # the JESD204B/C receiver
571
+ if self.solver == "gekko":
572
+ raise Exception("Not implemented for GEKKO")
573
+ elif self.solver == "CPLEX":
574
+ divs = [int(2**d) for d in range(16)]
575
+ self.config["serdes_pll_div"] = self._convert_input(
576
+ divs, "serdes_pll_div", default=1
577
+ )
578
+ else:
579
+ raise Exception(f"Unknown solver {self.solver}")
580
+
581
+ self._add_equation(
582
+ [self.config["serdes_pll_div"] * self.adc.bit_clock == self.dac.bit_clock]
583
+ )
584
+
585
+ def get_required_clocks(self) -> List:
586
+ """Generate list required clocks.
587
+
588
+ For AD9081 this will contain [converter clock, sysref requirement SOS]
589
+
590
+ Returns:
591
+ List: List of solver variables, equations, and constants
592
+ """
593
+ # SYSREF
594
+ self.config = {}
595
+ self.config["adc_lmfc_divisor_sysref"] = self._convert_input(
596
+ self.adc._adc_lmfc_divisor_sysref, "adc_lmfc_divisor_sysref"
597
+ )
598
+ self.config["dac_lmfc_divisor_sysref"] = self._convert_input(
599
+ self.dac._dac_lmfc_divisor_sysref, "dac_lmfc_divisor_sysref"
600
+ )
601
+
602
+ self.config["sysref_adc"] = self._add_intermediate(
603
+ self.adc.multiframe_clock / self.config["adc_lmfc_divisor_sysref"]
604
+ )
605
+ self.config["sysref_dac"] = self._add_intermediate(
606
+ self.dac.multiframe_clock / self.config["dac_lmfc_divisor_sysref"]
607
+ )
608
+
609
+ # Device Clocking
610
+ if self.clocking_option == "direct":
611
+ # raise Exception("Not implemented yet")
612
+ # adc_clk = self.sample_clock * self.datapath_decimation
613
+ # clk = dac_clk
614
+ clk = self.dac.interpolation * self.dac.sample_clock
615
+ else:
616
+ clk = self._pll_config(rxtx=True)
617
+
618
+ # Objectives
619
+ # self.model.Obj(self.config["sysref"]) # This breaks many searches
620
+ # self.model.Obj(-1*self.config["lmfc_divisor_sysref"])
621
+
622
+ return [clk, self.config["sysref_adc"], self.config["sysref_dac"]]
623
+
624
+
625
+ class ad9082_rx(ad9081_rx):
626
+ """AD9082 MxFE RX Clocking Model."""
627
+
628
+ converter_clock_max = 6e9
629
+
630
+
631
+ class ad9082_tx(ad9081_tx):
632
+ """AD9082 MxFE TX Clocking Model."""
633
+
634
+ pass
635
+
636
+
637
+ class ad9082(ad9081):
638
+ """AD9081 combined transmit and receive model."""
639
+
640
+ converter_clock_min = ad9082_rx.converter_clock_min
641
+ converter_clock_max = ad9082_rx.converter_clock_max
642
+ quick_configuration_modes: Dict[str, Any] = {}
643
+ _nested = ["adc", "dac"]
644
+
645
+ def __init__(
646
+ self, model: Union[GEKKO, CpoModel] = None, solver: str = None
647
+ ) -> None:
648
+ """Initialize AD9081 clocking model for TX and RX.
649
+
650
+ This is a common class used to handle TX and RX constraints
651
+ together.
652
+
653
+ Args:
654
+ model (GEKKO,CpoModel): Solver model
655
+ solver (str): Solver name (gekko or CPLEX)
656
+ """
657
+ if solver:
658
+ self.solver = solver
659
+ self.adc = ad9082_rx(model, solver=self.solver)
660
+ self.dac = ad9082_tx(model, solver=self.solver)
661
+ self.model = model
662
+
663
+ def _get_converters(self) -> List[Union[converter, converter]]:
664
+ return [self.adc, self.dac]
665
+
666
+ def get_required_clock_names(self) -> List[str]:
667
+ """Get list of strings of names of requested clocks.
668
+
669
+ This list of names is for the clocks defined by get_required_clocks
670
+
671
+ Returns:
672
+ List[str]: List of strings of clock names in order
673
+ """
674
+ clk = (
675
+ "ad9082_dac_clock"
676
+ if self.adc.clocking_option == "direct"
677
+ else "ad9082_pll_ref"
678
+ )
679
+ return [clk, "ad9082_adc_sysref", "ad9082_dac_sysref"]