Skip to content

Reference

AD9081 high speed MxFE clocking model.

ad9081

Bases: ad9081_core

AD9081 combined transmit and receive model.

Source code in adijif/converters/ad9081.py
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
class ad9081(ad9081_core):
    """AD9081 combined transmit and receive model."""

    converter_clock_min = ad9081_rx.converter_clock_min
    converter_clock_max = ad9081_rx.converter_clock_max
    quick_configuration_modes: Dict[str, Any] = {}
    _nested = ["adc", "dac"]
    converter_type = "adc_dac"

    def __init__(
        self, model: Union[GEKKO, CpoModel] = None, solver: str = None
    ) -> None:
        """Initialize AD9081 clocking model for TX and RX.

        This is a common class used to handle TX and RX constraints
        together.

        Args:
            model (GEKKO,CpoModel): Solver model
            solver (str): Solver name (gekko or CPLEX)
        """
        if solver:
            self.solver = solver
        self.adc = ad9081_rx(model, solver=self.solver)
        self.dac = ad9081_tx(model, solver=self.solver)
        self.model = model

    def validate_config(self) -> None:
        """Validate device configurations including JESD and clocks of both ADC and DAC.

        This check only is for static configuration that does not include
        variables which are solved.
        """
        self.adc.validate_config()
        self.dac.validate_config()

    def _get_converters(self) -> List[Union[converter, converter]]:
        return [self.adc, self.dac]

    def get_required_clock_names(self) -> List[str]:
        """Get list of strings of names of requested clocks.

        This list of names is for the clocks defined by get_required_clocks

        Returns:
            List[str]: List of strings of clock names in order
        """
        clk = (
            "ad9081_dac_clock"
            if self.adc.clocking_option == "direct"
            else "ad9081_pll_ref"
        )
        return [clk, "ad9081_adc_sysref", "ad9081_dac_sysref"]

    def _converter_clock_config(self) -> None:
        adc_clk = self.adc.decimation * self.adc.sample_clock
        dac_clk = self.dac.interpolation * self.dac.sample_clock
        l = dac_clk / adc_clk
        if np.abs(l - round(l)) > 1e-6:
            raise Exception(
                f"Sample clock ratio is not integer {adc_clk} {dac_clk}"
            )
        else:
            l = int(round(l))
        if l not in self.adc.l_available:
            raise Exception(
                f"ADC clock must be DAC clock/L where L={self.adc.l_available}."
                + f" Got {l} ({dac_clk}/{adc_clk})"
            )

        self.config["dac_clk"] = self._convert_input(dac_clk)
        self.config["adc_clk"] = self._convert_input(adc_clk)
        self.config["converter_clk"] = self._add_intermediate(
            self.config["dac_clk"]
        )

        # Add single PLL constraint
        # JESD204B/C transmitter is a power of 2 divisor of the lane rate of
        # the JESD204B/C receiver
        if self.solver == "gekko":
            raise Exception("Not implemented for GEKKO")
        elif self.solver == "CPLEX":
            divs = [int(2**d) for d in range(16)]
            self.config["serdes_pll_div"] = self._convert_input(
                divs, "serdes_pll_div", default=1
            )
        else:
            raise Exception(f"Unknown solver {self.solver}")

        self._add_equation(
            [
                self.config["serdes_pll_div"] * self.adc.bit_clock
                == self.dac.bit_clock
            ]
        )

    def get_required_clocks(self) -> List:
        """Generate list required clocks.

        For AD9081 this will contain [converter clock, sysref requirement SOS]

        Returns:
            List: List of solver variables, equations, and constants
        """
        # SYSREF
        self.config = {}
        self.config["adc_lmfc_divisor_sysref"] = self._convert_input(
            self.adc._adc_lmfc_divisor_sysref, "adc_lmfc_divisor_sysref"
        )
        self.config["dac_lmfc_divisor_sysref"] = self._convert_input(
            self.dac._dac_lmfc_divisor_sysref, "dac_lmfc_divisor_sysref"
        )

        self.config["sysref_adc"] = self._add_intermediate(
            self.adc.multiframe_clock / self.config["adc_lmfc_divisor_sysref"]
        )
        self.config["sysref_dac"] = self._add_intermediate(
            self.dac.multiframe_clock / self.config["dac_lmfc_divisor_sysref"]
        )

        # Device Clocking
        if self.clocking_option == "direct":
            # raise Exception("Not implemented yet")
            # adc_clk = self.sample_clock * self.datapath_decimation
            # clk = dac_clk
            clk = self.dac.interpolation * self.dac.sample_clock
        else:
            clk = self._pll_config(rxtx=True)

        # Objectives
        # self.model.Obj(self.config["sysref"])  # This breaks many searches
        # self.model.Obj(-1*self.config["lmfc_divisor_sysref"])

        return [clk, self.config["sysref_adc"], self.config["sysref_dac"]]

__init__(model=None, solver=None)

Initialize AD9081 clocking model for TX and RX.

This is a common class used to handle TX and RX constraints together.

Parameters:

Name Type Description Default
model (GEKKO, CpoModel)

Solver model

None
solver str

Solver name (gekko or CPLEX)

None
Source code in adijif/converters/ad9081.py
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
def __init__(
    self, model: Union[GEKKO, CpoModel] = None, solver: str = None
) -> None:
    """Initialize AD9081 clocking model for TX and RX.

    This is a common class used to handle TX and RX constraints
    together.

    Args:
        model (GEKKO,CpoModel): Solver model
        solver (str): Solver name (gekko or CPLEX)
    """
    if solver:
        self.solver = solver
    self.adc = ad9081_rx(model, solver=self.solver)
    self.dac = ad9081_tx(model, solver=self.solver)
    self.model = model

get_required_clock_names()

Get list of strings of names of requested clocks.

This list of names is for the clocks defined by get_required_clocks

Returns:

Type Description
List[str]

List[str]: List of strings of clock names in order

Source code in adijif/converters/ad9081.py
551
552
553
554
555
556
557
558
559
560
561
562
563
564
def get_required_clock_names(self) -> List[str]:
    """Get list of strings of names of requested clocks.

    This list of names is for the clocks defined by get_required_clocks

    Returns:
        List[str]: List of strings of clock names in order
    """
    clk = (
        "ad9081_dac_clock"
        if self.adc.clocking_option == "direct"
        else "ad9081_pll_ref"
    )
    return [clk, "ad9081_adc_sysref", "ad9081_dac_sysref"]

get_required_clocks()

Generate list required clocks.

For AD9081 this will contain [converter clock, sysref requirement SOS]

Returns:

Name Type Description
List List

List of solver variables, equations, and constants

Source code in adijif/converters/ad9081.py
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
def get_required_clocks(self) -> List:
    """Generate list required clocks.

    For AD9081 this will contain [converter clock, sysref requirement SOS]

    Returns:
        List: List of solver variables, equations, and constants
    """
    # SYSREF
    self.config = {}
    self.config["adc_lmfc_divisor_sysref"] = self._convert_input(
        self.adc._adc_lmfc_divisor_sysref, "adc_lmfc_divisor_sysref"
    )
    self.config["dac_lmfc_divisor_sysref"] = self._convert_input(
        self.dac._dac_lmfc_divisor_sysref, "dac_lmfc_divisor_sysref"
    )

    self.config["sysref_adc"] = self._add_intermediate(
        self.adc.multiframe_clock / self.config["adc_lmfc_divisor_sysref"]
    )
    self.config["sysref_dac"] = self._add_intermediate(
        self.dac.multiframe_clock / self.config["dac_lmfc_divisor_sysref"]
    )

    # Device Clocking
    if self.clocking_option == "direct":
        # raise Exception("Not implemented yet")
        # adc_clk = self.sample_clock * self.datapath_decimation
        # clk = dac_clk
        clk = self.dac.interpolation * self.dac.sample_clock
    else:
        clk = self._pll_config(rxtx=True)

    # Objectives
    # self.model.Obj(self.config["sysref"])  # This breaks many searches
    # self.model.Obj(-1*self.config["lmfc_divisor_sysref"])

    return [clk, self.config["sysref_adc"], self.config["sysref_dac"]]

validate_config()

Validate device configurations including JESD and clocks of both ADC and DAC.

This check only is for static configuration that does not include variables which are solved.

Source code in adijif/converters/ad9081.py
539
540
541
542
543
544
545
546
def validate_config(self) -> None:
    """Validate device configurations including JESD and clocks of both ADC and DAC.

    This check only is for static configuration that does not include
    variables which are solved.
    """
    self.adc.validate_config()
    self.dac.validate_config()

ad9081_core

Bases: converter

AD9081 high speed MxFE model.

This model supports both direct clock configurations and on-board generation

Clocking: AD9081 can internally generate or leverage external clocks. The high speed clock within the system is referred to as the DAC clock and the ADC clock will be a divided down version of the clock: adc_clock == dac_clock / L, where L = 1,2,3,4

For internal generation, the DAC clock is generated through an integer PLL through the following relation: dac_clock == ((m_vco * n_vco) / R * ref_clock) / D

For external clocks, the clock must be provided at the DAC clock rate

Once we have the DAC clock the data rates can be directly evaluated into each JESD framer:

rx_baseband_sample_rate = (dac_clock / L) / datapath_decimation tx_baseband_sample_rate = dac_clock / datapath_interpolation

Source code in adijif/converters/ad9081.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
class ad9081_core(converter, metaclass=ABCMeta):
    """AD9081 high speed MxFE model.

    This model supports both direct clock configurations and on-board
    generation

    Clocking: AD9081 can internally generate or leverage external clocks. The
    high speed clock within the system is referred to as the DAC clock and
    the ADC clock will be a divided down version of the clock:
        adc_clock  == dac_clock / L, where L = 1,2,3,4


    For internal generation, the DAC clock is generated through an integer PLL
    through the following relation:
        dac_clock == ((m_vco * n_vco) / R * ref_clock) / D

    For external clocks, the clock must be provided at the DAC clock rate

    Once we have the DAC clock the data rates can be directly evaluated into
    each JESD framer:

    rx_baseband_sample_rate = (dac_clock / L) / datapath_decimation
    tx_baseband_sample_rate = dac_clock / datapath_interpolation

    """

    device_clock_available = None  # FIXME
    device_clock_ranges = None  # FIXME

    model: Union[GEKKO, CpoModel] = None

    name = "AD9081"

    # Integrated PLL constants
    l_available = [1, 2, 3, 4]
    l = 1  # pylint:  disable=E741
    m_vco_available = [5, 7, 8, 11]  # 8 is nominal
    m_vco = 8
    n_vco_available = [*range(2, 50 + 1)]
    n_vco = 2
    r_available = [1, 2, 3, 4]
    r = 1
    d_available = [1, 2, 3, 4]
    d = 1
    # Integrated PLL limits
    pfd_min = 25e6
    pfd_max = 750e6
    vco_min = 6e9
    vco_max = 12e9

    # JESD parameters
    available_jesd_modes = ["jesd204b", "jesd204c"]
    M_available = [1, 2, 3, 4, 6, 8, 12, 16]
    L_available = [1, 2, 3, 4, 6, 8]
    N_available = [12, 16]
    Np_available = [12, 16, 24]
    F_available = [1, 2, 3, 4, 6, 8, 12, 16, 24, 32]
    S_available = [1, 2, 4, 8]
    # FIXME
    # K_available = [4, 8, 12, 16, 20, 24, 28, 32]
    K_available = [16, 32, 64, 128, 256]
    CS_available = [0, 1, 2, 3]
    CF_available = [0]
    # FIXME

    # Clocking constraints
    clocking_option_available = ["integrated_pll", "direct"]
    _clocking_option = "integrated_pll"
    bit_clock_min_available = {"jesd204b": 1.5e9, "jesd204c": 6e9}
    bit_clock_max_available = {"jesd204b": 15.5e9, "jesd204c": 24.75e9}

    config = {}  # type: ignore

    device_clock_max = 12e9
    _lmfc_divisor_sysref_available = [
        1,
        2,
        4,
        8,
        16,
        32,
        64,
        128,
        256,
        512,
        1024,
    ]

    def _check_valid_internal_configuration(self) -> None:
        # FIXME
        pass

    def get_config(self, solution: CpoSolveResult = None) -> Dict:
        """Extract configurations from solver results.

        Collect internal converter configuration and output clock definitions
        leading to connected devices (clock chips, FPGAs)

        Args:
            solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver

        Returns:
            Dict: Dictionary of clocking rates and dividers for configuration
        """
        if solution:
            self.solution = solution
        if self.clocking_option == "integrated_pll":
            pll_config: Dict = {
                "m_vco": self._get_val(self.config["ad9081_m_vco"]),
                "n_vco": self._get_val(self.config["ad9081_n_vco"]),
                "r": self._get_val(self.config["ad9081_r"]),
                "d": self._get_val(self.config["ad9081_d"]),
            }
            if "serdes_pll_div" in self.config:
                pll_config["serdes_pll_div"] = self._get_val(
                    self.config["serdes_pll_div"]
                )
            return {
                "clocking_option": self.clocking_option,
                "pll_config": pll_config,
            }
        else:
            if "serdes_pll_div" in self.config:
                return {
                    "serdes_pll_div": self._get_val(
                        self.config["serdes_pll_div"]
                    ),
                    "clocking_option": self.clocking_option,
                }
            return {"clocking_option": self.clocking_option}

    def get_required_clock_names(self) -> List[str]:
        """Get list of strings of names of requested clocks.

        This list of names is for the clocks defined by get_required_clocks

        Returns:
            List[str]: List of strings of clock names in order
        """
        clk = (
            "ad9081_dac_clock"
            if self.clocking_option == "direct"
            else "ad9081_pll_ref"
        )
        return [clk, "ad9081_sysref"]

    @property
    @abstractmethod
    def _converter_clock_config(self) -> None:
        """Define source clocking relation based on ADC, DAC, or both.

        Raises:
            NotImplementedError: Method not implemented
        """
        raise NotImplementedError

    def _pll_config(self, rxtx: bool = False) -> Dict:
        self._converter_clock_config()  # type: ignore

        self.config["ad9081_m_vco"] = self._convert_input(
            [5, 7, 8, 11], "ad9081_m_vco"
        )
        self.config["ad9081_n_vco"] = self._convert_input(
            [*range(2, 51)], "ad9081_n_vco"
        )
        self.config["ad9081_r"] = self._convert_input([1, 2, 3, 4], "ad9081_r")
        self.config["ad9081_d"] = self._convert_input([1, 2, 3, 4], "ad9081_d")

        self.config["ad9081_ref_clk"] = self._add_intermediate(
            self.config["converter_clk"]
            * self.config["ad9081_d"]
            * self.config["ad9081_r"]
            / (self.config["ad9081_m_vco"] * self.config["ad9081_n_vco"])
        )
        # if self.solver == "gekko":
        #     self.config["ref_clk"] = self.model.Var(
        #         integer=True,
        #         lb=1e6,
        #         ub=self.device_clock_max,
        #         value=self.device_clock_max,
        #     )
        # elif self.solver == "CPLEX":
        #     # self.config["ref_clk"] = integer_var(
        #     #     int(1e6), int(self.device_clock_max), "ref_clk"
        #     # )
        #     self.config["ref_clk"] = (
        #         self.config["converter_clk"]
        #         * self.config["d"]
        #         * self.config["r"]
        #         / (self.config["m_vco"] * self.config["n_vco"])
        #     )
        # else:
        #     raise Exception("Unknown solver")

        self.config["ad9081_vco"] = self._add_intermediate(
            self.config["ad9081_ref_clk"]
            * self.config["ad9081_m_vco"]
            * self.config["ad9081_n_vco"]
            / self.config["ad9081_r"],
        )

        # if self.solver == "gekko":
        #     self.config["vco"] = self.model.Intermediate(
        #         self.config["ref_clk"]
        #         * self.config["m_vco"]
        #         * self.config["n_vco"]
        #         / self.config["r"],
        #     )
        # elif self.solver == "CPLEX":
        #     self.config["vco"] = (
        #         self.config["ref_clk"]
        #         * self.config["m_vco"]
        #         * self.config["n_vco"]
        #         / self.config["r"]
        #     )
        # else:
        #     raise Exception("Unknown solver: %s" % self.solver)

        self._add_equation(
            [
                self.config["ad9081_vco"] >= self.vco_min,
                self.config["ad9081_vco"] <= self.vco_max,
                self.config["ad9081_ref_clk"] / self.config["ad9081_r"]
                <= self.pfd_max,
                self.config["ad9081_ref_clk"] / self.config["ad9081_r"]
                >= self.pfd_min,
                self.config["ad9081_ref_clk"] >= int(100e6),
                self.config["ad9081_ref_clk"] <= int(2e9),
                # self.config["converter_clk"] <= self.device_clock_max,
                self.config["converter_clk"]
                >= (
                    self.converter_clock_min
                    if not rxtx
                    else self.dac.converter_clock_min  # type: ignore
                ),
                self.config["converter_clk"]
                <= (
                    self.converter_clock_max
                    if not rxtx
                    else self.dac.converter_clock_max  # type: ignore
                ),
            ]
        )

        # Make ref_clk an integer since API requires it
        if self.solver == "CPLEX":
            self.config["integer_ad9081_ref_clk"] = integer_var(
                min=int(100e6), max=int(2e9), name="integer_ad9081_ref_clk"
            )
            self._add_equation(
                [
                    self.config["integer_ad9081_ref_clk"]
                    == self.config["ad9081_ref_clk"]
                ]
            )
        else:
            raise Exception("Only CPLEX solver supported")

        return self.config["ad9081_ref_clk"]

    def get_required_clocks(self) -> List:
        """Generate list required clocks.

        For AD9081 this will contain [converter clock, sysref requirement SOS]

        Returns:
            List: List of solver variables, equations, and constants
        """
        # SYSREF
        self.config = {}
        self.config["lmfc_divisor_sysref"] = self._convert_input(
            self._lmfc_divisor_sysref_available, "lmfc_divisor_sysref"
        )

        self.config["sysref"] = self._add_intermediate(
            self.multiframe_clock / self.config["lmfc_divisor_sysref"]
        )

        # Device Clocking
        if self.clocking_option == "direct":
            # raise Exception("Not implemented yet")
            clk = self.sample_clock * self.datapath_decimation
        else:
            clk = self._pll_config()  # type: ignore

        # Objectives
        # self.model.Obj(self.config["sysref"])  # This breaks many searches
        # self.model.Obj(-1*self.config["lmfc_divisor_sysref"])

        return [clk, self.config["sysref"]]

get_config(solution=None)

Extract configurations from solver results.

Collect internal converter configuration and output clock definitions leading to connected devices (clock chips, FPGAs)

Parameters:

Name Type Description Default
solution CpoSolveResult

CPlex solution. Only needed for CPlex solver

None

Returns:

Name Type Description
Dict Dict

Dictionary of clocking rates and dividers for configuration

Source code in adijif/converters/ad9081.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
def get_config(self, solution: CpoSolveResult = None) -> Dict:
    """Extract configurations from solver results.

    Collect internal converter configuration and output clock definitions
    leading to connected devices (clock chips, FPGAs)

    Args:
        solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver

    Returns:
        Dict: Dictionary of clocking rates and dividers for configuration
    """
    if solution:
        self.solution = solution
    if self.clocking_option == "integrated_pll":
        pll_config: Dict = {
            "m_vco": self._get_val(self.config["ad9081_m_vco"]),
            "n_vco": self._get_val(self.config["ad9081_n_vco"]),
            "r": self._get_val(self.config["ad9081_r"]),
            "d": self._get_val(self.config["ad9081_d"]),
        }
        if "serdes_pll_div" in self.config:
            pll_config["serdes_pll_div"] = self._get_val(
                self.config["serdes_pll_div"]
            )
        return {
            "clocking_option": self.clocking_option,
            "pll_config": pll_config,
        }
    else:
        if "serdes_pll_div" in self.config:
            return {
                "serdes_pll_div": self._get_val(
                    self.config["serdes_pll_div"]
                ),
                "clocking_option": self.clocking_option,
            }
        return {"clocking_option": self.clocking_option}

get_required_clock_names()

Get list of strings of names of requested clocks.

This list of names is for the clocks defined by get_required_clocks

Returns:

Type Description
List[str]

List[str]: List of strings of clock names in order

Source code in adijif/converters/ad9081.py
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def get_required_clock_names(self) -> List[str]:
    """Get list of strings of names of requested clocks.

    This list of names is for the clocks defined by get_required_clocks

    Returns:
        List[str]: List of strings of clock names in order
    """
    clk = (
        "ad9081_dac_clock"
        if self.clocking_option == "direct"
        else "ad9081_pll_ref"
    )
    return [clk, "ad9081_sysref"]

get_required_clocks()

Generate list required clocks.

For AD9081 this will contain [converter clock, sysref requirement SOS]

Returns:

Name Type Description
List List

List of solver variables, equations, and constants

Source code in adijif/converters/ad9081.py
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
def get_required_clocks(self) -> List:
    """Generate list required clocks.

    For AD9081 this will contain [converter clock, sysref requirement SOS]

    Returns:
        List: List of solver variables, equations, and constants
    """
    # SYSREF
    self.config = {}
    self.config["lmfc_divisor_sysref"] = self._convert_input(
        self._lmfc_divisor_sysref_available, "lmfc_divisor_sysref"
    )

    self.config["sysref"] = self._add_intermediate(
        self.multiframe_clock / self.config["lmfc_divisor_sysref"]
    )

    # Device Clocking
    if self.clocking_option == "direct":
        # raise Exception("Not implemented yet")
        clk = self.sample_clock * self.datapath_decimation
    else:
        clk = self._pll_config()  # type: ignore

    # Objectives
    # self.model.Obj(self.config["sysref"])  # This breaks many searches
    # self.model.Obj(-1*self.config["lmfc_divisor_sysref"])

    return [clk, self.config["sysref"]]

ad9081_rx

Bases: adc, ad9081_core

AD9081 Receive model.

Source code in adijif/converters/ad9081.py
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
class ad9081_rx(adc, ad9081_core):
    """AD9081 Receive model."""

    name = "AD9081_RX"
    converter_type = "adc"

    converter_clock_min = 1.45e9
    converter_clock_max = 4e9

    sample_clock_min = 312.5e6 / 16
    sample_clock_max = 4e9

    quick_configuration_modes = _load_rx_config_modes()

    datapath = ad9081_dp_rx()
    decimation_available = [
        1,
        2,
        3,
        4,
        6,
        8,
        9,
        12,
        16,
        18,
        24,
        32,
        36,
        48,
        64,
        72,
        96,
        144,
        "auto",
    ]

    @property
    def decimation(self) -> int:
        """Decimation factor. This is the product of the coarse and fine decimation."""
        return self.datapath.decimation_overall

    @decimation.setter
    def decimation(self, value: int) -> None:
        raise Exception(
            "Decimation is not writable and should be set by the properties\n"
            + " datapath.cddc_decimations and datapath.fddc_decimations"
        )

    _adc_lmfc_divisor_sysref = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]

    def __init__(
        self, model: Union[GEKKO, CpoModel] = None, solver: str = None
    ) -> None:
        """Initialize AD9081 clocking model for RX.

        This is a common class used to handle RX constraints
        together.

        Args:
            model (GEKKO,CpoModel): Solver model
            solver (str): Solver name (gekko or CPLEX)
        """
        if solver:
            self.solver = solver
        if model:
            self.model = model
        self.set_quick_configuration_mode("3.01", "jesd204b")

    def _converter_clock_config(self) -> None:
        """RX specific configuration of internal PLL config.

        This method will update the config struct to include
        the RX clocking constraints
        """
        adc_clk = self.decimation * self.sample_clock
        self.config["l"] = self._convert_input([1, 2, 3, 4], "l")
        self.config["adc_clk"] = self._convert_input(adc_clk)
        self.config["converter_clk"] = self._add_intermediate(
            self.config["adc_clk"] * self.config["l"]
        )

    def _check_valid_internal_configuration(self) -> None:
        mode = self._check_valid_jesd_mode()
        cfg = self.quick_configuration_modes[self.jesd_class][mode]

        # Check decimation is valid
        if isinstance(self.decimation, int) or isinstance(
            self.decimation, float
        ):
            found = False
            for dec in cfg["decimations"]:
                found = found or dec["coarse"] * dec["fine"] == self.decimation
            assert (
                found
            ), f"Decimation {self.decimation} not valid for current JESD mode"
        elif self.decimation == "auto":
            for dec in cfg["decimations"]:
                dec = dec["coarse"] * dec["fine"]
                # Check
                cc = dec * self.sample_clock
                # if dec == 64:
                #     print("dec", dec, cc, cfg["coarse"], cfg["fine"])
                if (
                    cc <= self.converter_clock_max
                    and cc >= self.converter_clock_min
                ):
                    self.decimation = dec
                    print("Decimation automatically determined:", dec)
                    return
            raise Exception("No valid decimation found")
        else:
            raise Exception("Decimation not valid")

decimation: int property writable

Decimation factor. This is the product of the coarse and fine decimation.

__init__(model=None, solver=None)

Initialize AD9081 clocking model for RX.

This is a common class used to handle RX constraints together.

Parameters:

Name Type Description Default
model (GEKKO, CpoModel)

Solver model

None
solver str

Solver name (gekko or CPLEX)

None
Source code in adijif/converters/ad9081.py
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
def __init__(
    self, model: Union[GEKKO, CpoModel] = None, solver: str = None
) -> None:
    """Initialize AD9081 clocking model for RX.

    This is a common class used to handle RX constraints
    together.

    Args:
        model (GEKKO,CpoModel): Solver model
        solver (str): Solver name (gekko or CPLEX)
    """
    if solver:
        self.solver = solver
    if model:
        self.model = model
    self.set_quick_configuration_mode("3.01", "jesd204b")

ad9081_tx

Bases: dac, ad9081_core

AD9081 Transmit model.

Source code in adijif/converters/ad9081.py
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
class ad9081_tx(dac, ad9081_core):
    """AD9081 Transmit model."""

    name = "AD9081_TX"
    converter_type = "dac"

    converter_clock_min = 2.9e9
    converter_clock_max = 12e9

    sample_clock_min = 2.9e9 / (6 * 24)  # with max interpolation
    sample_clock_max = 12e9

    quick_configuration_modes = _load_tx_config_modes()

    datapath = ad9081_dp_tx()
    interpolation_available = [
        1,
        2,
        3,
        4,
        6,
        8,
        9,
        12,
        16,
        18,
        24,
        32,
        36,
        48,
        64,
        72,
        96,
        144,
    ]

    @property
    def interpolation(self) -> int:
        """Interpolation factor.

        This is the product of the coarse and fine interpolation.

        Returns:
            int: Interpolation factor
        """
        return self.datapath.interpolation_overall

    @interpolation.setter
    def interpolation(self, value: int) -> None:
        raise Exception(
            "Interpolation is not writable and should be set by the properties\n"
            + " datapath.cduc_interpolation and datapath.fduc_interpolation"
        )

    _dac_lmfc_divisor_sysref = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]

    def __init__(
        self, model: Union[GEKKO, CpoModel] = None, solver: str = None
    ) -> None:
        """Initialize AD9081 clocking model for TX.

        This is a common class used to handle TX constraints
        together.

        Args:
            model (GEKKO,CpoModel): Solver model
            solver (str): Solver name (gekko or CPLEX)
        """
        if solver:
            self.solver = solver
        if model:
            self.model = model
        self.set_quick_configuration_mode("0", "jesd204c")

    def _converter_clock_config(self) -> None:
        """TX specific configuration of internall PLL config.

        This method will update the config struct to include
        the TX clocking constraints
        """
        dac_clk = self.interpolation * self.sample_clock
        self.config["dac_clk"] = self._convert_input(dac_clk)
        self.config["converter_clk"] = self._add_intermediate(
            self.config["dac_clk"]
        )

interpolation: int property writable

Interpolation factor.

This is the product of the coarse and fine interpolation.

Returns:

Name Type Description
int int

Interpolation factor

__init__(model=None, solver=None)

Initialize AD9081 clocking model for TX.

This is a common class used to handle TX constraints together.

Parameters:

Name Type Description Default
model (GEKKO, CpoModel)

Solver model

None
solver str

Solver name (gekko or CPLEX)

None
Source code in adijif/converters/ad9081.py
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
def __init__(
    self, model: Union[GEKKO, CpoModel] = None, solver: str = None
) -> None:
    """Initialize AD9081 clocking model for TX.

    This is a common class used to handle TX constraints
    together.

    Args:
        model (GEKKO,CpoModel): Solver model
        solver (str): Solver name (gekko or CPLEX)
    """
    if solver:
        self.solver = solver
    if model:
        self.model = model
    self.set_quick_configuration_mode("0", "jesd204c")

ad9082

Bases: ad9081

AD9081 combined transmit and receive model.

Source code in adijif/converters/ad9081.py
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
class ad9082(ad9081):
    """AD9081 combined transmit and receive model."""

    converter_clock_min = ad9082_rx.converter_clock_min
    converter_clock_max = ad9082_rx.converter_clock_max
    quick_configuration_modes: Dict[str, Any] = {}
    _nested = ["adc", "dac"]

    def __init__(
        self, model: Union[GEKKO, CpoModel] = None, solver: str = None
    ) -> None:
        """Initialize AD9081 clocking model for TX and RX.

        This is a common class used to handle TX and RX constraints
        together.

        Args:
            model (GEKKO,CpoModel): Solver model
            solver (str): Solver name (gekko or CPLEX)
        """
        if solver:
            self.solver = solver
        self.adc = ad9082_rx(model, solver=self.solver)
        self.dac = ad9082_tx(model, solver=self.solver)
        self.model = model

    def _get_converters(self) -> List[Union[converter, converter]]:
        return [self.adc, self.dac]

    def get_required_clock_names(self) -> List[str]:
        """Get list of strings of names of requested clocks.

        This list of names is for the clocks defined by get_required_clocks

        Returns:
            List[str]: List of strings of clock names in order
        """
        clk = (
            "ad9082_dac_clock"
            if self.adc.clocking_option == "direct"
            else "ad9082_pll_ref"
        )
        return [clk, "ad9082_adc_sysref", "ad9082_dac_sysref"]

__init__(model=None, solver=None)

Initialize AD9081 clocking model for TX and RX.

This is a common class used to handle TX and RX constraints together.

Parameters:

Name Type Description Default
model (GEKKO, CpoModel)

Solver model

None
solver str

Solver name (gekko or CPLEX)

None
Source code in adijif/converters/ad9081.py
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
def __init__(
    self, model: Union[GEKKO, CpoModel] = None, solver: str = None
) -> None:
    """Initialize AD9081 clocking model for TX and RX.

    This is a common class used to handle TX and RX constraints
    together.

    Args:
        model (GEKKO,CpoModel): Solver model
        solver (str): Solver name (gekko or CPLEX)
    """
    if solver:
        self.solver = solver
    self.adc = ad9082_rx(model, solver=self.solver)
    self.dac = ad9082_tx(model, solver=self.solver)
    self.model = model

get_required_clock_names()

Get list of strings of names of requested clocks.

This list of names is for the clocks defined by get_required_clocks

Returns:

Type Description
List[str]

List[str]: List of strings of clock names in order

Source code in adijif/converters/ad9081.py
689
690
691
692
693
694
695
696
697
698
699
700
701
702
def get_required_clock_names(self) -> List[str]:
    """Get list of strings of names of requested clocks.

    This list of names is for the clocks defined by get_required_clocks

    Returns:
        List[str]: List of strings of clock names in order
    """
    clk = (
        "ad9082_dac_clock"
        if self.adc.clocking_option == "direct"
        else "ad9082_pll_ref"
    )
    return [clk, "ad9082_adc_sysref", "ad9082_dac_sysref"]

ad9082_rx

Bases: ad9081_rx

AD9082 MxFE RX Clocking Model.

Source code in adijif/converters/ad9081.py
648
649
650
651
class ad9082_rx(ad9081_rx):
    """AD9082 MxFE RX Clocking Model."""

    converter_clock_max = 6e9

ad9082_tx

Bases: ad9081_tx

AD9082 MxFE TX Clocking Model.

Source code in adijif/converters/ad9081.py
654
655
656
657
class ad9082_tx(ad9081_tx):
    """AD9082 MxFE TX Clocking Model."""

    pass

AD9680 high speed ADC clocking model.

ad9680

Bases: ad9680_bf

AD9680 high speed ADC model.

This model supports direct clock configurations

Clocking: AD9680 has directly clocked ADC that have optional input dividers. The sample rate can be determined as follows:

baseband_sample_rate = (input_clock / input_clock_divider) / datapath_decimation
Source code in adijif/converters/ad9680.py
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
class ad9680(ad9680_bf):
    """AD9680 high speed ADC model.

    This model supports direct clock configurations

    Clocking: AD9680 has directly clocked ADC that have optional input dividers.
    The sample rate can be determined as follows:

        baseband_sample_rate = (input_clock / input_clock_divider) / datapath_decimation
    """

    name = "AD9680"
    converter_type = "adc"

    # JESD parameters
    _jesd_params_to_skip_check = ["DualLink", "CS", "N", "HD"]
    available_jesd_modes = ["jesd204b"]
    K_available = [4, 8, 12, 16, 20, 24, 28, 32]
    L_available = [1, 2, 4]
    M_available = [1, 2, 4, 8]
    N_available = [*range(7, 16)]
    Np_available = [8, 16]
    F_available = [1, 2, 4, 8, 16]
    CS_available = [0, 1, 2, 3]
    CF_available = [0]
    S_available = [1, 2, 4]

    # Clock constraints
    clocking_option_available = ["direct"]
    _clocking_option = "direct"
    converter_clock_min = 300e6
    converter_clock_max = 1.25e9
    bit_clock_min_available = {"jesd204b": 3.125e9}
    bit_clock_max_available = {"jesd204b": 12.5e9}
    sample_clock_min = 300e6
    sample_clock_max = 1250e6
    decimation_available = [1, 2, 4, 8, 16]
    _decimation = 1
    _lmfc_sys_divisor = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    quick_configuration_modes = {"jesd204b": quick_configuration_modes}

    # Input clock requirements
    input_clock_divider_available = [1, 2, 4, 8]  # FIXME
    input_clock_divider = 1  # FIXME
    input_clock_max = 4e9  # FIXME

    """ Clocking
        AD9680 has directly clocked ADCs that have optional input dividers.
        The sample rate can be determined as follows:

        baseband_sample_rate = (input_clock / input_clock_divider) / datapath_decimation
    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:  # noqa: ANN401
        """Initialize AD9680 class.

        Objects will default to mode 0x88 with 1e9 sample_clock.

        Args:
            *args (Any): Pass through arguments
            **kwargs (Any): Pass through keyword arguments
        """
        # Set default mode
        self.set_quick_configuration_mode(str(0x88))
        self.K = 32
        self.sample_clock = 1e9
        super().__init__(*args, **kwargs)

    def _check_valid_jesd_mode(self) -> str:
        """Verify current JESD configuration for part is valid.

        Returns:
            str: Current JESD mode
        """
        if self.F == 1:
            assert self.K in [20, 24, 28, 32], "Invalid K value for F=1"
        if self.F == 2:
            assert self.K in [12, 16, 20, 24, 28, 32], "Invalid K value for F=1"
        if self.F == 4:
            assert self.K in [
                8,
                12,
                16,
                20,
                24,
                28,
                32,
            ], "Invalid K value for F=1"
        if self.F in [8, 16]:
            assert self.K in [
                4,
                8,
                12,
                16,
                20,
                24,
                28,
                32,
            ], "Invalid K value for F=1"

        return super()._check_valid_jesd_mode()

    def get_config(self, solution: CpoSolveResult = None) -> Dict:
        """Extract configurations from solver results.

        Collect internal converter configuration and output clock definitions
        leading to connected devices (clock chips, FPGAs)

        Args:
            solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver

        Returns:
            Dict: Dictionary of clocking rates and dividers for configuration
        """
        return {
            "clocking_option": self.clocking_option,
            "decimation": self.decimation,
        }

    def get_required_clock_names(self) -> List[str]:
        """Get list of strings of names of requested clocks.

        This list of names is for the clocks defined by get_required_clocks

        Returns:
            List[str]: List of strings of clock names in order
        """
        return ["ad9680_adc_clock", "ad9680_sysref"]

    def get_required_clocks(self) -> List:
        """Generate list required clocks.

        For AD9680 this will contain [converter clock, sysref requirement SOS]

        Returns:
            List: List of solver variables, equations, and constants
        """
        # possible_sysrefs = []
        # for n in range(1, 10):
        #     r = self.multiframe_clock / (n * n)
        #     if r == int(r) and r > 1e6:
        #         possible_sysrefs.append(r)
        # self.config = {"sysref": self.model.sos1(possible_sysrefs)}

        self.config = {}
        self.config["lmfc_divisor_sysref"] = self._convert_input(
            self._lmfc_sys_divisor,
            default=self._lmfc_sys_divisor[-1],
            name="AD9680_lmfc_divisor_sysref",
        )

        self.config["lmfc_divisor_sysref_squared"] = self._add_intermediate(
            self.config["lmfc_divisor_sysref"]
            * self.config["lmfc_divisor_sysref"]
        )
        self.config["sysref"] = self._add_intermediate(
            self.multiframe_clock / self.config["lmfc_divisor_sysref_squared"]
        )

        # Objectives
        # self.model.Obj(self.config["sysref"])  # This breaks many searches

        return [self.converter_clock, self.config["sysref"]]

input_clock_max = 4000000000.0 class-attribute instance-attribute

Clocking AD9680 has directly clocked ADCs that have optional input dividers. The sample rate can be determined as follows:

baseband_sample_rate = (input_clock / input_clock_divider) / datapath_decimation

__init__(*args, **kwargs)

Initialize AD9680 class.

Objects will default to mode 0x88 with 1e9 sample_clock.

Parameters:

Name Type Description Default
*args Any

Pass through arguments

()
**kwargs Any

Pass through keyword arguments

{}
Source code in adijif/converters/ad9680.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def __init__(self, *args: Any, **kwargs: Any) -> None:  # noqa: ANN401
    """Initialize AD9680 class.

    Objects will default to mode 0x88 with 1e9 sample_clock.

    Args:
        *args (Any): Pass through arguments
        **kwargs (Any): Pass through keyword arguments
    """
    # Set default mode
    self.set_quick_configuration_mode(str(0x88))
    self.K = 32
    self.sample_clock = 1e9
    super().__init__(*args, **kwargs)

get_config(solution=None)

Extract configurations from solver results.

Collect internal converter configuration and output clock definitions leading to connected devices (clock chips, FPGAs)

Parameters:

Name Type Description Default
solution CpoSolveResult

CPlex solution. Only needed for CPlex solver

None

Returns:

Name Type Description
Dict Dict

Dictionary of clocking rates and dividers for configuration

Source code in adijif/converters/ad9680.py
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def get_config(self, solution: CpoSolveResult = None) -> Dict:
    """Extract configurations from solver results.

    Collect internal converter configuration and output clock definitions
    leading to connected devices (clock chips, FPGAs)

    Args:
        solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver

    Returns:
        Dict: Dictionary of clocking rates and dividers for configuration
    """
    return {
        "clocking_option": self.clocking_option,
        "decimation": self.decimation,
    }

get_required_clock_names()

Get list of strings of names of requested clocks.

This list of names is for the clocks defined by get_required_clocks

Returns:

Type Description
List[str]

List[str]: List of strings of clock names in order

Source code in adijif/converters/ad9680.py
167
168
169
170
171
172
173
174
175
def get_required_clock_names(self) -> List[str]:
    """Get list of strings of names of requested clocks.

    This list of names is for the clocks defined by get_required_clocks

    Returns:
        List[str]: List of strings of clock names in order
    """
    return ["ad9680_adc_clock", "ad9680_sysref"]

get_required_clocks()

Generate list required clocks.

For AD9680 this will contain [converter clock, sysref requirement SOS]

Returns:

Name Type Description
List List

List of solver variables, equations, and constants

Source code in adijif/converters/ad9680.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def get_required_clocks(self) -> List:
    """Generate list required clocks.

    For AD9680 this will contain [converter clock, sysref requirement SOS]

    Returns:
        List: List of solver variables, equations, and constants
    """
    # possible_sysrefs = []
    # for n in range(1, 10):
    #     r = self.multiframe_clock / (n * n)
    #     if r == int(r) and r > 1e6:
    #         possible_sysrefs.append(r)
    # self.config = {"sysref": self.model.sos1(possible_sysrefs)}

    self.config = {}
    self.config["lmfc_divisor_sysref"] = self._convert_input(
        self._lmfc_sys_divisor,
        default=self._lmfc_sys_divisor[-1],
        name="AD9680_lmfc_divisor_sysref",
    )

    self.config["lmfc_divisor_sysref_squared"] = self._add_intermediate(
        self.config["lmfc_divisor_sysref"]
        * self.config["lmfc_divisor_sysref"]
    )
    self.config["sysref"] = self._add_intermediate(
        self.multiframe_clock / self.config["lmfc_divisor_sysref_squared"]
    )

    # Objectives
    # self.model.Obj(self.config["sysref"])  # This breaks many searches

    return [self.converter_clock, self.config["sysref"]]

AD9144 high speed DAC clocking model.

ad9144

Bases: ad9144_bf

AD9144 high speed DAC model.

This model supports both direct clock configurations and on-board generation

Clocking: AD9144 has directly clocked DAC that have optional input dividers. The sample rate can be determined as follows:

baseband_sample_rate = (input_clock / input_clock_divider) / datapath_decimation
Source code in adijif/converters/ad9144.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
class ad9144(ad9144_bf):
    """AD9144 high speed DAC model.

    This model supports both direct clock configurations and on-board
    generation

    Clocking: AD9144 has directly clocked DAC that have optional input dividers.
    The sample rate can be determined as follows:

        baseband_sample_rate = (input_clock / input_clock_divider) / datapath_decimation
    """

    name = "AD9144"
    converter_type = "DAC"

    # JESD parameters
    _jesd_params_to_skip_check = ["DualLink", "K"]
    DualLink = False
    available_jesd_modes = ["jesd204b"]
    K_available = [16, 32]
    _K = 32  # Valid for all cases pg 31 of datasheet
    L_available = [1, 2, 4, 8]
    M_available = [1, 2, 4, 4]
    N_available = [*range(7, 16 + 1)]
    Np_available = [8, 16]
    F_available = [1, 2, 4, 8, 16]
    CS_available = [0, 1, 2, 3]
    CF_available = [0]
    S_available = [1, 2]

    # Clock constraints
    clocking_option_available = ["integrated_pll", "direct"]
    _clocking_option = "direct"

    converter_clock_min = 420e6  # checked by dac
    converter_clock_max = 2.8e9  # checked by dac

    bit_clock_min_available = {"jesd204b": 1.44e9}  # checked by jesd
    bit_clock_max_available = {"jesd204b": 12.4e9}  # checked by jesd

    sample_clock_min = 1.44e9 / 40  # checked by jesd
    sample_clock_max = 1.06e9  # checked by jesd

    interpolation_available = [1, 2, 4, 8]
    _interpolation = 1

    quick_configuration_modes = {"jesd204b": quick_configuration_modes}

    # Input clock requirements
    input_clock_divider_available = [
        1,
        2,
        4,
        8,
        16,
    ]  # only used with integrated PLL for now

    # Integrated PLL limits
    pfd_min = 35e6
    pfd_max = 80e6

    # With integrated PLL
    input_clock_min = 35e6
    input_clock_max = 1e9

    def get_config(self, solution: CpoSolveResult = None) -> Dict:
        """Extract configurations from solver results.

        Collect internal converter configuration and output clock definitions
        leading to connected devices (clock chips, FPGAs)

        Args:
            solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver

        Returns:
            Dict: Dictionary of clocking rates and dividers for configuration
        """
        config: Dict = {
            "clocking_option": self.clocking_option,
            "interpolation": self._interpolation,
        }
        if self.clocking_option == "direct":
            return config

        if self.solver == "CPLEX":
            if solution:
                self.solution = solution
            config.update(
                {
                    "BCount": self._get_val(self.config["BCount"]),
                    "ref_div_factor": self._get_val(
                        self.config["ref_div_factor"]
                    ),
                    "lo_div_mode": np.log2(
                        self._get_val(self.config["lo_div_mode_p2"])
                    ),
                }
            )
        return config

    def get_required_clock_names(self) -> List[str]:
        """Get list of strings of names of requested clocks.

        This list of names is for the clocks defined by get_required_clocks

        Returns:
            List[str]: List of strings of clock names in order
        """
        clk = (
            "ad9144_dac_clock"
            if self.clocking_option == "direct"
            else "ad9144_pll_ref"
        )
        return [clk, "ad9144_sysref"]

    def _check_valid_internal_configuration(self) -> None:
        mode = self._check_valid_jesd_mode()
        # cfg = self.quick_configuration_modes[self.jesd_class][mode]

        if mode in ["0", "4", "9"]:
            assert self.K == 32, "K must be 32 for JESD mode 0, 4, or 9"

    def _pll_config(self) -> Dict:
        dac_clk = self.interpolation * self.sample_clock
        self.config["dac_clk"] = self._convert_input(dac_clk, "dac_clk")

        self.config["BCount"] = self._convert_input(
            [*range(6, 127 + 1)], name="BCount"
        )

        # Datasheet says refclk div can support 32 but tables do not reflect this and
        # a div of 32 would put you under supported range

        if self.solver == "gekko":
            self.config["ref_div_factor"] = self.model.sos1(
                self.input_clock_divider_available
            )
            # self.config["ref_div_factor_i"] = self.model.Var(
            #     integer=True, lb=0, ub=4, value=4
            # )
            # self.config["ref_div_factor"] = self.model.Intermediate(
            #     2 ** (self.config["ref_div_factor_i"])
            # )

            self.config["ref_clk"] = self.model.Var(
                integer=False, lb=35e6, ub=1e9, value=35e6
            )
        elif self.solver == "CPLEX":
            self.config["ref_div_factor"] = self._convert_input(
                self.input_clock_divider_available, "ref_div_factor"
            )

            self.config["ref_clk"] = (
                self.config["dac_clk"]
                * self.config["ref_div_factor"]
                / self.config["BCount"]
                / 2
            )

        if dac_clk > 2800e6:
            raise Exception("DAC Clock too fast")
        elif dac_clk >= 1500e6:
            self.config["lo_div_mode_p2"] = self._convert_input(
                2 ** (1 + 1), name="lo_div_mode_p2"
            )
        elif dac_clk >= 720e6:
            self.config["lo_div_mode_p2"] = self._convert_input(
                2 ** (2 + 1), name="lo_div_mode_p2"
            )
        elif dac_clk >= 420e6:
            self.config["lo_div_mode_p2"] = self._convert_input(
                2 ** (3 + 1), name="lo_div_mode_p2"
            )
        else:
            raise Exception("DAC Clock too slow")

        self.config["vco"] = self._add_intermediate(
            self.config["dac_clk"] * self.config["lo_div_mode_p2"]
        )

        self._add_equation(
            [
                self.config["ref_div_factor"] * self.pfd_min
                < self.config["ref_clk"],
                self.config["ref_div_factor"] * self.pfd_max
                > self.config["ref_clk"],
                self.config["ref_clk"] >= self.input_clock_min,
                self.config["ref_clk"] <= self.input_clock_max,
            ]
        )

        if self.solver == "gekko":
            self._add_equation(
                [
                    self.config["ref_clk"] * 2 * self.config["BCount"]
                    == self.config["dac_clk"] * self.config["ref_div_factor"]
                ]
            )

        return self.config["ref_clk"]

    def get_required_clocks(self) -> List:
        """Generate list required clocks.

        For AD9144 this will contain [converter clock, sysref requirement SOS]

        Returns:
            List: List of dictionaries of solver components
        """
        self.config = {}

        self.config["lmfc_divisor_sysref"] = self._convert_input(
            [*range(1, 20 + 1)], name="lmfc_divisor_sysref"
        )

        self.config["sysref"] = self._add_intermediate(
            self.multiframe_clock / (self.config["lmfc_divisor_sysref"])
        )

        if self.clocking_option == "direct":
            clk = self.sample_clock * self.interpolation
            # LaneRate = (20 × DataRate × M)/L
            assert (
                self.bit_clock == (20 * self.sample_clock * self.M) / self.L
            ), "AD9144 direct clock requirement invalid"
        else:
            # vco = dac_clk * 2^(LO_DIV_MODE + 1)
            # 6 GHz <= vco <= 12 GHz
            # BCount = floor( dac_clk/(2 * ref_clk/ref_div ) )
            # 5 <= BCount <= 127
            # ref_div = 2^ref_div_mode = 1,2,4,8,16
            clk = self._pll_config()  # type: ignore

        # Objectives
        # self.model.Obj(self.config["sysref"])  # This breaks many searches
        # self.model.Obj(-1*self.config["lmfc_divisor_sysref"])

        return [clk, self.config["sysref"]]

get_config(solution=None)

Extract configurations from solver results.

Collect internal converter configuration and output clock definitions leading to connected devices (clock chips, FPGAs)

Parameters:

Name Type Description Default
solution CpoSolveResult

CPlex solution. Only needed for CPlex solver

None

Returns:

Name Type Description
Dict Dict

Dictionary of clocking rates and dividers for configuration

Source code in adijif/converters/ad9144.py
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def get_config(self, solution: CpoSolveResult = None) -> Dict:
    """Extract configurations from solver results.

    Collect internal converter configuration and output clock definitions
    leading to connected devices (clock chips, FPGAs)

    Args:
        solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver

    Returns:
        Dict: Dictionary of clocking rates and dividers for configuration
    """
    config: Dict = {
        "clocking_option": self.clocking_option,
        "interpolation": self._interpolation,
    }
    if self.clocking_option == "direct":
        return config

    if self.solver == "CPLEX":
        if solution:
            self.solution = solution
        config.update(
            {
                "BCount": self._get_val(self.config["BCount"]),
                "ref_div_factor": self._get_val(
                    self.config["ref_div_factor"]
                ),
                "lo_div_mode": np.log2(
                    self._get_val(self.config["lo_div_mode_p2"])
                ),
            }
        )
    return config

get_required_clock_names()

Get list of strings of names of requested clocks.

This list of names is for the clocks defined by get_required_clocks

Returns:

Type Description
List[str]

List[str]: List of strings of clock names in order

Source code in adijif/converters/ad9144.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
def get_required_clock_names(self) -> List[str]:
    """Get list of strings of names of requested clocks.

    This list of names is for the clocks defined by get_required_clocks

    Returns:
        List[str]: List of strings of clock names in order
    """
    clk = (
        "ad9144_dac_clock"
        if self.clocking_option == "direct"
        else "ad9144_pll_ref"
    )
    return [clk, "ad9144_sysref"]

get_required_clocks()

Generate list required clocks.

For AD9144 this will contain [converter clock, sysref requirement SOS]

Returns:

Name Type Description
List List

List of dictionaries of solver components

Source code in adijif/converters/ad9144.py
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
def get_required_clocks(self) -> List:
    """Generate list required clocks.

    For AD9144 this will contain [converter clock, sysref requirement SOS]

    Returns:
        List: List of dictionaries of solver components
    """
    self.config = {}

    self.config["lmfc_divisor_sysref"] = self._convert_input(
        [*range(1, 20 + 1)], name="lmfc_divisor_sysref"
    )

    self.config["sysref"] = self._add_intermediate(
        self.multiframe_clock / (self.config["lmfc_divisor_sysref"])
    )

    if self.clocking_option == "direct":
        clk = self.sample_clock * self.interpolation
        # LaneRate = (20 × DataRate × M)/L
        assert (
            self.bit_clock == (20 * self.sample_clock * self.M) / self.L
        ), "AD9144 direct clock requirement invalid"
    else:
        # vco = dac_clk * 2^(LO_DIV_MODE + 1)
        # 6 GHz <= vco <= 12 GHz
        # BCount = floor( dac_clk/(2 * ref_clk/ref_div ) )
        # 5 <= BCount <= 127
        # ref_div = 2^ref_div_mode = 1,2,4,8,16
        clk = self._pll_config()  # type: ignore

    # Objectives
    # self.model.Obj(self.config["sysref"])  # This breaks many searches
    # self.model.Obj(-1*self.config["lmfc_divisor_sysref"])

    return [clk, self.config["sysref"]]

ADRV9009 transceiver clocking model.

adrv9009

Bases: adrv9009_core

ADRV9009 combined transmit and receive model.

Source code in adijif/converters/adrv9009.py
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
class adrv9009(adrv9009_core):
    """ADRV9009 combined transmit and receive model."""

    name = "ADRV9009"
    solver = "CPLEX"
    _nested = ["adc", "dac"]
    converter_type = "adc_dac"

    def __init__(
        self, model: Union[GEKKO, CpoModel] = None, solver: str = None
    ) -> None:
        """Initialize ADRV9009 clocking model for TX and RX.

        This is a common class used to handle TX and RX constraints
        together.

        Args:
            model (GEKKO,CpoModel): Solver model
            solver (str): Solver name (gekko or CPLEX)
        """
        if solver:
            self.solver = solver
        self.adc = adrv9009_rx(model, solver=self.solver)
        self.dac = adrv9009_tx(model, solver=self.solver)
        self.model = model

    def validate_config(self) -> None:
        """Validate device configurations including JESD and clocks of both ADC and DAC.

        This check only is for static configuration that does not include
        variables which are solved.
        """
        self.adc.validate_config()
        self.dac.validate_config()

    def _get_converters(self) -> List[Union[converter, converter]]:
        return [self.adc, self.dac]

    def get_required_clocks(self) -> List[Dict]:
        """Generate list of required clocks.

        For ADRV9009 this will contain:
        [device clock requirement SOS, sysref requirement SOS]

        Returns:
            list[dict]: List of dictionaries of solver variables, equations, and constants

        Raises:
            Exception: Invalid relation of rates between RX and TX
            AssertionError: Gekko called
        """
        # Validate sample rates feasible
        if self.dac.sample_clock / self.adc.sample_clock not in [
            1,
            2,
            4,
        ] or self.adc.sample_clock / self.dac.sample_clock not in [1, 2, 4]:
            raise Exception(
                "ADRV9009 RX and TX sample rates must be related by power of 2"
            )

        if self.solver == "gekko":
            raise AssertionError

        self.config = {}
        self.config["adc_lmfc_divisor_sysref"] = self._convert_input(
            self._lmfc_divisor_sysref_available, name="adc_lmfc_divisor_sysref"
        )
        self.config["dac_lmfc_divisor_sysref"] = self._convert_input(
            self._lmfc_divisor_sysref_available, name="dac_lmfc_divisor_sysref"
        )

        self.config["input_clock_divider_x2"] = self._convert_input(
            self.input_clock_dividers_times2_available
        )

        faster_clk = max([self.adc.sample_clock, self.dac.sample_clock])
        self.config["device_clock"] = self._add_intermediate(
            faster_clk / self.config["input_clock_divider_x2"]
        )

        self.config["sysref_adc"] = self._add_intermediate(
            self.adc.multiframe_clock / self.config["adc_lmfc_divisor_sysref"]
        )
        self.config["sysref_dac"] = self._add_intermediate(
            self.dac.multiframe_clock / self.config["dac_lmfc_divisor_sysref"]
        )

        self._add_equation(
            [
                self.device_clock_min <= self.config["device_clock"],
                self.config["device_clock"] <= self.device_clock_max,
            ]
        )

        return [
            self.config["device_clock"],
            self.config["sysref_adc"],
            self.config["sysref_dac"],
        ]

__init__(model=None, solver=None)

Initialize ADRV9009 clocking model for TX and RX.

This is a common class used to handle TX and RX constraints together.

Parameters:

Name Type Description Default
model (GEKKO, CpoModel)

Solver model

None
solver str

Solver name (gekko or CPLEX)

None
Source code in adijif/converters/adrv9009.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
def __init__(
    self, model: Union[GEKKO, CpoModel] = None, solver: str = None
) -> None:
    """Initialize ADRV9009 clocking model for TX and RX.

    This is a common class used to handle TX and RX constraints
    together.

    Args:
        model (GEKKO,CpoModel): Solver model
        solver (str): Solver name (gekko or CPLEX)
    """
    if solver:
        self.solver = solver
    self.adc = adrv9009_rx(model, solver=self.solver)
    self.dac = adrv9009_tx(model, solver=self.solver)
    self.model = model

get_required_clocks()

Generate list of required clocks.

For ADRV9009 this will contain: [device clock requirement SOS, sysref requirement SOS]

Returns:

Type Description
List[Dict]

list[dict]: List of dictionaries of solver variables, equations, and constants

Raises:

Type Description
Exception

Invalid relation of rates between RX and TX

AssertionError

Gekko called

Source code in adijif/converters/adrv9009.py
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
def get_required_clocks(self) -> List[Dict]:
    """Generate list of required clocks.

    For ADRV9009 this will contain:
    [device clock requirement SOS, sysref requirement SOS]

    Returns:
        list[dict]: List of dictionaries of solver variables, equations, and constants

    Raises:
        Exception: Invalid relation of rates between RX and TX
        AssertionError: Gekko called
    """
    # Validate sample rates feasible
    if self.dac.sample_clock / self.adc.sample_clock not in [
        1,
        2,
        4,
    ] or self.adc.sample_clock / self.dac.sample_clock not in [1, 2, 4]:
        raise Exception(
            "ADRV9009 RX and TX sample rates must be related by power of 2"
        )

    if self.solver == "gekko":
        raise AssertionError

    self.config = {}
    self.config["adc_lmfc_divisor_sysref"] = self._convert_input(
        self._lmfc_divisor_sysref_available, name="adc_lmfc_divisor_sysref"
    )
    self.config["dac_lmfc_divisor_sysref"] = self._convert_input(
        self._lmfc_divisor_sysref_available, name="dac_lmfc_divisor_sysref"
    )

    self.config["input_clock_divider_x2"] = self._convert_input(
        self.input_clock_dividers_times2_available
    )

    faster_clk = max([self.adc.sample_clock, self.dac.sample_clock])
    self.config["device_clock"] = self._add_intermediate(
        faster_clk / self.config["input_clock_divider_x2"]
    )

    self.config["sysref_adc"] = self._add_intermediate(
        self.adc.multiframe_clock / self.config["adc_lmfc_divisor_sysref"]
    )
    self.config["sysref_dac"] = self._add_intermediate(
        self.dac.multiframe_clock / self.config["dac_lmfc_divisor_sysref"]
    )

    self._add_equation(
        [
            self.device_clock_min <= self.config["device_clock"],
            self.config["device_clock"] <= self.device_clock_max,
        ]
    )

    return [
        self.config["device_clock"],
        self.config["sysref_adc"],
        self.config["sysref_dac"],
    ]

validate_config()

Validate device configurations including JESD and clocks of both ADC and DAC.

This check only is for static configuration that does not include variables which are solved.

Source code in adijif/converters/adrv9009.py
298
299
300
301
302
303
304
305
def validate_config(self) -> None:
    """Validate device configurations including JESD and clocks of both ADC and DAC.

    This check only is for static configuration that does not include
    variables which are solved.
    """
    self.adc.validate_config()
    self.dac.validate_config()

adrv9009_clock_common

Bases: adrv9009_core, adrv9009_bf

ADRV9009 class managing common singleton (Rx,Tx) methods.

Source code in adijif/converters/adrv9009.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
class adrv9009_clock_common(adrv9009_core, adrv9009_bf):
    """ADRV9009 class managing common singleton (Rx,Tx) methods."""

    def _check_valid_jesd_mode(self) -> None:
        """Verify current JESD configuration for part is valid."""
        _extra_jesd_check(self)
        return super()._check_valid_jesd_mode()

    def get_config(self, solution: CpoSolveResult = None) -> Dict:
        """Extract configurations from solver results.

        Collect internal converter configuration and output clock definitions
        leading to connected devices (clock chips, FPGAs)

        Args:
            solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver

        Returns:
            Dict: Dictionary of clocking rates and dividers for configuration
        """
        return {"clocking_option": self.clocking_option}

    def _gekko_get_required_clocks(self) -> List[Dict]:
        possible_sysrefs = []
        for n in range(1, 20):
            r = self.multiframe_clock / (n * n)
            if r == int(r):
                possible_sysrefs.append(r)

        self.config = {"sysref": self.model.sos1(possible_sysrefs)}
        self.model.Obj(self.config["sysref"])

        possible_device_clocks = []
        for div in self.input_clock_dividers_available:
            dev_clock = self.sample_clock / div
            if self.device_clock_min <= dev_clock <= self.device_clock_max:
                possible_device_clocks.append(dev_clock)

        self.config["device_clock"] = self.model.sos1(possible_device_clocks)
        # self.model.Obj(-1 * self.config["device_clock"])

        return [self.config["device_clock"], self.config["sysref"]]

    def get_required_clocks(self) -> List[Dict]:
        """Generate list of required clocks.

        For ADRV9009 this will contain:
        [device clock requirement SOS, sysref requirement SOS]

        Returns:
            list[dict]: List of dictionaries of solver variables, equations, and constants
        """
        if self.solver == "gekko":
            return self._gekko_get_required_clocks()
        self.config = {}
        self.config["lmfc_divisor_sysref"] = self._convert_input(
            [*range(1, 20)], name="lmfc_divisor_sysref"
        )

        self.config["input_clock_divider_x2"] = self._convert_input(
            self.input_clock_dividers_times2_available
        )
        self.config["device_clock"] = self._add_intermediate(
            self.sample_clock / self.config["input_clock_divider_x2"]
        )
        self.config["sysref"] = self._add_intermediate(
            self.multiframe_clock
            / (
                self.config["lmfc_divisor_sysref"]
                * self.config["lmfc_divisor_sysref"]
            )
        )

        self._add_equation(
            [
                self.device_clock_min <= self.config["device_clock"],
                self.config["device_clock"] <= self.device_clock_max,
            ]
        )

        return [self.config["device_clock"], self.config["sysref"]]

get_config(solution=None)

Extract configurations from solver results.

Collect internal converter configuration and output clock definitions leading to connected devices (clock chips, FPGAs)

Parameters:

Name Type Description Default
solution CpoSolveResult

CPlex solution. Only needed for CPlex solver

None

Returns:

Name Type Description
Dict Dict

Dictionary of clocking rates and dividers for configuration

Source code in adijif/converters/adrv9009.py
116
117
118
119
120
121
122
123
124
125
126
127
128
def get_config(self, solution: CpoSolveResult = None) -> Dict:
    """Extract configurations from solver results.

    Collect internal converter configuration and output clock definitions
    leading to connected devices (clock chips, FPGAs)

    Args:
        solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver

    Returns:
        Dict: Dictionary of clocking rates and dividers for configuration
    """
    return {"clocking_option": self.clocking_option}

get_required_clocks()

Generate list of required clocks.

For ADRV9009 this will contain: [device clock requirement SOS, sysref requirement SOS]

Returns:

Type Description
List[Dict]

list[dict]: List of dictionaries of solver variables, equations, and constants

Source code in adijif/converters/adrv9009.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def get_required_clocks(self) -> List[Dict]:
    """Generate list of required clocks.

    For ADRV9009 this will contain:
    [device clock requirement SOS, sysref requirement SOS]

    Returns:
        list[dict]: List of dictionaries of solver variables, equations, and constants
    """
    if self.solver == "gekko":
        return self._gekko_get_required_clocks()
    self.config = {}
    self.config["lmfc_divisor_sysref"] = self._convert_input(
        [*range(1, 20)], name="lmfc_divisor_sysref"
    )

    self.config["input_clock_divider_x2"] = self._convert_input(
        self.input_clock_dividers_times2_available
    )
    self.config["device_clock"] = self._add_intermediate(
        self.sample_clock / self.config["input_clock_divider_x2"]
    )
    self.config["sysref"] = self._add_intermediate(
        self.multiframe_clock
        / (
            self.config["lmfc_divisor_sysref"]
            * self.config["lmfc_divisor_sysref"]
        )
    )

    self._add_equation(
        [
            self.device_clock_min <= self.config["device_clock"],
            self.config["device_clock"] <= self.device_clock_max,
        ]
    )

    return [self.config["device_clock"], self.config["sysref"]]

adrv9009_core

Bases: converter

ADRV9009 transceiver clocking model.

This model manage the JESD configuration and input clock constraints. External LO constraints are not modeled.

Clocking: ADRV9009 uses onboard PLLs to generate the JESD clocks

Lane Rate = I/Q Sample Rate * M * Np * (10 / 8) / L
Lane Rate = sample_clock * M * Np * (10 / 8) / L
Source code in adijif/converters/adrv9009.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
class adrv9009_core(converter, metaclass=ABCMeta):
    """ADRV9009 transceiver clocking model.

    This model manage the JESD configuration and input clock constraints.
    External LO constraints are not modeled.

    Clocking: ADRV9009 uses onboard PLLs to generate the JESD clocks

        Lane Rate = I/Q Sample Rate * M * Np * (10 / 8) / L
        Lane Rate = sample_clock * M * Np * (10 / 8) / L
    """

    device_clock_available = None  # FIXME
    device_clock_ranges = None  # FIXME

    name = "ADRV9009"

    # JESD configurations
    quick_configuration_modes = None  # FIXME
    available_jesd_modes = ["jesd204b"]
    M_available = [1, 2, 4]
    L_available = [1, 2, 3, 4, 6, 8]
    N_available = [12, 16]
    Np_available = [12, 16, 24]
    F_available = [1, 2, 3, 4, 8]
    S_available = [1]  # FIXME?
    K_available = [*np.arange(1, 32 + 1)]
    CS_available = [0]
    CF_available = [0]

    # Clock constraints
    converter_clock_min = 39.063e6 * 8
    converter_clock_max = 12288e6

    sample_clock_min = 39.063e6
    sample_clock_max = 491520000

    device_clock_min = 10e6
    device_clock_max = 1e9

    clocking_option_available = ["integrated_pll"]
    _clocking_option = "integrated_pll"

    # Divider ranges
    input_clock_dividers_available = [1 / 2, 1, 2, 4, 8, 16]
    input_clock_dividers_times2_available = [1, 2, 4, 8, 16, 32]

    _lmfc_divisor_sysref_available = [*range(1, 20)]

    # Unused
    max_rx_sample_clock = 250e6
    max_tx_sample_clock = 500e6
    max_obs_sample_clock = 500e6

    def _check_valid_internal_configuration(self) -> None:
        # FIXME
        pass

    def get_required_clock_names(self) -> List[str]:
        """Get list of strings of names of requested clocks.

        This list of names is for the clocks defined by
        get_required_clocks

        Returns:
            List[str]: List of strings of clock names mapped by get_required_clocks
        """
        return ["adrv9009_device_clock", "adrv9009_sysref"]

    def get_config(self, solution: CpoSolveResult = None) -> Dict:
        """Extract configurations from solver results.

        Collect internal converter configuration and output clock definitions
        leading to connected devices (clock chips, FPGAs)

        Args:
            solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver

        Returns:
            Dict: Dictionary of clocking rates and dividers for configuration
        """
        if solution:
            self.solution = solution
        return {}

get_config(solution=None)

Extract configurations from solver results.

Collect internal converter configuration and output clock definitions leading to connected devices (clock chips, FPGAs)

Parameters:

Name Type Description Default
solution CpoSolveResult

CPlex solution. Only needed for CPlex solver

None

Returns:

Name Type Description
Dict Dict

Dictionary of clocking rates and dividers for configuration

Source code in adijif/converters/adrv9009.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def get_config(self, solution: CpoSolveResult = None) -> Dict:
    """Extract configurations from solver results.

    Collect internal converter configuration and output clock definitions
    leading to connected devices (clock chips, FPGAs)

    Args:
        solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver

    Returns:
        Dict: Dictionary of clocking rates and dividers for configuration
    """
    if solution:
        self.solution = solution
    return {}

get_required_clock_names()

Get list of strings of names of requested clocks.

This list of names is for the clocks defined by get_required_clocks

Returns:

Type Description
List[str]

List[str]: List of strings of clock names mapped by get_required_clocks

Source code in adijif/converters/adrv9009.py
80
81
82
83
84
85
86
87
88
89
def get_required_clock_names(self) -> List[str]:
    """Get list of strings of names of requested clocks.

    This list of names is for the clocks defined by
    get_required_clocks

    Returns:
        List[str]: List of strings of clock names mapped by get_required_clocks
    """
    return ["adrv9009_device_clock", "adrv9009_sysref"]

adrv9009_rx

Bases: adc, adrv9009_clock_common, adrv9009_core

ADRV9009 Receive model.

Source code in adijif/converters/adrv9009.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
class adrv9009_rx(adc, adrv9009_clock_common, adrv9009_core):
    """ADRV9009 Receive model."""

    quick_configuration_modes = {"jesd204b": quick_configuration_modes_rx}
    name = "ADRV9009_RX"
    converter_type = "adc"

    # JESD configurations
    K_available = [*np.arange(1, 32 + 1)]
    L_available = [1, 2, 4]
    M_available = [1, 2, 4]
    N_available = [12, 14, 16, 24]
    Np_available = [12, 16, 24]
    F_available = [
        1,
        2,
        3,
        4,
        6,
        8,
    ]
    CS_available = [0]
    CF_available = [0]
    S_available = [1, 2, 4]

    # Clock constraints
    bit_clock_min_available = {"jesd204b": 3.6864e9}
    bit_clock_max_available = {"jesd204b": 12.288e9}

    """
    ADRV9009 Rx decimation stages.
                    +-----------+
        +-----------+ Dec 5 (5) +---------+
        |           +-----------+         |
        |                                 |
        |   +----------+   +----------+   |  +------------+   +--------------+
    >---+---+ RHB3 (2) +---+ RHB2 (2) +---+--+ RHB1 (1,2) +---+ RFIR (1,2,4) +
            +----------+   +----------+      +------------+   +--------------+

    """
    _decimation = 8
    decimation_available = [4, 5, 8, 10, 16, 20, 32, 40]

bit_clock_max_available = {'jesd204b': 12288000000.0} class-attribute instance-attribute

ADRV9009 Rx decimation stages. +-----------+ +-----------+ Dec 5 (5) +---------+ | +-----------+ | | | | +----------+ +----------+ | +------------+ +--------------+

---+---+ RHB3 (2) +---+ RHB2 (2) +---+--+ RHB1 (1,2) +---+ RFIR (1,2,4) + +----------+ +----------+ +------------+ +--------------+

adrv9009_tx

Bases: dac, adrv9009_clock_common, adrv9009_core

ADRV9009 Transmit model.

Source code in adijif/converters/adrv9009.py
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
class adrv9009_tx(dac, adrv9009_clock_common, adrv9009_core):
    """ADRV9009 Transmit model."""

    quick_configuration_modes = {"jesd204b": quick_configuration_modes_tx}
    name = "ADRV9009_TX"
    converter_type = "dac"

    # JESD configurations
    K_available = [*np.arange(1, 32 + 1)]
    L_available = [1, 2, 4]
    M_available = [1, 2, 4]
    N_available = [12, 16]
    Np_available = [12, 16]
    F_available = [1, 2, 3, 4, 8]
    CS_available = [0]
    CF_available = [0]
    S_available = [1]

    # Clock constraints
    bit_clock_min_available = {"jesd204b": 2457.6e6}
    bit_clock_max_available = {"jesd204b": 12.288e9}

    """
    ADRV9009 Tx interpolation stages.
                             +------------+
        +--------------------+ Int 5  (5) +--------------------+
        |                    +------------+                    |
        |                                                      |
        |   +------------+   +------------+   +------------+   |   +--------------+
    <---+---+ THB3 (1,2) +---+ THB2 (1,2) +---+ THB1 (1,2) +---+---+ TFIR (1,2,4) +
            +------------+   +------------+   +------------+       +--------------+

    """
    _interpolation = 8
    interpolation_available = [1, 2, 4, 5, 8, 10, 16, 20, 32]

bit_clock_max_available = {'jesd204b': 12288000000.0} class-attribute instance-attribute

ADRV9009 Tx interpolation stages. +------------+ +--------------------+ Int 5 (5) +--------------------+ | +------------+ | | | | +------------+ +------------+ +------------+ | +--------------+ <---+---+ THB3 (1,2) +---+ THB2 (1,2) +---+ THB1 (1,2) +---+---+ TFIR (1,2,4) + +------------+ +------------+ +------------+ +--------------+