diff --git a/adijif/cli.py b/adijif/cli.py index 7dbeaed..d821174 100644 --- a/adijif/cli.py +++ b/adijif/cli.py @@ -1,4 +1,5 @@ """Console script for adijif.""" + import sys from typing import List @@ -12,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/__init__.py b/adijif/clocks/__init__.py index 3858848..9639af4 100644 --- a/adijif/clocks/__init__.py +++ b/adijif/clocks/__init__.py @@ -1,2 +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 2dd0d58..2da97a9 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 @@ -24,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 @@ -154,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)) @@ -165,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"] @@ -184,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: @@ -261,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 304d203..124e2f0 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 @@ -277,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 @@ -345,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"] @@ -423,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 @@ -431,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/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..7830e1d 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 @@ -207,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} ) @@ -265,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 @@ -278,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 @@ -307,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() @@ -327,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 @@ -374,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)) @@ -455,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 200673b..9a56865 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 @@ -468,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 @@ -558,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) @@ -608,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 18e5887..db75853 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 @@ -382,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 @@ -426,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) @@ -452,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 a1e8f0b..2fbd063 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 @@ -18,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/__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..cf399f8 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 @@ -88,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 @@ -119,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} @@ -137,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"] @@ -154,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" ) @@ -215,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, @@ -241,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") @@ -367,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 @@ -381,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 @@ -472,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): @@ -534,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: @@ -545,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 @@ -561,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/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..8f547ff 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 @@ -44,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 + ), }, } @@ -157,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"]) ), @@ -174,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"] @@ -189,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 @@ -243,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 b1db1b2..4e7cafb 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 @@ -123,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() @@ -141,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. @@ -176,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/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..c53dee1 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 @@ -171,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/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..b6050db 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 @@ -58,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}") @@ -101,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 3447ae0..35b1f93 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 @@ -21,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 08e5068..394c142 100644 --- a/adijif/draw.py +++ b/adijif/draw.py @@ -1,4 +1,5 @@ """Diagraming functions for different components.""" + from __future__ import annotations import os @@ -8,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: @@ -84,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: @@ -220,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/__init__.py b/adijif/fpgas/__init__.py index 73aac15..896d9e4 100644 --- a/adijif/fpgas/__init__.py +++ b/adijif/fpgas/__init__.py @@ -1 +1 @@ -"""ADI JIF FPGA models.""" +"""ADI JIF FPGA Models and Utilities.""" 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.py b/adijif/fpgas/xilinx/__init__.py similarity index 53% rename from adijif/fpgas/xilinx.py rename to adijif/fpgas/xilinx/__init__.py index 1c5a030..6046ca4 100644 --- a/adijif/fpgas/xilinx.py +++ b/adijif/fpgas/xilinx/__init__.py @@ -1,1436 +1,992 @@ -"""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 ...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 + + +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 = ["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) -> str: + """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) -> 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: + 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", + "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 = False + + """ Force use of QPLL1 for transceiver source (GTHE3,GTHE4,GTYE4)""" + force_qpll1 = False + + """ Force use of CPLL for transceiver source """ + 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 = False + + """ 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 + _transceiver_models = {} # 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 == "GTXE2": + if str(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 == "GTXE2": + 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}") + + 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 = "GTXE2" + 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 = "GTHE4" + 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 = "GTYE4" + 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 = "GTYE4" + 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 + # FIXME: REIMPLEMENT BETTER + if converter.name + "_use_cpll" not in config.keys(): + print("Continued") + continue + + 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 + + # 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"] + ) + + 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 ["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, + ] + 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]) + + # Add transceiver + config = {} + if self.fpga_generation() == "7000": + 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, + speed_grade=self.speed_grade, + ) + else: + raise Exception( + f"Unsupported FPGA generation {self.fpga_generation()}" + ) + + # 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 = 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 + 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 diff --git a/adijif/fpgas/xilinx_bf.py b/adijif/fpgas/xilinx/bf.py similarity index 96% rename from adijif/fpgas/xilinx_bf.py rename to 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 new file mode 100644 index 0000000..762fd36 --- /dev/null +++ b/adijif/fpgas/xilinx/pll.py @@ -0,0 +1,234 @@ +"""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, # 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 + 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) -> 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: 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) -> CpoSolveResult: + """Solution object from solver.""" + if self.parent: + return self.parent.solution + return self._solution + + @solution.setter + 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) -> str: + """Transceiver type. + + Returns: + str: Transceiver type + """ + return self._transceiver_type + + @transceiver_type.setter + 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) -> 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: 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 + + 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): + """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) -> CpoModel: + """Internal system model for solver. + + Returns: + CpoModel: Internal system model for solver + """ + return self.parent.model + + @property + def solver(self) -> str: + """Solver type. + + Returns: + str: Solver type + """ + return self.parent.solver + + @property + 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 new file mode 100644 index 0000000..553cb93 --- /dev/null +++ b/adijif/fpgas/xilinx/sevenseries.py @@ -0,0 +1,543 @@ +"""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 new file mode 100644 index 0000000..3606cf6 --- /dev/null +++ b/adijif/fpgas/xilinx/ultrascaleplus.py @@ -0,0 +1,503 @@ +"""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/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/gekko_trans.py b/adijif/gekko_trans.py index c3dda17..72b6894 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 @@ -51,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. @@ -74,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. @@ -127,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, @@ -181,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 8f0cb68..42ac09c 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 @@ -32,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: @@ -53,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) @@ -743,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 64e95f8..4296d24 100644 --- a/adijif/plls/adf4371.py +++ b/adijif/plls/adf4371.py @@ -1,10 +1,11 @@ """ADF4371 Microwave Wideband Synthesizer with Integrated VCO model.""" + from typing import Dict, List, Union 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): @@ -190,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 @@ -218,6 +221,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 @@ -247,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 @@ -256,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 @@ -269,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": @@ -291,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] ) @@ -306,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] ) @@ -341,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"] @@ -349,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"], @@ -410,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/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/solvers.py b/adijif/solvers.py index a45dda4..cf3acd3 100644 --- a/adijif/solvers.py +++ b/adijif/solvers.py @@ -2,6 +2,8 @@ # 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 +43,12 @@ "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-4) -> 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) + return value diff --git a/adijif/system.py b/adijif/system.py index f28c2e3..58f9a03 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 @@ -53,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 @@ -127,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 @@ -181,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), @@ -203,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 ) @@ -308,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 @@ -358,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") @@ -388,18 +403,26 @@ 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] + 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: @@ -409,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 @@ -418,32 +443,36 @@ 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") @@ -458,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: @@ -487,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]) @@ -564,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 c8e6efe..5f302dc 100644 --- a/adijif/utils.py +++ b/adijif/utils.py @@ -1,8 +1,11 @@ """Collection of utility scripts for specialized checks.""" + from typing import List, Optional import numpy as np +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 @@ -71,7 +74,18 @@ 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/common.py b/tests/common.py new file mode 100644 index 0000000..9eb70d2 --- /dev/null +++ b/tests/common.py @@ -0,0 +1,12 @@ +# flake8: noqa +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_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_adf4371.py b/tests/test_adf4371.py index 36c34d1..3321987 100644 --- a/tests/test_adf4371.py +++ b/tests/test_adf4371.py @@ -85,9 +85,12 @@ def test_adf4371_ad9081_sys_example(): cfg = sys.solve() - # pprint.pprint(cfg) + pprint.pprint(cfg) + print(sys.converter.dac.converter_clock) - 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 + ), f"{cfg['pll_adf4371']['rf_out_frequency']} != {sys.converter.dac.converter_clock}" @pytest.mark.parametrize( diff --git a/tests/test_adrv9009.py b/tests/test_adrv9009.py index 80d5484..cf2a103 100644 --- a/tests/test_adrv9009.py +++ b/tests/test_adrv9009.py @@ -1,173 +1,196 @@ -# 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) + if solver == "gekko": + pytest.xfail("gekko currently unsupported") + + 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": [6, 48, 192, 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"] + + 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_bf.py b/tests/test_bf.py index 98e7d4b..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, @@ -463,6 +517,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() @@ -512,6 +567,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 @@ -1097,6 +1153,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 diff --git a/tests/test_clocks.py b/tests/test_clocks.py index 2502355..b2d276f 100644 --- a/tests/test_clocks.py +++ b/tests/test_clocks.py @@ -1,302 +1,325 @@ -# 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 diff --git a/tests/test_daq2.py b/tests/test_daq2.py index 05bef6f..f23d52d 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 @@ -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 a51c843..40febc2 100644 --- a/tests/test_fpga.py +++ b/tests/test_fpga.py @@ -6,29 +6,11 @@ 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}"): + 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 new file mode 100644 index 0000000..dcd147f --- /dev/null +++ b/tests/test_xilinx_pll.py @@ -0,0 +1,98 @@ +# flake8: noqa +from pprint import pprint + +import pytest + +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