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,43 @@
1
+ import numpy as np
2
+
3
+ from adijif.converters.converter import converter
4
+
5
+
6
+ class adrv9009_bf(converter):
7
+ """Brute force methods for calculating clocks
8
+
9
+ These are currently meant for debug to compare against
10
+ the solver solutions
11
+ """
12
+
13
+ def device_clock_available(self):
14
+ """Generate list of possible device clocks"""
15
+ aicd = sorted(self.available_input_clock_dividers)
16
+
17
+ dev_clocks = []
18
+ for div in aicd:
19
+ in_clock = self.sample_clock * self.datapath_decimation * div
20
+ if in_clock <= self.max_input_clock:
21
+ dev_clocks.append(in_clock)
22
+ if not dev_clocks:
23
+ raise Exception(
24
+ "No device clocks possible in current config. Sample rate too high"
25
+ )
26
+ return dev_clocks
27
+
28
+ def device_clock_ranges(self):
29
+ """Generate min and max values for device clock"""
30
+
31
+ clks = self.device_clock_available()
32
+ return np.min(clks), np.max(clks)
33
+
34
+ def sysref_clock_ranges(self):
35
+ """sysref must be multiple of LMFC"""
36
+ lmfc = self.multiframe_clock
37
+ return lmfc / 2048, lmfc / 2
38
+
39
+ def sysref_met(self, sysref_clock, sample_clock):
40
+ if sysref_clock % self.multiframe_clock != 0:
41
+ raise Exception("SYSREF not a multiple of LMFC")
42
+ if (self.multiframe_clock / sysref_clock) < 2 * self.input_clock_divider:
43
+ raise Exception("SYSREF not a multiple of LMFC > 1")
@@ -0,0 +1,89 @@
1
+ """ADRV9009 Utility Functions."""
2
+
3
+ from typing import Dict, Union
4
+
5
+ from adijif.converters.converter import converter
6
+
7
+
8
+ def _convert_to_config(
9
+ L: Union[int, float],
10
+ M: Union[int, float],
11
+ Np: Union[int, float],
12
+ S: Union[int, float] = 1,
13
+ ) -> Dict:
14
+ return {
15
+ "L": L,
16
+ "M": M,
17
+ "F": Np / 8 * M * S / L,
18
+ "S": S,
19
+ "HD": (
20
+ 1
21
+ if (M == 1 and L == 2 and S == 1)
22
+ or (M == 2 and L == 4 and S == 1)
23
+ or (M == 1 and L == 4 and S == 2)
24
+ else 0
25
+ ),
26
+ "Np": Np,
27
+ "N": Np,
28
+ "CS": 0,
29
+ "CF": 0,
30
+ "K": 32, # THIS IS A SUGGESTED VALUE
31
+ }
32
+
33
+
34
+ quick_configuration_modes_tx = {
35
+ # M = 1
36
+ str(0): _convert_to_config(M=1, L=1, Np=16),
37
+ str(1): _convert_to_config(M=1, L=2, Np=16),
38
+ # M = 2
39
+ str(2): _convert_to_config(M=2, L=1, Np=16),
40
+ str(3): _convert_to_config(M=2, L=2, Np=16),
41
+ str(4): _convert_to_config(M=2, L=4, Np=16),
42
+ # M = 4
43
+ str(5): _convert_to_config(M=4, L=1, Np=16),
44
+ str(6): _convert_to_config(M=4, L=2, Np=16),
45
+ str(7): _convert_to_config(M=4, L=4, Np=16),
46
+ str(8): _convert_to_config(M=4, L=2, Np=12),
47
+ }
48
+
49
+
50
+ quick_configuration_modes_rx = {
51
+ # M = 1
52
+ str(0): _convert_to_config(M=1, L=1, S=1, Np=16),
53
+ str(1): _convert_to_config(M=1, L=1, S=2, Np=16),
54
+ str(2): _convert_to_config(M=1, L=1, S=4, Np=16),
55
+ str(3): _convert_to_config(M=1, L=2, S=1, Np=16),
56
+ str(4): _convert_to_config(M=1, L=2, S=2, Np=16),
57
+ str(5): _convert_to_config(M=1, L=2, S=4, Np=16),
58
+ str(6): _convert_to_config(M=1, L=4, S=2, Np=16),
59
+ str(7): _convert_to_config(M=1, L=4, S=4, Np=16),
60
+ # M = 2
61
+ str(8): _convert_to_config(M=2, L=1, S=1, Np=16),
62
+ str(9): _convert_to_config(M=2, L=1, S=2, Np=16),
63
+ str(10): _convert_to_config(M=2, L=2, S=1, Np=16),
64
+ str(11): _convert_to_config(M=2, L=2, S=2, Np=16),
65
+ str(12): _convert_to_config(M=2, L=2, S=4, Np=16),
66
+ str(13): _convert_to_config(M=2, L=4, S=1, Np=16),
67
+ str(14): _convert_to_config(M=2, L=4, S=2, Np=16),
68
+ str(15): _convert_to_config(M=2, L=4, S=4, Np=16),
69
+ # M = 4
70
+ str(16): _convert_to_config(M=4, L=1, S=1, Np=16),
71
+ str(17): _convert_to_config(M=4, L=2, S=1, Np=16),
72
+ str(18): _convert_to_config(M=4, L=2, S=2, Np=16),
73
+ str(19): _convert_to_config(M=4, L=4, S=1, Np=16),
74
+ str(20): _convert_to_config(M=4, L=4, S=2, Np=16),
75
+ str(21): _convert_to_config(M=4, L=4, S=4, Np=16),
76
+ # Np = 12,24
77
+ str(22): _convert_to_config(M=2, L=1, S=2, Np=12),
78
+ str(23): _convert_to_config(M=4, L=1, S=1, Np=12),
79
+ str(24): _convert_to_config(M=4, L=2, S=1, Np=12),
80
+ str(25): _convert_to_config(M=2, L=2, S=2, Np=24),
81
+ str(26): _convert_to_config(M=4, L=2, S=1, Np=24),
82
+ }
83
+
84
+
85
+ def _extra_jesd_check(dev: converter) -> None:
86
+ FK = dev.F * dev.K
87
+ assert FK <= 256, "F x K must be <= 256"
88
+ assert FK >= 20, "F x K must be >= 20"
89
+ assert FK % 4 == 0, "F x K must be a multiple of 4"
@@ -0,0 +1,399 @@
1
+ """Converter base meta class for all converter clocking models."""
2
+
3
+ from abc import ABCMeta, abstractmethod
4
+ from typing import Dict, List, Union
5
+
6
+ from ..common import core
7
+ from ..draw import Layout, Node
8
+ from ..gekko_trans import gekko_translation
9
+ from ..jesd import jesd
10
+
11
+
12
+ class converter(core, jesd, gekko_translation, metaclass=ABCMeta):
13
+ """Converter base meta class used to enforce all converter classes.
14
+
15
+ This class should be inherited from for all converters: ADCs, DACs,
16
+ and transceivers.
17
+
18
+ """
19
+
20
+ """Nested device with multiple dependent converters. Includes MxFE and
21
+ transceivers"""
22
+ _nested = False
23
+
24
+ """DSP data path of device"""
25
+ datapath = None
26
+
27
+ config: Dict = {}
28
+ _jesd_params_to_skip = [
29
+ "E",
30
+ "global_index",
31
+ ] # Params in table not to set
32
+ _jesd_params_to_skip_check = [
33
+ "DualLink",
34
+ "E",
35
+ "decimations",
36
+ "global_index",
37
+ ]
38
+
39
+ def draw(
40
+ self, clocks: Dict, lo: Layout = None, clock_chip_node: Node = None
41
+ ) -> str:
42
+ """Generic Draw converter model.
43
+
44
+ Args:
45
+ clocks (Dict): Clocking configuration
46
+ lo (Layout): Layout object to draw on
47
+ clock_chip_node (Node): Clock chip node to add to. Defaults to None.
48
+
49
+ Returns:
50
+ str: Path to image file
51
+
52
+ Raises:
53
+ Exception: If no solution is saved
54
+ """
55
+ system_draw = lo is not None
56
+ name = self.name.lower()
57
+
58
+ if not system_draw:
59
+ lo = Layout(f"{name} Example")
60
+ else:
61
+ assert isinstance(lo, Layout), "lo must be a Layout object"
62
+
63
+ ic_node = Node(self.name)
64
+ lo.add_node(ic_node)
65
+
66
+ # rate = clocks[f"{name}_ref_clk"]
67
+ # Find key with ending
68
+ ref_clk_name = None
69
+ for key in clocks.keys():
70
+ if key.lower().endswith(f"{name.lower()}_ref_clk"):
71
+ ref_clk_name = key
72
+ break
73
+ if ref_clk_name is None:
74
+ raise Exception(
75
+ f"No clock found for {name}_ref_clk\n.Options: {clocks.keys()}"
76
+ )
77
+
78
+ sysref_clk_name = None
79
+ for key in clocks.keys():
80
+ if key.lower().endswith(f"{name.lower()}_sysref"):
81
+ sysref_clk_name = key
82
+ break
83
+ if sysref_clk_name is None:
84
+ raise Exception(
85
+ f"No clock found for {name}_sysref\n.Options: {clocks.keys()}"
86
+ )
87
+
88
+ if not system_draw:
89
+ ref_in = Node("REF_IN", ntype="input")
90
+ lo.add_node(ref_in)
91
+ else:
92
+ to_node = lo.get_node(ref_clk_name)
93
+ from_node = lo.get_connection(to=to_node.name)
94
+ assert from_node, "No connection found"
95
+ assert isinstance(from_node, list), "Connection must be a list"
96
+ assert len(from_node) == 1, "Only one connection allowed"
97
+ ref_in = from_node[0]["from"]
98
+ # Remove to_node since it is not needed
99
+ lo.remove_node(to_node.name)
100
+
101
+ rate = clocks[ref_clk_name]
102
+
103
+ lo.add_connection({"from": ref_in, "to": ic_node, "rate": rate})
104
+
105
+ # Add framer
106
+ jesd204_framer = Node("JESD204 Framer", ntype="jesd204framer")
107
+ ic_node.add_child(jesd204_framer)
108
+
109
+ # SYSREF
110
+ if not system_draw:
111
+ sysref_in = Node("SYSREF_IN", ntype="input")
112
+ lo.add_connection(
113
+ {
114
+ "from": sysref_in,
115
+ "to": jesd204_framer,
116
+ # "rate": clocks[f"{name}_sysref"],
117
+ "rate": clocks[sysref_clk_name],
118
+ }
119
+ )
120
+ else:
121
+ # to_node = lo.get_node(f"{name}_sysref")
122
+ to_node = lo.get_node(sysref_clk_name)
123
+ from_node = lo.get_connection(to=to_node.name)
124
+ assert from_node, "No connection found"
125
+ assert isinstance(from_node, list), "Connection must be a list"
126
+ assert len(from_node) == 1, "Only one connection allowed"
127
+ sysref_in = from_node[0]["from"]
128
+ lo.remove_node(to_node.name)
129
+
130
+ lo.add_connection(
131
+ {
132
+ "from": sysref_in,
133
+ "to": jesd204_framer,
134
+ # "rate": clocks[f"{name}_sysref"],
135
+ "rate": clocks[sysref_clk_name],
136
+ }
137
+ )
138
+
139
+ # WIP Add remote deframer
140
+ jesd204_deframer = Node("JESD204 Deframer", ntype="deframer")
141
+
142
+ # Add connect for each lane
143
+ for _ in range(self.L):
144
+ lane_rate = self.bit_clock
145
+ lo.add_connection(
146
+ {"from": jesd204_framer, "to": jesd204_deframer, "rate": lane_rate}
147
+ )
148
+
149
+ if not system_draw:
150
+ return lo.draw()
151
+
152
+ def validate_config(self) -> None:
153
+ """Validate device configuration including JESD and clocks.
154
+
155
+ This check only is for static configuration that does not include
156
+ variables which are solved.
157
+ """
158
+ self._check_valid_jesd_mode()
159
+ self._check_valid_internal_configuration() # type: ignore
160
+ self.validate_clocks()
161
+
162
+ def set_quick_configuration_mode(
163
+ self, mode: Union[str, int], jesd_class: str = "jesd204b"
164
+ ) -> None:
165
+ """Set JESD configuration based on preset mode table.
166
+
167
+ Args:
168
+ mode (str): Integer of desired mode.
169
+ jesd_class (str): JESD class to use. Default is jesd204b.
170
+
171
+ Raises:
172
+ Exception: Invalid mode selected
173
+ """
174
+ smode = str(mode)
175
+ if smode not in self.quick_configuration_modes[jesd_class].keys():
176
+ raise Exception(f"Mode {smode} not among configurations for {jesd_class}")
177
+
178
+ if jesd_class not in self.available_jesd_modes:
179
+ raise Exception(f"{jesd_class} not available for {self.name}")
180
+ self.jesd_class = jesd_class
181
+
182
+ cfg = self.quick_configuration_modes[jesd_class][smode]
183
+ for jparam in cfg:
184
+ if jparam in self._jesd_params_to_skip:
185
+ continue
186
+ setattr(self, jparam, cfg[jparam])
187
+
188
+ def _check_valid_jesd_mode(self) -> str:
189
+ """Verify current JESD configuration for part is valid.
190
+
191
+ Raises:
192
+ Exception: Invalid JESD configuration
193
+
194
+ Returns:
195
+ str: Current JESD mode
196
+ """
197
+ # Check to make sure JESD clocks in range
198
+ self._check_jesd_config()
199
+ # Pull current mode
200
+ k = next(iter(self.quick_configuration_modes[self.jesd_class]))
201
+ attrs = self.quick_configuration_modes[self.jesd_class][k].keys()
202
+
203
+ current_config = {}
204
+ for attr in attrs:
205
+ if attr in self._jesd_params_to_skip_check:
206
+ continue
207
+ current_config[attr] = getattr(self, attr)
208
+
209
+ # Check mode in supported modes
210
+ for mode in self.quick_configuration_modes[self.jesd_class].keys():
211
+ cmode = self.quick_configuration_modes[self.jesd_class][mode].copy()
212
+ for k in self._jesd_params_to_skip_check:
213
+ if hasattr(cmode, k) or k in cmode:
214
+ del cmode[k]
215
+ if hasattr(current_config, k) or k in current_config:
216
+ del current_config[k]
217
+ if current_config == cmode:
218
+ return mode
219
+ raise Exception(f"Invalid JESD configuration for {self.name}\n{current_config}")
220
+
221
+ def get_current_jesd_mode_settings(self) -> Dict:
222
+ """Get current JESD mode settings.
223
+
224
+ Returns:
225
+ Dict: Current JESD mode settings
226
+ str: Current JESD mode
227
+ """
228
+ k = next(iter(self.quick_configuration_modes[self.jesd_class]))
229
+ attrs = self.quick_configuration_modes[self.jesd_class][k].keys()
230
+ current_config = {}
231
+ for attr in attrs:
232
+ if attr in self._jesd_params_to_skip_check:
233
+ continue
234
+ current_config[attr] = getattr(self, attr)
235
+ return current_config
236
+
237
+ @property
238
+ @abstractmethod
239
+ def converter_type(self) -> str:
240
+ """Type of converter. ADC or DAC.
241
+
242
+ Returns:
243
+ str: Type of converter
244
+ """
245
+ raise NotImplementedError
246
+
247
+ @property
248
+ @abstractmethod
249
+ def clocking_option_available(self) -> List[str]:
250
+ """Supported clocking modes.
251
+
252
+ Returns:
253
+ List: List of string of clocking modes
254
+ """
255
+ raise NotImplementedError # pragma: no cover
256
+
257
+ _clocking_option = "direct"
258
+
259
+ @property
260
+ def clocking_option(self) -> str:
261
+ """Get clocking mode for device.
262
+
263
+ Returns:
264
+ str: Clocking mode for device (integrated_pll, direct)
265
+ """
266
+ return self._clocking_option
267
+
268
+ @clocking_option.setter
269
+ def clocking_option(self, value: str) -> None:
270
+ """Set clocking mode for device.
271
+
272
+ Args:
273
+ value (str): Clocking mode for device (integrated_pll, direct)
274
+
275
+ Raises:
276
+ Exception: clocking_option not supported by device
277
+ """
278
+ if value not in self.clocking_option_available:
279
+ raise Exception("clocking_option not available for device")
280
+ self._clocking_option = value
281
+
282
+ @property
283
+ @abstractmethod
284
+ def quick_configuration_modes(self) -> Dict:
285
+ """Supported JESD mode table.
286
+
287
+ Returns:
288
+ Dict: Dictionary of supported modes
289
+ """
290
+ raise NotImplementedError # pragma: no cover
291
+
292
+ @property
293
+ @abstractmethod
294
+ def _check_valid_internal_configuration(self) -> None:
295
+ """Check current device mode is valid."""
296
+ raise NotImplementedError # pragma: no cover
297
+
298
+ @property
299
+ @abstractmethod
300
+ def converter_clock_min(self) -> Union[int, float]:
301
+ """Minimum rate of data converter.
302
+
303
+ Returns:
304
+ Union[int, float]: converter min rate
305
+ """
306
+ raise NotImplementedError # pragma: no cover
307
+
308
+ @property
309
+ @abstractmethod
310
+ def converter_clock_max(self) -> Union[int, float]:
311
+ """Maximum rate of data converter.
312
+
313
+ Returns:
314
+ Union[int, float]: converter max rate
315
+ """
316
+ raise NotImplementedError # pragma: no cover
317
+
318
+ @property
319
+ @abstractmethod
320
+ def name(self) -> str:
321
+ """Name of supported by device.
322
+
323
+ Must be a string
324
+
325
+ Raises:
326
+ NotImplementedError: If child classes do not implement method/property
327
+ """
328
+ raise NotImplementedError # pragma: no cover
329
+
330
+ @property
331
+ @abstractmethod
332
+ def device_clock_available(self) -> None:
333
+ """Allowable K settings for device.
334
+
335
+ Must be a list ints
336
+
337
+ Raises:
338
+ NotImplementedError: If child classes do not implement method/property
339
+ """
340
+ raise NotImplementedError # pragma: no cover
341
+
342
+ @property
343
+ @abstractmethod
344
+ def device_clock_ranges(self) -> None:
345
+ """Allowable ranges for device clocks based on config.
346
+
347
+ This is only used for brute-force implementations and not solver based
348
+
349
+ Raises:
350
+ NotImplementedError: If child classes do not implement method/property
351
+ """
352
+ raise NotImplementedError # pragma: no cover
353
+
354
+ @property
355
+ @abstractmethod
356
+ def get_required_clocks(self) -> List:
357
+ """Get list of strings of names of requested clocks.
358
+
359
+ This list of names is for the clocks defined by get_required_clocks
360
+
361
+ Raises:
362
+ NotImplementedError: If child classes do not implement method/property
363
+ """
364
+ raise NotImplementedError # pragma: no cover
365
+
366
+ @property
367
+ @abstractmethod
368
+ def get_required_clock_names(self) -> List[str]:
369
+ """Generate list of required clock names.
370
+
371
+ This is a list of strings
372
+
373
+ Raises:
374
+ NotImplementedError: If child classes do not implement method/property
375
+ """
376
+ raise NotImplementedError # pragma: no cover
377
+
378
+ @property
379
+ @abstractmethod
380
+ def get_config(self) -> Dict:
381
+ """Extract configuration from solver solution.
382
+
383
+ Return dictionary of clocking config for converter
384
+
385
+ Raises:
386
+ NotImplementedError: If child classes do not implement method/property
387
+ """
388
+ raise NotImplementedError # pragma: no cover
389
+
390
+ def _get_converters(self) -> Union["converter", List["converter"]]:
391
+ return self
392
+
393
+ def __str__(self) -> str:
394
+ """Get string description of converter object.
395
+
396
+ Returns:
397
+ str: Description string
398
+ """
399
+ return f"{self.name} data converter model"
@@ -0,0 +1,85 @@
1
+ """Converter base meta class for all converter clocking models."""
2
+
3
+ from abc import ABCMeta, abstractmethod
4
+ from typing import List, Union
5
+
6
+ from adijif.converters.converter import converter
7
+
8
+
9
+ class dac(converter, metaclass=ABCMeta):
10
+ """DAC base meta class used to enforce all DAC classes.
11
+
12
+ This class should be inherited from for all DACs and transceivers.
13
+
14
+ """
15
+
16
+ def _check_valid_internal_configuration(self) -> None:
17
+ """Verify current internal clocking configuration for part is valid.
18
+
19
+ Raises:
20
+ Exception: Invalid clocking configuration
21
+ """
22
+ if self.interpolation * self.sample_clock > self.converter_clock_max:
23
+ raise Exception(
24
+ "DAC rate too fast for configuration {} (max: {})".format(
25
+ self.interpolation * self.sample_clock,
26
+ self.converter_clock_max,
27
+ )
28
+ )
29
+ if self.interpolation * self.sample_clock < self.converter_clock_min:
30
+ raise Exception(
31
+ "DAC rate too slow for configuration {} (min: {})".format(
32
+ self.interpolation * self.sample_clock,
33
+ self.converter_clock_min,
34
+ )
35
+ )
36
+
37
+ @property
38
+ @abstractmethod
39
+ def interpolation_available(self) -> List:
40
+ """Interpolation settings available.
41
+
42
+ Must be a list or None
43
+
44
+ Raises:
45
+ NotImplementedError: If child classes do not implement method/property
46
+ """
47
+ raise NotImplementedError # pragma: no cover
48
+
49
+ _interpolation = 1
50
+
51
+ @property
52
+ def interpolation(self) -> Union[int, float]:
53
+ """Interpolation between DAC and JESD framer. If none device is not an DAC.
54
+
55
+ Generally a multiple of 2
56
+
57
+ Returns:
58
+ int: interpolation value
59
+ """
60
+ return self._interpolation
61
+
62
+ @interpolation.setter
63
+ def interpolation(self, value: int) -> None:
64
+ """Interpolation between DAC and JESD framer. If none device is not an DAC.
65
+
66
+ Args:
67
+ value (int): interpolation
68
+
69
+ Raises:
70
+ Exception: interpolation not an integer or not in range
71
+ """
72
+ if int(value) != value:
73
+ raise Exception("interpolation_available must be an integer")
74
+ if value not in self.interpolation_available:
75
+ raise Exception("interpolation_available not in range for device")
76
+ self._interpolation = value
77
+
78
+ @property
79
+ def converter_clock(self) -> Union[int, float]:
80
+ """Get rate of converter in samples per second.
81
+
82
+ Returns:
83
+ float: converter clock rate in samples per second
84
+ """
85
+ return self.sample_clock * self.interpolation