From 6bbf5ebe8c652e82dad7c7fdd418661c7be8bc07 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Wed, 20 Sep 2023 17:28:12 -0400 Subject: [PATCH 01/29] tests: clocks: fix and use solver fixture Fix naming typo that remained undetected because the solver was never used. Signed-off-by: Liam Beguin --- tests/test_clocks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_clocks.py b/tests/test_clocks.py index 54cbbe3..900de12 100644 --- a/tests/test_clocks.py +++ b/tests/test_clocks.py @@ -159,7 +159,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 +168,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 From cf1bc6d59f56c79b8299fbc329ab76c7609bc425 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Wed, 20 Sep 2023 17:31:59 -0400 Subject: [PATCH 02/29] tests: clocks: drop duplicate test case Now that the fixture is used, this test case is duplicated. Signed-off-by: Liam Beguin --- tests/test_clocks.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/tests/test_clocks.py b/tests/test_clocks.py index 900de12..f175e49 100644 --- a/tests/test_clocks.py +++ b/tests/test_clocks.py @@ -193,35 +193,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): From a51246c1a735b87cd5b1d9998a692f5c5848dcba Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Thu, 21 Sep 2023 16:42:33 -0400 Subject: [PATCH 03/29] tests: adrv9009: factorize test cases Signed-off-by: Liam Beguin --- tests/test_adrv9009.py | 57 ++++++------------------------------------ 1 file changed, 7 insertions(+), 50 deletions(-) diff --git a/tests/test_adrv9009.py b/tests/test_adrv9009.py index 1e5f8dc..4dc9f5f 100644 --- a/tests/test_adrv9009.py +++ b/tests/test_adrv9009.py @@ -6,63 +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 - 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) - - ref = { - "gekko": {"clock": {"r1": 2, "n2": 12, "m1": 5, "out_dividers": [6, 9, 192]}}, - "CPLEX": {"clock": {"r1": 2, "n2": 16, "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 - ) # 98304000 - for div in cfg["clock"]["out_dividers"]: - assert div in ref[solver]["clock"]["out_dividers"] + if converter == "adrv9009_rx": + sys.converter.decimation = 4 + else: + sys.converter.interpolation = 4 -@pytest.mark.parametrize("solver", ["gekko", "CPLEX"]) -def test_adrv9009_tx_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 From e1065d8c9d29c29562567b83e16da44618fee61d Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Wed, 20 Sep 2023 17:33:06 -0400 Subject: [PATCH 04/29] tests: clocks: factorize ad9523_1_daq2_validate test case Use fixtures to drop duplicated code. Signed-off-by: Liam Beguin --- tests/test_clocks.py | 39 +++------------------------------------ 1 file changed, 3 insertions(+), 36 deletions(-) diff --git a/tests/test_clocks.py b/tests/test_clocks.py index f175e49..70e5211 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 From 03c9303a10f23f5f60c9c8056caa459265e05803 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Thu, 21 Sep 2023 12:30:25 -0400 Subject: [PATCH 05/29] clocks: ad9528: fix typo in divider name Signed-off-by: Liam Beguin --- adijif/clocks/ad9528.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adijif/clocks/ad9528.py b/adijif/clocks/ad9528.py index d16c463..741dcbf 100644 --- a/adijif/clocks/ad9528.py +++ b/adijif/clocks/ad9528.py @@ -104,7 +104,7 @@ def n2(self) -> Union[int, List[int]]: Returns: int: Current allowable dividers """ - return self._m2 + return self._n2 @n2.setter def n2(self, value: Union[int, List[int]]) -> None: @@ -117,7 +117,7 @@ def n2(self, value: Union[int, List[int]]) -> None: """ self._check_in_range(value, self.n2_available, "n2") - self._m2 = value + self._n2 = value @property def r1(self) -> Union[int, List[int]]: From 238a7768ee9dbc3fa5d96c1f9d4b5356e4ce3925 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Thu, 21 Sep 2023 12:48:22 -0400 Subject: [PATCH 06/29] clocks: ad9528: fix N2 range datasheet says the available range for N2 is 1 to 255. Signed-off-by: Liam Beguin --- adijif/clocks/ad9528.py | 8 ++++---- tests/test_adrv9009.py | 4 ++-- tests/test_bf.py | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/adijif/clocks/ad9528.py b/adijif/clocks/ad9528.py index 741dcbf..e0112ad 100644 --- a/adijif/clocks/ad9528.py +++ b/adijif/clocks/ad9528.py @@ -17,7 +17,7 @@ class ad9528(ad9528_bf): """ Output dividers """ d_available = [*range(1, 1024)] """ 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 +32,7 @@ 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)] + _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)] @@ -99,7 +99,7 @@ def d(self, value: Union[int, List[int]]) -> None: 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 @@ -110,7 +110,7 @@ def n2(self) -> Union[int, List[int]]: 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 diff --git a/tests/test_adrv9009.py b/tests/test_adrv9009.py index 4dc9f5f..8f2b231 100644 --- a/tests/test_adrv9009.py +++ b/tests/test_adrv9009.py @@ -46,8 +46,8 @@ def test_adrv9009_rxtx_ad9528_solver_compact(solver, converter): print(cfg) ref = { - "gekko": {"clock": {"r1": 2, "n2": 12, "m1": 5, "out_dividers": [6, 9, 192]}}, - "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"] 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, From ff2bb54cbdfb48ed864bf5372f937490855c8877 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Thu, 21 Sep 2023 15:36:42 -0400 Subject: [PATCH 07/29] clocks: ad9528: add sysref support Add SYSREF support, this makes sysref optional optional. By default, K will not be computed. Signed-off-by: Liam Beguin --- adijif/clocks/ad9528.py | 69 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/adijif/clocks/ad9528.py b/adijif/clocks/ad9528.py index e0112ad..0223c12 100644 --- a/adijif/clocks/ad9528.py +++ b/adijif/clocks/ad9528.py @@ -16,6 +16,8 @@ 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(1, 256)] """ VCO calibration dividers """ @@ -32,6 +34,7 @@ class ad9528(ad9528_bf): # Defaults _m1: Union[List[int], int] = [3, 4, 5] _d: Union[List[int], int] = [*range(1, 1024)] + _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)] @@ -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,6 +102,30 @@ 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. @@ -191,6 +222,27 @@ def b(self, value: Union[int, List[int]]) -> None: self._check_in_range(value, self.b_available, "b") self._b = value + @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. @@ -225,6 +277,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 +312,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 +375,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,6 +394,16 @@ 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: # od = self.model.Var(integer=True, lb=1, ub=256, value=1) From e8cdff36fade1a989bc561360c463429ad578e1d Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Thu, 21 Sep 2023 16:08:23 -0400 Subject: [PATCH 08/29] clocks: ad9528: add vco and vcxo to configuration output This can be useful to users, and to validate other parameters in test cases. These are also available in the ad9523_1 output. Signed-off-by: Liam Beguin --- adijif/clocks/ad9528.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/adijif/clocks/ad9528.py b/adijif/clocks/ad9528.py index 0223c12..08c424f 100644 --- a/adijif/clocks/ad9528.py +++ b/adijif/clocks/ad9528.py @@ -222,6 +222,14 @@ 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 @@ -268,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"]), From ea82ec08b859ed301f9fe626eb08a18745ce353c Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Fri, 22 Sep 2023 17:47:36 -0400 Subject: [PATCH 09/29] clocks: ad9528: allow for duplicate output frequencies Allow users to defines all output frequencies even if they have the same value. Signed-off-by: Liam Beguin --- adijif/clocks/ad9528.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adijif/clocks/ad9528.py b/adijif/clocks/ad9528.py index 08c424f..1da6b80 100644 --- a/adijif/clocks/ad9528.py +++ b/adijif/clocks/ad9528.py @@ -415,9 +415,9 @@ def set_requested_clocks( ) # 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] From 7bf6aa62393890bad1b7de38f89561eb623d9edd Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Thu, 21 Sep 2023 16:20:28 -0400 Subject: [PATCH 10/29] tests: clocks: add ad9528 test case Signed-off-by: Liam Beguin --- tests/test_clocks.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_clocks.py b/tests/test_clocks.py index 70e5211..4ba725c 100644 --- a/tests/test_clocks.py +++ b/tests/test_clocks.py @@ -255,3 +255,34 @@ 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 From e4ff1ab1f548a7f9e343f72c03f7025e90f3a6f2 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Thu, 21 Sep 2023 16:24:06 -0400 Subject: [PATCH 11/29] tests: clocks: add sysref test case for the ad9528 Signed-off-by: Liam Beguin --- tests/test_clocks.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_clocks.py b/tests/test_clocks.py index 4ba725c..47905b6 100644 --- a/tests/test_clocks.py +++ b/tests/test_clocks.py @@ -286,3 +286,30 @@ def test_ad9528_validate(solver): 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 From c98be2086b3c5085ebdfc5c426451e1d5f9a8603 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Thu, 21 Sep 2023 21:19:04 -0400 Subject: [PATCH 12/29] converters: adrv9009: add explicit converter names In preparation for adrv9009 test cases, add explicit names to each nested converter. Signed-off-by: Liam Beguin --- adijif/converters/adrv9009.py | 3 +++ tests/test_adrv9009.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/adijif/converters/adrv9009.py b/adijif/converters/adrv9009.py index 259ab7e..c82fdba 100644 --- a/adijif/converters/adrv9009.py +++ b/adijif/converters/adrv9009.py @@ -158,6 +158,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)] @@ -190,6 +191,7 @@ 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)] @@ -214,6 +216,7 @@ class adrv9009_tx(dac, adrv9009_clock_common, adrv9009_core): class adrv9009(core, adrv9009_core, gekko_translation): """ADRV9009 combined transmit and receive model.""" + name = "ADRV9009" solver = "CPLEX" def __init__( diff --git a/tests/test_adrv9009.py b/tests/test_adrv9009.py index 8f2b231..a512700 100644 --- a/tests/test_adrv9009.py +++ b/tests/test_adrv9009.py @@ -54,7 +54,7 @@ def test_adrv9009_rxtx_ad9528_solver_compact(solver, converter): 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"] From 6adeed2969b8cd8dafc844b0a4bd56e9c3f6b30e Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Thu, 21 Sep 2023 21:36:35 -0400 Subject: [PATCH 13/29] converters: ad9081: drop unused _model_type Signed-off-by: Liam Beguin --- adijif/converters/ad9081.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/adijif/converters/ad9081.py b/adijif/converters/ad9081.py index 2772ade..768f210 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: @@ -291,7 +290,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 @@ -410,7 +408,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 From 0f994808dc9006e5cc3249a14b81a3e8851ddd0d Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Thu, 21 Sep 2023 21:51:50 -0400 Subject: [PATCH 14/29] converters: ad9081: make use of _add_intermediate Simplify code by making use of gekko_trans.py Signed-off-by: Liam Beguin --- adijif/converters/ad9081.py | 68 ++++++++++--------------------------- 1 file changed, 18 insertions(+), 50 deletions(-) diff --git a/adijif/converters/ad9081.py b/adijif/converters/ad9081.py index 768f210..0d9f8c4 100644 --- a/adijif/converters/ad9081.py +++ b/adijif/converters/ad9081.py @@ -263,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": @@ -367,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() @@ -489,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): @@ -568,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 @@ -614,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": From d267a9f2764de25a53afe2b5f2e21914c8dc668c Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Thu, 21 Sep 2023 22:49:54 -0400 Subject: [PATCH 15/29] converters: adrv9009: fix nested support Update nested support to allow users to run something like: sys = adijif.system("adrv9009", "ad9528", "xilinx", vcxo, solver=solver) Signed-off-by: Liam Beguin --- adijif/converters/adrv9009.py | 48 +++++++++++++--- tests/test_adrv9009.py | 102 ++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 8 deletions(-) diff --git a/adijif/converters/adrv9009.py b/adijif/converters/adrv9009.py index c82fdba..30468e8 100644 --- a/adijif/converters/adrv9009.py +++ b/adijif/converters/adrv9009.py @@ -53,11 +53,28 @@ 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 + 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.""" @@ -218,6 +235,7 @@ class adrv9009(core, adrv9009_core, gekko_translation): name = "ADRV9009" solver = "CPLEX" + _nested = ["adc", "dac"] def __init__( self, model: Union[GEKKO, CpoModel] = None, solver: str = None @@ -237,6 +255,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] @@ -265,10 +292,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( @@ -280,10 +310,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( @@ -293,4 +324,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/tests/test_adrv9009.py b/tests/test_adrv9009.py index a512700..1a964f1 100644 --- a/tests/test_adrv9009.py +++ b/tests/test_adrv9009.py @@ -58,3 +58,105 @@ def test_adrv9009_rxtx_ad9528_solver_compact(solver, converter): ) # 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 + + sys.converter.L = sys.converter.adc.L + sys.converter.dac.L + + # 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) + + 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.converter.L = sys.converter.adc.L + sys.converter.dac.L + sys.fpga.setup_by_dev_kit_name("zc706") + + cfg = sys.solve() From bed526366802dc746d7e10f26ffbed3d235e8e88 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Thu, 21 Sep 2023 22:51:16 -0400 Subject: [PATCH 16/29] tests: adrv9009: mark gekko tests as expected failures The adrv9009 class currently doesn't support the gekko solver. Mark test cases as expected failures until this is implemented. Signed-off-by: Liam Beguin --- tests/test_adrv9009.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_adrv9009.py b/tests/test_adrv9009.py index 1a964f1..0a68309 100644 --- a/tests/test_adrv9009.py +++ b/tests/test_adrv9009.py @@ -107,6 +107,11 @@ def test_adrv9009_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) @@ -159,4 +164,9 @@ def test_adrv9009_ad9528_quick_config(solver): sys.converter.L = sys.converter.adc.L + sys.converter.dac.L 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() From 4869e76225adb4ccd383f06aa3c971622c42bce1 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Fri, 22 Sep 2023 12:17:38 -0400 Subject: [PATCH 17/29] jesd: fix copy-paste error in docstring Signed-off-by: Liam Beguin --- adijif/jesd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adijif/jesd.py b/adijif/jesd.py index 6af37f0..c728a0c 100644 --- a/adijif/jesd.py +++ b/adijif/jesd.py @@ -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 From cd2f84f2fcd88d60884ce10cfd111689b70e6597 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Fri, 22 Sep 2023 13:46:53 -0400 Subject: [PATCH 18/29] jesd: use f-strings Add missing f-string marker on Exceptions, and drop .format(), when not exceeding the characters per line limit. Signed-off-by: Liam Beguin --- adijif/jesd.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/adijif/jesd.py b/adijif/jesd.py index c728a0c..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}") From 78358bf719a92641f5413e96a553a5792c5e8eb6 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Mon, 25 Sep 2023 13:20:37 -0400 Subject: [PATCH 19/29] converters: adrv9009: derive adrv9009_core from converter Parts of the codebase rely on the fact that converters are derived form the converter class (and thus jesd class). Make sure the ADRV9009 follows that assumption before editing the rest of the code. Signed-off-by: Liam Beguin --- adijif/converters/adrv9009.py | 48 ++++++++++++++++++++++------------- tests/test_adrv9009.py | 3 --- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/adijif/converters/adrv9009.py b/adijif/converters/adrv9009.py index 30468e8..7129b6a 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,10 +32,23 @@ 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 @@ -60,6 +74,21 @@ class adrv9009_core: max_tx_sample_clock = 500e6 max_obs_sample_clock = 500e6 + def _check_valid_internal_configuration(self) -> None: + # FIXME + pass + + 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 mapped by get_required_clocks + """ + return ["adrv9009_device_clock", "adrv9009_sysref"] + def get_config(self, solution: CpoSolveResult = None) -> Dict: """Extract configurations from solver results. @@ -84,21 +113,6 @@ def _check_valid_jesd_mode(self) -> None: _extra_jesd_check(self) converter._check_valid_jesd_mode(self) - def _check_valid_internal_configuration(self) -> None: - # FIXME - pass - - 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 mapped by get_required_clocks - """ - return ["adrv9009_device_clock", "adrv9009_sysref"] - def get_config(self, solution: CpoSolveResult = None) -> Dict: """Extract configurations from solver results. @@ -230,7 +244,7 @@ class adrv9009_tx(dac, adrv9009_clock_common, adrv9009_core): interpolation_available = [1, 2, 4, 8, 16] -class adrv9009(core, adrv9009_core, gekko_translation): +class adrv9009(adrv9009_core): """ADRV9009 combined transmit and receive model.""" name = "ADRV9009" diff --git a/tests/test_adrv9009.py b/tests/test_adrv9009.py index 0a68309..53d6d55 100644 --- a/tests/test_adrv9009.py +++ b/tests/test_adrv9009.py @@ -97,8 +97,6 @@ def test_adrv9009_ad9528_solver_compact(solver): assert sys.converter.dac.multiframe_clock == 7.68e6 / 2 # LMFC assert sys.converter.dac.device_clock == 9830.4e6 / 2 / 40 - sys.converter.L = sys.converter.adc.L + sys.converter.dac.L - # Set FPGA config sys.fpga.setup_by_dev_kit_name("zc706") sys.fpga.out_clk_select = "XCVR_REFCLK" @@ -161,7 +159,6 @@ def test_adrv9009_ad9528_quick_config(solver): sys.converter.adc._check_clock_relations() sys.converter.dac._check_clock_relations() - sys.converter.L = sys.converter.adc.L + sys.converter.dac.L sys.fpga.setup_by_dev_kit_name("zc706") if solver == "gekko": From eb9658e429ba853c7dcd7e9b2441deead8b787c9 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Mon, 25 Sep 2023 13:26:53 -0400 Subject: [PATCH 20/29] system: fix type hint on converter input The codebase already allows for passing a list of strings. Make sure the type hint reflects that option. Signed-off-by: Liam Beguin --- adijif/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adijif/system.py b/adijif/system.py index c712a1e..38f9333 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], From a530ccd7db6a51cf2e0ea2aa9c2d27cdfd574c92 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Mon, 25 Sep 2023 15:50:53 -0400 Subject: [PATCH 21/29] system: use proper L parameter for nested devices Nested converters still inherit from the jesd class, and have their own default lane count parameter (L). This value isn't really used and should be the sum of nested converters. Signed-off-by: Liam Beguin --- adijif/system.py | 7 +++- tests/test_system.py | 84 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 tests/test_system.py diff --git a/adijif/system.py b/adijif/system.py index 38f9333..eb3fdc7 100644 --- a/adijif/system.py +++ b/adijif/system.py @@ -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/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() From 99ede21a906572adab062d705ccae3d328d9063f Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Mon, 25 Sep 2023 16:37:47 -0400 Subject: [PATCH 22/29] docs: update list of supported parts Signed-off-by: Liam Beguin --- docs/parts.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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) From f0d48f84b50e3c5ce25548a3e3141a5647bcff66 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Mon, 25 Sep 2023 16:52:57 -0400 Subject: [PATCH 23/29] utils: rename jesd_mode to jesd_class Rename key in get_jesd_mode_from_params' return list, allowing users directly expand an item into set_quick_configuration_mode(). This also avoids further confusion with jesd_mode in the rest of the codebase which usually refers to the mode number describing the jesd204 set of parameters and not the jesd204 class (jesd204, jesd204a, jesd204b, jesd204c). Signed-off-by: Liam Beguin --- adijif/utils.py | 2 +- examples/daq2_example_cplex.ipynb | 2 +- tests/test_ad9081.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/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 From 4d94b302d3a636a9c91077d5a2001975705a2a8a Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Mon, 25 Sep 2023 20:49:45 -0400 Subject: [PATCH 24/29] converters: adrv9009: update interpolation/decimation parameters Update the list of available parameters for the interpolation and decimation stages of the ADRV9009. Signed-off-by: Liam Beguin --- adijif/converters/adrv9009.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/adijif/converters/adrv9009.py b/adijif/converters/adrv9009.py index 7129b6a..9048b88 100644 --- a/adijif/converters/adrv9009.py +++ b/adijif/converters/adrv9009.py @@ -213,9 +213,19 @@ 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): @@ -239,9 +249,19 @@ 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(adrv9009_core): From 3ff7ce658772c148509db7de44bc148b0ba07006 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Mon, 25 Sep 2023 22:02:31 -0400 Subject: [PATCH 25/29] converters: adrv9009: adjust converter_clock range Update converter_clock_(min|max) based on https://www.analog.com/media/en/technical-documentation/data-sheets/ADRV9009.pdf Table 1: Data Rate per Channel (NRZ) min=2135 Mbps max=12288 Mbps Signed-off-by: Liam Beguin --- adijif/converters/adrv9009.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adijif/converters/adrv9009.py b/adijif/converters/adrv9009.py index 9048b88..1877ae4 100644 --- a/adijif/converters/adrv9009.py +++ b/adijif/converters/adrv9009.py @@ -52,7 +52,7 @@ class adrv9009_core(converter, metaclass=ABCMeta): # 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 From a088a2ed25ab73c99429a5162ce90069e150e546 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Mon, 25 Sep 2023 22:05:18 -0400 Subject: [PATCH 26/29] examples: add adrv9009-pcbz sample Add an example based on the default configuration of the adrv9009-pcbz. Signed-off-by: Liam Beguin --- examples/adrv9009_pcbz_example.py | 61 +++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 examples/adrv9009_pcbz_example.py 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) From 9d14f95974c31c355b914d82c704d48d8ec48495 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Tue, 26 Sep 2023 17:55:16 -0400 Subject: [PATCH 27/29] converters: adrv9009: fix jesd_mode config output jesd_mode in sys.get_config() always returns None. Make sure to pass down the parent class' data. Signed-off-by: Liam Beguin --- adijif/converters/adrv9009.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adijif/converters/adrv9009.py b/adijif/converters/adrv9009.py index 1877ae4..c506173 100644 --- a/adijif/converters/adrv9009.py +++ b/adijif/converters/adrv9009.py @@ -111,7 +111,7 @@ class adrv9009_clock_common(adrv9009_core, adrv9009_bf): 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) + return super()._check_valid_jesd_mode() def get_config(self, solution: CpoSolveResult = None) -> Dict: """Extract configurations from solver results. From 07f0a8b53dd4047c4200ce9bf3dcf72bbae3ea96 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Thu, 28 Sep 2023 19:03:34 -0400 Subject: [PATCH 28/29] docs: remove duplicate SYSREF definition Signed-off-by: Liam Beguin --- docs/defs.md | 3 --- 1 file changed, 3 deletions(-) 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 From dba090cb88c36435db89d1d2969778bf813bafa4 Mon Sep 17 00:00:00 2001 From: Liam Beguin Date: Thu, 28 Sep 2023 19:04:03 -0400 Subject: [PATCH 29/29] docs: fpga_internal: fix minor typos and formatting Signed-off-by: Liam Beguin --- docs/fpga_internal.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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