From 40b2cd172cf1fd43e12e059d02303a6f35a4587f Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Mon, 18 Nov 2024 13:27:19 -0700 Subject: [PATCH] [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