From 144d4ab2ecb47787596a7b1abaf6a513b6990472 Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Fri, 8 Nov 2024 03:22:56 -0700 Subject: [PATCH 01/17] [WIP] FPGA PLL updates Signed-off-by: Travis F. Collins --- adijif/fpgas/pll_constraints.py | 414 +++++ adijif/fpgas/xilinx.py | 2899 ++++++++++++++++--------------- 2 files changed, 1877 insertions(+), 1436 deletions(-) create mode 100644 adijif/fpgas/pll_constraints.py diff --git a/adijif/fpgas/pll_constraints.py b/adijif/fpgas/pll_constraints.py new file mode 100644 index 0000000..ae56450 --- /dev/null +++ b/adijif/fpgas/pll_constraints.py @@ -0,0 +1,414 @@ +from docplex.cp.modeler import if_then + + +class xilinx_pll_constraints: + # UltraScale+ GTY PLLs + # https://docs.amd.com/v/u/en-US/ug578-ultrascale-gty-transceivers + # GTHs + # https://docs.amd.com/v/u/en-US/ug576-ultrascale-gth-transceivers + # GTXs + # https://docs.amd.com/v/u/en-US/ug476_7Series_Transceivers + + transceivers = {'UltrascalePlus': ['GTYE4', 'GTHE4'], 'Ultrascale': ['GTYE3', 'GTHE3'], '7kSeries': ['GTXE2', 'GTHE2'} + + def add_cpll_contraints(self, config: dict, fpga_ref, converter) -> dict: + """Add Channel PLL (CPLL) constraints. + + This applies to GTH and GTY transceivers. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int): FPGA reference clock. + converter (converter): Converter object. + """ + config[converter.name + "_use_cpll"] = self._convert_input( + [0, 1], converter.name + "_use_cpll" + ) + # v = self.model.integer_var(min=0, max=1, name=converter.name + "_use_cpll2") + # self.model.export_model() + # input() + # return config + + # Add variables + config[converter.name + "_m_cpll"] = self._convert_input( + [1, 2], converter.name + "_m_cpll" + ) + # This is documented oddly + # There are footnotes that include 16 with GTHs, and 16,32 with GTYs + # but its says "not supported for CPLL"?!?!? + config[converter.name + "_d_cpll"] = self._convert_input( + [1, 2, 4, 8], converter.name + "_d_cpll" + ) + config[converter.name + "_n1_cpll"] = self._convert_input( + [4, 5], converter.name + "_n1_cpll" + ) + config[converter.name + "_n2_cpll"] = self._convert_input( + [1, 2, 3, 4, 5], converter.name + "_n2_cpll" + ) + + # Add intermediate variables + config[converter.name + "_pll_out_cpll"] = self._add_intermediate( + fpga_ref + * config[converter.name + "_n1_cpll"] + * config[converter.name + "_n2_cpll"] + / (config[converter.name + "_m_cpll"]) + ) + + # Add constraints + self._add_equation( + [ + if_then( + config[converter.name + "_use_cpll"] == 1, + config[converter.name + "_d_cpll"] * converter.bit_clock + == config[converter.name + "_pll_out_cpll"] * 2, + ), + if_then( + config[converter.name + "_use_cpll"] == 1, + config[converter.name + "_pll_out_cpll"] >= self.vco_min, + ), + if_then( + config[converter.name + "_use_cpll"] == 1, + config[converter.name + "_pll_out_cpll"] <= self.vco_max, + ), + ] + ) + + return config + + # QPLLs + def add_qpll_contraints(self, config: dict, fpga_ref, converter) -> dict: + if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: + return self._add_qpll_contraints_7_series(config, fpga_ref, converter) + + return self._add_qpllN_contraints(config, fpga_ref, converter, 0) + + def add_qpll1_contraints(self, config: dict, fpga_ref, converter): + if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: + config[converter.name + "_use_qpll1"] = 0 + return config + + return self._add_qpllN_contraints(config, fpga_ref, converter, 1) + + def _add_qpllN_contraints(self, config: dict, fpga_ref, converter, n: int) -> dict: + """Add constraints for QPLL{n}. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int): FPGA reference clock. + converter (converter): Converter object. + n (int): QPLL number 0 or 1. + """ + + assert False, "QPLL equations are seem to be wrong based on GT Series and are more based on 7 series vs US vs US+" + # See equation 2-4 and 2-5 in https://docs.amd.com/v/u/en-US/ug576-ultrascale-gth-transceivers (differences is US+ and doesn't seem to be just GTH based) + + if n == 0: + pname = "qpll" + elif n == 1: + pname = "qpll1" + else: + raise Exception("Unsupported QPLL type") + + if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: + config[converter.name + f"_use_{pname}"] = 0 + return config + + # Global flag to use QPLLn + config[converter.name + f"_use_{pname}"] = self._convert_input( + [0, 1], converter.name + f"_use_{pname}" + ) + + # Add variables + config[converter.name + f"_m_{pname}"] = self._convert_input( + [1, 2, 3, 4], converter.name + f"_m_{pname}" + ) + config[converter.name + f"_d_{pname}"] = self._convert_input( + [1, 2, 4, 8, 16, 32], converter.name + f"_d_{pname}" + ) + config[converter.name + f"_n_{pname}"] = self._convert_input( + [*range(16, 160)], converter.name + f"_n_{pname}" + ) + + assert False, "Confirm this is GTH specific" + if self.transceiver_type[:3] == "GTH": + clkout = 2 + else: + clkout = [1, 2] + + config[converter.name + f"_clkout_rate_{pname}"] = self._convert_input( + clkout, converter.name + f"_clkout_rate_{pname}" + ) + config[converter.name + f"_sdm_data_{pname}"] = self.model.integer_var( + min=0, max=(2**24 - 1), name=converter.name + f"_sdm_data_{pname}" + ) + config[converter.name + f"_sdm_width_{pname}"] = self._convert_input( + [16, 20, 24], converter.name + f"_sdm_width_{pname}" + ) + config[converter.name + f"_HIGH_RATE_{pname}"] = self._convert_input( + [0, 1], converter.name + f"_HIGH_RATE_{pname}" + ) + + # Add intermediate variables + config[converter.name + f"_frac_{pname}"] = self._add_intermediate( + config[converter.name + f"_sdm_data_{pname}"] + / (2 ** config[converter.name + f"_sdm_width_{pname}"]) + ) + config[converter.name + f"_n_dot_frac_{pname}"] = self._add_intermediate( + config[converter.name + f"_n_{pname}"] + + config[converter.name + f"_frac_{pname}"] + ) + config[converter.name + f"_pll_out_{pname}"] = self._add_intermediate( + fpga_ref + * config[converter.name + f"_n_dot_frac_{pname}"] + / ( + config[converter.name + f"_m_{pname}"] + * config[converter.name + f"_clkout_rate_{pname}"] + ) + ) + config[converter.name + f"_vco_{pname}"] = self._add_intermediate( + fpga_ref + * config[converter.name + f"_n_dot_frac_{pname}"] + / ( + config[converter.name + f"_m_{pname}"] + * config[converter.name + f"_m_{pname}"] + ) + ) + + # Add constraints + + self._add_equation( + [ + if_then( + config[converter.name + f"_use_{pname}"] == 1, + converter.bit_clock + == config[converter.name + f"_pll_out_{pname}"] + * 2 + / config[converter.name + f"_d_{pname}"], + ), + if_then( + config[converter.name + f"_use_{pname}"] == 1, + config[converter.name + f"_HIGH_RATE_{pname}"] + == (converter.bit_clock >= 28.1e9), + ), + ] + ) + + vco_min = 9.8e9 if n == 0 else 8e9 + vco_max = 16.375e9 if n == 0 else 13e9 + + self._add_equation( + [ + if_then( + config[converter.name + f"_use_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] >= vco_min, + ), + if_then( + config[converter.name + f"_use_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] <= vco_max, + ), + if_then( + config[converter.name + f"_HIGH_RATE_{pname}"] == 1, + config[converter.name + f"_frac_{pname}"] == 0, + ), + ] + ) + + return config + + def _add_qpll_contraints_7_series(self, config: dict, fpga_ref, converter) -> dict: + """Add constraints for QPLL for 7 series FPGAs. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int): FPGA reference clock. + converter (converter): Converter object. + """ + pname = "qpll" + + # if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: + # config[converter.name + f"_use_{pname}"] = 0 + # return config + + # Double check this constraint + if self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: + raise Exception("Invalid GT is for 7 series FPGAs") + + # Global flag to use QPLLn + config[converter.name + f"_use_{pname}"] = self._convert_input( + [0, 1], converter.name + f"_use_{pname}" + ) + + # Add variables + config[converter.name + f"_m_{pname}"] = self._convert_input( + [1, 2, 3, 4], converter.name + f"_m_{pname}" + ) + config[converter.name + f"_d_{pname}"] = self._convert_input( + [1, 2, 4, 8, 16], converter.name + f"_d_{pname}" + ) + config[converter.name + f"_n_{pname}"] = self._convert_input( + [16, 20, 32, 40, 64, 66, 80, 100], converter.name + f"_n_{pname}" + ) + + # Add intermediate variables + config[converter.name + f"_vco_{pname}"] = self._add_intermediate( + fpga_ref + * config[converter.name + f"_n_{pname}"] + / config[converter.name + f"_m_{pname}"] + ) + config[converter.name + f"_pll_out_{pname}"] = self._add_intermediate( + fpga_ref + * config[converter.name + f"_n_{pname}"] + / (config[converter.name + f"_m_{pname}"] * 2) + ) + + # Add constraints + self._add_equation( + [ + if_then( + config[converter.name + f"_use_{pname}"] == 1, + converter.bit_clock + == config[converter.name + f"_pll_out_{pname}"] + * 2 + / config[converter.name + f"_d_{pname}"], + ), + ] + ) + + assert False, "Confirm this is GTH/GTX specific" + if self.transceiver_type[:3] == "GTH": + vco_min = 8e9 + vco_max = 13.1e9 + self._add_equation( + [ + if_then( + config[converter.name + f"_use_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] >= vco_min, + ), + if_then( + config[converter.name + f"_use_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] <= vco_max, + ), + ] + ) + elif self.transceiver_type[:3] == "GTX": + config[converter.name + f"_lower_band_{pname}"] = self._convert_input( + [0, 1], converter.name + f"_lower_band_{pname}" + ) + # Lower band + c1 = if_then( + config[converter.name + f"_use_{pname}"] == 1, + if_then( + config[converter.name + f"_lower_band_{pname}"] == 0, + config[converter.name + f"_vco_{pname}"] >= 5.93e9, + ), + ) + c2 = if_then( + config[converter.name + f"_use_{pname}"] == 1, + if_then( + config[converter.name + f"_lower_band_{pname}"] == 0, + config[converter.name + f"_vco_{pname}"] <= 8e9, + ), + ) + # Upper band + c3 = if_then( + config[converter.name + f"_use_{pname}"] == 1, + if_then( + config[converter.name + f"_lower_band_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] >= 9.8e9, + ), + ) + c4 = if_then( + config[converter.name + f"_use_{pname}"] == 1, + if_then( + config[converter.name + f"_lower_band_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] <= 12.5e9, + ), + ) + self._add_equation([c1, c2, c3, c4]) + + else: + raise Exception("Unsupported transceiver type") + + return config + + def get_cpll_config(self, config: dict, converter, fpga_ref) -> dict: + """Get CPLL configuration. + + Args: + config (dict): Configuration dictionary. + converter (converter): Converter object. + """ + pll_config = {} + pll_config["type"] = "cpll" + for k in ["m", "d", "n1", "n2"]: + pll_config[k] = self._get_val(config[converter.name + "_" + k + "_cpll"]) + + pll_config["vco"] = ( + fpga_ref * pll_config["n1"] * pll_config["n2"] / pll_config["m"] # type: ignore # noqa: B950 + ) + # Check + assert ( + pll_config["vco"] * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 + ), "Invalid CPLL lane rate" + + return pll_config + + def get_qpll_config(self, config: dict, converter, fpga_ref, n: int) -> dict: + """Get QPLL configuration. + + Args: + config (dict): Configuration dictionary. + converter (converter): Converter object. + n (int): QPLL number 0 or 1. + """ + if n == 0: + pname = "qpll" + elif n == 1: + pname = "qpll1" + else: + raise Exception("Unsupported QPLL type") + + if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: + return self.get_qpll_config_7_series(config, converter, fpga_ref) + + pll_config = {} + pll_config["type"] = pname + for k in ["m", "d", "n", "clkout_rate", "sdm_data", "sdm_width", "HIGH_RATE"]: + pll_config[k] = self._get_val( + config[converter.name + "_" + k + "_" + pname] + ) + + pll_config["frac"] = pll_config["sdm_data"] / (2 ** pll_config["sdm_width"]) + pll_config["n_dot_frac"] = pll_config["n"] + pll_config["frac"] + # FIXME: Check clkout_rate between GTH and GTY + pll_config["vco"] = fpga_ref * pll_config["n_dot_frac"] / (pll_config["m"]) + # Check + assert ( + pll_config["vco"] * 2 / (pll_config["d"] * pll_config["clkout_rate"]) == converter.bit_clock # type: ignore # noqa: B950 + ), f"Invalid {pname} lane rate. {pll_config['vco'] * 2 / pll_config['d']} != {converter.bit_clock}" # type: ignore # noqa: B950 + + return pll_config + + def get_qpll_config_7_series(self, config: dict, converter, fpga_ref) -> dict: + """Get QPLL configuration for 7 series FPGAs. + + Args: + config (dict): Configuration dictionary. + converter (converter): Converter object. + """ + pname = "qpll" + + pll_config = {"type": pname} + for k in ["m", "d", "n"]: + pll_config[k] = self._get_val( + config[converter.name + "_" + k + "_" + pname] + ) + + pll_config["vco"] = fpga_ref * pll_config["n"] / pll_config["m"] + pll_clk_out = fpga_ref * pll_config["n"] / (pll_config["m"] * 2) + # Check + assert ( + pll_clk_out * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 + ), f"Invalid QPLL lane rate {pll_config['vco'] * 2 / pll_config['d']} != {converter.bit_clock}" + + return pll_config diff --git a/adijif/fpgas/xilinx.py b/adijif/fpgas/xilinx.py index 1c5a030..d61b4b1 100644 --- a/adijif/fpgas/xilinx.py +++ b/adijif/fpgas/xilinx.py @@ -1,1436 +1,1463 @@ -"""Xilinx FPGA clocking model.""" -from typing import Dict, List, Optional, Union - -from adijif.converters.converter import converter as conv -from adijif.fpgas.xilinx_bf import xilinx_bf -from adijif.solvers import CpoSolveResult # type: ignore -from adijif.solvers import integer_var # type: ignore -from adijif.solvers import CpoIntVar, GK_Intermediate, GK_Operators, GKVariable - - -class xilinx(xilinx_bf): - """Xilinx FPGA clocking model. - - This model captures different limitations of the Xilinx - PLLs and interfaces used for JESD. - - Currently only Zynq 7000 devices have been fully tested. - """ - - favor_cpll_over_qpll = False - minimize_fpga_ref_clock = False - - """Force generation of separate device clock from the clock chip. In many - cases, the ref clock and device clock can be the same.""" - force_separate_device_clock: bool = False - - """Constrain reference clock to be specific values. Options: - - CORE_CLOCK: Make reference clock the same as the core clock (LR/40 or LR/66) - - CORE_CLOCK_DIV2: Make reference clock the same as the core clock divided by 2 - - Unconstrained: No constraints on reference clock. Simply meet PLL constraints - """ - _ref_clock_constraint = "CORE_CLOCK" - - max_serdes_lanes = 24 - - hdl_core_version = 2.1 - - available_speed_grades = [-1, -2, -3] - speed_grade = -2 - - transceiver_voltage = 800 - - ref_clock_min = -1 # Not set - ref_clock_max = -1 # Not set - - available_fpga_packages = [ - "Unknown", - "RF", - "FL", - "FF", - "FB", - "HC", - "FH", - "CS", - "CP", - "FT", - "FG", - "SB", - "RB", - "RS", - "CL", - "SF", - "BA", - "FA", - ] - fpga_package = "FB" - - available_fpga_families = ["Unknown", "Artix", "Kintex", "Virtex", "Zynq"] - fpga_family = "Zynq" - - available_transceiver_types = ["GTX2"] - transciever_type = "GTX2" - - sys_clk_selections = [ - "XCVR_CPLL", - "XCVR_QPLL0", - "XCVR_QPLL1", - ] - sys_clk_select = "XCVR_QPLL1" - - _out_clk_selections = [ - # "XCVR_OUTCLK_PCS", - # "XCVR_OUTCLK_PMA", - "XCVR_REFCLK", - "XCVR_REFCLK_DIV2", - "XCVR_PROGDIV_CLK", - ] - _out_clk_select = [ - # "XCVR_OUTCLK_PCS", - # "XCVR_OUTCLK_PMA", - "XCVR_REFCLK", - "XCVR_REFCLK_DIV2", - "XCVR_PROGDIV_CLK", - ] - - """ Force use of QPLL for transceiver source """ - force_qpll = 0 - - """ Force use of QPLL1 for transceiver source (GTH3,GTH4,GTY4)""" - force_qpll1 = 0 - - """ Force use of CPLL for transceiver source """ - force_cpll = 0 - - """ Force all transceiver sources to be from a single PLL quad. - This will try to leverage the output dividers of the PLLs - """ - force_single_quad_tile = 0 - - """ Request that clock chip generated device clock - device clock == LMFC/40 - NOTE: THIS IS NOT FPGA REF CLOCK - """ - request_device_clock = False - - _clock_names: List[str] = [] - - """When PROGDIV, this will be set to the value of the divider""" - _used_progdiv = {} - - """FPGA target Fmax rate use to determine link layer output rate""" - target_Fmax = 250e6 - - """Require generation of separate clock specifically for link layer""" - requires_separate_link_layer_out_clock = True - - """Require generation of separate core clock (LR/40 or LR/66)""" - requires_core_clock_from_device_clock = False - - configs = [] # type: ignore - - @property - def ref_clock_constraint(self) -> str: - """Get reference clock constraint. - - Reference clock constraint can be set to: - - CORE_CLOCK: Make reference clock the same as the core clock (LR/40 or LR/66) - - CORE_CLOCK_DIV2: Make reference clock the same as the core clock divided by 2 - - Unconstrained: No constraints on reference clock. Simply meet PLL constraints - - Returns: - str: Reference clock constraint. - """ - return self._ref_clock_constraint - - @ref_clock_constraint.setter - def ref_clock_constraint(self, value: str) -> None: - """Set reference clock constraint. - - Reference clock constraint can be set to: - - CORE_CLOCK: Make reference clock the same as the core clock (LR/40 or LR/66) - - CORE_CLOCK_DIV2: Make reference clock the same as the core clock divided by 2 - - Unconstrained: No constraints on reference clock. Simply meet PLL constraints - - Args: - value (str): Reference clock constraint. - - Raises: - Exception: Invalid ref_clock_constraint selection. - """ - if value not in ["CORE_CLOCK", "CORE_CLOCK_DIV2", "Unconstrained"]: - raise Exception( - f"Invalid ref_clock_constraint {value}, " - + "options are CORE_CLOCK, CORE_CLOCK_DIV2, Unconstrained" - ) - self._ref_clock_constraint = value - - @property - def out_clk_select(self) -> Union[int, float]: - """Get current PLL clock output mux options for link layer clock. - - Valid options are: - "XCVR_REFCLK", - "XCVR_REFCLK_DIV2", - "XCVR_PROGDIV_CLK" - If a list of these is provided, the solver will determine one to use - - Returns: - str,list(str): Mux selection for link layer clock. - """ - return self._out_clk_select - - @out_clk_select.setter - def out_clk_select(self, value: Union[str, List[str]]) -> None: - """Set current PLL clock output mux options for link layer clock. - - Valid options are: - "XCVR_REFCLK", - "XCVR_REFCLK_DIV2", - "XCVR_PROGDIV_CLK" - If a list of these is provided, the solver will determine one to use - - Args: - value (str,List[str]): Mux selection for link layer clock. - - Raises: - Exception: Invalid out_clk_select selection. - """ - if isinstance(value, list): - for item in value: - if item not in self._out_clk_selections: - raise Exception( - f"Invalid out_clk_select {item}, " - + f"options are {self._out_clk_selections}" - ) - elif isinstance(value, dict): - for converter in value: - if not isinstance(converter, conv): - raise Exception("Keys of out_clk_select but be of type converter") - if value[converter] not in self._out_clk_selections: - raise Exception( - f"Invalid out_clk_select {value[converter]}, " - + f"options are {self._out_clk_selections}" - ) - elif value not in self._out_clk_selections: # str - raise Exception( - f"Invalid out_clk_select {value}, " - + f"options are {self._out_clk_selections}" - ) - - self._out_clk_select = value - - @property - def _ref_clock_max(self) -> int: - """Get maximum reference clock for config. - - Returns: - int: Rate in samples per second. - - Raises: - Exception: Unsupported transceiver type configured. - """ - # https://www.xilinx.com/support/documentation/data_sheets/ds191-XC7Z030-XC7Z045-data-sheet.pdf # noqa: B950 - if self.transciever_type == "GTX2": - if self.speed_grade == "-3E": - return 700000000 - else: - return 670000000 - else: - raise Exception( - f"Unknown ref_clock_max for transceiver type {self.transciever_type}" - ) - # raise Exception(f"Unknown transceiver type {self.transciever_type}") - - @property - def _ref_clock_min(self) -> int: - """Get minimum reference clock for config. - - Returns: - int: Rate in samples per second. - - Raises: - Exception: Unsupported transceiver type configured. - """ - # https://www.xilinx.com/support/documentation/data_sheets/ds191-XC7Z030-XC7Z045-data-sheet.pdf # noqa: B950 - if self.transciever_type == "GTX2": - return 60000000 - else: - raise Exception( - f"Unknown ref_clock_min for transceiver type {self.transciever_type}" - ) - # raise Exception(f"Unknown transceiver type {self.transciever_type}") - - # CPLL - @property - def vco_min(self) -> int: - """Get minimum CPLL VCO rate for config. - - Returns: - int: Rate in samples per second. - - Raises: - Exception: Unsupported transceiver type configured. - """ - if self.transciever_type == "GTX2": - return 1600000000 - elif self.transciever_type in ["GTH3", "GTH4", "GTY4"]: - return 2000000000 - else: - raise Exception( - f"Unknown vco_min for transceiver type {self.transciever_type}" - ) - - @property - def vco_max(self) -> int: - """Get maximum CPLL VCO rate for config. - - Returns: - int: Rate in samples per second. - - Raises: - Exception: Unsupported transceiver type configured. - """ - if self.transciever_type == "GTX2": - return 3300000000 - elif self.transciever_type in ["GTH3", "GTH4", "GTY4"]: - if self.hdl_core_version > 2: - if self.transciever_type in ["GTH3", "GTH4"]: - if self.transceiver_voltage < 850 or self.speed_grade == -1: - return 4250000000 - elif self.transciever_type == "GTY4" and self.speed_grade == -1: - return 4250000000 - return 6250000000 - else: - raise Exception( - f"Unknown vco_max for transceiver type {self.transciever_type}" - ) - - # QPLL - @property - def vco0_min(self) -> int: - """Get minimum QPLL VCO0 rate for config. - - This is applicable for QPLLs only. - - Returns: - int: Rate in samples per second. - - Raises: - Exception: Unsupported transceiver type configured. - """ - if self.transciever_type == "GTX2": - return 5930000000 - elif self.transciever_type in ["GTH3", "GTH4", "GTY4"]: - if self.sys_clk_select == "XCVR_QPLL1" and self.transciever_type in [ - "GTH3", - "GTH4", - ]: - return 8000000000 - else: - return 9800000000 - else: - raise Exception( - f"Unknown vco0_min for transceiver type {self.transciever_type}" - ) - - @property - def vco0_max(self) -> int: - """Get maximum QPLL VCO0 rate for config. - - This is applicable for QPLLs only. - - Returns: - int: Rate in samples per second. - - Raises: - Exception: Unsupported transceiver type configured. - """ - if self.transciever_type == "GTX2": - if ( - self.hdl_core_version > 2 - and self.fpga_family == "Kintex" - and self.fpga_package in ["FB", "RF", "FF"] - ): - return 6600000000 - return 8000000000 - elif self.transciever_type in ["GTH3", "GTH4", "GTY4"]: - if self.sys_clk_select == "XCVR_QPLL1" and self.transciever_type in [ - "GTH3", - "GTH4", - ]: - return 13000000000 - else: - return 16375000000 - else: - raise Exception( - f"Unknown vco0_max for transceiver type {self.transciever_type}" - ) - - @property - def vco1_min(self) -> int: - """Get minimum QPLL VCO1 rate for config. - - This is applicable for QPLLs only. - - Returns: - int: Rate in samples per second. - - Raises: - Exception: Unsupported transceiver type configured. - """ - if self.transciever_type == "GTX2": - return 9800000000 - elif self.transciever_type in ["GTH3", "GTH4", "GTY4"]: - return self.vco0_min - else: - raise Exception( - f"Unknown vco1_min for transceiver type {self.transciever_type}" - ) - - @property - def vco1_max(self) -> int: - """Get maximum QPLL VCO1 rate for config. - - This is applicable for QPLLs only. - - Returns: - int: Rate in samples per second. - - Raises: - Exception: Unsupported transceiver type configured. - """ - if self.transciever_type == "GTX2": - if self.hdl_core_version > 2 and self.speed_grade == -2: - return 10312500000 - return 12500000000 - elif self.transciever_type in ["GTH3", "GTH4", "GTY4"]: - return self.vco0_max - else: - raise Exception( - f"Unknown vco1_max for transceiver type {self.transciever_type}" - ) - - @property - def N(self) -> List[int]: - """Get available feedback divider settings. - - This is applicable for QPLLs only. - - Returns: - list[int]: List of divider integers. - - Raises: - Exception: Unsupported transceiver type configured. - """ - if self.transciever_type == "GTX2": - return [16, 20, 32, 40, 64, 66, 80, 100] - elif self.transciever_type in ["GTH3", "GTH4", "GTY4"]: - return [16, 20, 32, 40, 64, 66, 75, 80, 100, 112, 120, 125, 150, 160] - else: - raise Exception( - "Unknown N (feedback dividers) for transceiver type" - " {}".format(self.transciever_type) - ) - - def setup_by_dev_kit_name(self, name: str) -> None: - """Configure object based on board name. Ex: zc706, zcu102. - - Args: - name (str): Name of dev kit. Ex: zc706, zcu102 - - Raises: - Exception: Unsupported board requested. - - """ - if name.lower() == "zc706": - self.transciever_type = "GTX2" - self.fpga_family = "Zynq" - self.fpga_package = "FF" - self.speed_grade = -2 - self.ref_clock_min = 60000000 - self.ref_clock_max = 670000000 - self.max_serdes_lanes = 8 - # default PROGDIV not available - o = self._out_clk_selections.copy() - del o[o.index("XCVR_PROGDIV_CLK")] - self._out_clk_selections = o - self._out_clk_select = o - elif name.lower() == "zcu102": - self.transciever_type = "GTH4" - self.fpga_family = "Zynq" - self.fpga_package = "FF" - self.speed_grade = -2 - self.ref_clock_min = 60000000 - self.ref_clock_max = 820000000 - self.max_serdes_lanes = 8 - elif name.lower() == "vcu118": - # XCVU9P-L2FLGA2104 - self.transciever_type = "GTY4" - self.fpga_family = "Virtex" - self.fpga_package = "FL" - self.speed_grade = -2 - self.ref_clock_min = 60000000 - self.ref_clock_max = 820000000 - self.max_serdes_lanes = 24 - else: - raise Exception(f"No boardname found in library for {name}") - - def determine_pll(self, bit_clock: int, fpga_ref_clock: int) -> Dict: - """Determine if configuration is possible with CPLL or QPLL. - - CPLL is checked first and will check QPLL if that case is - invalid. - - This is only used for brute-force implementations. - - Args: - bit_clock (int): Equivalent to lane rate in bits/second - fpga_ref_clock (int): System reference clock - - Returns: - Dict: Dictionary of PLL configuration - """ - try: - info = self.determine_cpll(bit_clock, fpga_ref_clock) - except: # noqa: B001 - info = self.determine_qpll(bit_clock, fpga_ref_clock) - return info - - 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 - - Raises: - Exception: Clock have not been enumerated aka get_required_clocks not - not called yet. - """ - if not self._clock_names: - raise Exception( - "get_required_clocks must be run to generated" - + " dependent clocks before names are available" - ) - return self._clock_names - - def get_config( - self, - converter: conv, - fpga_ref: Union[float, int], - solution: Optional[CpoSolveResult] = None, - ) -> Union[List[Dict], Dict]: - """Extract configurations from solver results. - - Collect internal FPGA configuration and output clock definitions. - - Args: - converter (conv): Converter object connected to FPGA who config is - collected - fpga_ref (int or float): Reference clock generated for FPGA for specific - converter - solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver - - Raises: - Exception: Invalid PLL configuration. - - Returns: - Dict: Dictionary of clocking rates and dividers for configuration - """ - out = [] - if solution: - self.solution = solution - - for config in self.configs: - pll_config: Dict[str, Union[str, int, float]] = {} - - # Filter out other converters - if converter.name + "_use_cpll" not in config.keys(): - continue - # pll = self._get_val(config[converter.name + "qpll_0_cpll_1"]) - cpll = self._get_val(config[converter.name + "_use_cpll"]) > 0 - qpll = self._get_val(config[converter.name + "_use_qpll"]) > 0 - qpll1 = self._get_val(config[converter.name + "_use_qpll1"]) > 0 - - if sum([cpll, qpll, qpll1]) != 1: - raise Exception( - "More than one PLL selected" - + " can only be one of CPLL, QPLL, or QPLL1" - ) - - if cpll > 0: # type: ignore - pll_config["type"] = "cpll" - for k in ["m", "d", "n1", "n2"]: - pll_config[k] = self._get_val(config[converter.name + k + "_cpll"]) - - pll_config["vco"] = ( - fpga_ref * pll_config["n1"] * pll_config["n2"] / pll_config["m"] # type: ignore # noqa: B950 - ) - # Check - assert ( - pll_config["vco"] * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 - ), "Invalid CPLL lane rate" - else: - pll_name = "qpll" if qpll else "qpll1" - pll_config["type"] = pll_name - pll_name = "_" + pll_name - if self.transciever_type in ["GTY4"]: - args = ["m", "d", "band"] - else: - args = ["m", "d", "n", "band"] - - for k in args: - pll_config[k] = self._get_val(config[converter.name + k + pll_name]) # type: ignore # noqa: B950 - pll_config["qty4_full_rate_enabled"] = 1 - pll_config["band"] # type: ignore # noqa: B950 - - if self.transciever_type in ["GTY4"]: - pll_config["frac_mode"] = not self._get_val( - config[converter.name + "qpll_frac_bypass"] - ) - pll_config["qpll_clkoutrate"] = self._get_val( - config[converter.name + "qpll_clkoutrate"] - ) - pll_config["qpll_sdmdata"] = self._get_val( - config[converter.name + "qpll_sdmdata"] - ) - pll_config["qpll_sdmwidth"] = self._get_val( - config[converter.name + "qpll_sdmwidth"] - ) - - pll_config["qpll_N_dot_frac"] = self.solution.get_kpis()[ - converter.name + "qpll_N_dot_frac" - ] - - config["vco"] = self._add_intermediate( - fpga_ref - * pll_config["qpll_N_dot_frac"] - / (pll_config["m"] * pll_config["qpll_clkoutrate"]) - ) - - else: - pll_config["vco"] = fpga_ref * pll_config["n"] / pll_config["m"] # type: ignore # noqa: B950 - - # SERDES output mux - if pll_config["type"] == "cpll": - pll_config["sys_clk_select"] = "XCVR_CPLL" - elif pll_config["type"] == "qpll": - pll_config["sys_clk_select"] = "XCVR_QPLL0" - elif pll_config["type"] == "qpll1": - pll_config["sys_clk_select"] = "XCVR_QPLL1" - else: - raise Exception("Invalid PLL type") - - if self._used_progdiv[converter.name]: - pll_config["progdiv"] = self._used_progdiv[converter.name] - pll_config["out_clk_select"] = "XCVR_PROGDIV_CLK" - else: - div = self._get_val(config[converter.name + "_refclk_div"]) - pll_config["out_clk_select"] = "XCVR_REF_CLK" if div == 1 else "XCVR_REFCLK_DIV2" # type: ignore # noqa: B950 - - # if converter.Np == 12 or converter.F not in [ - # 1, - # 2, - # 4, - # ]: # self.requires_separate_link_layer_out_clock: - - if self.requires_core_clock_from_device_clock: - pll_config["separate_device_clock_required"] = True - - else: - pll_config["separate_device_clock_required"] = self._get_val( - config[converter.name + "two_clks"] - ) - - assert self._get_val( - config[converter.name + "two_clks"] - ) != self._get_val( - config[converter.name + "single_clk"] - ), "Solver failed when trying to determine if two clocks are required" - pll_config["transport_samples_per_clock"] = self._get_val( - config[converter.name + "_link_out_div"] - ) - - if qpll or qpll1: - if self.transciever_type in ["GTY4"]: - pll_clk_out = ( - fpga_ref - * pll_config["qpll_N_dot_frac"] - / (pll_config["m"] * pll_config["qpll_clkoutrate"]) - ) - lr = pll_clk_out * 2 / pll_config["d"] - assert ( - lr == converter.bit_clock - ), f"Invalid QPLL1 lane rate {lr} != {converter.bit_clock}" # type: ignore # noqa: B950 - - else: - div = self._get_val( - config[converter.name + "qty4_full_rate_divisor"] - ) - lr = ( - fpga_ref - * div - * pll_config["n"] - / (pll_config["m"] * 1) - * 1 - / pll_config["d"] - ) - assert ( - lr == converter.bit_clock - ), f"Invalid QPLL1 lane rate {lr} != {converter.bit_clock}" # type: ignore # noqa: B950 - - # Check - if pll_config["out_clk_select"] == "XCVR_REF_CLK" and not cpll: - assert ( - pll_config["vco"] == converter.bit_clock * pll_config["d"] # type: ignore # noqa: B950 - ), "Invalid QPLL lane rate {} != {}".format( - pll_config["vco"] / pll_config["d"], converter.bit_clock # type: ignore # noqa: B950 - ) - - out.append(pll_config) - - if len(out) == 1: - out = out[0] # type: ignore - return out - - def _get_conv_prop( - self, conv: conv, prop: Union[str, dict] - ) -> Union[int, float, str]: - """Helper to extract nested properties if present. - - Args: - conv (conv): Converter object - prop (str,dict): Property to extract - - Raises: - Exception: Converter does not have property - - Returns: - Union[int,float,str]: Value of property - """ - if isinstance(prop, dict): - if conv not in prop: - raise Exception(f"Converter {conv.name} not found in config") - return prop[conv] - return prop - - def _get_progdiv(self) -> Union[List[int], List[float]]: - """Get programmable SERDES dividers for FPGA. - - Raises: - Exception: PRODIV is not available for transceiver type. - - Returns: - List[int,float]: Programmable dividers for FPGA - """ - if self.transciever_type in ["GTY3", "GTH3"]: - return [1, 4, 5, 8, 10, 16, 16.5, 20, 32, 33, 40, 64, 66, 80, 100] - elif self.transciever_type in ["GTY4", "GTH4"]: - return [1, 4, 5, 8, 10, 16, 16.5, 20, 32, 33, 40, 64, 66, 80, 100, 128, 132] - else: - raise Exception( - "PROGDIV is not available for FPGA transciever type " - + str(self.transciever_type) - ) - - def _set_link_layer_requirements( - self, - converter: conv, - fpga_ref: Union[int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar], - config: Dict, - link_out_ref: Union[ - int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar - ] = None, - ) -> Dict: - """Set link layer constraints for downstream FPGA logic. - - The link layer is driven from the XCVR core which can route the - following signals to the link layer input: - - External Ref - - External Ref / 2 - - {CPLL,QPLL0,QPLL1} / PROGDIV - - The link layer input has a hard requirement that is must be at: - - JESD204B: lane rate (bit clock) / 40 or lane rate (bit clock) / 80 - - JESD204C: lane rate (bit clock) / 66 - - The link layer output rate will be equivalent to sample clock / N, where - N is an integer. Note the smaller N, the more channeling it can be to - synthesize the design. This output clock can be separate or be the same - as the XCVR reference. - - Based on this info, we set up the problem where we define N (sample per - clock increase), and set the lane rate based on the current converter - JESD config. - - Args: - converter (conv): Converter object connected to FPGA - fpga_ref (int or GKVariable): Reference clock generated for FPGA - config (Dict): Dictionary of clocking rates and dividers for link - layer - link_out_ref (int or GKVariable): Reference clock generated for FPGA - link layer output - - Returns: - Dict: Dictionary of clocking rates extended with dividers for link - layer - - Raises: - Exception: Link layer output clock select invalid - """ - if converter.jesd_class == "jesd204b": - link_layer_input_rate = converter.bit_clock / 40 - elif converter.jesd_class == "jesd204c": - link_layer_input_rate = converter.bit_clock / 66 - - if isinstance(self.out_clk_select, dict): - if converter not in self.out_clk_select.keys(): - raise Exception( - "Link layer out_clk_select invalid for converter " + converter.name - ) - if isinstance(self.out_clk_select[converter], dict): - out_clk_select = self.out_clk_select[converter].copy() - else: - out_clk_select = self.out_clk_select[converter] - else: - out_clk_select = self.out_clk_select - - # Try PROGDIV first since it doesn't require the solver - ocs_found = False - self._used_progdiv[converter.name] = False - if ( - isinstance(out_clk_select, str) - and out_clk_select == "XCVR_PROGDIV_CLK" - or isinstance(out_clk_select, list) - and "XCVR_PROGDIV_CLK" in out_clk_select - ): - progdiv = self._get_progdiv() - div = converter.bit_clock / link_layer_input_rate - if div in progdiv: - ocs_found = True - self._used_progdiv[converter.name] = div - elif isinstance(out_clk_select, str): - raise Exception( - f"Cannot use PROGDIV since required divider {div}," - + f" only available {progdiv}" - ) - else: - del out_clk_select[out_clk_select.index("XCVR_PROGDIV_CLK")] - - # REFCLK - if not ocs_found and ( - (isinstance(out_clk_select, str) and out_clk_select == "XCVR_REFCLK") - or (isinstance(out_clk_select, list) and out_clk_select == ["XCVR_REFCLK"]) - ): - ocs_found = True - config[converter.name + "_refclk_div"] = 1 - self._add_equation([fpga_ref == link_layer_input_rate]) - - # REFCLK / 2 - if not ocs_found and ( - (isinstance(out_clk_select, str) and out_clk_select == "XCVR_REFCLK_DIV2") - or ( - isinstance(out_clk_select, list) - and out_clk_select == ["XCVR_REFCLK_DIV2"] - ) - ): - ocs_found = True - config[converter.name + "_refclk_div"] = 2 - self._add_equation([fpga_ref == link_layer_input_rate * 2]) - - # Ref clk will use solver to determine if we need REFCLK or REFCLK / 2 - if not ocs_found and ( - isinstance(out_clk_select, list) - and out_clk_select == ["XCVR_REFCLK", "XCVR_REFCLK_DIV2"] - ): - ocs_found = True - config[converter.name + "_refclk_div"] = self._convert_input( - [1, 2], converter.name + "_refclk_div" - ) - self._add_equation( - [ - fpga_ref - == link_layer_input_rate * config[converter.name + "_refclk_div"] - ] - ) - - if not ocs_found: - raise Exception( - "Invalid (or unsupported) link layer output clock selection " - + str(out_clk_select) - ) - - return config - - def _setup_quad_tile( - self, - converter: conv, - fpga_ref: Union[int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar], - link_out_ref: Union[ - None, int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar - ] = None, - ) -> Dict: - """Configure FPGA {Q/C}PLL tile. - - Args: - converter (conv): Converter object(s) connected to FPGA - fpga_ref (int,GKVariable, GK_Intermediate, GK_Operators, CpoIntVar): - Reference clock for FPGA - link_out_ref (None, int,GKVariable, GK_Intermediate, GK_Operators, - CpoIntVar): Link layer output reference clock - - Returns: - Dict: Dictionary of clocking rates and dividers for configuration - - Raises: - Exception: Unsupported solver - """ - # Add reference clock constraints - self._add_equation( - [fpga_ref >= self.ref_clock_min, fpga_ref <= self.ref_clock_max] - ) - - if converter.jesd_class == "jesd204b": - core_clock = converter.bit_clock / 40 - else: - core_clock = converter.bit_clock / 66 - - if self.ref_clock_constraint == "CORE_CLOCK": - self._add_equation([fpga_ref == core_clock]) - elif self.ref_clock_constraint == "CORE_CLOCK_DIV2": - self._add_equation([fpga_ref == core_clock / 2]) - - # CPLL -> VCO = FPGA_REF * N1*N2/M - # PLLOUT = VCO - # LR = PLLOUT * 2/D - # LR = FPGA_REF * N1*N2*2/(M*D) - # - # QPLL -> VCO = FPGA_REF * N/(M*2) - # PLLOUT = VCO/2 - # LR = PLLOUT * 2/D - # LR = FPGA_REF * N/(M*D) - config = {} - # Save PLL settings - ref_sys_clk_select = self.sys_clk_select - - # Extract permutations - self.sys_clk_select = "XCVR_QPLL0" - vco0_min_qpll = self.vco0_min - vco0_max_qpll = self.vco0_max - vco1_min_qpll = self.vco1_min - vco1_max_qpll = self.vco1_max - self.sys_clk_select = "XCVR_QPLL1" - vco0_min_qpll1 = self.vco0_min - vco0_max_qpll1 = self.vco0_max - vco1_min_qpll1 = self.vco1_min - vco1_max_qpll1 = self.vco1_max - - self.sys_clk_select = ref_sys_clk_select # Restore PLL settings - - # GTHE3, GTHE4, GTYE4 - qpll1_allowed = self.transciever_type in ["GTH3", "GTH4", "GTY4"] - - if self.transciever_type in ["GTY4"]: - dqpll = [1, 2, 4, 8, 16, 32] - else: - dqpll = [1, 2, 4, 8, 16] - # QPLL - config[converter.name + "m_qpll"] = self._convert_input( - [1, 2, 3, 4], converter.name + "m_qpll" - ) - config[converter.name + "d_qpll"] = self._convert_input( - dqpll, converter.name + "d_qpll" - ) - config[converter.name + "n_qpll"] = self._convert_input( - self.N, converter.name + "n_qpll" - ) - - # QPLL1 - config[converter.name + "m_qpll1"] = self._convert_input( - [1, 2, 3, 4], converter.name + "m_qpll1" - ) - config[converter.name + "d_qpll1"] = self._convert_input( - dqpll, converter.name + "d_qpll1" - ) - config[converter.name + "n_qpll1"] = self._convert_input( - self.N, converter.name + "n_qpll1" - ) - - if self.transciever_type in ["GTY4"]: - # GTY fractional PLL - config[converter.name + "qpll_clkoutrate"] = self._convert_input( - [1, 2], converter.name + "qpll_clkoutrate" - ) - config[converter.name + "qpll_sdmdata"] = integer_var( - min=0, max=(2**24 - 1), name=converter.name + "qpll_sdmdata" - ) - config[converter.name + "qpll_sdmwidth"] = self._convert_input( - [16, 20, 24], converter.name + "qpll_sdmwidth" - ) - config[converter.name + "qpll_frac"] = self._add_intermediate( - config[converter.name + "qpll_sdmdata"] - / ( - 2 ** config[converter.name + "qpll_sdmwidth"] - ) # FIXME: REMOVE POWER OF 2 - ) - self._add_equation( - [ - config[converter.name + "qpll_frac"] < 1, - ] - ) - config[converter.name + "qpll_N_dot_frac"] = self._add_intermediate( - config[converter.name + "n_qpll"] + config[converter.name + "qpll_frac"] - ) - self.model.add_kpi( - config[converter.name + "qpll_N_dot_frac"], - converter.name + "qpll_N_dot_frac", - ) - - config[converter.name + "vco_qpll"] = self._add_intermediate( - fpga_ref - * config[converter.name + "qpll_N_dot_frac"] - / ( - config[converter.name + "m_qpll"] - * config[converter.name + "qpll_clkoutrate"] - ) - ) - config[converter.name + "vco_qpll1"] = self._add_intermediate( - fpga_ref - * config[converter.name + "qpll_N_dot_frac"] - / ( - config[converter.name + "m_qpll1"] - * config[converter.name + "qpll_clkoutrate"] - ) - ) - - # When lane rate > 28.1 Gbps, qpll_frac must be set to 0 - config[converter.name + "qpll_frac_bypass"] = self._convert_input( - [0, 1], converter.name + "qpll_frac_bypass" - ) - self._add_equation( - [ - (1 - config[converter.name + "qpll_frac_bypass"]) - * converter.bit_clock - <= int(28.1e9), - config[converter.name + "qpll_frac_bypass"] - * config[converter.name + "qpll_frac"] - == 0, - ] - ) - - else: - config[converter.name + "vco_qpll"] = self._add_intermediate( - fpga_ref - * config[converter.name + "n_qpll"] - / (config[converter.name + "m_qpll"]) - ) - config[converter.name + "vco_qpll1"] = self._add_intermediate( - fpga_ref - * config[converter.name + "n_qpll1"] - / (config[converter.name + "m_qpll1"]) - ) - - # Define QPLL band requirements - config[converter.name + "band_qpll"] = self._convert_input( - [0, 1], converter.name + "band_qpll" - ) - - config[converter.name + "vco_max_qpll"] = self._add_intermediate( - config[converter.name + "band_qpll"] * vco1_max_qpll - + (1 - config[converter.name + "band_qpll"]) * vco0_max_qpll - ) - config[converter.name + "vco_min_qpll"] = self._add_intermediate( - config[converter.name + "band_qpll"] * vco1_min_qpll - + (1 - config[converter.name + "band_qpll"]) * vco0_min_qpll - ) - - # Define QPLL1 band requirements - # if qpll1_allowed: - config[converter.name + "band_qpll1"] = self._convert_input( - [0, 1], converter.name + "band_qpll1" - ) - - config[converter.name + "vco_max_qpll1"] = self._add_intermediate( - config[converter.name + "band_qpll1"] * vco1_max_qpll1 - + (1 - config[converter.name + "band_qpll1"]) * vco0_max_qpll1 - ) - config[converter.name + "vco_min_qpll1"] = self._add_intermediate( - config[converter.name + "band_qpll1"] * vco1_min_qpll1 - + (1 - config[converter.name + "band_qpll1"]) * vco0_min_qpll1 - ) - - # Define if we can use GTY (is available) at full rate - if self.transciever_type != "GTY4": - # QPLL1 does not exist for GTY4 so we cannot bypass the extra dec 2 - config[converter.name + "qty4_full_rate_divisor"] = self._convert_input( - 1, name=converter.name + "qty4_full_rate_divisor" - ) - else: - config[converter.name + "qty4_full_rate_divisor"] = self._convert_input( - [1, 2], name=converter.name + "qty4_full_rate_divisor" - ) - - # config[converter.name + "qty4_full_rate_enabled"] = self._add_intermediate( - # 1 - config[converter.name + "qty4_full_rate_divisor"] - # ) - - ####################### - # CPLL - # CPLL -> VCO = FPGA_REF * N1*N2/M - # LR = VCO * 2/D - # LR = FPGA_REF * N1*N2*2/(M*D) - config[converter.name + "m_cpll"] = self._convert_input( - [1, 2], converter.name + "m_cpll" - ) - # We do not allow D=16 or D=32 since they do not allow TX/RXOUT DIV - config[converter.name + "d_cpll"] = self._convert_input( - [1, 2, 4, 8], converter.name + "d_cpll" - ) - config[converter.name + "n1_cpll"] = self._convert_input( - [4, 5], converter.name + "n1_cpll" - ) - config[converter.name + "n2_cpll"] = self._convert_input( - [1, 2, 3, 4, 5], converter.name + "n2_cpll" - ) - - config[converter.name + "vco_cpll"] = self._add_intermediate( - fpga_ref - * config[converter.name + "n1_cpll"] - * config[converter.name + "n2_cpll"] - / config[converter.name + "m_cpll"] - ) - - # Merge - # if sum([self.force_qpll, self.force_qpll1, self.force_cpll]) > 1: - # raise Exception("Cannot force multiple PLLs QPLL0, QPLL1, CPLL") - if ( - sum( - [ - self._get_conv_prop(converter, self.force_qpll), - self._get_conv_prop(converter, self.force_qpll1), - self._get_conv_prop(converter, self.force_cpll), - ] - ) - > 1 - ): - raise Exception("Cannot force multiple PLLs QPLL0, QPLL1, CPLL") - - if self._get_conv_prop(converter, self.force_qpll1) and not qpll1_allowed: - raise Exception( - "QPLL1 is not available for transceiver " + self.transciever_type - ) - - if self._get_conv_prop(converter, self.force_qpll): - qpll = 1 - qpll1 = 0 - cpll = 0 - elif self._get_conv_prop(converter, self.force_qpll1): - qpll = 0 - qpll1 = 1 - cpll = 0 - elif self._get_conv_prop(converter, self.force_cpll): - qpll = 0 - qpll1 = 0 - cpll = 1 - else: - qpll = [0, 1] - if qpll1_allowed: - qpll1 = [0, 1] - else: - qpll1 = 0 - cpll = [0, 1] - - config[converter.name + "_use_cpll"] = self._convert_input( - cpll, converter.name + "_use_cpll" - ) - config[converter.name + "_use_qpll"] = self._convert_input( - qpll, converter.name + "_use_qpll" - ) - config[converter.name + "_use_qpll1"] = self._convert_input( - qpll1, converter.name + "_use_qpll1" - ) - - # Select only one PLL - if ( - not self._get_conv_prop(converter, self.force_cpll) - and not self._get_conv_prop(converter, self.force_qpll) - and not self._get_conv_prop(converter, self.force_qpll1) - ): - self._add_equation( - 1 - == config[converter.name + "_use_cpll"] - + config[converter.name + "_use_qpll"] - + config[converter.name + "_use_qpll1"] - ) - - # VCO - config[converter.name + "vco_select"] = self._add_intermediate( - config[converter.name + "_use_cpll"] * config[converter.name + "vco_cpll"] - + config[converter.name + "_use_qpll"] * config[converter.name + "vco_qpll"] - + config[converter.name + "_use_qpll1"] - * config[converter.name + "vco_qpll1"] - ) - - config[converter.name + "vco_min_select"] = self._add_intermediate( - config[converter.name + "_use_cpll"] * self.vco_min - + config[converter.name + "_use_qpll"] - * config[converter.name + "vco_min_qpll"] - + config[converter.name + "_use_qpll1"] - * config[converter.name + "vco_min_qpll1"] - ) - - config[converter.name + "vco_max_select"] = self._add_intermediate( - config[converter.name + "_use_cpll"] * self.vco_max - + config[converter.name + "_use_qpll"] - * config[converter.name + "vco_max_qpll"] - + config[converter.name + "_use_qpll1"] - * config[converter.name + "vco_max_qpll1"] - ) - - config[converter.name + "d_select"] = self._add_intermediate( - config[converter.name + "_use_cpll"] * config[converter.name + "d_cpll"] - + config[converter.name + "_use_qpll"] * config[converter.name + "d_qpll"] - + config[converter.name + "_use_qpll1"] * config[converter.name + "d_qpll1"] - ) - - # Note: QPLL has extra /2 after VCO so: - # QPLL: lanerate == vco/d - # CPLL: lanerate == vco*2/d - - config[converter.name + "rate_divisor_select"] = self._add_intermediate( - config[converter.name + "_use_cpll"] * 2 - + config[converter.name + "_use_qpll"] - * config[converter.name + "qty4_full_rate_divisor"] - + config[converter.name + "_use_qpll1"] - * config[converter.name + "qty4_full_rate_divisor"] - ) - - ####################### - - # Set all relations - # QPLL+CPLL - # - # CPLL -> VCO = FPGA_REF * N1*N2/M - # PLLOUT = VCO - # LR = PLLOUT * 2/D - # LR = FPGA_REF * N1*N2*2/(M*D) - # - # QPLL -> VCO = FPGA_REF * N/(M) - # PLLOUT = VCO/2 - # LR = PLLOUT * 2/D - # LR = FPGA_REF * N/(M*D) - # - # LR = FPGA_REF*(A*N1*N2*2/(M*D) + (A-1)*N/(M*D)) - # A = 0,1 - # LR*D*M = FPGA_REF*(A*N1*N2*2 + (A-1)*N) - - self._add_equation( - [ - config[converter.name + "vco_select"] - >= config[converter.name + "vco_min_select"], - config[converter.name + "vco_select"] - <= config[converter.name + "vco_max_select"], - # CPLL - # converter.bit_clock == vco * 2 / d - # QPLL - # converter.bit_clock == vco / d - config[converter.name + "vco_select"] - * config[converter.name + "rate_divisor_select"] - == converter.bit_clock * config[converter.name + "d_select"], - ] - ) - - # Add constraints for link clock - # - Must be lanerate/40 204B or lanerate/66 204C - config = self._set_link_layer_requirements(converter, fpga_ref, config, None) - - # Add optimization to favor a single reference clock vs unique ref+device clocks - config[converter.name + "single_clk"] = self._convert_input( - [0, 1], converter.name + "single_clk" - ) - if self.force_separate_device_clock: - sdc = [1] - else: - sdc = [0, 1] - config[converter.name + "two_clks"] = self._convert_input( - sdc, converter.name + "two_clks" - ) - self._add_equation( - [ - config[converter.name + "single_clk"] - + config[converter.name + "two_clks"] - == 1, - ] - ) - # Favor single clock, this equation will be minimized - v = ( - config[converter.name + "single_clk"] - + 1000 * config[converter.name + "two_clks"] - ) - self._add_objective(v) - - # Add constraints to meet sample clock - if self.requires_core_clock_from_device_clock: - if converter.jesd_class == "jesd204b": - core_clock = converter.bit_clock / 40 - else: - core_clock = converter.bit_clock / 66 - - self._add_equation([core_clock == link_out_ref]) - - else: - possible_divs = [] - for samples_per_clock in [1, 2, 4, 8, 16]: - if converter.sample_clock / samples_per_clock <= self.target_Fmax: - possible_divs.append(samples_per_clock) - - if len(possible_divs) == 0: - raise Exception("Link layer output clock rate too high") - - config[converter.name + "_link_out_div"] = self._convert_input( - possible_divs, converter.name + "_samples_per_clock" - ) - - self._add_equation( - [ - ( - config[converter.name + "single_clk"] * fpga_ref - + config[converter.name + "two_clks"] - * link_out_ref - * config[converter.name + "_link_out_div"] - ) - == converter.sample_clock - ] - ) - - return config - - def get_required_clocks( - self, - converter: conv, - fpga_ref: Union[int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar], - link_out_ref: Union[ - int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar - ] = None, - ) -> List: - """Get necessary clocks for QPLL/CPLL configuration. - - Args: - converter (conv): Converter object of converter connected to FPGA - fpga_ref (int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar): - Abstract or concrete reference to FPGA reference clock - link_out_ref (int or GKVariable): Reference clock generated for FPGA - link layer output, also called device clock - - Returns: - List: List of solver variables and constraints - - Raises: - Exception: If solver is not valid - Exception: Link layer out clock required - """ - if self.ref_clock_min == -1 or self.ref_clock_max == -1: - raise Exception("ref_clock_min or ref_clock_max not set") - if "_get_converters" in dir(converter): - converter = ( - converter._get_converters() # type: ignore - ) # Handle nested converters - - if not isinstance(converter, list): - converter = [converter] # type: ignore - - # if self.solver == "gekko": - # self.config = { - # "fpga_ref": self.model.Var( - # # integer=True, - # lb=self.ref_clock_min, - # ub=self.ref_clock_max, - # value=self.ref_clock_min, - # ) - # } - # elif self.solver == "CPLEX": - # # self.config = { - # # "fpga_ref": integer_var( - # # self.ref_clock_min, self.ref_clock_max, "fpga_ref" - # # ) - # # } - # pass - # else: - # raise Exception(f"Unknown solver {self.solver}") - - # https://www.xilinx.com/support/documentation/user_guides/ug476_7Series_Transceivers.pdf # noqa: B950 - - # clock_names = ["fpga_ref"] - clock_names = [] - self.config = {} - if self.force_single_quad_tile: - raise Exception("force_single_quad_tile==1 not implemented") - else: - ####################### - # self.configs = [] - self.dev_clocks = [] - self.ref_clocks = [] - # obs = [] - for cnv in converter: # type: ignore - # rsl = self._get_conv_prop( - # cnv, self.requires_separate_link_layer_out_clock - # ) - # if link_out_ref is None and rsl: - # raise Exception("Link layer out clock required") - - clock_names.append(cnv.name + "fpga_ref") - # self.config[cnv.name+"fpga_ref"] = interval_var( - # self.ref_clock_min, self.ref_clock_max, name=cnv.name+"fpga_ref" - # ) - self.config[cnv.name + "fpga_ref"] = fpga_ref - self.ref_clocks.append(self.config[cnv.name + "fpga_ref"]) - if ( - link_out_ref is not None - ): # self.requires_separate_link_layer_out_clock: - self.config[cnv.name + "link_out_ref"] = link_out_ref - self.ref_clocks.append(self.config[cnv.name + "link_out_ref"]) - config = self._setup_quad_tile( - cnv, - self.config[cnv.name + "fpga_ref"], - self.config[cnv.name + "link_out_ref"], - ) - else: - config = self._setup_quad_tile( - cnv, self.config[cnv.name + "fpga_ref"] - ) - # Set optimizations - # self.model.Obj(self.config[converter.name+"d"]) - # self.model.Obj(self.config[converter.name+"d_cpll"]) - # self.model.Obj(config[converter.name+"d_select"]) - if self.favor_cpll_over_qpll: - if self.solver == "gekko": - self.model.Obj( - -1 * config[cnv.name + "qpll_0_cpll_1"] - ) # Favor CPLL over QPLL - elif self.solver == "CPLEX": - self.model.maximize(config[cnv.name + "qpll_0_cpll_1"]) - # obs.append(-1 * config[cnv.name + "qpll_0_cpll_1"]) - else: - raise Exception(f"Unknown solver {self.solver}") - - self.configs.append(config) - # FPGA also requires clock at device clock rate - if self.request_device_clock: - self.dev_clocks.append(cnv.device_clock) - clock_names.append(cnv.name + "_fpga_device_clock") - - if self.minimize_fpga_ref_clock: - if self.solver == "gekko": - self.model.Obj(self.config[cnv.name + "fpga_ref"]) - elif self.solver == "CPLEX": - # self.model.minimize_static_lex(obs + [self.config[converter.name+"fpga_ref"]]) # noqa: B950 - self.model.minimize(self.config[cnv.name + "fpga_ref"]) # noqa: B950 - # self.model.maximize(obs + self.config[converter.name+"fpga_ref"]) - else: - raise Exception(f"Unknown solver {self.solver}") - - self._clock_names = clock_names - - # return [self.config["fpga_ref"]] + self.dev_clocks - return self.ref_clocks + self.dev_clocks +"""Xilinx FPGA clocking model.""" +from typing import Dict, List, Optional, Union + +from adijif.converters.converter import converter as conv +from adijif.fpgas.pll_constraints import xilinx_pll_constraints +from adijif.fpgas.xilinx_bf import xilinx_bf +from adijif.solvers import CpoSolveResult # type: ignore +from adijif.solvers import integer_var # type: ignore +from adijif.solvers import CpoIntVar, GK_Intermediate, GK_Operators, GKVariable + + +class xilinx(xilinx_bf, xilinx_pll_constraints): + """Xilinx FPGA clocking model. + + This model captures different limitations of the Xilinx + PLLs and interfaces used for JESD. + + Currently only Zynq 7000 devices have been fully tested. + """ + + favor_cpll_over_qpll = False + minimize_fpga_ref_clock = False + + """Force generation of separate device clock from the clock chip. In many + cases, the ref clock and device clock can be the same.""" + force_separate_device_clock: bool = False + + """Constrain reference clock to be specific values. Options: + - CORE_CLOCK: Make reference clock the same as the core clock (LR/40 or LR/66) + - CORE_CLOCK_DIV2: Make reference clock the same as the core clock divided by 2 + - Unconstrained: No constraints on reference clock. Simply meet PLL constraints + """ + _ref_clock_constraint = "CORE_CLOCK" + + max_serdes_lanes = 24 + + hdl_core_version = 2.1 + + available_speed_grades = [-1, -2, -3] + speed_grade = -2 + + transceiver_voltage = 800 + + ref_clock_min = -1 # Not set + ref_clock_max = -1 # Not set + + available_fpga_packages = [ + "Unknown", + "RF", + "FL", + "FF", + "FB", + "HC", + "FH", + "CS", + "CP", + "FT", + "FG", + "SB", + "RB", + "RS", + "CL", + "SF", + "BA", + "FA", + ] + fpga_package = "FB" + + available_fpga_families = ["Unknown", "Artix", "Kintex", "Virtex", "Zynq"] + fpga_family = "Zynq" + + available_transceiver_types = ["GTX2"] + transceiver_type = "GTX2" + + sys_clk_selections = [ + "XCVR_CPLL", + "XCVR_QPLL0", + "XCVR_QPLL1", + ] + sys_clk_select = "XCVR_QPLL1" + + _out_clk_selections = [ + # "XCVR_OUTCLK_PCS", + # "XCVR_OUTCLK_PMA", + "XCVR_REFCLK", + "XCVR_REFCLK_DIV2", + "XCVR_PROGDIV_CLK", + ] + _out_clk_select = [ + # "XCVR_OUTCLK_PCS", + # "XCVR_OUTCLK_PMA", + "XCVR_REFCLK", + "XCVR_REFCLK_DIV2", + "XCVR_PROGDIV_CLK", + ] + + """ Force use of QPLL for transceiver source """ + force_qpll = 0 + + """ Force use of QPLL1 for transceiver source (GTH3,GTH4,GTY4)""" + force_qpll1 = 0 + + """ Force use of CPLL for transceiver source """ + force_cpll = 0 + + """ Force all transceiver sources to be from a single PLL quad. + This will try to leverage the output dividers of the PLLs + """ + force_single_quad_tile = 0 + + """ Request that clock chip generated device clock + device clock == LMFC/40 + NOTE: THIS IS NOT FPGA REF CLOCK + """ + request_device_clock = False + + _clock_names: List[str] = [] + + """When PROGDIV, this will be set to the value of the divider""" + _used_progdiv = {} + + """FPGA target Fmax rate use to determine link layer output rate""" + target_Fmax = 250e6 + + """Require generation of separate clock specifically for link layer""" + requires_separate_link_layer_out_clock = True + + """Require generation of separate core clock (LR/40 or LR/66)""" + requires_core_clock_from_device_clock = False + + configs = [] # type: ignore + + @property + def ref_clock_constraint(self) -> str: + """Get reference clock constraint. + + Reference clock constraint can be set to: + - CORE_CLOCK: Make reference clock the same as the core clock (LR/40 or LR/66) + - CORE_CLOCK_DIV2: Make reference clock the same as the core clock divided by 2 + - Unconstrained: No constraints on reference clock. Simply meet PLL constraints + + Returns: + str: Reference clock constraint. + """ + return self._ref_clock_constraint + + @ref_clock_constraint.setter + def ref_clock_constraint(self, value: str) -> None: + """Set reference clock constraint. + + Reference clock constraint can be set to: + - CORE_CLOCK: Make reference clock the same as the core clock (LR/40 or LR/66) + - CORE_CLOCK_DIV2: Make reference clock the same as the core clock divided by 2 + - Unconstrained: No constraints on reference clock. Simply meet PLL constraints + + Args: + value (str): Reference clock constraint. + + Raises: + Exception: Invalid ref_clock_constraint selection. + """ + if value not in ["CORE_CLOCK", "CORE_CLOCK_DIV2", "Unconstrained"]: + raise Exception( + f"Invalid ref_clock_constraint {value}, " + + "options are CORE_CLOCK, CORE_CLOCK_DIV2, Unconstrained" + ) + self._ref_clock_constraint = value + + @property + def out_clk_select(self) -> Union[int, float]: + """Get current PLL clock output mux options for link layer clock. + + Valid options are: + "XCVR_REFCLK", + "XCVR_REFCLK_DIV2", + "XCVR_PROGDIV_CLK" + If a list of these is provided, the solver will determine one to use + + Returns: + str,list(str): Mux selection for link layer clock. + """ + return self._out_clk_select + + @out_clk_select.setter + def out_clk_select(self, value: Union[str, List[str]]) -> None: + """Set current PLL clock output mux options for link layer clock. + + Valid options are: + "XCVR_REFCLK", + "XCVR_REFCLK_DIV2", + "XCVR_PROGDIV_CLK" + If a list of these is provided, the solver will determine one to use + + Args: + value (str,List[str]): Mux selection for link layer clock. + + Raises: + Exception: Invalid out_clk_select selection. + """ + if isinstance(value, list): + for item in value: + if item not in self._out_clk_selections: + raise Exception( + f"Invalid out_clk_select {item}, " + + f"options are {self._out_clk_selections}" + ) + elif isinstance(value, dict): + for converter in value: + if not isinstance(converter, conv): + raise Exception("Keys of out_clk_select but be of type converter") + if value[converter] not in self._out_clk_selections: + raise Exception( + f"Invalid out_clk_select {value[converter]}, " + + f"options are {self._out_clk_selections}" + ) + elif value not in self._out_clk_selections: # str + raise Exception( + f"Invalid out_clk_select {value}, " + + f"options are {self._out_clk_selections}" + ) + + self._out_clk_select = value + + @property + def _ref_clock_max(self) -> int: + """Get maximum reference clock for config. + + Returns: + int: Rate in samples per second. + + Raises: + Exception: Unsupported transceiver type configured. + """ + # https://www.xilinx.com/support/documentation/data_sheets/ds191-XC7Z030-XC7Z045-data-sheet.pdf # noqa: B950 + if self.transceiver_type == "GTX2": + if self.speed_grade == "-3E": + return 700000000 + else: + return 670000000 + else: + raise Exception( + f"Unknown ref_clock_max for transceiver type {self.transceiver_type}" + ) + # raise Exception(f"Unknown transceiver type {self.transceiver_type}") + + @property + def _ref_clock_min(self) -> int: + """Get minimum reference clock for config. + + Returns: + int: Rate in samples per second. + + Raises: + Exception: Unsupported transceiver type configured. + """ + # https://www.xilinx.com/support/documentation/data_sheets/ds191-XC7Z030-XC7Z045-data-sheet.pdf # noqa: B950 + if self.transceiver_type == "GTX2": + return 60000000 + else: + raise Exception( + f"Unknown ref_clock_min for transceiver type {self.transceiver_type}" + ) + # raise Exception(f"Unknown transceiver type {self.transceiver_type}") + + # CPLL + @property + def vco_min(self) -> int: + """Get minimum CPLL VCO rate for config. + + Returns: + int: Rate in samples per second. + + Raises: + Exception: Unsupported transceiver type configured. + """ + if self.transceiver_type == "GTX2": + return 1600000000 + elif self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: + return 2000000000 + else: + raise Exception( + f"Unknown vco_min for transceiver type {self.transceiver_type}" + ) + + @property + def vco_max(self) -> int: + """Get maximum CPLL VCO rate for config. + + Returns: + int: Rate in samples per second. + + Raises: + Exception: Unsupported transceiver type configured. + """ + if self.transceiver_type == "GTX2": + return 3300000000 + elif self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: + if self.hdl_core_version > 2: + if self.transceiver_type in ["GTH3", "GTH4"]: + if self.transceiver_voltage < 850 or self.speed_grade == -1: + return 4250000000 + elif self.transceiver_type == "GTY4" and self.speed_grade == -1: + return 4250000000 + return 6250000000 + else: + raise Exception( + f"Unknown vco_max for transceiver type {self.transceiver_type}" + ) + + # QPLL + @property + def vco0_min(self) -> int: + """Get minimum QPLL VCO0 rate for config. + + This is applicable for QPLLs only. + + Returns: + int: Rate in samples per second. + + Raises: + Exception: Unsupported transceiver type configured. + """ + if self.transceiver_type == "GTX2": + return 5930000000 + elif self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: + if self.sys_clk_select == "XCVR_QPLL1" and self.transceiver_type in [ + "GTH3", + "GTH4", + ]: + return 8000000000 + else: + return 9800000000 + else: + raise Exception( + f"Unknown vco0_min for transceiver type {self.transceiver_type}" + ) + + @property + def vco0_max(self) -> int: + """Get maximum QPLL VCO0 rate for config. + + This is applicable for QPLLs only. + + Returns: + int: Rate in samples per second. + + Raises: + Exception: Unsupported transceiver type configured. + """ + if self.transceiver_type == "GTX2": + if ( + self.hdl_core_version > 2 + and self.fpga_family == "Kintex" + and self.fpga_package in ["FB", "RF", "FF"] + ): + return 6600000000 + return 8000000000 + elif self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: + if self.sys_clk_select == "XCVR_QPLL1" and self.transceiver_type in [ + "GTH3", + "GTH4", + ]: + return 13000000000 + else: + return 16375000000 + else: + raise Exception( + f"Unknown vco0_max for transceiver type {self.transceiver_type}" + ) + + @property + def vco1_min(self) -> int: + """Get minimum QPLL VCO1 rate for config. + + This is applicable for QPLLs only. + + Returns: + int: Rate in samples per second. + + Raises: + Exception: Unsupported transceiver type configured. + """ + if self.transceiver_type == "GTX2": + return 9800000000 + elif self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: + return self.vco0_min + else: + raise Exception( + f"Unknown vco1_min for transceiver type {self.transceiver_type}" + ) + + @property + def vco1_max(self) -> int: + """Get maximum QPLL VCO1 rate for config. + + This is applicable for QPLLs only. + + Returns: + int: Rate in samples per second. + + Raises: + Exception: Unsupported transceiver type configured. + """ + if self.transceiver_type == "GTX2": + if self.hdl_core_version > 2 and self.speed_grade == -2: + return 10312500000 + return 12500000000 + elif self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: + return self.vco0_max + else: + raise Exception( + f"Unknown vco1_max for transceiver type {self.transceiver_type}" + ) + + @property + def N(self) -> List[int]: + """Get available feedback divider settings. + + This is applicable for QPLLs only. + + Returns: + list[int]: List of divider integers. + + Raises: + Exception: Unsupported transceiver type configured. + """ + if self.transceiver_type == "GTX2": + return [16, 20, 32, 40, 64, 66, 80, 100] + elif self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: + return [16, 20, 32, 40, 64, 66, 75, 80, 100, 112, 120, 125, 150, 160] + else: + raise Exception( + "Unknown N (feedback dividers) for transceiver type" + " {}".format(self.transceiver_type) + ) + + def setup_by_dev_kit_name(self, name: str) -> None: + """Configure object based on board name. Ex: zc706, zcu102. + + Args: + name (str): Name of dev kit. Ex: zc706, zcu102 + + Raises: + Exception: Unsupported board requested. + + """ + if name.lower() == "zc706": + self.transceiver_type = "GTX2" + self.fpga_family = "Zynq" + self.fpga_package = "FF" + self.speed_grade = -2 + self.ref_clock_min = 60000000 + self.ref_clock_max = 670000000 + self.max_serdes_lanes = 8 + # default PROGDIV not available + o = self._out_clk_selections.copy() + del o[o.index("XCVR_PROGDIV_CLK")] + self._out_clk_selections = o + self._out_clk_select = o + elif name.lower() == "zcu102": + self.transceiver_type = "GTH4" + self.fpga_family = "Zynq" + self.fpga_package = "FF" + self.speed_grade = -2 + self.ref_clock_min = 60000000 + self.ref_clock_max = 820000000 + self.max_serdes_lanes = 8 + elif name.lower() == "vcu118": + # XCVU9P-L2FLGA2104 + self.transceiver_type = "GTY4" + self.fpga_family = "Virtex" + self.fpga_package = "FL" + self.speed_grade = -2 + self.ref_clock_min = 60000000 + self.ref_clock_max = 820000000 + self.max_serdes_lanes = 24 + elif name.lower() == "adsy1100": + # ADI VPX Module + # VU11P: xcvu11p-flgb2104-2-i + self.transceiver_type = "GTY4" + self.fpga_family = "Virtex" + self.fpga_package = "FL" + self.speed_grade = -2 + self.ref_clock_min = 60000000 # NEED TO VERIFY + self.ref_clock_max = 820000000 # NEED TO VERIFY + self.max_serdes_lanes = 24 # Connected to AD9084 + else: + raise Exception(f"No boardname found in library for {name}") + + def determine_pll(self, bit_clock: int, fpga_ref_clock: int) -> Dict: + """Determine if configuration is possible with CPLL or QPLL. + + CPLL is checked first and will check QPLL if that case is + invalid. + + This is only used for brute-force implementations. + + Args: + bit_clock (int): Equivalent to lane rate in bits/second + fpga_ref_clock (int): System reference clock + + Returns: + Dict: Dictionary of PLL configuration + """ + try: + info = self.determine_cpll(bit_clock, fpga_ref_clock) + except: # noqa: B001 + info = self.determine_qpll(bit_clock, fpga_ref_clock) + return info + + 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 + + Raises: + Exception: Clock have not been enumerated aka get_required_clocks not + not called yet. + """ + if not self._clock_names: + raise Exception( + "get_required_clocks must be run to generated" + + " dependent clocks before names are available" + ) + return self._clock_names + + def get_config( + self, + converter: conv, + fpga_ref: Union[float, int], + solution: Optional[CpoSolveResult] = None, + ) -> Union[List[Dict], Dict]: + """Extract configurations from solver results. + + Collect internal FPGA configuration and output clock definitions. + + Args: + converter (conv): Converter object connected to FPGA who config is + collected + fpga_ref (int or float): Reference clock generated for FPGA for specific + converter + solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver + + Raises: + Exception: Invalid PLL configuration. + + Returns: + Dict: Dictionary of clocking rates and dividers for configuration + """ + out = [] + if solution: + self.solution = solution + + for config in self.configs: + pll_config: Dict[str, Union[str, int, float]] = {} + + # Filter out other converters + if converter.name + "_use_cpll" not in config.keys(): + print("Continued") + continue + # print('Not continued') + # continue + # pll = self._get_val(config[converter.name + "qpll_0_cpll_1"]) + cpll = self._get_val(config[converter.name + "_use_cpll"]) > 0 + qpll = self._get_val(config[converter.name + "_use_qpll"]) > 0 + qpll1 = self._get_val(config[converter.name + "_use_qpll1"]) > 0 + + if sum([cpll, qpll, qpll1]) != 1: + raise Exception( + "More than one PLL selected" + + " can only be one of CPLL, QPLL, or QPLL1" + ) + + if cpll > 0: # type: ignore + pll_config = self.get_cpll_config(config, converter, fpga_ref) + else: + pll_name = "qpll" if qpll else "qpll1" + pll_config = self.get_qpll_config( + config, converter, fpga_ref, n=int(qpll1) + ) + + # SERDES output mux + if pll_config["type"] == "cpll": + pll_config["sys_clk_select"] = "XCVR_CPLL" + elif pll_config["type"] == "qpll": + pll_config["sys_clk_select"] = "XCVR_QPLL0" + elif pll_config["type"] == "qpll1": + pll_config["sys_clk_select"] = "XCVR_QPLL1" + else: + raise Exception("Invalid PLL type") + + if self._used_progdiv[converter.name]: + pll_config["progdiv"] = self._used_progdiv[converter.name] + pll_config["out_clk_select"] = "XCVR_PROGDIV_CLK" + else: + div = self._get_val(config[converter.name + "_refclk_div"]) + pll_config["out_clk_select"] = "XCVR_REF_CLK" if div == 1 else "XCVR_REFCLK_DIV2" # type: ignore # noqa: B950 + + # if converter.Np == 12 or converter.F not in [ + # 1, + # 2, + # 4, + # ]: # self.requires_separate_link_layer_out_clock: + + if self.requires_core_clock_from_device_clock: + pll_config["separate_device_clock_required"] = True + + else: + pll_config["separate_device_clock_required"] = self._get_val( + config[converter.name + "two_clks"] + ) + + assert self._get_val( + config[converter.name + "two_clks"] + ) != self._get_val( + config[converter.name + "single_clk"] + ), "Solver failed when trying to determine if two clocks are required" + pll_config["transport_samples_per_clock"] = self._get_val( + config[converter.name + "_link_out_div"] + ) + + if qpll or qpll1: + pname = "qpll" if qpll else "qpll1" + + self.solution.print_solution() + + if self.transceiver_type[:3] == "GTY": + # n_dot_frac = self._get_val(config[converter.name + f"_n_dot_frac_{pname}"]) + frac = self._get_val( + config[converter.name + f"_sdm_data_{pname}"] + ) / ( + 2 + ** self._get_val(config[converter.name + f"_sdm_width_{pname}"]) + ) + n_dot_frac = ( + self._get_val(config[converter.name + f"_n_{pname}"]) + frac + ) + if ( + not float(n_dot_frac).is_integer() + and converter.bit_clock >= 28.1e9 + ): + raise Exception( + f"Invalid QPLL1 n_dot_frac {n_dot_frac} must be integer" + ) + pll_clk_out = ( + fpga_ref + * n_dot_frac + / (pll_config["m"] * pll_config["clkout_rate"]) + ) + lr = pll_clk_out * 2 / pll_config["d"] + assert ( + lr == converter.bit_clock + ), f"Invalid {pname} lane rate {lr} != {converter.bit_clock}" + + elif self.transceiver_type[:3] in ["GTX", "GTH"]: + pll_clk_out = fpga_ref * pll_config["n"] / (pll_config["m"] * 2) + lr = pll_clk_out * 2 / pll_config["d"] + assert ( + lr == converter.bit_clock + ), f"Invalid {pname} lane rate {lr} != {converter.bit_clock}" + + else: + raise Exception( + f"Unsupported transceiver type {self.transceiver_type}" + ) + + # if self.transceiver_type in ["GTY4"]: + # pll_clk_out = ( + # fpga_ref + # * pll_config["n_dot_frac"] + # / (pll_config["m"] * pll_config["clkout_rate"]) + # ) + # lr = pll_clk_out * 2 / pll_config["d"] + # assert ( + # lr == converter.bit_clock + # ), f"Invalid QPLL1 lane rate {lr} != {converter.bit_clock}" # type: ignore # noqa: B950 + + # else: + # div = self._get_val( + # config[converter.name + "qty4_full_rate_divisor"] + # ) + # lr = ( + # fpga_ref + # * div + # * pll_config["n"] + # / (pll_config["m"] * 1) + # * 1 + # / pll_config["d"] + # ) + # assert ( + # lr == converter.bit_clock + # ), f"Invalid QPLL1 lane rate {lr} != {converter.bit_clock}" # type: ignore # noqa: B950 + + # Check + if pll_config["out_clk_select"] == "XCVR_REF_CLK" and not cpll: + assert ( + pll_config["vco"] == converter.bit_clock * pll_config["d"] # type: ignore # noqa: B950 + ), "Invalid QPLL lane rate {} != {}".format( + pll_config["vco"] / pll_config["d"], converter.bit_clock # type: ignore # noqa: B950 + ) + + out.append(pll_config) + + if len(out) == 1: + out = out[0] # type: ignore + return out + + def _get_conv_prop( + self, conv: conv, prop: Union[str, dict] + ) -> Union[int, float, str]: + """Helper to extract nested properties if present. + + Args: + conv (conv): Converter object + prop (str,dict): Property to extract + + Raises: + Exception: Converter does not have property + + Returns: + Union[int,float,str]: Value of property + """ + if isinstance(prop, dict): + if conv not in prop: + raise Exception(f"Converter {conv.name} not found in config") + return prop[conv] + return prop + + def _get_progdiv(self) -> Union[List[int], List[float]]: + """Get programmable SERDES dividers for FPGA. + + Raises: + Exception: PRODIV is not available for transceiver type. + + Returns: + List[int,float]: Programmable dividers for FPGA + """ + if self.transceiver_type in ["GTY3", "GTH3"]: + return [1, 4, 5, 8, 10, 16, 16.5, 20, 32, 33, 40, 64, 66, 80, 100] + elif self.transceiver_type in ["GTY4", "GTH4"]: + return [1, 4, 5, 8, 10, 16, 16.5, 20, 32, 33, 40, 64, 66, 80, 100, 128, 132] + else: + raise Exception( + "PROGDIV is not available for FPGA transciever type " + + str(self.transceiver_type) + ) + + def _set_link_layer_requirements( + self, + converter: conv, + fpga_ref: Union[int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar], + config: Dict, + link_out_ref: Union[ + int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar + ] = None, + ) -> Dict: + """Set link layer constraints for downstream FPGA logic. + + The link layer is driven from the XCVR core which can route the + following signals to the link layer input: + - External Ref + - External Ref / 2 + - {CPLL,QPLL0,QPLL1} / PROGDIV + + The link layer input has a hard requirement that is must be at: + - JESD204B: lane rate (bit clock) / 40 or lane rate (bit clock) / 80 + - JESD204C: lane rate (bit clock) / 66 + + The link layer output rate will be equivalent to sample clock / N, where + N is an integer. Note the smaller N, the more channeling it can be to + synthesize the design. This output clock can be separate or be the same + as the XCVR reference. + + Based on this info, we set up the problem where we define N (sample per + clock increase), and set the lane rate based on the current converter + JESD config. + + Args: + converter (conv): Converter object connected to FPGA + fpga_ref (int or GKVariable): Reference clock generated for FPGA + config (Dict): Dictionary of clocking rates and dividers for link + layer + link_out_ref (int or GKVariable): Reference clock generated for FPGA + link layer output + + Returns: + Dict: Dictionary of clocking rates extended with dividers for link + layer + + Raises: + Exception: Link layer output clock select invalid + """ + if converter.jesd_class == "jesd204b": + link_layer_input_rate = converter.bit_clock / 40 + elif converter.jesd_class == "jesd204c": + link_layer_input_rate = converter.bit_clock / 66 + + if isinstance(self.out_clk_select, dict): + if converter not in self.out_clk_select.keys(): + raise Exception( + "Link layer out_clk_select invalid for converter " + converter.name + ) + if isinstance(self.out_clk_select[converter], dict): + out_clk_select = self.out_clk_select[converter].copy() + else: + out_clk_select = self.out_clk_select[converter] + else: + out_clk_select = self.out_clk_select + + # Try PROGDIV first since it doesn't require the solver + ocs_found = False + self._used_progdiv[converter.name] = False + if ( + isinstance(out_clk_select, str) + and out_clk_select == "XCVR_PROGDIV_CLK" + or isinstance(out_clk_select, list) + and "XCVR_PROGDIV_CLK" in out_clk_select + ): + progdiv = self._get_progdiv() + div = converter.bit_clock / link_layer_input_rate + if div in progdiv: + ocs_found = True + self._used_progdiv[converter.name] = div + elif isinstance(out_clk_select, str): + raise Exception( + f"Cannot use PROGDIV since required divider {div}," + + f" only available {progdiv}" + ) + else: + del out_clk_select[out_clk_select.index("XCVR_PROGDIV_CLK")] + + # REFCLK + if not ocs_found and ( + (isinstance(out_clk_select, str) and out_clk_select == "XCVR_REFCLK") + or (isinstance(out_clk_select, list) and out_clk_select == ["XCVR_REFCLK"]) + ): + ocs_found = True + config[converter.name + "_refclk_div"] = 1 + self._add_equation([fpga_ref == link_layer_input_rate]) + + # REFCLK / 2 + if not ocs_found and ( + (isinstance(out_clk_select, str) and out_clk_select == "XCVR_REFCLK_DIV2") + or ( + isinstance(out_clk_select, list) + and out_clk_select == ["XCVR_REFCLK_DIV2"] + ) + ): + ocs_found = True + config[converter.name + "_refclk_div"] = 2 + self._add_equation([fpga_ref == link_layer_input_rate * 2]) + + # Ref clk will use solver to determine if we need REFCLK or REFCLK / 2 + if not ocs_found and ( + isinstance(out_clk_select, list) + and out_clk_select == ["XCVR_REFCLK", "XCVR_REFCLK_DIV2"] + ): + ocs_found = True + config[converter.name + "_refclk_div"] = self._convert_input( + [1, 2], converter.name + "_refclk_div" + ) + self._add_equation( + [ + fpga_ref + == link_layer_input_rate * config[converter.name + "_refclk_div"] + ] + ) + + if not ocs_found: + raise Exception( + "Invalid (or unsupported) link layer output clock selection " + + str(out_clk_select) + ) + + return config + + def _setup_quad_tile( + self, + converter: conv, + fpga_ref: Union[int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar], + link_out_ref: Union[ + None, int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar + ] = None, + ) -> Dict: + """Configure FPGA {Q/C}PLL tile. + + Args: + converter (conv): Converter object(s) connected to FPGA + fpga_ref (int,GKVariable, GK_Intermediate, GK_Operators, CpoIntVar): + Reference clock for FPGA + link_out_ref (None, int,GKVariable, GK_Intermediate, GK_Operators, + CpoIntVar): Link layer output reference clock + + Returns: + Dict: Dictionary of clocking rates and dividers for configuration + + Raises: + Exception: Unsupported solver + """ + # Add reference clock constraints + self._add_equation( + [fpga_ref >= self.ref_clock_min, fpga_ref <= self.ref_clock_max] + ) + + if converter.jesd_class == "jesd204b": + core_clock = converter.bit_clock / 40 + else: + core_clock = converter.bit_clock / 66 + + if self.ref_clock_constraint == "CORE_CLOCK": + self._add_equation([fpga_ref == core_clock]) + elif self.ref_clock_constraint == "CORE_CLOCK_DIV2": + self._add_equation([fpga_ref == core_clock / 2]) + + # CPLL -> VCO = FPGA_REF * N1*N2/M + # PLLOUT = VCO + # LR = PLLOUT * 2/D + # LR = FPGA_REF * N1*N2*2/(M*D) + # + # QPLL -> VCO = FPGA_REF * N/(M*2) + # PLLOUT = VCO/2 + # LR = PLLOUT * 2/D + # LR = FPGA_REF * N/(M*D) + config = {} + # Save PLL settings + ref_sys_clk_select = self.sys_clk_select + + # Extract permutations + self.sys_clk_select = "XCVR_QPLL0" + vco0_min_qpll = self.vco0_min + vco0_max_qpll = self.vco0_max + vco1_min_qpll = self.vco1_min + vco1_max_qpll = self.vco1_max + self.sys_clk_select = "XCVR_QPLL1" + vco0_min_qpll1 = self.vco0_min + vco0_max_qpll1 = self.vco0_max + vco1_min_qpll1 = self.vco1_min + vco1_max_qpll1 = self.vco1_max + + self.sys_clk_select = ref_sys_clk_select # Restore PLL settings + + config = self.add_cpll_contraints(config, fpga_ref, converter) + config = self.add_qpll_contraints(config, fpga_ref, converter) + config = self.add_qpll1_contraints(config, fpga_ref, converter) + + self._add_equation( + [ + config[converter.name + "_use_cpll"] + + config[converter.name + "_use_qpll"] + + config[converter.name + "_use_qpll1"] + == 1 + ] + ) + + # # GTHE3, GTHE4, GTYE4 + # qpll1_allowed = self.transceiver_type in ["GTH3", "GTH4", "GTY4"] + + # if self.transceiver_type in ["GTY4"]: + # dqpll = [1, 2, 4, 8, 16, 32] + # else: + # dqpll = [1, 2, 4, 8, 16] + # # QPLL + # config[converter.name + "m_qpll"] = self._convert_input( + # [1, 2, 3, 4], converter.name + "m_qpll" + # ) + # config[converter.name + "d_qpll"] = self._convert_input( + # dqpll, converter.name + "d_qpll" + # ) + # config[converter.name + "n_qpll"] = self._convert_input( + # self.N, converter.name + "n_qpll" + # ) + + # # QPLL1 + # config[converter.name + "m_qpll1"] = self._convert_input( + # [1, 2, 3, 4], converter.name + "m_qpll1" + # ) + # config[converter.name + "d_qpll1"] = self._convert_input( + # dqpll, converter.name + "d_qpll1" + # ) + # config[converter.name + "n_qpll1"] = self._convert_input( + # self.N, converter.name + "n_qpll1" + # ) + + # if self.transceiver_type in ["GTY4"]: + # # GTY fractional PLL + # config[converter.name + "qpll_clkoutrate"] = self._convert_input( + # [1, 2], converter.name + "qpll_clkoutrate" + # ) + # config[converter.name + "qpll_sdmdata"] = integer_var( + # min=0, max=(2**24 - 1), name=converter.name + "qpll_sdmdata" + # ) + # config[converter.name + "qpll_sdmwidth"] = self._convert_input( + # [16, 20, 24], converter.name + "qpll_sdmwidth" + # ) + # config[converter.name + "qpll_frac"] = self._add_intermediate( + # config[converter.name + "qpll_sdmdata"] + # / ( + # 2 ** config[converter.name + "qpll_sdmwidth"] + # ) # FIXME: REMOVE POWER OF 2 + # ) + # self._add_equation( + # [ + # config[converter.name + "qpll_frac"] < 1, + # ] + # ) + # config[converter.name + "qpll_N_dot_frac"] = self._add_intermediate( + # config[converter.name + "n_qpll"] + config[converter.name + "qpll_frac"] + # ) + # self.model.add_kpi( + # config[converter.name + "qpll_N_dot_frac"], + # converter.name + "qpll_N_dot_frac", + # ) + + # config[converter.name + "vco_qpll"] = self._add_intermediate( + # fpga_ref + # * config[converter.name + "qpll_N_dot_frac"] + # / ( + # config[converter.name + "m_qpll"] + # * config[converter.name + "qpll_clkoutrate"] + # ) + # ) + # config[converter.name + "vco_qpll1"] = self._add_intermediate( + # fpga_ref + # * config[converter.name + "qpll_N_dot_frac"] + # / ( + # config[converter.name + "m_qpll1"] + # * config[converter.name + "qpll_clkoutrate"] + # ) + # ) + + # # When lane rate > 28.1 Gbps, qpll_frac must be set to 0 + # config[converter.name + "qpll_frac_bypass"] = self._convert_input( + # [0, 1], converter.name + "qpll_frac_bypass" + # ) + # self._add_equation( + # [ + # (1 - config[converter.name + "qpll_frac_bypass"]) + # * converter.bit_clock + # <= int(28.1e9), + # config[converter.name + "qpll_frac_bypass"] + # * config[converter.name + "qpll_frac"] + # == 0, + # ] + # ) + + # else: + # config[converter.name + "vco_qpll"] = self._add_intermediate( + # fpga_ref + # * config[converter.name + "n_qpll"] + # / (config[converter.name + "m_qpll"]) + # ) + # config[converter.name + "vco_qpll1"] = self._add_intermediate( + # fpga_ref + # * config[converter.name + "n_qpll1"] + # / (config[converter.name + "m_qpll1"]) + # ) + + # # Define QPLL band requirements + # config[converter.name + "band_qpll"] = self._convert_input( + # [0, 1], converter.name + "band_qpll" + # ) + + # config[converter.name + "vco_max_qpll"] = self._add_intermediate( + # config[converter.name + "band_qpll"] * vco1_max_qpll + # + (1 - config[converter.name + "band_qpll"]) * vco0_max_qpll + # ) + # config[converter.name + "vco_min_qpll"] = self._add_intermediate( + # config[converter.name + "band_qpll"] * vco1_min_qpll + # + (1 - config[converter.name + "band_qpll"]) * vco0_min_qpll + # ) + + # # Define QPLL1 band requirements + # # if qpll1_allowed: + # config[converter.name + "band_qpll1"] = self._convert_input( + # [0, 1], converter.name + "band_qpll1" + # ) + + # config[converter.name + "vco_max_qpll1"] = self._add_intermediate( + # config[converter.name + "band_qpll1"] * vco1_max_qpll1 + # + (1 - config[converter.name + "band_qpll1"]) * vco0_max_qpll1 + # ) + # config[converter.name + "vco_min_qpll1"] = self._add_intermediate( + # config[converter.name + "band_qpll1"] * vco1_min_qpll1 + # + (1 - config[converter.name + "band_qpll1"]) * vco0_min_qpll1 + # ) + + # # Define if we can use GTY (is available) at full rate + # if self.transceiver_type != "GTY4": + # # QPLL1 does not exist for GTY4 so we cannot bypass the extra dec 2 + # config[converter.name + "qty4_full_rate_divisor"] = self._convert_input( + # 1, name=converter.name + "qty4_full_rate_divisor" + # ) + # else: + # config[converter.name + "qty4_full_rate_divisor"] = self._convert_input( + # [1, 2], name=converter.name + "qty4_full_rate_divisor" + # ) + + # # config[converter.name + "qty4_full_rate_enabled"] = self._add_intermediate( + # # 1 - config[converter.name + "qty4_full_rate_divisor"] + # # ) + + # ####################### + # # CPLL + # # CPLL -> VCO = FPGA_REF * N1*N2/M + # # LR = VCO * 2/D + # # LR = FPGA_REF * N1*N2*2/(M*D) + # config[converter.name + "m_cpll"] = self._convert_input( + # [1, 2], converter.name + "m_cpll" + # ) + # # We do not allow D=16 or D=32 since they do not allow TX/RXOUT DIV + # config[converter.name + "d_cpll"] = self._convert_input( + # [1, 2, 4, 8], converter.name + "d_cpll" + # ) + # config[converter.name + "n1_cpll"] = self._convert_input( + # [4, 5], converter.name + "n1_cpll" + # ) + # config[converter.name + "n2_cpll"] = self._convert_input( + # [1, 2, 3, 4, 5], converter.name + "n2_cpll" + # ) + + # config[converter.name + "vco_cpll"] = self._add_intermediate( + # fpga_ref + # * config[converter.name + "n1_cpll"] + # * config[converter.name + "n2_cpll"] + # / config[converter.name + "m_cpll"] + # ) + + # # Merge + # # if sum([self.force_qpll, self.force_qpll1, self.force_cpll]) > 1: + # # raise Exception("Cannot force multiple PLLs QPLL0, QPLL1, CPLL") + # if ( + # sum( + # [ + # self._get_conv_prop(converter, self.force_qpll), + # self._get_conv_prop(converter, self.force_qpll1), + # self._get_conv_prop(converter, self.force_cpll), + # ] + # ) + # > 1 + # ): + # raise Exception("Cannot force multiple PLLs QPLL0, QPLL1, CPLL") + + # if self._get_conv_prop(converter, self.force_qpll1) and not qpll1_allowed: + # raise Exception( + # "QPLL1 is not available for transceiver " + self.transceiver_type + # ) + + # if self._get_conv_prop(converter, self.force_qpll): + # qpll = 1 + # qpll1 = 0 + # cpll = 0 + # elif self._get_conv_prop(converter, self.force_qpll1): + # qpll = 0 + # qpll1 = 1 + # cpll = 0 + # elif self._get_conv_prop(converter, self.force_cpll): + # qpll = 0 + # qpll1 = 0 + # cpll = 1 + # else: + # qpll = [0, 1] + # if qpll1_allowed: + # qpll1 = [0, 1] + # else: + # qpll1 = 0 + # cpll = [0, 1] + + # config[converter.name + "_use_cpll"] = self._convert_input( + # cpll, converter.name + "_use_cpll" + # ) + # config[converter.name + "_use_qpll"] = self._convert_input( + # qpll, converter.name + "_use_qpll" + # ) + # config[converter.name + "_use_qpll1"] = self._convert_input( + # qpll1, converter.name + "_use_qpll1" + # ) + + # # Select only one PLL + # if ( + # not self._get_conv_prop(converter, self.force_cpll) + # and not self._get_conv_prop(converter, self.force_qpll) + # and not self._get_conv_prop(converter, self.force_qpll1) + # ): + # self._add_equation( + # 1 + # == config[converter.name + "_use_cpll"] + # + config[converter.name + "_use_qpll"] + # + config[converter.name + "_use_qpll1"] + # ) + + # # VCO + # config[converter.name + "vco_select"] = self._add_intermediate( + # config[converter.name + "_use_cpll"] * config[converter.name + "vco_cpll"] + # + config[converter.name + "_use_qpll"] * config[converter.name + "vco_qpll"] + # + config[converter.name + "_use_qpll1"] + # * config[converter.name + "vco_qpll1"] + # ) + + # config[converter.name + "vco_min_select"] = self._add_intermediate( + # config[converter.name + "_use_cpll"] * self.vco_min + # + config[converter.name + "_use_qpll"] + # * config[converter.name + "vco_min_qpll"] + # + config[converter.name + "_use_qpll1"] + # * config[converter.name + "vco_min_qpll1"] + # ) + + # config[converter.name + "vco_max_select"] = self._add_intermediate( + # config[converter.name + "_use_cpll"] * self.vco_max + # + config[converter.name + "_use_qpll"] + # * config[converter.name + "vco_max_qpll"] + # + config[converter.name + "_use_qpll1"] + # * config[converter.name + "vco_max_qpll1"] + # ) + + # config[converter.name + "d_select"] = self._add_intermediate( + # config[converter.name + "_use_cpll"] * config[converter.name + "d_cpll"] + # + config[converter.name + "_use_qpll"] * config[converter.name + "d_qpll"] + # + config[converter.name + "_use_qpll1"] * config[converter.name + "d_qpll1"] + # ) + + # # Note: QPLL has extra /2 after VCO so: + # # QPLL: lanerate == vco/d + # # CPLL: lanerate == vco*2/d + + # config[converter.name + "rate_divisor_select"] = self._add_intermediate( + # config[converter.name + "_use_cpll"] * 2 + # + config[converter.name + "_use_qpll"] + # * config[converter.name + "qty4_full_rate_divisor"] + # + config[converter.name + "_use_qpll1"] + # * config[converter.name + "qty4_full_rate_divisor"] + # ) + + # ####################### + + # # Set all relations + # # QPLL+CPLL + # # + # # CPLL -> VCO = FPGA_REF * N1*N2/M + # # PLLOUT = VCO + # # LR = PLLOUT * 2/D + # # LR = FPGA_REF * N1*N2*2/(M*D) + # # + # # QPLL -> VCO = FPGA_REF * N/(M) + # # PLLOUT = VCO/2 + # # LR = PLLOUT * 2/D + # # LR = FPGA_REF * N/(M*D) + # # + # # LR = FPGA_REF*(A*N1*N2*2/(M*D) + (A-1)*N/(M*D)) + # # A = 0,1 + # # LR*D*M = FPGA_REF*(A*N1*N2*2 + (A-1)*N) + + # self._add_equation( + # [ + # config[converter.name + "vco_select"] + # >= config[converter.name + "vco_min_select"], + # config[converter.name + "vco_select"] + # <= config[converter.name + "vco_max_select"], + # # CPLL + # # converter.bit_clock == vco * 2 / d + # # QPLL + # # converter.bit_clock == vco / d + # config[converter.name + "vco_select"] + # * config[converter.name + "rate_divisor_select"] + # == converter.bit_clock * config[converter.name + "d_select"], + # ] + # ) + + # Add constraints for link clock + # - Must be lanerate/40 204B or lanerate/66 204C + config = self._set_link_layer_requirements(converter, fpga_ref, config, None) + + # Add optimization to favor a single reference clock vs unique ref+device clocks + config[converter.name + "single_clk"] = self._convert_input( + [0, 1], converter.name + "single_clk" + ) + if self.force_separate_device_clock: + sdc = [1] + else: + sdc = [0, 1] + config[converter.name + "two_clks"] = self._convert_input( + sdc, converter.name + "two_clks" + ) + self._add_equation( + [ + config[converter.name + "single_clk"] + + config[converter.name + "two_clks"] + == 1, + ] + ) + # Favor single clock, this equation will be minimized + v = ( + config[converter.name + "single_clk"] + + 1000 * config[converter.name + "two_clks"] + ) + self._add_objective(v) + + # Add constraints to meet sample clock + if self.requires_core_clock_from_device_clock: + if converter.jesd_class == "jesd204b": + core_clock = converter.bit_clock / 40 + else: + core_clock = converter.bit_clock / 66 + + self._add_equation([core_clock == link_out_ref]) + + else: + possible_divs = [] + for samples_per_clock in [1, 2, 4, 8, 16]: + if converter.sample_clock / samples_per_clock <= self.target_Fmax: + possible_divs.append(samples_per_clock) + + if len(possible_divs) == 0: + raise Exception("Link layer output clock rate too high") + + config[converter.name + "_link_out_div"] = self._convert_input( + possible_divs, converter.name + "_samples_per_clock" + ) + + self._add_equation( + [ + ( + config[converter.name + "single_clk"] * fpga_ref + + config[converter.name + "two_clks"] + * link_out_ref + * config[converter.name + "_link_out_div"] + ) + == converter.sample_clock + ] + ) + + return config + + def get_required_clocks( + self, + converter: conv, + fpga_ref: Union[int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar], + link_out_ref: Union[ + int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar + ] = None, + ) -> List: + """Get necessary clocks for QPLL/CPLL configuration. + + Args: + converter (conv): Converter object of converter connected to FPGA + fpga_ref (int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar): + Abstract or concrete reference to FPGA reference clock + link_out_ref (int or GKVariable): Reference clock generated for FPGA + link layer output, also called device clock + + Returns: + List: List of solver variables and constraints + + Raises: + Exception: If solver is not valid + Exception: Link layer out clock required + """ + if self.ref_clock_min == -1 or self.ref_clock_max == -1: + raise Exception("ref_clock_min or ref_clock_max not set") + if "_get_converters" in dir(converter): + converter = ( + converter._get_converters() # type: ignore + ) # Handle nested converters + + if not isinstance(converter, list): + converter = [converter] # type: ignore + + # if self.solver == "gekko": + # self.config = { + # "fpga_ref": self.model.Var( + # # integer=True, + # lb=self.ref_clock_min, + # ub=self.ref_clock_max, + # value=self.ref_clock_min, + # ) + # } + # elif self.solver == "CPLEX": + # # self.config = { + # # "fpga_ref": integer_var( + # # self.ref_clock_min, self.ref_clock_max, "fpga_ref" + # # ) + # # } + # pass + # else: + # raise Exception(f"Unknown solver {self.solver}") + + # https://www.xilinx.com/support/documentation/user_guides/ug476_7Series_Transceivers.pdf # noqa: B950 + + # clock_names = ["fpga_ref"] + clock_names = [] + self.config = {} + if self.force_single_quad_tile: + raise Exception("force_single_quad_tile==1 not implemented") + else: + ####################### + # self.configs = [] + self.dev_clocks = [] + self.ref_clocks = [] + # obs = [] + for cnv in converter: # type: ignore + # rsl = self._get_conv_prop( + # cnv, self.requires_separate_link_layer_out_clock + # ) + # if link_out_ref is None and rsl: + # raise Exception("Link layer out clock required") + + clock_names.append(cnv.name + "fpga_ref") + # self.config[cnv.name+"fpga_ref"] = interval_var( + # self.ref_clock_min, self.ref_clock_max, name=cnv.name+"fpga_ref" + # ) + self.config[cnv.name + "fpga_ref"] = fpga_ref + self.ref_clocks.append(self.config[cnv.name + "fpga_ref"]) + if ( + link_out_ref is not None + ): # self.requires_separate_link_layer_out_clock: + self.config[cnv.name + "link_out_ref"] = link_out_ref + self.ref_clocks.append(self.config[cnv.name + "link_out_ref"]) + config = self._setup_quad_tile( + cnv, + self.config[cnv.name + "fpga_ref"], + self.config[cnv.name + "link_out_ref"], + ) + else: + config = self._setup_quad_tile( + cnv, self.config[cnv.name + "fpga_ref"] + ) + # Set optimizations + # self.model.Obj(self.config[converter.name+"d"]) + # self.model.Obj(self.config[converter.name+"d_cpll"]) + # self.model.Obj(config[converter.name+"d_select"]) + if self.favor_cpll_over_qpll: + if self.solver == "gekko": + self.model.Obj( + -1 * config[cnv.name + "qpll_0_cpll_1"] + ) # Favor CPLL over QPLL + elif self.solver == "CPLEX": + self.model.maximize(config[cnv.name + "qpll_0_cpll_1"]) + # obs.append(-1 * config[cnv.name + "qpll_0_cpll_1"]) + else: + raise Exception(f"Unknown solver {self.solver}") + + self.configs.append(config) + # FPGA also requires clock at device clock rate + if self.request_device_clock: + self.dev_clocks.append(cnv.device_clock) + clock_names.append(cnv.name + "_fpga_device_clock") + + if self.minimize_fpga_ref_clock: + if self.solver == "gekko": + self.model.Obj(self.config[cnv.name + "fpga_ref"]) + elif self.solver == "CPLEX": + # self.model.minimize_static_lex(obs + [self.config[converter.name+"fpga_ref"]]) # noqa: B950 + self.model.minimize(self.config[cnv.name + "fpga_ref"]) # noqa: B950 + # self.model.maximize(obs + self.config[converter.name+"fpga_ref"]) + else: + raise Exception(f"Unknown solver {self.solver}") + + self._clock_names = clock_names + + # return [self.config["fpga_ref"]] + self.dev_clocks + return self.ref_clocks + self.dev_clocks From 40b2cd172cf1fd43e12e059d02303a6f35a4587f Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Mon, 18 Nov 2024 13:27:19 -0700 Subject: [PATCH 02/17] [WIP] Xilinx PLL restructure Signed-off-by: Travis F. Collins --- adijif/fpgas/pll_constraints.py | 854 +++++++++++++++-------------- adijif/fpgas/xilinx/pll.py | 6 + adijif/fpgas/xilinx/sevenseries.py | 29 + tests/common.py | 11 + tests/test_adrv9009.py | 350 ++++++------ tests/test_clocks.py | 621 +++++++++++---------- 6 files changed, 982 insertions(+), 889 deletions(-) create mode 100644 adijif/fpgas/xilinx/pll.py create mode 100644 adijif/fpgas/xilinx/sevenseries.py create mode 100644 tests/common.py diff --git a/adijif/fpgas/pll_constraints.py b/adijif/fpgas/pll_constraints.py index ae56450..546edba 100644 --- a/adijif/fpgas/pll_constraints.py +++ b/adijif/fpgas/pll_constraints.py @@ -1,414 +1,440 @@ -from docplex.cp.modeler import if_then - - -class xilinx_pll_constraints: - # UltraScale+ GTY PLLs - # https://docs.amd.com/v/u/en-US/ug578-ultrascale-gty-transceivers - # GTHs - # https://docs.amd.com/v/u/en-US/ug576-ultrascale-gth-transceivers - # GTXs - # https://docs.amd.com/v/u/en-US/ug476_7Series_Transceivers - - transceivers = {'UltrascalePlus': ['GTYE4', 'GTHE4'], 'Ultrascale': ['GTYE3', 'GTHE3'], '7kSeries': ['GTXE2', 'GTHE2'} - - def add_cpll_contraints(self, config: dict, fpga_ref, converter) -> dict: - """Add Channel PLL (CPLL) constraints. - - This applies to GTH and GTY transceivers. - - Args: - config (dict): Configuration dictionary. - fpga_ref (int): FPGA reference clock. - converter (converter): Converter object. - """ - config[converter.name + "_use_cpll"] = self._convert_input( - [0, 1], converter.name + "_use_cpll" - ) - # v = self.model.integer_var(min=0, max=1, name=converter.name + "_use_cpll2") - # self.model.export_model() - # input() - # return config - - # Add variables - config[converter.name + "_m_cpll"] = self._convert_input( - [1, 2], converter.name + "_m_cpll" - ) - # This is documented oddly - # There are footnotes that include 16 with GTHs, and 16,32 with GTYs - # but its says "not supported for CPLL"?!?!? - config[converter.name + "_d_cpll"] = self._convert_input( - [1, 2, 4, 8], converter.name + "_d_cpll" - ) - config[converter.name + "_n1_cpll"] = self._convert_input( - [4, 5], converter.name + "_n1_cpll" - ) - config[converter.name + "_n2_cpll"] = self._convert_input( - [1, 2, 3, 4, 5], converter.name + "_n2_cpll" - ) - - # Add intermediate variables - config[converter.name + "_pll_out_cpll"] = self._add_intermediate( - fpga_ref - * config[converter.name + "_n1_cpll"] - * config[converter.name + "_n2_cpll"] - / (config[converter.name + "_m_cpll"]) - ) - - # Add constraints - self._add_equation( - [ - if_then( - config[converter.name + "_use_cpll"] == 1, - config[converter.name + "_d_cpll"] * converter.bit_clock - == config[converter.name + "_pll_out_cpll"] * 2, - ), - if_then( - config[converter.name + "_use_cpll"] == 1, - config[converter.name + "_pll_out_cpll"] >= self.vco_min, - ), - if_then( - config[converter.name + "_use_cpll"] == 1, - config[converter.name + "_pll_out_cpll"] <= self.vco_max, - ), - ] - ) - - return config - - # QPLLs - def add_qpll_contraints(self, config: dict, fpga_ref, converter) -> dict: - if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: - return self._add_qpll_contraints_7_series(config, fpga_ref, converter) - - return self._add_qpllN_contraints(config, fpga_ref, converter, 0) - - def add_qpll1_contraints(self, config: dict, fpga_ref, converter): - if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: - config[converter.name + "_use_qpll1"] = 0 - return config - - return self._add_qpllN_contraints(config, fpga_ref, converter, 1) - - def _add_qpllN_contraints(self, config: dict, fpga_ref, converter, n: int) -> dict: - """Add constraints for QPLL{n}. - - Args: - config (dict): Configuration dictionary. - fpga_ref (int): FPGA reference clock. - converter (converter): Converter object. - n (int): QPLL number 0 or 1. - """ - - assert False, "QPLL equations are seem to be wrong based on GT Series and are more based on 7 series vs US vs US+" - # See equation 2-4 and 2-5 in https://docs.amd.com/v/u/en-US/ug576-ultrascale-gth-transceivers (differences is US+ and doesn't seem to be just GTH based) - - if n == 0: - pname = "qpll" - elif n == 1: - pname = "qpll1" - else: - raise Exception("Unsupported QPLL type") - - if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: - config[converter.name + f"_use_{pname}"] = 0 - return config - - # Global flag to use QPLLn - config[converter.name + f"_use_{pname}"] = self._convert_input( - [0, 1], converter.name + f"_use_{pname}" - ) - - # Add variables - config[converter.name + f"_m_{pname}"] = self._convert_input( - [1, 2, 3, 4], converter.name + f"_m_{pname}" - ) - config[converter.name + f"_d_{pname}"] = self._convert_input( - [1, 2, 4, 8, 16, 32], converter.name + f"_d_{pname}" - ) - config[converter.name + f"_n_{pname}"] = self._convert_input( - [*range(16, 160)], converter.name + f"_n_{pname}" - ) - - assert False, "Confirm this is GTH specific" - if self.transceiver_type[:3] == "GTH": - clkout = 2 - else: - clkout = [1, 2] - - config[converter.name + f"_clkout_rate_{pname}"] = self._convert_input( - clkout, converter.name + f"_clkout_rate_{pname}" - ) - config[converter.name + f"_sdm_data_{pname}"] = self.model.integer_var( - min=0, max=(2**24 - 1), name=converter.name + f"_sdm_data_{pname}" - ) - config[converter.name + f"_sdm_width_{pname}"] = self._convert_input( - [16, 20, 24], converter.name + f"_sdm_width_{pname}" - ) - config[converter.name + f"_HIGH_RATE_{pname}"] = self._convert_input( - [0, 1], converter.name + f"_HIGH_RATE_{pname}" - ) - - # Add intermediate variables - config[converter.name + f"_frac_{pname}"] = self._add_intermediate( - config[converter.name + f"_sdm_data_{pname}"] - / (2 ** config[converter.name + f"_sdm_width_{pname}"]) - ) - config[converter.name + f"_n_dot_frac_{pname}"] = self._add_intermediate( - config[converter.name + f"_n_{pname}"] - + config[converter.name + f"_frac_{pname}"] - ) - config[converter.name + f"_pll_out_{pname}"] = self._add_intermediate( - fpga_ref - * config[converter.name + f"_n_dot_frac_{pname}"] - / ( - config[converter.name + f"_m_{pname}"] - * config[converter.name + f"_clkout_rate_{pname}"] - ) - ) - config[converter.name + f"_vco_{pname}"] = self._add_intermediate( - fpga_ref - * config[converter.name + f"_n_dot_frac_{pname}"] - / ( - config[converter.name + f"_m_{pname}"] - * config[converter.name + f"_m_{pname}"] - ) - ) - - # Add constraints - - self._add_equation( - [ - if_then( - config[converter.name + f"_use_{pname}"] == 1, - converter.bit_clock - == config[converter.name + f"_pll_out_{pname}"] - * 2 - / config[converter.name + f"_d_{pname}"], - ), - if_then( - config[converter.name + f"_use_{pname}"] == 1, - config[converter.name + f"_HIGH_RATE_{pname}"] - == (converter.bit_clock >= 28.1e9), - ), - ] - ) - - vco_min = 9.8e9 if n == 0 else 8e9 - vco_max = 16.375e9 if n == 0 else 13e9 - - self._add_equation( - [ - if_then( - config[converter.name + f"_use_{pname}"] == 1, - config[converter.name + f"_vco_{pname}"] >= vco_min, - ), - if_then( - config[converter.name + f"_use_{pname}"] == 1, - config[converter.name + f"_vco_{pname}"] <= vco_max, - ), - if_then( - config[converter.name + f"_HIGH_RATE_{pname}"] == 1, - config[converter.name + f"_frac_{pname}"] == 0, - ), - ] - ) - - return config - - def _add_qpll_contraints_7_series(self, config: dict, fpga_ref, converter) -> dict: - """Add constraints for QPLL for 7 series FPGAs. - - Args: - config (dict): Configuration dictionary. - fpga_ref (int): FPGA reference clock. - converter (converter): Converter object. - """ - pname = "qpll" - - # if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: - # config[converter.name + f"_use_{pname}"] = 0 - # return config - - # Double check this constraint - if self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: - raise Exception("Invalid GT is for 7 series FPGAs") - - # Global flag to use QPLLn - config[converter.name + f"_use_{pname}"] = self._convert_input( - [0, 1], converter.name + f"_use_{pname}" - ) - - # Add variables - config[converter.name + f"_m_{pname}"] = self._convert_input( - [1, 2, 3, 4], converter.name + f"_m_{pname}" - ) - config[converter.name + f"_d_{pname}"] = self._convert_input( - [1, 2, 4, 8, 16], converter.name + f"_d_{pname}" - ) - config[converter.name + f"_n_{pname}"] = self._convert_input( - [16, 20, 32, 40, 64, 66, 80, 100], converter.name + f"_n_{pname}" - ) - - # Add intermediate variables - config[converter.name + f"_vco_{pname}"] = self._add_intermediate( - fpga_ref - * config[converter.name + f"_n_{pname}"] - / config[converter.name + f"_m_{pname}"] - ) - config[converter.name + f"_pll_out_{pname}"] = self._add_intermediate( - fpga_ref - * config[converter.name + f"_n_{pname}"] - / (config[converter.name + f"_m_{pname}"] * 2) - ) - - # Add constraints - self._add_equation( - [ - if_then( - config[converter.name + f"_use_{pname}"] == 1, - converter.bit_clock - == config[converter.name + f"_pll_out_{pname}"] - * 2 - / config[converter.name + f"_d_{pname}"], - ), - ] - ) - - assert False, "Confirm this is GTH/GTX specific" - if self.transceiver_type[:3] == "GTH": - vco_min = 8e9 - vco_max = 13.1e9 - self._add_equation( - [ - if_then( - config[converter.name + f"_use_{pname}"] == 1, - config[converter.name + f"_vco_{pname}"] >= vco_min, - ), - if_then( - config[converter.name + f"_use_{pname}"] == 1, - config[converter.name + f"_vco_{pname}"] <= vco_max, - ), - ] - ) - elif self.transceiver_type[:3] == "GTX": - config[converter.name + f"_lower_band_{pname}"] = self._convert_input( - [0, 1], converter.name + f"_lower_band_{pname}" - ) - # Lower band - c1 = if_then( - config[converter.name + f"_use_{pname}"] == 1, - if_then( - config[converter.name + f"_lower_band_{pname}"] == 0, - config[converter.name + f"_vco_{pname}"] >= 5.93e9, - ), - ) - c2 = if_then( - config[converter.name + f"_use_{pname}"] == 1, - if_then( - config[converter.name + f"_lower_band_{pname}"] == 0, - config[converter.name + f"_vco_{pname}"] <= 8e9, - ), - ) - # Upper band - c3 = if_then( - config[converter.name + f"_use_{pname}"] == 1, - if_then( - config[converter.name + f"_lower_band_{pname}"] == 1, - config[converter.name + f"_vco_{pname}"] >= 9.8e9, - ), - ) - c4 = if_then( - config[converter.name + f"_use_{pname}"] == 1, - if_then( - config[converter.name + f"_lower_band_{pname}"] == 1, - config[converter.name + f"_vco_{pname}"] <= 12.5e9, - ), - ) - self._add_equation([c1, c2, c3, c4]) - - else: - raise Exception("Unsupported transceiver type") - - return config - - def get_cpll_config(self, config: dict, converter, fpga_ref) -> dict: - """Get CPLL configuration. - - Args: - config (dict): Configuration dictionary. - converter (converter): Converter object. - """ - pll_config = {} - pll_config["type"] = "cpll" - for k in ["m", "d", "n1", "n2"]: - pll_config[k] = self._get_val(config[converter.name + "_" + k + "_cpll"]) - - pll_config["vco"] = ( - fpga_ref * pll_config["n1"] * pll_config["n2"] / pll_config["m"] # type: ignore # noqa: B950 - ) - # Check - assert ( - pll_config["vco"] * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 - ), "Invalid CPLL lane rate" - - return pll_config - - def get_qpll_config(self, config: dict, converter, fpga_ref, n: int) -> dict: - """Get QPLL configuration. - - Args: - config (dict): Configuration dictionary. - converter (converter): Converter object. - n (int): QPLL number 0 or 1. - """ - if n == 0: - pname = "qpll" - elif n == 1: - pname = "qpll1" - else: - raise Exception("Unsupported QPLL type") - - if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: - return self.get_qpll_config_7_series(config, converter, fpga_ref) - - pll_config = {} - pll_config["type"] = pname - for k in ["m", "d", "n", "clkout_rate", "sdm_data", "sdm_width", "HIGH_RATE"]: - pll_config[k] = self._get_val( - config[converter.name + "_" + k + "_" + pname] - ) - - pll_config["frac"] = pll_config["sdm_data"] / (2 ** pll_config["sdm_width"]) - pll_config["n_dot_frac"] = pll_config["n"] + pll_config["frac"] - # FIXME: Check clkout_rate between GTH and GTY - pll_config["vco"] = fpga_ref * pll_config["n_dot_frac"] / (pll_config["m"]) - # Check - assert ( - pll_config["vco"] * 2 / (pll_config["d"] * pll_config["clkout_rate"]) == converter.bit_clock # type: ignore # noqa: B950 - ), f"Invalid {pname} lane rate. {pll_config['vco'] * 2 / pll_config['d']} != {converter.bit_clock}" # type: ignore # noqa: B950 - - return pll_config - - def get_qpll_config_7_series(self, config: dict, converter, fpga_ref) -> dict: - """Get QPLL configuration for 7 series FPGAs. - - Args: - config (dict): Configuration dictionary. - converter (converter): Converter object. - """ - pname = "qpll" - - pll_config = {"type": pname} - for k in ["m", "d", "n"]: - pll_config[k] = self._get_val( - config[converter.name + "_" + k + "_" + pname] - ) - - pll_config["vco"] = fpga_ref * pll_config["n"] / pll_config["m"] - pll_clk_out = fpga_ref * pll_config["n"] / (pll_config["m"] * 2) - # Check - assert ( - pll_clk_out * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 - ), f"Invalid QPLL lane rate {pll_config['vco'] * 2 / pll_config['d']} != {converter.bit_clock}" - - return pll_config +from docplex.cp.modeler import if_then + + +class xilinx_pll_constraints: + # UltraScale+ GTY PLLs + # https://docs.amd.com/v/u/en-US/ug578-ultrascale-gty-transceivers + # GTHs + # https://docs.amd.com/v/u/en-US/ug576-ultrascale-gth-transceivers + # GTXs + # https://docs.amd.com/v/u/en-US/ug476_7Series_Transceivers + + transceivers = { + "UltrascalePlus": ["GTYE4", "GTHE4"], + "Ultrascale": ["GTYE3", "GTHE3"], + "7kSeries": ["GTXE2", "GTHE2"], + } + + def trx_gen(self) -> int: + """Get transceiver generation (2,3,4) + + Returns: + int: generation of transceiver + """ + return int(self.transceiver_type[-1]) + + def trx_variant(self): + """Get transceiver variant (GTX, GTH, GTY, ...) + + Returns: + str: Transceiver variant + """ + # return self.transceiver_type[:2] + trxt = self.transceiver_type[:2] + print(trxt) + assert len(trxt) == 3 + return trxt + + def add_cpll_contraints(self, config: dict, fpga_ref, converter) -> dict: + """Add Channel PLL (CPLL) constraints. + + This applies to GTH and GTY transceivers. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int): FPGA reference clock. + converter (converter): Converter object. + """ + config[converter.name + "_use_cpll"] = self._convert_input( + [0, 1], converter.name + "_use_cpll" + ) + # v = self.model.integer_var(min=0, max=1, name=converter.name + "_use_cpll2") + # self.model.export_model() + # input() + # return config + + # Add variables + config[converter.name + "_m_cpll"] = self._convert_input( + [1, 2], converter.name + "_m_cpll" + ) + # This is documented oddly + # There are footnotes that include 16 with GTHs, and 16,32 with GTYs + # but its says "not supported for CPLL"?!?!? + config[converter.name + "_d_cpll"] = self._convert_input( + [1, 2, 4, 8], converter.name + "_d_cpll" + ) + config[converter.name + "_n1_cpll"] = self._convert_input( + [4, 5], converter.name + "_n1_cpll" + ) + config[converter.name + "_n2_cpll"] = self._convert_input( + [1, 2, 3, 4, 5], converter.name + "_n2_cpll" + ) + + # Add intermediate variables + config[converter.name + "_pll_out_cpll"] = self._add_intermediate( + fpga_ref + * config[converter.name + "_n1_cpll"] + * config[converter.name + "_n2_cpll"] + / (config[converter.name + "_m_cpll"]) + ) + + # Add constraints + self._add_equation( + [ + if_then( + config[converter.name + "_use_cpll"] == 1, + config[converter.name + "_d_cpll"] * converter.bit_clock + == config[converter.name + "_pll_out_cpll"] * 2, + ), + if_then( + config[converter.name + "_use_cpll"] == 1, + config[converter.name + "_pll_out_cpll"] >= self.vco_min, + ), + if_then( + config[converter.name + "_use_cpll"] == 1, + config[converter.name + "_pll_out_cpll"] <= self.vco_max, + ), + ] + ) + + return config + + # QPLLs + def add_qpll_contraints(self, config: dict, fpga_ref, converter) -> dict: + if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: + return self._add_qpll_contraints_7_series(config, fpga_ref, converter) + + return self._add_qpllN_contraints(config, fpga_ref, converter, 0) + + def add_qpll1_contraints(self, config: dict, fpga_ref, converter): + if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: + config[converter.name + "_use_qpll1"] = 0 + return config + + return self._add_qpllN_contraints(config, fpga_ref, converter, 1) + + def _add_qpllN_contraints(self, config: dict, fpga_ref, converter, n: int) -> dict: + """Add constraints for QPLL{n}. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int): FPGA reference clock. + converter (converter): Converter object. + n (int): QPLL number 0 or 1. + """ + + assert ( + False + ), "QPLL equations are seem to be wrong based on GT Series and are more based on 7 series vs US vs US+" + # See equation 2-4 and 2-5 in https://docs.amd.com/v/u/en-US/ug576-ultrascale-gth-transceivers (differences is US+ and doesn't seem to be just GTH based) + + if n == 0: + pname = "qpll" + elif n == 1: + pname = "qpll1" + else: + raise Exception("Unsupported QPLL type") + + if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: + config[converter.name + f"_use_{pname}"] = 0 + return config + + # Global flag to use QPLLn + config[converter.name + f"_use_{pname}"] = self._convert_input( + [0, 1], converter.name + f"_use_{pname}" + ) + + # Add variables + config[converter.name + f"_m_{pname}"] = self._convert_input( + [1, 2, 3, 4], converter.name + f"_m_{pname}" + ) + config[converter.name + f"_d_{pname}"] = self._convert_input( + [1, 2, 4, 8, 16, 32], converter.name + f"_d_{pname}" + ) + config[converter.name + f"_n_{pname}"] = self._convert_input( + [*range(16, 160)], converter.name + f"_n_{pname}" + ) + + assert False, "Confirm this is GTH specific" + if self.transceiver_type[:3] == "GTH": + clkout = 2 + else: + clkout = [1, 2] + + config[converter.name + f"_clkout_rate_{pname}"] = self._convert_input( + clkout, converter.name + f"_clkout_rate_{pname}" + ) + config[converter.name + f"_sdm_data_{pname}"] = self.model.integer_var( + min=0, max=(2**24 - 1), name=converter.name + f"_sdm_data_{pname}" + ) + config[converter.name + f"_sdm_width_{pname}"] = self._convert_input( + [16, 20, 24], converter.name + f"_sdm_width_{pname}" + ) + config[converter.name + f"_HIGH_RATE_{pname}"] = self._convert_input( + [0, 1], converter.name + f"_HIGH_RATE_{pname}" + ) + + # Add intermediate variables + config[converter.name + f"_frac_{pname}"] = self._add_intermediate( + config[converter.name + f"_sdm_data_{pname}"] + / (2 ** config[converter.name + f"_sdm_width_{pname}"]) + ) + config[converter.name + f"_n_dot_frac_{pname}"] = self._add_intermediate( + config[converter.name + f"_n_{pname}"] + + config[converter.name + f"_frac_{pname}"] + ) + config[converter.name + f"_pll_out_{pname}"] = self._add_intermediate( + fpga_ref + * config[converter.name + f"_n_dot_frac_{pname}"] + / ( + config[converter.name + f"_m_{pname}"] + * config[converter.name + f"_clkout_rate_{pname}"] + ) + ) + config[converter.name + f"_vco_{pname}"] = self._add_intermediate( + fpga_ref + * config[converter.name + f"_n_dot_frac_{pname}"] + / ( + config[converter.name + f"_m_{pname}"] + * config[converter.name + f"_m_{pname}"] + ) + ) + + # Add constraints + + self._add_equation( + [ + if_then( + config[converter.name + f"_use_{pname}"] == 1, + converter.bit_clock + == config[converter.name + f"_pll_out_{pname}"] + * 2 + / config[converter.name + f"_d_{pname}"], + ), + if_then( + config[converter.name + f"_use_{pname}"] == 1, + config[converter.name + f"_HIGH_RATE_{pname}"] + == (converter.bit_clock >= 28.1e9), + ), + ] + ) + + vco_min = 9.8e9 if n == 0 else 8e9 + vco_max = 16.375e9 if n == 0 else 13e9 + + self._add_equation( + [ + if_then( + config[converter.name + f"_use_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] >= vco_min, + ), + if_then( + config[converter.name + f"_use_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] <= vco_max, + ), + if_then( + config[converter.name + f"_HIGH_RATE_{pname}"] == 1, + config[converter.name + f"_frac_{pname}"] == 0, + ), + ] + ) + + return config + + def _add_qpll_contraints_7_series(self, config: dict, fpga_ref, converter) -> dict: + """Add constraints for QPLL for 7 series FPGAs. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int): FPGA reference clock. + converter (converter): Converter object. + """ + pname = "qpll" + + # if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: + # config[converter.name + f"_use_{pname}"] = 0 + # return config + + # Double check this constraint + if self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: + raise Exception("Invalid GT is for 7 series FPGAs") + + # Global flag to use QPLLn + config[converter.name + f"_use_{pname}"] = self._convert_input( + [0, 1], converter.name + f"_use_{pname}" + ) + + # Add variables + config[converter.name + f"_m_{pname}"] = self._convert_input( + [1, 2, 3, 4], converter.name + f"_m_{pname}" + ) + config[converter.name + f"_d_{pname}"] = self._convert_input( + [1, 2, 4, 8, 16], converter.name + f"_d_{pname}" + ) + config[converter.name + f"_n_{pname}"] = self._convert_input( + [16, 20, 32, 40, 64, 66, 80, 100], converter.name + f"_n_{pname}" + ) + + # Add intermediate variables + config[converter.name + f"_vco_{pname}"] = self._add_intermediate( + fpga_ref + * config[converter.name + f"_n_{pname}"] + / config[converter.name + f"_m_{pname}"] + ) + config[converter.name + f"_pll_out_{pname}"] = self._add_intermediate( + fpga_ref + * config[converter.name + f"_n_{pname}"] + / (config[converter.name + f"_m_{pname}"] * 2) + ) + + # Add constraints + self._add_equation( + [ + if_then( + config[converter.name + f"_use_{pname}"] == 1, + converter.bit_clock + == config[converter.name + f"_pll_out_{pname}"] + * 2 + / config[converter.name + f"_d_{pname}"], + ), + ] + ) + + assert False, "Confirm this is GTH/GTX specific" + if self.transceiver_type[:3] == "GTH": + vco_min = 8e9 + vco_max = 13.1e9 + self._add_equation( + [ + if_then( + config[converter.name + f"_use_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] >= vco_min, + ), + if_then( + config[converter.name + f"_use_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] <= vco_max, + ), + ] + ) + elif self.transceiver_type[:3] == "GTX": + config[converter.name + f"_lower_band_{pname}"] = self._convert_input( + [0, 1], converter.name + f"_lower_band_{pname}" + ) + # Lower band + c1 = if_then( + config[converter.name + f"_use_{pname}"] == 1, + if_then( + config[converter.name + f"_lower_band_{pname}"] == 0, + config[converter.name + f"_vco_{pname}"] >= 5.93e9, + ), + ) + c2 = if_then( + config[converter.name + f"_use_{pname}"] == 1, + if_then( + config[converter.name + f"_lower_band_{pname}"] == 0, + config[converter.name + f"_vco_{pname}"] <= 8e9, + ), + ) + # Upper band + c3 = if_then( + config[converter.name + f"_use_{pname}"] == 1, + if_then( + config[converter.name + f"_lower_band_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] >= 9.8e9, + ), + ) + c4 = if_then( + config[converter.name + f"_use_{pname}"] == 1, + if_then( + config[converter.name + f"_lower_band_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] <= 12.5e9, + ), + ) + self._add_equation([c1, c2, c3, c4]) + + else: + raise Exception("Unsupported transceiver type") + + return config + + def get_cpll_config(self, config: dict, converter, fpga_ref) -> dict: + """Get CPLL configuration. + + Args: + config (dict): Configuration dictionary. + converter (converter): Converter object. + """ + pll_config = {} + pll_config["type"] = "cpll" + for k in ["m", "d", "n1", "n2"]: + pll_config[k] = self._get_val(config[converter.name + "_" + k + "_cpll"]) + + pll_config["vco"] = ( + fpga_ref * pll_config["n1"] * pll_config["n2"] / pll_config["m"] # type: ignore # noqa: B950 + ) + # Check + assert ( + pll_config["vco"] * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 + ), "Invalid CPLL lane rate" + + return pll_config + + def get_qpll_config(self, config: dict, converter, fpga_ref, n: int) -> dict: + """Get QPLL configuration. + + Args: + config (dict): Configuration dictionary. + converter (converter): Converter object. + n (int): QPLL number 0 or 1. + """ + if n == 0: + pname = "qpll" + elif n == 1: + pname = "qpll1" + else: + raise Exception("Unsupported QPLL type") + + if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: + return self.get_qpll_config_7_series(config, converter, fpga_ref) + + pll_config = {} + pll_config["type"] = pname + for k in ["m", "d", "n", "clkout_rate", "sdm_data", "sdm_width", "HIGH_RATE"]: + pll_config[k] = self._get_val( + config[converter.name + "_" + k + "_" + pname] + ) + + pll_config["frac"] = pll_config["sdm_data"] / (2 ** pll_config["sdm_width"]) + pll_config["n_dot_frac"] = pll_config["n"] + pll_config["frac"] + # FIXME: Check clkout_rate between GTH and GTY + pll_config["vco"] = fpga_ref * pll_config["n_dot_frac"] / (pll_config["m"]) + # Check + assert ( + pll_config["vco"] * 2 / (pll_config["d"] * pll_config["clkout_rate"]) == converter.bit_clock # type: ignore # noqa: B950 + ), f"Invalid {pname} lane rate. {pll_config['vco'] * 2 / pll_config['d']} != {converter.bit_clock}" # type: ignore # noqa: B950 + + return pll_config + + def get_qpll_config_7_series(self, config: dict, converter, fpga_ref) -> dict: + """Get QPLL configuration for 7 series FPGAs. + + Args: + config (dict): Configuration dictionary. + converter (converter): Converter object. + """ + pname = "qpll" + + pll_config = {"type": pname} + for k in ["m", "d", "n"]: + pll_config[k] = self._get_val( + config[converter.name + "_" + k + "_" + pname] + ) + + pll_config["vco"] = fpga_ref * pll_config["n"] / pll_config["m"] + pll_clk_out = fpga_ref * pll_config["n"] / (pll_config["m"] * 2) + # Check + assert ( + pll_clk_out * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 + ), f"Invalid QPLL lane rate {pll_config['vco'] * 2 / pll_config['d']} != {converter.bit_clock}" + + return pll_config diff --git a/adijif/fpgas/xilinx/pll.py b/adijif/fpgas/xilinx/pll.py new file mode 100644 index 0000000..4fb24d5 --- /dev/null +++ b/adijif/fpgas/xilinx/pll.py @@ -0,0 +1,6 @@ +from abc import ABC, ABCMeta, abstractmethod + + +class XilinxPLL: + def transceiver_types_available(self): + ... diff --git a/adijif/fpgas/xilinx/sevenseries.py b/adijif/fpgas/xilinx/sevenseries.py new file mode 100644 index 0000000..629f793 --- /dev/null +++ b/adijif/fpgas/xilinx/sevenseries.py @@ -0,0 +1,29 @@ +from .pll import XilinxPLL + + +class SevenSeries(XilinxPLL): + + # References + # GTXs + # https://docs.amd.com/v/u/en-US/ug476_7Series_Transceivers + + transceiver_types_available = ["GTXE2", "GTHE2"] + + +class SevenSeriesCPLL: + + M_available = [1, 2] + _M = [1, 2] + + @property + def M(self): + return self._M + + @M.setter + def M(self, value): + if value in self.M_available: + self._M = value + else: + raise ValueError(f"M value not available. Choose from {self.M_available}") + + diff --git a/tests/common.py b/tests/common.py new file mode 100644 index 0000000..c45068d --- /dev/null +++ b/tests/common.py @@ -0,0 +1,11 @@ +import pytest + +try: + from gekko import GEKKO # type: ignore +except ImportError: + GEKKO = None + + +def skip_solver(solver): + if solver.lower() == "gekko" and GEKKO is None: + pytest.skip("Gekko not available") diff --git a/tests/test_adrv9009.py b/tests/test_adrv9009.py index 80d5484..fad4634 100644 --- a/tests/test_adrv9009.py +++ b/tests/test_adrv9009.py @@ -1,173 +1,177 @@ -# flake8: noqa - -import pytest - -import adijif - - -@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) -@pytest.mark.parametrize("converter", ["adrv9009_rx", "adrv9009_tx"]) -def test_adrv9009_rxtx_ad9528_solver_compact(solver, converter): - vcxo = 122.88e6 - - sys = adijif.system(converter, "ad9528", "xilinx", vcxo, solver=solver) - - # Get Converter clocking requirements - sys.converter.sample_clock = 122.88e6 - - if converter == "adrv9009_rx": - sys.converter.decimation = 4 - else: - sys.converter.interpolation = 4 - - sys.converter.L = 2 - sys.converter.M = 4 - sys.converter.N = 16 - sys.converter.Np = 16 - - sys.converter.K = 32 - sys.converter.F = 4 - assert sys.converter.S == 1 - # sys.Debug_Solver = True - - assert 9830.4e6 / 2 == sys.converter.bit_clock - assert sys.converter.multiframe_clock == 7.68e6 / 2 # LMFC - assert sys.converter.device_clock == 9830.4e6 / 2 / 40 - - # Set FPGA config - sys.fpga.setup_by_dev_kit_name("zc706") - sys.fpga.out_clk_select = "XCVR_REFCLK" - sys.fpga.force_qpll = True - - # Set clock chip - sys.clock.d = [*range(1, 257)] # Limit output dividers - - cfg = sys.solve() - # print(cfg) - from pprint import pprint - - pprint(cfg) - - ref = { - "gekko": {"clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 4, 8, 256]}}, - "CPLEX": {"clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 8, 256]}}, - } - - assert cfg["clock"]["r1"] == ref[solver]["clock"]["r1"] - assert cfg["clock"]["n2"] == ref[solver]["clock"]["n2"] - assert cfg["clock"]["m1"] == ref[solver]["clock"]["m1"] - assert ( - cfg["clock"]["output_clocks"][f"{converter.upper()}_fpga_ref_clk"]["rate"] - == 122880000.0 - ) # 98304000 - for div in cfg["clock"]["out_dividers"]: - assert div in ref[solver]["clock"]["out_dividers"] - - -@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) -def test_adrv9009_ad9528_solver_compact(solver): - vcxo = 122.88e6 - sys = adijif.system("adrv9009", "ad9528", "xilinx", vcxo, solver=solver) - - # Rx - sys.converter.adc.sample_clock = 122.88e6 - sys.converter.adc.decimation = 4 - sys.converter.adc.L = 2 - sys.converter.adc.M = 4 - sys.converter.adc.N = 16 - sys.converter.adc.Np = 16 - - sys.converter.adc.K = 32 - sys.converter.adc.F = 4 - assert sys.converter.adc.S == 1 - - assert 9830.4e6 / 2 == sys.converter.adc.bit_clock - assert sys.converter.adc.multiframe_clock == 7.68e6 / 2 # LMFC - assert sys.converter.adc.device_clock == 9830.4e6 / 2 / 40 - - # Tx - sys.converter.dac.sample_clock = 122.88e6 - sys.converter.dac.interpolation = 4 - sys.converter.dac.L = 2 - sys.converter.dac.M = 4 - sys.converter.dac.N = 16 - sys.converter.dac.Np = 16 - - sys.converter.dac.K = 32 - sys.converter.dac.F = 4 - assert sys.converter.dac.S == 1 - - assert 9830.4e6 / 2 == sys.converter.dac.bit_clock - assert sys.converter.dac.multiframe_clock == 7.68e6 / 2 # LMFC - assert sys.converter.dac.device_clock == 9830.4e6 / 2 / 40 - - # Set FPGA config - sys.fpga.setup_by_dev_kit_name("zc706") - sys.fpga.out_clk_select = "XCVR_REFCLK" - sys.fpga.force_qpll = True - - # Set clock chip - sys.clock.d = [*range(1, 257)] # Limit output dividers - - if solver == "gekko": - with pytest.raises(AssertionError): - cfg = sys.solve() - pytest.xfail("gekko currently unsupported") - - cfg = sys.solve() - print(cfg) - - ref = { - "gekko": {"clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 8, 256]}}, - "CPLEX": {"clock": {"r1": 1, "n2": 6, "m1": 5, "out_dividers": [1, 6, 192]}}, - } - - assert cfg["clock"]["r1"] == ref[solver]["clock"]["r1"] - assert cfg["clock"]["n2"] == ref[solver]["clock"]["n2"] - assert cfg["clock"]["m1"] == ref[solver]["clock"]["m1"] - - output_clocks = cfg["clock"]["output_clocks"] - assert output_clocks["adc_fpga_ref_clk"]["rate"] == 122880000.0 - assert output_clocks["dac_fpga_ref_clk"]["rate"] == 122880000.0 - - for div in cfg["clock"]["out_dividers"]: - assert div in ref[solver]["clock"]["out_dividers"] - - -@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) -def test_adrv9009_ad9528_quick_config(solver): - vcxo = 122.88e6 - - sys = adijif.system("adrv9009", "ad9528", "xilinx", vcxo, solver=solver) - sys.converter.adc.sample_clock = 122.88e6 - sys.converter.dac.sample_clock = 122.88e6 - - sys.converter.adc.decimation = 4 - sys.converter.dac.interpolation = 4 - - mode_rx = "17" - mode_tx = "6" - sys.converter.adc.set_quick_configuration_mode(mode_rx, "jesd204b") - sys.converter.dac.set_quick_configuration_mode(mode_tx, "jesd204b") - - assert sys.converter.adc.L == 2 - assert sys.converter.adc.M == 4 - assert sys.converter.adc.N == 16 - assert sys.converter.adc.Np == 16 - - assert sys.converter.dac.L == 2 - assert sys.converter.dac.M == 4 - assert sys.converter.dac.N == 16 - assert sys.converter.dac.Np == 16 - - sys.converter.adc._check_clock_relations() - sys.converter.dac._check_clock_relations() - - sys.fpga.setup_by_dev_kit_name("zc706") - - if solver == "gekko": - with pytest.raises(AssertionError): - cfg = sys.solve() - pytest.xfail("gekko currently unsupported") - - cfg = sys.solve() +# flake8: noqa + +import pytest + +import adijif +from .common import skip_solver + + +@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) +@pytest.mark.parametrize("converter", ["adrv9009_rx", "adrv9009_tx"]) +def test_adrv9009_rxtx_ad9528_solver_compact(solver, converter): + skip_solver(solver) + vcxo = 122.88e6 + + sys = adijif.system(converter, "ad9528", "xilinx", vcxo, solver=solver) + + # Get Converter clocking requirements + sys.converter.sample_clock = 122.88e6 + + if converter == "adrv9009_rx": + sys.converter.decimation = 4 + else: + sys.converter.interpolation = 4 + + sys.converter.L = 2 + sys.converter.M = 4 + sys.converter.N = 16 + sys.converter.Np = 16 + + sys.converter.K = 32 + sys.converter.F = 4 + assert sys.converter.S == 1 + # sys.Debug_Solver = True + + assert 9830.4e6 / 2 == sys.converter.bit_clock + assert sys.converter.multiframe_clock == 7.68e6 / 2 # LMFC + assert sys.converter.device_clock == 9830.4e6 / 2 / 40 + + # Set FPGA config + sys.fpga.setup_by_dev_kit_name("zc706") + sys.fpga.out_clk_select = "XCVR_REFCLK" + sys.fpga.force_qpll = True + + # Set clock chip + sys.clock.d = [*range(1, 257)] # Limit output dividers + + cfg = sys.solve() + # print(cfg) + from pprint import pprint + + pprint(cfg) + + ref = { + "gekko": {"clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 4, 8, 256]}}, + "CPLEX": {"clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 8, 256]}}, + } + + assert cfg["clock"]["r1"] == ref[solver]["clock"]["r1"] + assert cfg["clock"]["n2"] == ref[solver]["clock"]["n2"] + assert cfg["clock"]["m1"] == ref[solver]["clock"]["m1"] + assert ( + cfg["clock"]["output_clocks"][f"{converter.upper()}_fpga_ref_clk"]["rate"] + == 122880000.0 + ) # 98304000 + for div in cfg["clock"]["out_dividers"]: + assert div in ref[solver]["clock"]["out_dividers"] + + +@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) +def test_adrv9009_ad9528_solver_compact(solver): + skip_solver(solver) + vcxo = 122.88e6 + sys = adijif.system("adrv9009", "ad9528", "xilinx", vcxo, solver=solver) + + # Rx + sys.converter.adc.sample_clock = 122.88e6 + sys.converter.adc.decimation = 4 + sys.converter.adc.L = 2 + sys.converter.adc.M = 4 + sys.converter.adc.N = 16 + sys.converter.adc.Np = 16 + + sys.converter.adc.K = 32 + sys.converter.adc.F = 4 + assert sys.converter.adc.S == 1 + + assert 9830.4e6 / 2 == sys.converter.adc.bit_clock + assert sys.converter.adc.multiframe_clock == 7.68e6 / 2 # LMFC + assert sys.converter.adc.device_clock == 9830.4e6 / 2 / 40 + + # Tx + sys.converter.dac.sample_clock = 122.88e6 + sys.converter.dac.interpolation = 4 + sys.converter.dac.L = 2 + sys.converter.dac.M = 4 + sys.converter.dac.N = 16 + sys.converter.dac.Np = 16 + + sys.converter.dac.K = 32 + sys.converter.dac.F = 4 + assert sys.converter.dac.S == 1 + + assert 9830.4e6 / 2 == sys.converter.dac.bit_clock + assert sys.converter.dac.multiframe_clock == 7.68e6 / 2 # LMFC + assert sys.converter.dac.device_clock == 9830.4e6 / 2 / 40 + + # Set FPGA config + sys.fpga.setup_by_dev_kit_name("zc706") + sys.fpga.out_clk_select = "XCVR_REFCLK" + sys.fpga.force_qpll = True + + # Set clock chip + sys.clock.d = [*range(1, 257)] # Limit output dividers + + if solver == "gekko": + with pytest.raises(AssertionError): + cfg = sys.solve() + pytest.xfail("gekko currently unsupported") + + cfg = sys.solve() + print(cfg) + + ref = { + "gekko": {"clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 8, 256]}}, + "CPLEX": {"clock": {"r1": 1, "n2": 6, "m1": 5, "out_dividers": [1, 6, 192]}}, + } + + assert cfg["clock"]["r1"] == ref[solver]["clock"]["r1"] + assert cfg["clock"]["n2"] == ref[solver]["clock"]["n2"] + assert cfg["clock"]["m1"] == ref[solver]["clock"]["m1"] + + output_clocks = cfg["clock"]["output_clocks"] + assert output_clocks["adc_fpga_ref_clk"]["rate"] == 122880000.0 + assert output_clocks["dac_fpga_ref_clk"]["rate"] == 122880000.0 + + for div in cfg["clock"]["out_dividers"]: + assert div in ref[solver]["clock"]["out_dividers"] + + +@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) +def test_adrv9009_ad9528_quick_config(solver): + skip_solver(solver) + vcxo = 122.88e6 + + sys = adijif.system("adrv9009", "ad9528", "xilinx", vcxo, solver=solver) + sys.converter.adc.sample_clock = 122.88e6 + sys.converter.dac.sample_clock = 122.88e6 + + sys.converter.adc.decimation = 4 + sys.converter.dac.interpolation = 4 + + mode_rx = "17" + mode_tx = "6" + sys.converter.adc.set_quick_configuration_mode(mode_rx, "jesd204b") + sys.converter.dac.set_quick_configuration_mode(mode_tx, "jesd204b") + + assert sys.converter.adc.L == 2 + assert sys.converter.adc.M == 4 + assert sys.converter.adc.N == 16 + assert sys.converter.adc.Np == 16 + + assert sys.converter.dac.L == 2 + assert sys.converter.dac.M == 4 + assert sys.converter.dac.N == 16 + assert sys.converter.dac.Np == 16 + + sys.converter.adc._check_clock_relations() + sys.converter.dac._check_clock_relations() + + sys.fpga.setup_by_dev_kit_name("zc706") + + if solver == "gekko": + with pytest.raises(AssertionError): + cfg = sys.solve() + pytest.xfail("gekko currently unsupported") + + cfg = sys.solve() diff --git a/tests/test_clocks.py b/tests/test_clocks.py index 2502355..94e08fd 100644 --- a/tests/test_clocks.py +++ b/tests/test_clocks.py @@ -1,302 +1,319 @@ -# flake8: noqa -import pprint - -import pytest -from gekko import GEKKO # type: ignore - -import adijif - - -@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) -def test_ad9545_validate_fail(solver): - msg = r"Solution Not Found" - - with pytest.raises(Exception, match=msg): - clk = adijif.ad9545(solver=solver) - - clk.avoid_min_max_PLL_rates = True - clk.minimize_input_dividers = True - - input_refs = [(0, 42345235)] - output_clocks = [(0, 3525235234123)] - - input_refs = list(map(lambda x: (int(x[0]), int(x[1])), input_refs)) - output_clocks = list(map(lambda x: (int(x[0]), int(x[1])), output_clocks)) - - clk.set_requested_clocks(input_refs, output_clocks) - - clk.solve() - - -@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) -@pytest.mark.parametrize("out_freq", [30720000, 25e6]) -def test_ad9545_validate_pass(solver, out_freq): - clk = adijif.ad9545(solver=solver) - - clk.avoid_min_max_PLL_rates = True - clk.minimize_input_dividers = True - - input_refs = [(0, 1), (1, 10e6)] - output_clocks = [(0, int(out_freq))] - - input_refs = list(map(lambda x: (int(x[0]), int(x[1])), input_refs)) - output_clocks = list(map(lambda x: (int(x[0]), int(x[1])), output_clocks)) - - clk.set_requested_clocks(input_refs, output_clocks) - - clk.solve() - sol = clk.get_config() - - PLLs_used = [False, False] - for out_clock in output_clocks: - if out_clock[0] > 5: - PLLs_used[1] = True - else: - PLLs_used[0] = True - - for in_ref in input_refs: - for pll_number in range(0, 2): - if PLLs_used[pll_number]: - pll_rate = sol["PLL" + str(pll_number)]["rate_hz"] - n_name = "n" + str(pll_number) + "_profile_" + str(in_ref[0]) - assert ( - pll_rate - == (in_ref[1] // sol["r" + str(in_ref[0])]) - * sol["PLL" + str(pll_number)][n_name] - ) - - for out_clock in output_clocks: - if out_clock[0] > 5: - pll_rate = sol["PLL1"]["rate_hz"] - else: - pll_rate = sol["PLL0"]["rate_hz"] - - assert out_clock[1] == pll_rate / sol["q" + str(out_clock[0])] - - -def test_ad9545_fail_no_solver(): - with pytest.raises(Exception, match=r"Unknown solver NAN"): - clk = adijif.ad9545(solver="NAN") - - input_refs = [(0, 1), (1, 10e6)] - output_clocks = [(0, 30720000)] - - input_refs = list(map(lambda x: (int(x[0]), int(x[1])), input_refs)) - output_clocks = list(map(lambda x: (int(x[0]), int(x[1])), output_clocks)) - - clk.set_requested_clocks(input_refs, output_clocks) - - clk.solve() - - -@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) -def test_ad9523_1_daq2_validate(solver): - vcxo = 125000000 - n2 = 24 - - clk = adijif.ad9523_1(solver=solver) - - # Check config valid - clk.n2 = n2 - clk.use_vcxo_double = False - - output_clocks = [1e9, 500e6, 7.8125e6] - clock_names = ["ADC", "FPGA", "SYSREF"] - - clk.set_requested_clocks(vcxo, output_clocks, clock_names) - - clk.solve() - - o = clk.get_config() - - assert sorted(o["out_dividers"]) == [1, 2, 128] - assert o["m1"] == 3 - assert o["m1"] in clk.m1_available - assert o["n2"] == n2 - assert o["n2"] in clk.n2_available - assert o["r2"] == 1 - assert o["r2"] in clk.r2_available - - assert o["output_clocks"]["ADC"] == {"divider": 1, "rate": 1000000000.0} - assert o["output_clocks"]["FPGA"] == {"divider": 2, "rate": 500000000.0} - assert o["output_clocks"]["SYSREF"] == {"divider": 128, "rate": 7812500.0} - - -@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) -def test_ad9523_1_daq2_validate_fail(solver): - msg = r"Solution Not Found" - - with pytest.raises(Exception, match=msg): - vcxo = 125000000 - n2 = 12 - - clk = adijif.ad9523_1(solver=solver) - - # Check config valid - clk.n2 = n2 - # clk.r2 = 1 - clk.use_vcxo_double = False - # clk.m = 3 - - output_clocks = [1e9, 500e6, 7.8125e6] - clock_names = ["ADC", "FPGA", "SYSREF"] - - clk.set_requested_clocks(vcxo, output_clocks, clock_names) - - o = clk.solve() - - print(o) - - assert sorted(o["out_dividers"]) == [ - 2, - 4, - 256, - ] # This seems weird but its different per CPLEX version - assert o["n2"] == n2 - - -@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) -def test_ad9523_1_daq2_variable_vcxo_validate(solver): - vcxo = adijif.types.range(100000000, 126000000, 1000000, "vcxo") - n2 = 24 - - clk = adijif.ad9523_1(solver=solver) - - # Check config valid - clk.n2 = n2 - clk.use_vcxo_double = False - - output_clocks = [1e9, 500e6, 7.8125e6] - clock_names = ["ADC", "FPGA", "SYSREF"] - - clk.set_requested_clocks(vcxo, output_clocks, clock_names) - - clk.solve() - - o = clk.get_config() - - print(o) - - assert sorted(o["out_dividers"]) == [1, 2, 128] - assert o["m1"] == 3 - assert o["m1"] in clk.m1_available - assert o["n2"] == n2 - assert o["n2"] in clk.n2_available - assert o["r2"] == 1 - assert o["r2"] in clk.r2_available - assert o["output_clocks"]["ADC"]["rate"] == 1e9 - assert o["vcxo"] == 125000000 - - -def test_ad9523_1_fail_no_solver(): - with pytest.raises(Exception, match=r"Unknown solver NAN"): - clk = adijif.ad9523_1(solver="NAN") - output_clocks = [1e9, 500e6, 7.8125e6] - clock_names = ["ADC", "FPGA", "SYSREF"] - clk.set_requested_clocks(vcxo, output_clocks, clock_names) - clk.solve() - - -def test_ad9523_1_fail_no_solver2(): - with pytest.raises(Exception, match=r"Unknown solver NAN2"): - vcxo = 125000000 - clk = adijif.ad9523_1() - clk.solver = "NAN2" - output_clocks = [1e9, 500e6, 7.8125e6] - clock_names = ["ADC", "FPGA", "SYSREF"] - clk.set_requested_clocks(vcxo, output_clocks, clock_names) - clk.solve() - - -def test_ad9523_1_fail_no_solver3(): - with pytest.raises(Exception, match=r"Unknown solver NAN3"): - vcxo = 125000000 - clk = adijif.ad9523_1() - output_clocks = [1e9, 500e6, 7.8125e6] - clock_names = ["ADC", "FPGA", "SYSREF"] - clk.set_requested_clocks(vcxo, output_clocks, clock_names) - clk.solver = "NAN3" - clk.solve() - - -def test_system_fail_no_solver3(): - with pytest.raises(Exception, match=r"Unknown solver NAN4"): - vcxo = 125000000 - sys = adijif.system("ad9680", "hmc7044", "xilinx", vcxo, solver="NAN4") - sys.solve() - - -def test_ltc6953_validate(): - ref_in = adijif.types.range(1000000000, 4500000000, 1000000, "ref_in") - - clk = adijif.ltc6953(solver="CPLEX") - - output_clocks = [1e9, 500e6, 7.8125e6] - print(output_clocks) - output_clocks = list(map(int, output_clocks)) # force to be ints - clock_names = ["ADC", "FPGA", "SYSREF"] - - clk.set_requested_clocks(ref_in, output_clocks, clock_names) - - clk.solve() - - o = clk.get_config() - - assert sorted(o["out_dividers"]) == [2, 4, 256] - assert o["input_ref"] == 2000000000 - - -@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) -def test_ad9528_validate(solver): - n2 = 10 - vcxo = 122.88e6 - - clk = adijif.ad9528(solver=solver) - - clk.n2 = n2 - clk.use_vcxo_double = False - - output_clocks = [245.76e6, 245.76e6] - output_clocks = list(map(int, output_clocks)) - clock_names = ["ADC", "FPGA"] - - clk.set_requested_clocks(vcxo, output_clocks, clock_names) - - clk.solve() - o = clk.get_config() - - assert sorted(o["out_dividers"]) == [5, 5] - assert o["m1"] == 3 - assert o["m1"] in clk.m1_available - assert o["n2"] == n2 - assert o["n2"] in clk.n2_available - assert o["output_clocks"]["ADC"]["rate"] == 245.76e6 - assert o["output_clocks"]["FPGA"]["rate"] == 245.76e6 - assert o["vcxo"] == vcxo - assert o["vco"] == 3686400000.0 - - -@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) -def test_ad9528_sysref(solver): - n2 = 10 - vcxo = 122.88e6 - - clk = adijif.ad9528(solver=solver) - - clk.n2 = n2 - clk.k = [*range(500, 600)] # FIXME gekko fails to find a solution without this. - clk.use_vcxo_double = False - - clk.sysref = 120e3 - - output_clocks = [245.76e6, 245.76e6] - output_clocks = list(map(int, output_clocks)) - clock_names = ["ADC", "FPGA"] - - clk.set_requested_clocks(vcxo, output_clocks, clock_names) - - clk.solve() - o = clk.get_config() - - assert o["k"] == 512 - assert o["sysref"] == 120e3 +# flake8: noqa +import pprint + +import pytest + +try: + from gekko import GEKKO # type: ignore +except ImportError: + GEKKO = None + +import adijif + + +def skip_solver(solver): + if solver.lower() == "gekko" and GEKKO is None: + pytest.skip("Gekko not available") + + +@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) +def test_ad9545_validate_fail(solver): + msg = r"Solution Not Found" + + skip_solver(solver) + + with pytest.raises(Exception, match=msg): + clk = adijif.ad9545(solver=solver) + + clk.avoid_min_max_PLL_rates = True + clk.minimize_input_dividers = True + + input_refs = [(0, 42345235)] + output_clocks = [(0, 3525235234123)] + + input_refs = list(map(lambda x: (int(x[0]), int(x[1])), input_refs)) + output_clocks = list(map(lambda x: (int(x[0]), int(x[1])), output_clocks)) + + clk.set_requested_clocks(input_refs, output_clocks) + + clk.solve() + + +@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) +@pytest.mark.parametrize("out_freq", [30720000, 25e6]) +def test_ad9545_validate_pass(solver, out_freq): + skip_solver(solver) + clk = adijif.ad9545(solver=solver) + + clk.avoid_min_max_PLL_rates = True + clk.minimize_input_dividers = True + + input_refs = [(0, 1), (1, 10e6)] + output_clocks = [(0, int(out_freq))] + + input_refs = list(map(lambda x: (int(x[0]), int(x[1])), input_refs)) + output_clocks = list(map(lambda x: (int(x[0]), int(x[1])), output_clocks)) + + clk.set_requested_clocks(input_refs, output_clocks) + + clk.solve() + sol = clk.get_config() + + PLLs_used = [False, False] + for out_clock in output_clocks: + if out_clock[0] > 5: + PLLs_used[1] = True + else: + PLLs_used[0] = True + + for in_ref in input_refs: + for pll_number in range(0, 2): + if PLLs_used[pll_number]: + pll_rate = sol["PLL" + str(pll_number)]["rate_hz"] + n_name = "n" + str(pll_number) + "_profile_" + str(in_ref[0]) + assert ( + pll_rate + == (in_ref[1] // sol["r" + str(in_ref[0])]) + * sol["PLL" + str(pll_number)][n_name] + ) + + for out_clock in output_clocks: + if out_clock[0] > 5: + pll_rate = sol["PLL1"]["rate_hz"] + else: + pll_rate = sol["PLL0"]["rate_hz"] + + assert out_clock[1] == pll_rate / sol["q" + str(out_clock[0])] + + +def test_ad9545_fail_no_solver(): + with pytest.raises(Exception, match=r"Unknown solver NAN"): + clk = adijif.ad9545(solver="NAN") + + input_refs = [(0, 1), (1, 10e6)] + output_clocks = [(0, 30720000)] + + input_refs = list(map(lambda x: (int(x[0]), int(x[1])), input_refs)) + output_clocks = list(map(lambda x: (int(x[0]), int(x[1])), output_clocks)) + + clk.set_requested_clocks(input_refs, output_clocks) + + clk.solve() + + +@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) +def test_ad9523_1_daq2_validate(solver): + skip_solver(solver) + vcxo = 125000000 + n2 = 24 + + clk = adijif.ad9523_1(solver=solver) + + # Check config valid + clk.n2 = n2 + clk.use_vcxo_double = False + + output_clocks = [1e9, 500e6, 7.8125e6] + clock_names = ["ADC", "FPGA", "SYSREF"] + + clk.set_requested_clocks(vcxo, output_clocks, clock_names) + + clk.solve() + + o = clk.get_config() + + assert sorted(o["out_dividers"]) == [1, 2, 128] + assert o["m1"] == 3 + assert o["m1"] in clk.m1_available + assert o["n2"] == n2 + assert o["n2"] in clk.n2_available + assert o["r2"] == 1 + assert o["r2"] in clk.r2_available + + assert o["output_clocks"]["ADC"] == {"divider": 1, "rate": 1000000000.0} + assert o["output_clocks"]["FPGA"] == {"divider": 2, "rate": 500000000.0} + assert o["output_clocks"]["SYSREF"] == {"divider": 128, "rate": 7812500.0} + + +@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) +def test_ad9523_1_daq2_validate_fail(solver): + skip_solver(solver) + msg = r"Solution Not Found" + + with pytest.raises(Exception, match=msg): + vcxo = 125000000 + n2 = 12 + + clk = adijif.ad9523_1(solver=solver) + + # Check config valid + clk.n2 = n2 + # clk.r2 = 1 + clk.use_vcxo_double = False + # clk.m = 3 + + output_clocks = [1e9, 500e6, 7.8125e6] + clock_names = ["ADC", "FPGA", "SYSREF"] + + clk.set_requested_clocks(vcxo, output_clocks, clock_names) + + o = clk.solve() + + print(o) + + assert sorted(o["out_dividers"]) == [ + 2, + 4, + 256, + ] # This seems weird but its different per CPLEX version + assert o["n2"] == n2 + + +@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) +def test_ad9523_1_daq2_variable_vcxo_validate(solver): + skip_solver(solver) + vcxo = adijif.types.range(100000000, 126000000, 1000000, "vcxo") + n2 = 24 + + clk = adijif.ad9523_1(solver=solver) + + # Check config valid + clk.n2 = n2 + clk.use_vcxo_double = False + + output_clocks = [1e9, 500e6, 7.8125e6] + clock_names = ["ADC", "FPGA", "SYSREF"] + + clk.set_requested_clocks(vcxo, output_clocks, clock_names) + + clk.solve() + + o = clk.get_config() + + print(o) + + assert sorted(o["out_dividers"]) == [1, 2, 128] + assert o["m1"] == 3 + assert o["m1"] in clk.m1_available + assert o["n2"] == n2 + assert o["n2"] in clk.n2_available + assert o["r2"] == 1 + assert o["r2"] in clk.r2_available + assert o["output_clocks"]["ADC"]["rate"] == 1e9 + assert o["vcxo"] == 125000000 + + +def test_ad9523_1_fail_no_solver(): + with pytest.raises(Exception, match=r"Unknown solver NAN"): + clk = adijif.ad9523_1(solver="NAN") + output_clocks = [1e9, 500e6, 7.8125e6] + clock_names = ["ADC", "FPGA", "SYSREF"] + clk.set_requested_clocks(vcxo, output_clocks, clock_names) + clk.solve() + + +def test_ad9523_1_fail_no_solver2(): + with pytest.raises(Exception, match=r"Unknown solver NAN2"): + vcxo = 125000000 + clk = adijif.ad9523_1() + clk.solver = "NAN2" + output_clocks = [1e9, 500e6, 7.8125e6] + clock_names = ["ADC", "FPGA", "SYSREF"] + clk.set_requested_clocks(vcxo, output_clocks, clock_names) + clk.solve() + + +def test_ad9523_1_fail_no_solver3(): + with pytest.raises(Exception, match=r"Unknown solver NAN3"): + vcxo = 125000000 + clk = adijif.ad9523_1() + output_clocks = [1e9, 500e6, 7.8125e6] + clock_names = ["ADC", "FPGA", "SYSREF"] + clk.set_requested_clocks(vcxo, output_clocks, clock_names) + clk.solver = "NAN3" + clk.solve() + + +def test_system_fail_no_solver3(): + with pytest.raises(Exception, match=r"Unknown solver NAN4"): + vcxo = 125000000 + sys = adijif.system("ad9680", "hmc7044", "xilinx", vcxo, solver="NAN4") + sys.solve() + + +def test_ltc6953_validate(): + ref_in = adijif.types.range(1000000000, 4500000000, 1000000, "ref_in") + + clk = adijif.ltc6953(solver="CPLEX") + + output_clocks = [1e9, 500e6, 7.8125e6] + print(output_clocks) + output_clocks = list(map(int, output_clocks)) # force to be ints + clock_names = ["ADC", "FPGA", "SYSREF"] + + clk.set_requested_clocks(ref_in, output_clocks, clock_names) + + clk.solve() + + o = clk.get_config() + + assert sorted(o["out_dividers"]) == [2, 4, 256] + assert o["input_ref"] == 2000000000 + + +@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) +def test_ad9528_validate(solver): + skip_solver(solver) + n2 = 10 + vcxo = 122.88e6 + + clk = adijif.ad9528(solver=solver) + + clk.n2 = n2 + clk.use_vcxo_double = False + + output_clocks = [245.76e6, 245.76e6] + output_clocks = list(map(int, output_clocks)) + clock_names = ["ADC", "FPGA"] + + clk.set_requested_clocks(vcxo, output_clocks, clock_names) + + clk.solve() + o = clk.get_config() + + assert sorted(o["out_dividers"]) == [5, 5] + assert o["m1"] == 3 + assert o["m1"] in clk.m1_available + assert o["n2"] == n2 + assert o["n2"] in clk.n2_available + assert o["output_clocks"]["ADC"]["rate"] == 245.76e6 + assert o["output_clocks"]["FPGA"]["rate"] == 245.76e6 + assert o["vcxo"] == vcxo + assert o["vco"] == 3686400000.0 + + +@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) +def test_ad9528_sysref(solver): + skip_solver(solver) + n2 = 10 + vcxo = 122.88e6 + + clk = adijif.ad9528(solver=solver) + + clk.n2 = n2 + clk.k = [*range(500, 600)] # FIXME gekko fails to find a solution without this. + clk.use_vcxo_double = False + + clk.sysref = 120e3 + + output_clocks = [245.76e6, 245.76e6] + output_clocks = list(map(int, output_clocks)) + clock_names = ["ADC", "FPGA"] + + clk.set_requested_clocks(vcxo, output_clocks, clock_names) + + clk.solve() + o = clk.get_config() + + assert o["k"] == 512 + assert o["sysref"] == 120e3 From eed4bd88d6fceef31a93f63d1a9de8ba269552c9 Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Wed, 27 Nov 2024 13:12:38 -0700 Subject: [PATCH 03/17] Add transceiver models for FPGAs Signed-off-by: Travis F. Collins --- adijif/fpgas/__init__.py | 1 - adijif/fpgas/pll_constraints.py | 440 ----------- .../fpgas/{xilinx.py => xilinx/__init__.py} | 708 ++---------------- adijif/fpgas/{xilinx_bf.py => xilinx/bf.py} | 0 adijif/fpgas/xilinx/pll.py | 139 +++- adijif/fpgas/xilinx/sevenseries.py | 408 +++++++++- adijif/fpgas/xilinx/ultrascaleplus.py | 398 ++++++++++ .../fpgas/{ => xilinx}/xilinx_fpga_table.yml | 0 adijif/plls/adf4371.py | 4 +- adijif/solvers.py | 9 + adijif/utils.py | 11 +- examples/ad9081_rx_calc.py | 5 +- tests/test_bf.py | 1 + tests/test_daq2.py | 4 +- tests/test_fpga.py | 20 - tests/test_xilinx_pll.py | 91 +++ 16 files changed, 1127 insertions(+), 1112 deletions(-) delete mode 100644 adijif/fpgas/__init__.py delete mode 100644 adijif/fpgas/pll_constraints.py rename adijif/fpgas/{xilinx.py => xilinx/__init__.py} (53%) rename adijif/fpgas/{xilinx_bf.py => xilinx/bf.py} (100%) create mode 100644 adijif/fpgas/xilinx/ultrascaleplus.py rename adijif/fpgas/{ => xilinx}/xilinx_fpga_table.yml (100%) create mode 100644 tests/test_xilinx_pll.py diff --git a/adijif/fpgas/__init__.py b/adijif/fpgas/__init__.py deleted file mode 100644 index 73aac15..0000000 --- a/adijif/fpgas/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""ADI JIF FPGA models.""" diff --git a/adijif/fpgas/pll_constraints.py b/adijif/fpgas/pll_constraints.py deleted file mode 100644 index 546edba..0000000 --- a/adijif/fpgas/pll_constraints.py +++ /dev/null @@ -1,440 +0,0 @@ -from docplex.cp.modeler import if_then - - -class xilinx_pll_constraints: - # UltraScale+ GTY PLLs - # https://docs.amd.com/v/u/en-US/ug578-ultrascale-gty-transceivers - # GTHs - # https://docs.amd.com/v/u/en-US/ug576-ultrascale-gth-transceivers - # GTXs - # https://docs.amd.com/v/u/en-US/ug476_7Series_Transceivers - - transceivers = { - "UltrascalePlus": ["GTYE4", "GTHE4"], - "Ultrascale": ["GTYE3", "GTHE3"], - "7kSeries": ["GTXE2", "GTHE2"], - } - - def trx_gen(self) -> int: - """Get transceiver generation (2,3,4) - - Returns: - int: generation of transceiver - """ - return int(self.transceiver_type[-1]) - - def trx_variant(self): - """Get transceiver variant (GTX, GTH, GTY, ...) - - Returns: - str: Transceiver variant - """ - # return self.transceiver_type[:2] - trxt = self.transceiver_type[:2] - print(trxt) - assert len(trxt) == 3 - return trxt - - def add_cpll_contraints(self, config: dict, fpga_ref, converter) -> dict: - """Add Channel PLL (CPLL) constraints. - - This applies to GTH and GTY transceivers. - - Args: - config (dict): Configuration dictionary. - fpga_ref (int): FPGA reference clock. - converter (converter): Converter object. - """ - config[converter.name + "_use_cpll"] = self._convert_input( - [0, 1], converter.name + "_use_cpll" - ) - # v = self.model.integer_var(min=0, max=1, name=converter.name + "_use_cpll2") - # self.model.export_model() - # input() - # return config - - # Add variables - config[converter.name + "_m_cpll"] = self._convert_input( - [1, 2], converter.name + "_m_cpll" - ) - # This is documented oddly - # There are footnotes that include 16 with GTHs, and 16,32 with GTYs - # but its says "not supported for CPLL"?!?!? - config[converter.name + "_d_cpll"] = self._convert_input( - [1, 2, 4, 8], converter.name + "_d_cpll" - ) - config[converter.name + "_n1_cpll"] = self._convert_input( - [4, 5], converter.name + "_n1_cpll" - ) - config[converter.name + "_n2_cpll"] = self._convert_input( - [1, 2, 3, 4, 5], converter.name + "_n2_cpll" - ) - - # Add intermediate variables - config[converter.name + "_pll_out_cpll"] = self._add_intermediate( - fpga_ref - * config[converter.name + "_n1_cpll"] - * config[converter.name + "_n2_cpll"] - / (config[converter.name + "_m_cpll"]) - ) - - # Add constraints - self._add_equation( - [ - if_then( - config[converter.name + "_use_cpll"] == 1, - config[converter.name + "_d_cpll"] * converter.bit_clock - == config[converter.name + "_pll_out_cpll"] * 2, - ), - if_then( - config[converter.name + "_use_cpll"] == 1, - config[converter.name + "_pll_out_cpll"] >= self.vco_min, - ), - if_then( - config[converter.name + "_use_cpll"] == 1, - config[converter.name + "_pll_out_cpll"] <= self.vco_max, - ), - ] - ) - - return config - - # QPLLs - def add_qpll_contraints(self, config: dict, fpga_ref, converter) -> dict: - if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: - return self._add_qpll_contraints_7_series(config, fpga_ref, converter) - - return self._add_qpllN_contraints(config, fpga_ref, converter, 0) - - def add_qpll1_contraints(self, config: dict, fpga_ref, converter): - if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: - config[converter.name + "_use_qpll1"] = 0 - return config - - return self._add_qpllN_contraints(config, fpga_ref, converter, 1) - - def _add_qpllN_contraints(self, config: dict, fpga_ref, converter, n: int) -> dict: - """Add constraints for QPLL{n}. - - Args: - config (dict): Configuration dictionary. - fpga_ref (int): FPGA reference clock. - converter (converter): Converter object. - n (int): QPLL number 0 or 1. - """ - - assert ( - False - ), "QPLL equations are seem to be wrong based on GT Series and are more based on 7 series vs US vs US+" - # See equation 2-4 and 2-5 in https://docs.amd.com/v/u/en-US/ug576-ultrascale-gth-transceivers (differences is US+ and doesn't seem to be just GTH based) - - if n == 0: - pname = "qpll" - elif n == 1: - pname = "qpll1" - else: - raise Exception("Unsupported QPLL type") - - if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: - config[converter.name + f"_use_{pname}"] = 0 - return config - - # Global flag to use QPLLn - config[converter.name + f"_use_{pname}"] = self._convert_input( - [0, 1], converter.name + f"_use_{pname}" - ) - - # Add variables - config[converter.name + f"_m_{pname}"] = self._convert_input( - [1, 2, 3, 4], converter.name + f"_m_{pname}" - ) - config[converter.name + f"_d_{pname}"] = self._convert_input( - [1, 2, 4, 8, 16, 32], converter.name + f"_d_{pname}" - ) - config[converter.name + f"_n_{pname}"] = self._convert_input( - [*range(16, 160)], converter.name + f"_n_{pname}" - ) - - assert False, "Confirm this is GTH specific" - if self.transceiver_type[:3] == "GTH": - clkout = 2 - else: - clkout = [1, 2] - - config[converter.name + f"_clkout_rate_{pname}"] = self._convert_input( - clkout, converter.name + f"_clkout_rate_{pname}" - ) - config[converter.name + f"_sdm_data_{pname}"] = self.model.integer_var( - min=0, max=(2**24 - 1), name=converter.name + f"_sdm_data_{pname}" - ) - config[converter.name + f"_sdm_width_{pname}"] = self._convert_input( - [16, 20, 24], converter.name + f"_sdm_width_{pname}" - ) - config[converter.name + f"_HIGH_RATE_{pname}"] = self._convert_input( - [0, 1], converter.name + f"_HIGH_RATE_{pname}" - ) - - # Add intermediate variables - config[converter.name + f"_frac_{pname}"] = self._add_intermediate( - config[converter.name + f"_sdm_data_{pname}"] - / (2 ** config[converter.name + f"_sdm_width_{pname}"]) - ) - config[converter.name + f"_n_dot_frac_{pname}"] = self._add_intermediate( - config[converter.name + f"_n_{pname}"] - + config[converter.name + f"_frac_{pname}"] - ) - config[converter.name + f"_pll_out_{pname}"] = self._add_intermediate( - fpga_ref - * config[converter.name + f"_n_dot_frac_{pname}"] - / ( - config[converter.name + f"_m_{pname}"] - * config[converter.name + f"_clkout_rate_{pname}"] - ) - ) - config[converter.name + f"_vco_{pname}"] = self._add_intermediate( - fpga_ref - * config[converter.name + f"_n_dot_frac_{pname}"] - / ( - config[converter.name + f"_m_{pname}"] - * config[converter.name + f"_m_{pname}"] - ) - ) - - # Add constraints - - self._add_equation( - [ - if_then( - config[converter.name + f"_use_{pname}"] == 1, - converter.bit_clock - == config[converter.name + f"_pll_out_{pname}"] - * 2 - / config[converter.name + f"_d_{pname}"], - ), - if_then( - config[converter.name + f"_use_{pname}"] == 1, - config[converter.name + f"_HIGH_RATE_{pname}"] - == (converter.bit_clock >= 28.1e9), - ), - ] - ) - - vco_min = 9.8e9 if n == 0 else 8e9 - vco_max = 16.375e9 if n == 0 else 13e9 - - self._add_equation( - [ - if_then( - config[converter.name + f"_use_{pname}"] == 1, - config[converter.name + f"_vco_{pname}"] >= vco_min, - ), - if_then( - config[converter.name + f"_use_{pname}"] == 1, - config[converter.name + f"_vco_{pname}"] <= vco_max, - ), - if_then( - config[converter.name + f"_HIGH_RATE_{pname}"] == 1, - config[converter.name + f"_frac_{pname}"] == 0, - ), - ] - ) - - return config - - def _add_qpll_contraints_7_series(self, config: dict, fpga_ref, converter) -> dict: - """Add constraints for QPLL for 7 series FPGAs. - - Args: - config (dict): Configuration dictionary. - fpga_ref (int): FPGA reference clock. - converter (converter): Converter object. - """ - pname = "qpll" - - # if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: - # config[converter.name + f"_use_{pname}"] = 0 - # return config - - # Double check this constraint - if self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: - raise Exception("Invalid GT is for 7 series FPGAs") - - # Global flag to use QPLLn - config[converter.name + f"_use_{pname}"] = self._convert_input( - [0, 1], converter.name + f"_use_{pname}" - ) - - # Add variables - config[converter.name + f"_m_{pname}"] = self._convert_input( - [1, 2, 3, 4], converter.name + f"_m_{pname}" - ) - config[converter.name + f"_d_{pname}"] = self._convert_input( - [1, 2, 4, 8, 16], converter.name + f"_d_{pname}" - ) - config[converter.name + f"_n_{pname}"] = self._convert_input( - [16, 20, 32, 40, 64, 66, 80, 100], converter.name + f"_n_{pname}" - ) - - # Add intermediate variables - config[converter.name + f"_vco_{pname}"] = self._add_intermediate( - fpga_ref - * config[converter.name + f"_n_{pname}"] - / config[converter.name + f"_m_{pname}"] - ) - config[converter.name + f"_pll_out_{pname}"] = self._add_intermediate( - fpga_ref - * config[converter.name + f"_n_{pname}"] - / (config[converter.name + f"_m_{pname}"] * 2) - ) - - # Add constraints - self._add_equation( - [ - if_then( - config[converter.name + f"_use_{pname}"] == 1, - converter.bit_clock - == config[converter.name + f"_pll_out_{pname}"] - * 2 - / config[converter.name + f"_d_{pname}"], - ), - ] - ) - - assert False, "Confirm this is GTH/GTX specific" - if self.transceiver_type[:3] == "GTH": - vco_min = 8e9 - vco_max = 13.1e9 - self._add_equation( - [ - if_then( - config[converter.name + f"_use_{pname}"] == 1, - config[converter.name + f"_vco_{pname}"] >= vco_min, - ), - if_then( - config[converter.name + f"_use_{pname}"] == 1, - config[converter.name + f"_vco_{pname}"] <= vco_max, - ), - ] - ) - elif self.transceiver_type[:3] == "GTX": - config[converter.name + f"_lower_band_{pname}"] = self._convert_input( - [0, 1], converter.name + f"_lower_band_{pname}" - ) - # Lower band - c1 = if_then( - config[converter.name + f"_use_{pname}"] == 1, - if_then( - config[converter.name + f"_lower_band_{pname}"] == 0, - config[converter.name + f"_vco_{pname}"] >= 5.93e9, - ), - ) - c2 = if_then( - config[converter.name + f"_use_{pname}"] == 1, - if_then( - config[converter.name + f"_lower_band_{pname}"] == 0, - config[converter.name + f"_vco_{pname}"] <= 8e9, - ), - ) - # Upper band - c3 = if_then( - config[converter.name + f"_use_{pname}"] == 1, - if_then( - config[converter.name + f"_lower_band_{pname}"] == 1, - config[converter.name + f"_vco_{pname}"] >= 9.8e9, - ), - ) - c4 = if_then( - config[converter.name + f"_use_{pname}"] == 1, - if_then( - config[converter.name + f"_lower_band_{pname}"] == 1, - config[converter.name + f"_vco_{pname}"] <= 12.5e9, - ), - ) - self._add_equation([c1, c2, c3, c4]) - - else: - raise Exception("Unsupported transceiver type") - - return config - - def get_cpll_config(self, config: dict, converter, fpga_ref) -> dict: - """Get CPLL configuration. - - Args: - config (dict): Configuration dictionary. - converter (converter): Converter object. - """ - pll_config = {} - pll_config["type"] = "cpll" - for k in ["m", "d", "n1", "n2"]: - pll_config[k] = self._get_val(config[converter.name + "_" + k + "_cpll"]) - - pll_config["vco"] = ( - fpga_ref * pll_config["n1"] * pll_config["n2"] / pll_config["m"] # type: ignore # noqa: B950 - ) - # Check - assert ( - pll_config["vco"] * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 - ), "Invalid CPLL lane rate" - - return pll_config - - def get_qpll_config(self, config: dict, converter, fpga_ref, n: int) -> dict: - """Get QPLL configuration. - - Args: - config (dict): Configuration dictionary. - converter (converter): Converter object. - n (int): QPLL number 0 or 1. - """ - if n == 0: - pname = "qpll" - elif n == 1: - pname = "qpll1" - else: - raise Exception("Unsupported QPLL type") - - if self.transceiver_type not in ["GTH3", "GTH4", "GTY4"]: - return self.get_qpll_config_7_series(config, converter, fpga_ref) - - pll_config = {} - pll_config["type"] = pname - for k in ["m", "d", "n", "clkout_rate", "sdm_data", "sdm_width", "HIGH_RATE"]: - pll_config[k] = self._get_val( - config[converter.name + "_" + k + "_" + pname] - ) - - pll_config["frac"] = pll_config["sdm_data"] / (2 ** pll_config["sdm_width"]) - pll_config["n_dot_frac"] = pll_config["n"] + pll_config["frac"] - # FIXME: Check clkout_rate between GTH and GTY - pll_config["vco"] = fpga_ref * pll_config["n_dot_frac"] / (pll_config["m"]) - # Check - assert ( - pll_config["vco"] * 2 / (pll_config["d"] * pll_config["clkout_rate"]) == converter.bit_clock # type: ignore # noqa: B950 - ), f"Invalid {pname} lane rate. {pll_config['vco'] * 2 / pll_config['d']} != {converter.bit_clock}" # type: ignore # noqa: B950 - - return pll_config - - def get_qpll_config_7_series(self, config: dict, converter, fpga_ref) -> dict: - """Get QPLL configuration for 7 series FPGAs. - - Args: - config (dict): Configuration dictionary. - converter (converter): Converter object. - """ - pname = "qpll" - - pll_config = {"type": pname} - for k in ["m", "d", "n"]: - pll_config[k] = self._get_val( - config[converter.name + "_" + k + "_" + pname] - ) - - pll_config["vco"] = fpga_ref * pll_config["n"] / pll_config["m"] - pll_clk_out = fpga_ref * pll_config["n"] / (pll_config["m"] * 2) - # Check - assert ( - pll_clk_out * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 - ), f"Invalid QPLL lane rate {pll_config['vco'] * 2 / pll_config['d']} != {converter.bit_clock}" - - return pll_config diff --git a/adijif/fpgas/xilinx.py b/adijif/fpgas/xilinx/__init__.py similarity index 53% rename from adijif/fpgas/xilinx.py rename to adijif/fpgas/xilinx/__init__.py index d61b4b1..1857bcd 100644 --- a/adijif/fpgas/xilinx.py +++ b/adijif/fpgas/xilinx/__init__.py @@ -2,14 +2,15 @@ from typing import Dict, List, Optional, Union from adijif.converters.converter import converter as conv -from adijif.fpgas.pll_constraints import xilinx_pll_constraints -from adijif.fpgas.xilinx_bf import xilinx_bf +from .sevenseries import SevenSeries as SSTransceiver +from .ultrascaleplus import UltraScalePlus as USPTransceiver +from .bf import xilinx_bf from adijif.solvers import CpoSolveResult # type: ignore from adijif.solvers import integer_var # type: ignore from adijif.solvers import CpoIntVar, GK_Intermediate, GK_Operators, GKVariable -class xilinx(xilinx_bf, xilinx_pll_constraints): +class xilinx(xilinx_bf): """Xilinx FPGA clocking model. This model captures different limitations of the Xilinx @@ -69,8 +70,41 @@ class xilinx(xilinx_bf, xilinx_pll_constraints): available_fpga_families = ["Unknown", "Artix", "Kintex", "Virtex", "Zynq"] fpga_family = "Zynq" - available_transceiver_types = ["GTX2"] - transceiver_type = "GTX2" + available_transceiver_types = ["GTXE2"] + transceiver_type = "GTXE2" + + def trx_gen(self) -> int: + """Get transceiver generation (2,3,4) + + Returns: + int: generation of transceiver + """ + return int(self.transceiver_type[-1]) + + def trx_variant(self): + """Get transceiver variant (GTX, GTH, GTY, ...) + + Returns: + str: Transceiver variant + """ + # return self.transceiver_type[:2] + trxt = self.transceiver_type[:2] + print(trxt) + assert len(trxt) == 3 + return trxt + + def fpga_generation(self): + """Get FPGA generation 7000, Ultrascale, Ultrascale+... based on transceiver type""" + if self.trx_gen() == 2: + return '7000' + elif self.trx_gen() == 3: + return 'Ultrascale' + elif self.trx_gen() == 4: + return 'Ultrascale+' + elif self.trx_gen() == 5: + return 'Versal' + raise Exception(f"Unknown transceiver generation {self.trx_gen()}") + sys_clk_selections = [ "XCVR_CPLL", @@ -97,7 +131,7 @@ class xilinx(xilinx_bf, xilinx_pll_constraints): """ Force use of QPLL for transceiver source """ force_qpll = 0 - """ Force use of QPLL1 for transceiver source (GTH3,GTH4,GTY4)""" + """ Force use of QPLL1 for transceiver source (GTHE3,GTHE4,GTYE4)""" force_qpll1 = 0 """ Force use of CPLL for transceiver source """ @@ -129,6 +163,7 @@ class xilinx(xilinx_bf, xilinx_pll_constraints): requires_core_clock_from_device_clock = False configs = [] # type: ignore + _transceiver_models = {} # type: ignore @property def ref_clock_constraint(self) -> str: @@ -232,7 +267,7 @@ def _ref_clock_max(self) -> int: Exception: Unsupported transceiver type configured. """ # https://www.xilinx.com/support/documentation/data_sheets/ds191-XC7Z030-XC7Z045-data-sheet.pdf # noqa: B950 - if self.transceiver_type == "GTX2": + if self.transceiver_type == "GTXE2": if self.speed_grade == "-3E": return 700000000 else: @@ -254,7 +289,7 @@ def _ref_clock_min(self) -> int: Exception: Unsupported transceiver type configured. """ # https://www.xilinx.com/support/documentation/data_sheets/ds191-XC7Z030-XC7Z045-data-sheet.pdf # noqa: B950 - if self.transceiver_type == "GTX2": + if self.transceiver_type == "GTXE2": return 60000000 else: raise Exception( @@ -262,178 +297,7 @@ def _ref_clock_min(self) -> int: ) # raise Exception(f"Unknown transceiver type {self.transceiver_type}") - # CPLL - @property - def vco_min(self) -> int: - """Get minimum CPLL VCO rate for config. - - Returns: - int: Rate in samples per second. - - Raises: - Exception: Unsupported transceiver type configured. - """ - if self.transceiver_type == "GTX2": - return 1600000000 - elif self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: - return 2000000000 - else: - raise Exception( - f"Unknown vco_min for transceiver type {self.transceiver_type}" - ) - - @property - def vco_max(self) -> int: - """Get maximum CPLL VCO rate for config. - - Returns: - int: Rate in samples per second. - - Raises: - Exception: Unsupported transceiver type configured. - """ - if self.transceiver_type == "GTX2": - return 3300000000 - elif self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: - if self.hdl_core_version > 2: - if self.transceiver_type in ["GTH3", "GTH4"]: - if self.transceiver_voltage < 850 or self.speed_grade == -1: - return 4250000000 - elif self.transceiver_type == "GTY4" and self.speed_grade == -1: - return 4250000000 - return 6250000000 - else: - raise Exception( - f"Unknown vco_max for transceiver type {self.transceiver_type}" - ) - - # QPLL - @property - def vco0_min(self) -> int: - """Get minimum QPLL VCO0 rate for config. - - This is applicable for QPLLs only. - - Returns: - int: Rate in samples per second. - - Raises: - Exception: Unsupported transceiver type configured. - """ - if self.transceiver_type == "GTX2": - return 5930000000 - elif self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: - if self.sys_clk_select == "XCVR_QPLL1" and self.transceiver_type in [ - "GTH3", - "GTH4", - ]: - return 8000000000 - else: - return 9800000000 - else: - raise Exception( - f"Unknown vco0_min for transceiver type {self.transceiver_type}" - ) - - @property - def vco0_max(self) -> int: - """Get maximum QPLL VCO0 rate for config. - - This is applicable for QPLLs only. - - Returns: - int: Rate in samples per second. - - Raises: - Exception: Unsupported transceiver type configured. - """ - if self.transceiver_type == "GTX2": - if ( - self.hdl_core_version > 2 - and self.fpga_family == "Kintex" - and self.fpga_package in ["FB", "RF", "FF"] - ): - return 6600000000 - return 8000000000 - elif self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: - if self.sys_clk_select == "XCVR_QPLL1" and self.transceiver_type in [ - "GTH3", - "GTH4", - ]: - return 13000000000 - else: - return 16375000000 - else: - raise Exception( - f"Unknown vco0_max for transceiver type {self.transceiver_type}" - ) - - @property - def vco1_min(self) -> int: - """Get minimum QPLL VCO1 rate for config. - - This is applicable for QPLLs only. - - Returns: - int: Rate in samples per second. - - Raises: - Exception: Unsupported transceiver type configured. - """ - if self.transceiver_type == "GTX2": - return 9800000000 - elif self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: - return self.vco0_min - else: - raise Exception( - f"Unknown vco1_min for transceiver type {self.transceiver_type}" - ) - - @property - def vco1_max(self) -> int: - """Get maximum QPLL VCO1 rate for config. - - This is applicable for QPLLs only. - - Returns: - int: Rate in samples per second. - - Raises: - Exception: Unsupported transceiver type configured. - """ - if self.transceiver_type == "GTX2": - if self.hdl_core_version > 2 and self.speed_grade == -2: - return 10312500000 - return 12500000000 - elif self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: - return self.vco0_max - else: - raise Exception( - f"Unknown vco1_max for transceiver type {self.transceiver_type}" - ) - - @property - def N(self) -> List[int]: - """Get available feedback divider settings. - - This is applicable for QPLLs only. - - Returns: - list[int]: List of divider integers. - - Raises: - Exception: Unsupported transceiver type configured. - """ - if self.transceiver_type == "GTX2": - return [16, 20, 32, 40, 64, 66, 80, 100] - elif self.transceiver_type in ["GTH3", "GTH4", "GTY4"]: - return [16, 20, 32, 40, 64, 66, 75, 80, 100, 112, 120, 125, 150, 160] - else: - raise Exception( - "Unknown N (feedback dividers) for transceiver type" - " {}".format(self.transceiver_type) - ) - + def setup_by_dev_kit_name(self, name: str) -> None: """Configure object based on board name. Ex: zc706, zcu102. @@ -445,7 +309,7 @@ def setup_by_dev_kit_name(self, name: str) -> None: """ if name.lower() == "zc706": - self.transceiver_type = "GTX2" + self.transceiver_type = "GTXE2" self.fpga_family = "Zynq" self.fpga_package = "FF" self.speed_grade = -2 @@ -458,7 +322,7 @@ def setup_by_dev_kit_name(self, name: str) -> None: self._out_clk_selections = o self._out_clk_select = o elif name.lower() == "zcu102": - self.transceiver_type = "GTH4" + self.transceiver_type = "GTHE4" self.fpga_family = "Zynq" self.fpga_package = "FF" self.speed_grade = -2 @@ -467,7 +331,7 @@ def setup_by_dev_kit_name(self, name: str) -> None: self.max_serdes_lanes = 8 elif name.lower() == "vcu118": # XCVU9P-L2FLGA2104 - self.transceiver_type = "GTY4" + self.transceiver_type = "GTYE4" self.fpga_family = "Virtex" self.fpga_package = "FL" self.speed_grade = -2 @@ -477,7 +341,7 @@ def setup_by_dev_kit_name(self, name: str) -> None: elif name.lower() == "adsy1100": # ADI VPX Module # VU11P: xcvu11p-flgb2104-2-i - self.transceiver_type = "GTY4" + self.transceiver_type = "GTYE4" self.fpga_family = "Virtex" self.fpga_package = "FL" self.speed_grade = -2 @@ -554,33 +418,24 @@ def get_config( if solution: self.solution = solution + a = 1 + for config in self.configs: pll_config: Dict[str, Union[str, int, float]] = {} # Filter out other converters + # FIXME: REIMPLEMENT BETTER if converter.name + "_use_cpll" not in config.keys(): print("Continued") continue - # print('Not continued') - # continue - # pll = self._get_val(config[converter.name + "qpll_0_cpll_1"]) - cpll = self._get_val(config[converter.name + "_use_cpll"]) > 0 - qpll = self._get_val(config[converter.name + "_use_qpll"]) > 0 - qpll1 = self._get_val(config[converter.name + "_use_qpll1"]) > 0 - - if sum([cpll, qpll, qpll1]) != 1: - raise Exception( - "More than one PLL selected" - + " can only be one of CPLL, QPLL, or QPLL1" - ) - if cpll > 0: # type: ignore - pll_config = self.get_cpll_config(config, converter, fpga_ref) + pll_config = self._transceiver_models[converter.name].get_config(config, converter, fpga_ref) + cpll = self._get_val(config[converter.name + "_use_cpll"]) + qpll = self._get_val(config[converter.name + "_use_qpll"]) + if converter.name + "_use_qpll1" in config.keys(): + qpll1 = self._get_val(config[converter.name + "_use_qpll1"]) else: - pll_name = "qpll" if qpll else "qpll1" - pll_config = self.get_qpll_config( - config, converter, fpga_ref, n=int(qpll1) - ) + qpll1 = False # SERDES output mux if pll_config["type"] == "cpll": @@ -622,86 +477,6 @@ def get_config( config[converter.name + "_link_out_div"] ) - if qpll or qpll1: - pname = "qpll" if qpll else "qpll1" - - self.solution.print_solution() - - if self.transceiver_type[:3] == "GTY": - # n_dot_frac = self._get_val(config[converter.name + f"_n_dot_frac_{pname}"]) - frac = self._get_val( - config[converter.name + f"_sdm_data_{pname}"] - ) / ( - 2 - ** self._get_val(config[converter.name + f"_sdm_width_{pname}"]) - ) - n_dot_frac = ( - self._get_val(config[converter.name + f"_n_{pname}"]) + frac - ) - if ( - not float(n_dot_frac).is_integer() - and converter.bit_clock >= 28.1e9 - ): - raise Exception( - f"Invalid QPLL1 n_dot_frac {n_dot_frac} must be integer" - ) - pll_clk_out = ( - fpga_ref - * n_dot_frac - / (pll_config["m"] * pll_config["clkout_rate"]) - ) - lr = pll_clk_out * 2 / pll_config["d"] - assert ( - lr == converter.bit_clock - ), f"Invalid {pname} lane rate {lr} != {converter.bit_clock}" - - elif self.transceiver_type[:3] in ["GTX", "GTH"]: - pll_clk_out = fpga_ref * pll_config["n"] / (pll_config["m"] * 2) - lr = pll_clk_out * 2 / pll_config["d"] - assert ( - lr == converter.bit_clock - ), f"Invalid {pname} lane rate {lr} != {converter.bit_clock}" - - else: - raise Exception( - f"Unsupported transceiver type {self.transceiver_type}" - ) - - # if self.transceiver_type in ["GTY4"]: - # pll_clk_out = ( - # fpga_ref - # * pll_config["n_dot_frac"] - # / (pll_config["m"] * pll_config["clkout_rate"]) - # ) - # lr = pll_clk_out * 2 / pll_config["d"] - # assert ( - # lr == converter.bit_clock - # ), f"Invalid QPLL1 lane rate {lr} != {converter.bit_clock}" # type: ignore # noqa: B950 - - # else: - # div = self._get_val( - # config[converter.name + "qty4_full_rate_divisor"] - # ) - # lr = ( - # fpga_ref - # * div - # * pll_config["n"] - # / (pll_config["m"] * 1) - # * 1 - # / pll_config["d"] - # ) - # assert ( - # lr == converter.bit_clock - # ), f"Invalid QPLL1 lane rate {lr} != {converter.bit_clock}" # type: ignore # noqa: B950 - - # Check - if pll_config["out_clk_select"] == "XCVR_REF_CLK" and not cpll: - assert ( - pll_config["vco"] == converter.bit_clock * pll_config["d"] # type: ignore # noqa: B950 - ), "Invalid QPLL lane rate {} != {}".format( - pll_config["vco"] / pll_config["d"], converter.bit_clock # type: ignore # noqa: B950 - ) - out.append(pll_config) if len(out) == 1: @@ -738,9 +513,9 @@ def _get_progdiv(self) -> Union[List[int], List[float]]: Returns: List[int,float]: Programmable dividers for FPGA """ - if self.transceiver_type in ["GTY3", "GTH3"]: + if self.transceiver_type in ["GTYE3", "GTHE3"]: return [1, 4, 5, 8, 10, 16, 16.5, 20, 32, 33, 40, 64, 66, 80, 100] - elif self.transceiver_type in ["GTY4", "GTH4"]: + elif self.transceiver_type in ["GTYE4", "GTHE4"]: return [1, 4, 5, 8, 10, 16, 16.5, 20, 32, 33, 40, 64, 66, 80, 100, 128, 132] else: raise Exception( @@ -915,358 +690,21 @@ def _setup_quad_tile( elif self.ref_clock_constraint == "CORE_CLOCK_DIV2": self._add_equation([fpga_ref == core_clock / 2]) - # CPLL -> VCO = FPGA_REF * N1*N2/M - # PLLOUT = VCO - # LR = PLLOUT * 2/D - # LR = FPGA_REF * N1*N2*2/(M*D) - # - # QPLL -> VCO = FPGA_REF * N/(M*2) - # PLLOUT = VCO/2 - # LR = PLLOUT * 2/D - # LR = FPGA_REF * N/(M*D) + # Add transceiver config = {} - # Save PLL settings - ref_sys_clk_select = self.sys_clk_select - - # Extract permutations - self.sys_clk_select = "XCVR_QPLL0" - vco0_min_qpll = self.vco0_min - vco0_max_qpll = self.vco0_max - vco1_min_qpll = self.vco1_min - vco1_max_qpll = self.vco1_max - self.sys_clk_select = "XCVR_QPLL1" - vco0_min_qpll1 = self.vco0_min - vco0_max_qpll1 = self.vco0_max - vco1_min_qpll1 = self.vco1_min - vco1_max_qpll1 = self.vco1_max - - self.sys_clk_select = ref_sys_clk_select # Restore PLL settings - - config = self.add_cpll_contraints(config, fpga_ref, converter) - config = self.add_qpll_contraints(config, fpga_ref, converter) - config = self.add_qpll1_contraints(config, fpga_ref, converter) - - self._add_equation( - [ - config[converter.name + "_use_cpll"] - + config[converter.name + "_use_qpll"] - + config[converter.name + "_use_qpll1"] - == 1 - ] - ) - - # # GTHE3, GTHE4, GTYE4 - # qpll1_allowed = self.transceiver_type in ["GTH3", "GTH4", "GTY4"] - - # if self.transceiver_type in ["GTY4"]: - # dqpll = [1, 2, 4, 8, 16, 32] - # else: - # dqpll = [1, 2, 4, 8, 16] - # # QPLL - # config[converter.name + "m_qpll"] = self._convert_input( - # [1, 2, 3, 4], converter.name + "m_qpll" - # ) - # config[converter.name + "d_qpll"] = self._convert_input( - # dqpll, converter.name + "d_qpll" - # ) - # config[converter.name + "n_qpll"] = self._convert_input( - # self.N, converter.name + "n_qpll" - # ) - - # # QPLL1 - # config[converter.name + "m_qpll1"] = self._convert_input( - # [1, 2, 3, 4], converter.name + "m_qpll1" - # ) - # config[converter.name + "d_qpll1"] = self._convert_input( - # dqpll, converter.name + "d_qpll1" - # ) - # config[converter.name + "n_qpll1"] = self._convert_input( - # self.N, converter.name + "n_qpll1" - # ) - - # if self.transceiver_type in ["GTY4"]: - # # GTY fractional PLL - # config[converter.name + "qpll_clkoutrate"] = self._convert_input( - # [1, 2], converter.name + "qpll_clkoutrate" - # ) - # config[converter.name + "qpll_sdmdata"] = integer_var( - # min=0, max=(2**24 - 1), name=converter.name + "qpll_sdmdata" - # ) - # config[converter.name + "qpll_sdmwidth"] = self._convert_input( - # [16, 20, 24], converter.name + "qpll_sdmwidth" - # ) - # config[converter.name + "qpll_frac"] = self._add_intermediate( - # config[converter.name + "qpll_sdmdata"] - # / ( - # 2 ** config[converter.name + "qpll_sdmwidth"] - # ) # FIXME: REMOVE POWER OF 2 - # ) - # self._add_equation( - # [ - # config[converter.name + "qpll_frac"] < 1, - # ] - # ) - # config[converter.name + "qpll_N_dot_frac"] = self._add_intermediate( - # config[converter.name + "n_qpll"] + config[converter.name + "qpll_frac"] - # ) - # self.model.add_kpi( - # config[converter.name + "qpll_N_dot_frac"], - # converter.name + "qpll_N_dot_frac", - # ) - - # config[converter.name + "vco_qpll"] = self._add_intermediate( - # fpga_ref - # * config[converter.name + "qpll_N_dot_frac"] - # / ( - # config[converter.name + "m_qpll"] - # * config[converter.name + "qpll_clkoutrate"] - # ) - # ) - # config[converter.name + "vco_qpll1"] = self._add_intermediate( - # fpga_ref - # * config[converter.name + "qpll_N_dot_frac"] - # / ( - # config[converter.name + "m_qpll1"] - # * config[converter.name + "qpll_clkoutrate"] - # ) - # ) - - # # When lane rate > 28.1 Gbps, qpll_frac must be set to 0 - # config[converter.name + "qpll_frac_bypass"] = self._convert_input( - # [0, 1], converter.name + "qpll_frac_bypass" - # ) - # self._add_equation( - # [ - # (1 - config[converter.name + "qpll_frac_bypass"]) - # * converter.bit_clock - # <= int(28.1e9), - # config[converter.name + "qpll_frac_bypass"] - # * config[converter.name + "qpll_frac"] - # == 0, - # ] - # ) - - # else: - # config[converter.name + "vco_qpll"] = self._add_intermediate( - # fpga_ref - # * config[converter.name + "n_qpll"] - # / (config[converter.name + "m_qpll"]) - # ) - # config[converter.name + "vco_qpll1"] = self._add_intermediate( - # fpga_ref - # * config[converter.name + "n_qpll1"] - # / (config[converter.name + "m_qpll1"]) - # ) - - # # Define QPLL band requirements - # config[converter.name + "band_qpll"] = self._convert_input( - # [0, 1], converter.name + "band_qpll" - # ) - - # config[converter.name + "vco_max_qpll"] = self._add_intermediate( - # config[converter.name + "band_qpll"] * vco1_max_qpll - # + (1 - config[converter.name + "band_qpll"]) * vco0_max_qpll - # ) - # config[converter.name + "vco_min_qpll"] = self._add_intermediate( - # config[converter.name + "band_qpll"] * vco1_min_qpll - # + (1 - config[converter.name + "band_qpll"]) * vco0_min_qpll - # ) - - # # Define QPLL1 band requirements - # # if qpll1_allowed: - # config[converter.name + "band_qpll1"] = self._convert_input( - # [0, 1], converter.name + "band_qpll1" - # ) - - # config[converter.name + "vco_max_qpll1"] = self._add_intermediate( - # config[converter.name + "band_qpll1"] * vco1_max_qpll1 - # + (1 - config[converter.name + "band_qpll1"]) * vco0_max_qpll1 - # ) - # config[converter.name + "vco_min_qpll1"] = self._add_intermediate( - # config[converter.name + "band_qpll1"] * vco1_min_qpll1 - # + (1 - config[converter.name + "band_qpll1"]) * vco0_min_qpll1 - # ) - - # # Define if we can use GTY (is available) at full rate - # if self.transceiver_type != "GTY4": - # # QPLL1 does not exist for GTY4 so we cannot bypass the extra dec 2 - # config[converter.name + "qty4_full_rate_divisor"] = self._convert_input( - # 1, name=converter.name + "qty4_full_rate_divisor" - # ) - # else: - # config[converter.name + "qty4_full_rate_divisor"] = self._convert_input( - # [1, 2], name=converter.name + "qty4_full_rate_divisor" - # ) - - # # config[converter.name + "qty4_full_rate_enabled"] = self._add_intermediate( - # # 1 - config[converter.name + "qty4_full_rate_divisor"] - # # ) - - # ####################### - # # CPLL - # # CPLL -> VCO = FPGA_REF * N1*N2/M - # # LR = VCO * 2/D - # # LR = FPGA_REF * N1*N2*2/(M*D) - # config[converter.name + "m_cpll"] = self._convert_input( - # [1, 2], converter.name + "m_cpll" - # ) - # # We do not allow D=16 or D=32 since they do not allow TX/RXOUT DIV - # config[converter.name + "d_cpll"] = self._convert_input( - # [1, 2, 4, 8], converter.name + "d_cpll" - # ) - # config[converter.name + "n1_cpll"] = self._convert_input( - # [4, 5], converter.name + "n1_cpll" - # ) - # config[converter.name + "n2_cpll"] = self._convert_input( - # [1, 2, 3, 4, 5], converter.name + "n2_cpll" - # ) - - # config[converter.name + "vco_cpll"] = self._add_intermediate( - # fpga_ref - # * config[converter.name + "n1_cpll"] - # * config[converter.name + "n2_cpll"] - # / config[converter.name + "m_cpll"] - # ) - - # # Merge - # # if sum([self.force_qpll, self.force_qpll1, self.force_cpll]) > 1: - # # raise Exception("Cannot force multiple PLLs QPLL0, QPLL1, CPLL") - # if ( - # sum( - # [ - # self._get_conv_prop(converter, self.force_qpll), - # self._get_conv_prop(converter, self.force_qpll1), - # self._get_conv_prop(converter, self.force_cpll), - # ] - # ) - # > 1 - # ): - # raise Exception("Cannot force multiple PLLs QPLL0, QPLL1, CPLL") - - # if self._get_conv_prop(converter, self.force_qpll1) and not qpll1_allowed: - # raise Exception( - # "QPLL1 is not available for transceiver " + self.transceiver_type - # ) - - # if self._get_conv_prop(converter, self.force_qpll): - # qpll = 1 - # qpll1 = 0 - # cpll = 0 - # elif self._get_conv_prop(converter, self.force_qpll1): - # qpll = 0 - # qpll1 = 1 - # cpll = 0 - # elif self._get_conv_prop(converter, self.force_cpll): - # qpll = 0 - # qpll1 = 0 - # cpll = 1 - # else: - # qpll = [0, 1] - # if qpll1_allowed: - # qpll1 = [0, 1] - # else: - # qpll1 = 0 - # cpll = [0, 1] - - # config[converter.name + "_use_cpll"] = self._convert_input( - # cpll, converter.name + "_use_cpll" - # ) - # config[converter.name + "_use_qpll"] = self._convert_input( - # qpll, converter.name + "_use_qpll" - # ) - # config[converter.name + "_use_qpll1"] = self._convert_input( - # qpll1, converter.name + "_use_qpll1" - # ) - - # # Select only one PLL - # if ( - # not self._get_conv_prop(converter, self.force_cpll) - # and not self._get_conv_prop(converter, self.force_qpll) - # and not self._get_conv_prop(converter, self.force_qpll1) - # ): - # self._add_equation( - # 1 - # == config[converter.name + "_use_cpll"] - # + config[converter.name + "_use_qpll"] - # + config[converter.name + "_use_qpll1"] - # ) - - # # VCO - # config[converter.name + "vco_select"] = self._add_intermediate( - # config[converter.name + "_use_cpll"] * config[converter.name + "vco_cpll"] - # + config[converter.name + "_use_qpll"] * config[converter.name + "vco_qpll"] - # + config[converter.name + "_use_qpll1"] - # * config[converter.name + "vco_qpll1"] - # ) - - # config[converter.name + "vco_min_select"] = self._add_intermediate( - # config[converter.name + "_use_cpll"] * self.vco_min - # + config[converter.name + "_use_qpll"] - # * config[converter.name + "vco_min_qpll"] - # + config[converter.name + "_use_qpll1"] - # * config[converter.name + "vco_min_qpll1"] - # ) - - # config[converter.name + "vco_max_select"] = self._add_intermediate( - # config[converter.name + "_use_cpll"] * self.vco_max - # + config[converter.name + "_use_qpll"] - # * config[converter.name + "vco_max_qpll"] - # + config[converter.name + "_use_qpll1"] - # * config[converter.name + "vco_max_qpll1"] - # ) - - # config[converter.name + "d_select"] = self._add_intermediate( - # config[converter.name + "_use_cpll"] * config[converter.name + "d_cpll"] - # + config[converter.name + "_use_qpll"] * config[converter.name + "d_qpll"] - # + config[converter.name + "_use_qpll1"] * config[converter.name + "d_qpll1"] - # ) - - # # Note: QPLL has extra /2 after VCO so: - # # QPLL: lanerate == vco/d - # # CPLL: lanerate == vco*2/d - - # config[converter.name + "rate_divisor_select"] = self._add_intermediate( - # config[converter.name + "_use_cpll"] * 2 - # + config[converter.name + "_use_qpll"] - # * config[converter.name + "qty4_full_rate_divisor"] - # + config[converter.name + "_use_qpll1"] - # * config[converter.name + "qty4_full_rate_divisor"] - # ) - - # ####################### - - # # Set all relations - # # QPLL+CPLL - # # - # # CPLL -> VCO = FPGA_REF * N1*N2/M - # # PLLOUT = VCO - # # LR = PLLOUT * 2/D - # # LR = FPGA_REF * N1*N2*2/(M*D) - # # - # # QPLL -> VCO = FPGA_REF * N/(M) - # # PLLOUT = VCO/2 - # # LR = PLLOUT * 2/D - # # LR = FPGA_REF * N/(M*D) - # # - # # LR = FPGA_REF*(A*N1*N2*2/(M*D) + (A-1)*N/(M*D)) - # # A = 0,1 - # # LR*D*M = FPGA_REF*(A*N1*N2*2 + (A-1)*N) - - # self._add_equation( - # [ - # config[converter.name + "vco_select"] - # >= config[converter.name + "vco_min_select"], - # config[converter.name + "vco_select"] - # <= config[converter.name + "vco_max_select"], - # # CPLL - # # converter.bit_clock == vco * 2 / d - # # QPLL - # # converter.bit_clock == vco / d - # config[converter.name + "vco_select"] - # * config[converter.name + "rate_divisor_select"] - # == converter.bit_clock * config[converter.name + "d_select"], - # ] - # ) + if self.fpga_generation() == "7000": + self._transceiver_models[converter.name] = SSTransceiver(parent=self, transceiver_type=self.transceiver_type) + elif self.fpga_generation() in ["Ultrascale", "Ultrascale+"]: + self._transceiver_models[converter.name] = USPTransceiver(parent=self, transceiver_type=self.transceiver_type) + else: + raise Exception(f"Unsupported FPGA generation {self.fpga_generation()}") + + self._transceiver_models[converter.name].force_cpll = self.force_cpll + self._transceiver_models[converter.name].force_qpll = self.force_qpll + if hasattr(self._transceiver_models[converter.name], 'force_qpll1'): + self._transceiver_models[converter.name].force_qpll1 = self.force_qpll1 + + config = self._transceiver_models[converter.name].add_constraints(config, fpga_ref, converter) # Add constraints for link clock # - Must be lanerate/40 204B or lanerate/66 204C diff --git a/adijif/fpgas/xilinx_bf.py b/adijif/fpgas/xilinx/bf.py similarity index 100% rename from adijif/fpgas/xilinx_bf.py rename to adijif/fpgas/xilinx/bf.py diff --git a/adijif/fpgas/xilinx/pll.py b/adijif/fpgas/xilinx/pll.py index 4fb24d5..d35f91c 100644 --- a/adijif/fpgas/xilinx/pll.py +++ b/adijif/fpgas/xilinx/pll.py @@ -1,6 +1,141 @@ from abc import ABC, ABCMeta, abstractmethod +from typing import Union + +from docplex.cp.solution import CpoSolveResult # type: ignore + +from ...gekko_trans import gekko_translation class XilinxPLL: - def transceiver_types_available(self): - ... + plls = None + parent = None + _model = None # Hold internal model when used standalone + _solution = None # Hold internal solution when used standalone + + def __init__(self, parent=None, speed_grade="-2", transceiver_type="GTXE2", *args, **kwargs) -> None: + """Initalize 7 series transceiver PLLs. + + Args: + parent (system or converter, optional): Parent object. Defaults to None. + transceiver_type (str, optional): Transceiver type. Defaults to "GTXE2". + """ + self.transceiver_type = transceiver_type + super().__init__(*args, **kwargs) + self.parent = parent + self.add_plls() + + @property + def model(self): + """Internal system model for solver.""" + if self.parent: + return self.parent.model + return self._model + + @model.setter + def model(self, val): + if self.parent: + raise Exception("Cannot set model when parent model is used") + self._model = val + + @property + def solution(self): + """Solution object from solver.""" + if self.parent: + return self.parent.solution + return self._solution + + @solution.setter + def solution(self, val): + if self.parent: + raise Exception("Cannot set solution when parent model is used") + self._solution = val + + @property + def transceiver_type(self): + return self._transceiver_type + + @transceiver_type.setter + def transceiver_type(self, val): + self._check_in_range(val, self.transceiver_types_available, "transceiver_type") + self._transceiver_type = val + + _speed_grade = -2 + @property + def speed_grade(self): + if self.parent: + return self.parent.speed_grade + return self._speed_grade + + @speed_grade.setter + def speed_grade(self, val): + if self.parent: + raise Exception("Cannot set speed_grade when parent model is used") + self._speed_grade = val + + def _solve_gekko(self) -> bool: + """Local solve method for clock model. + + Call model solver with correct arguments. + + Returns: + bool: Always False + """ + self.model.options.SOLVER = 1 # APOPT solver + self.model.solver_options = [ + "minlp_maximum_iterations 1000", # minlp iterations with integer solution + "minlp_max_iter_with_int_sol 100", # treat minlp as nlp + "minlp_as_nlp 0", # nlp sub-problem max iterations + "nlp_maximum_iterations 500", # 1 = depth first, 2 = breadth first + "minlp_branch_method 1", # maximum deviation from whole number + "minlp_integer_tol 0", # covergence tolerance (MUST BE 0 TFC) + "minlp_gap_tol 0.1", + ] + + self.model.solve(disp=False) + self.model.cleanup() + return False + + # def _add_objective(self, sysrefs: List) -> None: + # pass + + def _solve_cplex(self) -> CpoSolveResult: + self.solution = self.model.solve(LogVerbosity="Normal") + if self.solution.solve_status not in ["Feasible", "Optimal"]: + raise Exception("Solution Not Found") + return self.solution + + def solve(self) -> Union[None, CpoSolveResult]: + """Local solve method for clock model. + + Call model solver with correct arguments. + + Returns: + [None,CpoSolveResult]: When cplex solver is used CpoSolveResult is returned + + Raises: + Exception: If solver is not valid + + """ + if self.solver == "gekko": + return self._solve_gekko() + elif self.solver == "CPLEX": + return self._solve_cplex() + else: + raise Exception(f"Unknown solver {self.solver}") + + +class PLLCommon(gekko_translation): + def __init__(self, parent_transceiver) -> None: + self.parent = parent_transceiver + + @property + def model(self): + return self.parent.model + + @property + def solver(self): + return self.parent.solver + + @property + def solution(self): + return self.parent.solution diff --git a/adijif/fpgas/xilinx/sevenseries.py b/adijif/fpgas/xilinx/sevenseries.py index 629f793..40328e0 100644 --- a/adijif/fpgas/xilinx/sevenseries.py +++ b/adijif/fpgas/xilinx/sevenseries.py @@ -1,16 +1,86 @@ -from .pll import XilinxPLL +from docplex.cp.modeler import if_then +from .pll import XilinxPLL, PLLCommon +from ...gekko_trans import gekko_translation +from ...common import core -class SevenSeries(XilinxPLL): +class SevenSeries(XilinxPLL, core, gekko_translation): # References # GTXs # https://docs.amd.com/v/u/en-US/ug476_7Series_Transceivers + # https://docs.amd.com/v/u/en-US/ds191-XC7Z030-XC7Z045-data-sheet - transceiver_types_available = ["GTXE2", "GTHE2"] + transceiver_types_available = ["GTXE2", "GTHE2"] # We don't support GTHE2 yet! + _transceiver_type = "GTXE2" + force_cpll = False + force_qpll = False -class SevenSeriesCPLL: + def add_plls(self) -> None: + """Add PLLs to the model.""" + self.plls = {"CPLL": CPLL(self), "QPLL": QPLL(self)} + + def add_constraints(self, config, fpga_ref, converter) -> dict: + """Add constraints for PLLs. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int, var): FPGA reference clock. + converter (converter): Converter object. + + Returns: + dict: Updated configuration dictionary. + """ + assert self.plls, "No PLLs configured. Run the add_plls method" + assert not (self.force_cpll and self.force_qpll), "Both CPLL and QPLL enabled" + for pll in self.plls: + config = self.plls[pll].add_constraints(config, fpga_ref, converter) + self._add_equation( + config[converter.name + "_use_cpll"] + config[converter.name + "_use_qpll"] + == 1 + ) + return config + + def get_config(self, config: dict, converter, fpga_ref) -> dict: + if self.force_cpll: + ecpll = 1 + eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] + elif self.force_qpll: + ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] + eqpll = 1 + else: + ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] + eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] + + assert ecpll != eqpll, "Both CPLL and QPLL enabled" + pll = "CPLL" if ecpll else "QPLL" + return self.plls[pll].get_config(config, converter, fpga_ref) + + +class CPLL(PLLCommon): + # vco_min = int(1.6e9) + # vco_max = int(5.16e9) + + @property + def vco_min(self): + if self.parent.transceiver_type == "GTXE2": + return 1600000000 + elif self.parent.transceiver_type == "GTHE2": + return 1600000000 + raise Exception( + f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" + ) + + @property + def vco_max(self): + if self.parent.transceiver_type == "GTXE2": + return 3300000000 + elif self.parent.transceiver_type == "GTHE2": + return 5160000000 + raise Exception( + f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" + ) M_available = [1, 2] _M = [1, 2] @@ -21,9 +91,331 @@ def M(self): @M.setter def M(self, value): - if value in self.M_available: - self._M = value + self._check_in_range(value, self.M_available, "M") + self._M = value + + N2_available = [1, 2, 3, 4, 5] + _N2 = [1, 2, 3, 4, 5] + + @property + def N2(self): + return self._N2 + + @N2.setter + def N2(self, value): + self._check_in_range(value, self.N2_available, "N2") + self._N2 = value + + N1_available = [4, 5] + _N1 = [4, 5] + + @property + def N1(self): + return self._N1 + + @N1.setter + def N1(self, value): + self._check_in_range(value, self.N1_available, "N1") + self._N1 = value + + D_available = [1, 2, 4, 8] + _D = [1, 2, 4, 8] + + @property + def D(self): + return self._D + + @N1.setter + def D(self, value): + self._check_in_range(value, self.D_available, "D") + self._D = value + + def get_config(self, config: dict, converter, fpga_ref) -> dict: + """Get CPLL configuration. + + Args: + config (dict): Configuration dictionary. + converter (converter): Converter object. + """ + pll_config = {} + pll_config["type"] = "cpll" + for k in ["m", "d", "n1", "n2"]: + pll_config[k] = self._get_val(config[converter.name + "_" + k + "_cpll"]) + + pll_config["vco"] = ( + fpga_ref * pll_config["n1"] * pll_config["n2"] / pll_config["m"] # type: ignore # noqa: B950 + ) + # Check + assert ( + pll_config["vco"] * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 + ), "Invalid CPLL lane rate" + + return pll_config + + def add_constraints(self, config: dict, fpga_ref, converter) -> dict: + """Add Channel PLL (CPLL) constraints. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int): FPGA reference clock. + converter (converter): Converter object. + """ + if self.parent.force_cpll: + v = 1 else: - raise ValueError(f"M value not available. Choose from {self.M_available}") - + v = [0, 1] + config[converter.name + "_use_cpll"] = self._convert_input( + v, converter.name + "_use_cpll" + ) + if v == [0, 1]: + self.model.add_kpi( + config[converter.name + "_use_cpll"], name=converter.name + "_use_cpll" + ) + # Add variables + config[converter.name + "_m_cpll"] = self._convert_input( + self._M, converter.name + "_m_cpll" + ) + # This is documented oddly + # There are footnotes that include 16 with GTHs, and 16,32 with GTYs + # but its says "not supported for CPLL"?!?!? + config[converter.name + "_d_cpll"] = self._convert_input( + self._D, converter.name + "_d_cpll" + ) + config[converter.name + "_n1_cpll"] = self._convert_input( + self._N1, converter.name + "_n1_cpll" + ) + config[converter.name + "_n2_cpll"] = self._convert_input( + self._N2, converter.name + "_n2_cpll" + ) + + # Add intermediate variables + config[converter.name + "_pll_out_cpll"] = self._add_intermediate( + fpga_ref + * config[converter.name + "_n1_cpll"] + * config[converter.name + "_n2_cpll"] + / (config[converter.name + "_m_cpll"]) + ) + + # Add constraints + self._add_equation( + [ + if_then( + config[converter.name + "_use_cpll"] == 1, + config[converter.name + "_d_cpll"] * converter.bit_clock + == config[converter.name + "_pll_out_cpll"] * 2, + ), + ] + ) + self._add_equation( + [ + if_then( + config[converter.name + "_use_cpll"] == 1, + config[converter.name + "_pll_out_cpll"] >= self.vco_min, + ), + if_then( + config[converter.name + "_use_cpll"] == 1, + config[converter.name + "_pll_out_cpll"] <= self.vco_max, + ), + ] + ) + + return config + + +class QPLL(PLLCommon): + M_available = [1, 2, 3, 4] + _M = [1, 2, 3, 4] + + @property + def M(self): + return self._M + + @M.setter + def M(self, value): + self._check_in_range(value, self.M_available, "M") + self._M = value + + N_available = [16, 20, 32, 40, 64, 66, 80, 100] + _N = [16, 20, 32, 40, 64, 66, 80, 100] + + @property + def N(self): + return self._N + + @N.setter + def N(self, value): + self._check_in_range(value, self.N_available, "N") + self._N = value + + D_available = [1, 2, 4, 8, 16] + _D = [1, 2, 4, 8, 16] + + @property + def D(self): + return self._D + + @D.setter + def D(self, value): + self._check_in_range(value, self.D_available, "D") + self._D = value + + @property + def vco_min(self): + if self.parent.transceiver_type[:3] == "GTH": + return 8000000000 + elif self.parent.transceiver_type[:3] == "GTX": # FIXME: This is only the lower band + return 5930000000 + raise Exception(f"Unknown vco_min for transceiver type {self.parent.transceiver_type}") + @property + def vco_max(self): + if self.parent.transceiver_type[:3] == "GTH": + if self.parent.speed_grade >= -2: + return 10312500000 + return 13100000000 + elif self.parent.transceiver_type[:3] == "GTX": # FIXME: This is only the lower band + if self.parent.speed_grade >= -2: + return 10312500000 + return 12500000000 + raise Exception(f"Unknown vco_max for transceiver type {self.parent.transceiver_type}") + + def add_constraints(self, config: dict, fpga_ref, converter) -> dict: + """Add constraints for QPLL for 7 series FPGAs. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int): FPGA reference clock. + converter (converter): Converter object. + """ + if self.parent.force_qpll: + v = 1 + else: + v = [0, 1] + # Global flag to use QPLLn + config[converter.name + "_use_qpll"] = self._convert_input( + v, converter.name + "_use_qpll" + ) + if v == [0, 1]: + self.model.add_kpi( + config[converter.name + "_use_qpll"], name=converter.name + "_use_qpll" + ) + + # Add variables + config[converter.name + "_m_qpll"] = self._convert_input( + self._M, converter.name + "_m_qpll" + ) + config[converter.name + "_d_qpll"] = self._convert_input( + self._D, converter.name + "_d_qpll" + ) + config[converter.name + "_n_qpll"] = self._convert_input( + self._N, converter.name + "_n_qpll" + ) + + # Add intermediate variables + config[converter.name + "_vco_qpll"] = self._add_intermediate( + fpga_ref + * config[converter.name + "_n_qpll"] + / config[converter.name + "_m_qpll"] + ) + config[converter.name + "_pll_out_qpll"] = self._add_intermediate( + fpga_ref + * config[converter.name + "_n_qpll"] + / (config[converter.name + "_m_qpll"] * 2) + ) + + # Add constraints + self._add_equation( + [ + if_then( + config[converter.name + "_use_qpll"] == 1, + converter.bit_clock + == config[converter.name + "_pll_out_qpll"] + * 2 + / config[converter.name + "_d_qpll"], + ), + ] + ) + + if self.parent.transceiver_type[:3] == "GTH": + self._add_equation( + [ + if_then( + config[converter.name + "_use_qpll"] == 1, + config[converter.name + "_vco_qpll"] >= int(self.vco_min), + ), + if_then( + config[converter.name + "_use_qpll"] == 1, + config[converter.name + "_vco_qpll"] <= int(self.vco_max), + ), + ] + ) + elif self.parent.transceiver_type[:3] == "GTX": + config[converter.name + "_lower_band_qpll"] = self._convert_input( + [0, 1], converter.name + "_lower_band_qpll" + ) + # Lower band + c1 = if_then( + config[converter.name + "_use_qpll"] == 1, + if_then( + config[converter.name + "_lower_band_qpll"] == 0, + config[converter.name + "_vco_qpll"] >= int(self.vco_min), + ), + ) + c2 = if_then( + config[converter.name + "_use_qpll"] == 1, + if_then( + config[converter.name + "_lower_band_qpll"] == 0, + config[converter.name + "_vco_qpll"] <= int(self.vco_max), + ), + ) + # See page 72 of https://docs.amd.com/v/u/en-US/ds191-XC7Z030-XC7Z045-data-sheet + c5 = if_then( + config[converter.name + "_use_qpll"] == 1, + if_then( + config[converter.name + "_lower_band_qpll"] == 0, + config[converter.name + "_d_qpll"] <= 8, # no 16 + ), + ) + + # Upper band + c3 = if_then( + config[converter.name + "_use_qpll"] == 1, + if_then( + config[converter.name + "_lower_band_qpll"] == 1, + config[converter.name + "_vco_qpll"] >= int(9.8e9), + ), + ) + c4 = if_then( + config[converter.name + "_use_qpll"] == 1, + if_then( + config[converter.name + "_lower_band_qpll"] == 1, + config[converter.name + "_vco_qpll"] + <= int(12.5e9), # FIXME: only for -3 silicon + ), + ) + self._add_equation([c1, c2, c3, c4, c5]) + + else: + raise Exception("Unsupported transceiver type") + + return config + + def get_config(self, config: dict, converter, fpga_ref) -> dict: + """Get QPLL configuration for 7 series FPGAs. + + Args: + config (dict): Configuration dictionary. + converter (converter): Converter object. + """ + pll_config = {"type": "qpll"} + for k in ["m", "d", "n"]: + pll_config[k] = self._get_val(config[converter.name + "_" + k + "_qpll"]) + + pll_config["vco"] = fpga_ref * pll_config["n"] / pll_config["m"] + pll_clk_out = fpga_ref * pll_config["n"] / (pll_config["m"] * 2) + # Check + assert ( + pll_clk_out * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 + ), f"Invalid QPLL lane rate {pll_config['vco'] * 2 / pll_config['d']} != {converter.bit_clock}" + + return pll_config diff --git a/adijif/fpgas/xilinx/ultrascaleplus.py b/adijif/fpgas/xilinx/ultrascaleplus.py new file mode 100644 index 0000000..3d6b163 --- /dev/null +++ b/adijif/fpgas/xilinx/ultrascaleplus.py @@ -0,0 +1,398 @@ +"""Ultrascale+ PLLs transceiver models.""" + +from docplex.cp.modeler import if_then + +from .pll import XilinxPLL, PLLCommon +from .sevenseries import CPLL as SevenSeriesCPLL +from .sevenseries import QPLL as SevenSeriesQPLL +from ...gekko_trans import gekko_translation +from ...common import core + + +class UltraScalePlus(XilinxPLL, core, gekko_translation): + # References + # GTYs + # https://docs.amd.com/v/u/en-US/ug578-ultrascale-gty-transceivers + # https://docs.amd.com/r/en-US/ds925-zynq-ultrascale-plus/GTY-Transceiver-Switching-Characteristics + # GTHs + # https://docs.amd.com/v/u/en-US/ug576-ultrascale-gth-transceivers + + transceiver_types_available = ["GTHE4", "GTYE3", "GTYE4"] + _transceiver_type = "GTHE4" + + force_cpll = False + force_qpll = False + force_qpll1 = False + + def add_plls(self) -> None: + """Add PLLs to the model.""" + self.plls = {"CPLL": CPLL(self), "QPLL": QPLL(self), "QPLL1": QPLL1(self)} + + def add_constraints(self, config, fpga_ref, converter) -> dict: + """Add constraints for PLLs. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int, var): FPGA reference clock. + converter (converter): Converter object. + + Returns: + dict: Updated configuration dictionary. + """ + assert self.plls, "No PLLs configured. Run the add_plls method" + assert ( + self.force_cpll + self.force_qpll + self.force_qpll1 <= 1 + ), "Only one PLL can be enabled" + for pll in self.plls: + config = self.plls[pll].add_constraints(config, fpga_ref, converter) + self._add_equation( + config[converter.name + "_use_cpll"] + + config[converter.name + "_use_qpll"] + + config[converter.name + "_use_qpll1"] + == 1 + ) + return config + + def get_config(self, config: dict, converter, fpga_ref) -> dict: + if self.force_cpll or self.force_qpll or self.force_qpll1: + if self.force_cpll: + ecpll = 1 + eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] + eqpll1 = self.solution.get_kpis()[converter.name + "_use_qpll1"] + elif self.force_qpll: + ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] + eqpll = 1 + eqpll1 = self.solution.get_kpis()[converter.name + "_use_qpll1"] + else: + ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] + eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] + eqpll1 = 1 + else: + ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] + eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] + eqpll1 = self.solution.get_kpis()[converter.name + "_use_qpll1"] + assert ecpll + eqpll + eqpll1 == 1, "Only one PLL can be enabled" + if ecpll: + pll = "CPLL" + elif eqpll: + pll = "QPLL" + else: + pll = "QPLL1" + return self.plls[pll].get_config(config, converter, fpga_ref) + + +class CPLL(SevenSeriesCPLL): + """CPLL model for Ultrascale+ transceivers.""" + + @property + def vco_min(self): + if self.parent.transceiver_type in ["GTHE4", "GTYE3", "GTYE4"]: + return 2000000000 + raise Exception( + f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" + ) + + @property + def vco_max(self): + if self.parent.transceiver_type in ["GTHE4", "GTYE3", "GTYE4"]: + return 6250000000 + raise Exception( + f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" + ) + + +class QPLL(SevenSeriesQPLL): + """QPLL model for Ultrascale+ transceivers.""" + + force_integer_mode = False + + @property + def vco_min(self): + if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE4"]: + return 9800000000 + raise Exception( + f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" + ) + + @property + def vco_max(self): + if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE4"]: + return 16375000000 + raise Exception( + f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" + ) + + N_available = [*range(16, 160 + 1)] + _N = [*range(16, 160 + 1)] + + D_available = [1, 2, 4, 8, 16] + _D = [1, 2, 4, 8, 16] + # 32 not available in AC modes https://docs.amd.com/r/en-US/ds925-zynq-ultrascale-plus/GTY-Transceiver-Switching-Characteristics + + @property + def QPLL_CLKOUTRATE_available(self): + if self.parent.transceiver_type == "GTHE4": + return [1, 2] + return [1] + + _QPLL_CLKOUTRATE_GTY = [1, 2] + _QPLL_CLKOUTRATE_GTH = [1, 2] + + @property + def QPLL_CLKOUTRATE(self): + if "GTH" in self.parent.transceiver_type: + return self._QPLL_CLKOUTRATE_GTH + return self._QPLL_CLKOUTRATE_GTY + + @QPLL_CLKOUTRATE.setter + def QPLL_CLKOUTRATE(self, val): + self._check_in_range(val, self.QPLL_CLKOUTRATE_available, "QPLL_CLKOUTRATE") + if "GTH" in self.parent.transceiver_type: + self._QPLL_CLKOUTRATE_GTH = val + return self._QPLL_CLKOUTRATE_GTY + + SDMDATA_min_max = [0, 2**24 - 1] + _SDMDATA_min = 0 + + @property + def SDMDATA_min(self): + return self._SDMDATA_min + + @SDMDATA_min.setter + def SDMDATA_min(self, val): + if val < self.SDMDATA_min_max[0] or val > self.SDMDATA_min_max[1]: + raise ValueError( + f"SDMDATA_min must be between {self.SDMDATA_min_max[0]} and {self.SDMDATA_min_max[1]}" + ) + self._SDMDATA_min = val + + _SDMDATA_max = 2**24 - 1 + + @property + def SDMDATA_max(self): + return self._SDMDATA_max + + @SDMDATA_max.setter + def SDMDATA_max(self, val): + if val < self.SDMDATA_min_max[0] or val > self.SDMDATA_min_max[1]: + raise ValueError( + f"SDMDATA must be between {self.SDMDATA_min_max[0]} and {self.SDMDATA_min_max[1]}" + ) + self._SDMDATA_max = val + + SDMWIDTH_available = [16, 20, 24] + _SDMWIDTH = [16, 20, 24] + + @property + def SDMWIDTH(self): + return self._SDMWIDTH + + @SDMWIDTH.setter + def SDMWIDTH(self, val): + self._check_in_range(val, self.SDMWIDTH_available, "SDMWIDTH") + self._SDMWIDTH = val + + _pname = "qpll" + + def get_config(self, config: dict, converter, fpga_ref) -> dict: + """Get the configuration of the QPLL. + + Args: + config (dict): Configuration dictionary. + converter (converter): Converter object. + fpga_ref (int, var): FPGA reference clock. + + Returns: + dict: Updated configuration dictionary. + """ + pname = self._pname + pll_config = {"type": self._pname} + pll_config["n"] = self._get_val(config[converter.name + f"_n_{pname}"]) + pll_config["m"] = self._get_val(config[converter.name + f"_m_{pname}"]) + pll_config["d"] = self._get_val(config[converter.name + f"_d_{pname}"]) + pll_config["clkout_rate"] = self._get_val( + config[converter.name + f"_clkout_rate_{pname}"] + ) + if converter.bit_clock < 28.1e9 and not self.force_integer_mode: + sdm_data = self._get_val(config[converter.name + f"_sdm_data_{pname}"]) + if sdm_data > 0: + pll_config["sdm_data"] = sdm_data + pll_config["sdm_width"] = self._get_val( + config[converter.name + f"_sdm_width_{pname}"] + ) + # config['frac'] = self._get_val(config[converter.name + f"_frac_{pname}"]) + pll_config["frac"] = self.solution.get_kpis()[ + converter.name + f"_frac_{pname}" + ] + # config['n_dot_frac'] = self._get_val(config[converter.name + f"_n_dot_frac_{pname}"]) + pll_config["n_dot_frac"] = self.solution.get_kpis()[ + converter.name + f"_n_dot_frac_{pname}" + ] + else: + pll_config["n_dot_frac"] = pll_config["n"] + else: + pll_config["n_dot_frac"] = pll_config["n"] + + pll_config["n"] = pll_config["n_dot_frac"] + + # config['vco'] = self._get_val(config[converter.name + f"_vco_{pname}"]) + pll_config["vco"] = self.solution.get_kpis()[converter.name + f"_vco_{pname}"] + + # Check + pll_out = fpga_ref * pll_config["n_dot_frac"] / (pll_config["m"] * pll_config["clkout_rate"]) + lane_rate = pll_out * 2 / pll_config["d"] + assert lane_rate == converter.bit_clock, f"{lane_rate} != {converter.bit_clock}" + + return pll_config + + def add_constraints(self, config, fpga_ref, converter): + pname = self._pname + + # Global flag to use QPLLn + if self.parent.force_qpll and self._pname == "qpll": + v = 1 + elif self.parent.force_qpll1 and self._pname == "qpll1": + v = 1 + elif self.parent.force_cpll and self._pname == "cpll": + v = 1 + else: + v = [0, 1] + + config[converter.name + f"_use_{pname}"] = self._convert_input( + v, converter.name + f"_use_{pname}" + ) + if v == [0, 1]: + self.model.add_kpi( + config[converter.name + f"_use_{pname}"], + name=converter.name + f"_use_{pname}", + ) + + # Add variables + config[converter.name + f"_m_{pname}"] = self._convert_input( + self.M, converter.name + f"_m_{pname}" + ) + config[converter.name + f"_d_{pname}"] = self._convert_input( + self.D, converter.name + f"_d_{pname}" + ) + config[converter.name + f"_n_{pname}"] = self._convert_input( + self.N, converter.name + f"_n_{pname}" + ) + config[converter.name + f"_clkout_rate_{pname}"] = self._convert_input( + self.QPLL_CLKOUTRATE, converter.name + f"_clkout_rate_{pname}" + ) + + # Frac part + if converter.bit_clock < 28.1e9 and not self.force_integer_mode: + config[converter.name + f"_sdm_data_{pname}"] = self.model.integer_var( + min=self.SDMDATA_min, + max=self.SDMDATA_max, + name=converter.name + f"_sdm_data_{pname}", + ) + config[converter.name + f"_sdm_width_{pname}"] = self._convert_input( + self.SDMWIDTH, converter.name + f"_sdm_width_{pname}" + ) + config[converter.name + f"_HIGH_RATE_{pname}"] = self._convert_input( + self.QPLL_CLKOUTRATE, converter.name + f"_HIGH_RATE_{pname}" + ) + + # Add intermediate variables + if converter.bit_clock < 28.1e9 and not self.force_integer_mode: + config[converter.name + f"_frac_{pname}"] = self._add_intermediate( + config[converter.name + f"_sdm_data_{pname}"] + / (2 ** config[converter.name + f"_sdm_width_{pname}"]) + ) + self.model.add_kpi( + config[converter.name + f"_frac_{pname}"], + name=converter.name + f"_frac_{pname}", + ) + self._add_equation([config[converter.name + f"_frac_{pname}"] < 1]) + config[converter.name + f"_n_dot_frac_{pname}"] = self._add_intermediate( + config[converter.name + f"_n_{pname}"] + + config[converter.name + f"_frac_{pname}"] + ) + self.model.add_kpi( + config[converter.name + f"_n_dot_frac_{pname}"], + name=converter.name + f"_n_dot_frac_{pname}", + ) + else: + config[converter.name + f"_n_dot_frac_{pname}"] = self._add_intermediate( + config[converter.name + f"_n_{pname}"] + ) + + config[converter.name + f"_pll_out_{pname}"] = self._add_intermediate( + fpga_ref + * config[converter.name + f"_n_dot_frac_{pname}"] + / ( + config[converter.name + f"_m_{pname}"] + * config[converter.name + f"_clkout_rate_{pname}"] + ) + ) + config[converter.name + f"_vco_{pname}"] = self._add_intermediate( + fpga_ref + * config[converter.name + f"_n_dot_frac_{pname}"] + / ( + config[converter.name + f"_m_{pname}"] + * config[converter.name + f"_clkout_rate_{pname}"] + ) + ) + self.model.add_kpi( + config[converter.name + f"_vco_{pname}"], + name=converter.name + f"_vco_{pname}", + ) + + # Add constraints + self._add_equation( + [ + if_then( + config[converter.name + f"_use_{pname}"] == 1, + converter.bit_clock * config[converter.name + f"_d_{pname}"] + == config[converter.name + f"_pll_out_{pname}"] * 2, + ), + # if_then( + # config[converter.name + f"_use_{pname}"] == 1, + # config[converter.name + f"_HIGH_RATE_{pname}"] + # == (converter.bit_clock >= 28.1e9), + # ), + ] + ) + + self._add_equation( + [ + if_then( + config[converter.name + f"_use_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] >= self.vco_min, + ), + if_then( + config[converter.name + f"_use_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] <= self.vco_max, + ), + # if_then( + # config[converter.name + f"_HIGH_RATE_{pname}"] == 1, + # config[converter.name + f"_frac_{pname}"] == 0, + # ), + ] + ) + + return config + + +class QPLL1(QPLL): + """QPLL1 model for Ultrascale+ transceivers.""" + + _pname = "qpll1" + + @property + def vco_min(self): + if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE3", "GTYE4"]: + return 8000000000 + raise Exception( + f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" + ) + + @property + def vco_max(self): + if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE3", "GTYE4"]: + return 13000000000 + raise Exception( + f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" + ) diff --git a/adijif/fpgas/xilinx_fpga_table.yml b/adijif/fpgas/xilinx/xilinx_fpga_table.yml similarity index 100% rename from adijif/fpgas/xilinx_fpga_table.yml rename to adijif/fpgas/xilinx/xilinx_fpga_table.yml diff --git a/adijif/plls/adf4371.py b/adijif/plls/adf4371.py index 64e95f8..7bdd100 100644 --- a/adijif/plls/adf4371.py +++ b/adijif/plls/adf4371.py @@ -4,8 +4,7 @@ from docplex.cp.solution import CpoSolveResult # type: ignore from adijif.plls.pll import pll -from adijif.solvers import CpoExpr, GK_Intermediate, integer_var - +from adijif.solvers import CpoExpr, GK_Intermediate, integer_var, tround class adf4371(pll): """ADF4371 PLL model. @@ -218,6 +217,7 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: vco = self.solution.get_kpis()["vco"] config["rf_out_frequency"] = vco / config["rf_div"] + config["rf_out_frequency"] = tround(config["rf_out_frequency"]) return config diff --git a/adijif/solvers.py b/adijif/solvers.py index a45dda4..497a72b 100644 --- a/adijif/solvers.py +++ b/adijif/solvers.py @@ -2,6 +2,7 @@ # pytype: skip-file """Common solver API management layer.""" +from typing import Union try: from docplex.cp.expression import CpoExpr # type: ignore from docplex.cp.expression import CpoFunctionCall # type: ignore @@ -41,3 +42,11 @@ "No solver found. gekko or docplex/cplex must be installed." + "\n-> Use `pip install pyadi-jif[cplex]` or `pip install pyadi-jif[gekko]`" ) + + +def tround(value: float, tol: float = 1e-6) -> Union[float,int]: + """Round if expected to have computational noise.""" + if value.is_integer(): + return int(value) + if abs(value - round(value)) < tol: + return round(value) diff --git a/adijif/utils.py b/adijif/utils.py index c8e6efe..e5115a8 100644 --- a/adijif/utils.py +++ b/adijif/utils.py @@ -5,6 +5,8 @@ from adijif.converters.converter import converter from adijif.fpgas.fpga import fpga +import adijif.fpgas.xilinx.sevenseries as xp +import adijif.fpgas.xilinx.ultrascaleplus as us def get_jesd_mode_from_params(conv: converter, **kwargs: int) -> List[dict]: @@ -71,7 +73,14 @@ def get_max_sample_rates( """ if fpga: max_lanes = fpga.max_serdes_lanes - max_lane_rate = max([fpga.vco1_max, fpga.vco0_max]) + if int(fpga.transceiver_type[4]) == 2: + trx = xp.SevenSeries(parent=fpga, transceiver_type=fpga.transceiver_type) + max_lane_rate = trx.plls["QPLL"].vco_max + elif int(fpga.transceiver_type[4]) in [3, 4]: + trx = us.UltraScalePlus(parent=fpga, transceiver_type=fpga.transceiver_type) + max_lane_rate = trx.plls["QPLL"].vco_max * 2 + else: + raise Exception("Unsupported FPGA transceiver type") else: max_lanes = None max_lane_rate = None diff --git a/examples/ad9081_rx_calc.py b/examples/ad9081_rx_calc.py index 4c47ab3..4d334fb 100644 --- a/examples/ad9081_rx_calc.py +++ b/examples/ad9081_rx_calc.py @@ -1,6 +1,8 @@ # This example determines the maximum sample rate based on # FPGA platform and JESD204 class import adijif as jif +import adijif.fpgas.xilinx.sevenseries as xp +import adijif.fpgas.xilinx.ultrascaleplus as us import numpy as np conv = jif.ad9081_rx() @@ -10,7 +12,8 @@ fpga.setup_by_dev_kit_name("zc706") fpga.sys_clk_select = "GTH34_SYSCLK_QPLL0" # Use faster QPLL max_lanes = fpga.max_serdes_lanes -max_lane_rate = max([fpga.vco1_max, fpga.vco0_max]) +trx = xp.SevenSeries(transceiver_type=fpga.transceiver_type) +max_lane_rate = trx.plls['QPLL'].vco_max print( "Max aggregate bandwidth into FPGA SERDES:", max_lanes * max_lane_rate / 1e9, "Gbps" diff --git a/tests/test_bf.py b/tests/test_bf.py index 98e7d4b..3bedc09 100644 --- a/tests/test_bf.py +++ b/tests/test_bf.py @@ -463,6 +463,7 @@ def test_ad9523_1_daq2_config_force_m2(): assert cfs == ref +@pytest.mark.skip(reason="Deprecated due to new transceiver models") def test_daq2_fpga_qpll_rxtx_zc706_config(): # Full bandwidth example 1b clk = adijif.ad9523_1() diff --git a/tests/test_daq2.py b/tests/test_daq2.py index 05bef6f..1c72548 100644 --- a/tests/test_daq2.py +++ b/tests/test_daq2.py @@ -104,10 +104,10 @@ def test_ad9680_all_clk_chips_fpga_pll_modes_solver( try: o = sys.solve() - except: + except Exception as e: sys.model = [] del sys - raise Exception("ERROR") + raise e sys.model = [] del sys diff --git a/tests/test_fpga.py b/tests/test_fpga.py index a51c843..9b4797f 100644 --- a/tests/test_fpga.py +++ b/tests/test_fpga.py @@ -6,26 +6,6 @@ import adijif -@pytest.mark.parametrize( - "attr", - [ - "vco_min", - "vco_max", - "vco0_min", - "vco0_max", - "vco1_min", - "vco1_max", - "N", - ], -) -def test_jesd_unknown_trx(attr): - transciever_type = "NAN" - with pytest.raises(Exception, match=r"Unknown .* for transceiver type NAN"): - f = adijif.xilinx() - f.transciever_type = transciever_type - d = getattr(f, attr) - - def test_jesd_unknown_dev(): name = "zcu103" with pytest.raises(Exception, match=f"No boardname found in library for {name}"): diff --git a/tests/test_xilinx_pll.py b/tests/test_xilinx_pll.py new file mode 100644 index 0000000..8accb72 --- /dev/null +++ b/tests/test_xilinx_pll.py @@ -0,0 +1,91 @@ +import pytest +from pprint import pprint + +import adijif as jif +import adijif.fpgas.xilinx.sevenseries as xp +import adijif.fpgas.xilinx.ultrascaleplus as us + + +@pytest.mark.parametrize("pll, out_clock", [("cpll", 0.5e9), ("qpll", 10e9)]) +def test_7s_pll(pll, out_clock): + ss_plls = xp.SevenSeries(transceiver_type="GTXE2", solver="CPLEX") + + in_clock = int(100e6) + out_clock = int(out_clock) + cnv = jif.ad9680() + + # Change sample rate to get to the desired bit clock + # sample_rate * cnv.Np * cnv.M * (cnv.encoding_d / cnv.encoding_n) / cnv.L = bit_clock + sample_rate = ( + out_clock * cnv.L / (cnv.Np * cnv.M * (cnv.encoding_d / cnv.encoding_n)) + ) + cnv.sample_clock = sample_rate + + assert cnv.bit_clock == out_clock + + cfg = {} + config = ss_plls.add_constraints(cfg, in_clock, cnv) + + print(ss_plls.model.export_model()) + + ss_plls.solve() + print(ss_plls.solution.print_solution()) + + cfg = ss_plls.get_config(config, cnv, in_clock) + pprint(cfg) + + if cfg["type"] == "cpll": + pll_out = (in_clock * cfg["n1"] * cfg["n2"]) / cfg["m"] + lane_rate = pll_out * 2 / cfg["d"] + else: + pll_out = in_clock * cfg["n"] / (cfg["m"] * 2) + lane_rate = pll_out * 2 / cfg["d"] + + assert lane_rate == cnv.bit_clock + + assert cfg["type"] == pll + + +@pytest.mark.parametrize( + "pll, out_clock", + [("cpll", 0.5e9), ("qpll", 14e9), ("qpll", 32e9), ("qpll1", 9e9)], # No frac mode +) +def test_us_pll(pll, out_clock): + us_plls = us.UltraScalePlus(transceiver_type="GTYE4", solver="CPLEX") + us_plls.plls["QPLL"].force_integer_mode = True + us_plls.plls["QPLL1"].force_integer_mode = True + + in_clock = int(100e6) + out_clock = int(out_clock) + cnv = jif.ad9680() + + # Change sample rate to get to the desired bit clock + # sample_rate * cnv.Np * cnv.M * (cnv.encoding_d / cnv.encoding_n) / cnv.L = bit_clock + sample_rate = ( + out_clock * cnv.L / (cnv.Np * cnv.M * (cnv.encoding_d / cnv.encoding_n)) + ) + cnv.sample_clock = sample_rate + + assert cnv.bit_clock == out_clock + + cfg = {} + config = us_plls.add_constraints(cfg, in_clock, cnv) + + print(us_plls.model.export_model()) + + us_plls.solve() + print(us_plls.solution.print_solution()) + + cfg = us_plls.get_config(config, cnv, in_clock) + pprint(cfg) + + if cfg["type"] == "cpll": + pll_out = (in_clock * cfg["n1"] * cfg["n2"]) / cfg["m"] + lane_rate = pll_out * 2 / cfg["d"] + else: + pll_out = in_clock * cfg["n"] / (cfg["m"] * cfg["clkout_rate"]) + lane_rate = pll_out * 2 / cfg["d"] + + assert lane_rate == cnv.bit_clock + + assert cfg["type"] == pll From 1620c8a6c68dbb1cbb302cc8248d4a15becf485e Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Fri, 6 Dec 2024 16:01:11 -0700 Subject: [PATCH 04/17] Pass more arguments from fpga model to trx models Signed-off-by: Travis F. Collins --- adijif/fpgas/xilinx/__init__.py | 47 +++++++++++++++++---------- adijif/fpgas/xilinx/pll.py | 17 ++++++++-- adijif/fpgas/xilinx/sevenseries.py | 28 ++++++++++------ adijif/fpgas/xilinx/ultrascaleplus.py | 12 ++++--- adijif/plls/adf4371.py | 1 + adijif/solvers.py | 3 +- adijif/utils.py | 4 +-- 7 files changed, 74 insertions(+), 38 deletions(-) diff --git a/adijif/fpgas/xilinx/__init__.py b/adijif/fpgas/xilinx/__init__.py index 1857bcd..c5c5258 100644 --- a/adijif/fpgas/xilinx/__init__.py +++ b/adijif/fpgas/xilinx/__init__.py @@ -2,13 +2,14 @@ from typing import Dict, List, Optional, Union from adijif.converters.converter import converter as conv -from .sevenseries import SevenSeries as SSTransceiver -from .ultrascaleplus import UltraScalePlus as USPTransceiver -from .bf import xilinx_bf from adijif.solvers import CpoSolveResult # type: ignore from adijif.solvers import integer_var # type: ignore from adijif.solvers import CpoIntVar, GK_Intermediate, GK_Operators, GKVariable +from .bf import xilinx_bf +from .sevenseries import SevenSeries as SSTransceiver +from .ultrascaleplus import UltraScalePlus as USPTransceiver + class xilinx(xilinx_bf): """Xilinx FPGA clocking model. @@ -92,20 +93,19 @@ def trx_variant(self): print(trxt) assert len(trxt) == 3 return trxt - + def fpga_generation(self): """Get FPGA generation 7000, Ultrascale, Ultrascale+... based on transceiver type""" if self.trx_gen() == 2: - return '7000' + return "7000" elif self.trx_gen() == 3: - return 'Ultrascale' + return "Ultrascale" elif self.trx_gen() == 4: - return 'Ultrascale+' + return "Ultrascale+" elif self.trx_gen() == 5: - return 'Versal' + return "Versal" raise Exception(f"Unknown transceiver generation {self.trx_gen()}") - sys_clk_selections = [ "XCVR_CPLL", "XCVR_QPLL0", @@ -163,7 +163,7 @@ def fpga_generation(self): requires_core_clock_from_device_clock = False configs = [] # type: ignore - _transceiver_models = {} # type: ignore + _transceiver_models = {} # type: ignore @property def ref_clock_constraint(self) -> str: @@ -268,7 +268,7 @@ def _ref_clock_max(self) -> int: """ # https://www.xilinx.com/support/documentation/data_sheets/ds191-XC7Z030-XC7Z045-data-sheet.pdf # noqa: B950 if self.transceiver_type == "GTXE2": - if self.speed_grade == "-3E": + if str(self.speed_grade) == "-3E": return 700000000 else: return 670000000 @@ -297,7 +297,6 @@ def _ref_clock_min(self) -> int: ) # raise Exception(f"Unknown transceiver type {self.transceiver_type}") - def setup_by_dev_kit_name(self, name: str) -> None: """Configure object based on board name. Ex: zc706, zcu102. @@ -429,7 +428,9 @@ def get_config( print("Continued") continue - pll_config = self._transceiver_models[converter.name].get_config(config, converter, fpga_ref) + pll_config = self._transceiver_models[converter.name].get_config( + config, converter, fpga_ref + ) cpll = self._get_val(config[converter.name + "_use_cpll"]) qpll = self._get_val(config[converter.name + "_use_qpll"]) if converter.name + "_use_qpll1" in config.keys(): @@ -693,18 +694,28 @@ def _setup_quad_tile( # Add transceiver config = {} if self.fpga_generation() == "7000": - self._transceiver_models[converter.name] = SSTransceiver(parent=self, transceiver_type=self.transceiver_type) + self._transceiver_models[converter.name] = SSTransceiver( + parent=self, + transceiver_type=self.transceiver_type, + speed_grade=self.speed_grade, + ) elif self.fpga_generation() in ["Ultrascale", "Ultrascale+"]: - self._transceiver_models[converter.name] = USPTransceiver(parent=self, transceiver_type=self.transceiver_type) + self._transceiver_models[converter.name] = USPTransceiver( + parent=self, + transceiver_type=self.transceiver_type, + speed_grade=self.speed_grade, + ) else: raise Exception(f"Unsupported FPGA generation {self.fpga_generation()}") - + self._transceiver_models[converter.name].force_cpll = self.force_cpll self._transceiver_models[converter.name].force_qpll = self.force_qpll - if hasattr(self._transceiver_models[converter.name], 'force_qpll1'): + if hasattr(self._transceiver_models[converter.name], "force_qpll1"): self._transceiver_models[converter.name].force_qpll1 = self.force_qpll1 - config = self._transceiver_models[converter.name].add_constraints(config, fpga_ref, converter) + config = self._transceiver_models[converter.name].add_constraints( + config, fpga_ref, converter + ) # Add constraints for link clock # - Must be lanerate/40 204B or lanerate/66 204C diff --git a/adijif/fpgas/xilinx/pll.py b/adijif/fpgas/xilinx/pll.py index d35f91c..70c39a6 100644 --- a/adijif/fpgas/xilinx/pll.py +++ b/adijif/fpgas/xilinx/pll.py @@ -3,16 +3,19 @@ from docplex.cp.solution import CpoSolveResult # type: ignore +from ...common import core from ...gekko_trans import gekko_translation -class XilinxPLL: +class XilinxPLL(core, gekko_translation): plls = None parent = None _model = None # Hold internal model when used standalone _solution = None # Hold internal solution when used standalone - def __init__(self, parent=None, speed_grade="-2", transceiver_type="GTXE2", *args, **kwargs) -> None: + def __init__( + self, parent=None, speed_grade="-2", transceiver_type="GTXE2", *args, **kwargs + ) -> None: """Initalize 7 series transceiver PLLs. Args: @@ -20,9 +23,16 @@ def __init__(self, parent=None, speed_grade="-2", transceiver_type="GTXE2", *arg transceiver_type (str, optional): Transceiver type. Defaults to "GTXE2". """ self.transceiver_type = transceiver_type + self.speed_grade = speed_grade super().__init__(*args, **kwargs) self.parent = parent + if parent: + self._model = parent.model + self._solution = parent.solution + self.solver = parent.solver self.add_plls() + if self.solver == "gekko": + raise Exception("Gekko solver not supported for Xilinx PLLs") @property def model(self): @@ -60,12 +70,13 @@ def transceiver_type(self, val): self._transceiver_type = val _speed_grade = -2 + @property def speed_grade(self): if self.parent: return self.parent.speed_grade return self._speed_grade - + @speed_grade.setter def speed_grade(self, val): if self.parent: diff --git a/adijif/fpgas/xilinx/sevenseries.py b/adijif/fpgas/xilinx/sevenseries.py index 40328e0..54687e1 100644 --- a/adijif/fpgas/xilinx/sevenseries.py +++ b/adijif/fpgas/xilinx/sevenseries.py @@ -1,11 +1,11 @@ from docplex.cp.modeler import if_then -from .pll import XilinxPLL, PLLCommon -from ...gekko_trans import gekko_translation from ...common import core +from ...gekko_trans import gekko_translation +from .pll import PLLCommon, XilinxPLL -class SevenSeries(XilinxPLL, core, gekko_translation): +class SevenSeries(XilinxPLL): # References # GTXs # https://docs.amd.com/v/u/en-US/ug476_7Series_Transceivers @@ -263,21 +263,29 @@ def D(self, value): def vco_min(self): if self.parent.transceiver_type[:3] == "GTH": return 8000000000 - elif self.parent.transceiver_type[:3] == "GTX": # FIXME: This is only the lower band + elif ( + self.parent.transceiver_type[:3] == "GTX" + ): # FIXME: This is only the lower band return 5930000000 - raise Exception(f"Unknown vco_min for transceiver type {self.parent.transceiver_type}") - + raise Exception( + f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" + ) + @property def vco_max(self): if self.parent.transceiver_type[:3] == "GTH": - if self.parent.speed_grade >= -2: + if float(str(self.parent.speed_grade)[:2]) >= -2: return 10312500000 return 13100000000 - elif self.parent.transceiver_type[:3] == "GTX": # FIXME: This is only the lower band - if self.parent.speed_grade >= -2: + elif ( + self.parent.transceiver_type[:3] == "GTX" + ): # FIXME: This is only the lower band + if float(str(self.parent.speed_grade)[:2]) >= -2: return 10312500000 return 12500000000 - raise Exception(f"Unknown vco_max for transceiver type {self.parent.transceiver_type}") + raise Exception( + f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" + ) def add_constraints(self, config: dict, fpga_ref, converter) -> dict: """Add constraints for QPLL for 7 series FPGAs. diff --git a/adijif/fpgas/xilinx/ultrascaleplus.py b/adijif/fpgas/xilinx/ultrascaleplus.py index 3d6b163..8430f48 100644 --- a/adijif/fpgas/xilinx/ultrascaleplus.py +++ b/adijif/fpgas/xilinx/ultrascaleplus.py @@ -2,11 +2,11 @@ from docplex.cp.modeler import if_then -from .pll import XilinxPLL, PLLCommon +from ...common import core +from ...gekko_trans import gekko_translation +from .pll import PLLCommon, XilinxPLL from .sevenseries import CPLL as SevenSeriesCPLL from .sevenseries import QPLL as SevenSeriesQPLL -from ...gekko_trans import gekko_translation -from ...common import core class UltraScalePlus(XilinxPLL, core, gekko_translation): @@ -239,7 +239,11 @@ def get_config(self, config: dict, converter, fpga_ref) -> dict: pll_config["vco"] = self.solution.get_kpis()[converter.name + f"_vco_{pname}"] # Check - pll_out = fpga_ref * pll_config["n_dot_frac"] / (pll_config["m"] * pll_config["clkout_rate"]) + pll_out = ( + fpga_ref + * pll_config["n_dot_frac"] + / (pll_config["m"] * pll_config["clkout_rate"]) + ) lane_rate = pll_out * 2 / pll_config["d"] assert lane_rate == converter.bit_clock, f"{lane_rate} != {converter.bit_clock}" diff --git a/adijif/plls/adf4371.py b/adijif/plls/adf4371.py index 7bdd100..9074fb6 100644 --- a/adijif/plls/adf4371.py +++ b/adijif/plls/adf4371.py @@ -6,6 +6,7 @@ from adijif.plls.pll import pll from adijif.solvers import CpoExpr, GK_Intermediate, integer_var, tround + class adf4371(pll): """ADF4371 PLL model. diff --git a/adijif/solvers.py b/adijif/solvers.py index 497a72b..667837d 100644 --- a/adijif/solvers.py +++ b/adijif/solvers.py @@ -3,6 +3,7 @@ """Common solver API management layer.""" from typing import Union + try: from docplex.cp.expression import CpoExpr # type: ignore from docplex.cp.expression import CpoFunctionCall # type: ignore @@ -44,7 +45,7 @@ ) -def tround(value: float, tol: float = 1e-6) -> Union[float,int]: +def tround(value: float, tol: float = 1e-6) -> Union[float, int]: """Round if expected to have computational noise.""" if value.is_integer(): return int(value) diff --git a/adijif/utils.py b/adijif/utils.py index e5115a8..f9647de 100644 --- a/adijif/utils.py +++ b/adijif/utils.py @@ -3,10 +3,10 @@ import numpy as np -from adijif.converters.converter import converter -from adijif.fpgas.fpga import fpga import adijif.fpgas.xilinx.sevenseries as xp import adijif.fpgas.xilinx.ultrascaleplus as us +from adijif.converters.converter import converter +from adijif.fpgas.fpga import fpga def get_jesd_mode_from_params(conv: converter, **kwargs: int) -> List[dict]: From 552c84fdb222ae3c1c6cdfaba7be583ebdd7c09c Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Fri, 6 Dec 2024 16:02:14 -0700 Subject: [PATCH 05/17] Disable unsupported tests and new results with PLL models Signed-off-by: Travis F. Collins --- tests/test_adrv9009.py | 8 +++++++- tests/test_xilinx_pll.py | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/test_adrv9009.py b/tests/test_adrv9009.py index fad4634..3c9de61 100644 --- a/tests/test_adrv9009.py +++ b/tests/test_adrv9009.py @@ -3,6 +3,7 @@ import pytest import adijif + from .common import skip_solver @@ -10,6 +11,9 @@ @pytest.mark.parametrize("converter", ["adrv9009_rx", "adrv9009_tx"]) def test_adrv9009_rxtx_ad9528_solver_compact(solver, converter): skip_solver(solver) + if solver == "gekko": + pytest.xfail("gekko currently unsupported") + vcxo = 122.88e6 sys = adijif.system(converter, "ad9528", "xilinx", vcxo, solver=solver) @@ -122,7 +126,9 @@ def test_adrv9009_ad9528_solver_compact(solver): ref = { "gekko": {"clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 8, 256]}}, - "CPLEX": {"clock": {"r1": 1, "n2": 6, "m1": 5, "out_dividers": [1, 6, 192]}}, + "CPLEX": { + "clock": {"r1": 1, "n2": 6, "m1": 5, "out_dividers": [6, 48, 192, 256]} + }, } assert cfg["clock"]["r1"] == ref[solver]["clock"]["r1"] diff --git a/tests/test_xilinx_pll.py b/tests/test_xilinx_pll.py index 8accb72..3980836 100644 --- a/tests/test_xilinx_pll.py +++ b/tests/test_xilinx_pll.py @@ -1,6 +1,7 @@ -import pytest from pprint import pprint +import pytest + import adijif as jif import adijif.fpgas.xilinx.sevenseries as xp import adijif.fpgas.xilinx.ultrascaleplus as us From c61752b6af10504e1c5dbb5d88ea130f81d8de9d Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Fri, 6 Dec 2024 16:09:12 -0700 Subject: [PATCH 06/17] Disable old tests which do not support new PLL models Signed-off-by: Travis F. Collins --- tests/test_bf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_bf.py b/tests/test_bf.py index 3bedc09..0c0cfe4 100644 --- a/tests/test_bf.py +++ b/tests/test_bf.py @@ -513,6 +513,7 @@ def test_daq2_fpga_qpll_rxtx_zc706_config(): assert info == ref +@pytest.mark.skip(reason="Deprecated due to new transceiver models") def test_system_daq2_rx_ad9528(): vcxo = 125000000 @@ -1098,6 +1099,7 @@ def test_system_daq2_rx_hmc7044(): assert clks == ref +@pytest.mark.skip(reason="Deprecated due to new transceiver models") def test_system_daq2_rx(): vcxo = 125000000 From 150d1fd530216d3a4150f666a1c9d69de1cdbcce Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Wed, 11 Dec 2024 14:20:40 -0700 Subject: [PATCH 07/17] Fix force pll APIs Signed-off-by: Travis F. Collins --- adijif/fpgas/xilinx/__init__.py | 37 ++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/adijif/fpgas/xilinx/__init__.py b/adijif/fpgas/xilinx/__init__.py index c5c5258..6ae13ad 100644 --- a/adijif/fpgas/xilinx/__init__.py +++ b/adijif/fpgas/xilinx/__init__.py @@ -129,18 +129,18 @@ def fpga_generation(self): ] """ Force use of QPLL for transceiver source """ - force_qpll = 0 + force_qpll = False """ Force use of QPLL1 for transceiver source (GTHE3,GTHE4,GTYE4)""" - force_qpll1 = 0 + force_qpll1 = False """ Force use of CPLL for transceiver source """ - force_cpll = 0 + force_cpll = False """ Force all transceiver sources to be from a single PLL quad. This will try to leverage the output dividers of the PLLs """ - force_single_quad_tile = 0 + force_single_quad_tile = False """ Request that clock chip generated device clock device clock == LMFC/40 @@ -708,10 +708,33 @@ def _setup_quad_tile( else: raise Exception(f"Unsupported FPGA generation {self.fpga_generation()}") - self._transceiver_models[converter.name].force_cpll = self.force_cpll - self._transceiver_models[converter.name].force_qpll = self.force_qpll + # Handle force PLLs for nested devices and multiple converters + force_cpll = False + force_qpll = False + force_qpll1 = False + + if isinstance(self.force_cpll, dict): + if converter in self.force_cpll: + force_cpll = self.force_cpll[converter] + else: + force_cpll = self.force_cpll + + if isinstance(self.force_qpll, dict): + if converter in self.force_qpll: + force_qpll = self.force_qpll[converter] + else: + force_qpll = self.force_qpll + + if isinstance(self.force_qpll1, dict): + if converter in self.force_qpll1: + force_qpll1 = self.force_qpll1[converter] + else: + force_qpll1 = self.force_qpll1 + + self._transceiver_models[converter.name].force_cpll = force_cpll + self._transceiver_models[converter.name].force_qpll = force_qpll if hasattr(self._transceiver_models[converter.name], "force_qpll1"): - self._transceiver_models[converter.name].force_qpll1 = self.force_qpll1 + self._transceiver_models[converter.name].force_qpll1 = force_qpll1 config = self._transceiver_models[converter.name].add_constraints( config, fpga_ref, converter From 8c508a6a008791908e1a65705e7e30079ff80d65 Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Wed, 11 Dec 2024 14:44:03 -0700 Subject: [PATCH 08/17] Fix test type cast issue Signed-off-by: Travis F. Collins --- tests/test_adf4371.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_adf4371.py b/tests/test_adf4371.py index 36c34d1..bb03002 100644 --- a/tests/test_adf4371.py +++ b/tests/test_adf4371.py @@ -87,7 +87,9 @@ def test_adf4371_ad9081_sys_example(): # pprint.pprint(cfg) - assert cfg["pll_adf4371"]["rf_out_frequency"] == sys.converter.dac.converter_clock + assert float(cfg["pll_adf4371"]["rf_out_frequency"]) == float( + sys.converter.dac.converter_clock + ) @pytest.mark.parametrize( From 0515d89d687724cebeab4ca7d12dcc879e88f11c Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Wed, 11 Dec 2024 14:50:13 -0700 Subject: [PATCH 09/17] Fix test type cast issue Signed-off-by: Travis F. Collins --- tests/test_adf4371.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_adf4371.py b/tests/test_adf4371.py index bb03002..4c022a0 100644 --- a/tests/test_adf4371.py +++ b/tests/test_adf4371.py @@ -89,7 +89,7 @@ def test_adf4371_ad9081_sys_example(): assert float(cfg["pll_adf4371"]["rf_out_frequency"]) == float( sys.converter.dac.converter_clock - ) + ), f"{cfg['pll_adf4371']['rf_out_frequency']} != {sys.converter.dac.converter_clock}" @pytest.mark.parametrize( From 47ca1fa84a8205b341822ab826923b9395377e33 Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Wed, 11 Dec 2024 14:52:08 -0700 Subject: [PATCH 10/17] Fix test type cast issue Signed-off-by: Travis F. Collins --- tests/test_adf4371.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_adf4371.py b/tests/test_adf4371.py index 4c022a0..3321987 100644 --- a/tests/test_adf4371.py +++ b/tests/test_adf4371.py @@ -85,7 +85,8 @@ def test_adf4371_ad9081_sys_example(): cfg = sys.solve() - # pprint.pprint(cfg) + pprint.pprint(cfg) + print(sys.converter.dac.converter_clock) assert float(cfg["pll_adf4371"]["rf_out_frequency"]) == float( sys.converter.dac.converter_clock From 9ac9865f5691bed744cd5cbee3845ad4d020125b Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Wed, 11 Dec 2024 14:59:43 -0700 Subject: [PATCH 11/17] Fix rounding helper Signed-off-by: Travis F. Collins --- adijif/solvers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adijif/solvers.py b/adijif/solvers.py index 667837d..74d3f04 100644 --- a/adijif/solvers.py +++ b/adijif/solvers.py @@ -51,3 +51,4 @@ def tround(value: float, tol: float = 1e-6) -> Union[float, int]: return int(value) if abs(value - round(value)) < tol: return round(value) + return value From e2f24d404a2fea39270f42ee8b00f801669244d9 Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Wed, 11 Dec 2024 15:02:59 -0700 Subject: [PATCH 12/17] Change numerical noise threshold Signed-off-by: Travis F. Collins --- adijif/solvers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adijif/solvers.py b/adijif/solvers.py index 74d3f04..cf3acd3 100644 --- a/adijif/solvers.py +++ b/adijif/solvers.py @@ -45,7 +45,7 @@ ) -def tround(value: float, tol: float = 1e-6) -> Union[float, int]: +def tround(value: float, tol: float = 1e-4) -> Union[float, int]: """Round if expected to have computational noise.""" if value.is_integer(): return int(value) From 5abd5678535f4e109d301ac6fdcfb02901047144 Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Wed, 11 Dec 2024 15:09:32 -0700 Subject: [PATCH 13/17] Fix formatting issues Signed-off-by: Travis F. Collins --- adijif/cli.py | 1 + adijif/clocks/__init__.py | 1 + adijif/clocks/ad9523.py | 1 + adijif/clocks/ad9528.py | 1 + adijif/clocks/clock.py | 1 + adijif/clocks/hmc7044.py | 1 + adijif/clocks/ltc6952.py | 1 + adijif/clocks/ltc6953.py | 1 + adijif/common.py | 1 + adijif/converters/__init__.py | 1 + adijif/converters/ad9081.py | 1 + adijif/converters/ad9081_util.py | 1 + adijif/converters/ad9144.py | 1 + adijif/converters/ad9680.py | 1 + adijif/converters/adc.py | 1 + adijif/converters/adrv9009.py | 1 + adijif/converters/adrv9009_util.py | 13 +++++++---- adijif/converters/converter.py | 1 + adijif/converters/dac.py | 1 + adijif/draw.py | 1 + adijif/fpgas/fpga.py | 1 + adijif/fpgas/xilinx/__init__.py | 1 + adijif/gekko_trans.py | 1 + adijif/jesd.py | 1 + adijif/plls/adf4371.py | 1 + adijif/plls/pll.py | 1 + adijif/system.py | 37 +++++++++++++++--------------- adijif/utils.py | 1 + 28 files changed, 53 insertions(+), 23 deletions(-) diff --git a/adijif/cli.py b/adijif/cli.py index 7dbeaed..c2337ab 100644 --- a/adijif/cli.py +++ b/adijif/cli.py @@ -1,4 +1,5 @@ """Console script for adijif.""" + import sys from typing import List diff --git a/adijif/clocks/__init__.py b/adijif/clocks/__init__.py index 3858848..290434e 100644 --- a/adijif/clocks/__init__.py +++ b/adijif/clocks/__init__.py @@ -1,2 +1,3 @@ """ADI JIF clock chip models.""" + supported_parts = ["ad9523_1", "ad9528", "ad9545", "hmc7044", "ltc6952", "ltc6953"] diff --git a/adijif/clocks/ad9523.py b/adijif/clocks/ad9523.py index 2dd0d58..14ac290 100644 --- a/adijif/clocks/ad9523.py +++ b/adijif/clocks/ad9523.py @@ -1,4 +1,5 @@ """AD9523-1 clock chip model.""" + from typing import Dict, List, Union from adijif.clocks.ad9523_1_bf import ad9523_1_bf diff --git a/adijif/clocks/ad9528.py b/adijif/clocks/ad9528.py index 304d203..d5f05ca 100644 --- a/adijif/clocks/ad9528.py +++ b/adijif/clocks/ad9528.py @@ -1,4 +1,5 @@ """AD9528 clock chip model.""" + from typing import Dict, List, Union from adijif.clocks.ad9528_bf import ad9528_bf diff --git a/adijif/clocks/clock.py b/adijif/clocks/clock.py index 38dfdda..0286eb7 100644 --- a/adijif/clocks/clock.py +++ b/adijif/clocks/clock.py @@ -1,4 +1,5 @@ """Clock parent metaclass to maintain consistency for all clock chip.""" + from abc import ABCMeta, abstractmethod from typing import Dict, List, Union diff --git a/adijif/clocks/hmc7044.py b/adijif/clocks/hmc7044.py index cab6fb7..312ad32 100644 --- a/adijif/clocks/hmc7044.py +++ b/adijif/clocks/hmc7044.py @@ -1,4 +1,5 @@ """HMC7044 clock chip model.""" + from typing import Dict, List, Union from adijif.clocks.hmc7044_bf import hmc7044_bf diff --git a/adijif/clocks/ltc6952.py b/adijif/clocks/ltc6952.py index 200673b..0a8ac95 100644 --- a/adijif/clocks/ltc6952.py +++ b/adijif/clocks/ltc6952.py @@ -1,4 +1,5 @@ """LTC6952 clock chip model.""" + from typing import Dict, List, Union from docplex.cp.solution import CpoSolveResult # type: ignore diff --git a/adijif/clocks/ltc6953.py b/adijif/clocks/ltc6953.py index 18e5887..59649d5 100644 --- a/adijif/clocks/ltc6953.py +++ b/adijif/clocks/ltc6953.py @@ -1,4 +1,5 @@ """LTC6953 clock chip model.""" + from typing import Dict, List, Union from docplex.cp.solution import CpoSolveResult # type: ignore diff --git a/adijif/common.py b/adijif/common.py index a1e8f0b..5c36e79 100644 --- a/adijif/common.py +++ b/adijif/common.py @@ -1,4 +1,5 @@ """Common class for all JIF components.""" + from typing import List, Union from adijif.solvers import CpoModel # noqa: BLK100 diff --git a/adijif/converters/__init__.py b/adijif/converters/__init__.py index e0625a0..5dab42e 100644 --- a/adijif/converters/__init__.py +++ b/adijif/converters/__init__.py @@ -1,2 +1,3 @@ """ADI JIF converter models.""" + supported_parts = ["ad9680", "adrv9009", "ad9081_rx", "ad9144"] diff --git a/adijif/converters/ad9081.py b/adijif/converters/ad9081.py index 8a6bd49..0ff0d73 100644 --- a/adijif/converters/ad9081.py +++ b/adijif/converters/ad9081.py @@ -1,4 +1,5 @@ """AD9081 high speed MxFE clocking model.""" + from abc import ABCMeta, abstractmethod from typing import Any, Dict, List, Union diff --git a/adijif/converters/ad9081_util.py b/adijif/converters/ad9081_util.py index f4413aa..e239875 100644 --- a/adijif/converters/ad9081_util.py +++ b/adijif/converters/ad9081_util.py @@ -1,4 +1,5 @@ """AD9081 MxFE Utility Functions.""" + import csv import os from typing import Dict, Union diff --git a/adijif/converters/ad9144.py b/adijif/converters/ad9144.py index b3ab159..0db3f55 100644 --- a/adijif/converters/ad9144.py +++ b/adijif/converters/ad9144.py @@ -1,4 +1,5 @@ """AD9144 high speed DAC clocking model.""" + from typing import Dict, List, Union import numpy as np diff --git a/adijif/converters/ad9680.py b/adijif/converters/ad9680.py index b1db1b2..2f8cb8d 100644 --- a/adijif/converters/ad9680.py +++ b/adijif/converters/ad9680.py @@ -1,4 +1,5 @@ """AD9680 high speed ADC clocking model.""" + from typing import Any, Dict, List, Union from adijif.converters.ad9680_bf import ad9680_bf diff --git a/adijif/converters/adc.py b/adijif/converters/adc.py index 0dba105..aa3e2e5 100644 --- a/adijif/converters/adc.py +++ b/adijif/converters/adc.py @@ -1,4 +1,5 @@ """Converter base meta class for all converter clocking models.""" + from abc import ABCMeta, abstractmethod from typing import List, Union diff --git a/adijif/converters/adrv9009.py b/adijif/converters/adrv9009.py index 06710ff..e3022a8 100644 --- a/adijif/converters/adrv9009.py +++ b/adijif/converters/adrv9009.py @@ -1,4 +1,5 @@ """ADRV9009 transceiver clocking model.""" + from abc import ABCMeta from typing import Dict, List, Union diff --git a/adijif/converters/adrv9009_util.py b/adijif/converters/adrv9009_util.py index 0209c7d..9cdf1b6 100644 --- a/adijif/converters/adrv9009_util.py +++ b/adijif/converters/adrv9009_util.py @@ -1,4 +1,5 @@ """ADRV9009 Utility Functions.""" + from typing import Dict, Union from adijif.converters.converter import converter @@ -15,11 +16,13 @@ def _convert_to_config( "M": M, "F": Np / 8 * M * S / L, "S": S, - "HD": 1 - if (M == 1 and L == 2 and S == 1) - or (M == 2 and L == 4 and S == 1) - or (M == 1 and L == 4 and S == 2) - else 0, + "HD": ( + 1 + if (M == 1 and L == 2 and S == 1) + or (M == 2 and L == 4 and S == 1) + or (M == 1 and L == 4 and S == 2) + else 0 + ), "Np": Np, "N": Np, "CS": 0, diff --git a/adijif/converters/converter.py b/adijif/converters/converter.py index aa7e55a..3478a1f 100644 --- a/adijif/converters/converter.py +++ b/adijif/converters/converter.py @@ -1,4 +1,5 @@ """Converter base meta class for all converter clocking models.""" + from abc import ABCMeta, abstractmethod from typing import Dict, List, Union diff --git a/adijif/converters/dac.py b/adijif/converters/dac.py index 3447ae0..b897360 100644 --- a/adijif/converters/dac.py +++ b/adijif/converters/dac.py @@ -1,4 +1,5 @@ """Converter base meta class for all converter clocking models.""" + from abc import ABCMeta, abstractmethod from typing import List, Union diff --git a/adijif/draw.py b/adijif/draw.py index 08e5068..a539de9 100644 --- a/adijif/draw.py +++ b/adijif/draw.py @@ -1,4 +1,5 @@ """Diagraming functions for different components.""" + from __future__ import annotations import os diff --git a/adijif/fpgas/fpga.py b/adijif/fpgas/fpga.py index 51276f7..76db192 100644 --- a/adijif/fpgas/fpga.py +++ b/adijif/fpgas/fpga.py @@ -1,4 +1,5 @@ """FPGA parent metaclass to maintain consistency for all FPGA classes.""" + from abc import ABCMeta, abstractmethod from typing import Dict, List, Optional, Union diff --git a/adijif/fpgas/xilinx/__init__.py b/adijif/fpgas/xilinx/__init__.py index 6ae13ad..2392fbc 100644 --- a/adijif/fpgas/xilinx/__init__.py +++ b/adijif/fpgas/xilinx/__init__.py @@ -1,4 +1,5 @@ """Xilinx FPGA clocking model.""" + from typing import Dict, List, Optional, Union from adijif.converters.converter import converter as conv diff --git a/adijif/gekko_trans.py b/adijif/gekko_trans.py index c3dda17..e4f81f3 100644 --- a/adijif/gekko_trans.py +++ b/adijif/gekko_trans.py @@ -1,4 +1,5 @@ """Translation methods for solvers and module.""" + from typing import List, Optional, Union import numpy as np diff --git a/adijif/jesd.py b/adijif/jesd.py index 8f0cb68..060abc9 100644 --- a/adijif/jesd.py +++ b/adijif/jesd.py @@ -1,4 +1,5 @@ """JESD parameterization definitions and helper functions.""" + from abc import ABCMeta, abstractmethod from typing import Dict, List, Union diff --git a/adijif/plls/adf4371.py b/adijif/plls/adf4371.py index 9074fb6..c5303a3 100644 --- a/adijif/plls/adf4371.py +++ b/adijif/plls/adf4371.py @@ -1,4 +1,5 @@ """ADF4371 Microwave Wideband Synthesizer with Integrated VCO model.""" + from typing import Dict, List, Union from docplex.cp.solution import CpoSolveResult # type: ignore diff --git a/adijif/plls/pll.py b/adijif/plls/pll.py index aa43b46..ac49487 100644 --- a/adijif/plls/pll.py +++ b/adijif/plls/pll.py @@ -1,4 +1,5 @@ """PLL parent metaclass to maintain consistency for all pll chips.""" + from abc import ABCMeta from typing import Union diff --git a/adijif/system.py b/adijif/system.py index f28c2e3..7249695 100644 --- a/adijif/system.py +++ b/adijif/system.py @@ -1,4 +1,5 @@ """System level interface for manage clocks across all devices.""" + import os import shutil # noqa: F401 from typing import Dict, List, Tuple, Union @@ -388,10 +389,10 @@ def solve(self) -> Dict: # Give clock chip output as PLL's reference pll._setup(config[conv.name + "_ref_clk"]) # Give PLL's output as converter's reference - config[ - conv.name + "_ref_clk_from_ext_pll" - ] = pll._get_clock_constraint( - conv.name + "_ref_clk_from_ext_pll" + config[conv.name + "_ref_clk_from_ext_pll"] = ( + pll._get_clock_constraint( + conv.name + "_ref_clk_from_ext_pll" + ) ) self.clock._add_equation( config[conv.name + "_ref_clk_from_ext_pll"] == clks[0] @@ -418,32 +419,32 @@ def solve(self) -> Dict: # Ask clock chip for fpga ref if conv._nested: for name in names: - config[ - name + "_fpga_ref_clk" - ] = self.clock._get_clock_constraint("xilinx" + "_" + name) + config[name + "_fpga_ref_clk"] = ( + self.clock._get_clock_constraint("xilinx" + "_" + name) + ) clock_names.append(name + "_fpga_ref_clk") sys_refs.append(config[name + "_fpga_ref_clk"]) if need_separate_link_clock: - config[ - name + "_fpga_link_out_clk" - ] = self.clock._get_clock_constraint( - "xilinx" + "_" + name + "_link_out" + config[name + "_fpga_link_out_clk"] = ( + self.clock._get_clock_constraint( + "xilinx" + "_" + name + "_link_out" + ) ) clock_names.append(name + "_fpga_link_out_clk") else: - config[ - conv.name + "_fpga_ref_clk" - ] = self.clock._get_clock_constraint("xilinx" + "_" + conv.name) + config[conv.name + "_fpga_ref_clk"] = ( + self.clock._get_clock_constraint("xilinx" + "_" + conv.name) + ) clock_names.append(conv.name + "_fpga_ref_clk") sys_refs.append(config[conv.name + "_fpga_ref_clk"]) if need_separate_link_clock: - config[ - conv.name + "_fpga_link_out_clk" - ] = self.clock._get_clock_constraint( - "xilinx" + "_" + conv.name + "_link_out" + config[conv.name + "_fpga_link_out_clk"] = ( + self.clock._get_clock_constraint( + "xilinx" + "_" + conv.name + "_link_out" + ) ) clock_names.append(conv.name + "_fpga_link_out_clk") diff --git a/adijif/utils.py b/adijif/utils.py index f9647de..feb9baa 100644 --- a/adijif/utils.py +++ b/adijif/utils.py @@ -1,4 +1,5 @@ """Collection of utility scripts for specialized checks.""" + from typing import List, Optional import numpy as np From 2bec03a6ec819d91ac6ead645f194ceab09b2e4d Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Wed, 11 Dec 2024 15:49:08 -0700 Subject: [PATCH 14/17] Fix lint issues in US+ model Signed-off-by: Travis F. Collins --- adijif/clocks/__init__.py | 9 +- adijif/clocks/ad9523.py | 13 +- adijif/fpgas/xilinx/ultrascaleplus.py | 199 +++++++++++++++++++------- 3 files changed, 170 insertions(+), 51 deletions(-) diff --git a/adijif/clocks/__init__.py b/adijif/clocks/__init__.py index 290434e..9639af4 100644 --- a/adijif/clocks/__init__.py +++ b/adijif/clocks/__init__.py @@ -1,3 +1,10 @@ """ADI JIF clock chip models.""" -supported_parts = ["ad9523_1", "ad9528", "ad9545", "hmc7044", "ltc6952", "ltc6953"] +supported_parts = [ + "ad9523_1", + "ad9528", + "ad9545", + "hmc7044", + "ltc6952", + "ltc6953", +] diff --git a/adijif/clocks/ad9523.py b/adijif/clocks/ad9523.py index 14ac290..200e07b 100644 --- a/adijif/clocks/ad9523.py +++ b/adijif/clocks/ad9523.py @@ -25,7 +25,18 @@ class ad9523_1(ad9523_1_bf): # Defaults _m1: Union[List[int], int] = [3, 4, 5] _d: Union[List[int], int] = [*range(1, 1024)] - _n2: Union[List[int], int] = [12, 16, 17, 20, 21, 22, 24, 25, 26, *range(28, 255)] + _n2: Union[List[int], int] = [ + 12, + 16, + 17, + 20, + 21, + 22, + 24, + 25, + 26, + *range(28, 255), + ] _r2: Union[List[int], int] = list(range(1, 31 + 1)) # Limits diff --git a/adijif/fpgas/xilinx/ultrascaleplus.py b/adijif/fpgas/xilinx/ultrascaleplus.py index 8430f48..a205451 100644 --- a/adijif/fpgas/xilinx/ultrascaleplus.py +++ b/adijif/fpgas/xilinx/ultrascaleplus.py @@ -1,15 +1,21 @@ """Ultrascale+ PLLs transceiver models.""" +from typing import Union + from docplex.cp.modeler import if_then from ...common import core +from ...converters.converter import converter as conv from ...gekko_trans import gekko_translation -from .pll import PLLCommon, XilinxPLL +from ...solvers import CpoIntVar, GK_Intermediate, GK_Operators, GKVariable +from .pll import XilinxPLL from .sevenseries import CPLL as SevenSeriesCPLL from .sevenseries import QPLL as SevenSeriesQPLL class UltraScalePlus(XilinxPLL, core, gekko_translation): + """Ultrascale+ PLLs transceiver models.""" + # References # GTYs # https://docs.amd.com/v/u/en-US/ug578-ultrascale-gty-transceivers @@ -26,15 +32,21 @@ class UltraScalePlus(XilinxPLL, core, gekko_translation): def add_plls(self) -> None: """Add PLLs to the model.""" - self.plls = {"CPLL": CPLL(self), "QPLL": QPLL(self), "QPLL1": QPLL1(self)} - - def add_constraints(self, config, fpga_ref, converter) -> dict: + self.plls = { + "CPLL": CPLL(self), + "QPLL": QPLL(self), + "QPLL1": QPLL1(self), + } + + def add_constraints( + self, config: dict, fpga_ref: Union[int, float], converter: conv + ) -> dict: """Add constraints for PLLs. Args: config (dict): Configuration dictionary. - fpga_ref (int, var): FPGA reference clock. - converter (converter): Converter object. + fpga_ref (int, float): FPGA reference clock. + converter (conv): Converter object. Returns: dict: Updated configuration dictionary. @@ -53,7 +65,19 @@ def add_constraints(self, config, fpga_ref, converter) -> dict: ) return config - def get_config(self, config: dict, converter, fpga_ref) -> dict: + def get_config( + self, config: dict, converter: conv, fpga_ref: Union[int, float] + ) -> dict: + """Get the configuration of the PLLs. + + Args: + config (dict): Configuration dictionary. + converter (conv): Converter object. + fpga_ref (int, float): FPGA reference clock. + + Returns: + dict: Updated configuration dictionary. + """ if self.force_cpll or self.force_qpll or self.force_qpll1: if self.force_cpll: ecpll = 1 @@ -85,7 +109,8 @@ class CPLL(SevenSeriesCPLL): """CPLL model for Ultrascale+ transceivers.""" @property - def vco_min(self): + def vco_min(self) -> int: + """Get the VCO min frequency in Hz for CPLL.""" if self.parent.transceiver_type in ["GTHE4", "GTYE3", "GTYE4"]: return 2000000000 raise Exception( @@ -93,7 +118,8 @@ def vco_min(self): ) @property - def vco_max(self): + def vco_max(self) -> int: + """Get the VCO max frequency in Hz for CPLL.""" if self.parent.transceiver_type in ["GTHE4", "GTYE3", "GTYE4"]: return 6250000000 raise Exception( @@ -107,7 +133,8 @@ class QPLL(SevenSeriesQPLL): force_integer_mode = False @property - def vco_min(self): + def vco_min(self) -> int: + """Get the VCO min frequency in Hz for QPLL.""" if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE4"]: return 9800000000 raise Exception( @@ -115,7 +142,8 @@ def vco_min(self): ) @property - def vco_max(self): + def vco_max(self) -> int: + """Get the VCO max frequency in Hz for QPLL.""" if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE4"]: return 16375000000 raise Exception( @@ -127,10 +155,11 @@ def vco_max(self): D_available = [1, 2, 4, 8, 16] _D = [1, 2, 4, 8, 16] - # 32 not available in AC modes https://docs.amd.com/r/en-US/ds925-zynq-ultrascale-plus/GTY-Transceiver-Switching-Characteristics + # 32 not available in AC modes https://docs.amd.com/r/en-US/ds925-zynq-ultrascale-plus/GTY-Transceiver-Switching-Characteristics # type: ignore # noqa: B950 @property - def QPLL_CLKOUTRATE_available(self): + def QPLL_CLKOUTRATE_available(self) -> list[int]: + """Get the QPLL_CLKOUTRATE available values.""" if self.parent.transceiver_type == "GTHE4": return [1, 2] return [1] @@ -139,44 +168,77 @@ def QPLL_CLKOUTRATE_available(self): _QPLL_CLKOUTRATE_GTH = [1, 2] @property - def QPLL_CLKOUTRATE(self): + def QPLL_CLKOUTRATE(self) -> int: + """Get the QPLL_CLKOUTRATE value.""" if "GTH" in self.parent.transceiver_type: return self._QPLL_CLKOUTRATE_GTH return self._QPLL_CLKOUTRATE_GTY @QPLL_CLKOUTRATE.setter - def QPLL_CLKOUTRATE(self, val): - self._check_in_range(val, self.QPLL_CLKOUTRATE_available, "QPLL_CLKOUTRATE") + def QPLL_CLKOUTRATE(self, val: int) -> None: + """Set the QPLL_CLKOUTRATE. + + Args: + val (int): QPLL_CLKOUTRATE value. + + Raises: + ValueError: If QPLL_CLKOUTRATE is out of range. + """ + self._check_in_range( + val, self.QPLL_CLKOUTRATE_available, "QPLL_CLKOUTRATE" + ) if "GTH" in self.parent.transceiver_type: self._QPLL_CLKOUTRATE_GTH = val - return self._QPLL_CLKOUTRATE_GTY + raise ValueError( + f"QPLL_CLKOUTRATE not available for {self.parent.transceiver_type}" + ) SDMDATA_min_max = [0, 2**24 - 1] _SDMDATA_min = 0 @property - def SDMDATA_min(self): + def SDMDATA_min(self) -> int: + """Get the SDMDATA_min value.""" return self._SDMDATA_min @SDMDATA_min.setter - def SDMDATA_min(self, val): + def SDMDATA_min(self, val: int) -> None: + """Set the SDMDATA_min. + + Args: + val (int): SDMDATA_min value. + + Raises: + ValueError: If SDMDATA_min is out of range. + """ if val < self.SDMDATA_min_max[0] or val > self.SDMDATA_min_max[1]: raise ValueError( - f"SDMDATA_min must be between {self.SDMDATA_min_max[0]} and {self.SDMDATA_min_max[1]}" + f"SDMDATA_min must be between {self.SDMDATA_min_max[0]} and" + + f" {self.SDMDATA_min_max[1]}" ) self._SDMDATA_min = val _SDMDATA_max = 2**24 - 1 @property - def SDMDATA_max(self): + def SDMDATA_max(self) -> int: + """Get the SDMDATA_max value.""" return self._SDMDATA_max @SDMDATA_max.setter - def SDMDATA_max(self, val): + def SDMDATA_max(self, val: int) -> None: + """Set the SDMDATA_max. + + Args: + val (int): SDMDATA_max value. + + Raises: + ValueError: If SDMDATA_max is out of range. + """ if val < self.SDMDATA_min_max[0] or val > self.SDMDATA_min_max[1]: raise ValueError( - f"SDMDATA must be between {self.SDMDATA_min_max[0]} and {self.SDMDATA_min_max[1]}" + f"SDMDATA must be between {self.SDMDATA_min_max[0]} and" + + f" {self.SDMDATA_min_max[1]}" ) self._SDMDATA_max = val @@ -184,23 +246,31 @@ def SDMDATA_max(self, val): _SDMWIDTH = [16, 20, 24] @property - def SDMWIDTH(self): + def SDMWIDTH(self) -> int: + """Get the SDMWIDTH value.""" return self._SDMWIDTH @SDMWIDTH.setter - def SDMWIDTH(self, val): + def SDMWIDTH(self, val: int) -> None: + """Set the SDMWIDTH. + + Args: + val (int): SDMWIDTH value. + """ self._check_in_range(val, self.SDMWIDTH_available, "SDMWIDTH") self._SDMWIDTH = val _pname = "qpll" - def get_config(self, config: dict, converter, fpga_ref) -> dict: + def get_config( + self, config: dict, converter: conv, fpga_ref: Union[int, float] + ) -> dict: """Get the configuration of the QPLL. Args: config (dict): Configuration dictionary. - converter (converter): Converter object. - fpga_ref (int, var): FPGA reference clock. + converter (conv): Converter object. + fpga_ref (int, float): FPGA reference clock. Returns: dict: Updated configuration dictionary. @@ -214,17 +284,17 @@ def get_config(self, config: dict, converter, fpga_ref) -> dict: config[converter.name + f"_clkout_rate_{pname}"] ) if converter.bit_clock < 28.1e9 and not self.force_integer_mode: - sdm_data = self._get_val(config[converter.name + f"_sdm_data_{pname}"]) + sdm_data = self._get_val( + config[converter.name + f"_sdm_data_{pname}"] + ) if sdm_data > 0: pll_config["sdm_data"] = sdm_data pll_config["sdm_width"] = self._get_val( config[converter.name + f"_sdm_width_{pname}"] ) - # config['frac'] = self._get_val(config[converter.name + f"_frac_{pname}"]) pll_config["frac"] = self.solution.get_kpis()[ converter.name + f"_frac_{pname}" ] - # config['n_dot_frac'] = self._get_val(config[converter.name + f"_n_dot_frac_{pname}"]) pll_config["n_dot_frac"] = self.solution.get_kpis()[ converter.name + f"_n_dot_frac_{pname}" ] @@ -236,7 +306,9 @@ def get_config(self, config: dict, converter, fpga_ref) -> dict: pll_config["n"] = pll_config["n_dot_frac"] # config['vco'] = self._get_val(config[converter.name + f"_vco_{pname}"]) - pll_config["vco"] = self.solution.get_kpis()[converter.name + f"_vco_{pname}"] + pll_config["vco"] = self.solution.get_kpis()[ + converter.name + f"_vco_{pname}" + ] # Check pll_out = ( @@ -245,11 +317,30 @@ def get_config(self, config: dict, converter, fpga_ref) -> dict: / (pll_config["m"] * pll_config["clkout_rate"]) ) lane_rate = pll_out * 2 / pll_config["d"] - assert lane_rate == converter.bit_clock, f"{lane_rate} != {converter.bit_clock}" + assert ( + lane_rate == converter.bit_clock + ), f"{lane_rate} != {converter.bit_clock}" return pll_config - def add_constraints(self, config, fpga_ref, converter): + def add_constraints( + self, + config: dict, + fpga_ref: Union[ + int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar + ], + converter: conv, + ) -> dict: + """Add constraints for the Transceiver. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int, CpoIntVar): FPGA reference clock. + converter (conv): Converter object. + + Returns: + dict: Updated configuration dictionary. + """ pname = self._pname # Global flag to use QPLLn @@ -287,16 +378,22 @@ def add_constraints(self, config, fpga_ref, converter): # Frac part if converter.bit_clock < 28.1e9 and not self.force_integer_mode: - config[converter.name + f"_sdm_data_{pname}"] = self.model.integer_var( - min=self.SDMDATA_min, - max=self.SDMDATA_max, - name=converter.name + f"_sdm_data_{pname}", + config[converter.name + f"_sdm_data_{pname}"] = ( + self.model.integer_var( + min=self.SDMDATA_min, + max=self.SDMDATA_max, + name=converter.name + f"_sdm_data_{pname}", + ) ) - config[converter.name + f"_sdm_width_{pname}"] = self._convert_input( - self.SDMWIDTH, converter.name + f"_sdm_width_{pname}" + config[converter.name + f"_sdm_width_{pname}"] = ( + self._convert_input( + self.SDMWIDTH, converter.name + f"_sdm_width_{pname}" + ) ) - config[converter.name + f"_HIGH_RATE_{pname}"] = self._convert_input( - self.QPLL_CLKOUTRATE, converter.name + f"_HIGH_RATE_{pname}" + config[converter.name + f"_HIGH_RATE_{pname}"] = ( + self._convert_input( + self.QPLL_CLKOUTRATE, converter.name + f"_HIGH_RATE_{pname}" + ) ) # Add intermediate variables @@ -310,17 +407,19 @@ def add_constraints(self, config, fpga_ref, converter): name=converter.name + f"_frac_{pname}", ) self._add_equation([config[converter.name + f"_frac_{pname}"] < 1]) - config[converter.name + f"_n_dot_frac_{pname}"] = self._add_intermediate( - config[converter.name + f"_n_{pname}"] - + config[converter.name + f"_frac_{pname}"] + config[converter.name + f"_n_dot_frac_{pname}"] = ( + self._add_intermediate( + config[converter.name + f"_n_{pname}"] + + config[converter.name + f"_frac_{pname}"] + ) ) self.model.add_kpi( config[converter.name + f"_n_dot_frac_{pname}"], name=converter.name + f"_n_dot_frac_{pname}", ) else: - config[converter.name + f"_n_dot_frac_{pname}"] = self._add_intermediate( - config[converter.name + f"_n_{pname}"] + config[converter.name + f"_n_dot_frac_{pname}"] = ( + self._add_intermediate(config[converter.name + f"_n_{pname}"]) ) config[converter.name + f"_pll_out_{pname}"] = self._add_intermediate( @@ -386,7 +485,8 @@ class QPLL1(QPLL): _pname = "qpll1" @property - def vco_min(self): + def vco_min(self) -> int: + """VCO min frequency in Hz for QPLL1.""" if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE3", "GTYE4"]: return 8000000000 raise Exception( @@ -394,7 +494,8 @@ def vco_min(self): ) @property - def vco_max(self): + def vco_max(self) -> int: + """VCO max frequency in Hz for QPLL1.""" if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE3", "GTYE4"]: return 13000000000 raise Exception( From 65b8b785ccded960b72862fd92d6bf6ffa9b8023 Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Thu, 12 Dec 2024 14:36:36 -0700 Subject: [PATCH 15/17] Fix remaining lint issues Signed-off-by: Travis F. Collins --- adijif/cli.py | 4 +- adijif/clocks/ad9523.py | 22 +- adijif/clocks/ad9523_1_bf.py | 3 +- adijif/clocks/ad9528.py | 23 +- adijif/clocks/ad9528_bf.py | 11 +- adijif/clocks/ad9545.py | 70 +- adijif/clocks/hmc7044.py | 30 +- adijif/clocks/ltc6952.py | 13 +- adijif/clocks/ltc6953.py | 12 +- adijif/common.py | 5 +- adijif/converters/ad9081.py | 68 +- adijif/converters/ad9081_dp.py | 24 +- adijif/converters/ad9144.py | 46 +- adijif/converters/ad9144_bf.py | 4 +- adijif/converters/ad9680.py | 29 +- adijif/converters/ad9680_bf.py | 4 +- adijif/converters/adrv9009.py | 5 +- adijif/converters/adrv9009_bf.py | 4 +- adijif/converters/converter.py | 8 +- adijif/converters/dac.py | 6 +- adijif/draw.py | 12 +- adijif/fpgas/xilinx/__init__.py | 122 ++- adijif/fpgas/xilinx/bf.py | 6 +- adijif/fpgas/xilinx/pll.py | 116 ++- adijif/fpgas/xilinx/sevenseries.py | 972 +++++++++++++----------- adijif/fpgas/xilinx/ultrascaleplus.py | 1006 ++++++++++++------------- adijif/gekko_trans.py | 14 +- adijif/jesd.py | 14 +- adijif/plls/adf4371.py | 47 +- adijif/system.py | 91 ++- adijif/types.py | 8 +- adijif/utils.py | 8 +- tests/common.py | 1 + tests/test_ad9081.py | 4 +- tests/test_adrv9009.py | 23 +- tests/test_bf.py | 72 +- tests/test_clocks.py | 12 +- tests/test_daq2.py | 13 +- tests/test_fpga.py | 4 +- tests/test_jesd.py | 4 +- tests/test_system.py | 4 +- tests/test_xilinx_pll.py | 8 +- 42 files changed, 1798 insertions(+), 1154 deletions(-) diff --git a/adijif/cli.py b/adijif/cli.py index c2337ab..d821174 100644 --- a/adijif/cli.py +++ b/adijif/cli.py @@ -13,7 +13,9 @@ def main(args: List[str] = None) -> None: Args: args (List[str]): List of input argument """ - click.echo("Replace this message by putting your code into " "adijif.cli.main") + click.echo( + "Replace this message by putting your code into " "adijif.cli.main" + ) click.echo("See click documentation at https://click.palletsprojects.com/") diff --git a/adijif/clocks/ad9523.py b/adijif/clocks/ad9523.py index 200e07b..2da97a9 100644 --- a/adijif/clocks/ad9523.py +++ b/adijif/clocks/ad9523.py @@ -166,7 +166,9 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: Exception: If solver is not called first """ if not self._clk_names: - raise Exception("set_requested_clocks must be called before get_config") + raise Exception( + "set_requested_clocks must be called before get_config" + ) for k in ["out_dividers", "m1", "n2", "r2"]: if k not in self.config.keys(): raise Exception("Missing key: " + str(k)) @@ -177,11 +179,15 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: "m1": self._get_val(self.config["m1"]), "n2": self._get_val(self.config["n2"]), "r2": self._get_val(self.config["r2"]), - "out_dividers": [self._get_val(x) for x in self.config["out_dividers"]], + "out_dividers": [ + self._get_val(x) for x in self.config["out_dividers"] + ], "output_clocks": [], } - config["vcxo"] = self._get_val(self.vcxo) # pytype: disable=attribute-error + config["vcxo"] = self._get_val( + self.vcxo + ) # pytype: disable=attribute-error vcxo = config["vcxo"] clk = vcxo / config["r2"] * config["n2"] / config["m1"] @@ -196,7 +202,9 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: return config - def _setup_solver_constraints(self, vcxo: Union[float, int, CpoIntVar]) -> None: + def _setup_solver_constraints( + self, vcxo: Union[float, int, CpoIntVar] + ) -> None: """Apply constraints to solver model. Args: @@ -273,7 +281,11 @@ def _get_clock_constraint( od = self._convert_input(self._d, "d_" + str(clk_name)) self.config["out_dividers"].append(od) return ( - self.vcxo / self.config["r2"] * self.config["n2"] / self.config["m1"] / od + self.vcxo + / self.config["r2"] + * self.config["n2"] + / self.config["m1"] + / od ) def set_requested_clocks( diff --git a/adijif/clocks/ad9523_1_bf.py b/adijif/clocks/ad9523_1_bf.py index b94f3c4..58416c2 100644 --- a/adijif/clocks/ad9523_1_bf.py +++ b/adijif/clocks/ad9523_1_bf.py @@ -30,7 +30,8 @@ def list_available_references(self, divider_set): "Input must be of type dict with fields: " + str(ref.keys()) ) return [ - divider_set["vco"] / divider_set["m1"] / div for div in self.d_available + divider_set["vco"] / divider_set["m1"] / div + for div in self.d_available ] def find_dividers(self, vcxo, required_output_rates, find=3): diff --git a/adijif/clocks/ad9528.py b/adijif/clocks/ad9528.py index d5f05ca..124e2f0 100644 --- a/adijif/clocks/ad9528.py +++ b/adijif/clocks/ad9528.py @@ -278,7 +278,9 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: Exception: If solver is not called first """ if not self._clk_names: - raise Exception("set_requested_clocks must be called before get_config") + raise Exception( + "set_requested_clocks must be called before get_config" + ) if solution: self.solution = solution @@ -346,9 +348,15 @@ def _setup_solver_constraints(self, vcxo: int) -> None: self._add_equation( [ self.vcxo / self.config["r1"] <= self.pfd_max, - self.vcxo / self.config["r1"] * self.config["m1"] * self.config["n2"] + self.vcxo + / self.config["r1"] + * self.config["m1"] + * self.config["n2"] <= self.vco_max, - self.vcxo / self.config["r1"] * self.config["m1"] * self.config["n2"] + self.vcxo + / self.config["r1"] + * self.config["m1"] + * self.config["n2"] >= self.vco_min, 4 * self.config["b"] + self.config["a"] >= 16, 4 * self.config["b"] + self.config["a"] @@ -424,7 +432,9 @@ def set_requested_clocks( else: sysref_src = self.vcxo / self.config["r1"] - self._add_equation([sysref_src / (2 * self.config["k"]) == self._sysref]) + self._add_equation( + [sysref_src / (2 * self.config["k"]) == self._sysref] + ) # Add requested clocks to output constraints for out_freq, name in zip(out_freqs, clk_names): # noqa: B905 @@ -432,6 +442,9 @@ def set_requested_clocks( od = self._convert_input(self._d, f"d_{name}_{out_freq}") # od = self.model.sos1([n*n for n in range(1,9)]) self._add_equation( - [self.vcxo / self.config["r1"] * self.config["n2"] / od == out_freq] + [ + self.vcxo / self.config["r1"] * self.config["n2"] / od + == out_freq + ] ) self.config["out_dividers"].append(od) diff --git a/adijif/clocks/ad9528_bf.py b/adijif/clocks/ad9528_bf.py index 610ffdd..58097a6 100644 --- a/adijif/clocks/ad9528_bf.py +++ b/adijif/clocks/ad9528_bf.py @@ -31,7 +31,8 @@ def list_available_references(self, divider_set): "Input must be of type dict with fields: " + str(ref.keys()) ) return [ - divider_set["vco"] / divider_set["m1"] / div for div in self.d_available + divider_set["vco"] / divider_set["m1"] / div + for div in self.d_available ] def find_dividers(self, vcxo, required_output_rates, find=3): @@ -55,8 +56,12 @@ def find_dividers(self, vcxo, required_output_rates, find=3): and vco < self.vco_max and (vco / m1) % mod == 0 ): - required_output_divs = (vco / m1) / required_output_rates - if np.all(np.in1d(required_output_divs, self.d_available)): + required_output_divs = ( + vco / m1 + ) / required_output_rates + if np.all( + np.in1d(required_output_divs, self.d_available) + ): configs.append( { "m1": m1, diff --git a/adijif/clocks/ad9545.py b/adijif/clocks/ad9545.py index cfc10c5..3e07d79 100644 --- a/adijif/clocks/ad9545.py +++ b/adijif/clocks/ad9545.py @@ -171,7 +171,9 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: self.config[n_dpll_name] ) - config["PLL" + str(i)][n_name] = self._get_val(self.config[n_name]) + config["PLL" + str(i)][n_name] = self._get_val( + self.config[n_name] + ) config["PLL" + str(i)]["rate_hz"] = self._get_val( self.config["PLL" + str(i) + "_rate"] @@ -183,7 +185,9 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: if self.PLL_used[i] and dpll_profile_name in self.profiles: if self.profiles[dpll_profile_name]["hitless"]: - source_nr = int(self.profiles[dpll_profile_name]["fb_source"]) + source_nr = int( + self.profiles[dpll_profile_name]["fb_source"] + ) config["PLL" + str(i)]["hitless"] = { "fb_source": source_nr, @@ -259,11 +263,18 @@ def _setup_solver_constraints( for j in range(0, 4): if input_refs[j] != 0: n_name = "n" + str(i) + "_profile_" + str(j) - n_dpll_name = "n_dpll" + str(i) + "_profile_" + str(j) - m_apll_name = "m_apll" + str(i) + "_profile_" + str(j) + n_dpll_name = ( + "n_dpll" + str(i) + "_profile_" + str(j) + ) + m_apll_name = ( + "m_apll" + str(i) + "_profile_" + str(j) + ) self.config[n_name] = self.model.Var( - integer=True, lb=self.N_min, ub=self.N_max, name=n_name + integer=True, + lb=self.N_min, + ub=self.N_max, + name=n_name, ) """ Internally the PLL block is composed of a @@ -317,8 +328,12 @@ def _setup_solver_constraints( for j in range(0, 4): if input_refs[j] != 0: n_name = "n" + str(i) + "_profile_" + str(j) - n_dpll_name = "n_dpll" + str(i) + "_profile_" + str(j) - m_apll_name = "m_apll" + str(i) + "_profile_" + str(j) + n_dpll_name = ( + "n_dpll" + str(i) + "_profile_" + str(j) + ) + m_apll_name = ( + "m_apll" + str(i) + "_profile_" + str(j) + ) self.config[n_name] = exp.integer_var( int(self.N_min), int(self.N_max), n_name @@ -328,9 +343,13 @@ def _setup_solver_constraints( PLL and an Analog PLL with different constraints on dividers """ - DPLL_N = exp.integer_var(int(1), int(350e6), n_dpll_name) + DPLL_N = exp.integer_var( + int(1), int(350e6), n_dpll_name + ) self.config[n_dpll_name] = DPLL_N - APLL_M = exp.integer_var(int(7), int(255), m_apll_name) + APLL_M = exp.integer_var( + int(7), int(255), m_apll_name + ) self.config[m_apll_name] = APLL_M equations = equations + [ @@ -358,7 +377,8 @@ def _setup_solver_constraints( raise Exception("Unknown solver {}".format(self.solver)) equations = equations + [ - self.config["PLL_in_rate_" + str(i)] * self.config["r" + str(i)] + self.config["PLL_in_rate_" + str(i)] + * self.config["r" + str(i)] == self.config["input_ref_" + str(i)] ] @@ -400,18 +420,22 @@ def _setup_solver_constraints( if self.avoid_min_max_PLL_rates: for i in range(0, 2): if self.PLL_used[i]: - average_PLL_rate = self.PLL_out_min[i] / 2 + self.PLL_out_max[i] / 2 + average_PLL_rate = ( + self.PLL_out_min[i] / 2 + self.PLL_out_max[i] / 2 + ) if self.solver == "CPLEX": cplex_objectives = cplex_objectives + [ mod.abs( - self.config["PLL" + str(i) + "_rate"] - average_PLL_rate + self.config["PLL" + str(i) + "_rate"] + - average_PLL_rate ) ] elif self.solver == "gekko": self.model.Minimize( self.model.abs3( - self.config["PLL" + str(i) + "_rate"] - average_PLL_rate + self.config["PLL" + str(i) + "_rate"] + - average_PLL_rate ) ) else: @@ -426,7 +450,9 @@ def _setup_solver_constraints( self.config["r" + str(i)] ] elif self.solver == "gekko": - self.model.Maximize(self.config["PLL_in_rate_" + str(i)]) + self.model.Maximize( + self.config["PLL_in_rate_" + str(i)] + ) else: raise Exception("Unknown solver {}".format(self.solver)) @@ -489,8 +515,13 @@ def set_requested_clocks(self, ins: List[int], outs: List[int]) -> None: for j in range(0, 4): dpll_profile_name = "dpll_" + str(i) + "_profile_" + str(j) - if self.PLL_used[i] and self.profiles[dpll_profile_name]["hitless"]: - source_nr = int(self.profiles[dpll_profile_name]["fb_source"]) + if ( + self.PLL_used[i] + and self.profiles[dpll_profile_name]["hitless"] + ): + source_nr = int( + self.profiles[dpll_profile_name]["fb_source"] + ) n_dpll_name = "n_dpll" + str(i) + "_profile_" + str(j) if out_freqs[source_nr] == 0: @@ -508,7 +539,9 @@ def set_requested_clocks(self, ins: List[int], outs: List[int]) -> None: """ Frequency translation factor: N * input_ref_j == out_rate_x * r_div_j """ - self._add_equation([input_ref * pll_n_div == out_rate * r_div]) + self._add_equation( + [input_ref * pll_n_div == out_rate * r_div] + ) """ Hitless mode places a strict constraint on Q dividers """ self.config["q" + str(i)] @@ -524,7 +557,8 @@ def set_requested_clocks(self, ins: List[int], outs: List[int]) -> None: self._add_equation( [ self.config["PLL" + str(pll_number) + "_rate"] - == self.config["out_rate_" + str(i)] * self.config["q" + str(i)] + == self.config["out_rate_" + str(i)] + * self.config["q" + str(i)] ] ) diff --git a/adijif/clocks/hmc7044.py b/adijif/clocks/hmc7044.py index 312ad32..7830e1d 100644 --- a/adijif/clocks/hmc7044.py +++ b/adijif/clocks/hmc7044.py @@ -208,7 +208,9 @@ def _init_diagram(self) -> None: # Connections inside the IC # lo.add_connection({"from": ref_in, "to": r2_div, 'rate': 125000000}) - self.ic_diagram_node.add_connection({"from": vcxo_doubler, "to": r2_div}) + self.ic_diagram_node.add_connection( + {"from": vcxo_doubler, "to": r2_div} + ) self.ic_diagram_node.add_connection( {"from": r2_div, "to": pfd, "rate": 125000000 / 2} ) @@ -266,7 +268,11 @@ def draw(self) -> str: lo.add_node(ref_in) vcxo_double = self.ic_diagram_node.get_child("VCXO Doubler") lo.add_connection( - {"from": ref_in, "to": vcxo_double, "rate": self._saved_solution["vcxo"]} + { + "from": ref_in, + "to": vcxo_double, + "rate": self._saved_solution["vcxo"], + } ) # Update Node values @@ -279,7 +285,9 @@ def draw(self) -> str: # Update VCXO Doubler to R2 # con = self.ic_diagram_node.get_connection("VCXO Doubler", "R2") - rate = self._saved_solution["vcxo_doubler"] * self._saved_solution["vcxo"] + rate = ( + self._saved_solution["vcxo_doubler"] * self._saved_solution["vcxo"] + ) self.ic_diagram_node.update_connection("VCXO Doubler", "R2", rate) # Update R2 to PFD @@ -308,7 +316,9 @@ def draw(self) -> str: div.value = str(div_value) d += 1 lo.add_node(clk_node) - lo.add_connection({"from": div, "to": clk_node, "rate": val["rate"]}) + lo.add_connection( + {"from": div, "to": clk_node, "rate": val["rate"]} + ) return lo.draw() @@ -328,7 +338,9 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: Exception: If solver is not called first """ if not self._clk_names: - raise Exception("set_requested_clocks must be called before get_config") + raise Exception( + "set_requested_clocks must be called before get_config" + ) if solution: self.solution = solution @@ -375,7 +387,9 @@ def _setup_solver_constraints(self, vcxo: int) -> None: self.vcxo = vcxo if self.solver == "gekko": - self.config = {"r2": self.model.Var(integer=True, lb=1, ub=4095, value=1)} + self.config = { + "r2": self.model.Var(integer=True, lb=1, ub=4095, value=1) + } self.config["n2"] = self.model.Var(integer=True, lb=8, ub=4095) if isinstance(vcxo, (int, float)): vcxo_var = self.model.Const(int(vcxo)) @@ -456,7 +470,9 @@ def _get_clock_constraint( __d = self._d if isinstance(self._d, list) else [self._d] if __d.sort() != self.d_available.sort(): - raise Exception("For solver gekko d is not configurable for HMC7044") + raise Exception( + "For solver gekko d is not configurable for HMC7044" + ) even = self.model.Var(integer=True, lb=3, ub=2047) odd = self.model.Intermediate(even * 2) diff --git a/adijif/clocks/ltc6952.py b/adijif/clocks/ltc6952.py index 0a8ac95..9a56865 100644 --- a/adijif/clocks/ltc6952.py +++ b/adijif/clocks/ltc6952.py @@ -469,7 +469,9 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: Exception: If solver is not called first """ if not self._clk_names: - raise Exception("set_requested_clocks must be called before get_config") + raise Exception( + "set_requested_clocks must be called before get_config" + ) if solution: self.solution = solution @@ -559,7 +561,9 @@ def _get_clock_constraint( __d = self._d if isinstance(self._d, list) else [self._d] if __d.sort() != self.d_available.sort(): - raise Exception("For solver gekko d is not configurable for LTC6952") + raise Exception( + "For solver gekko d is not configurable for LTC6952" + ) # Since d is so disjoint it is very annoying to solve. mp = self.model.Var(integer=True, lb=1, ub=32) nx = self.model.Var(integer=True, lb=0, ub=7) @@ -609,7 +613,10 @@ def set_requested_clocks( od = self._convert_input(self._d, "d_" + str(out_freq)) self._add_equation( - [self.vcxo / self.config["r2"] * self.config["n2"] / od == out_freq] + [ + self.vcxo / self.config["r2"] * self.config["n2"] / od + == out_freq + ] ) self.config["out_dividers"].append(od) diff --git a/adijif/clocks/ltc6953.py b/adijif/clocks/ltc6953.py index 59649d5..db75853 100644 --- a/adijif/clocks/ltc6953.py +++ b/adijif/clocks/ltc6953.py @@ -383,7 +383,9 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: Exception: If solver is not called first """ if not self._clk_names: - raise Exception("set_requested_clocks must be called before get_config") + raise Exception( + "set_requested_clocks must be called before get_config" + ) if solution: self.solution = solution @@ -427,7 +429,9 @@ def _setup_solver_constraints(self, input_ref: int) -> None: def _setup(self, input_ref: int) -> None: if isinstance(input_ref, (float, int)): - assert self.input_freq_max >= input_ref >= 0, "Input frequency out of range" + assert ( + self.input_freq_max >= input_ref >= 0 + ), "Input frequency out of range" # Setup clock chip internal constraints self._setup_solver_constraints(input_ref) @@ -453,7 +457,9 @@ def _get_clock_constraint( if self.solver == "gekko": __m = self._m if isinstance(self._m, list) else [self._m] if __m.sort() != self.m_available.sort(): - raise Exception("For solver gekko d is not configurable for LTC6952") + raise Exception( + "For solver gekko d is not configurable for LTC6952" + ) mp = self.model.Var(integer=True, lb=1, ub=32) nx = self.model.Var(integer=True, lb=0, ub=7) od = self.model.Intermediate(mp * pow(2, nx)) diff --git a/adijif/common.py b/adijif/common.py index 5c36e79..2fbd063 100644 --- a/adijif/common.py +++ b/adijif/common.py @@ -19,7 +19,10 @@ class core: _objectives = [] def _add_objective( - self, objective: List[Union[GKVariable, GK_Intermediate, GK_Operators, CpoExpr]] + self, + objective: List[ + Union[GKVariable, GK_Intermediate, GK_Operators, CpoExpr] + ], ) -> None: if isinstance(objective, list): self._objectives += objective diff --git a/adijif/converters/ad9081.py b/adijif/converters/ad9081.py index 0ff0d73..cf399f8 100644 --- a/adijif/converters/ad9081.py +++ b/adijif/converters/ad9081.py @@ -89,7 +89,19 @@ class ad9081_core(converter, metaclass=ABCMeta): config = {} # type: ignore device_clock_max = 12e9 - _lmfc_divisor_sysref_available = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024] + _lmfc_divisor_sysref_available = [ + 1, + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256, + 512, + 1024, + ] def _check_valid_internal_configuration(self) -> None: # FIXME @@ -120,11 +132,16 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: pll_config["serdes_pll_div"] = self._get_val( self.config["serdes_pll_div"] ) - return {"clocking_option": self.clocking_option, "pll_config": pll_config} + 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"]), + "serdes_pll_div": self._get_val( + self.config["serdes_pll_div"] + ), "clocking_option": self.clocking_option, } return {"clocking_option": self.clocking_option} @@ -138,7 +155,9 @@ def get_required_clock_names(self) -> List[str]: List[str]: List of strings of clock names in order """ clk = ( - "ad9081_dac_clock" if self.clocking_option == "direct" else "ad9081_pll_ref" + "ad9081_dac_clock" + if self.clocking_option == "direct" + else "ad9081_pll_ref" ) return [clk, "ad9081_sysref"] @@ -155,7 +174,9 @@ def _converter_clock_config(self) -> None: 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_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" ) @@ -216,8 +237,10 @@ def _pll_config(self, rxtx: bool = False) -> Dict: [ 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"] / 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, @@ -242,7 +265,10 @@ def _pll_config(self, rxtx: bool = False) -> Dict: 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"]] + [ + self.config["integer_ad9081_ref_clk"] + == self.config["ad9081_ref_clk"] + ] ) else: raise Exception("Only CPLEX solver supported") @@ -368,7 +394,9 @@ def _check_valid_internal_configuration(self) -> None: cfg = self.quick_configuration_modes[self.jesd_class][mode] # Check decimation is valid - if isinstance(self.decimation, int) or isinstance(self.decimation, float): + 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 @@ -382,7 +410,10 @@ def _check_valid_internal_configuration(self) -> None: 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: + if ( + cc <= self.converter_clock_max + and cc >= self.converter_clock_min + ): self.decimation = dec print("Decimation automatically determined:", dec) return @@ -473,7 +504,9 @@ def _converter_clock_config(self) -> None: """ 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"]) + self.config["converter_clk"] = self._add_intermediate( + self.config["dac_clk"] + ) class ad9081(ad9081_core): @@ -535,7 +568,9 @@ def _converter_clock_config(self) -> None: 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}") + 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: @@ -546,7 +581,9 @@ def _converter_clock_config(self) -> None: 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"]) + 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 @@ -562,7 +599,10 @@ def _converter_clock_config(self) -> None: raise Exception(f"Unknown solver {self.solver}") self._add_equation( - [self.config["serdes_pll_div"] * self.adc.bit_clock == self.dac.bit_clock] + [ + self.config["serdes_pll_div"] * self.adc.bit_clock + == self.dac.bit_clock + ] ) def get_required_clocks(self) -> List: diff --git a/adijif/converters/ad9081_dp.py b/adijif/converters/ad9081_dp.py index c15e56f..cf8c20d 100644 --- a/adijif/converters/ad9081_dp.py +++ b/adijif/converters/ad9081_dp.py @@ -42,13 +42,19 @@ def __setattr__(self, key: str, value: any) -> None: raise TypeError("Decimations must be a list") if key == "cddc_decimations": if len(value) != 4: - raise TypeError("CDDC Decimations must be a list of length 4") + raise TypeError( + "CDDC Decimations must be a list of length 4" + ) for v in value: if v not in [1, 2, 3, 4, 6]: - raise TypeError("CDDC Decimations must be 1, 2, 3, 4, or 8") + raise TypeError( + "CDDC Decimations must be 1, 2, 3, 4, or 8" + ) if key == "fddc_decimations": if len(value) != 8: - raise TypeError("FDDC Decimations must be a list of length 8") + raise TypeError( + "FDDC Decimations must be a list of length 8" + ) for v in value: if v not in [1, 2, 3, 4, 6, 8, 12, 16, 24]: raise TypeError( @@ -101,7 +107,9 @@ def decimation_overall(self) -> int: if self.fddc_enabled[i]: cddc = self.fddc_source[i] - 1 if not self.cddc_enabled: - raise Exception(f"Source CDDC {cddc} not enabled for FDDC {i}") + raise Exception( + f"Source CDDC {cddc} not enabled for FDDC {i}" + ) cdec = self.cddc_decimations[cddc] if (cdec * fdec < min_dec) or min_dec == -1: min_dec = cdec * fdec @@ -154,9 +162,13 @@ def __setattr__(self, key: str, value: any) -> None: if isinstance(value, list): raise TypeError("Interpolation must be an integer, not a list") if key == "cduc_interpolation" and value not in [1, 2, 4, 6, 8, 12]: - raise TypeError("CDUC Interpolation must be 1, 2, 4, 6, 8, or 12") + raise TypeError( + "CDUC Interpolation must be 1, 2, 4, 6, 8, or 12" + ) if key == "fduc_interpolation" and value not in [1, 2, 3, 4, 6, 8]: - raise TypeError("FDUC Interpolation must be 1, 2, 3, 4, 6, or 8") + raise TypeError( + "FDUC Interpolation must be 1, 2, 3, 4, 6, or 8" + ) object.__setattr__(self, key, value) def _freeze(self) -> None: diff --git a/adijif/converters/ad9144.py b/adijif/converters/ad9144.py index 0db3f55..8f547ff 100644 --- a/adijif/converters/ad9144.py +++ b/adijif/converters/ad9144.py @@ -45,25 +45,39 @@ def _convert_to_config( str(7): _convert_to_config(DualLink=False, M=2, L=1, S=1, F=4, N=16, Np=16), # 8 is missing in datasheet str(9): _convert_to_config(DualLink=False, M=1, L=2, S=1, F=1, N=16, Np=16), - str(10): _convert_to_config(DualLink=False, M=1, L=1, S=1, F=2, N=16, Np=16), + str(10): _convert_to_config( + DualLink=False, M=1, L=1, S=1, F=2, N=16, Np=16 + ), } quick_configuration_modes = { **quick_configuration_modes, **{ str(4) - + "-DL": _convert_to_config(DualLink=False, M=2, L=4, S=1, F=1, N=16, Np=16), + + "-DL": _convert_to_config( + DualLink=False, M=2, L=4, S=1, F=1, N=16, Np=16 + ), str(5) - + "-DL": _convert_to_config(DualLink=False, M=2, L=4, S=2, F=2, N=16, Np=16), + + "-DL": _convert_to_config( + DualLink=False, M=2, L=4, S=2, F=2, N=16, Np=16 + ), str(6) - + "-DL": _convert_to_config(DualLink=False, M=2, L=2, S=1, F=2, N=16, Np=16), + + "-DL": _convert_to_config( + DualLink=False, M=2, L=2, S=1, F=2, N=16, Np=16 + ), str(7) - + "-DL": _convert_to_config(DualLink=False, M=2, L=1, S=1, F=4, N=16, Np=16), + + "-DL": _convert_to_config( + DualLink=False, M=2, L=1, S=1, F=4, N=16, Np=16 + ), # 8 is missing in datasheet str(9) - + "-DL": _convert_to_config(DualLink=False, M=1, L=2, S=1, F=1, N=16, Np=16), + + "-DL": _convert_to_config( + DualLink=False, M=1, L=2, S=1, F=1, N=16, Np=16 + ), str(10) - + "-DL": _convert_to_config(DualLink=False, M=1, L=1, S=1, F=2, N=16, Np=16), + + "-DL": _convert_to_config( + DualLink=False, M=1, L=1, S=1, F=2, N=16, Np=16 + ), }, } @@ -158,7 +172,9 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: config.update( { "BCount": self._get_val(self.config["BCount"]), - "ref_div_factor": self._get_val(self.config["ref_div_factor"]), + "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"]) ), @@ -175,7 +191,9 @@ def get_required_clock_names(self) -> List[str]: List[str]: List of strings of clock names in order """ clk = ( - "ad9144_dac_clock" if self.clocking_option == "direct" else "ad9144_pll_ref" + "ad9144_dac_clock" + if self.clocking_option == "direct" + else "ad9144_pll_ref" ) return [clk, "ad9144_sysref"] @@ -190,7 +208,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") + 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 @@ -244,8 +264,10 @@ def _pll_config(self) -> Dict: 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_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, ] diff --git a/adijif/converters/ad9144_bf.py b/adijif/converters/ad9144_bf.py index 17c2b5b..beb0c35 100644 --- a/adijif/converters/ad9144_bf.py +++ b/adijif/converters/ad9144_bf.py @@ -40,5 +40,7 @@ def sysref_clock_ranges(self): def sysref_met(self, sysref_clock, sample_clock): if sysref_clock % self.multiframe_clock != 0: raise Exception("SYSREF not a multiple of LMFC") - if (self.multiframe_clock / sysref_clock) < 2 * self.input_clock_divider: + if ( + self.multiframe_clock / sysref_clock + ) < 2 * self.input_clock_divider: raise Exception("SYSREF not a multiple of LMFC > 1") diff --git a/adijif/converters/ad9680.py b/adijif/converters/ad9680.py index 2f8cb8d..4e7cafb 100644 --- a/adijif/converters/ad9680.py +++ b/adijif/converters/ad9680.py @@ -124,9 +124,26 @@ def _check_valid_jesd_mode(self) -> str: 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" + 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" + assert self.K in [ + 4, + 8, + 12, + 16, + 20, + 24, + 28, + 32, + ], "Invalid K value for F=1" return super()._check_valid_jesd_mode() @@ -142,7 +159,10 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: Returns: Dict: Dictionary of clocking rates and dividers for configuration """ - return {"clocking_option": self.clocking_option, "decimation": self.decimation} + 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. @@ -177,7 +197,8 @@ def get_required_clocks(self) -> List: ) self.config["lmfc_divisor_sysref_squared"] = self._add_intermediate( - self.config["lmfc_divisor_sysref"] * self.config["lmfc_divisor_sysref"] + 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"] diff --git a/adijif/converters/ad9680_bf.py b/adijif/converters/ad9680_bf.py index 070623d..740ddc3 100644 --- a/adijif/converters/ad9680_bf.py +++ b/adijif/converters/ad9680_bf.py @@ -39,5 +39,7 @@ def sysref_clock_ranges(self): def sysref_met(self, sysref_clock, sample_clock): if sysref_clock % self.multiframe_clock != 0: raise Exception("SYSREF not a multiple of LMFC") - if (self.multiframe_clock / sysref_clock) < 2 * self.input_clock_divider: + if ( + self.multiframe_clock / sysref_clock + ) < 2 * self.input_clock_divider: raise Exception("SYSREF not a multiple of LMFC > 1") diff --git a/adijif/converters/adrv9009.py b/adijif/converters/adrv9009.py index e3022a8..c53dee1 100644 --- a/adijif/converters/adrv9009.py +++ b/adijif/converters/adrv9009.py @@ -172,7 +172,10 @@ def get_required_clocks(self) -> List[Dict]: ) self.config["sysref"] = self._add_intermediate( self.multiframe_clock - / (self.config["lmfc_divisor_sysref"] * self.config["lmfc_divisor_sysref"]) + / ( + self.config["lmfc_divisor_sysref"] + * self.config["lmfc_divisor_sysref"] + ) ) self._add_equation( diff --git a/adijif/converters/adrv9009_bf.py b/adijif/converters/adrv9009_bf.py index 75eaaa2..5b5878f 100644 --- a/adijif/converters/adrv9009_bf.py +++ b/adijif/converters/adrv9009_bf.py @@ -39,5 +39,7 @@ def sysref_clock_ranges(self): def sysref_met(self, sysref_clock, sample_clock): if sysref_clock % self.multiframe_clock != 0: raise Exception("SYSREF not a multiple of LMFC") - if (self.multiframe_clock / sysref_clock) < 2 * self.input_clock_divider: + if ( + self.multiframe_clock / sysref_clock + ) < 2 * self.input_clock_divider: raise Exception("SYSREF not a multiple of LMFC > 1") diff --git a/adijif/converters/converter.py b/adijif/converters/converter.py index 3478a1f..b6050db 100644 --- a/adijif/converters/converter.py +++ b/adijif/converters/converter.py @@ -59,7 +59,9 @@ def set_quick_configuration_mode( """ smode = str(mode) if smode not in self.quick_configuration_modes[jesd_class].keys(): - raise Exception(f"Mode {smode} not among configurations for {jesd_class}") + raise Exception( + f"Mode {smode} not among configurations for {jesd_class}" + ) if jesd_class not in self.available_jesd_modes: raise Exception(f"{jesd_class} not available for {self.name}") @@ -102,7 +104,9 @@ def _check_valid_jesd_mode(self) -> str: del current_config[k] if current_config == cmode: return mode - raise Exception(f"Invalid JESD configuration for {self.name}\n{current_config}") + raise Exception( + f"Invalid JESD configuration for {self.name}\n{current_config}" + ) def get_current_jesd_mode_settings(self) -> Dict: """Get current JESD mode settings. diff --git a/adijif/converters/dac.py b/adijif/converters/dac.py index b897360..35b1f93 100644 --- a/adijif/converters/dac.py +++ b/adijif/converters/dac.py @@ -22,13 +22,15 @@ def _check_valid_internal_configuration(self) -> None: if self.interpolation * self.sample_clock > self.converter_clock_max: raise Exception( "DAC rate too fast for configuration {} (max: {})".format( - self.interpolation * self.sample_clock, self.converter_clock_max + self.interpolation * self.sample_clock, + self.converter_clock_max, ) ) if self.interpolation * self.sample_clock < self.converter_clock_min: raise Exception( "DAC rate too slow for configuration {} (min: {})".format( - self.interpolation * self.sample_clock, self.converter_clock_min + self.interpolation * self.sample_clock, + self.converter_clock_min, ) ) diff --git a/adijif/draw.py b/adijif/draw.py index a539de9..394c142 100644 --- a/adijif/draw.py +++ b/adijif/draw.py @@ -9,7 +9,9 @@ class Node: """Node model for diagraming which can have children and connections.""" - def __init__(self, name: str, ntype: str = None, parent: Node = None) -> None: + def __init__( + self, name: str, ntype: str = None, parent: Node = None + ) -> None: """Initialize node with name, type and parent node. Args: @@ -85,7 +87,9 @@ def get_connection(self, from_s: str, to: str) -> dict: return conn raise ValueError(f"Connection from {from_s} to {to} not found.") - def update_connection(self, from_s: str, to: str, rate: Union(int, float)) -> None: + def update_connection( + self, from_s: str, to: str, rate: Union(int, float) + ) -> None: """Update connection rate between this node and another node. Args: @@ -221,7 +225,9 @@ def draw_subnodes(node: Node, spacing: str = " ") -> str: diag = " {\n" for child in node.children: if child.value: - diag += spacing + child.name + f": {{tooltip: {child.value} }}" + diag += ( + spacing + child.name + f": {{tooltip: {child.value} }}" + ) else: diag += spacing + child.name if child.children: diff --git a/adijif/fpgas/xilinx/__init__.py b/adijif/fpgas/xilinx/__init__.py index 2392fbc..6046ca4 100644 --- a/adijif/fpgas/xilinx/__init__.py +++ b/adijif/fpgas/xilinx/__init__.py @@ -2,11 +2,14 @@ from typing import Dict, List, Optional, Union -from adijif.converters.converter import converter as conv -from adijif.solvers import CpoSolveResult # type: ignore -from adijif.solvers import integer_var # type: ignore -from adijif.solvers import CpoIntVar, GK_Intermediate, GK_Operators, GKVariable - +from ...converters.converter import converter as conv +from ...solvers import ( + CpoIntVar, + CpoSolveResult, + GK_Intermediate, + GK_Operators, + GKVariable, +) from .bf import xilinx_bf from .sevenseries import SevenSeries as SSTransceiver from .ultrascaleplus import UltraScalePlus as USPTransceiver @@ -76,15 +79,15 @@ class xilinx(xilinx_bf): transceiver_type = "GTXE2" def trx_gen(self) -> int: - """Get transceiver generation (2,3,4) + """Get transceiver generation (2,3,4). Returns: int: generation of transceiver """ return int(self.transceiver_type[-1]) - def trx_variant(self): - """Get transceiver variant (GTX, GTH, GTY, ...) + def trx_variant(self) -> str: + """Get transceiver variant (GTX, GTH, GTY, ...). Returns: str: Transceiver variant @@ -95,8 +98,15 @@ def trx_variant(self): assert len(trxt) == 3 return trxt - def fpga_generation(self): - """Get FPGA generation 7000, Ultrascale, Ultrascale+... based on transceiver type""" + def fpga_generation(self) -> str: + """Get FPGA generation 7000, US, US+... based on transceiver type. + + Returns: + str: FPGA generation + + Raises: + Exception: Unknown transceiver generation + """ if self.trx_gen() == 2: return "7000" elif self.trx_gen() == 3: @@ -243,7 +253,9 @@ def out_clk_select(self, value: Union[str, List[str]]) -> None: elif isinstance(value, dict): for converter in value: if not isinstance(converter, conv): - raise Exception("Keys of out_clk_select but be of type converter") + raise Exception( + "Keys of out_clk_select but be of type converter" + ) if value[converter] not in self._out_clk_selections: raise Exception( f"Invalid out_clk_select {value[converter]}, " @@ -418,8 +430,6 @@ def get_config( if solution: self.solution = solution - a = 1 - for config in self.configs: pll_config: Dict[str, Union[str, int, float]] = {} @@ -432,12 +442,12 @@ def get_config( pll_config = self._transceiver_models[converter.name].get_config( config, converter, fpga_ref ) - cpll = self._get_val(config[converter.name + "_use_cpll"]) - qpll = self._get_val(config[converter.name + "_use_qpll"]) - if converter.name + "_use_qpll1" in config.keys(): - qpll1 = self._get_val(config[converter.name + "_use_qpll1"]) - else: - qpll1 = False + # cpll = self._get_val(config[converter.name + "_use_cpll"]) + # qpll = self._get_val(config[converter.name + "_use_qpll"]) + # if converter.name + "_use_qpll1" in config.keys(): + # qpll1 = self._get_val(config[converter.name + "_use_qpll1"]) + # else: + # qpll1 = False # SERDES output mux if pll_config["type"] == "cpll": @@ -518,7 +528,25 @@ def _get_progdiv(self) -> Union[List[int], List[float]]: if self.transceiver_type in ["GTYE3", "GTHE3"]: return [1, 4, 5, 8, 10, 16, 16.5, 20, 32, 33, 40, 64, 66, 80, 100] elif self.transceiver_type in ["GTYE4", "GTHE4"]: - return [1, 4, 5, 8, 10, 16, 16.5, 20, 32, 33, 40, 64, 66, 80, 100, 128, 132] + return [ + 1, + 4, + 5, + 8, + 10, + 16, + 16.5, + 20, + 32, + 33, + 40, + 64, + 66, + 80, + 100, + 128, + 132, + ] else: raise Exception( "PROGDIV is not available for FPGA transciever type " @@ -528,7 +556,9 @@ def _get_progdiv(self) -> Union[List[int], List[float]]: def _set_link_layer_requirements( self, converter: conv, - fpga_ref: Union[int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar], + fpga_ref: Union[ + int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar + ], config: Dict, link_out_ref: Union[ int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar @@ -578,7 +608,8 @@ def _set_link_layer_requirements( if isinstance(self.out_clk_select, dict): if converter not in self.out_clk_select.keys(): raise Exception( - "Link layer out_clk_select invalid for converter " + converter.name + "Link layer out_clk_select invalid for converter " + + converter.name ) if isinstance(self.out_clk_select[converter], dict): out_clk_select = self.out_clk_select[converter].copy() @@ -611,8 +642,14 @@ def _set_link_layer_requirements( # REFCLK if not ocs_found and ( - (isinstance(out_clk_select, str) and out_clk_select == "XCVR_REFCLK") - or (isinstance(out_clk_select, list) and out_clk_select == ["XCVR_REFCLK"]) + ( + isinstance(out_clk_select, str) + and out_clk_select == "XCVR_REFCLK" + ) + or ( + isinstance(out_clk_select, list) + and out_clk_select == ["XCVR_REFCLK"] + ) ): ocs_found = True config[converter.name + "_refclk_div"] = 1 @@ -620,7 +657,10 @@ def _set_link_layer_requirements( # REFCLK / 2 if not ocs_found and ( - (isinstance(out_clk_select, str) and out_clk_select == "XCVR_REFCLK_DIV2") + ( + isinstance(out_clk_select, str) + and out_clk_select == "XCVR_REFCLK_DIV2" + ) or ( isinstance(out_clk_select, list) and out_clk_select == ["XCVR_REFCLK_DIV2"] @@ -642,7 +682,8 @@ def _set_link_layer_requirements( self._add_equation( [ fpga_ref - == link_layer_input_rate * config[converter.name + "_refclk_div"] + == link_layer_input_rate + * config[converter.name + "_refclk_div"] ] ) @@ -657,7 +698,9 @@ def _set_link_layer_requirements( def _setup_quad_tile( self, converter: conv, - fpga_ref: Union[int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar], + fpga_ref: Union[ + int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar + ], link_out_ref: Union[ None, int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar ] = None, @@ -707,7 +750,9 @@ def _setup_quad_tile( speed_grade=self.speed_grade, ) else: - raise Exception(f"Unsupported FPGA generation {self.fpga_generation()}") + raise Exception( + f"Unsupported FPGA generation {self.fpga_generation()}" + ) # Handle force PLLs for nested devices and multiple converters force_cpll = False @@ -743,7 +788,9 @@ def _setup_quad_tile( # Add constraints for link clock # - Must be lanerate/40 204B or lanerate/66 204C - config = self._set_link_layer_requirements(converter, fpga_ref, config, None) + config = self._set_link_layer_requirements( + converter, fpga_ref, config, None + ) # Add optimization to favor a single reference clock vs unique ref+device clocks config[converter.name + "single_clk"] = self._convert_input( @@ -782,7 +829,10 @@ def _setup_quad_tile( else: possible_divs = [] for samples_per_clock in [1, 2, 4, 8, 16]: - if converter.sample_clock / samples_per_clock <= self.target_Fmax: + if ( + converter.sample_clock / samples_per_clock + <= self.target_Fmax + ): possible_divs.append(samples_per_clock) if len(possible_divs) == 0: @@ -809,7 +859,9 @@ def _setup_quad_tile( def get_required_clocks( self, converter: conv, - fpga_ref: Union[int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar], + fpga_ref: Union[ + int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar + ], link_out_ref: Union[ int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar ] = None, @@ -889,7 +941,9 @@ def get_required_clocks( link_out_ref is not None ): # self.requires_separate_link_layer_out_clock: self.config[cnv.name + "link_out_ref"] = link_out_ref - self.ref_clocks.append(self.config[cnv.name + "link_out_ref"]) + self.ref_clocks.append( + self.config[cnv.name + "link_out_ref"] + ) config = self._setup_quad_tile( cnv, self.config[cnv.name + "fpga_ref"], @@ -925,7 +979,9 @@ def get_required_clocks( self.model.Obj(self.config[cnv.name + "fpga_ref"]) elif self.solver == "CPLEX": # self.model.minimize_static_lex(obs + [self.config[converter.name+"fpga_ref"]]) # noqa: B950 - self.model.minimize(self.config[cnv.name + "fpga_ref"]) # noqa: B950 + self.model.minimize( + self.config[cnv.name + "fpga_ref"] + ) # noqa: B950 # self.model.maximize(obs + self.config[converter.name+"fpga_ref"]) else: raise Exception(f"Unknown solver {self.solver}") diff --git a/adijif/fpgas/xilinx/bf.py b/adijif/fpgas/xilinx/bf.py index 19df553..b826cb0 100644 --- a/adijif/fpgas/xilinx/bf.py +++ b/adijif/fpgas/xilinx/bf.py @@ -1,3 +1,4 @@ +# flake8: noqa from adijif.fpgas.fpga import fpga @@ -58,7 +59,10 @@ def determine_qpll(self, bit_clock, fpga_ref_clock): System reference clock """ - if self.ref_clock_max < fpga_ref_clock or fpga_ref_clock < self.ref_clock_min: + if ( + self.ref_clock_max < fpga_ref_clock + or fpga_ref_clock < self.ref_clock_min + ): raise Exception("fpga_ref_clock not within range") for m in [1, 2, 3, 4]: diff --git a/adijif/fpgas/xilinx/pll.py b/adijif/fpgas/xilinx/pll.py index 70c39a6..762fd36 100644 --- a/adijif/fpgas/xilinx/pll.py +++ b/adijif/fpgas/xilinx/pll.py @@ -1,26 +1,41 @@ -from abc import ABC, ABCMeta, abstractmethod -from typing import Union +"""Xilinx Common PLL class.""" + +from typing import Optional, Union from docplex.cp.solution import CpoSolveResult # type: ignore from ...common import core from ...gekko_trans import gekko_translation +from ...solvers import CpoModel class XilinxPLL(core, gekko_translation): + """Xilinx Common PLL class.""" + plls = None parent = None _model = None # Hold internal model when used standalone _solution = None # Hold internal solution when used standalone def __init__( - self, parent=None, speed_grade="-2", transceiver_type="GTXE2", *args, **kwargs + self, + parent=None, # noqa: ANN001 + speed_grade: Optional[str] = "-2", + transceiver_type: Optional[str] = "GTXE2", + *args, # noqa: ANN002 + **kwargs, # noqa: ANN003 ) -> None: """Initalize 7 series transceiver PLLs. Args: parent (system or converter, optional): Parent object. Defaults to None. + speed_grade (str, optional): Speed grade. Defaults to "-2". transceiver_type (str, optional): Transceiver type. Defaults to "GTXE2". + *args: Variable length argument list. + **kwargs: Arbitrary keyword arguments. + + Raises: + Exception: If Gekko solver is used """ self.transceiver_type = transceiver_type self.speed_grade = speed_grade @@ -35,50 +50,95 @@ def __init__( raise Exception("Gekko solver not supported for Xilinx PLLs") @property - def model(self): - """Internal system model for solver.""" + def model(self) -> CpoModel: + """Internal system model for solver. + + Returns: + CpoModel: Internal system model for solver + """ if self.parent: return self.parent.model return self._model @model.setter - def model(self, val): + def model(self, val: CpoModel) -> None: + """Set internal system model for solver. + + Args: + val (CpoModel): Internal system model for solver + + Raises: + Exception: If parent model is used + """ if self.parent: raise Exception("Cannot set model when parent model is used") self._model = val @property - def solution(self): + def solution(self) -> CpoSolveResult: """Solution object from solver.""" if self.parent: return self.parent.solution return self._solution @solution.setter - def solution(self, val): + def solution(self, val: CpoSolveResult) -> None: + """Set solution object from solver. + + Args: + val (CpoSolveResult): Solution object from solver + + Raises: + Exception: If parent model is used + """ if self.parent: raise Exception("Cannot set solution when parent model is used") self._solution = val @property - def transceiver_type(self): + def transceiver_type(self) -> str: + """Transceiver type. + + Returns: + str: Transceiver type + """ return self._transceiver_type @transceiver_type.setter - def transceiver_type(self, val): - self._check_in_range(val, self.transceiver_types_available, "transceiver_type") + def transceiver_type(self, val: str) -> None: + """Set transceiver type. + + Args: + val (str): Transceiver type + """ + self._check_in_range( + val, self.transceiver_types_available, "transceiver_type" + ) self._transceiver_type = val _speed_grade = -2 @property - def speed_grade(self): + def speed_grade(self) -> str: + """speed_grade for transceiver. + + Returns: + str: Speed grade + """ if self.parent: return self.parent.speed_grade return self._speed_grade @speed_grade.setter - def speed_grade(self, val): + def speed_grade(self, val: str) -> None: + """Set speed grade for transceiver. + + Args: + val (str): Speed grade + + Raises: + Exception: If parent model is used + """ if self.parent: raise Exception("Cannot set speed_grade when parent model is used") self._speed_grade = val @@ -136,17 +196,39 @@ def solve(self) -> Union[None, CpoSolveResult]: class PLLCommon(gekko_translation): - def __init__(self, parent_transceiver) -> None: + """Common PLL class for Xilinx and Intel PLLs.""" + + def __init__(self, parent_transceiver: CpoModel) -> None: + """Initialize PLL common class. + + Args: + parent_transceiver (CpoModel): Parent transceiver object + """ self.parent = parent_transceiver @property - def model(self): + def model(self) -> CpoModel: + """Internal system model for solver. + + Returns: + CpoModel: Internal system model for solver + """ return self.parent.model @property - def solver(self): + def solver(self) -> str: + """Solver type. + + Returns: + str: Solver type + """ return self.parent.solver @property - def solution(self): + def solution(self) -> CpoSolveResult: + """Solution object from solver. + + Returns: + CpoSolveResult: Solution object from solver + """ return self.parent.solution diff --git a/adijif/fpgas/xilinx/sevenseries.py b/adijif/fpgas/xilinx/sevenseries.py index 54687e1..553cb93 100644 --- a/adijif/fpgas/xilinx/sevenseries.py +++ b/adijif/fpgas/xilinx/sevenseries.py @@ -1,429 +1,543 @@ -from docplex.cp.modeler import if_then - -from ...common import core -from ...gekko_trans import gekko_translation -from .pll import PLLCommon, XilinxPLL - - -class SevenSeries(XilinxPLL): - # References - # GTXs - # https://docs.amd.com/v/u/en-US/ug476_7Series_Transceivers - # https://docs.amd.com/v/u/en-US/ds191-XC7Z030-XC7Z045-data-sheet - - transceiver_types_available = ["GTXE2", "GTHE2"] # We don't support GTHE2 yet! - _transceiver_type = "GTXE2" - - force_cpll = False - force_qpll = False - - def add_plls(self) -> None: - """Add PLLs to the model.""" - self.plls = {"CPLL": CPLL(self), "QPLL": QPLL(self)} - - def add_constraints(self, config, fpga_ref, converter) -> dict: - """Add constraints for PLLs. - - Args: - config (dict): Configuration dictionary. - fpga_ref (int, var): FPGA reference clock. - converter (converter): Converter object. - - Returns: - dict: Updated configuration dictionary. - """ - assert self.plls, "No PLLs configured. Run the add_plls method" - assert not (self.force_cpll and self.force_qpll), "Both CPLL and QPLL enabled" - for pll in self.plls: - config = self.plls[pll].add_constraints(config, fpga_ref, converter) - self._add_equation( - config[converter.name + "_use_cpll"] + config[converter.name + "_use_qpll"] - == 1 - ) - return config - - def get_config(self, config: dict, converter, fpga_ref) -> dict: - if self.force_cpll: - ecpll = 1 - eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] - elif self.force_qpll: - ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] - eqpll = 1 - else: - ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] - eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] - - assert ecpll != eqpll, "Both CPLL and QPLL enabled" - pll = "CPLL" if ecpll else "QPLL" - return self.plls[pll].get_config(config, converter, fpga_ref) - - -class CPLL(PLLCommon): - # vco_min = int(1.6e9) - # vco_max = int(5.16e9) - - @property - def vco_min(self): - if self.parent.transceiver_type == "GTXE2": - return 1600000000 - elif self.parent.transceiver_type == "GTHE2": - return 1600000000 - raise Exception( - f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" - ) - - @property - def vco_max(self): - if self.parent.transceiver_type == "GTXE2": - return 3300000000 - elif self.parent.transceiver_type == "GTHE2": - return 5160000000 - raise Exception( - f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" - ) - - M_available = [1, 2] - _M = [1, 2] - - @property - def M(self): - return self._M - - @M.setter - def M(self, value): - self._check_in_range(value, self.M_available, "M") - self._M = value - - N2_available = [1, 2, 3, 4, 5] - _N2 = [1, 2, 3, 4, 5] - - @property - def N2(self): - return self._N2 - - @N2.setter - def N2(self, value): - self._check_in_range(value, self.N2_available, "N2") - self._N2 = value - - N1_available = [4, 5] - _N1 = [4, 5] - - @property - def N1(self): - return self._N1 - - @N1.setter - def N1(self, value): - self._check_in_range(value, self.N1_available, "N1") - self._N1 = value - - D_available = [1, 2, 4, 8] - _D = [1, 2, 4, 8] - - @property - def D(self): - return self._D - - @N1.setter - def D(self, value): - self._check_in_range(value, self.D_available, "D") - self._D = value - - def get_config(self, config: dict, converter, fpga_ref) -> dict: - """Get CPLL configuration. - - Args: - config (dict): Configuration dictionary. - converter (converter): Converter object. - """ - pll_config = {} - pll_config["type"] = "cpll" - for k in ["m", "d", "n1", "n2"]: - pll_config[k] = self._get_val(config[converter.name + "_" + k + "_cpll"]) - - pll_config["vco"] = ( - fpga_ref * pll_config["n1"] * pll_config["n2"] / pll_config["m"] # type: ignore # noqa: B950 - ) - # Check - assert ( - pll_config["vco"] * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 - ), "Invalid CPLL lane rate" - - return pll_config - - def add_constraints(self, config: dict, fpga_ref, converter) -> dict: - """Add Channel PLL (CPLL) constraints. - - Args: - config (dict): Configuration dictionary. - fpga_ref (int): FPGA reference clock. - converter (converter): Converter object. - """ - if self.parent.force_cpll: - v = 1 - else: - v = [0, 1] - config[converter.name + "_use_cpll"] = self._convert_input( - v, converter.name + "_use_cpll" - ) - if v == [0, 1]: - self.model.add_kpi( - config[converter.name + "_use_cpll"], name=converter.name + "_use_cpll" - ) - # Add variables - config[converter.name + "_m_cpll"] = self._convert_input( - self._M, converter.name + "_m_cpll" - ) - # This is documented oddly - # There are footnotes that include 16 with GTHs, and 16,32 with GTYs - # but its says "not supported for CPLL"?!?!? - config[converter.name + "_d_cpll"] = self._convert_input( - self._D, converter.name + "_d_cpll" - ) - config[converter.name + "_n1_cpll"] = self._convert_input( - self._N1, converter.name + "_n1_cpll" - ) - config[converter.name + "_n2_cpll"] = self._convert_input( - self._N2, converter.name + "_n2_cpll" - ) - - # Add intermediate variables - config[converter.name + "_pll_out_cpll"] = self._add_intermediate( - fpga_ref - * config[converter.name + "_n1_cpll"] - * config[converter.name + "_n2_cpll"] - / (config[converter.name + "_m_cpll"]) - ) - - # Add constraints - self._add_equation( - [ - if_then( - config[converter.name + "_use_cpll"] == 1, - config[converter.name + "_d_cpll"] * converter.bit_clock - == config[converter.name + "_pll_out_cpll"] * 2, - ), - ] - ) - self._add_equation( - [ - if_then( - config[converter.name + "_use_cpll"] == 1, - config[converter.name + "_pll_out_cpll"] >= self.vco_min, - ), - if_then( - config[converter.name + "_use_cpll"] == 1, - config[converter.name + "_pll_out_cpll"] <= self.vco_max, - ), - ] - ) - - return config - - -class QPLL(PLLCommon): - M_available = [1, 2, 3, 4] - _M = [1, 2, 3, 4] - - @property - def M(self): - return self._M - - @M.setter - def M(self, value): - self._check_in_range(value, self.M_available, "M") - self._M = value - - N_available = [16, 20, 32, 40, 64, 66, 80, 100] - _N = [16, 20, 32, 40, 64, 66, 80, 100] - - @property - def N(self): - return self._N - - @N.setter - def N(self, value): - self._check_in_range(value, self.N_available, "N") - self._N = value - - D_available = [1, 2, 4, 8, 16] - _D = [1, 2, 4, 8, 16] - - @property - def D(self): - return self._D - - @D.setter - def D(self, value): - self._check_in_range(value, self.D_available, "D") - self._D = value - - @property - def vco_min(self): - if self.parent.transceiver_type[:3] == "GTH": - return 8000000000 - elif ( - self.parent.transceiver_type[:3] == "GTX" - ): # FIXME: This is only the lower band - return 5930000000 - raise Exception( - f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" - ) - - @property - def vco_max(self): - if self.parent.transceiver_type[:3] == "GTH": - if float(str(self.parent.speed_grade)[:2]) >= -2: - return 10312500000 - return 13100000000 - elif ( - self.parent.transceiver_type[:3] == "GTX" - ): # FIXME: This is only the lower band - if float(str(self.parent.speed_grade)[:2]) >= -2: - return 10312500000 - return 12500000000 - raise Exception( - f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" - ) - - def add_constraints(self, config: dict, fpga_ref, converter) -> dict: - """Add constraints for QPLL for 7 series FPGAs. - - Args: - config (dict): Configuration dictionary. - fpga_ref (int): FPGA reference clock. - converter (converter): Converter object. - """ - if self.parent.force_qpll: - v = 1 - else: - v = [0, 1] - # Global flag to use QPLLn - config[converter.name + "_use_qpll"] = self._convert_input( - v, converter.name + "_use_qpll" - ) - if v == [0, 1]: - self.model.add_kpi( - config[converter.name + "_use_qpll"], name=converter.name + "_use_qpll" - ) - - # Add variables - config[converter.name + "_m_qpll"] = self._convert_input( - self._M, converter.name + "_m_qpll" - ) - config[converter.name + "_d_qpll"] = self._convert_input( - self._D, converter.name + "_d_qpll" - ) - config[converter.name + "_n_qpll"] = self._convert_input( - self._N, converter.name + "_n_qpll" - ) - - # Add intermediate variables - config[converter.name + "_vco_qpll"] = self._add_intermediate( - fpga_ref - * config[converter.name + "_n_qpll"] - / config[converter.name + "_m_qpll"] - ) - config[converter.name + "_pll_out_qpll"] = self._add_intermediate( - fpga_ref - * config[converter.name + "_n_qpll"] - / (config[converter.name + "_m_qpll"] * 2) - ) - - # Add constraints - self._add_equation( - [ - if_then( - config[converter.name + "_use_qpll"] == 1, - converter.bit_clock - == config[converter.name + "_pll_out_qpll"] - * 2 - / config[converter.name + "_d_qpll"], - ), - ] - ) - - if self.parent.transceiver_type[:3] == "GTH": - self._add_equation( - [ - if_then( - config[converter.name + "_use_qpll"] == 1, - config[converter.name + "_vco_qpll"] >= int(self.vco_min), - ), - if_then( - config[converter.name + "_use_qpll"] == 1, - config[converter.name + "_vco_qpll"] <= int(self.vco_max), - ), - ] - ) - elif self.parent.transceiver_type[:3] == "GTX": - config[converter.name + "_lower_band_qpll"] = self._convert_input( - [0, 1], converter.name + "_lower_band_qpll" - ) - # Lower band - c1 = if_then( - config[converter.name + "_use_qpll"] == 1, - if_then( - config[converter.name + "_lower_band_qpll"] == 0, - config[converter.name + "_vco_qpll"] >= int(self.vco_min), - ), - ) - c2 = if_then( - config[converter.name + "_use_qpll"] == 1, - if_then( - config[converter.name + "_lower_band_qpll"] == 0, - config[converter.name + "_vco_qpll"] <= int(self.vco_max), - ), - ) - # See page 72 of https://docs.amd.com/v/u/en-US/ds191-XC7Z030-XC7Z045-data-sheet - c5 = if_then( - config[converter.name + "_use_qpll"] == 1, - if_then( - config[converter.name + "_lower_band_qpll"] == 0, - config[converter.name + "_d_qpll"] <= 8, # no 16 - ), - ) - - # Upper band - c3 = if_then( - config[converter.name + "_use_qpll"] == 1, - if_then( - config[converter.name + "_lower_band_qpll"] == 1, - config[converter.name + "_vco_qpll"] >= int(9.8e9), - ), - ) - c4 = if_then( - config[converter.name + "_use_qpll"] == 1, - if_then( - config[converter.name + "_lower_band_qpll"] == 1, - config[converter.name + "_vco_qpll"] - <= int(12.5e9), # FIXME: only for -3 silicon - ), - ) - self._add_equation([c1, c2, c3, c4, c5]) - - else: - raise Exception("Unsupported transceiver type") - - return config - - def get_config(self, config: dict, converter, fpga_ref) -> dict: - """Get QPLL configuration for 7 series FPGAs. - - Args: - config (dict): Configuration dictionary. - converter (converter): Converter object. - """ - pll_config = {"type": "qpll"} - for k in ["m", "d", "n"]: - pll_config[k] = self._get_val(config[converter.name + "_" + k + "_qpll"]) - - pll_config["vco"] = fpga_ref * pll_config["n"] / pll_config["m"] - pll_clk_out = fpga_ref * pll_config["n"] / (pll_config["m"] * 2) - # Check - assert ( - pll_clk_out * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 - ), f"Invalid QPLL lane rate {pll_config['vco'] * 2 / pll_config['d']} != {converter.bit_clock}" - - return pll_config +"""7 series transceiver model.""" + +from typing import List, Union + +from docplex.cp.modeler import if_then + +from ...converters.converter import converter as conv +from ...solvers import CpoIntVar, GK_Intermediate, GK_Operators, GKVariable +from .pll import PLLCommon, XilinxPLL + + +class SevenSeries(XilinxPLL): + """7 series Transceiver model.""" + + # References + # GTXs + # https://docs.amd.com/v/u/en-US/ug476_7Series_Transceivers + # https://docs.amd.com/v/u/en-US/ds191-XC7Z030-XC7Z045-data-sheet + + transceiver_types_available = [ + "GTXE2", + "GTHE2", + ] # We don't support GTHE2 yet! + _transceiver_type = "GTXE2" + + force_cpll = False + force_qpll = False + + def add_plls(self) -> None: + """Add PLLs to the model.""" + self.plls = {"CPLL": CPLL(self), "QPLL": QPLL(self)} + + def add_constraints( + self, + config: dict, + fpga_ref: Union[ + CpoIntVar, GK_Intermediate, GK_Operators, GKVariable, int + ], + converter: conv, + ) -> dict: + """Add constraints for PLLs. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int, CpoIntVar): FPGA reference clock. + converter (conv): Converter object. + + Returns: + dict: Updated configuration dictionary. + """ + assert self.plls, "No PLLs configured. Run the add_plls method" + assert not ( + self.force_cpll and self.force_qpll + ), "Both CPLL and QPLL enabled" + for pll in self.plls: + config = self.plls[pll].add_constraints(config, fpga_ref, converter) + self._add_equation( + config[converter.name + "_use_cpll"] + + config[converter.name + "_use_qpll"] + == 1 + ) + return config + + def get_config( + self, config: dict, converter: conv, fpga_ref: Union[int, float] + ) -> dict: + """Get PLL configuration. + + Args: + config (dict): Configuration dictionary. + converter (conv): Converter object. + fpga_ref (Union[int, float]): FPGA reference clock. + + Returns: + dict: Updated configuration dictionary. + """ + if self.force_cpll: + ecpll = 1 + eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] + elif self.force_qpll: + ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] + eqpll = 1 + else: + ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] + eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] + + assert ecpll != eqpll, "Both CPLL and QPLL enabled" + pll = "CPLL" if ecpll else "QPLL" + return self.plls[pll].get_config(config, converter, fpga_ref) + + +class CPLL(PLLCommon): + """Channel PLL (CPLL) for 7 series FPGAs.""" + + @property + def vco_min(self) -> int: + """Get the minimum VCO frequency for the transceiver type. + + Returns: + int: Minimum VCO frequency. + + Raises: + Exception: Unsupported transceiver type. + """ + if self.parent.transceiver_type == "GTXE2": + return 1600000000 + elif self.parent.transceiver_type == "GTHE2": + return 1600000000 + raise Exception( + f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" + ) + + @property + def vco_max(self) -> int: + """Get the maximum VCO frequency for the transceiver type. + + Returns: + int: Maximum VCO frequency. + + Raises: + Exception: Unsupported transceiver type. + """ + if self.parent.transceiver_type == "GTXE2": + return 3300000000 + elif self.parent.transceiver_type == "GTHE2": + return 5160000000 + raise Exception( + f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" + ) + + M_available = [1, 2] + _M = [1, 2] + + @property + def M(self) -> Union[int, List[int]]: + """Get the M value for the CPLL.""" + return self._M + + @M.setter + def M(self, value: Union[int, List[int]]) -> None: + """Set the M value for the CPLL.""" + self._check_in_range(value, self.M_available, "M") + self._M = value + + N2_available = [1, 2, 3, 4, 5] + _N2 = [1, 2, 3, 4, 5] + + @property + def N2(self) -> Union[int, List[int]]: + """Get the N2 value for the CPLL.""" + return self._N2 + + @N2.setter + def N2(self, value: Union[int, List[int]]) -> None: + """Set the N2 value for the CPLL.""" + self._check_in_range(value, self.N2_available, "N2") + self._N2 = value + + N1_available = [4, 5] + _N1 = [4, 5] + + @property + def N1(self) -> Union[int, List[int]]: + """Get the N1 value for the CPLL.""" + return self._N1 + + @N1.setter + def N1(self, value: Union[int, List[int]]) -> None: + """Set the N1 value for the CPLL.""" + self._check_in_range(value, self.N1_available, "N1") + self._N1 = value + + D_available = [1, 2, 4, 8] + _D = [1, 2, 4, 8] + + @property + def D(self) -> Union[int, List[int]]: + """Get the D value for the CPLL.""" + return self._D + + @D.setter + def D(self, value: Union[int, List[int]]) -> None: + """Set the D value for the CPLL.""" + self._check_in_range(value, self.D_available, "D") + self._D = value + + def get_config( + self, config: dict, converter: conv, fpga_ref: Union[int, CpoIntVar] + ) -> dict: + """Get CPLL configuration. + + Args: + config (dict): Configuration dictionary. + converter (conv): Converter object. + fpga_ref (int, CpoIntVar): FPGA reference clock. + + Returns: + dict: Updated configuration dictionary. + """ + pll_config = {} + pll_config["type"] = "cpll" + for k in ["m", "d", "n1", "n2"]: + pll_config[k] = self._get_val( + config[converter.name + "_" + k + "_cpll"] + ) + + pll_config["vco"] = ( + fpga_ref * pll_config["n1"] * pll_config["n2"] / pll_config["m"] # type: ignore # noqa: B950 + ) + # Check + assert ( + pll_config["vco"] * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 + ), "Invalid CPLL lane rate" + + return pll_config + + def add_constraints( + self, config: dict, fpga_ref: Union[int, CpoIntVar], converter: conv + ) -> dict: + """Add Channel PLL (CPLL) constraints. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int, CpoIntVar): FPGA reference clock. + converter (conv): Converter object. + + Returns: + dict: Updated configuration dictionary. + """ + if self.parent.force_cpll: + v = 1 + else: + v = [0, 1] + config[converter.name + "_use_cpll"] = self._convert_input( + v, converter.name + "_use_cpll" + ) + if v == [0, 1]: + self.model.add_kpi( + config[converter.name + "_use_cpll"], + name=converter.name + "_use_cpll", + ) + # Add variables + config[converter.name + "_m_cpll"] = self._convert_input( + self._M, converter.name + "_m_cpll" + ) + # This is documented oddly + # There are footnotes that include 16 with GTHs, and 16,32 with GTYs + # but its says "not supported for CPLL"?!?!? + config[converter.name + "_d_cpll"] = self._convert_input( + self._D, converter.name + "_d_cpll" + ) + config[converter.name + "_n1_cpll"] = self._convert_input( + self._N1, converter.name + "_n1_cpll" + ) + config[converter.name + "_n2_cpll"] = self._convert_input( + self._N2, converter.name + "_n2_cpll" + ) + + # Add intermediate variables + config[converter.name + "_pll_out_cpll"] = self._add_intermediate( + fpga_ref + * config[converter.name + "_n1_cpll"] + * config[converter.name + "_n2_cpll"] + / (config[converter.name + "_m_cpll"]) + ) + + # Add constraints + self._add_equation( + [ + if_then( + config[converter.name + "_use_cpll"] == 1, + config[converter.name + "_d_cpll"] * converter.bit_clock + == config[converter.name + "_pll_out_cpll"] * 2, + ), + ] + ) + self._add_equation( + [ + if_then( + config[converter.name + "_use_cpll"] == 1, + config[converter.name + "_pll_out_cpll"] >= self.vco_min, + ), + if_then( + config[converter.name + "_use_cpll"] == 1, + config[converter.name + "_pll_out_cpll"] <= self.vco_max, + ), + ] + ) + + return config + + +class QPLL(PLLCommon): + """QPLL for 7 series FPGAs.""" + + M_available = [1, 2, 3, 4] + _M = [1, 2, 3, 4] + + @property + def M(self) -> Union[int, List[int]]: + """Get the M value for the QPLL.""" + return self._M + + @M.setter + def M(self, value: Union[int, List[int]]) -> None: + """Set the M value for the QPLL.""" + self._check_in_range(value, self.M_available, "M") + self._M = value + + N_available = [16, 20, 32, 40, 64, 66, 80, 100] + _N = [16, 20, 32, 40, 64, 66, 80, 100] + + @property + def N(self) -> Union[int, List[int]]: + """Get the N value for the QPLL.""" + return self._N + + @N.setter + def N(self, value: Union[int, List[int]]) -> None: + """Set the N value for the QPLL.""" + self._check_in_range(value, self.N_available, "N") + self._N = value + + D_available = [1, 2, 4, 8, 16] + _D = [1, 2, 4, 8, 16] + + @property + def D(self) -> Union[int, List[int]]: + """Get the D value for the QPLL.""" + return self._D + + @D.setter + def D(self, value: Union[int, List[int]]) -> None: + """Set the D value for the QPLL.""" + self._check_in_range(value, self.D_available, "D") + self._D = value + + @property + def vco_min(self) -> int: + """Get the minimum VCO frequency for the transceiver type. + + Returns: + int: Minimum VCO frequency. + + Raises: + Exception: Unsupported transceiver type. + """ + if self.parent.transceiver_type[:3] == "GTH": + return 8000000000 + elif ( + self.parent.transceiver_type[:3] == "GTX" + ): # FIXME: This is only the lower band + return 5930000000 + raise Exception( + f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" + ) + + @property + def vco_max(self) -> int: + """Get the maximum VCO frequency for the transceiver type. + + Returns: + int: Maximum VCO frequency. + + Raises: + Exception: Unsupported transceiver type. + """ + if self.parent.transceiver_type[:3] == "GTH": + if float(str(self.parent.speed_grade)[:2]) >= -2: + return 10312500000 + return 13100000000 + elif ( + self.parent.transceiver_type[:3] == "GTX" + ): # FIXME: This is only the lower band + if float(str(self.parent.speed_grade)[:2]) >= -2: + return 10312500000 + return 12500000000 + raise Exception( + f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" + ) + + def add_constraints( + self, config: dict, fpga_ref: Union[int, CpoIntVar], converter: conv + ) -> dict: + """Add constraints for QPLL for 7 series FPGAs. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int, CpoIntVar): FPGA reference clock. + converter (conv): Converter object. + + Returns: + dict: Updated configuration dictionary. + + Raises: + Exception: Unsupported transceiver type. + """ + if self.parent.force_qpll: + v = 1 + else: + v = [0, 1] + # Global flag to use QPLLn + config[converter.name + "_use_qpll"] = self._convert_input( + v, converter.name + "_use_qpll" + ) + if v == [0, 1]: + self.model.add_kpi( + config[converter.name + "_use_qpll"], + name=converter.name + "_use_qpll", + ) + + # Add variables + config[converter.name + "_m_qpll"] = self._convert_input( + self._M, converter.name + "_m_qpll" + ) + config[converter.name + "_d_qpll"] = self._convert_input( + self._D, converter.name + "_d_qpll" + ) + config[converter.name + "_n_qpll"] = self._convert_input( + self._N, converter.name + "_n_qpll" + ) + + # Add intermediate variables + config[converter.name + "_vco_qpll"] = self._add_intermediate( + fpga_ref + * config[converter.name + "_n_qpll"] + / config[converter.name + "_m_qpll"] + ) + config[converter.name + "_pll_out_qpll"] = self._add_intermediate( + fpga_ref + * config[converter.name + "_n_qpll"] + / (config[converter.name + "_m_qpll"] * 2) + ) + + # Add constraints + self._add_equation( + [ + if_then( + config[converter.name + "_use_qpll"] == 1, + converter.bit_clock + == config[converter.name + "_pll_out_qpll"] + * 2 + / config[converter.name + "_d_qpll"], + ), + ] + ) + + if self.parent.transceiver_type[:3] == "GTH": + self._add_equation( + [ + if_then( + config[converter.name + "_use_qpll"] == 1, + config[converter.name + "_vco_qpll"] + >= int(self.vco_min), + ), + if_then( + config[converter.name + "_use_qpll"] == 1, + config[converter.name + "_vco_qpll"] + <= int(self.vco_max), + ), + ] + ) + elif self.parent.transceiver_type[:3] == "GTX": + config[converter.name + "_lower_band_qpll"] = self._convert_input( + [0, 1], converter.name + "_lower_band_qpll" + ) + # Lower band + c1 = if_then( + config[converter.name + "_use_qpll"] == 1, + if_then( + config[converter.name + "_lower_band_qpll"] == 0, + config[converter.name + "_vco_qpll"] >= int(self.vco_min), + ), + ) + c2 = if_then( + config[converter.name + "_use_qpll"] == 1, + if_then( + config[converter.name + "_lower_band_qpll"] == 0, + config[converter.name + "_vco_qpll"] <= int(self.vco_max), + ), + ) + # See page 72 of https://docs.amd.com/v/u/en-US/ds191-XC7Z030-XC7Z045-data-sheet # noqa: B950 + c5 = if_then( + config[converter.name + "_use_qpll"] == 1, + if_then( + config[converter.name + "_lower_band_qpll"] == 0, + config[converter.name + "_d_qpll"] <= 8, # no 16 + ), + ) + + # Upper band + c3 = if_then( + config[converter.name + "_use_qpll"] == 1, + if_then( + config[converter.name + "_lower_band_qpll"] == 1, + config[converter.name + "_vco_qpll"] >= int(9.8e9), + ), + ) + c4 = if_then( + config[converter.name + "_use_qpll"] == 1, + if_then( + config[converter.name + "_lower_band_qpll"] == 1, + config[converter.name + "_vco_qpll"] + <= int(12.5e9), # FIXME: only for -3 silicon + ), + ) + self._add_equation([c1, c2, c3, c4, c5]) + + else: + raise Exception("Unsupported transceiver type") + + return config + + def get_config( + self, config: dict, converter: conv, fpga_ref: Union[int, float] + ) -> dict: + """Get QPLL configuration for 7 series FPGAs. + + Args: + config (dict): Configuration dictionary. + converter (conv): Converter object. + fpga_ref (Union[int, float]): FPGA reference clock. + + Returns: + dict: Updated configuration dictionary. + """ + pll_config = {"type": "qpll"} + for k in ["m", "d", "n"]: + pll_config[k] = self._get_val( + config[converter.name + "_" + k + "_qpll"] + ) + + pll_config["vco"] = fpga_ref * pll_config["n"] / pll_config["m"] + pll_clk_out = fpga_ref * pll_config["n"] / (pll_config["m"] * 2) + # Check + assert ( + pll_clk_out * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950 + ), ( + f"Invalid QPLL lane rate {pll_config['vco'] * 2 / pll_config['d']} " + + f"!= {converter.bit_clock}" + ) + + return pll_config diff --git a/adijif/fpgas/xilinx/ultrascaleplus.py b/adijif/fpgas/xilinx/ultrascaleplus.py index a205451..3606cf6 100644 --- a/adijif/fpgas/xilinx/ultrascaleplus.py +++ b/adijif/fpgas/xilinx/ultrascaleplus.py @@ -1,503 +1,503 @@ -"""Ultrascale+ PLLs transceiver models.""" - -from typing import Union - -from docplex.cp.modeler import if_then - -from ...common import core -from ...converters.converter import converter as conv -from ...gekko_trans import gekko_translation -from ...solvers import CpoIntVar, GK_Intermediate, GK_Operators, GKVariable -from .pll import XilinxPLL -from .sevenseries import CPLL as SevenSeriesCPLL -from .sevenseries import QPLL as SevenSeriesQPLL - - -class UltraScalePlus(XilinxPLL, core, gekko_translation): - """Ultrascale+ PLLs transceiver models.""" - - # References - # GTYs - # https://docs.amd.com/v/u/en-US/ug578-ultrascale-gty-transceivers - # https://docs.amd.com/r/en-US/ds925-zynq-ultrascale-plus/GTY-Transceiver-Switching-Characteristics - # GTHs - # https://docs.amd.com/v/u/en-US/ug576-ultrascale-gth-transceivers - - transceiver_types_available = ["GTHE4", "GTYE3", "GTYE4"] - _transceiver_type = "GTHE4" - - force_cpll = False - force_qpll = False - force_qpll1 = False - - def add_plls(self) -> None: - """Add PLLs to the model.""" - self.plls = { - "CPLL": CPLL(self), - "QPLL": QPLL(self), - "QPLL1": QPLL1(self), - } - - def add_constraints( - self, config: dict, fpga_ref: Union[int, float], converter: conv - ) -> dict: - """Add constraints for PLLs. - - Args: - config (dict): Configuration dictionary. - fpga_ref (int, float): FPGA reference clock. - converter (conv): Converter object. - - Returns: - dict: Updated configuration dictionary. - """ - assert self.plls, "No PLLs configured. Run the add_plls method" - assert ( - self.force_cpll + self.force_qpll + self.force_qpll1 <= 1 - ), "Only one PLL can be enabled" - for pll in self.plls: - config = self.plls[pll].add_constraints(config, fpga_ref, converter) - self._add_equation( - config[converter.name + "_use_cpll"] - + config[converter.name + "_use_qpll"] - + config[converter.name + "_use_qpll1"] - == 1 - ) - return config - - def get_config( - self, config: dict, converter: conv, fpga_ref: Union[int, float] - ) -> dict: - """Get the configuration of the PLLs. - - Args: - config (dict): Configuration dictionary. - converter (conv): Converter object. - fpga_ref (int, float): FPGA reference clock. - - Returns: - dict: Updated configuration dictionary. - """ - if self.force_cpll or self.force_qpll or self.force_qpll1: - if self.force_cpll: - ecpll = 1 - eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] - eqpll1 = self.solution.get_kpis()[converter.name + "_use_qpll1"] - elif self.force_qpll: - ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] - eqpll = 1 - eqpll1 = self.solution.get_kpis()[converter.name + "_use_qpll1"] - else: - ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] - eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] - eqpll1 = 1 - else: - ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] - eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] - eqpll1 = self.solution.get_kpis()[converter.name + "_use_qpll1"] - assert ecpll + eqpll + eqpll1 == 1, "Only one PLL can be enabled" - if ecpll: - pll = "CPLL" - elif eqpll: - pll = "QPLL" - else: - pll = "QPLL1" - return self.plls[pll].get_config(config, converter, fpga_ref) - - -class CPLL(SevenSeriesCPLL): - """CPLL model for Ultrascale+ transceivers.""" - - @property - def vco_min(self) -> int: - """Get the VCO min frequency in Hz for CPLL.""" - if self.parent.transceiver_type in ["GTHE4", "GTYE3", "GTYE4"]: - return 2000000000 - raise Exception( - f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" - ) - - @property - def vco_max(self) -> int: - """Get the VCO max frequency in Hz for CPLL.""" - if self.parent.transceiver_type in ["GTHE4", "GTYE3", "GTYE4"]: - return 6250000000 - raise Exception( - f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" - ) - - -class QPLL(SevenSeriesQPLL): - """QPLL model for Ultrascale+ transceivers.""" - - force_integer_mode = False - - @property - def vco_min(self) -> int: - """Get the VCO min frequency in Hz for QPLL.""" - if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE4"]: - return 9800000000 - raise Exception( - f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" - ) - - @property - def vco_max(self) -> int: - """Get the VCO max frequency in Hz for QPLL.""" - if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE4"]: - return 16375000000 - raise Exception( - f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" - ) - - N_available = [*range(16, 160 + 1)] - _N = [*range(16, 160 + 1)] - - D_available = [1, 2, 4, 8, 16] - _D = [1, 2, 4, 8, 16] - # 32 not available in AC modes https://docs.amd.com/r/en-US/ds925-zynq-ultrascale-plus/GTY-Transceiver-Switching-Characteristics # type: ignore # noqa: B950 - - @property - def QPLL_CLKOUTRATE_available(self) -> list[int]: - """Get the QPLL_CLKOUTRATE available values.""" - if self.parent.transceiver_type == "GTHE4": - return [1, 2] - return [1] - - _QPLL_CLKOUTRATE_GTY = [1, 2] - _QPLL_CLKOUTRATE_GTH = [1, 2] - - @property - def QPLL_CLKOUTRATE(self) -> int: - """Get the QPLL_CLKOUTRATE value.""" - if "GTH" in self.parent.transceiver_type: - return self._QPLL_CLKOUTRATE_GTH - return self._QPLL_CLKOUTRATE_GTY - - @QPLL_CLKOUTRATE.setter - def QPLL_CLKOUTRATE(self, val: int) -> None: - """Set the QPLL_CLKOUTRATE. - - Args: - val (int): QPLL_CLKOUTRATE value. - - Raises: - ValueError: If QPLL_CLKOUTRATE is out of range. - """ - self._check_in_range( - val, self.QPLL_CLKOUTRATE_available, "QPLL_CLKOUTRATE" - ) - if "GTH" in self.parent.transceiver_type: - self._QPLL_CLKOUTRATE_GTH = val - raise ValueError( - f"QPLL_CLKOUTRATE not available for {self.parent.transceiver_type}" - ) - - SDMDATA_min_max = [0, 2**24 - 1] - _SDMDATA_min = 0 - - @property - def SDMDATA_min(self) -> int: - """Get the SDMDATA_min value.""" - return self._SDMDATA_min - - @SDMDATA_min.setter - def SDMDATA_min(self, val: int) -> None: - """Set the SDMDATA_min. - - Args: - val (int): SDMDATA_min value. - - Raises: - ValueError: If SDMDATA_min is out of range. - """ - if val < self.SDMDATA_min_max[0] or val > self.SDMDATA_min_max[1]: - raise ValueError( - f"SDMDATA_min must be between {self.SDMDATA_min_max[0]} and" - + f" {self.SDMDATA_min_max[1]}" - ) - self._SDMDATA_min = val - - _SDMDATA_max = 2**24 - 1 - - @property - def SDMDATA_max(self) -> int: - """Get the SDMDATA_max value.""" - return self._SDMDATA_max - - @SDMDATA_max.setter - def SDMDATA_max(self, val: int) -> None: - """Set the SDMDATA_max. - - Args: - val (int): SDMDATA_max value. - - Raises: - ValueError: If SDMDATA_max is out of range. - """ - if val < self.SDMDATA_min_max[0] or val > self.SDMDATA_min_max[1]: - raise ValueError( - f"SDMDATA must be between {self.SDMDATA_min_max[0]} and" - + f" {self.SDMDATA_min_max[1]}" - ) - self._SDMDATA_max = val - - SDMWIDTH_available = [16, 20, 24] - _SDMWIDTH = [16, 20, 24] - - @property - def SDMWIDTH(self) -> int: - """Get the SDMWIDTH value.""" - return self._SDMWIDTH - - @SDMWIDTH.setter - def SDMWIDTH(self, val: int) -> None: - """Set the SDMWIDTH. - - Args: - val (int): SDMWIDTH value. - """ - self._check_in_range(val, self.SDMWIDTH_available, "SDMWIDTH") - self._SDMWIDTH = val - - _pname = "qpll" - - def get_config( - self, config: dict, converter: conv, fpga_ref: Union[int, float] - ) -> dict: - """Get the configuration of the QPLL. - - Args: - config (dict): Configuration dictionary. - converter (conv): Converter object. - fpga_ref (int, float): FPGA reference clock. - - Returns: - dict: Updated configuration dictionary. - """ - pname = self._pname - pll_config = {"type": self._pname} - pll_config["n"] = self._get_val(config[converter.name + f"_n_{pname}"]) - pll_config["m"] = self._get_val(config[converter.name + f"_m_{pname}"]) - pll_config["d"] = self._get_val(config[converter.name + f"_d_{pname}"]) - pll_config["clkout_rate"] = self._get_val( - config[converter.name + f"_clkout_rate_{pname}"] - ) - if converter.bit_clock < 28.1e9 and not self.force_integer_mode: - sdm_data = self._get_val( - config[converter.name + f"_sdm_data_{pname}"] - ) - if sdm_data > 0: - pll_config["sdm_data"] = sdm_data - pll_config["sdm_width"] = self._get_val( - config[converter.name + f"_sdm_width_{pname}"] - ) - pll_config["frac"] = self.solution.get_kpis()[ - converter.name + f"_frac_{pname}" - ] - pll_config["n_dot_frac"] = self.solution.get_kpis()[ - converter.name + f"_n_dot_frac_{pname}" - ] - else: - pll_config["n_dot_frac"] = pll_config["n"] - else: - pll_config["n_dot_frac"] = pll_config["n"] - - pll_config["n"] = pll_config["n_dot_frac"] - - # config['vco'] = self._get_val(config[converter.name + f"_vco_{pname}"]) - pll_config["vco"] = self.solution.get_kpis()[ - converter.name + f"_vco_{pname}" - ] - - # Check - pll_out = ( - fpga_ref - * pll_config["n_dot_frac"] - / (pll_config["m"] * pll_config["clkout_rate"]) - ) - lane_rate = pll_out * 2 / pll_config["d"] - assert ( - lane_rate == converter.bit_clock - ), f"{lane_rate} != {converter.bit_clock}" - - return pll_config - - def add_constraints( - self, - config: dict, - fpga_ref: Union[ - int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar - ], - converter: conv, - ) -> dict: - """Add constraints for the Transceiver. - - Args: - config (dict): Configuration dictionary. - fpga_ref (int, CpoIntVar): FPGA reference clock. - converter (conv): Converter object. - - Returns: - dict: Updated configuration dictionary. - """ - pname = self._pname - - # Global flag to use QPLLn - if self.parent.force_qpll and self._pname == "qpll": - v = 1 - elif self.parent.force_qpll1 and self._pname == "qpll1": - v = 1 - elif self.parent.force_cpll and self._pname == "cpll": - v = 1 - else: - v = [0, 1] - - config[converter.name + f"_use_{pname}"] = self._convert_input( - v, converter.name + f"_use_{pname}" - ) - if v == [0, 1]: - self.model.add_kpi( - config[converter.name + f"_use_{pname}"], - name=converter.name + f"_use_{pname}", - ) - - # Add variables - config[converter.name + f"_m_{pname}"] = self._convert_input( - self.M, converter.name + f"_m_{pname}" - ) - config[converter.name + f"_d_{pname}"] = self._convert_input( - self.D, converter.name + f"_d_{pname}" - ) - config[converter.name + f"_n_{pname}"] = self._convert_input( - self.N, converter.name + f"_n_{pname}" - ) - config[converter.name + f"_clkout_rate_{pname}"] = self._convert_input( - self.QPLL_CLKOUTRATE, converter.name + f"_clkout_rate_{pname}" - ) - - # Frac part - if converter.bit_clock < 28.1e9 and not self.force_integer_mode: - config[converter.name + f"_sdm_data_{pname}"] = ( - self.model.integer_var( - min=self.SDMDATA_min, - max=self.SDMDATA_max, - name=converter.name + f"_sdm_data_{pname}", - ) - ) - config[converter.name + f"_sdm_width_{pname}"] = ( - self._convert_input( - self.SDMWIDTH, converter.name + f"_sdm_width_{pname}" - ) - ) - config[converter.name + f"_HIGH_RATE_{pname}"] = ( - self._convert_input( - self.QPLL_CLKOUTRATE, converter.name + f"_HIGH_RATE_{pname}" - ) - ) - - # Add intermediate variables - if converter.bit_clock < 28.1e9 and not self.force_integer_mode: - config[converter.name + f"_frac_{pname}"] = self._add_intermediate( - config[converter.name + f"_sdm_data_{pname}"] - / (2 ** config[converter.name + f"_sdm_width_{pname}"]) - ) - self.model.add_kpi( - config[converter.name + f"_frac_{pname}"], - name=converter.name + f"_frac_{pname}", - ) - self._add_equation([config[converter.name + f"_frac_{pname}"] < 1]) - config[converter.name + f"_n_dot_frac_{pname}"] = ( - self._add_intermediate( - config[converter.name + f"_n_{pname}"] - + config[converter.name + f"_frac_{pname}"] - ) - ) - self.model.add_kpi( - config[converter.name + f"_n_dot_frac_{pname}"], - name=converter.name + f"_n_dot_frac_{pname}", - ) - else: - config[converter.name + f"_n_dot_frac_{pname}"] = ( - self._add_intermediate(config[converter.name + f"_n_{pname}"]) - ) - - config[converter.name + f"_pll_out_{pname}"] = self._add_intermediate( - fpga_ref - * config[converter.name + f"_n_dot_frac_{pname}"] - / ( - config[converter.name + f"_m_{pname}"] - * config[converter.name + f"_clkout_rate_{pname}"] - ) - ) - config[converter.name + f"_vco_{pname}"] = self._add_intermediate( - fpga_ref - * config[converter.name + f"_n_dot_frac_{pname}"] - / ( - config[converter.name + f"_m_{pname}"] - * config[converter.name + f"_clkout_rate_{pname}"] - ) - ) - self.model.add_kpi( - config[converter.name + f"_vco_{pname}"], - name=converter.name + f"_vco_{pname}", - ) - - # Add constraints - self._add_equation( - [ - if_then( - config[converter.name + f"_use_{pname}"] == 1, - converter.bit_clock * config[converter.name + f"_d_{pname}"] - == config[converter.name + f"_pll_out_{pname}"] * 2, - ), - # if_then( - # config[converter.name + f"_use_{pname}"] == 1, - # config[converter.name + f"_HIGH_RATE_{pname}"] - # == (converter.bit_clock >= 28.1e9), - # ), - ] - ) - - self._add_equation( - [ - if_then( - config[converter.name + f"_use_{pname}"] == 1, - config[converter.name + f"_vco_{pname}"] >= self.vco_min, - ), - if_then( - config[converter.name + f"_use_{pname}"] == 1, - config[converter.name + f"_vco_{pname}"] <= self.vco_max, - ), - # if_then( - # config[converter.name + f"_HIGH_RATE_{pname}"] == 1, - # config[converter.name + f"_frac_{pname}"] == 0, - # ), - ] - ) - - return config - - -class QPLL1(QPLL): - """QPLL1 model for Ultrascale+ transceivers.""" - - _pname = "qpll1" - - @property - def vco_min(self) -> int: - """VCO min frequency in Hz for QPLL1.""" - if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE3", "GTYE4"]: - return 8000000000 - raise Exception( - f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" - ) - - @property - def vco_max(self) -> int: - """VCO max frequency in Hz for QPLL1.""" - if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE3", "GTYE4"]: - return 13000000000 - raise Exception( - f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" - ) +"""Ultrascale+ PLLs transceiver models.""" + +from typing import List, Union + +from docplex.cp.modeler import if_then + +from ...common import core +from ...converters.converter import converter as conv +from ...gekko_trans import gekko_translation +from ...solvers import CpoIntVar, GK_Intermediate, GK_Operators, GKVariable +from .pll import XilinxPLL +from .sevenseries import CPLL as SevenSeriesCPLL +from .sevenseries import QPLL as SevenSeriesQPLL + + +class UltraScalePlus(XilinxPLL, core, gekko_translation): + """Ultrascale+ PLLs transceiver models.""" + + # References + # GTYs + # https://docs.amd.com/v/u/en-US/ug578-ultrascale-gty-transceivers + # https://docs.amd.com/r/en-US/ds925-zynq-ultrascale-plus/GTY-Transceiver-Switching-Characteristics + # GTHs + # https://docs.amd.com/v/u/en-US/ug576-ultrascale-gth-transceivers + + transceiver_types_available = ["GTHE4", "GTYE3", "GTYE4"] + _transceiver_type = "GTHE4" + + force_cpll = False + force_qpll = False + force_qpll1 = False + + def add_plls(self) -> None: + """Add PLLs to the model.""" + self.plls = { + "CPLL": CPLL(self), + "QPLL": QPLL(self), + "QPLL1": QPLL1(self), + } + + def add_constraints( + self, config: dict, fpga_ref: Union[int, float], converter: conv + ) -> dict: + """Add constraints for PLLs. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int, float): FPGA reference clock. + converter (conv): Converter object. + + Returns: + dict: Updated configuration dictionary. + """ + assert self.plls, "No PLLs configured. Run the add_plls method" + assert ( + self.force_cpll + self.force_qpll + self.force_qpll1 <= 1 + ), "Only one PLL can be enabled" + for pll in self.plls: + config = self.plls[pll].add_constraints(config, fpga_ref, converter) + self._add_equation( + config[converter.name + "_use_cpll"] + + config[converter.name + "_use_qpll"] + + config[converter.name + "_use_qpll1"] + == 1 + ) + return config + + def get_config( + self, config: dict, converter: conv, fpga_ref: Union[int, float] + ) -> dict: + """Get the configuration of the PLLs. + + Args: + config (dict): Configuration dictionary. + converter (conv): Converter object. + fpga_ref (int, float): FPGA reference clock. + + Returns: + dict: Updated configuration dictionary. + """ + if self.force_cpll or self.force_qpll or self.force_qpll1: + if self.force_cpll: + ecpll = 1 + eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] + eqpll1 = self.solution.get_kpis()[converter.name + "_use_qpll1"] + elif self.force_qpll: + ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] + eqpll = 1 + eqpll1 = self.solution.get_kpis()[converter.name + "_use_qpll1"] + else: + ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] + eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] + eqpll1 = 1 + else: + ecpll = self.solution.get_kpis()[converter.name + "_use_cpll"] + eqpll = self.solution.get_kpis()[converter.name + "_use_qpll"] + eqpll1 = self.solution.get_kpis()[converter.name + "_use_qpll1"] + assert ecpll + eqpll + eqpll1 == 1, "Only one PLL can be enabled" + if ecpll: + pll = "CPLL" + elif eqpll: + pll = "QPLL" + else: + pll = "QPLL1" + return self.plls[pll].get_config(config, converter, fpga_ref) + + +class CPLL(SevenSeriesCPLL): + """CPLL model for Ultrascale+ transceivers.""" + + @property + def vco_min(self) -> int: + """Get the VCO min frequency in Hz for CPLL.""" + if self.parent.transceiver_type in ["GTHE4", "GTYE3", "GTYE4"]: + return 2000000000 + raise Exception( + f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" + ) + + @property + def vco_max(self) -> int: + """Get the VCO max frequency in Hz for CPLL.""" + if self.parent.transceiver_type in ["GTHE4", "GTYE3", "GTYE4"]: + return 6250000000 + raise Exception( + f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" + ) + + +class QPLL(SevenSeriesQPLL): + """QPLL model for Ultrascale+ transceivers.""" + + force_integer_mode = False + + @property + def vco_min(self) -> int: + """Get the VCO min frequency in Hz for QPLL.""" + if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE4"]: + return 9800000000 + raise Exception( + f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" + ) + + @property + def vco_max(self) -> int: + """Get the VCO max frequency in Hz for QPLL.""" + if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE4"]: + return 16375000000 + raise Exception( + f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" + ) + + N_available = [*range(16, 160 + 1)] + _N = [*range(16, 160 + 1)] + + D_available = [1, 2, 4, 8, 16] + _D = [1, 2, 4, 8, 16] + # 32 not available in AC modes https://docs.amd.com/r/en-US/ds925-zynq-ultrascale-plus/GTY-Transceiver-Switching-Characteristics # type: ignore # noqa: B950 + + @property + def QPLL_CLKOUTRATE_available(self) -> List[int]: + """Get the QPLL_CLKOUTRATE available values.""" + if self.parent.transceiver_type == "GTHE4": + return [1, 2] + return [1] + + _QPLL_CLKOUTRATE_GTY = [1, 2] + _QPLL_CLKOUTRATE_GTH = [1, 2] + + @property + def QPLL_CLKOUTRATE(self) -> int: + """Get the QPLL_CLKOUTRATE value.""" + if "GTH" in self.parent.transceiver_type: + return self._QPLL_CLKOUTRATE_GTH + return self._QPLL_CLKOUTRATE_GTY + + @QPLL_CLKOUTRATE.setter + def QPLL_CLKOUTRATE(self, val: int) -> None: + """Set the QPLL_CLKOUTRATE. + + Args: + val (int): QPLL_CLKOUTRATE value. + + Raises: + ValueError: If QPLL_CLKOUTRATE is out of range. + """ + self._check_in_range( + val, self.QPLL_CLKOUTRATE_available, "QPLL_CLKOUTRATE" + ) + if "GTH" in self.parent.transceiver_type: + self._QPLL_CLKOUTRATE_GTH = val + raise ValueError( + f"QPLL_CLKOUTRATE not available for {self.parent.transceiver_type}" + ) + + SDMDATA_min_max = [0, 2**24 - 1] + _SDMDATA_min = 0 + + @property + def SDMDATA_min(self) -> int: + """Get the SDMDATA_min value.""" + return self._SDMDATA_min + + @SDMDATA_min.setter + def SDMDATA_min(self, val: int) -> None: + """Set the SDMDATA_min. + + Args: + val (int): SDMDATA_min value. + + Raises: + ValueError: If SDMDATA_min is out of range. + """ + if val < self.SDMDATA_min_max[0] or val > self.SDMDATA_min_max[1]: + raise ValueError( + f"SDMDATA_min must be between {self.SDMDATA_min_max[0]} and" + + f" {self.SDMDATA_min_max[1]}" + ) + self._SDMDATA_min = val + + _SDMDATA_max = 2**24 - 1 + + @property + def SDMDATA_max(self) -> int: + """Get the SDMDATA_max value.""" + return self._SDMDATA_max + + @SDMDATA_max.setter + def SDMDATA_max(self, val: int) -> None: + """Set the SDMDATA_max. + + Args: + val (int): SDMDATA_max value. + + Raises: + ValueError: If SDMDATA_max is out of range. + """ + if val < self.SDMDATA_min_max[0] or val > self.SDMDATA_min_max[1]: + raise ValueError( + f"SDMDATA must be between {self.SDMDATA_min_max[0]} and" + + f" {self.SDMDATA_min_max[1]}" + ) + self._SDMDATA_max = val + + SDMWIDTH_available = [16, 20, 24] + _SDMWIDTH = [16, 20, 24] + + @property + def SDMWIDTH(self) -> int: + """Get the SDMWIDTH value.""" + return self._SDMWIDTH + + @SDMWIDTH.setter + def SDMWIDTH(self, val: int) -> None: + """Set the SDMWIDTH. + + Args: + val (int): SDMWIDTH value. + """ + self._check_in_range(val, self.SDMWIDTH_available, "SDMWIDTH") + self._SDMWIDTH = val + + _pname = "qpll" + + def get_config( + self, config: dict, converter: conv, fpga_ref: Union[int, float] + ) -> dict: + """Get the configuration of the QPLL. + + Args: + config (dict): Configuration dictionary. + converter (conv): Converter object. + fpga_ref (int, float): FPGA reference clock. + + Returns: + dict: Updated configuration dictionary. + """ + pname = self._pname + pll_config = {"type": self._pname} + pll_config["n"] = self._get_val(config[converter.name + f"_n_{pname}"]) + pll_config["m"] = self._get_val(config[converter.name + f"_m_{pname}"]) + pll_config["d"] = self._get_val(config[converter.name + f"_d_{pname}"]) + pll_config["clkout_rate"] = self._get_val( + config[converter.name + f"_clkout_rate_{pname}"] + ) + if converter.bit_clock < 28.1e9 and not self.force_integer_mode: + sdm_data = self._get_val( + config[converter.name + f"_sdm_data_{pname}"] + ) + if sdm_data > 0: + pll_config["sdm_data"] = sdm_data + pll_config["sdm_width"] = self._get_val( + config[converter.name + f"_sdm_width_{pname}"] + ) + pll_config["frac"] = self.solution.get_kpis()[ + converter.name + f"_frac_{pname}" + ] + pll_config["n_dot_frac"] = self.solution.get_kpis()[ + converter.name + f"_n_dot_frac_{pname}" + ] + else: + pll_config["n_dot_frac"] = pll_config["n"] + else: + pll_config["n_dot_frac"] = pll_config["n"] + + pll_config["n"] = pll_config["n_dot_frac"] + + # config['vco'] = self._get_val(config[converter.name + f"_vco_{pname}"]) + pll_config["vco"] = self.solution.get_kpis()[ + converter.name + f"_vco_{pname}" + ] + + # Check + pll_out = ( + fpga_ref + * pll_config["n_dot_frac"] + / (pll_config["m"] * pll_config["clkout_rate"]) + ) + lane_rate = pll_out * 2 / pll_config["d"] + assert ( + lane_rate == converter.bit_clock + ), f"{lane_rate} != {converter.bit_clock}" + + return pll_config + + def add_constraints( + self, + config: dict, + fpga_ref: Union[ + int, GKVariable, GK_Intermediate, GK_Operators, CpoIntVar + ], + converter: conv, + ) -> dict: + """Add constraints for the Transceiver. + + Args: + config (dict): Configuration dictionary. + fpga_ref (int, CpoIntVar): FPGA reference clock. + converter (conv): Converter object. + + Returns: + dict: Updated configuration dictionary. + """ + pname = self._pname + + # Global flag to use QPLLn + if self.parent.force_qpll and self._pname == "qpll": + v = 1 + elif self.parent.force_qpll1 and self._pname == "qpll1": + v = 1 + elif self.parent.force_cpll and self._pname == "cpll": + v = 1 + else: + v = [0, 1] + + config[converter.name + f"_use_{pname}"] = self._convert_input( + v, converter.name + f"_use_{pname}" + ) + if v == [0, 1]: + self.model.add_kpi( + config[converter.name + f"_use_{pname}"], + name=converter.name + f"_use_{pname}", + ) + + # Add variables + config[converter.name + f"_m_{pname}"] = self._convert_input( + self.M, converter.name + f"_m_{pname}" + ) + config[converter.name + f"_d_{pname}"] = self._convert_input( + self.D, converter.name + f"_d_{pname}" + ) + config[converter.name + f"_n_{pname}"] = self._convert_input( + self.N, converter.name + f"_n_{pname}" + ) + config[converter.name + f"_clkout_rate_{pname}"] = self._convert_input( + self.QPLL_CLKOUTRATE, converter.name + f"_clkout_rate_{pname}" + ) + + # Frac part + if converter.bit_clock < 28.1e9 and not self.force_integer_mode: + config[converter.name + f"_sdm_data_{pname}"] = ( + self.model.integer_var( + min=self.SDMDATA_min, + max=self.SDMDATA_max, + name=converter.name + f"_sdm_data_{pname}", + ) + ) + config[converter.name + f"_sdm_width_{pname}"] = ( + self._convert_input( + self.SDMWIDTH, converter.name + f"_sdm_width_{pname}" + ) + ) + config[converter.name + f"_HIGH_RATE_{pname}"] = ( + self._convert_input( + self.QPLL_CLKOUTRATE, converter.name + f"_HIGH_RATE_{pname}" + ) + ) + + # Add intermediate variables + if converter.bit_clock < 28.1e9 and not self.force_integer_mode: + config[converter.name + f"_frac_{pname}"] = self._add_intermediate( + config[converter.name + f"_sdm_data_{pname}"] + / (2 ** config[converter.name + f"_sdm_width_{pname}"]) + ) + self.model.add_kpi( + config[converter.name + f"_frac_{pname}"], + name=converter.name + f"_frac_{pname}", + ) + self._add_equation([config[converter.name + f"_frac_{pname}"] < 1]) + config[converter.name + f"_n_dot_frac_{pname}"] = ( + self._add_intermediate( + config[converter.name + f"_n_{pname}"] + + config[converter.name + f"_frac_{pname}"] + ) + ) + self.model.add_kpi( + config[converter.name + f"_n_dot_frac_{pname}"], + name=converter.name + f"_n_dot_frac_{pname}", + ) + else: + config[converter.name + f"_n_dot_frac_{pname}"] = ( + self._add_intermediate(config[converter.name + f"_n_{pname}"]) + ) + + config[converter.name + f"_pll_out_{pname}"] = self._add_intermediate( + fpga_ref + * config[converter.name + f"_n_dot_frac_{pname}"] + / ( + config[converter.name + f"_m_{pname}"] + * config[converter.name + f"_clkout_rate_{pname}"] + ) + ) + config[converter.name + f"_vco_{pname}"] = self._add_intermediate( + fpga_ref + * config[converter.name + f"_n_dot_frac_{pname}"] + / ( + config[converter.name + f"_m_{pname}"] + * config[converter.name + f"_clkout_rate_{pname}"] + ) + ) + self.model.add_kpi( + config[converter.name + f"_vco_{pname}"], + name=converter.name + f"_vco_{pname}", + ) + + # Add constraints + self._add_equation( + [ + if_then( + config[converter.name + f"_use_{pname}"] == 1, + converter.bit_clock * config[converter.name + f"_d_{pname}"] + == config[converter.name + f"_pll_out_{pname}"] * 2, + ), + # if_then( + # config[converter.name + f"_use_{pname}"] == 1, + # config[converter.name + f"_HIGH_RATE_{pname}"] + # == (converter.bit_clock >= 28.1e9), + # ), + ] + ) + + self._add_equation( + [ + if_then( + config[converter.name + f"_use_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] >= self.vco_min, + ), + if_then( + config[converter.name + f"_use_{pname}"] == 1, + config[converter.name + f"_vco_{pname}"] <= self.vco_max, + ), + # if_then( + # config[converter.name + f"_HIGH_RATE_{pname}"] == 1, + # config[converter.name + f"_frac_{pname}"] == 0, + # ), + ] + ) + + return config + + +class QPLL1(QPLL): + """QPLL1 model for Ultrascale+ transceivers.""" + + _pname = "qpll1" + + @property + def vco_min(self) -> int: + """VCO min frequency in Hz for QPLL1.""" + if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE3", "GTYE4"]: + return 8000000000 + raise Exception( + f"Unknown vco_min for transceiver type {self.parent.transceiver_type}" + ) + + @property + def vco_max(self) -> int: + """VCO max frequency in Hz for QPLL1.""" + if self.parent.transceiver_type in ["GTHE3", "GTHE4", "GTYE3", "GTYE4"]: + return 13000000000 + raise Exception( + f"Unknown vco_max for transceiver type {self.parent.transceiver_type}" + ) diff --git a/adijif/gekko_trans.py b/adijif/gekko_trans.py index e4f81f3..72b6894 100644 --- a/adijif/gekko_trans.py +++ b/adijif/gekko_trans.py @@ -52,7 +52,8 @@ def _add_intermediate( raise Exception(f"Unknown solver: {self.solver}") def _add_equation( - self, eqs: List[Union[GKVariable, GK_Intermediate, GK_Operators, CpoExpr]] + self, + eqs: List[Union[GKVariable, GK_Intermediate, GK_Operators, CpoExpr]], ) -> None: """Add equation or relation to solver. @@ -75,7 +76,8 @@ def _add_equation( raise Exception(f"Unknown solver {self.solver}") def _get_val( - self, value: Union[int, float, GKVariable, GK_Intermediate, GK_Operators] + self, + value: Union[int, float, GKVariable, GK_Intermediate, GK_Operators], ) -> Union[int, float, str]: """Extract value from solver types. @@ -128,7 +130,9 @@ def _check_in_range( value = [value] # type: ignore for v in value: # type: ignore if v not in possible: - raise Exception(f"{v} invalid for {varname}. Only {possible} possible") + raise Exception( + f"{v} invalid for {varname}. Only {possible} possible" + ) def _convert_input( self, @@ -182,7 +186,9 @@ def _convert_input_gekko( return self.model.Const(value=val, name=name) def _convert_input_cplex( - self, val: Union[int, List[int], float, List[float]], name: Optional[str] = None + self, + val: Union[int, List[int], float, List[float]], + name: Optional[str] = None, ) -> CpoExpr: """Convert input to CPLEX solver variables. diff --git a/adijif/jesd.py b/adijif/jesd.py index 060abc9..42ac09c 100644 --- a/adijif/jesd.py +++ b/adijif/jesd.py @@ -33,7 +33,9 @@ class jesd(metaclass=ABCMeta): _skip_clock_validation = False - def __init__(self, sample_clock: int, M: int, L: int, Np: int, K: int) -> None: + def __init__( + self, sample_clock: int, M: int, L: int, Np: int, K: int + ) -> None: """Initialize JESD device through link parameterization. Args: @@ -54,7 +56,9 @@ def __init__(self, sample_clock: int, M: int, L: int, Np: int, K: int) -> None: def _check_clock_relations(self) -> None: """Check clock relations between clocks and JESD parameters.""" sc = self.sample_clock - assert sc == self.frame_clock * self.S, "sample_clock != S * frame_clock" + assert ( + sc == self.frame_clock * self.S + ), "sample_clock != S * frame_clock" if self.jesd_class == "jesd204b": assert sc == (self.bit_clock / 10 / self.F) * self.S assert sc == (self.multiframe_clock * self.K * self.S) @@ -744,7 +748,11 @@ def bit_clock(self, value: int) -> None: # frame_clock = bit_clock*L*encoding_n/encoding_d / (M*S*Np) # sample_clock = bit_clock*L*encoding_n/encoding_d / (M*Np) value_cal = ( - value * self.L * self.encoding_n / self.encoding_d / (self.M * self.Np) + value + * self.L + * self.encoding_n + / self.encoding_d + / (self.M * self.Np) ) self._sample_clock = value_cal diff --git a/adijif/plls/adf4371.py b/adijif/plls/adf4371.py index c5303a3..4296d24 100644 --- a/adijif/plls/adf4371.py +++ b/adijif/plls/adf4371.py @@ -191,7 +191,9 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: Exception: If solver is not called first """ if not self._clk_names: - raise Exception("set_requested_clocks must be called before get_config") + raise Exception( + "set_requested_clocks must be called before get_config" + ) if solution: self.solution = solution @@ -249,7 +251,10 @@ def _setup_solver_constraints( self.config["f_pfd"] = self._add_intermediate( input_ref - * ((1 + self.config["d"]) / (self.config["r"] * (1 + self.config["t"]))) + * ( + (1 + self.config["d"]) + / (self.config["r"] * (1 + self.config["t"])) + ) ) # Configure fractional mode or integer mode constraints @@ -258,7 +263,9 @@ def _setup_solver_constraints( elif self._mode == "integer": self.config["fact_0_int_1"] = self._convert_input(1, "fact_0_int_1") else: - self.config["fact_0_int_1"] = self._convert_input([0, 1], "fact_0_int_1") + self.config["fact_0_int_1"] = self._convert_input( + [0, 1], "fact_0_int_1" + ) self.config["pfd_max_freq"] = self._add_intermediate( (1 - self.config["fact_0_int_1"]) * self.pfd_freq_max_frac @@ -271,10 +278,14 @@ def _setup_solver_constraints( # Configure INT setting based on prescalers if self.solver == "CPLEX": self.config["frac1"] = integer_var( - min=self._frac1_min_max[0], max=self._frac1_min_max[1], name="frac1" + min=self._frac1_min_max[0], + max=self._frac1_min_max[1], + name="frac1", ) self.config["frac2"] = integer_var( - min=self._frac2_min_max[0], max=self._frac2_min_max[1], name="frac2" + min=self._frac2_min_max[0], + max=self._frac2_min_max[1], + name="frac2", ) if self._prescaler == "4/5": @@ -293,13 +304,15 @@ def _setup_solver_constraints( self.config["int_min"] = self._add_intermediate( self.config["fact_0_int_1"] * ( - self.config["prescaler_4/5_0_8/9_1"] * self._int_8d9_min_max[0] + self.config["prescaler_4/5_0_8/9_1"] + * self._int_8d9_min_max[0] + (1 - self.config["prescaler_4/5_0_8/9_1"]) * self._int_4d5_min_max[0] ) + (1 - self.config["fact_0_int_1"]) * ( - self.config["prescaler_4/5_0_8/9_1"] * self._int_frac_8d9_min_max[0] + self.config["prescaler_4/5_0_8/9_1"] + * self._int_frac_8d9_min_max[0] + (1 - self.config["prescaler_4/5_0_8/9_1"]) * self._int_frac_4d5_min_max[0] ) @@ -308,13 +321,15 @@ def _setup_solver_constraints( self.config["int_max"] = self._add_intermediate( self.config["fact_0_int_1"] * ( - self.config["prescaler_4/5_0_8/9_1"] * self._int_8d9_min_max[1] + self.config["prescaler_4/5_0_8/9_1"] + * self._int_8d9_min_max[1] + (1 - self.config["prescaler_4/5_0_8/9_1"]) * self._int_4d5_min_max[1] ) + (1 - self.config["fact_0_int_1"]) * ( - self.config["prescaler_4/5_0_8/9_1"] * self._int_frac_8d9_min_max[1] + self.config["prescaler_4/5_0_8/9_1"] + * self._int_frac_8d9_min_max[1] + (1 - self.config["prescaler_4/5_0_8/9_1"]) * self._int_frac_4d5_min_max[1] ) @@ -343,7 +358,10 @@ def _setup_solver_constraints( self.config["vco"] = self._add_intermediate( ( self.config["int"] - + (self.config["frac1"] + self.config["frac2"] / self.config["MOD2"]) + + ( + self.config["frac1"] + + self.config["frac2"] / self.config["MOD2"] + ) / self._MOD1 ) * self.config["f_pfd"] @@ -351,7 +369,10 @@ def _setup_solver_constraints( self.model.add_kpi( ( self.config["int"] - + (self.config["frac1"] + self.config["frac2"] / self.config["MOD2"]) + + ( + self.config["frac1"] + + self.config["frac2"] / self.config["MOD2"] + ) / self._MOD1 ) * self.config["f_pfd"], @@ -412,4 +433,6 @@ def set_requested_clocks( self.config["rf_div"] = self._convert_input(self.rf_div, "rf_div") - self._add_equation([self.config["rf_div"] * out_freq == self.config["vco"]]) + self._add_equation( + [self.config["rf_div"] * out_freq == self.config["vco"]] + ) diff --git a/adijif/system.py b/adijif/system.py index 7249695..58f9a03 100644 --- a/adijif/system.py +++ b/adijif/system.py @@ -54,7 +54,9 @@ def add_pll_inline(self, pll_name: str, clk: clockc, cnv: convc) -> None: clk (clockc): Clock chip reference cnv (convc): Converter reference """ - pll = eval(f"adijif.{pll_name}(self.model,solver=self.solver)") # noqa: S307 + pll = eval( # noqa: S307 + f"adijif.{pll_name}(self.model,solver=self.solver)" + ) self._plls.append(pll) pll._connected_to_output = cnv.name @@ -128,14 +130,20 @@ def __init__( if isinstance(conv, list): for c in conv: self.converter.append( - eval(f"adijif.{c}(self.model,solver=self.solver)") # noqa: S307 + eval( # noqa: S307 + f"adijif.{c}(self.model,solver=self.solver)" + ) ) else: self.converter: convc = eval( # noqa: S307 f"adijif.{conv}(self.model,solver=self.solver)" ) - self.clock = eval(f"adijif.{clk}(self.model,solver=self.solver)") # noqa: S307 - self.fpga = eval(f"adijif.{fpga}(self.model,solver=self.solver)") # noqa: S307 + self.clock = eval( # noqa: S307 + f"adijif.{clk}(self.model,solver=self.solver)" + ) + self.fpga = eval( # noqa: S307 + f"adijif.{fpga}(self.model,solver=self.solver)" + ) self.vcxo = vcxo # TODO: Add these constraints to solver options @@ -182,14 +190,18 @@ def _get_configs(self) -> Dict: Dict: Dictionary containing all clocking configurations of all components """ cfg = {"clock": self.clock.get_config(self.solution), "converter": []} - c = self.converter if isinstance(self.converter, list) else [self.converter] + c = ( + self.converter + if isinstance(self.converter, list) + else [self.converter] + ) for conv in c: if conv._nested: names = conv._nested for name in names: - clk_ref = cfg["clock"]["output_clocks"][name + "_fpga_ref_clk"][ - "rate" - ] + clk_ref = cfg["clock"]["output_clocks"][ + name + "_fpga_ref_clk" + ]["rate"] cfg["fpga_" + name] = self.fpga.get_config( solution=self.solution, converter=getattr(conv, name), @@ -204,9 +216,9 @@ def _get_configs(self) -> Dict: conv, name ).datapath.get_config() else: - clk_ref = cfg["clock"]["output_clocks"][conv.name + "_fpga_ref_clk"][ - "rate" - ] + clk_ref = cfg["clock"]["output_clocks"][ + conv.name + "_fpga_ref_clk" + ]["rate"] cfg["fpga_" + conv.name] = self.fpga.get_config( solution=self.solution, converter=conv, fpga_ref=clk_ref ) @@ -309,7 +321,9 @@ def solve(self) -> Dict: config = {} if self.enable_converter_clocks: convs: List[convc] = ( - self.converter if isinstance(self.converter, list) else [self.converter] + self.converter + if isinstance(self.converter, list) + else [self.converter] ) # Setup clock chip @@ -359,21 +373,21 @@ def solve(self) -> Dict: if conv._nested: # MxFE, Transceivers assert isinstance(conv._nested, list) names = conv._nested - config[conv.name + "_ref_clk"] = self.clock._get_clock_constraint( - conv.name + "_ref_clk" + config[conv.name + "_ref_clk"] = ( + self.clock._get_clock_constraint(conv.name + "_ref_clk") ) clock_names.append(conv.name + "_ref_clk") for name in names: - config[name + "_sysref"] = self.clock._get_clock_constraint( - name + "_sysref" + config[name + "_sysref"] = ( + self.clock._get_clock_constraint(name + "_sysref") ) clock_names.append(name + "_sysref") else: - config[conv.name + "_ref_clk"] = self.clock._get_clock_constraint( - conv.name + "_ref_clk" + config[conv.name + "_ref_clk"] = ( + self.clock._get_clock_constraint(conv.name + "_ref_clk") ) - config[conv.name + "_sysref"] = self.clock._get_clock_constraint( - conv.name + "_sysref" + config[conv.name + "_sysref"] = ( + self.clock._get_clock_constraint(conv.name + "_sysref") ) clock_names.append(conv.name + "_ref_clk") clock_names.append(conv.name + "_sysref") @@ -395,12 +409,20 @@ def solve(self) -> Dict: ) ) self.clock._add_equation( - config[conv.name + "_ref_clk_from_ext_pll"] == clks[0] + config[conv.name + "_ref_clk_from_ext_pll"] + == clks[0] ) # Connect converter to clock chip direct if no external PLL is used - if all([conv.name != pll._connected_to_output for pll in self._plls]): - self.clock._add_equation(config[conv.name + "_ref_clk"] == clks[0]) + if all( + [ + conv.name != pll._connected_to_output + for pll in self._plls + ] + ): + self.clock._add_equation( + config[conv.name + "_ref_clk"] == clks[0] + ) # Converter sysref if conv._nested: @@ -410,7 +432,9 @@ def solve(self) -> Dict: ) sys_refs.append(config[name + "_sysref"]) else: - self.clock._add_equation(config[conv.name + "_sysref"] == clks[1]) + self.clock._add_equation( + config[conv.name + "_sysref"] == clks[1] + ) sys_refs.append(config[conv.name + "_sysref"]) # Determine if separate device clock / link clock output is needed @@ -420,7 +444,9 @@ def solve(self) -> Dict: if conv._nested: for name in names: config[name + "_fpga_ref_clk"] = ( - self.clock._get_clock_constraint("xilinx" + "_" + name) + self.clock._get_clock_constraint( + "xilinx" + "_" + name + ) ) clock_names.append(name + "_fpga_ref_clk") sys_refs.append(config[name + "_fpga_ref_clk"]) @@ -435,7 +461,9 @@ def solve(self) -> Dict: else: config[conv.name + "_fpga_ref_clk"] = ( - self.clock._get_clock_constraint("xilinx" + "_" + conv.name) + self.clock._get_clock_constraint( + "xilinx" + "_" + conv.name + ) ) clock_names.append(conv.name + "_fpga_ref_clk") sys_refs.append(config[conv.name + "_fpga_ref_clk"]) @@ -459,7 +487,8 @@ def solve(self) -> Dict: ) else: self.fpga.get_required_clocks( - getattr(conv, name), config[name + "_fpga_ref_clk"] + getattr(conv, name), + config[name + "_fpga_ref_clk"], ) else: if need_separate_link_clock: @@ -488,7 +517,9 @@ def solve(self) -> Dict: if objectives: if len(self.fpga._objectives) > 1: - self.model.add(self.model.minimize_static_lex(objectives)) + self.model.add( + self.model.minimize_static_lex(objectives) + ) else: self.model.minimize(objectives[0]) @@ -565,7 +596,9 @@ def determine_clocks(self) -> List: out.append({"Converter": rate, "ClockChip": complete_clock_configs}) if not out: - raise Exception("No valid configurations possible converter sample rate") + raise Exception( + "No valid configurations possible converter sample rate" + ) return out diff --git a/adijif/types.py b/adijif/types.py index bd9aa4e..96ee79d 100644 --- a/adijif/types.py +++ b/adijif/types.py @@ -63,7 +63,9 @@ def __call__(self, model: Union[GEKKO, CpoModel]) -> Dict: config = {} if isinstance(model, CpoModel): o_array = np.arange(self.start, self.stop, self.step) - config["range"] = integer_var(domain=o_array, name=self.name + "_Var") + config["range"] = integer_var( + domain=o_array, name=self.name + "_Var" + ) return config if self.step == 1: @@ -79,7 +81,9 @@ def __call__(self, model: Union[GEKKO, CpoModel]) -> Dict: o_array = np.arange(self.start, self.stop, self.step) o_array = list(map(int, o_array)) - options = model.Array(model.Var, len(o_array), lb=0, ub=1, integer=True) + options = model.Array( + model.Var, len(o_array), lb=0, ub=1, integer=True + ) model.Equation(model.sum(options) == 1) # only select one config["range"] = model.Intermediate(model.sum(o_array * options)) diff --git a/adijif/utils.py b/adijif/utils.py index feb9baa..5f302dc 100644 --- a/adijif/utils.py +++ b/adijif/utils.py @@ -75,10 +75,14 @@ def get_max_sample_rates( if fpga: max_lanes = fpga.max_serdes_lanes if int(fpga.transceiver_type[4]) == 2: - trx = xp.SevenSeries(parent=fpga, transceiver_type=fpga.transceiver_type) + trx = xp.SevenSeries( + parent=fpga, transceiver_type=fpga.transceiver_type + ) max_lane_rate = trx.plls["QPLL"].vco_max elif int(fpga.transceiver_type[4]) in [3, 4]: - trx = us.UltraScalePlus(parent=fpga, transceiver_type=fpga.transceiver_type) + trx = us.UltraScalePlus( + parent=fpga, transceiver_type=fpga.transceiver_type + ) max_lane_rate = trx.plls["QPLL"].vco_max * 2 else: raise Exception("Unsupported FPGA transceiver type") diff --git a/tests/common.py b/tests/common.py index c45068d..9eb70d2 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,3 +1,4 @@ +# flake8: noqa import pytest try: diff --git a/tests/test_ad9081.py b/tests/test_ad9081.py index dcb2918..c2c2fe0 100644 --- a/tests/test_ad9081.py +++ b/tests/test_ad9081.py @@ -184,7 +184,9 @@ def test_ad9081_rxtx_zcu102_default_config(): sys.fpga.setup_by_dev_kit_name("zc706") sys.Debug_Solver = False sys.converter.clocking_option = "integrated_pll" - sys.fpga.out_clk_select = "XCVR_REFCLK" # force reference to be core clock rate + sys.fpga.out_clk_select = ( + "XCVR_REFCLK" # force reference to be core clock rate + ) sys.converter.adc.sample_clock = 4000000000 // (4 * 4) sys.converter.dac.sample_clock = 12000000000 // (8 * 6) diff --git a/tests/test_adrv9009.py b/tests/test_adrv9009.py index 3c9de61..cf2a103 100644 --- a/tests/test_adrv9009.py +++ b/tests/test_adrv9009.py @@ -55,15 +55,21 @@ def test_adrv9009_rxtx_ad9528_solver_compact(solver, converter): pprint(cfg) ref = { - "gekko": {"clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 4, 8, 256]}}, - "CPLEX": {"clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 8, 256]}}, + "gekko": { + "clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 4, 8, 256]} + }, + "CPLEX": { + "clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 8, 256]} + }, } assert cfg["clock"]["r1"] == ref[solver]["clock"]["r1"] assert cfg["clock"]["n2"] == ref[solver]["clock"]["n2"] assert cfg["clock"]["m1"] == ref[solver]["clock"]["m1"] assert ( - cfg["clock"]["output_clocks"][f"{converter.upper()}_fpga_ref_clk"]["rate"] + cfg["clock"]["output_clocks"][f"{converter.upper()}_fpga_ref_clk"][ + "rate" + ] == 122880000.0 ) # 98304000 for div in cfg["clock"]["out_dividers"]: @@ -125,9 +131,16 @@ def test_adrv9009_ad9528_solver_compact(solver): print(cfg) ref = { - "gekko": {"clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 8, 256]}}, + "gekko": { + "clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 8, 256]} + }, "CPLEX": { - "clock": {"r1": 1, "n2": 6, "m1": 5, "out_dividers": [6, 48, 192, 256]} + "clock": { + "r1": 1, + "n2": 6, + "m1": 5, + "out_dividers": [6, 48, 192, 256], + } }, } diff --git a/tests/test_bf.py b/tests/test_bf.py index 0c0cfe4..419571c 100644 --- a/tests/test_bf.py +++ b/tests/test_bf.py @@ -70,15 +70,69 @@ def test_ad9523_1_daq2_config(): cfs = clk.find_dividers(vcxo, rates) ref = [ - {"m1": 3, "n2": 24, "vco": 3000000000.0, "r2": 1, "required_output_divs": 1.0}, - {"m1": 3, "n2": 48, "vco": 3000000000.0, "r2": 2, "required_output_divs": 1.0}, - {"m1": 3, "n2": 72, "vco": 3000000000.0, "r2": 3, "required_output_divs": 1.0}, - {"m1": 3, "n2": 96, "vco": 3000000000.0, "r2": 4, "required_output_divs": 1.0}, - {"m1": 3, "n2": 120, "vco": 3000000000.0, "r2": 5, "required_output_divs": 1.0}, - {"m1": 3, "n2": 144, "vco": 3000000000.0, "r2": 6, "required_output_divs": 1.0}, - {"m1": 3, "n2": 168, "vco": 3000000000.0, "r2": 7, "required_output_divs": 1.0}, - {"m1": 3, "n2": 192, "vco": 3000000000.0, "r2": 8, "required_output_divs": 1.0}, - {"m1": 3, "n2": 216, "vco": 3000000000.0, "r2": 9, "required_output_divs": 1.0}, + { + "m1": 3, + "n2": 24, + "vco": 3000000000.0, + "r2": 1, + "required_output_divs": 1.0, + }, + { + "m1": 3, + "n2": 48, + "vco": 3000000000.0, + "r2": 2, + "required_output_divs": 1.0, + }, + { + "m1": 3, + "n2": 72, + "vco": 3000000000.0, + "r2": 3, + "required_output_divs": 1.0, + }, + { + "m1": 3, + "n2": 96, + "vco": 3000000000.0, + "r2": 4, + "required_output_divs": 1.0, + }, + { + "m1": 3, + "n2": 120, + "vco": 3000000000.0, + "r2": 5, + "required_output_divs": 1.0, + }, + { + "m1": 3, + "n2": 144, + "vco": 3000000000.0, + "r2": 6, + "required_output_divs": 1.0, + }, + { + "m1": 3, + "n2": 168, + "vco": 3000000000.0, + "r2": 7, + "required_output_divs": 1.0, + }, + { + "m1": 3, + "n2": 192, + "vco": 3000000000.0, + "r2": 8, + "required_output_divs": 1.0, + }, + { + "m1": 3, + "n2": 216, + "vco": 3000000000.0, + "r2": 9, + "required_output_divs": 1.0, + }, { "m1": 3, "n2": 240, diff --git a/tests/test_clocks.py b/tests/test_clocks.py index 94e08fd..b2d276f 100644 --- a/tests/test_clocks.py +++ b/tests/test_clocks.py @@ -32,7 +32,9 @@ def test_ad9545_validate_fail(solver): output_clocks = [(0, 3525235234123)] input_refs = list(map(lambda x: (int(x[0]), int(x[1])), input_refs)) - output_clocks = list(map(lambda x: (int(x[0]), int(x[1])), output_clocks)) + output_clocks = list( + map(lambda x: (int(x[0]), int(x[1])), output_clocks) + ) clk.set_requested_clocks(input_refs, output_clocks) @@ -94,7 +96,9 @@ def test_ad9545_fail_no_solver(): output_clocks = [(0, 30720000)] input_refs = list(map(lambda x: (int(x[0]), int(x[1])), input_refs)) - output_clocks = list(map(lambda x: (int(x[0]), int(x[1])), output_clocks)) + output_clocks = list( + map(lambda x: (int(x[0]), int(x[1])), output_clocks) + ) clk.set_requested_clocks(input_refs, output_clocks) @@ -301,7 +305,9 @@ def test_ad9528_sysref(solver): clk = adijif.ad9528(solver=solver) clk.n2 = n2 - clk.k = [*range(500, 600)] # FIXME gekko fails to find a solution without this. + clk.k = [ + *range(500, 600) + ] # FIXME gekko fails to find a solution without this. clk.use_vcxo_double = False clk.sysref = 120e3 diff --git a/tests/test_daq2.py b/tests/test_daq2.py index 1c72548..f23d52d 100644 --- a/tests/test_daq2.py +++ b/tests/test_daq2.py @@ -121,7 +121,9 @@ def test_ad9680_all_clk_chips_fpga_pll_modes_solver( @pytest.mark.parametrize("solver", solvers_to_test) def test_daq2_split_rates_solver(solver): vcxo = 125000000 - sys = adijif.system(["ad9680", "ad9144"], "ad9523_1", "xilinx", vcxo, solver=solver) + sys = adijif.system( + ["ad9680", "ad9144"], "ad9523_1", "xilinx", vcxo, solver=solver + ) sys.fpga.setup_by_dev_kit_name("zc706") sys.fpga.out_clk_select = "XCVR_REFCLK" @@ -168,7 +170,9 @@ def test_ad9680_clock_check1_solver(): assert cfg["clock"]["n2"] == 24 assert cfg["clock"]["r2"] == 1 assert cfg["clock"]["m1"] == 3 - assert cfg["clock"]["output_clocks"]["AD9680_fpga_ref_clk"]["rate"] == 1e9 / 4 + assert ( + cfg["clock"]["output_clocks"]["AD9680_fpga_ref_clk"]["rate"] == 1e9 / 4 + ) assert cfg["fpga_AD9680"]["type"] == "qpll" assert cfg["fpga_AD9680"]["sys_clk_select"] == "XCVR_QPLL0" @@ -208,6 +212,9 @@ def test_ad9680_clock_check2_solver(solver): assert cfg["clock"]["n2"] == 24 assert cfg["clock"]["r2"] == 1 assert cfg["clock"]["m1"] == 3 - assert cfg["clock"]["output_clocks"]["AD9680_fpga_ref_clk"]["rate"] == 250000000 + assert ( + cfg["clock"]["output_clocks"]["AD9680_fpga_ref_clk"]["rate"] + == 250000000 + ) for div in divs: assert div in [1, 4, 32] diff --git a/tests/test_fpga.py b/tests/test_fpga.py index 9b4797f..40febc2 100644 --- a/tests/test_fpga.py +++ b/tests/test_fpga.py @@ -8,7 +8,9 @@ def test_jesd_unknown_dev(): name = "zcu103" - with pytest.raises(Exception, match=f"No boardname found in library for {name}"): + with pytest.raises( + Exception, match=f"No boardname found in library for {name}" + ): f = adijif.xilinx() f.setup_by_dev_kit_name(name) diff --git a/tests/test_jesd.py b/tests/test_jesd.py index 681f24c..b8b4cc5 100644 --- a/tests/test_jesd.py +++ b/tests/test_jesd.py @@ -6,7 +6,9 @@ import adijif -@pytest.mark.parametrize("attr", ["data_path_width", "K", "F", "L", "M", "N", "Np"]) +@pytest.mark.parametrize( + "attr", ["data_path_width", "K", "F", "L", "M", "N", "Np"] +) def test_jesd_ints(attr): with pytest.raises(Exception, match=f"{attr} must be an integer"): cnv = adijif.ad9680() diff --git a/tests/test_system.py b/tests/test_system.py index db0fe73..ed41d76 100644 --- a/tests/test_system.py +++ b/tests/test_system.py @@ -42,7 +42,9 @@ def test_converter_lane_count_exceeds_fpga_lane_count(): sys.converter[i].HD = 1 sys.converter[i].clocking_option = "integrated_pll" - with pytest.raises(Exception, match=f"Max SERDES lanes exceeded. 8 only available"): + with pytest.raises( + Exception, match=f"Max SERDES lanes exceeded. 8 only available" + ): cfg = sys.solve() diff --git a/tests/test_xilinx_pll.py b/tests/test_xilinx_pll.py index 3980836..dcd147f 100644 --- a/tests/test_xilinx_pll.py +++ b/tests/test_xilinx_pll.py @@ -1,3 +1,4 @@ +# flake8: noqa from pprint import pprint import pytest @@ -49,7 +50,12 @@ def test_7s_pll(pll, out_clock): @pytest.mark.parametrize( "pll, out_clock", - [("cpll", 0.5e9), ("qpll", 14e9), ("qpll", 32e9), ("qpll1", 9e9)], # No frac mode + [ + ("cpll", 0.5e9), + ("qpll", 14e9), + ("qpll", 32e9), + ("qpll1", 9e9), + ], # No frac mode ) def test_us_pll(pll, out_clock): us_plls = us.UltraScalePlus(transceiver_type="GTYE4", solver="CPLEX") From 21715d6e967c24c218504f0bcede8e68171a0e7f Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Thu, 12 Dec 2024 14:44:53 -0700 Subject: [PATCH 16/17] Add module load for FPGAs needed for doc Signed-off-by: Travis F. Collins --- adijif/fpgas/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 adijif/fpgas/__init__.py diff --git a/adijif/fpgas/__init__.py b/adijif/fpgas/__init__.py new file mode 100644 index 0000000..e69de29 From d85a8b8b9382a7206308c290e4e48c3982ab59f1 Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Thu, 12 Dec 2024 14:49:17 -0700 Subject: [PATCH 17/17] Add module doc for FPGA models Signed-off-by: Travis F. Collins --- adijif/fpgas/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adijif/fpgas/__init__.py b/adijif/fpgas/__init__.py index e69de29..896d9e4 100644 --- a/adijif/fpgas/__init__.py +++ b/adijif/fpgas/__init__.py @@ -0,0 +1 @@ +"""ADI JIF FPGA Models and Utilities."""