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/__init__.py
ADDED
adijif/plls/adf4030.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""ADF4030 10-Channel Precision Synchronizer."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Union
|
|
4
|
+
|
|
5
|
+
from docplex.cp.solution import CpoSolveResult # type: ignore
|
|
6
|
+
|
|
7
|
+
from adijif.plls.pll import pll
|
|
8
|
+
from adijif.solvers import CpoExpr, GK_Intermediate
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class adf4030(pll):
|
|
12
|
+
"""ADF4030 PLL model.
|
|
13
|
+
|
|
14
|
+
This model currently supports all divider configurations
|
|
15
|
+
|
|
16
|
+
https://www.analog.com/media/en/technical-documentation/data-sheets/adf4030.pdf
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
name = "adf4030"
|
|
20
|
+
|
|
21
|
+
input_freq_min = int(10e6)
|
|
22
|
+
input_freq_max = int(250e6)
|
|
23
|
+
|
|
24
|
+
pfd_freq_min = int(10e6)
|
|
25
|
+
pfd_freq_max = int(20e6)
|
|
26
|
+
|
|
27
|
+
vco_freq_min = int(2.5e9 * 0.95)
|
|
28
|
+
vco_freq_max = int(2.5e9 * 1.05)
|
|
29
|
+
|
|
30
|
+
_r = [*range(1, 31 + 1)]
|
|
31
|
+
r_available = [*range(1, 31 + 1)]
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def r(self) -> Union[int, List[int]]:
|
|
35
|
+
"""Reference divider.
|
|
36
|
+
|
|
37
|
+
Valid values are 1->31
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
int: Current allowable setting
|
|
41
|
+
"""
|
|
42
|
+
return self._r
|
|
43
|
+
|
|
44
|
+
@r.setter
|
|
45
|
+
def r(self, value: Union[int, List[int]]) -> None:
|
|
46
|
+
"""Reference divider.
|
|
47
|
+
|
|
48
|
+
Valid values are 1->31
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
value (int, list[int]): Current allowable setting
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
self._check_in_range(value, self.r_available, "r")
|
|
55
|
+
self._r = value
|
|
56
|
+
|
|
57
|
+
_n = [*range(8, 255 + 1)]
|
|
58
|
+
n_available = [*range(8, 255 + 1)]
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def n(self) -> Union[int, List[int]]:
|
|
62
|
+
"""Feedback divider.
|
|
63
|
+
|
|
64
|
+
Valid values are 8->255
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
int: Current allowable setting
|
|
68
|
+
"""
|
|
69
|
+
return self._n
|
|
70
|
+
|
|
71
|
+
@n.setter
|
|
72
|
+
def n(self, value: Union[int, List[int]]) -> None:
|
|
73
|
+
"""Feedback divider.
|
|
74
|
+
|
|
75
|
+
Valid values are 8->255
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
value (int, list[int]): Current allowable setting
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
self._check_in_range(value, self.n_available, "n")
|
|
82
|
+
self._n = value
|
|
83
|
+
|
|
84
|
+
_o = [*range(10, 4095 + 1)]
|
|
85
|
+
o_available = [*range(10, 4095 + 1)]
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def o(self) -> Union[int, List[int]]:
|
|
89
|
+
"""Output divider.
|
|
90
|
+
|
|
91
|
+
Valid values are 10->4095
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
int: Current allowable setting
|
|
95
|
+
"""
|
|
96
|
+
return self._o
|
|
97
|
+
|
|
98
|
+
@o.setter
|
|
99
|
+
def o(self, value: Union[int, List[int]]) -> None:
|
|
100
|
+
"""Output divider.
|
|
101
|
+
|
|
102
|
+
Valid values are 10->4095
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
value (int, list[int]): Current allowable setting
|
|
106
|
+
|
|
107
|
+
"""
|
|
108
|
+
self._check_in_range(value, self.o_available, "o")
|
|
109
|
+
self._o = value
|
|
110
|
+
|
|
111
|
+
def get_config(self, solution: CpoSolveResult = None) -> Dict:
|
|
112
|
+
"""Extract configurations from solver results.
|
|
113
|
+
|
|
114
|
+
Collect internal clock chip configuration and output clock definitions
|
|
115
|
+
leading to connected devices (converters, FPGAs)
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Dict: Dictionary of clocking rates and dividers for configuration
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
Exception: If solver is not called first
|
|
125
|
+
"""
|
|
126
|
+
if not self._clk_names:
|
|
127
|
+
raise Exception("set_requested_clocks must be called before get_config")
|
|
128
|
+
|
|
129
|
+
if solution:
|
|
130
|
+
self.solution = solution
|
|
131
|
+
|
|
132
|
+
out_dividers = [self._get_val(x) for x in self.config["out_dividers"]]
|
|
133
|
+
|
|
134
|
+
config: Dict = {
|
|
135
|
+
"r": self._get_val(self.config["r"]),
|
|
136
|
+
"n": self._get_val(self.config["n"]),
|
|
137
|
+
"out_dividers": out_dividers,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
vco = self.solution.get_kpis()["vco_adf4030"]
|
|
141
|
+
config["vco"] = vco
|
|
142
|
+
|
|
143
|
+
# Outputs
|
|
144
|
+
output_config = {}
|
|
145
|
+
for i, clk in enumerate(self._clk_names):
|
|
146
|
+
o_val = out_dividers[i]
|
|
147
|
+
output_config[clk] = {
|
|
148
|
+
"rate": vco / o_val,
|
|
149
|
+
"divider": o_val,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
config["output_clocks"] = output_config
|
|
153
|
+
|
|
154
|
+
return config
|
|
155
|
+
|
|
156
|
+
def _setup_solver_constraints(
|
|
157
|
+
self, input_ref: Union[int, float, CpoExpr, GK_Intermediate]
|
|
158
|
+
) -> None:
|
|
159
|
+
"""Apply constraints to solver model.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
input_ref (int, float, CpoExpr, GK_Intermediate): Input reference
|
|
163
|
+
frequency in hertz
|
|
164
|
+
"""
|
|
165
|
+
self.config = {}
|
|
166
|
+
|
|
167
|
+
# if not isinstance(input_ref, (int, float)):
|
|
168
|
+
# self.config["input_ref_set"] = input_ref(self.model) # type: ignore
|
|
169
|
+
# input_ref = self.config["input_ref_set"]["range"]
|
|
170
|
+
self.input_ref = input_ref
|
|
171
|
+
|
|
172
|
+
# PFD
|
|
173
|
+
self.config["r"] = self._convert_input(self.r, name="r_adf4030")
|
|
174
|
+
self.config["n"] = self._convert_input(self.n, name="n_adf4030")
|
|
175
|
+
self.config["o"] = self._convert_input(self.o, name="o_adf4030")
|
|
176
|
+
|
|
177
|
+
self.config["vco"] = self._add_intermediate(
|
|
178
|
+
input_ref * self.config["n"] / self.config["r"]
|
|
179
|
+
)
|
|
180
|
+
self.model.add_kpi(
|
|
181
|
+
input_ref * self.config["n"] / self.config["r"],
|
|
182
|
+
"vco_adf4030",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
self._add_equation(
|
|
186
|
+
[
|
|
187
|
+
input_ref <= self.input_freq_max,
|
|
188
|
+
input_ref >= self.input_freq_min,
|
|
189
|
+
self.config["vco"] <= self.vco_freq_max,
|
|
190
|
+
self.config["vco"] >= self.vco_freq_min,
|
|
191
|
+
input_ref <= self.pfd_freq_max * self.config["r"],
|
|
192
|
+
input_ref >= self.pfd_freq_min * self.config["r"],
|
|
193
|
+
]
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def _setup(self, input_ref: int) -> None:
|
|
197
|
+
if isinstance(input_ref, (float, int)):
|
|
198
|
+
assert (
|
|
199
|
+
self.input_freq_max >= input_ref >= self.input_freq_min
|
|
200
|
+
), "Input frequency out of range"
|
|
201
|
+
|
|
202
|
+
# Setup clock chip internal constraints
|
|
203
|
+
self._setup_solver_constraints(input_ref)
|
|
204
|
+
|
|
205
|
+
self._clk_names = [] # List of clock names to be generated
|
|
206
|
+
self.config["out_dividers"] = []
|
|
207
|
+
|
|
208
|
+
def _get_clock_constraint(
|
|
209
|
+
self, clk_name: str
|
|
210
|
+
) -> Union[int, float, CpoExpr, GK_Intermediate]:
|
|
211
|
+
"""Get abstract clock output.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
clk_name (str): Reference clock name
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
(int or float or CpoExpr or GK_Intermediate): Abstract
|
|
218
|
+
or concrete clock reference
|
|
219
|
+
"""
|
|
220
|
+
od = self._convert_input(self._o, f"o_div_{clk_name}_adf4030")
|
|
221
|
+
|
|
222
|
+
# Update diagram to include new divider
|
|
223
|
+
# d_n = len(self.config["out_dividers"])
|
|
224
|
+
# self._update_diagram({f"o{d_n}": od})
|
|
225
|
+
|
|
226
|
+
self._clk_names.append(clk_name)
|
|
227
|
+
|
|
228
|
+
self.config["out_dividers"].append(od)
|
|
229
|
+
return self.config["vco"] / od
|
|
230
|
+
|
|
231
|
+
def set_requested_clocks(
|
|
232
|
+
self,
|
|
233
|
+
ref_in: Union[int, float, CpoExpr, GK_Intermediate],
|
|
234
|
+
out_freq: Union[int, List[int]],
|
|
235
|
+
clk_names: List[str],
|
|
236
|
+
) -> None:
|
|
237
|
+
"""Define necessary clocks to be generated in model.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
ref_in (int, float, CpoExpr, GK_Intermediate): Reference frequency in hertz
|
|
241
|
+
out_freq (int): list of required clocks to be output
|
|
242
|
+
clk_names (List[str]): list of clock names
|
|
243
|
+
|
|
244
|
+
Raises:
|
|
245
|
+
Exception: If out_freq and clk_names are not the same length
|
|
246
|
+
"""
|
|
247
|
+
if len(out_freq) != len(clk_names):
|
|
248
|
+
raise Exception("out_freq and clk_names must be the same length")
|
|
249
|
+
self._setup(ref_in)
|
|
250
|
+
self._clk_names = clk_names
|
|
251
|
+
|
|
252
|
+
for i, clk in enumerate(clk_names):
|
|
253
|
+
o_div_name = f"o_div_{clk}_adf4030"
|
|
254
|
+
self.config[o_div_name] = self._convert_input(self.o, o_div_name)
|
|
255
|
+
self.config["out_dividers"].append(self.config[o_div_name])
|
|
256
|
+
|
|
257
|
+
self._add_equation(
|
|
258
|
+
self.config[o_div_name] * out_freq[i] == self.config["vco"],
|
|
259
|
+
)
|
adijif/plls/adf4371.py
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
"""ADF4371 Microwave Wideband Synthesizer with Integrated VCO model."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Union
|
|
4
|
+
|
|
5
|
+
from docplex.cp.solution import CpoSolveResult # type: ignore
|
|
6
|
+
|
|
7
|
+
from adijif.plls.pll import pll
|
|
8
|
+
from adijif.solvers import CpoExpr, GK_Intermediate, integer_var, tround
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class adf4371(pll):
|
|
12
|
+
"""ADF4371 PLL model.
|
|
13
|
+
|
|
14
|
+
This model currently supports all divider configurations
|
|
15
|
+
|
|
16
|
+
https://www.analog.com/media/en/technical-documentation/data-sheets/adf4371.pdf
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
name = "adf4371"
|
|
20
|
+
|
|
21
|
+
input_freq_max = int(600e6)
|
|
22
|
+
input_freq_min = int(10e6)
|
|
23
|
+
|
|
24
|
+
pfd_freq_max_frac = int(160e6)
|
|
25
|
+
pfd_freq_max_int = int(250e6)
|
|
26
|
+
|
|
27
|
+
vco_freq_max = int(8e9)
|
|
28
|
+
vco_freq_min = int(4e9)
|
|
29
|
+
|
|
30
|
+
_d = [0, 1]
|
|
31
|
+
d_available = [0, 1]
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def d(self) -> Union[int, List[int]]:
|
|
35
|
+
"""REF-in doubler.
|
|
36
|
+
|
|
37
|
+
Valid values are 1,2
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
int: Current allowable setting
|
|
41
|
+
"""
|
|
42
|
+
return self._d
|
|
43
|
+
|
|
44
|
+
@d.setter
|
|
45
|
+
def d(self, value: Union[int, List[int]]) -> None:
|
|
46
|
+
"""REF-in double.
|
|
47
|
+
|
|
48
|
+
Valid values are 1,2
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
value (int, list[int]): Current allowable setting
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
self._check_in_range(value, self.d_available, "d")
|
|
55
|
+
self._d = value
|
|
56
|
+
|
|
57
|
+
_r = [*range(1, 32 + 1)]
|
|
58
|
+
r_available = [*range(1, 32 + 1)]
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def r(self) -> Union[int, List[int]]:
|
|
62
|
+
"""Reference divider.
|
|
63
|
+
|
|
64
|
+
Valid values are 0->(2^5-1)
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
int: Current allowable setting
|
|
68
|
+
"""
|
|
69
|
+
return self._r
|
|
70
|
+
|
|
71
|
+
@r.setter
|
|
72
|
+
def r(self, value: Union[int, List[int]]) -> None:
|
|
73
|
+
"""Reference divider.
|
|
74
|
+
|
|
75
|
+
Valid values are 1->32
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
value (int, list[int]): Current allowable setting
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
self._check_in_range(value, self.r_available, "r")
|
|
82
|
+
self._r = value
|
|
83
|
+
|
|
84
|
+
_t = [0, 1]
|
|
85
|
+
t_available = [0, 1]
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def t(self) -> Union[int, List[int]]:
|
|
89
|
+
"""Reference divide by 2.
|
|
90
|
+
|
|
91
|
+
Valid values are 0,1
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
int: Current allowable setting
|
|
95
|
+
"""
|
|
96
|
+
return self._t
|
|
97
|
+
|
|
98
|
+
@t.setter
|
|
99
|
+
def t(self, value: Union[int, List[int]]) -> None:
|
|
100
|
+
"""Reference divide by 2.
|
|
101
|
+
|
|
102
|
+
Valid values are 0,1
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
value (int, list[int]): Current allowable setting
|
|
106
|
+
|
|
107
|
+
"""
|
|
108
|
+
self._check_in_range(value, self.t_available, "t")
|
|
109
|
+
self._t = value
|
|
110
|
+
|
|
111
|
+
_rf_div = [1, 2, 4, 8, 16, 32, 64]
|
|
112
|
+
rf_div_available = [1, 2, 4, 8, 16, 32, 64]
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def rf_div(self) -> Union[int, List[int]]:
|
|
116
|
+
"""Output RF divider.
|
|
117
|
+
|
|
118
|
+
Valid dividers are 1,2,3,4,5,6..32->(even)->4096
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
int: Current allowable dividers
|
|
122
|
+
"""
|
|
123
|
+
return self._rf_div
|
|
124
|
+
|
|
125
|
+
@rf_div.setter
|
|
126
|
+
def rf_div(self, value: Union[int, List[int]]) -> None:
|
|
127
|
+
"""Output RF divider.
|
|
128
|
+
|
|
129
|
+
Valid dividers are 1,2,3,4,5,6..32->(even)->4096
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
value (int, list[int]): Allowable values for divider
|
|
133
|
+
|
|
134
|
+
"""
|
|
135
|
+
self._check_in_range(value, self.rf_div_available, "rf_div")
|
|
136
|
+
self._rf_div = value
|
|
137
|
+
|
|
138
|
+
_mode = ["integer", "fractional"]
|
|
139
|
+
mode_available = ["integer", "fractional"]
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def mode(self) -> Union[str, List[str]]:
|
|
143
|
+
"""Set operational mode.
|
|
144
|
+
|
|
145
|
+
Options are: fractional, integer or [fractional, integer]
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
str: Current allowable modes
|
|
149
|
+
"""
|
|
150
|
+
return self._mode
|
|
151
|
+
|
|
152
|
+
@mode.setter
|
|
153
|
+
def mode(self, value: Union[str, List[str]]) -> None:
|
|
154
|
+
"""Set operational mode.
|
|
155
|
+
|
|
156
|
+
Options are: fractional, integer or [fractional, integer]
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
value (str, list[str]): Allowable values for mode
|
|
160
|
+
|
|
161
|
+
"""
|
|
162
|
+
self._check_in_range(value, self.mode_available, "mode")
|
|
163
|
+
self._mode = value
|
|
164
|
+
|
|
165
|
+
# These are too large for user to set
|
|
166
|
+
_int_4d5_min_max = [20, 32767]
|
|
167
|
+
_int_8d9_min_max = [64, 65535]
|
|
168
|
+
_int_frac_4d5_min_max = [23, 32767]
|
|
169
|
+
_int_frac_8d9_min_max = [75, 65535]
|
|
170
|
+
|
|
171
|
+
_frac1_min_max = [0, 33554431]
|
|
172
|
+
_frac2_min_max = [0, 16383]
|
|
173
|
+
_MOD1 = 2**25
|
|
174
|
+
_MOD2 = [*range(2, 16383 + 1)]
|
|
175
|
+
|
|
176
|
+
_prescaler = ["4/5", "8/9"]
|
|
177
|
+
|
|
178
|
+
def get_config(self, solution: CpoSolveResult = None) -> Dict:
|
|
179
|
+
"""Extract configurations from solver results.
|
|
180
|
+
|
|
181
|
+
Collect internal clock chip configuration and output clock definitions
|
|
182
|
+
leading to connected devices (converters, FPGAs)
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Dict: Dictionary of clocking rates and dividers for configuration
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
Exception: If solver is not called first
|
|
192
|
+
"""
|
|
193
|
+
if not self._clk_names:
|
|
194
|
+
raise Exception("set_requested_clocks must be called before get_config")
|
|
195
|
+
|
|
196
|
+
if solution:
|
|
197
|
+
self.solution = solution
|
|
198
|
+
|
|
199
|
+
config: Dict = {
|
|
200
|
+
"d": self._get_val(self.config["d"]),
|
|
201
|
+
"r": self._get_val(self.config["r"]),
|
|
202
|
+
"t": self._get_val(self.config["t"]),
|
|
203
|
+
"frac1": self._get_val(self.config["frac1"]),
|
|
204
|
+
"frac2": self._get_val(self.config["frac2"]),
|
|
205
|
+
"MOD2": self._get_val(self.config["MOD2"]),
|
|
206
|
+
"int": self._get_val(self.config["int"]),
|
|
207
|
+
"rf_div": self._get_val(self.config["rf_div"]),
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if self._get_val(self.config["fact_0_int_1"]) == 1:
|
|
211
|
+
config["mode"] = "fractional"
|
|
212
|
+
else:
|
|
213
|
+
config["mode"] = "integer"
|
|
214
|
+
|
|
215
|
+
if self._get_val(self.config["prescaler_4/5_0_8/9_1"]) == 1:
|
|
216
|
+
config["prescaler"] = "8/9"
|
|
217
|
+
else:
|
|
218
|
+
config["prescaler"] = "4/5"
|
|
219
|
+
|
|
220
|
+
vco = self.solution.get_kpis()["vco"]
|
|
221
|
+
config["rf_out_frequency"] = vco / config["rf_div"]
|
|
222
|
+
config["rf_out_frequency"] = tround(config["rf_out_frequency"])
|
|
223
|
+
|
|
224
|
+
return config
|
|
225
|
+
|
|
226
|
+
def _setup_solver_constraints(
|
|
227
|
+
self, input_ref: Union[int, float, CpoExpr, GK_Intermediate]
|
|
228
|
+
) -> None:
|
|
229
|
+
"""Apply constraints to solver model.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
input_ref (int, float, CpoExpr, GK_Intermediate): Input reference
|
|
233
|
+
frequency in hertz
|
|
234
|
+
|
|
235
|
+
Raises:
|
|
236
|
+
NotImplementedError: If solver is not CPLEX
|
|
237
|
+
"""
|
|
238
|
+
self.config = {}
|
|
239
|
+
|
|
240
|
+
# if not isinstance(input_ref, (int, float)):
|
|
241
|
+
# self.config["input_ref_set"] = input_ref(self.model) # type: ignore
|
|
242
|
+
# input_ref = self.config["input_ref_set"]["range"]
|
|
243
|
+
self.input_ref = input_ref
|
|
244
|
+
|
|
245
|
+
# PFD
|
|
246
|
+
self.config["d"] = self._convert_input(self.d, name="d")
|
|
247
|
+
self.config["r"] = self._convert_input(self.r, name="r")
|
|
248
|
+
self.config["t"] = self._convert_input(self.t, name="t")
|
|
249
|
+
|
|
250
|
+
self.config["f_pfd"] = self._add_intermediate(
|
|
251
|
+
input_ref
|
|
252
|
+
* ((1 + self.config["d"]) / (self.config["r"] * (1 + self.config["t"])))
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Configure fractional mode or integer mode constraints
|
|
256
|
+
if self._mode == "fractional":
|
|
257
|
+
self.config["fact_0_int_1"] = self._convert_input(0, "fact_0_int_1")
|
|
258
|
+
elif self._mode == "integer":
|
|
259
|
+
self.config["fact_0_int_1"] = self._convert_input(1, "fact_0_int_1")
|
|
260
|
+
else:
|
|
261
|
+
self.config["fact_0_int_1"] = self._convert_input([0, 1], "fact_0_int_1")
|
|
262
|
+
|
|
263
|
+
self.config["pfd_max_freq"] = self._add_intermediate(
|
|
264
|
+
(1 - self.config["fact_0_int_1"]) * self.pfd_freq_max_frac
|
|
265
|
+
+ self.config["fact_0_int_1"] * self.pfd_freq_max_int,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# VCO + output
|
|
269
|
+
self.config["MOD2"] = self._convert_input(self._MOD2, name="MOD2")
|
|
270
|
+
|
|
271
|
+
# Configure INT setting based on prescalers
|
|
272
|
+
if self.solver == "CPLEX":
|
|
273
|
+
self.config["frac1"] = integer_var(
|
|
274
|
+
min=self._frac1_min_max[0],
|
|
275
|
+
max=self._frac1_min_max[1],
|
|
276
|
+
name="frac1",
|
|
277
|
+
)
|
|
278
|
+
self.config["frac2"] = integer_var(
|
|
279
|
+
min=self._frac2_min_max[0],
|
|
280
|
+
max=self._frac2_min_max[1],
|
|
281
|
+
name="frac2",
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
if self._prescaler == "4/5":
|
|
285
|
+
self.config["prescaler_4/5_0_8/9_1"] = self._convert_input(
|
|
286
|
+
0, "prescaler"
|
|
287
|
+
)
|
|
288
|
+
elif self._prescaler == "8/9":
|
|
289
|
+
self.config["prescaler_4/5_0_8/9_1"] = self._convert_input(
|
|
290
|
+
1, "prescaler"
|
|
291
|
+
)
|
|
292
|
+
else:
|
|
293
|
+
self.config["prescaler_4/5_0_8/9_1"] = self._convert_input(
|
|
294
|
+
[0, 1], "prescaler"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
self.config["int_min"] = self._add_intermediate(
|
|
298
|
+
self.config["fact_0_int_1"]
|
|
299
|
+
* (
|
|
300
|
+
self.config["prescaler_4/5_0_8/9_1"] * self._int_8d9_min_max[0]
|
|
301
|
+
+ (1 - self.config["prescaler_4/5_0_8/9_1"])
|
|
302
|
+
* self._int_4d5_min_max[0]
|
|
303
|
+
)
|
|
304
|
+
+ (1 - self.config["fact_0_int_1"])
|
|
305
|
+
* (
|
|
306
|
+
self.config["prescaler_4/5_0_8/9_1"] * self._int_frac_8d9_min_max[0]
|
|
307
|
+
+ (1 - self.config["prescaler_4/5_0_8/9_1"])
|
|
308
|
+
* self._int_frac_4d5_min_max[0]
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
self.config["int_max"] = self._add_intermediate(
|
|
313
|
+
self.config["fact_0_int_1"]
|
|
314
|
+
* (
|
|
315
|
+
self.config["prescaler_4/5_0_8/9_1"] * self._int_8d9_min_max[1]
|
|
316
|
+
+ (1 - self.config["prescaler_4/5_0_8/9_1"])
|
|
317
|
+
* self._int_4d5_min_max[1]
|
|
318
|
+
)
|
|
319
|
+
+ (1 - self.config["fact_0_int_1"])
|
|
320
|
+
* (
|
|
321
|
+
self.config["prescaler_4/5_0_8/9_1"] * self._int_frac_8d9_min_max[1]
|
|
322
|
+
+ (1 - self.config["prescaler_4/5_0_8/9_1"])
|
|
323
|
+
* self._int_frac_4d5_min_max[1]
|
|
324
|
+
)
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
min_o = min(
|
|
328
|
+
[
|
|
329
|
+
self._int_8d9_min_max[0],
|
|
330
|
+
self._int_4d5_min_max[0],
|
|
331
|
+
self._int_frac_8d9_min_max[0],
|
|
332
|
+
self._int_frac_4d5_min_max[0],
|
|
333
|
+
]
|
|
334
|
+
)
|
|
335
|
+
max_o = max(
|
|
336
|
+
[
|
|
337
|
+
self._int_8d9_min_max[1],
|
|
338
|
+
self._int_4d5_min_max[1],
|
|
339
|
+
self._int_frac_8d9_min_max[1],
|
|
340
|
+
self._int_frac_4d5_min_max[1],
|
|
341
|
+
]
|
|
342
|
+
)
|
|
343
|
+
self.config["int"] = integer_var(min=min_o, max=max_o, name="int")
|
|
344
|
+
else:
|
|
345
|
+
raise NotImplementedError("Only CPLEX solver is implemented")
|
|
346
|
+
|
|
347
|
+
self.config["vco"] = self._add_intermediate(
|
|
348
|
+
(
|
|
349
|
+
self.config["int"]
|
|
350
|
+
+ (self.config["frac1"] + self.config["frac2"] / self.config["MOD2"])
|
|
351
|
+
/ self._MOD1
|
|
352
|
+
)
|
|
353
|
+
* self.config["f_pfd"]
|
|
354
|
+
)
|
|
355
|
+
self.model.add_kpi(
|
|
356
|
+
(
|
|
357
|
+
self.config["int"]
|
|
358
|
+
+ (self.config["frac1"] + self.config["frac2"] / self.config["MOD2"])
|
|
359
|
+
/ self._MOD1
|
|
360
|
+
)
|
|
361
|
+
* self.config["f_pfd"],
|
|
362
|
+
"vco",
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
self._add_equation(
|
|
366
|
+
[
|
|
367
|
+
input_ref <= self.input_freq_max,
|
|
368
|
+
input_ref >= self.input_freq_min,
|
|
369
|
+
self.config["f_pfd"] <= self.config["pfd_max_freq"],
|
|
370
|
+
self.config["vco"] <= self.vco_freq_max,
|
|
371
|
+
self.config["vco"] >= self.vco_freq_min,
|
|
372
|
+
self.config["int"] <= self.config["int_max"],
|
|
373
|
+
self.config["int"] >= self.config["int_min"],
|
|
374
|
+
]
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
def _setup(self, input_ref: int) -> None:
|
|
378
|
+
if isinstance(input_ref, (float, int)):
|
|
379
|
+
assert (
|
|
380
|
+
self.input_freq_max >= input_ref >= self.input_freq_min
|
|
381
|
+
), "Input frequency out of range"
|
|
382
|
+
|
|
383
|
+
# Setup clock chip internal constraints
|
|
384
|
+
self._setup_solver_constraints(input_ref)
|
|
385
|
+
|
|
386
|
+
def _get_clock_constraint(
|
|
387
|
+
self, clk_name: str
|
|
388
|
+
) -> Union[int, float, CpoExpr, GK_Intermediate]:
|
|
389
|
+
"""Get abstract clock output.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
clk_name (str): Reference clock name
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
(int or float or CpoExpr or GK_Intermediate): Abstract
|
|
396
|
+
or concrete clock reference
|
|
397
|
+
"""
|
|
398
|
+
self._clk_names = ["clk_name"]
|
|
399
|
+
|
|
400
|
+
self.config["rf_div"] = self._convert_input(self.rf_div, "rf_div")
|
|
401
|
+
|
|
402
|
+
return self.config["vco"] / self.config["rf_div"]
|
|
403
|
+
|
|
404
|
+
def set_requested_clocks(
|
|
405
|
+
self, ref_in: Union[int, float, CpoExpr, GK_Intermediate], out_freq: int
|
|
406
|
+
) -> None:
|
|
407
|
+
"""Define necessary clocks to be generated in model.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
ref_in (int, float, CpoExpr, GK_Intermediate): Reference frequency in hertz
|
|
411
|
+
out_freq (int): list of required clocks to be output
|
|
412
|
+
|
|
413
|
+
"""
|
|
414
|
+
self._setup(ref_in)
|
|
415
|
+
self._clk_names = ["rf_out"]
|
|
416
|
+
|
|
417
|
+
self.config["rf_div"] = self._convert_input(self.rf_div, "rf_div")
|
|
418
|
+
|
|
419
|
+
self._add_equation([self.config["rf_div"] * out_freq == self.config["vco"]])
|