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,3 @@
1
+ """ADI JIF PLL models."""
2
+
3
+ supported_parts = ["adf4371", "adf4382"]
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"]])