From efe318f40d57a20d11325065559ed407723f748a Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Thu, 6 Jun 2024 17:11:28 -0600 Subject: [PATCH] Add drawing features to AD9680 plus minor fixes. Plus generic clock source Signed-off-by: Travis F. Collins --- adijif/converters/__init__.py | 2 +- adijif/converters/ad9680.py | 8 +- adijif/converters/ad9680_draw.py | 121 +++++++++++++++++++++++++++++++ adijif/jesd.py | 4 +- adijif/solvers.py | 1 + adijif/types.py | 61 ++++++++++++++++ 6 files changed, 192 insertions(+), 5 deletions(-) create mode 100644 adijif/converters/ad9680_draw.py diff --git a/adijif/converters/__init__.py b/adijif/converters/__init__.py index e0625a0..16443f3 100644 --- a/adijif/converters/__init__.py +++ b/adijif/converters/__init__.py @@ -1,2 +1,2 @@ """ADI JIF converter models.""" -supported_parts = ["ad9680", "adrv9009", "ad9081_rx", "ad9144"] +supported_parts = ["ad9680", "adrv9009", "ad9081_rx", "ad9081_tx", "ad9144"] diff --git a/adijif/converters/ad9680.py b/adijif/converters/ad9680.py index bbae9d6..d1bb354 100644 --- a/adijif/converters/ad9680.py +++ b/adijif/converters/ad9680.py @@ -4,6 +4,7 @@ from adijif.converters.ad9680_bf import ad9680_bf from ..solvers import CpoSolveResult # noqa: I202 +from .ad9680_draw import ad9680_draw # noqa: I202 def _convert_to_config( @@ -17,7 +18,7 @@ def _convert_to_config( CS: Union[int, float], ) -> Dict: # return {"L": L, "M": M, "F": F, "S": S, "HD": HD, "N": N, "Np": Np, "CS": CS} - return {"L": L, "M": M, "F": F, "S": S, "HD": HD, "Np": Np} + return {"L": L, "M": M, "F": F, "S": S, "HD": HD, "Np": Np, "jesd_class": "jesd204b"} quick_configuration_modes = { @@ -43,7 +44,7 @@ def _convert_to_config( } -class ad9680(ad9680_bf): +class ad9680(ad9680_bf, ad9680_draw): """AD9680 high speed ADC model. This model supports direct clock configurations @@ -110,6 +111,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401 self.K = 32 self.sample_clock = 1e9 super().__init__(*args, **kwargs) + self._init_diagram() def _check_valid_jesd_mode(self) -> str: """Verify current JESD configuration for part is valid. @@ -140,6 +142,8 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict: Returns: Dict: Dictionary of clocking rates and dividers for configuration """ + if solution: + self._saved_solution = solution return {"clocking_option": self.clocking_option, "decimation": self.decimation} def get_required_clock_names(self) -> List[str]: diff --git a/adijif/converters/ad9680_draw.py b/adijif/converters/ad9680_draw.py new file mode 100644 index 0000000..717ba69 --- /dev/null +++ b/adijif/converters/ad9680_draw.py @@ -0,0 +1,121 @@ +"""Drawing features for AD9680.""" +from typing import Dict + +from adijif.draw import Layout, Node # type: ignore # isort: skip # noqa: I202 + +class ad9680_draw: + + def _init_diagram(self) -> None: + """Initialize diagram for AD9680 alone.""" + self.ic_diagram_node = None + self._diagram_output_dividers = [] + + + self.ic_diagram_node = Node("AD9680") + + # External + # ref_in = Node("REF_IN", ntype="input") + # lo.add_node(ref_in) + + crossbar = Node("Crossbar", ntype="crossbar") + self.ic_diagram_node.add_child(crossbar) + for adc in range(2): + adc_node = Node(f"ADC{adc}", ntype="adc") + self.ic_diagram_node.add_child(adc_node) + adc_node.shape = "parallelogram" + self.ic_diagram_node.add_connection({"from": adc_node, "to": crossbar}) + + for ddc in range(4): + ddc_node = Node(f"DDC{ddc}", ntype="ddc") + self.ic_diagram_node.add_child(ddc_node) + self.ic_diagram_node.add_connection({"from": crossbar, "to": ddc_node}) + + jesd204_framer = Node("JESD204 Framer", ntype="jesd204framer") + self.ic_diagram_node.add_child(jesd204_framer) + + for ddc in range(4): + ddc = self.ic_diagram_node.get_child(f"DDC{ddc}") + self.ic_diagram_node.add_connection({"from": ddc, "to": jesd204_framer}) + + + def _update_diagram(self, config: Dict) -> None: + """Update diagram with configuration. + + Args: + config (Dict): Configuration dictionary + + Raises: + Exception: If key is not D followed by a number + """ + # Add output dividers + keys = config.keys() + output_dividers = self.ic_diagram_node.get_child("Output Dividers") + for key in keys: + if key.startswith("D"): + div = Node(key, ntype="divider") + output_dividers.add_child(div) + self.ic_diagram_node.add_connection( + {"from": output_dividers, "to": div} + ) + else: + raise Exception( + f"Unknown key {key}. Must be of for DX where X is a number" + ) + + def draw(self, clocks) -> str: + """Draw diagram in d2 language for IC alone with reference clock. + + Args: + clocks (Dict): Dictionary of clocks + + Returns: + str: Diagram in d2 language + + Raises: + Exception: If no solution is saved + """ + if not self._saved_solution: + raise Exception("No solution to draw. Must call solve first.") + lo = Layout("AD9680 Example") + lo.add_node(self.ic_diagram_node) + + static_options = self.get_config() + + ref_in = Node("REF_IN", ntype="input") + lo.add_node(ref_in) + for i in range(2): + adc = self.ic_diagram_node.get_child(f"ADC{i}") + lo.add_connection({"from": ref_in, "to": adc, "rate": clocks["ad9680_adc_clock"]}) + + # Update Node values + for ddc in range(4): + rate = clocks["ad9680_adc_clock"] + self.ic_diagram_node.update_connection("Crossbar", f"DDC{ddc}", rate) + + ddc_node = self.ic_diagram_node.get_child(f"DDC{ddc}") + ddc_node.value = str(static_options["decimation"]) + drate = rate/static_options["decimation"] + + self.ic_diagram_node.update_connection(f"DDC{ddc}", "JESD204 Framer", drate) + + # Connect clock to framer + sysref_in = Node("SYSREF_IN", ntype="input") + + lo.add_connection( + { + "from": sysref_in, + "to": self.ic_diagram_node.get_child("JESD204 Framer"), + "rate": clocks["ad9680_sysref"], + } + ) + + # Connect Remote Deframer + remote_deframer = Node("JESD204 Deframer", ntype="deframer") + + # Add connect for each lane + for i in range(self.L): + lane_rate = self.bit_clock + lo.add_connection({"from": self.ic_diagram_node.get_child("JESD204 Framer"), "to": remote_deframer, "rate": lane_rate}) + + + return lo.draw() \ No newline at end of file diff --git a/adijif/jesd.py b/adijif/jesd.py index 6af37f0..e69a5b9 100644 --- a/adijif/jesd.py +++ b/adijif/jesd.py @@ -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}") diff --git a/adijif/solvers.py b/adijif/solvers.py index a45dda4..504129c 100644 --- a/adijif/solvers.py +++ b/adijif/solvers.py @@ -9,6 +9,7 @@ from docplex.cp.model import binary_var # type: ignore from docplex.cp.model import CpoModel, integer_var, interval_var # type: ignore from docplex.cp.solution import CpoSolveResult # type: ignore + from docplex.cp.modeler import constant # type: ignore cplex_solver = True except ImportError: diff --git a/adijif/types.py b/adijif/types.py index bd9aa4e..a81b213 100644 --- a/adijif/types.py +++ b/adijif/types.py @@ -84,3 +84,64 @@ def __call__(self, model: Union[GEKKO, CpoModel]) -> Dict: config["range"] = model.Intermediate(model.sum(o_array * options)) return config + +class arb_source: + """Arbitrary source for solver. + + This internally uses a division of two integers to create an arbitrary clock + source. + """ + + _max_scalar = int(1e11) + + def __init__(self, name): + """Arbitrary source for solver. + + Args: + name (str): Name of source + """ + self.name = name + + self._a = integer_var(0, self._max_scalar, name=name + "_a") + self._b = integer_var(0, self._max_scalar, name=name + "_b") + + def __call__(self, model: Union[GEKKO, CpoModel]) -> Dict: + """Generate arbitrary source for solver. + + Args: + model (GEKKO, CpoModel): Model of JESD system or part to solve + + Returns: + Dict: Dictionary of solver variable(s) for model + """ + if GEKKO and CpoModel: + assert isinstance( + model, (GEKKO, CpoModel) + ), "arb_source must be called with input type model" + elif GEKKO: + assert isinstance( + model, GEKKO + ), "arb_source must be called with input type model" + elif CpoModel: + assert isinstance( + model, CpoModel + ), "arb_source must be called with input type model" + + config = {} + if isinstance(model, CpoModel): + # config[self.name] = self._a / self._b + # return config + return self._a / self._b + + raise NotImplementedError("Only CpoModel is supported") + + def get_config(self, solution: Dict) -> Dict: + """Get configuration from solver results. + + Args: + solution (Dict): Solver results + + Returns: + Dict: Dictionary of solver variable(s) for model + """ + return {self.name: solution[self._a] / solution[self._b]} \ No newline at end of file