From bc45c16b0fee1d624a9fd937bcfa5055ec5d8f42 Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Fri, 27 Sep 2024 15:56:20 -0600 Subject: [PATCH 1/3] Add constraint for ref clock on FPGAs to be related to core clock Signed-off-by: Travis F. Collins --- adijif/clocks/hmc7044.py | 2 +- adijif/fpgas/xilinx.py | 53 +++++++++++++++++++ adijif/system.py | 11 ++-- examples/ad9081_rx_hmc7044_ext_pll_adf4371.py | 1 + examples/ad9082_rxtx_hmc7044.py | 1 + tests/test_adf4371.py | 1 + 6 files changed, 63 insertions(+), 6 deletions(-) diff --git a/adijif/clocks/hmc7044.py b/adijif/clocks/hmc7044.py index 1da54bb..cab6fb7 100644 --- a/adijif/clocks/hmc7044.py +++ b/adijif/clocks/hmc7044.py @@ -517,7 +517,7 @@ def set_requested_clocks( od = self.model.Intermediate(eo * odd + (1 - eo) * even * 2) elif self.solver == "CPLEX": - od = self._convert_input(self._d, "d_" + str(out_freq)) + od = self._convert_input(self._d, f"d_{out_freq}_{d_n}") self._add_equation( [ diff --git a/adijif/fpgas/xilinx.py b/adijif/fpgas/xilinx.py index 58c2989..1c5a030 100644 --- a/adijif/fpgas/xilinx.py +++ b/adijif/fpgas/xilinx.py @@ -24,6 +24,13 @@ class xilinx(xilinx_bf): 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 @@ -122,6 +129,42 @@ class xilinx(xilinx_bf): 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. @@ -848,6 +891,16 @@ def _setup_quad_tile( [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 diff --git a/adijif/system.py b/adijif/system.py index d3a4ce3..fc716c3 100644 --- a/adijif/system.py +++ b/adijif/system.py @@ -318,11 +318,12 @@ def solve(self) -> Dict: sys_refs = [] for conv in convs: - if conv._nested: # MxFE, Transceivers - for name in conv._nested: - serdes_used += getattr(conv, name).L - else: - serdes_used += conv.L + # MIX ME, this need to be directional!!! + # 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( diff --git a/examples/ad9081_rx_hmc7044_ext_pll_adf4371.py b/examples/ad9081_rx_hmc7044_ext_pll_adf4371.py index d7455d2..ac511ca 100644 --- a/examples/ad9081_rx_hmc7044_ext_pll_adf4371.py +++ b/examples/ad9081_rx_hmc7044_ext_pll_adf4371.py @@ -7,6 +7,7 @@ sys = adijif.system("ad9081", "hmc7044", "xilinx", vcxo, solver="CPLEX") sys.fpga.setup_by_dev_kit_name("zcu102") +sys.fpga.ref_clock_constraint = "Unconstrained" sys.fpga.sys_clk_select = "GTH34_SYSCLK_QPLL0" # Use faster QPLL sys.fpga.out_clk_select = "XCVR_PROGDIV_CLK" # force reference to be core clock rate sys.converter.adc.sample_clock = 2900000000 / (8 * 6) diff --git a/examples/ad9082_rxtx_hmc7044.py b/examples/ad9082_rxtx_hmc7044.py index 324c268..d8dc8c7 100644 --- a/examples/ad9082_rxtx_hmc7044.py +++ b/examples/ad9082_rxtx_hmc7044.py @@ -7,6 +7,7 @@ sys = adijif.system("ad9082", "hmc7044", "xilinx", vcxo, solver="CPLEX") sys.fpga.setup_by_dev_kit_name("zcu102") +sys.fpga.ref_clock_constraint = "Unconstrained" sys.fpga.sys_clk_select = "GTH34_SYSCLK_QPLL0" # Use faster QPLL sys.converter.clocking_option = "integrated_pll" sys.fpga.out_clk_select = "XCVR_PROGDIV_CLK" # force reference to be core clock rate diff --git a/tests/test_adf4371.py b/tests/test_adf4371.py index 1e39bcd..36c34d1 100644 --- a/tests/test_adf4371.py +++ b/tests/test_adf4371.py @@ -46,6 +46,7 @@ def test_adf4371_ad9081_sys_example(): sys = adijif.system("ad9081", "hmc7044", "xilinx", vcxo, solver="CPLEX") sys.fpga.setup_by_dev_kit_name("zcu102") + sys.fpga.ref_clock_constraint = "Unconstrained" sys.fpga.sys_clk_select = "GTH34_SYSCLK_QPLL0" # Use faster QPLL sys.fpga.out_clk_select = ( "XCVR_PROGDIV_CLK" # force reference to be core clock rate From 09377624b6fb552891c56d03ef779c4aa06738fc Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Fri, 27 Sep 2024 16:21:09 -0600 Subject: [PATCH 2/3] Update SERDES lane counting to handle JTX and JRX separately Signed-off-by: Travis F. Collins --- adijif/converters/ad9081.py | 3 +++ adijif/converters/ad9144.py | 1 + adijif/converters/ad9680.py | 1 + adijif/converters/adrv9009.py | 3 +++ adijif/converters/converter.py | 10 ++++++++++ adijif/system.py | 33 ++++++++++++++++++++++++--------- tests/test_system.py | 2 +- 7 files changed, 43 insertions(+), 10 deletions(-) diff --git a/adijif/converters/ad9081.py b/adijif/converters/ad9081.py index d6a8889..8a6bd49 100644 --- a/adijif/converters/ad9081.py +++ b/adijif/converters/ad9081.py @@ -284,6 +284,7 @@ class ad9081_rx(adc, ad9081_core): """AD9081 Receive model.""" name = "AD9081_RX" + converter_type = "adc" converter_clock_min = 1.45e9 converter_clock_max = 4e9 @@ -393,6 +394,7 @@ class ad9081_tx(dac, ad9081_core): """AD9081 Transmit model.""" name = "AD9081_TX" + converter_type = "dac" converter_clock_min = 2.9e9 converter_clock_max = 12e9 @@ -480,6 +482,7 @@ class ad9081(ad9081_core): converter_clock_max = ad9081_rx.converter_clock_max quick_configuration_modes: Dict[str, Any] = {} _nested = ["adc", "dac"] + converter_type = "adc_dac" def __init__( self, model: Union[GEKKO, CpoModel] = None, solver: str = None diff --git a/adijif/converters/ad9144.py b/adijif/converters/ad9144.py index 3f820b3..b3ab159 100644 --- a/adijif/converters/ad9144.py +++ b/adijif/converters/ad9144.py @@ -80,6 +80,7 @@ class ad9144(ad9144_bf): """ name = "AD9144" + converter_type = "DAC" # JESD parameters _jesd_params_to_skip_check = ["DualLink", "K"] diff --git a/adijif/converters/ad9680.py b/adijif/converters/ad9680.py index bbae9d6..b1db1b2 100644 --- a/adijif/converters/ad9680.py +++ b/adijif/converters/ad9680.py @@ -55,6 +55,7 @@ class ad9680(ad9680_bf): """ name = "AD9680" + converter_type = "adc" # JESD parameters _jesd_params_to_skip_check = ["DualLink", "CS", "N", "HD"] diff --git a/adijif/converters/adrv9009.py b/adijif/converters/adrv9009.py index 0e27eb6..06710ff 100644 --- a/adijif/converters/adrv9009.py +++ b/adijif/converters/adrv9009.py @@ -189,6 +189,7 @@ class adrv9009_rx(adc, adrv9009_clock_common, adrv9009_core): quick_configuration_modes = {"jesd204b": quick_configuration_modes_rx} name = "ADRV9009_RX" + converter_type = "adc" # JESD configurations K_available = [*np.arange(1, 32 + 1)] @@ -232,6 +233,7 @@ class adrv9009_tx(dac, adrv9009_clock_common, adrv9009_core): quick_configuration_modes = {"jesd204b": quick_configuration_modes_tx} name = "ADRV9009_TX" + converter_type = "dac" # JESD configurations K_available = [*np.arange(1, 32 + 1)] @@ -269,6 +271,7 @@ class adrv9009(adrv9009_core): name = "ADRV9009" solver = "CPLEX" _nested = ["adc", "dac"] + converter_type = "adc_dac" def __init__( self, model: Union[GEKKO, CpoModel] = None, solver: str = None diff --git a/adijif/converters/converter.py b/adijif/converters/converter.py index 78b77b3..aa7e55a 100644 --- a/adijif/converters/converter.py +++ b/adijif/converters/converter.py @@ -119,6 +119,16 @@ def get_current_jesd_mode_settings(self) -> Dict: current_config[attr] = getattr(self, attr) return current_config + @property + @abstractmethod + def converter_type(self) -> str: + """Type of converter. ADC or DAC. + + Returns: + str: Type of converter + """ + raise NotImplementedError + @property @abstractmethod def clocking_option_available(self) -> List[str]: diff --git a/adijif/system.py b/adijif/system.py index fc716c3..f28c2e3 100644 --- a/adijif/system.py +++ b/adijif/system.py @@ -314,18 +314,33 @@ def solve(self) -> Dict: # Setup clock chip self.clock._setup(self.vcxo) self.fpga.configs = [] # reset - serdes_used: float = 0 + serdes_used_tx: int = 0 + serdes_used_rx: int = 0 sys_refs = [] for conv in convs: - # MIX ME, this need to be directional!!! - # 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: + if conv._nested: # MxFE, Transceivers + for name in conv._nested: + ctype = getattr(conv, name).converter_type.lower() + if ctype == "adc": + serdes_used_rx += getattr(conv, name).L + elif ctype == "dac": + serdes_used_tx += getattr(conv, name).L + else: + raise Exception(f"Unknown converter type {ctype}") + else: + ctype = conv.converter_type.lower() + if ctype == "adc": + serdes_used_rx += conv.L + elif ctype == "dac": + serdes_used_tx += conv.L + else: + raise Exception(f"Unknown converter type: {ctype}") + + if ( + serdes_used_rx > self.fpga.max_serdes_lanes + or serdes_used_tx > self.fpga.max_serdes_lanes + ): raise Exception( "Max SERDES lanes exceeded. {} only available".format( self.fpga.max_serdes_lanes diff --git a/tests/test_system.py b/tests/test_system.py index 939b318..db0fe73 100644 --- a/tests/test_system.py +++ b/tests/test_system.py @@ -64,7 +64,7 @@ def test_nested_converter_lane_count_valid(): def test_nested_converter_lane_count_exceeds_fpga_lane_count(): - fpga_L = 2 + fpga_L = 1 sys = adijif.system("adrv9009", "ad9528", "xilinx", 122.88e6) From c801c74692c83db89c4baabef6211965c18ed44c Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Fri, 27 Sep 2024 16:26:43 -0600 Subject: [PATCH 3/3] Fix example for new FPGA API Signed-off-by: Travis F. Collins --- examples/ad9081_rxtx_hmc7044.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/ad9081_rxtx_hmc7044.py b/examples/ad9081_rxtx_hmc7044.py index 8e4edf0..0711c54 100644 --- a/examples/ad9081_rxtx_hmc7044.py +++ b/examples/ad9081_rxtx_hmc7044.py @@ -7,6 +7,7 @@ sys = adijif.system("ad9081", "hmc7044", "xilinx", vcxo, solver="CPLEX") sys.fpga.setup_by_dev_kit_name("zcu102") +sys.fpga.ref_clock_constraint = "Unconstrained" sys.fpga.sys_clk_select = "GTH34_SYSCLK_QPLL0" # Use faster QPLL sys.converter.clocking_option = "integrated_pll" sys.fpga.out_clk_select = "XCVR_PROGDIV_CLK" # force reference to be core clock rate