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
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}")
|