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.
- adijif/__init__.py +32 -0
- adijif/adijif.py +1 -0
- adijif/cli.py +21 -0
- adijif/clocks/__init__.py +10 -0
- adijif/clocks/ad9523.py +321 -0
- adijif/clocks/ad9523_1_bf.py +91 -0
- adijif/clocks/ad9528.py +444 -0
- adijif/clocks/ad9528_bf.py +70 -0
- adijif/clocks/ad9545.py +553 -0
- adijif/clocks/clock.py +153 -0
- adijif/clocks/hmc7044.py +558 -0
- adijif/clocks/hmc7044_bf.py +68 -0
- adijif/clocks/ltc6952.py +624 -0
- adijif/clocks/ltc6952_bf.py +67 -0
- adijif/clocks/ltc6953.py +509 -0
- adijif/common.py +70 -0
- adijif/converters/__init__.py +3 -0
- adijif/converters/ad9081.py +679 -0
- adijif/converters/ad9081_dp.py +206 -0
- adijif/converters/ad9081_util.py +124 -0
- adijif/converters/ad9084.py +588 -0
- adijif/converters/ad9084_dp.py +111 -0
- adijif/converters/ad9084_draw.py +203 -0
- adijif/converters/ad9084_util.py +365 -0
- adijif/converters/ad9144.py +316 -0
- adijif/converters/ad9144_bf.py +44 -0
- adijif/converters/ad9680.py +201 -0
- adijif/converters/ad9680_bf.py +43 -0
- adijif/converters/ad9680_draw.py +184 -0
- adijif/converters/adc.py +83 -0
- adijif/converters/adrv9009.py +426 -0
- adijif/converters/adrv9009_bf.py +43 -0
- adijif/converters/adrv9009_util.py +89 -0
- adijif/converters/converter.py +399 -0
- adijif/converters/dac.py +85 -0
- adijif/converters/resources/AD9084_JTX_JRX.xlsx +0 -0
- adijif/converters/resources/ad9081_JRx_204B.csv +180 -0
- adijif/converters/resources/ad9081_JRx_204C.csv +411 -0
- adijif/converters/resources/ad9081_JTx_204B.csv +1488 -0
- adijif/converters/resources/ad9081_JTx_204C.csv +1064 -0
- adijif/converters/resources/full_rx_mode_table_ad9081.csv +1904 -0
- adijif/converters/resources/full_tx_mode_table_ad9081.csv +994 -0
- adijif/d2/__init__.py +26 -0
- adijif/d2/d2lib.h +81 -0
- adijif/draw.py +498 -0
- adijif/fpgas/__init__.py +1 -0
- adijif/fpgas/fpga.py +64 -0
- adijif/fpgas/xilinx/__init__.py +1143 -0
- adijif/fpgas/xilinx/bf.py +101 -0
- adijif/fpgas/xilinx/pll.py +232 -0
- adijif/fpgas/xilinx/sevenseries.py +531 -0
- adijif/fpgas/xilinx/ultrascaleplus.py +485 -0
- adijif/fpgas/xilinx/xilinx_draw.py +516 -0
- adijif/gekko_trans.py +295 -0
- adijif/jesd.py +760 -0
- adijif/plls/__init__.py +3 -0
- adijif/plls/adf4030.py +259 -0
- adijif/plls/adf4371.py +419 -0
- adijif/plls/adf4382.py +581 -0
- adijif/plls/pll.py +103 -0
- adijif/solvers.py +54 -0
- adijif/sys/__init__.py +1 -0
- adijif/sys/s_plls.py +185 -0
- adijif/system.py +567 -0
- adijif/system_draw.py +65 -0
- adijif/types.py +151 -0
- adijif/utils.py +191 -0
- pyadi_jif-0.1.0.dist-info/METADATA +62 -0
- pyadi_jif-0.1.0.dist-info/RECORD +73 -0
- pyadi_jif-0.1.0.dist-info/WHEEL +6 -0
- pyadi_jif-0.1.0.dist-info/licenses/AUTHORS.rst +13 -0
- pyadi_jif-0.1.0.dist-info/licenses/LICENSE +277 -0
- 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"]
|