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,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