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,1143 @@
|
|
|
1
|
+
"""Xilinx FPGA clocking model."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from docplex.cp.modeler import if_then
|
|
6
|
+
|
|
7
|
+
from ...converters.converter import converter as conv
|
|
8
|
+
from ...solvers import (
|
|
9
|
+
CpoIntVar,
|
|
10
|
+
CpoSolveResult,
|
|
11
|
+
GK_Intermediate,
|
|
12
|
+
GK_Operators,
|
|
13
|
+
GKVariable,
|
|
14
|
+
)
|
|
15
|
+
from .bf import xilinx_bf
|
|
16
|
+
from .sevenseries import SevenSeries as SSTransceiver
|
|
17
|
+
from .ultrascaleplus import UltraScalePlus as USPTransceiver
|
|
18
|
+
from .xilinx_draw import xilinx_draw
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class xilinx(xilinx_bf, xilinx_draw):
|
|
22
|
+
"""Xilinx FPGA clocking model.
|
|
23
|
+
|
|
24
|
+
This model captures different limitations of the Xilinx
|
|
25
|
+
PLLs and interfaces used for JESD.
|
|
26
|
+
|
|
27
|
+
Currently only Zynq 7000 devices have been fully tested.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
name = "Xilinx-FPGA"
|
|
31
|
+
|
|
32
|
+
favor_cpll_over_qpll = False
|
|
33
|
+
minimize_fpga_ref_clock = False
|
|
34
|
+
|
|
35
|
+
"""Force generation of separate device clock from the clock chip. In many
|
|
36
|
+
cases, the ref clock and device clock can be the same."""
|
|
37
|
+
force_separate_device_clock: bool = False
|
|
38
|
+
|
|
39
|
+
"""Constrain reference clock to be specific values. Options:
|
|
40
|
+
- CORE_CLOCK: Make reference clock the same as the core clock (LR/40 or LR/66)
|
|
41
|
+
- CORE_CLOCK_DIV2: Make reference clock the same as the core clock divided by 2
|
|
42
|
+
- Unconstrained: No constraints on reference clock. Simply meet PLL constraints
|
|
43
|
+
"""
|
|
44
|
+
_ref_clock_constraint = "CORE_CLOCK"
|
|
45
|
+
|
|
46
|
+
max_serdes_lanes = 24
|
|
47
|
+
|
|
48
|
+
hdl_core_version = 2.1
|
|
49
|
+
|
|
50
|
+
available_speed_grades = [-1, -2, -3]
|
|
51
|
+
speed_grade = -2
|
|
52
|
+
|
|
53
|
+
transceiver_voltage = 800
|
|
54
|
+
|
|
55
|
+
ref_clock_min = -1 # Not set
|
|
56
|
+
ref_clock_max = -1 # Not set
|
|
57
|
+
|
|
58
|
+
available_fpga_packages = [
|
|
59
|
+
"Unknown",
|
|
60
|
+
"RF",
|
|
61
|
+
"FL",
|
|
62
|
+
"FF",
|
|
63
|
+
"FB",
|
|
64
|
+
"HC",
|
|
65
|
+
"FH",
|
|
66
|
+
"CS",
|
|
67
|
+
"CP",
|
|
68
|
+
"FT",
|
|
69
|
+
"FG",
|
|
70
|
+
"SB",
|
|
71
|
+
"RB",
|
|
72
|
+
"RS",
|
|
73
|
+
"CL",
|
|
74
|
+
"SF",
|
|
75
|
+
"BA",
|
|
76
|
+
"FA",
|
|
77
|
+
]
|
|
78
|
+
fpga_package = "FB"
|
|
79
|
+
|
|
80
|
+
available_fpga_families = ["Unknown", "Artix", "Kintex", "Virtex", "Zynq"]
|
|
81
|
+
fpga_family = "Zynq"
|
|
82
|
+
|
|
83
|
+
available_transceiver_types = ["GTXE2"]
|
|
84
|
+
transceiver_type = "GTXE2"
|
|
85
|
+
|
|
86
|
+
def trx_gen(self) -> int:
|
|
87
|
+
"""Get transceiver generation (2,3,4).
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
int: generation of transceiver
|
|
91
|
+
"""
|
|
92
|
+
return int(self.transceiver_type[-1])
|
|
93
|
+
|
|
94
|
+
def trx_variant(self) -> str:
|
|
95
|
+
"""Get transceiver variant (GTX, GTH, GTY, ...).
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
str: Transceiver variant
|
|
99
|
+
"""
|
|
100
|
+
# return self.transceiver_type[:2]
|
|
101
|
+
trxt = self.transceiver_type[:2]
|
|
102
|
+
assert len(trxt) == 3
|
|
103
|
+
return trxt
|
|
104
|
+
|
|
105
|
+
def fpga_generation(self) -> str:
|
|
106
|
+
"""Get FPGA generation 7000, US, US+... based on transceiver type.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
str: FPGA generation
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
Exception: Unknown transceiver generation
|
|
113
|
+
"""
|
|
114
|
+
if self.trx_gen() == 2:
|
|
115
|
+
return "7000"
|
|
116
|
+
elif self.trx_gen() == 3:
|
|
117
|
+
return "Ultrascale"
|
|
118
|
+
elif self.trx_gen() == 4:
|
|
119
|
+
return "Ultrascale+"
|
|
120
|
+
elif self.trx_gen() == 5:
|
|
121
|
+
return "Versal"
|
|
122
|
+
raise Exception(f"Unknown transceiver generation {self.trx_gen()}")
|
|
123
|
+
|
|
124
|
+
sys_clk_selections = [
|
|
125
|
+
"XCVR_CPLL",
|
|
126
|
+
"XCVR_QPLL0",
|
|
127
|
+
"XCVR_QPLL1",
|
|
128
|
+
]
|
|
129
|
+
sys_clk_select = "XCVR_QPLL1"
|
|
130
|
+
|
|
131
|
+
_out_clk_selections = [
|
|
132
|
+
# "XCVR_OUTCLK_PCS",
|
|
133
|
+
# "XCVR_OUTCLK_PMA",
|
|
134
|
+
"XCVR_REFCLK",
|
|
135
|
+
"XCVR_REFCLK_DIV2",
|
|
136
|
+
"XCVR_PROGDIV_CLK",
|
|
137
|
+
]
|
|
138
|
+
_out_clk_select = [
|
|
139
|
+
# "XCVR_OUTCLK_PCS",
|
|
140
|
+
# "XCVR_OUTCLK_PMA",
|
|
141
|
+
"XCVR_REFCLK",
|
|
142
|
+
"XCVR_REFCLK_DIV2",
|
|
143
|
+
"XCVR_PROGDIV_CLK",
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
""" Force use of QPLL for transceiver source """
|
|
147
|
+
force_qpll = False
|
|
148
|
+
|
|
149
|
+
""" Force use of QPLL1 for transceiver source (GTHE3,GTHE4,GTYE4)"""
|
|
150
|
+
force_qpll1 = False
|
|
151
|
+
|
|
152
|
+
""" Force use of CPLL for transceiver source """
|
|
153
|
+
force_cpll = False
|
|
154
|
+
|
|
155
|
+
""" Force all transceiver sources to be from a single PLL quad.
|
|
156
|
+
This will try to leverage the output dividers of the PLLs
|
|
157
|
+
"""
|
|
158
|
+
force_single_quad_tile = False
|
|
159
|
+
|
|
160
|
+
""" Request that clock chip generated device clock
|
|
161
|
+
device clock == LMFC/40
|
|
162
|
+
NOTE: THIS IS NOT FPGA REF CLOCK
|
|
163
|
+
"""
|
|
164
|
+
request_device_clock = False
|
|
165
|
+
|
|
166
|
+
_clock_names: List[str] = []
|
|
167
|
+
|
|
168
|
+
"""When PROGDIV, this will be set to the value of the divider"""
|
|
169
|
+
_used_progdiv = {}
|
|
170
|
+
|
|
171
|
+
"""FPGA target Fmax rate use to determine link layer output rate"""
|
|
172
|
+
target_Fmax = 500e6
|
|
173
|
+
|
|
174
|
+
"""Device and reference clock relation"""
|
|
175
|
+
_device_clock_and_ref_clock_relation_options = [
|
|
176
|
+
"NA",
|
|
177
|
+
"ref_clock_2x_device_clock",
|
|
178
|
+
"ref_clock_eq_device_clock",
|
|
179
|
+
]
|
|
180
|
+
_device_clock_and_ref_clock_relation = "NA"
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def device_clock_and_ref_clock_relation(self) -> str:
|
|
184
|
+
"""Get device clock and reference clock relation.
|
|
185
|
+
|
|
186
|
+
Device clock and reference clock relation can be set to:
|
|
187
|
+
- NA: No relation
|
|
188
|
+
- ref_clock_2x_device_clock: Reference clock is 2x device clock
|
|
189
|
+
- ref_clock_eq_device_clock: Reference clock is equal to device clock
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
str: Device clock and reference clock relation.
|
|
193
|
+
"""
|
|
194
|
+
return self._device_clock_and_ref_clock_relation
|
|
195
|
+
|
|
196
|
+
@device_clock_and_ref_clock_relation.setter
|
|
197
|
+
def device_clock_and_ref_clock_relation(self, value: str) -> None:
|
|
198
|
+
"""Set device clock and reference clock relation.
|
|
199
|
+
|
|
200
|
+
Device clock and reference clock relation can be set to:
|
|
201
|
+
- NA: No relation
|
|
202
|
+
- ref_clock_2x_device_clock: Reference clock is 2x device clock
|
|
203
|
+
- ref_clock_eq_device_clock: Reference clock is equal to device clock
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
value (str): Device clock and reference clock relation.
|
|
207
|
+
|
|
208
|
+
Raises:
|
|
209
|
+
Exception: Invalid device clock and reference clock relation selection.
|
|
210
|
+
"""
|
|
211
|
+
if value not in self._device_clock_and_ref_clock_relation_options:
|
|
212
|
+
raise Exception(
|
|
213
|
+
f"Invalid device_clock_and_ref_clock_relation {value}, "
|
|
214
|
+
+ f"options are {self._device_clock_and_ref_clock_relation_options}"
|
|
215
|
+
)
|
|
216
|
+
self._device_clock_and_ref_clock_relation = value
|
|
217
|
+
|
|
218
|
+
# """Require generation of separate clock specifically for link layer"""
|
|
219
|
+
# requires_separate_link_layer_out_clock = True
|
|
220
|
+
|
|
221
|
+
"""Require generation of separate core clock (LR/40 or LR/66)"""
|
|
222
|
+
requires_core_clock_from_device_clock = False
|
|
223
|
+
|
|
224
|
+
_device_clock_source_options = ["external", "link_clock", "ref_clock"]
|
|
225
|
+
_device_clock_source = ["external"]
|
|
226
|
+
|
|
227
|
+
configs = [] # type: ignore
|
|
228
|
+
_transceiver_models = {} # type: ignore
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def device_clock_source(self) -> str:
|
|
232
|
+
"""Get device clock source.
|
|
233
|
+
|
|
234
|
+
Device clock source can be set to:
|
|
235
|
+
- external: External clock from clock chip
|
|
236
|
+
- link_clock: Link layer clock is reused for as device clock
|
|
237
|
+
- ref_clock: Reference clock is reused for device clock
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
str: Device clock source.
|
|
241
|
+
"""
|
|
242
|
+
return self._device_clock_source
|
|
243
|
+
|
|
244
|
+
@device_clock_source.setter
|
|
245
|
+
def device_clock_source(self, value: Union[str, List[str]]) -> None:
|
|
246
|
+
"""Set device clock source.
|
|
247
|
+
|
|
248
|
+
Device clock source can be set to:
|
|
249
|
+
- external: External clock
|
|
250
|
+
- link_clock: Link layer clock
|
|
251
|
+
- ref_clock: Reference clock
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
value (str, List[str]): Device clock source.
|
|
255
|
+
|
|
256
|
+
Raises:
|
|
257
|
+
Exception: Invalid device clock source selection.
|
|
258
|
+
"""
|
|
259
|
+
if not isinstance(value, list):
|
|
260
|
+
value = [value]
|
|
261
|
+
for item in value:
|
|
262
|
+
if item not in self._device_clock_source_options:
|
|
263
|
+
raise Exception(
|
|
264
|
+
f"Invalid device clock source {item}, "
|
|
265
|
+
+ f"options are {self._device_clock_source_options}"
|
|
266
|
+
)
|
|
267
|
+
self._device_clock_source = value
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def ref_clock_constraint(self) -> str:
|
|
271
|
+
"""Get reference clock constraint.
|
|
272
|
+
|
|
273
|
+
Reference clock constraint can be set to:
|
|
274
|
+
- CORE_CLOCK: Make reference clock the same as the core clock (LR/40 or LR/66)
|
|
275
|
+
- CORE_CLOCK_DIV2: Make reference clock the same as the core clock divided by 2
|
|
276
|
+
- Unconstrained: No constraints on reference clock. Simply meet PLL constraints
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
str: Reference clock constraint.
|
|
280
|
+
"""
|
|
281
|
+
return self._ref_clock_constraint
|
|
282
|
+
|
|
283
|
+
@ref_clock_constraint.setter
|
|
284
|
+
def ref_clock_constraint(self, value: str) -> None:
|
|
285
|
+
"""Set reference clock constraint.
|
|
286
|
+
|
|
287
|
+
Reference clock constraint can be set to:
|
|
288
|
+
- CORE_CLOCK: Make reference clock the same as the core clock (LR/40 or LR/66)
|
|
289
|
+
- CORE_CLOCK_DIV2: Make reference clock the same as the core clock divided by 2
|
|
290
|
+
- Unconstrained: No constraints on reference clock. Simply meet PLL constraints
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
value (str): Reference clock constraint.
|
|
294
|
+
|
|
295
|
+
Raises:
|
|
296
|
+
Exception: Invalid ref_clock_constraint selection.
|
|
297
|
+
"""
|
|
298
|
+
if value not in ["CORE_CLOCK", "CORE_CLOCK_DIV2", "Unconstrained"]:
|
|
299
|
+
raise Exception(
|
|
300
|
+
f"Invalid ref_clock_constraint {value}, "
|
|
301
|
+
+ "options are CORE_CLOCK, CORE_CLOCK_DIV2, Unconstrained"
|
|
302
|
+
)
|
|
303
|
+
self._ref_clock_constraint = value
|
|
304
|
+
|
|
305
|
+
@property
|
|
306
|
+
def out_clk_select(self) -> Union[int, float]:
|
|
307
|
+
"""Get current PLL clock output mux options for link layer clock.
|
|
308
|
+
|
|
309
|
+
Valid options are: "XCVR_REFCLK", "XCVR_REFCLK_DIV2", "XCVR_PROGDIV_CLK"
|
|
310
|
+
If a list of these is provided, the solver will determine one to use.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
str, list(str): Mux selection for link layer clock.
|
|
314
|
+
"""
|
|
315
|
+
return self._out_clk_select
|
|
316
|
+
|
|
317
|
+
@out_clk_select.setter
|
|
318
|
+
def out_clk_select(self, value: Union[str, List[str]]) -> None:
|
|
319
|
+
"""Set current PLL clock output mux options for link layer clock.
|
|
320
|
+
|
|
321
|
+
Valid options are: "XCVR_REFCLK", "XCVR_REFCLK_DIV2", "XCVR_PROGDIV_CLK"
|
|
322
|
+
If a list of these is provided, the solver will determine one to use.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
value (str, List[str]): Mux selection for link layer clock.
|
|
326
|
+
|
|
327
|
+
Raises:
|
|
328
|
+
Exception: Invalid out_clk_select selection.
|
|
329
|
+
"""
|
|
330
|
+
if isinstance(value, list):
|
|
331
|
+
for item in value:
|
|
332
|
+
if item not in self._out_clk_selections:
|
|
333
|
+
raise Exception(
|
|
334
|
+
f"Invalid out_clk_select {item}, "
|
|
335
|
+
+ f"options are {self._out_clk_selections}"
|
|
336
|
+
)
|
|
337
|
+
elif isinstance(value, dict):
|
|
338
|
+
for converter in value:
|
|
339
|
+
if not isinstance(converter, conv):
|
|
340
|
+
raise Exception("Keys of out_clk_select but be of type converter")
|
|
341
|
+
if value[converter] not in self._out_clk_selections:
|
|
342
|
+
raise Exception(
|
|
343
|
+
f"Invalid out_clk_select {value[converter]}, "
|
|
344
|
+
+ f"options are {self._out_clk_selections}"
|
|
345
|
+
)
|
|
346
|
+
elif value not in self._out_clk_selections: # str
|
|
347
|
+
raise Exception(
|
|
348
|
+
f"Invalid out_clk_select {value}, "
|
|
349
|
+
+ f"options are {self._out_clk_selections}"
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
self._out_clk_select = value
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def _ref_clock_max(self) -> int:
|
|
356
|
+
"""Get maximum reference clock for config.
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
int: Rate in samples per second.
|
|
360
|
+
|
|
361
|
+
Raises:
|
|
362
|
+
Exception: Unsupported transceiver type configured.
|
|
363
|
+
"""
|
|
364
|
+
# https://www.xilinx.com/support/documentation/data_sheets/ds191-XC7Z030-XC7Z045-data-sheet.pdf # noqa: B950
|
|
365
|
+
if self.transceiver_type == "GTXE2":
|
|
366
|
+
if str(self.speed_grade) == "-3E":
|
|
367
|
+
return 700000000
|
|
368
|
+
else:
|
|
369
|
+
return 670000000
|
|
370
|
+
else:
|
|
371
|
+
raise Exception(
|
|
372
|
+
f"Unknown ref_clock_max for transceiver type {self.transceiver_type}"
|
|
373
|
+
)
|
|
374
|
+
# raise Exception(f"Unknown transceiver type {self.transceiver_type}")
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def _ref_clock_min(self) -> int:
|
|
378
|
+
"""Get minimum reference clock for config.
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
int: Rate in samples per second.
|
|
382
|
+
|
|
383
|
+
Raises:
|
|
384
|
+
Exception: Unsupported transceiver type configured.
|
|
385
|
+
"""
|
|
386
|
+
# https://www.xilinx.com/support/documentation/data_sheets/ds191-XC7Z030-XC7Z045-data-sheet.pdf # noqa: B950
|
|
387
|
+
if self.transceiver_type == "GTXE2":
|
|
388
|
+
return 60000000
|
|
389
|
+
else:
|
|
390
|
+
raise Exception(
|
|
391
|
+
f"Unknown ref_clock_min for transceiver type {self.transceiver_type}"
|
|
392
|
+
)
|
|
393
|
+
# raise Exception(f"Unknown transceiver type {self.transceiver_type}")
|
|
394
|
+
|
|
395
|
+
_available_dev_kit_names = [
|
|
396
|
+
"zcu102",
|
|
397
|
+
"zc706",
|
|
398
|
+
"vcu118",
|
|
399
|
+
"adsy1100",
|
|
400
|
+
]
|
|
401
|
+
|
|
402
|
+
def setup_by_dev_kit_name(self, name: str) -> None:
|
|
403
|
+
"""Configure object based on board name. Ex: zc706, zcu102.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
name (str): Name of dev kit. Ex: zc706, zcu102
|
|
407
|
+
|
|
408
|
+
Raises:
|
|
409
|
+
Exception: Unsupported board requested.
|
|
410
|
+
|
|
411
|
+
"""
|
|
412
|
+
if name.lower() == "zc706":
|
|
413
|
+
self.transceiver_type = "GTXE2"
|
|
414
|
+
self.fpga_family = "Zynq"
|
|
415
|
+
self.fpga_package = "FF"
|
|
416
|
+
self.speed_grade = -2
|
|
417
|
+
self.ref_clock_min = 60000000
|
|
418
|
+
self.ref_clock_max = 670000000
|
|
419
|
+
self.max_serdes_lanes = 8
|
|
420
|
+
# default PROGDIV not available
|
|
421
|
+
o = self._out_clk_selections.copy()
|
|
422
|
+
del o[o.index("XCVR_PROGDIV_CLK")]
|
|
423
|
+
self._out_clk_selections = o
|
|
424
|
+
self._out_clk_select = o
|
|
425
|
+
elif name.lower() == "zcu102":
|
|
426
|
+
self.transceiver_type = "GTHE4"
|
|
427
|
+
self.fpga_family = "Zynq"
|
|
428
|
+
self.fpga_package = "FF"
|
|
429
|
+
self.speed_grade = -2
|
|
430
|
+
self.ref_clock_min = 60000000
|
|
431
|
+
self.ref_clock_max = 820000000
|
|
432
|
+
self.max_serdes_lanes = 8
|
|
433
|
+
elif name.lower() == "vcu118":
|
|
434
|
+
# XCVU9P-L2FLGA2104
|
|
435
|
+
self.transceiver_type = "GTYE4"
|
|
436
|
+
self.fpga_family = "Virtex"
|
|
437
|
+
self.fpga_package = "FL"
|
|
438
|
+
self.speed_grade = -2
|
|
439
|
+
self.ref_clock_min = 60000000
|
|
440
|
+
self.ref_clock_max = 820000000
|
|
441
|
+
self.max_serdes_lanes = 24
|
|
442
|
+
elif name.lower() == "adsy1100":
|
|
443
|
+
# ADI VPX Module
|
|
444
|
+
# VU11P: xcvu11p-flgb2104-2-i
|
|
445
|
+
self.transceiver_type = "GTYE4"
|
|
446
|
+
self.fpga_family = "Virtex"
|
|
447
|
+
self.fpga_package = "FL"
|
|
448
|
+
self.speed_grade = -2
|
|
449
|
+
self.ref_clock_min = 60000000 # NEED TO VERIFY
|
|
450
|
+
self.ref_clock_max = 820000000 # NEED TO VERIFY
|
|
451
|
+
self.max_serdes_lanes = 24 # Connected to AD9084
|
|
452
|
+
else:
|
|
453
|
+
raise Exception(f"No boardname found in library for {name}")
|
|
454
|
+
self.name = name
|
|
455
|
+
|
|
456
|
+
def determine_pll(self, bit_clock: int, fpga_ref_clock: int) -> Dict:
|
|
457
|
+
"""Determine if configuration is possible with CPLL or QPLL.
|
|
458
|
+
|
|
459
|
+
CPLL is checked first and will check QPLL if that case is
|
|
460
|
+
invalid.
|
|
461
|
+
|
|
462
|
+
This is only used for brute-force implementations.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
bit_clock (int): Equivalent to lane rate in bits/second
|
|
466
|
+
fpga_ref_clock (int): System reference clock
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
Dict: Dictionary of PLL configuration
|
|
470
|
+
"""
|
|
471
|
+
try:
|
|
472
|
+
info = self.determine_cpll(bit_clock, fpga_ref_clock)
|
|
473
|
+
except: # noqa: B001
|
|
474
|
+
info = self.determine_qpll(bit_clock, fpga_ref_clock)
|
|
475
|
+
return info
|
|
476
|
+
|
|
477
|
+
def get_required_clock_names(self) -> List[str]:
|
|
478
|
+
"""Get list of strings of names of requested clocks.
|
|
479
|
+
|
|
480
|
+
This list of names is for the clocks defined by get_required_clocks
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
List[str]: List of strings of clock names in order
|
|
484
|
+
|
|
485
|
+
Raises:
|
|
486
|
+
Exception: Clock have not been enumerated aka get_required_clocks not
|
|
487
|
+
not called yet.
|
|
488
|
+
"""
|
|
489
|
+
if not self._clock_names:
|
|
490
|
+
raise Exception(
|
|
491
|
+
"get_required_clocks must be run to generated"
|
|
492
|
+
+ " dependent clocks before names are available"
|
|
493
|
+
)
|
|
494
|
+
return self._clock_names
|
|
495
|
+
|
|
496
|
+
def get_config(
|
|
497
|
+
self,
|
|
498
|
+
converter: conv,
|
|
499
|
+
fpga_ref: Union[float, int],
|
|
500
|
+
solution: Optional[CpoSolveResult] = None,
|
|
501
|
+
) -> Union[List[Dict], Dict]:
|
|
502
|
+
"""Extract configurations from solver results.
|
|
503
|
+
|
|
504
|
+
Collect internal FPGA configuration and output clock definitions.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
converter (conv): Converter object connected to FPGA who config is
|
|
508
|
+
collected
|
|
509
|
+
fpga_ref (int or float): Reference clock generated for FPGA for specific
|
|
510
|
+
converter
|
|
511
|
+
solution (CpoSolveResult): CPlex solution and only needed for CPlex
|
|
512
|
+
solver
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
Dict: Dictionary of clocking rates and dividers for configuration.
|
|
516
|
+
|
|
517
|
+
Raises:
|
|
518
|
+
Exception: Invalid PLL configuration.
|
|
519
|
+
"""
|
|
520
|
+
out = []
|
|
521
|
+
if solution:
|
|
522
|
+
self.solution = solution
|
|
523
|
+
|
|
524
|
+
self._saved_solution = solution # needed for draw
|
|
525
|
+
|
|
526
|
+
for config in self.configs:
|
|
527
|
+
pll_config: Dict[str, Union[str, int, float]] = {}
|
|
528
|
+
|
|
529
|
+
# Filter out other converters
|
|
530
|
+
# FIXME: REIMPLEMENT BETTER
|
|
531
|
+
if converter.name + "_use_cpll" not in config.keys():
|
|
532
|
+
print("Continued")
|
|
533
|
+
continue
|
|
534
|
+
|
|
535
|
+
pll_config = self._transceiver_models[converter.name].get_config(
|
|
536
|
+
config, converter, fpga_ref
|
|
537
|
+
)
|
|
538
|
+
# cpll = self._get_val(config[converter.name + "_use_cpll"])
|
|
539
|
+
# qpll = self._get_val(config[converter.name + "_use_qpll"])
|
|
540
|
+
# if converter.name + "_use_qpll1" in config.keys():
|
|
541
|
+
# qpll1 = self._get_val(config[converter.name + "_use_qpll1"])
|
|
542
|
+
# else:
|
|
543
|
+
# qpll1 = False
|
|
544
|
+
|
|
545
|
+
# SERDES output mux
|
|
546
|
+
if pll_config["type"] == "cpll":
|
|
547
|
+
pll_config["sys_clk_select"] = "XCVR_CPLL"
|
|
548
|
+
elif pll_config["type"] == "qpll":
|
|
549
|
+
pll_config["sys_clk_select"] = "XCVR_QPLL0"
|
|
550
|
+
elif pll_config["type"] == "qpll1":
|
|
551
|
+
pll_config["sys_clk_select"] = "XCVR_QPLL1"
|
|
552
|
+
else:
|
|
553
|
+
raise Exception("Invalid PLL type")
|
|
554
|
+
|
|
555
|
+
pll_config["out_clk_select"] = self._get_val(
|
|
556
|
+
config[converter.name + "_out_clk_select"]
|
|
557
|
+
)
|
|
558
|
+
pll_config["out_clk_select"] = self._out_clk_selections[
|
|
559
|
+
pll_config["out_clk_select"]
|
|
560
|
+
]
|
|
561
|
+
|
|
562
|
+
if pll_config["out_clk_select"] == "XCVR_PROGDIV_CLK":
|
|
563
|
+
progdiv = self._get_val(config[converter.name + "_progdiv_times2"])
|
|
564
|
+
if float(int(progdiv / 2) * 2) != float(progdiv):
|
|
565
|
+
raise Exception(
|
|
566
|
+
f"PROGDIV divider {progdiv} is not an integer, "
|
|
567
|
+
+ "this is not supported by the solver"
|
|
568
|
+
)
|
|
569
|
+
progdiv = progdiv / 2
|
|
570
|
+
progdiv = int(progdiv)
|
|
571
|
+
|
|
572
|
+
# Check if we need progdiv really
|
|
573
|
+
if progdiv == 1 and "XCVR_REFCLK" in self._out_clk_select:
|
|
574
|
+
# Change to XCVR_REFCLK
|
|
575
|
+
pll_config["out_clk_select"] = "XCVR_REFCLK"
|
|
576
|
+
elif progdiv == 2 and "XCVR_REFCLK_DIV2" in self._out_clk_select:
|
|
577
|
+
# Change to XCVR_REFCLK_DIV2
|
|
578
|
+
pll_config["out_clk_select"] = "XCVR_REFCLK_DIV2"
|
|
579
|
+
else:
|
|
580
|
+
if progdiv not in self._get_progdiv():
|
|
581
|
+
raise Exception(
|
|
582
|
+
f"PROGDIV divider {progdiv} not available for "
|
|
583
|
+
+ f"transceiver type {self.transceiver_type}"
|
|
584
|
+
)
|
|
585
|
+
pll_config["progdiv"] = progdiv
|
|
586
|
+
|
|
587
|
+
source = self._get_val(config[converter.name + "_device_clock_source"])
|
|
588
|
+
pll_config["device_clock_source"] = self._device_clock_source_options[
|
|
589
|
+
source
|
|
590
|
+
]
|
|
591
|
+
|
|
592
|
+
if converter.jesd_class == "jesd204b":
|
|
593
|
+
sps = converter.L * 32 / (converter.M * converter.Np)
|
|
594
|
+
elif converter.jesd_class == "jesd204c":
|
|
595
|
+
sps = converter.L * 64 / (converter.M * converter.Np)
|
|
596
|
+
else:
|
|
597
|
+
raise Exception("Invalid JESD class")
|
|
598
|
+
pll_config["transport_samples_per_clock"] = sps
|
|
599
|
+
|
|
600
|
+
out.append(pll_config)
|
|
601
|
+
|
|
602
|
+
if len(out) == 1:
|
|
603
|
+
out = out[0] # type: ignore
|
|
604
|
+
return out
|
|
605
|
+
|
|
606
|
+
def _get_conv_prop(
|
|
607
|
+
self, conv: conv, prop: Union[str, dict]
|
|
608
|
+
) -> Union[int, float, str]:
|
|
609
|
+
"""Helper to extract nested properties if present.
|
|
610
|
+
|
|
611
|
+
Args:
|
|
612
|
+
conv (conv): Converter object
|
|
613
|
+
prop (str,dict): Property to extract
|
|
614
|
+
|
|
615
|
+
Raises:
|
|
616
|
+
Exception: Converter does not have property
|
|
617
|
+
|
|
618
|
+
Returns:
|
|
619
|
+
Union[int,float,str]: Value of property
|
|
620
|
+
"""
|
|
621
|
+
if isinstance(prop, dict):
|
|
622
|
+
if conv not in prop:
|
|
623
|
+
raise Exception(f"Converter {conv.name} not found in config")
|
|
624
|
+
return prop[conv]
|
|
625
|
+
return prop
|
|
626
|
+
|
|
627
|
+
def _get_progdiv(self) -> Union[List[int], List[float]]:
|
|
628
|
+
"""Get programmable SERDES dividers for FPGA.
|
|
629
|
+
|
|
630
|
+
Raises:
|
|
631
|
+
Exception: PRODIV is not available for transceiver type.
|
|
632
|
+
|
|
633
|
+
Returns:
|
|
634
|
+
List[int,float]: Programmable dividers for FPGA
|
|
635
|
+
"""
|
|
636
|
+
if self.transceiver_type in ["GTYE3", "GTHE3"]:
|
|
637
|
+
return [1, 4, 5, 8, 10, 16, 16.5, 20, 32, 33, 40, 64, 66, 80, 100]
|
|
638
|
+
elif self.transceiver_type in ["GTYE4", "GTHE4"]:
|
|
639
|
+
return [
|
|
640
|
+
1,
|
|
641
|
+
4,
|
|
642
|
+
5,
|
|
643
|
+
8,
|
|
644
|
+
10,
|
|
645
|
+
16,
|
|
646
|
+
16.5,
|
|
647
|
+
20,
|
|
648
|
+
32,
|
|
649
|
+
33,
|
|
650
|
+
40,
|
|
651
|
+
64,
|
|
652
|
+
66,
|
|
653
|
+
80,
|
|
654
|
+
100,
|
|
655
|
+
128,
|
|
656
|
+
132,
|
|
657
|
+
]
|
|
658
|
+
else:
|
|
659
|
+
raise Exception(
|
|
660
|
+
"PROGDIV is not available for FPGA transciever type "
|
|
661
|
+
+ str(self.transceiver_type)
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
def _set_link_layer_requirements(
|
|
665
|
+
self,
|
|
666
|
+
converter: conv,
|
|
667
|
+
fpga_ref: Union[int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar],
|
|
668
|
+
config: Dict,
|
|
669
|
+
link_out_ref: Union[
|
|
670
|
+
int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar
|
|
671
|
+
] = None,
|
|
672
|
+
) -> Dict:
|
|
673
|
+
"""Set link layer constraints for downstream FPGA logic.
|
|
674
|
+
|
|
675
|
+
The link layer is driven from the XCVR core which can route the
|
|
676
|
+
following signals to the link layer input:
|
|
677
|
+
- External Ref
|
|
678
|
+
- External Ref / 2
|
|
679
|
+
- {CPLL,QPLL0,QPLL1} / PROGDIV
|
|
680
|
+
|
|
681
|
+
The link layer input has a hard requirement that is must be at:
|
|
682
|
+
- JESD204B: lane rate (bit clock) / 40 or lane rate (bit clock) / 80
|
|
683
|
+
- JESD204C: lane rate (bit clock) / 66
|
|
684
|
+
|
|
685
|
+
The link layer output rate will be equivalent to sample clock / N, where
|
|
686
|
+
N is an integer. Note the smaller N, the more channeling it can be to
|
|
687
|
+
synthesize the design. This output clock can be separate or be the same
|
|
688
|
+
as the XCVR reference.
|
|
689
|
+
|
|
690
|
+
Based on this info, we set up the problem where we define N (sample per
|
|
691
|
+
clock increase), and set the lane rate based on the current converter
|
|
692
|
+
JESD config.
|
|
693
|
+
|
|
694
|
+
Args:
|
|
695
|
+
converter (conv): Converter object connected to FPGA
|
|
696
|
+
fpga_ref (int or GKVariable): Reference clock generated for FPGA
|
|
697
|
+
config (Dict): Dictionary of clocking rates and dividers for link
|
|
698
|
+
layer
|
|
699
|
+
link_out_ref (int or GKVariable): Reference clock generated for FPGA
|
|
700
|
+
link layer output
|
|
701
|
+
|
|
702
|
+
Returns:
|
|
703
|
+
Dict: Dictionary of clocking rates extended with dividers for link
|
|
704
|
+
layer
|
|
705
|
+
|
|
706
|
+
Raises:
|
|
707
|
+
Exception: Link layer output clock select invalid
|
|
708
|
+
"""
|
|
709
|
+
if converter.jesd_class == "jesd204b":
|
|
710
|
+
link_layer_input_rate = converter.bit_clock / 40
|
|
711
|
+
elif converter.jesd_class == "jesd204c":
|
|
712
|
+
link_layer_input_rate = converter.bit_clock / 66
|
|
713
|
+
|
|
714
|
+
if isinstance(self.out_clk_select, dict):
|
|
715
|
+
if converter not in self.out_clk_select.keys():
|
|
716
|
+
raise Exception(
|
|
717
|
+
"Link layer out_clk_select invalid for converter " + converter.name
|
|
718
|
+
)
|
|
719
|
+
if isinstance(self.out_clk_select[converter], dict):
|
|
720
|
+
out_clk_select = self.out_clk_select[converter].copy()
|
|
721
|
+
else:
|
|
722
|
+
out_clk_select = self.out_clk_select[converter]
|
|
723
|
+
else:
|
|
724
|
+
out_clk_select = self.out_clk_select
|
|
725
|
+
|
|
726
|
+
out_clk_select_options = []
|
|
727
|
+
if "XCVR_REFCLK" in out_clk_select:
|
|
728
|
+
out_clk_select_options.append(0)
|
|
729
|
+
if "XCVR_REFCLK_DIV2" in out_clk_select:
|
|
730
|
+
out_clk_select_options.append(1)
|
|
731
|
+
if "XCVR_PROGDIV_CLK" in out_clk_select:
|
|
732
|
+
out_clk_select_options.append(2)
|
|
733
|
+
|
|
734
|
+
# Quick check for progdiv since it doesn't require the solver
|
|
735
|
+
if "XCVR_PROGDIV_CLK" in out_clk_select and len(out_clk_select) == 1:
|
|
736
|
+
progdiv = self._get_progdiv()
|
|
737
|
+
div = converter.bit_clock / link_layer_input_rate
|
|
738
|
+
if div not in progdiv:
|
|
739
|
+
raise Exception(
|
|
740
|
+
f"Cannot use PROGDIV since required divider {div},"
|
|
741
|
+
+ f" only available {progdiv}"
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
config[converter.name + "_out_clk_select"] = self._convert_input(
|
|
745
|
+
out_clk_select_options, converter.name + "_out_clk_select"
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
if "XCVR_PROGDIV_CLK" in out_clk_select:
|
|
749
|
+
progdiv = self._get_progdiv()
|
|
750
|
+
progdiv_times2 = [i * 2 for i in progdiv]
|
|
751
|
+
# Check that casting to int is valid
|
|
752
|
+
for i in progdiv_times2:
|
|
753
|
+
if i != int(i):
|
|
754
|
+
raise Exception(
|
|
755
|
+
f"PROGDIV times 2 divider {i} is not an integer, "
|
|
756
|
+
+ "this is not supported by the solver"
|
|
757
|
+
)
|
|
758
|
+
progdiv_times2 = [int(i) for i in progdiv_times2]
|
|
759
|
+
config[converter.name + "_progdiv_times2"] = self._convert_input(
|
|
760
|
+
progdiv_times2, converter.name + "_progdiv"
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
self._add_equation(
|
|
764
|
+
[
|
|
765
|
+
if_then(
|
|
766
|
+
config[converter.name + "_out_clk_select"] == 0, # XCVR_REFCLK
|
|
767
|
+
fpga_ref == link_layer_input_rate,
|
|
768
|
+
),
|
|
769
|
+
if_then(
|
|
770
|
+
config[converter.name + "_out_clk_select"] == 1, # XCVR_REFCLK_DIV2
|
|
771
|
+
fpga_ref == link_layer_input_rate * 2,
|
|
772
|
+
),
|
|
773
|
+
]
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
if "XCVR_PROGDIV_CLK" in out_clk_select:
|
|
777
|
+
self._add_equation(
|
|
778
|
+
[
|
|
779
|
+
if_then(
|
|
780
|
+
config[converter.name + "_out_clk_select"]
|
|
781
|
+
== 2, # XCVR_PROGDIV_CLK
|
|
782
|
+
converter.bit_clock * 2
|
|
783
|
+
== link_layer_input_rate
|
|
784
|
+
* config[converter.name + "_progdiv_times2"],
|
|
785
|
+
),
|
|
786
|
+
]
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
return config, link_layer_input_rate
|
|
790
|
+
|
|
791
|
+
def _setup_quad_tile(
|
|
792
|
+
self,
|
|
793
|
+
converter: conv,
|
|
794
|
+
fpga_ref: Union[int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar],
|
|
795
|
+
link_out_ref: Union[
|
|
796
|
+
None, int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar
|
|
797
|
+
] = None,
|
|
798
|
+
) -> Dict:
|
|
799
|
+
"""Configure FPGA {Q/C}PLL tile.
|
|
800
|
+
|
|
801
|
+
Args:
|
|
802
|
+
converter (conv): Converter object(s) connected to FPGA
|
|
803
|
+
fpga_ref (int,GKVariable, GK_Intermediate, GK_Operators, CpoIntVar):
|
|
804
|
+
Reference clock for FPGA
|
|
805
|
+
link_out_ref (None, int,GKVariable, GK_Intermediate, GK_Operators,
|
|
806
|
+
CpoIntVar): Link layer output reference clock
|
|
807
|
+
|
|
808
|
+
Returns:
|
|
809
|
+
Dict: Dictionary of clocking rates and dividers for configuration
|
|
810
|
+
|
|
811
|
+
Raises:
|
|
812
|
+
Exception: Unsupported solver
|
|
813
|
+
"""
|
|
814
|
+
# Add reference clock constraints
|
|
815
|
+
self._add_equation(
|
|
816
|
+
[fpga_ref >= self.ref_clock_min, fpga_ref <= self.ref_clock_max]
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
if converter.jesd_class == "jesd204b":
|
|
820
|
+
core_clock = converter.bit_clock / 40
|
|
821
|
+
else:
|
|
822
|
+
core_clock = converter.bit_clock / 66
|
|
823
|
+
|
|
824
|
+
if self.ref_clock_constraint == "CORE_CLOCK":
|
|
825
|
+
self._add_equation([fpga_ref == core_clock])
|
|
826
|
+
elif self.ref_clock_constraint == "CORE_CLOCK_DIV2":
|
|
827
|
+
self._add_equation([fpga_ref == core_clock / 2])
|
|
828
|
+
|
|
829
|
+
# Add transceiver
|
|
830
|
+
config = {}
|
|
831
|
+
if self.fpga_generation() == "7000":
|
|
832
|
+
self._transceiver_models[converter.name] = SSTransceiver(
|
|
833
|
+
parent=self,
|
|
834
|
+
transceiver_type=self.transceiver_type,
|
|
835
|
+
speed_grade=self.speed_grade,
|
|
836
|
+
)
|
|
837
|
+
elif self.fpga_generation() in ["Ultrascale", "Ultrascale+"]:
|
|
838
|
+
self._transceiver_models[converter.name] = USPTransceiver(
|
|
839
|
+
parent=self,
|
|
840
|
+
transceiver_type=self.transceiver_type,
|
|
841
|
+
speed_grade=self.speed_grade,
|
|
842
|
+
)
|
|
843
|
+
else:
|
|
844
|
+
raise Exception(f"Unsupported FPGA generation {self.fpga_generation()}")
|
|
845
|
+
|
|
846
|
+
# Handle force PLLs for nested devices and multiple converters
|
|
847
|
+
force_cpll = False
|
|
848
|
+
force_qpll = False
|
|
849
|
+
force_qpll1 = False
|
|
850
|
+
|
|
851
|
+
if isinstance(self.force_cpll, dict):
|
|
852
|
+
if converter in self.force_cpll:
|
|
853
|
+
force_cpll = self.force_cpll[converter]
|
|
854
|
+
else:
|
|
855
|
+
force_cpll = self.force_cpll
|
|
856
|
+
|
|
857
|
+
if isinstance(self.force_qpll, dict):
|
|
858
|
+
if converter in self.force_qpll:
|
|
859
|
+
force_qpll = self.force_qpll[converter]
|
|
860
|
+
else:
|
|
861
|
+
force_qpll = self.force_qpll
|
|
862
|
+
|
|
863
|
+
if isinstance(self.force_qpll1, dict):
|
|
864
|
+
if converter in self.force_qpll1:
|
|
865
|
+
force_qpll1 = self.force_qpll1[converter]
|
|
866
|
+
else:
|
|
867
|
+
force_qpll1 = self.force_qpll1
|
|
868
|
+
|
|
869
|
+
self._transceiver_models[converter.name].force_cpll = force_cpll
|
|
870
|
+
self._transceiver_models[converter.name].force_qpll = force_qpll
|
|
871
|
+
if hasattr(self._transceiver_models[converter.name], "force_qpll1"):
|
|
872
|
+
self._transceiver_models[converter.name].force_qpll1 = force_qpll1
|
|
873
|
+
|
|
874
|
+
config = self._transceiver_models[converter.name].add_constraints(
|
|
875
|
+
config, fpga_ref, converter
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
# Add constraints for link clock and transport clock
|
|
879
|
+
# Link clock in must be lane rate / 40 or lane rate / 66
|
|
880
|
+
# Link clock out and transport must be sample rate / SPS
|
|
881
|
+
|
|
882
|
+
# Add constraints for link clock input
|
|
883
|
+
config, link_layer_input_rate = self._set_link_layer_requirements(
|
|
884
|
+
converter, fpga_ref, config, None
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
# Add device clock constraints
|
|
888
|
+
# Device clock drives the output clock for the link layer and the
|
|
889
|
+
# transport clock. It is sample_rates / SPS
|
|
890
|
+
if converter.jesd_class == "jesd204b":
|
|
891
|
+
sps = converter.L * 32 / (converter.M * converter.Np)
|
|
892
|
+
elif converter.jesd_class == "jesd204c":
|
|
893
|
+
sps = converter.L * 64 / (converter.M * converter.Np)
|
|
894
|
+
|
|
895
|
+
device_clock_rate = converter.sample_clock / sps
|
|
896
|
+
|
|
897
|
+
# Quick check for non-solver case(s)
|
|
898
|
+
if (
|
|
899
|
+
len(self._device_clock_source) == 1
|
|
900
|
+
and self._device_clock_source[0] == "link_clock"
|
|
901
|
+
and device_clock_rate != link_layer_input_rate
|
|
902
|
+
):
|
|
903
|
+
raise Exception(
|
|
904
|
+
"Configuration not possible if device_clock_source is link_clock\n"
|
|
905
|
+
+ f"Device clock rate {device_clock_rate} != Link layer input "
|
|
906
|
+
+ f"rate {link_layer_input_rate}"
|
|
907
|
+
)
|
|
908
|
+
# Options: "external", "link_clock", "ref_clock"
|
|
909
|
+
dcs = []
|
|
910
|
+
if "external" in self._device_clock_source:
|
|
911
|
+
dcs.append(0)
|
|
912
|
+
if "link_clock" in self._device_clock_source:
|
|
913
|
+
dcs.append(1)
|
|
914
|
+
if "ref_clock" in self._device_clock_source:
|
|
915
|
+
dcs.append(2)
|
|
916
|
+
|
|
917
|
+
config[converter.name + "_device_clock_source"] = self._convert_input(
|
|
918
|
+
dcs, converter.name + "_device_clock_source"
|
|
919
|
+
)
|
|
920
|
+
self._add_equation(
|
|
921
|
+
[
|
|
922
|
+
if_then(
|
|
923
|
+
config[converter.name + "_device_clock_source"] == 0, # external
|
|
924
|
+
link_out_ref == device_clock_rate,
|
|
925
|
+
),
|
|
926
|
+
if_then(
|
|
927
|
+
config[converter.name + "_device_clock_source"]
|
|
928
|
+
== 1, # link_clock_in
|
|
929
|
+
link_layer_input_rate == device_clock_rate,
|
|
930
|
+
),
|
|
931
|
+
if_then(
|
|
932
|
+
config[converter.name + "_device_clock_source"] == 2, # ref_clk
|
|
933
|
+
fpga_ref == link_out_ref,
|
|
934
|
+
),
|
|
935
|
+
]
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
# Add device clock and ref clock interaction
|
|
939
|
+
if not self.device_clock_and_ref_clock_relation == "NA":
|
|
940
|
+
if self.device_clock_and_ref_clock_relation == "ref_clock_2x_device_clock":
|
|
941
|
+
self._add_equation([fpga_ref == 2 * link_out_ref])
|
|
942
|
+
elif (
|
|
943
|
+
self.device_clock_and_ref_clock_relation == "ref_clock_eq_device_clock"
|
|
944
|
+
):
|
|
945
|
+
self._add_equation([fpga_ref == link_out_ref])
|
|
946
|
+
else:
|
|
947
|
+
raise Exception(
|
|
948
|
+
"Invalid device clock and reference clock \n"
|
|
949
|
+
+ f"relation {self.device_clock_and_ref_clock_relation}"
|
|
950
|
+
)
|
|
951
|
+
|
|
952
|
+
return config
|
|
953
|
+
|
|
954
|
+
# Add optimization to favor a single reference clock vs unique ref+device clocks
|
|
955
|
+
config[converter.name + "single_clk"] = self._convert_input(
|
|
956
|
+
[0, 1], converter.name + "single_clk"
|
|
957
|
+
)
|
|
958
|
+
if self.force_separate_device_clock:
|
|
959
|
+
sdc = [1]
|
|
960
|
+
else:
|
|
961
|
+
sdc = [0, 1]
|
|
962
|
+
config[converter.name + "two_clks"] = self._convert_input(
|
|
963
|
+
sdc, converter.name + "two_clks"
|
|
964
|
+
)
|
|
965
|
+
self._add_equation(
|
|
966
|
+
[
|
|
967
|
+
config[converter.name + "single_clk"]
|
|
968
|
+
+ config[converter.name + "two_clks"]
|
|
969
|
+
== 1,
|
|
970
|
+
]
|
|
971
|
+
)
|
|
972
|
+
# Favor single clock, this equation will be minimized
|
|
973
|
+
v = (
|
|
974
|
+
config[converter.name + "single_clk"]
|
|
975
|
+
+ 1000 * config[converter.name + "two_clks"]
|
|
976
|
+
)
|
|
977
|
+
self._add_objective(v)
|
|
978
|
+
|
|
979
|
+
# Add constraints to meet sample clock
|
|
980
|
+
if self.requires_core_clock_from_device_clock:
|
|
981
|
+
if converter.jesd_class == "jesd204b":
|
|
982
|
+
core_clock = converter.bit_clock / 40
|
|
983
|
+
else:
|
|
984
|
+
core_clock = converter.bit_clock / 66
|
|
985
|
+
|
|
986
|
+
self._add_equation([core_clock == link_out_ref])
|
|
987
|
+
|
|
988
|
+
else:
|
|
989
|
+
possible_divs = []
|
|
990
|
+
for samples_per_clock in [1, 2, 4, 8, 16]:
|
|
991
|
+
if converter.sample_clock / samples_per_clock <= self.target_Fmax:
|
|
992
|
+
possible_divs.append(samples_per_clock)
|
|
993
|
+
|
|
994
|
+
if len(possible_divs) == 0:
|
|
995
|
+
raise Exception("Link layer output clock rate too high")
|
|
996
|
+
|
|
997
|
+
config[converter.name + "_link_out_div"] = self._convert_input(
|
|
998
|
+
possible_divs, converter.name + "_samples_per_clock"
|
|
999
|
+
)
|
|
1000
|
+
|
|
1001
|
+
self._add_equation(
|
|
1002
|
+
[
|
|
1003
|
+
(
|
|
1004
|
+
config[converter.name + "single_clk"] * fpga_ref
|
|
1005
|
+
+ config[converter.name + "two_clks"]
|
|
1006
|
+
* link_out_ref
|
|
1007
|
+
* config[converter.name + "_link_out_div"]
|
|
1008
|
+
)
|
|
1009
|
+
== converter.sample_clock
|
|
1010
|
+
]
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
return config
|
|
1014
|
+
|
|
1015
|
+
def get_required_clocks(
|
|
1016
|
+
self,
|
|
1017
|
+
converter: conv,
|
|
1018
|
+
fpga_ref: Union[int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar],
|
|
1019
|
+
link_out_ref: Union[
|
|
1020
|
+
int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar
|
|
1021
|
+
] = None,
|
|
1022
|
+
) -> List:
|
|
1023
|
+
"""Get necessary clocks for QPLL/CPLL configuration.
|
|
1024
|
+
|
|
1025
|
+
Args:
|
|
1026
|
+
converter (conv): Converter object of converter connected to
|
|
1027
|
+
FPGA
|
|
1028
|
+
fpga_ref (int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar):
|
|
1029
|
+
Abstract or concrete reference to FPGA reference clock
|
|
1030
|
+
link_out_ref (int or GKVariable): Reference clock generated for FPGA
|
|
1031
|
+
link layer output, also called device clock
|
|
1032
|
+
|
|
1033
|
+
Returns:
|
|
1034
|
+
List: List of solver variables and constraints
|
|
1035
|
+
|
|
1036
|
+
Raises:
|
|
1037
|
+
Exception: If solver is not valid
|
|
1038
|
+
Exception: Link layer out clock required
|
|
1039
|
+
"""
|
|
1040
|
+
if self.ref_clock_min == -1 or self.ref_clock_max == -1:
|
|
1041
|
+
raise Exception("ref_clock_min or ref_clock_max not set")
|
|
1042
|
+
if "_get_converters" in dir(converter):
|
|
1043
|
+
converter = (
|
|
1044
|
+
converter._get_converters() # type: ignore
|
|
1045
|
+
) # Handle nested converters
|
|
1046
|
+
|
|
1047
|
+
if not isinstance(converter, list):
|
|
1048
|
+
converter = [converter] # type: ignore
|
|
1049
|
+
|
|
1050
|
+
# if self.solver == "gekko":
|
|
1051
|
+
# self.config = {
|
|
1052
|
+
# "fpga_ref": self.model.Var(
|
|
1053
|
+
# # integer=True,
|
|
1054
|
+
# lb=self.ref_clock_min,
|
|
1055
|
+
# ub=self.ref_clock_max,
|
|
1056
|
+
# value=self.ref_clock_min,
|
|
1057
|
+
# )
|
|
1058
|
+
# }
|
|
1059
|
+
# elif self.solver == "CPLEX":
|
|
1060
|
+
# # self.config = {
|
|
1061
|
+
# # "fpga_ref": integer_var(
|
|
1062
|
+
# # self.ref_clock_min, self.ref_clock_max, "fpga_ref"
|
|
1063
|
+
# # )
|
|
1064
|
+
# # }
|
|
1065
|
+
# pass
|
|
1066
|
+
# else:
|
|
1067
|
+
# raise Exception(f"Unknown solver {self.solver}")
|
|
1068
|
+
|
|
1069
|
+
# https://www.xilinx.com/support/documentation/user_guides/ug476_7Series_Transceivers.pdf # noqa: B950
|
|
1070
|
+
|
|
1071
|
+
# clock_names = ["fpga_ref"]
|
|
1072
|
+
clock_names = []
|
|
1073
|
+
self.config = {}
|
|
1074
|
+
if self.force_single_quad_tile:
|
|
1075
|
+
raise Exception("force_single_quad_tile==1 not implemented")
|
|
1076
|
+
else:
|
|
1077
|
+
#######################
|
|
1078
|
+
# self.configs = []
|
|
1079
|
+
self.dev_clocks = []
|
|
1080
|
+
self.ref_clocks = []
|
|
1081
|
+
# obs = []
|
|
1082
|
+
for cnv in converter: # type: ignore
|
|
1083
|
+
# rsl = self._get_conv_prop(
|
|
1084
|
+
# cnv, self.requires_separate_link_layer_out_clock
|
|
1085
|
+
# )
|
|
1086
|
+
# if link_out_ref is None and rsl:
|
|
1087
|
+
# raise Exception("Link layer out clock required")
|
|
1088
|
+
|
|
1089
|
+
clock_names.append(cnv.name + "fpga_ref")
|
|
1090
|
+
# self.config[cnv.name+"fpga_ref"] = interval_var(
|
|
1091
|
+
# self.ref_clock_min, self.ref_clock_max, name=cnv.name+"fpga_ref"
|
|
1092
|
+
# )
|
|
1093
|
+
self.config[cnv.name + "fpga_ref"] = fpga_ref
|
|
1094
|
+
self.ref_clocks.append(self.config[cnv.name + "fpga_ref"])
|
|
1095
|
+
if (
|
|
1096
|
+
link_out_ref is not None
|
|
1097
|
+
): # self.requires_separate_link_layer_out_clock:
|
|
1098
|
+
self.config[cnv.name + "link_out_ref"] = link_out_ref
|
|
1099
|
+
self.ref_clocks.append(self.config[cnv.name + "link_out_ref"])
|
|
1100
|
+
config = self._setup_quad_tile(
|
|
1101
|
+
cnv,
|
|
1102
|
+
self.config[cnv.name + "fpga_ref"],
|
|
1103
|
+
self.config[cnv.name + "link_out_ref"],
|
|
1104
|
+
)
|
|
1105
|
+
else:
|
|
1106
|
+
config = self._setup_quad_tile(
|
|
1107
|
+
cnv, self.config[cnv.name + "fpga_ref"]
|
|
1108
|
+
)
|
|
1109
|
+
# Set optimizations
|
|
1110
|
+
# self.model.Obj(self.config[converter.name+"d"])
|
|
1111
|
+
# self.model.Obj(self.config[converter.name+"d_cpll"])
|
|
1112
|
+
# self.model.Obj(config[converter.name+"d_select"])
|
|
1113
|
+
if self.favor_cpll_over_qpll:
|
|
1114
|
+
if self.solver == "gekko":
|
|
1115
|
+
self.model.Obj(
|
|
1116
|
+
-1 * config[cnv.name + "qpll_0_cpll_1"]
|
|
1117
|
+
) # Favor CPLL over QPLL
|
|
1118
|
+
elif self.solver == "CPLEX":
|
|
1119
|
+
self.model.maximize(config[cnv.name + "qpll_0_cpll_1"])
|
|
1120
|
+
# obs.append(-1 * config[cnv.name + "qpll_0_cpll_1"])
|
|
1121
|
+
else:
|
|
1122
|
+
raise Exception(f"Unknown solver {self.solver}")
|
|
1123
|
+
|
|
1124
|
+
self.configs.append(config)
|
|
1125
|
+
# FPGA also requires clock at device clock rate
|
|
1126
|
+
if self.request_device_clock:
|
|
1127
|
+
self.dev_clocks.append(cnv.device_clock)
|
|
1128
|
+
clock_names.append(cnv.name + "_fpga_device_clock")
|
|
1129
|
+
|
|
1130
|
+
if self.minimize_fpga_ref_clock:
|
|
1131
|
+
if self.solver == "gekko":
|
|
1132
|
+
self.model.Obj(self.config[cnv.name + "fpga_ref"])
|
|
1133
|
+
elif self.solver == "CPLEX":
|
|
1134
|
+
# self.model.minimize_static_lex(obs + [self.config[converter.name+"fpga_ref"]]) # noqa: B950
|
|
1135
|
+
self.model.minimize(self.config[cnv.name + "fpga_ref"]) # noqa: B950
|
|
1136
|
+
# self.model.maximize(obs + self.config[converter.name+"fpga_ref"])
|
|
1137
|
+
else:
|
|
1138
|
+
raise Exception(f"Unknown solver {self.solver}")
|
|
1139
|
+
|
|
1140
|
+
self._clock_names = clock_names
|
|
1141
|
+
|
|
1142
|
+
# return [self.config["fpga_ref"]] + self.dev_clocks
|
|
1143
|
+
return self.ref_clocks + self.dev_clocks
|