diff --git a/adijif/clocks/ad9528.py b/adijif/clocks/ad9528.py index d16c463..1da6b80 100644 --- a/adijif/clocks/ad9528.py +++ b/adijif/clocks/ad9528.py @@ -16,8 +16,10 @@ class ad9528(ad9528_bf): m1_available = [3, 4, 5] """ Output dividers """ d_available = [*range(1, 1024)] + """ sysref dividers """ + k_available = [*range(0, 65536)] """ VCXO multiplier """ - n2_available = [*range(12, 256)] + n2_available = [*range(1, 256)] """ VCO calibration dividers """ a_available = [0, 1, 2, 3] b_availble = [*range(3, 64)] @@ -32,7 +34,8 @@ class ad9528(ad9528_bf): # Defaults _m1: Union[List[int], int] = [3, 4, 5] _d: Union[List[int], int] = [*range(1, 1024)] - _n2: Union[List[int], int] = [*range(12, 255)] + _k: Union[List[int], int] = k_available + _n2: Union[List[int], int] = n2_available _r1: Union[List[int], int] = [*range(1, 32)] _a: Union[List[int], int] = [*range(0, 4)] _b: Union[List[int], int] = [*range(3, 64)] @@ -47,6 +50,10 @@ class ad9528(ad9528_bf): use_vcxo_double = False vcxo = 125e6 + # sysref parameters + sysref_external = False + _sysref = None + @property def m1(self) -> Union[int, List[int]]: """VCO divider path 1. @@ -95,29 +102,53 @@ def d(self, value: Union[int, List[int]]) -> None: self._check_in_range(value, self.d_available, "d") self._d = value + @property + def k(self) -> Union[int, List[int]]: + """Sysref dividers. + + Valid dividers are 0->65535 + + Returns: + int: Current allowable dividers + """ + return self._k + + @k.setter + def k(self, value: Union[int, List[int]]) -> None: + """Sysref dividers. + + Valid dividers are 0->65535 + + Args: + value (int, list[int]): Allowable values for divider + + """ + self._check_in_range(value, self.d_available, "k") + self._k = value + @property def n2(self) -> Union[int, List[int]]: """n2: VCO feedback divider. - Valid dividers are 12->255 + Valid dividers are 1->255 Returns: int: Current allowable dividers """ - return self._m2 + return self._n2 @n2.setter def n2(self, value: Union[int, List[int]]) -> None: """VCO feedback divider. - Valid dividers are 12->255 + Valid dividers are 1->255 Args: value (int, list[int]): Allowable values for divider """ self._check_in_range(value, self.n2_available, "n2") - self._m2 = value + self._n2 = value @property def r1(self) -> Union[int, List[int]]: @@ -191,6 +222,35 @@ def b(self, value: Union[int, List[int]]) -> None: self._check_in_range(value, self.b_available, "b") self._b = value + @property + def vco(self): + r1 = self._get_val(self.config["r1"]) + m1 = self._get_val(self.config["m1"]) + n2 = self._get_val(self.config["n2"]) + + return self.vcxo / r1 * m1 * n2 + + @property + def sysref(self): + """SYSREF Frequency + + Returns: + float: computed sysref frequency + """ + r1 = self._get_val(self.config["r1"]) + k = self._get_val(self.config["k"]) + + if self.sysref_external: + sysref_src = self.vcxo + else: + sysref_src = self.vcxo / r1 + + return sysref_src / (2 * k) + + @sysref.setter + def sysref(self, value: Union[int, float]): + self._sysref = int(value) + def get_config(self, solution: CpoSolveResult = None) -> Dict: """Extract configurations from solver results. @@ -216,6 +276,8 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: out_dividers = [self._get_val(x) for x in self.config["out_dividers"]] config: Dict = { + "vcxo": self.vcxo / 2 if self.use_vcxo_double else self.vcxo, + "vco": self.vco, "r1": self._get_val(self.config["r1"]), "n2": self._get_val(self.config["n2"]), "m1": self._get_val(self.config["m1"]), @@ -225,6 +287,10 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: "output_clocks": [], } + if self._sysref: + config["k"] = self._get_val(self.config["k"]) + config["sysref"] = self.sysref + clk = self.vcxo * config["n2"] / config["r1"] output_cfg = {} @@ -256,6 +322,7 @@ def _setup_solver_constraints(self, vcxo: int) -> None: self.config = { "r1": self._convert_input(self._r1, "r1"), "m1": self._convert_input(self._m1, "m1"), + "k": self._convert_input(self._k, "k"), "n2": self._convert_input(self._n2, "n2"), "a": self._convert_input(self._a, "a"), "b": self._convert_input(self._b, "b"), @@ -318,7 +385,7 @@ def _get_clock_constraint( return self.vcxo / self.config["r1"] * self.config["n2"] / od def set_requested_clocks( - self, vcxo: int, out_freqs: List, clk_names: List[str] + self, vcxo: int, out_freqs: List, clk_names: List[str], ) -> None: """Define necessary clocks to be generated in model. @@ -337,10 +404,20 @@ def set_requested_clocks( # Setup clock chip internal constraints self._setup(vcxo) + if self._sysref: + if self.sysref_external: + sysref_src = self.vcxo + else: + sysref_src = self.vcxo / self.config["r1"] + + self._add_equation( + [sysref_src / (2 * self.config["k"]) == self._sysref] + ) + # Add requested clocks to output constraints - for out_freq in out_freqs: + for out_freq, name in zip(out_freqs, clk_names): # od = self.model.Var(integer=True, lb=1, ub=256, value=1) - od = self._convert_input(self._d, "d_" + str(out_freq)) + 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] diff --git a/adijif/converters/ad9081.py b/adijif/converters/ad9081.py index 2772ade..0d9f8c4 100644 --- a/adijif/converters/ad9081.py +++ b/adijif/converters/ad9081.py @@ -88,7 +88,6 @@ class ad9081_core(converter, metaclass=ABCMeta): config = {} # type: ignore device_clock_max = 12e9 - _model_type = "adc" _lmfc_divisor_sysref_available = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024] def _check_valid_internal_configuration(self) -> None: @@ -264,15 +263,9 @@ def get_required_clocks(self) -> List: self._lmfc_divisor_sysref_available, "lmfc_divisor_sysref" ) - if self.solver == "gekko": - self.config["sysref"] = self.model.Intermediate( - self.multiframe_clock # type: ignore - / self.config["lmfc_divisor_sysref"] - ) - elif self.solver == "CPLEX": - self.config["sysref"] = ( - self.multiframe_clock / self.config["lmfc_divisor_sysref"] - ) + self.config["sysref"] = self._add_intermediate( + self.multiframe_clock / self.config["lmfc_divisor_sysref"] + ) # Device Clocking if self.clocking_option == "direct": @@ -291,7 +284,6 @@ def get_required_clocks(self) -> List: class ad9081_rx(adc, ad9081_core): """AD9081 Receive model.""" - _model_type = "adc" name = "AD9081_RX" converter_clock_min = 1.45e9 @@ -369,15 +361,9 @@ def _converter_clock_config(self) -> None: adc_clk = self.decimation * self.sample_clock self.config["l"] = self._convert_input([1, 2, 3, 4], "l") self.config["adc_clk"] = self._convert_input(adc_clk) - - if self.solver == "gekko": - self.config["converter_clk"] = self.model.Intermediate( - self.config["adc_clk"] * self.config["l"] - ) - elif self.solver == "CPLEX": - self.config["converter_clk"] = self.config["adc_clk"] * self.config["l"] - else: - raise Exception(f"Unknown solver {self.solver}") + self.config["converter_clk"] = self._add_intermediate( + self.config["adc_clk"] * self.config["l"] + ) def _check_valid_internal_configuration(self) -> None: mode = self._check_valid_jesd_mode() @@ -410,7 +396,6 @@ def _check_valid_internal_configuration(self) -> None: class ad9081_tx(dac, ad9081_core): """AD9081 Transmit model.""" - _model_type = "dac" name = "AD9081_TX" converter_clock_min = 2.9e9 @@ -492,14 +477,9 @@ def _converter_clock_config(self) -> None: """ dac_clk = self.interpolation * self.sample_clock self.config["dac_clk"] = self._convert_input(dac_clk) - if self.solver == "gekko": - self.config["converter_clk"] = self.model.Intermediate( - self.config["dac_clk"] - ) - elif self.solver == "CPLEX": - self.config["converter_clk"] = self.config["dac_clk"] - else: - raise Exception(f"Unknown solver {self.solver}") + self.config["converter_clk"] = self._add_intermediate( + self.config["dac_clk"] + ) class ad9081(ad9081_core): @@ -571,14 +551,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) - if self.solver == "gekko": - self.config["converter_clk"] = self.model.Intermediate( - self.config["dac_clk"] - ) - elif self.solver == "CPLEX": - self.config["converter_clk"] = self.config["dac_clk"] - else: - raise Exception(f"Unknown solver {self.solver}") + 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 @@ -617,22 +592,12 @@ def get_required_clocks(self) -> List: self.dac._dac_lmfc_divisor_sysref, "dac_lmfc_divisor_sysref" ) - if self.solver == "gekko": - self.config["sysref_adc"] = self.model.Intermediate( - self.adc.multiframe_clock / self.config["adc_lmfc_divisor_sysref"] - ) - self.config["sysref_dac"] = self.model.Intermediate( - self.dac.multiframe_clock / self.config["dac_lmfc_divisor_sysref"] - ) - elif self.solver == "CPLEX": - self.config["sysref_adc"] = self.adc.multiframe_clock / ( - self.config["adc_lmfc_divisor_sysref"] - ) - self.config["sysref_dac"] = self.dac.multiframe_clock / ( - self.config["dac_lmfc_divisor_sysref"] - ) - else: - raise Exception(f"Unknown solver {self.solver}") + self.config["sysref_adc"] = self._add_intermediate( + self.adc.multiframe_clock / self.config["adc_lmfc_divisor_sysref"] + ) + self.config["sysref_dac"] = self._add_intermediate( + self.dac.multiframe_clock / self.config["dac_lmfc_divisor_sysref"] + ) # Device Clocking if self.clocking_option == "direct": diff --git a/adijif/converters/adrv9009.py b/adijif/converters/adrv9009.py index 259ab7e..c506173 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 import numpy as np @@ -19,7 +20,7 @@ # https://ez.analog.com/wide-band-rf-transceivers/design-support-adrv9008-1-adrv9008-2-adrv9009/f/q-a/103757/adrv9009-clock-configuration/308013#308013 -class adrv9009_core: +class adrv9009_core(converter, metaclass=ABCMeta): """ADRV9009 transceiver clocking model. This model manage the JESD configuration and input clock constraints. @@ -31,14 +32,27 @@ class adrv9009_core: Lane Rate = sample_clock * M * Np * (10 / 8) / L """ + device_clock_available = None # FIXME + device_clock_ranges = None # FIXME + name = "ADRV9009" # JESD configurations + quick_configuration_modes = None # FIXME available_jesd_modes = ["jesd204b"] + M_available = [1, 2, 4] + L_available = [1, 2, 3, 4, 6, 8] + N_available = [12, 16] + Np_available = [12, 16, 24] + F_available = [1, 2, 3, 4, 8] + S_available = [1] # FIXME? + K_available = [*np.arange(1, 32 + 1)] + CS_available = [0] + CF_available = [0] # Clock constraints converter_clock_min = 39.063e6 * 8 - converter_clock_max = 491520000 + converter_clock_max = 12288e6 sample_clock_min = 39.063e6 sample_clock_max = 491520000 @@ -53,20 +67,13 @@ class adrv9009_core: input_clock_dividers_available = [1 / 2, 1, 2, 4, 8, 16] input_clock_dividers_times2_available = [1, 2, 4, 8, 16, 32] + _lmfc_divisor_sysref_available = [*range(1, 20)] + # Unused max_rx_sample_clock = 250e6 max_tx_sample_clock = 500e6 max_obs_sample_clock = 500e6 - -class adrv9009_clock_common(adrv9009_core, adrv9009_bf): - """ADRV9009 class managing common singleton (Rx,Tx) methods.""" - - def _check_valid_jesd_mode(self) -> None: - """Verify current JESD configuration for part is valid.""" - _extra_jesd_check(self) - converter._check_valid_jesd_mode(self) - def _check_valid_internal_configuration(self) -> None: # FIXME pass @@ -82,6 +89,30 @@ def get_required_clock_names(self) -> List[str]: """ return ["adrv9009_device_clock", "adrv9009_sysref"] + def get_config(self, solution: CpoSolveResult = None) -> Dict: + """Extract configurations from solver results. + + Collect internal converter configuration and output clock definitions + leading to connected devices (clock chips, FPGAs) + + Args: + solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver + + Returns: + Dict: Dictionary of clocking rates and dividers for configuration + """ + if solution: + self.solution = solution + return {} + +class adrv9009_clock_common(adrv9009_core, adrv9009_bf): + """ADRV9009 class managing common singleton (Rx,Tx) methods.""" + + def _check_valid_jesd_mode(self) -> None: + """Verify current JESD configuration for part is valid.""" + _extra_jesd_check(self) + return super()._check_valid_jesd_mode() + def get_config(self, solution: CpoSolveResult = None) -> Dict: """Extract configurations from solver results. @@ -158,6 +189,7 @@ class adrv9009_rx(adc, adrv9009_clock_common, adrv9009_core): """ADRV9009 Receive model.""" quick_configuration_modes = {"jesd204b": quick_configuration_modes_rx} + name = "ADRV9009_RX" # JESD configurations K_available = [*np.arange(1, 32 + 1)] @@ -181,15 +213,26 @@ class adrv9009_rx(adc, adrv9009_clock_common, adrv9009_core): bit_clock_min_available = {"jesd204b": 3.6864e9} bit_clock_max_available = {"jesd204b": 12.288e9} - # FIXME - _decimation = 1 - decimation_available = [1, 2, 4, 8, 16] + """ + ADRV9009 Rx decimation stages. + +-----------+ + +-----------+ Dec 5 (5) +---------+ + | +-----------+ | + | | + | +----------+ +----------+ | +------------+ +--------------+ + >---+---+ RHB3 (2) +---+ RHB2 (2) +---+--+ RHB1 (1,2) +---+ RFIR (1,2,4) + + +----------+ +----------+ +------------+ +--------------+ + + """ + _decimation = 8 + decimation_available = [4, 5, 8, 10, 16, 20, 32, 40] class adrv9009_tx(dac, adrv9009_clock_common, adrv9009_core): """ADRV9009 Transmit model.""" quick_configuration_modes = {"jesd204b": quick_configuration_modes_tx} + name = "ADRV9009_TX" # JESD configurations K_available = [*np.arange(1, 32 + 1)] @@ -206,15 +249,27 @@ class adrv9009_tx(dac, adrv9009_clock_common, adrv9009_core): bit_clock_min_available = {"jesd204b": 2457.6e6} bit_clock_max_available = {"jesd204b": 12.288e9} - # FIXME - _interpolation = 1 - interpolation_available = [1, 2, 4, 8, 16] + """ + ADRV9009 Tx interpolation stages. + +------------+ + +--------------------+ Int 5 (5) +--------------------+ + | +------------+ | + | | + | +------------+ +------------+ +------------+ | +--------------+ + <---+---+ THB3 (1,2) +---+ THB2 (1,2) +---+ THB1 (1,2) +---+---+ TFIR (1,2,4) + + +------------+ +------------+ +------------+ +--------------+ + """ + _interpolation = 8 + interpolation_available = [1, 2, 4, 5, 8, 10, 16, 20, 32] -class adrv9009(core, adrv9009_core, gekko_translation): + +class adrv9009(adrv9009_core): """ADRV9009 combined transmit and receive model.""" + name = "ADRV9009" solver = "CPLEX" + _nested = ["adc", "dac"] def __init__( self, model: Union[GEKKO, CpoModel] = None, solver: str = None @@ -234,6 +289,15 @@ def __init__( self.dac = adrv9009_tx(model, solver=self.solver) self.model = model + def validate_config(self) -> None: + """Validate device configurations including JESD and clocks of both ADC and DAC. + + This check only is for static configuration that does not include + variables which are solved. + """ + self.adc.validate_config() + self.dac.validate_config() + def _get_converters(self) -> List[Union[converter, converter]]: return [self.adc, self.dac] @@ -262,10 +326,13 @@ def get_required_clocks(self) -> List[Dict]: if self.solver == "gekko": raise AssertionError - # return self._gekko_get_required_clocks() + self.config = {} - self.config["lmfc_divisor_sysref"] = self._convert_input( - [*range(1, 20)], name="lmfc_divisor_sysref" + self.config["adc_lmfc_divisor_sysref"] = self._convert_input( + self._lmfc_divisor_sysref_available, name="adc_lmfc_divisor_sysref" + ) + self.config["dac_lmfc_divisor_sysref"] = self._convert_input( + self._lmfc_divisor_sysref_available, name="dac_lmfc_divisor_sysref" ) self.config["input_clock_divider_x2"] = self._convert_input( @@ -277,10 +344,11 @@ def get_required_clocks(self) -> List[Dict]: faster_clk / self.config["input_clock_divider_x2"] ) - faster_clk = max([self.adc.multiframe_clock, self.dac.multiframe_clock]) - self.config["sysref"] = self._add_intermediate( - faster_clk - / (self.config["lmfc_divisor_sysref"] * self.config["lmfc_divisor_sysref"]) + self.config["sysref_adc"] = self._add_intermediate( + self.adc.multiframe_clock / self.config["adc_lmfc_divisor_sysref"] + ) + self.config["sysref_dac"] = self._add_intermediate( + self.dac.multiframe_clock / self.config["dac_lmfc_divisor_sysref"] ) self._add_equation( @@ -290,4 +358,5 @@ def get_required_clocks(self) -> List[Dict]: ] ) - return [self.config["device_clock"], self.config["sysref"]] + return [self.config["device_clock"], self.config["sysref_adc"], + self.config["sysref_dac"]] diff --git a/adijif/jesd.py b/adijif/jesd.py index 6af37f0..794919a 100644 --- a/adijif/jesd.py +++ b/adijif/jesd.py @@ -83,11 +83,11 @@ def validate_clocks(self) -> None: lim = getattr(self, name + "_clock_max") assert ( clk <= lim - ), name + " clock too fast for device {} (limit: {})".format(clk, lim) + ), name + f" clock too fast for device {clk} (limit: {lim})" lim = getattr(self, name + "_clock_min") assert ( clk >= lim - ), name + " clock too slow for device {} (limit: {})".format(clk, lim) + ), name + f" clock too slow for device {clk} (limit: {lim})" @property def bit_clock_min(self) -> Union[int, float]: @@ -130,7 +130,7 @@ def jesd_class(self, value: str) -> None: """ if value not in self.available_jesd_modes: raise Exception( - "Invalid JESD class. Valid are: {}".format(self.available_jesd_modes) + f"Invalid JESD class. Valid are: {self.available_jesd_modes}" ) self._jesd_class = value if value == "jesd204b": @@ -147,12 +147,12 @@ def _check_jesd_config(self) -> None: if "jesd204c" in self.available_jesd_modes: if self.bit_clock > 32e9: raise Exception( - "bit clock (lane rate) {self.bit_clock} too high for JESD204C" + f"bit clock (lane rate) {self.bit_clock} too high for JESD204C" ) elif "jesd204b" in self.available_jesd_modes: if self.bit_clock > 12.5e9: raise Exception( - "bit clock (lane rate) {self.bit_clock} too high for JESD204B" + f"bit clock (lane rate) {self.bit_clock} too high for JESD204B" ) else: raise Exception(f"JESD mode(s) {self.available_jesd_modes}") @@ -586,7 +586,7 @@ def N(self) -> Union[int, float]: @N.setter def N(self, value: int) -> None: - """Set Frames per multiframe. + """Set number of non-dummy bits per sample. Args: value (int): Number of non-dummy bits per sample diff --git a/adijif/system.py b/adijif/system.py index c712a1e..eb3fdc7 100644 --- a/adijif/system.py +++ b/adijif/system.py @@ -86,7 +86,7 @@ def _model_reset(self) -> None: def __init__( self, - conv: str, + conv: Union[str, List[str]], clk: str, fpga: str, vcxo: Union[int, rangec], @@ -320,7 +320,12 @@ def solve(self) -> Dict: for conv in convs: - serdes_used += conv.L + if conv._nested: # MxFE, Transceivers + for name in conv._nested: + serdes_used += getattr(conv, name).L + else: + serdes_used += conv.L + if serdes_used > self.fpga.max_serdes_lanes: raise Exception( "Max SERDES lanes exceeded. {} only available".format( diff --git a/adijif/utils.py b/adijif/utils.py index 6af2f80..c8e6efe 100644 --- a/adijif/utils.py +++ b/adijif/utils.py @@ -40,7 +40,7 @@ def get_jesd_mode_from_params(conv: converter, **kwargs: int) -> List[dict]: if settings[key] == value: found += 1 if found == needed: - results.append({"mode": mode, "jesd_mode": standard}) + results.append({"mode": mode, "jesd_class": standard}) if not results: raise Exception(f"No JESD mode found for {kwargs}") diff --git a/docs/defs.md b/docs/defs.md index 8a7e442..8f92219 100644 --- a/docs/defs.md +++ b/docs/defs.md @@ -66,9 +66,6 @@ To better understand the system as a whole common definitions must be used betwe **local clock** : A clock generated inside a JESD204B device. -**SYSREF clock** -: Slow clock used for cross-device synchronization purposes. - !!! info "All clocks inside a JESD204B system must have a integer relationship" ### Control characters diff --git a/docs/fpga_internal.md b/docs/fpga_internal.md index f52a1e9..4e41ab4 100644 --- a/docs/fpga_internal.md +++ b/docs/fpga_internal.md @@ -4,7 +4,7 @@ JESD204 is a protocol that is made up of layers to manage the different aspects ![JESD204 Chain](imgs/jesd204_chain.png) -From the diagram above, we can see in the FPGA there are explicit cores within the FPGA to manager the PHY Layer, Link Layer, and Transport Layer aspects for the JESD204 protocol. These will have specific drivers and HDL IP that need to configured for a configuration. By configuration, it primarily refers to the clocking and JESD modes. In the diagram there are both TX and RX data paths but generically they can considered identical, data will just flow in a specific direction in each case. +From the diagram above, we can see in the FPGA there are explicit cores within the FPGA to manager the PHY Layer, Link Layer, and Transport Layer aspects for the JESD204 protocol. These will have specific drivers and HDL IP that need to configured for a configuration. By configuration, it primarily refers to the clocking and JESD modes. In the diagram there are both TX and RX data paths but generically they can be considered identical, data will just flow in a specific direction in each case. ## Clocking Layout @@ -29,10 +29,10 @@ Technically, only the **device clock** is needed by the FPGA and all other clock ### Search Strategy There are two main unique cases when selecting the **ref clock** and **device clock**: -* *N'* is not 8 or 16, or when *F* != 1, 2, or 4 -* Otherwise +- *N'* is not 8 or 16, or when *F* != 1, 2, or 4 +- Otherwise -In case (1) the **ref clock** is unlikely to be derived from the **device clock**. Therefore, two separate clocks need to be provided to the FPGA. Otherwise, only a single clock (ignoring **SYSREF**) is required. This is the general behavior based on current analysis; however, this is not a hard definition. The internal solver is configured to favor **ref clock** and **device clock** to be the same value. When this is not possible it will automatically create a secondary clock from the clock chip to be specifically used a the **device clock**. The generation of a separate clock for device clock can be forced by setting *force_separate_device_clock* in the *fpga* object instantiated in the *system* object. +In case (1) the **ref clock** is unlikely to be derived from the **device clock**. Therefore, two separate clocks need to be provided to the FPGA. Otherwise, only a single clock (ignoring **SYSREF**) is required. This is the general behavior based on current analysis; however, this is not a hard definition. The internal solver is configured to favor **ref clock** and **device clock** to be the same value. When this is not possible it will automatically create a secondary clock from the clock chip to be specifically used as the **device clock**. The generation of a separate clock for device clock can be forced by setting *force_separate_device_clock* in the *fpga* object instantiated in the *system* object. ## API Controls diff --git a/docs/parts.md b/docs/parts.md index 509e2a2..aa8aa31 100644 --- a/docs/parts.md +++ b/docs/parts.md @@ -3,12 +3,15 @@ ### Data Converters - [AD9081](devs/converters.md#adijif.converters.ad9081.ad9081) -- [AD9680](devs/converters.md#adijif.converters.ad9680.ad9680) - [AD9144](devs/converters.md#adijif.converters.ad9144.ad9144) +- [AD9680](devs/converters.md#adijif.converters.ad9680.ad9680) - [ADRV9009](devs/converters.md#adijif.converters.adrv9009.adrv9009) ### Clock Chips - [AD9523-1](devs/clocks.md#adijif.clocks.ad9523.ad9523) - [AD9528](devs/clocks.md#adijif.clocks.ad9528.ad9528) -- [HMC7044](devs/clocks.md#adijif.clocks.hmc7044.hmc7044) \ No newline at end of file +- [AD9545](devs/clocks.md#adijif.clocks.ad9545.ad9545) +- [HMC7044](devs/clocks.md#adijif.clocks.hmc7044.hmc7044) +- [LTC6952](devs/clocks.md#adijif.clocks.ltc6952.ltc6952) +- [LTC6953](devs/clocks.md#adijif.clocks.ltc6953.ltc6953) diff --git a/examples/adrv9009_pcbz_example.py b/examples/adrv9009_pcbz_example.py new file mode 100644 index 0000000..598cf3c --- /dev/null +++ b/examples/adrv9009_pcbz_example.py @@ -0,0 +1,61 @@ +import adijif +from rich import print + +""" +Example extracted from the default adrv9009 devicetree, and HDL project. + +- JESD204 parameters + - https://github.com/analogdevicesinc/hdl/blob/master/projects/adrv9009/zcu102/system_project.tcl#L26 + +- decimation: + - adi,rx-profile-rx-fir-decimation = <2> -> RFIR=2 + - adi,rx-profile-rx-dec5-decimation = <4> -> RHB2 and RHB3 enabled + - adi,rx-profile-rhb1-decimation = <1> -> bypass + - decimation: 2 * 4 * 1 = 8 + +- interpolation: + - adi,tx-profile-tx-fir-interpolation = <1> -> bypass + - adi,tx-profile-thb1-interpolation = <2> -> THB1 enabled + - adi,tx-profile-thb2-interpolation = <2> -> THB2 enabled + - adi,tx-profile-thb3-interpolation = <2> -> THB3 enabled + - adi,tx-profile-tx-int5-interpolation = <1> -> bypass + - interpolation = 1 * 2 * 2 * 2 * 1 = 8 + +- sample_clock + - adi,rx-profile-rx-output-rate_khz = IQ data rate = <245760> + - adi,tx-profile-tx-input-rate_khz = IQ data rate at the input of the TFIR = <245760>; + - sample_clock = 245.76e6 +""" + + +vcxo = 122.88e6 +sys = adijif.system("adrv9009", "ad9528", "xilinx", vcxo=vcxo) + +# Clock +sys.clock.m1 = 3 +sys.clock.use_vcxo_doubler = True + +# FPGA +sys.fpga.setup_by_dev_kit_name("zcu102") +sys.fpga.force_qpll = True + + +# Converters +mode_rx = adijif.utils.get_jesd_mode_from_params( + sys.converter.adc, M=4, L=2, S=1, Np=16, +) +sys.converter.adc.set_quick_configuration_mode(**mode_rx[0]) + +mode_tx = adijif.utils.get_jesd_mode_from_params( + sys.converter.dac, M=4, L=4, S=1, Np=16, +) +sys.converter.dac.set_quick_configuration_mode(**mode_tx[0]) + +sys.converter.adc.decimation = 8 +sys.converter.adc.sample_clock = 245.76e6 +sys.converter.dac.interpolation = 8 +sys.converter.dac.sample_clock = 245.76e6 + + +conf = sys.solve() +print(conf) diff --git a/examples/daq2_example_cplex.ipynb b/examples/daq2_example_cplex.ipynb index b90f3f0..ba53d5d 100644 --- a/examples/daq2_example_cplex.ipynb +++ b/examples/daq2_example_cplex.ipynb @@ -98,7 +98,7 @@ "mode = adijif.utils.get_jesd_mode_from_params(\n", " sys.converter, L=4, M=2, Np=16, F=1\n", " )\n", - "sys.converter.set_quick_configuration_mode(mode[0]['mode'],mode[0]['jesd_mode'])\n", + "sys.converter.set_quick_configuration_mode(**mode[0])\n", "\n", "# Get FPGA clocking requirements\n", "sys.fpga.setup_by_dev_kit_name(\"zc706\")\n", diff --git a/tests/test_ad9081.py b/tests/test_ad9081.py index c718cc6..dcb2918 100644 --- a/tests/test_ad9081.py +++ b/tests/test_ad9081.py @@ -81,7 +81,7 @@ def test_ad9081_core_tx_solver(part): ) assert len(mode) == 1 print(mode) - sys.converter.set_quick_configuration_mode(mode[0]["mode"], mode[0]["jesd_mode"]) + sys.converter.set_quick_configuration_mode(**mode[0]) assert sys.converter.L == 4 assert sys.converter.M == 8 assert sys.converter.Np == 16 diff --git a/tests/test_adrv9009.py b/tests/test_adrv9009.py index 8c4dd0b..4364c93 100644 --- a/tests/test_adrv9009.py +++ b/tests/test_adrv9009.py @@ -6,14 +6,20 @@ @pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) -def test_adrv9009_rx_ad9528_solver_compact(solver): +@pytest.mark.parametrize("converter", ["adrv9009_rx", "adrv9009_tx"]) +def test_adrv9009_rxtx_ad9528_solver_compact(solver, converter): vcxo = 122.88e6 - sys = adijif.system("adrv9009_rx", "ad9528", "xilinx", vcxo, solver=solver) + sys = adijif.system(converter, "ad9528", "xilinx", vcxo, solver=solver) # Get Converter clocking requirements sys.converter.sample_clock = 122.88e6 - sys.converter.decimation = 4 + + 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 @@ -43,44 +49,56 @@ def test_adrv9009_rx_ad9528_solver_compact(solver): pprint(cfg) ref = { - "gekko": { - "clock": {"r1": 2, "n2": 16, "m1": 4, "out_dividers": [1, 8, 32, 256]} - }, - "CPLEX": {"clock": {"r1": 2, "n2": 16, "m1": 4, "out_dividers": [1, 8, 256]}}, + "gekko": {"clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 8, 256]}}, + "CPLEX": {"clock": {"r1": 1, "n2": 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"]["ADRV9009_fpga_ref_clk"]["rate"] == 122880000.0 + 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_tx_ad9528_solver_compact(solver): +def test_adrv9009_ad9528_solver_compact(solver): vcxo = 122.88e6 - - sys = adijif.system("adrv9009_tx", "ad9528", "xilinx", vcxo, solver=solver) - - # Get Converter clocking requirements - sys.converter.sample_clock = 122.88e6 - 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 + 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") @@ -90,21 +108,65 @@ def test_adrv9009_tx_ad9528_solver_compact(solver): # 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": 2, "n2": 16, "m1": 4, "out_dividers": [1, 8, 32, 256]} - }, - "CPLEX": {"clock": {"r1": 2, "n2": 16, "m1": 4, "out_dividers": [1, 8, 256]}}, + "gekko": {"clock": {"r1": 1, "n2": 8, "m1": 4, "out_dividers": [1, 8, 256]}}, + "CPLEX": {"clock": {"r1": 1, "n2": 6, "m1": 5, "out_dividers": [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"] - assert ( - cfg["clock"]["output_clocks"]["ADRV9009_fpga_ref_clk"]["rate"] == 122880000.0 - ) # 98304000 + + 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() diff --git a/tests/test_bf.py b/tests/test_bf.py index 8a74ee4..4f95b0d 100644 --- a/tests/test_bf.py +++ b/tests/test_bf.py @@ -534,6 +534,23 @@ def test_system_daq2_rx_ad9528(): { "Converter": np.array(1000000000), "ClockChip": [ + { + 'm1': 4, + 'vco': 4000000000.0, + 'n2': 8, + 'r1': 1, + 'required_output_divs': 1.0, + 'fpga_pll_config': { + 'vco': 10000000000.0, + 'band': 1, + 'd': 1, + 'm': 1, + 'n': 20, + 'qty4_full_rate': 0, + 'type': 'QPLL', + }, + 'sysref_rate': 7812500.0, + }, { "m1": 4, "vco": 4000000000.0, diff --git a/tests/test_clocks.py b/tests/test_clocks.py index 54cbbe3..47905b6 100644 --- a/tests/test_clocks.py +++ b/tests/test_clocks.py @@ -92,44 +92,13 @@ def test_ad9545_fail_no_solver(): clk.solve() -def test_ad9523_1_daq2_validate(): - - vcxo = 125000000 - n2 = 24 - - clk = adijif.ad9523_1() - - # 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 - - -def test_ad9523_1_daq2_cplex_validate(): +@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) +def test_ad9523_1_daq2_validate(solver): vcxo = 125000000 n2 = 24 - clk = adijif.ad9523_1(solver="CPLEX") - # clk = adijif.ad9523_1() + clk = adijif.ad9523_1(solver=solver) # Check config valid clk.n2 = n2 @@ -144,8 +113,6 @@ def test_ad9523_1_daq2_cplex_validate(): o = clk.get_config() - pprint.pprint(o) - assert sorted(o["out_dividers"]) == [1, 2, 128] assert o["m1"] == 3 assert o["m1"] in clk.m1_available @@ -159,7 +126,7 @@ def test_ad9523_1_daq2_cplex_validate(): assert o["output_clocks"]["SYSREF"] == {"divider": 128, "rate": 7812500.0} -@pytest.mark.parametrize("solver", ["geko", "CPLEX"]) +@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) def test_ad9523_1_daq2_validate_fail(solver): msg = r"Solution Not Found" @@ -168,7 +135,7 @@ def test_ad9523_1_daq2_validate_fail(solver): vcxo = 125000000 n2 = 12 - clk = adijif.ad9523_1() + clk = adijif.ad9523_1(solver=solver) # Check config valid clk.n2 = n2 @@ -193,35 +160,6 @@ def test_ad9523_1_daq2_validate_fail(solver): assert o["n2"] == n2 -def test_ad9523_1_daq2_validate_fail_cplex(): - - with pytest.raises(Exception, match=r"Solution Not Found"): - vcxo = 125000000 - n2 = 12 - - clk = adijif.ad9523_1(solver="CPLEX") - - # 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) - - clk.solve() - - # o = clk.get_config() - - # print(o) - - # assert sorted(o["out_dividers"]) == [1, 2, 128] - # assert o["n2"] == n2 - - @pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) def test_ad9523_1_daq2_variable_vcxo_validate(solver): @@ -317,3 +255,61 @@ def test_ltc6953_validate(): 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 diff --git a/tests/test_system.py b/tests/test_system.py new file mode 100644 index 0000000..01aec1d --- /dev/null +++ b/tests/test_system.py @@ -0,0 +1,84 @@ +import pytest + +import adijif + + +def test_converter_lane_count_valid(): + sys = adijif.system("ad9144", "ad9523_1", "xilinx", 125e6) + sys.fpga.setup_by_dev_kit_name("zcu102") + + sys.converter.sample_clock = 1e9 + # Mode 0 + sys.converter.interpolation = 1 + sys.converter.L = 8 + sys.converter.M = 4 + sys.converter.N = 16 + sys.converter.Np = 16 + sys.converter.K = 32 + sys.converter.F = 1 + sys.converter.HD = 1 + sys.converter.clocking_option = "integrated_pll" + + cfg = sys.solve() + + +def test_converter_lane_count_exceeds_fpga_lane_count(): + convs = ["ad9144", "ad9144", "ad9144"] + sys = adijif.system(convs, "ad9523_1", "xilinx", 125e6) + sys.fpga.setup_by_dev_kit_name("zcu102") + + for i, _ in enumerate(convs): + # Mode 0 + sys.converter[i].sample_clock = 1e9 + sys.converter[i].interpolation = 1 + sys.converter[i].L = 8 + sys.converter[i].M = 4 + sys.converter[i].N = 16 + sys.converter[i].Np = 16 + sys.converter[i].K = 32 + sys.converter[i].F = 1 + 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"): + cfg = sys.solve() + + +def test_nested_converter_lane_count_valid(): + sys = adijif.system("adrv9009", "ad9528", "xilinx", 122.88e6) + sys.fpga.setup_by_dev_kit_name("zcu102") + 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") + + cfg = sys.solve() + + +def test_nested_converter_lane_count_exceeds_fpga_lane_count(): + fpga_L = 2 + + sys = adijif.system("adrv9009", "ad9528", "xilinx", 122.88e6) + + sys.fpga.setup_by_dev_kit_name("zcu102") + sys.fpga.max_serdes_lanes = fpga_L # Force it to break + + 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") + + with pytest.raises(Exception, match=f"Max SERDES lanes exceeded. {fpga_L} only available"): + cfg = sys.solve()