From 458c014a312715c1fb6dc2d59c18f6581d78b4a6 Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Tue, 18 Jun 2024 16:51:13 -0600 Subject: [PATCH] Update drawing to work at system level and indepently on components Signed-off-by: Travis F. Collins --- adijif/converters/ad9680.py | 12 +- adijif/converters/ad9680_draw.py | 27 +-- adijif/draw.py | 8 +- adijif/fpgas/xilinx_draw.py | 287 +++++++++++++++++++++---------- adijif/system_draw.py | 4 +- tests/test_draw.py | 52 +++++- 6 files changed, 274 insertions(+), 116 deletions(-) diff --git a/adijif/converters/ad9680.py b/adijif/converters/ad9680.py index d1bb354..8216a13 100644 --- a/adijif/converters/ad9680.py +++ b/adijif/converters/ad9680.py @@ -18,7 +18,15 @@ 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, "jesd_class": "jesd204b"} + return { + "L": L, + "M": M, + "F": F, + "S": S, + "HD": HD, + "Np": Np, + "jesd_class": "jesd204b", + } quick_configuration_modes = { @@ -154,7 +162,7 @@ def get_required_clock_names(self) -> List[str]: Returns: List[str]: List of strings of clock names in order """ - return ["ad9680_adc_clock", "ad9680_sysref"] + return ["AD9680_ref_clk", "AD9680_sysref"] def get_required_clocks(self) -> List: """Generate list required clocks. diff --git a/adijif/converters/ad9680_draw.py b/adijif/converters/ad9680_draw.py index 573efdf..3b0f98a 100644 --- a/adijif/converters/ad9680_draw.py +++ b/adijif/converters/ad9680_draw.py @@ -3,6 +3,7 @@ from adijif.draw import Layout, Node # type: ignore # isort: skip # noqa: I202 + class ad9680_draw: _system_draw = False @@ -12,7 +13,6 @@ def _init_diagram(self) -> None: self.ic_diagram_node = None self._diagram_output_dividers = [] - self.ic_diagram_node = Node("AD9680") # External @@ -39,7 +39,6 @@ def _init_diagram(self) -> None: 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. @@ -78,7 +77,7 @@ def draw(self, clocks, lo=None, clock_chip_node=None) -> str: """ if not self._saved_solution: raise Exception("No solution to draw. Must call solve first.") - + system_draw = lo is not None if not system_draw: @@ -92,6 +91,7 @@ def draw(self, clocks, lo=None, clock_chip_node=None) -> str: if not system_draw: ref_in = Node("REF_IN", ntype="input") + lo.add_node(ref_in) else: to_node = lo.get_node("AD9680_ref_clk") # Locate node connected to this one @@ -103,19 +103,20 @@ def draw(self, clocks, lo=None, clock_chip_node=None) -> str: # Remove to_node since it is not needed lo.remove_node(to_node.name) - 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_ref_clk"]}) + lo.add_connection( + {"from": ref_in, "to": adc, "rate": clocks["AD9680_ref_clk"]} + ) # Update Node values for ddc in range(4): - rate = clocks["AD9680_ref_clk"] + rate = clocks["AD9680_ref_clk"] 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"] + drate = rate / static_options["decimation"] self.ic_diagram_node.update_connection(f"DDC{ddc}", "JESD204 Framer", drate) @@ -155,7 +156,13 @@ def draw(self, clocks, lo=None, clock_chip_node=None) -> str: # 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}) + lo.add_connection( + { + "from": self.ic_diagram_node.get_child("JESD204 Framer"), + "to": remote_deframer, + "rate": lane_rate, + } + ) if not system_draw: - return lo.draw() \ No newline at end of file + return lo.draw() diff --git a/adijif/draw.py b/adijif/draw.py index 88749ef..42338cc 100644 --- a/adijif/draw.py +++ b/adijif/draw.py @@ -135,7 +135,7 @@ def get_child(self, name: str) -> Node: if child.name == name: return child raise ValueError(f"Child with name {name} not found.") - + def remove_child(self, name: str) -> None: """Remove child node by name. @@ -262,7 +262,6 @@ def get_connection(self, from_s: str = None, to: str = None) -> dict: if conn["from"].name == from_s and conn["to"].name == to: return conn - def get_node(self, name: str) -> Node: """Get node by name. @@ -370,9 +369,9 @@ def draw_nodes_connections(nodes): if node.children: diag += draw_nodes_connections(node.children) - + return diag - + # for node in self.nodes: diag += draw_nodes_connections(self.nodes) @@ -402,6 +401,7 @@ def draw_nodes_connections(nodes): # Use bindings from .d2 import compile + out = compile(diag) # with open(self.output_image_filename, "w") as f: # f.write(out) diff --git a/adijif/fpgas/xilinx_draw.py b/adijif/fpgas/xilinx_draw.py index 82a9872..a505ece 100644 --- a/adijif/fpgas/xilinx_draw.py +++ b/adijif/fpgas/xilinx_draw.py @@ -36,41 +36,47 @@ def _init_diagram(self) -> None: {"from": jesd204_transport, "to": jesd204_application} ) - def _draw_phy(self, config: Dict) -> None: - - cfg = { - "clocks": {"FPGA_REF": 500000000.0, "LINK_OUT_REF": 125000000.0}, - "fpga": { - "type": "cpll", - "m": 2, - "d": 1, - "n1": 5, - "n2": 4, - "vco": 5000000000.0, - "sys_clk_select": "XCVR_CPLL", - "progdiv": 40.0, - "out_clk_select": "XCVR_PROGDIV_CLK", - "separate_device_clock_required": 1, - "transport_samples_per_clock": 8, - }, - } + def _draw_phy(self, config: Dict, converter=None) -> None: + + # cfg = { + # "clocks": {"FPGA_REF": 500000000.0, "LINK_OUT_REF": 125000000.0}, + # "fpga": { + # "type": "cpll", + # "m": 2, + # "d": 1, + # "n1": 5, + # "n2": 4, + # "vco": 5000000000.0, + # "sys_clk_select": "XCVR_CPLL", + # "progdiv": 40.0, + # "out_clk_select": "XCVR_PROGDIV_CLK", + # "separate_device_clock_required": 1, + # "transport_samples_per_clock": 8, + # }, + # } + + if converter: + name = f"{converter.name.upper()}_fpga_ref_clk" + ref_in_rate = config["clocks"][name] + else: + ref_in_rate = config["clocks"]["FPGA_REF"] phy = Node("JESD204-PHY-IP", ntype="phy") self.ic_diagram_node.add_child(phy) # PLL - if config['fpga']['type'] == 'cpll': + if config["fpga"]["type"] == "cpll": cpll = Node("CPLL", ntype="cpll") phy.add_child(cpll) # Put stuff in CPLL m = Node("M", ntype="divider") cpll.add_child(m) - m.value = config['fpga']['m'] + m.value = config["fpga"]["m"] pfd = Node("PFD", ntype="phase-frequency-detector") cpll.add_child(pfd) - cpll.add_connection({"from": m, "to": pfd, "rate": config['clocks']['FPGA_REF']}) + cpll.add_connection({"from": m, "to": pfd, "rate": ref_in_rate}) cp = Node("CP", ntype="charge-pump") cpll.add_child(cp) @@ -86,17 +92,17 @@ def _draw_phy(self, config: Dict) -> None: d = Node("D", ntype="divider") cpll.add_child(d) - d.value = config['fpga']['d'] - cpll.add_connection({"from": vco, "to": d, "rate": config['fpga']['vco']}) + d.value = config["fpga"]["d"] + cpll.add_connection({"from": vco, "to": d, "rate": config["fpga"]["vco"]}) n1 = Node("N1", ntype="divider") cpll.add_child(n1) - n1.value = config['fpga']['n1'] + n1.value = config["fpga"]["n1"] cpll.add_connection({"from": vco, "to": n1}) n2 = Node("N2", ntype="divider") cpll.add_child(n2) - n2.value = config['fpga']['n2'] + n2.value = config["fpga"]["n2"] cpll.add_connection({"from": n1, "to": n2}) cpll.add_connection({"from": n2, "to": pfd}) @@ -104,48 +110,107 @@ def _draw_phy(self, config: Dict) -> None: phy.add_child(transceiver) phy.add_connection({"from": d, "to": transceiver}) - + # Define common in/out in_c = m xcvr_out = d - xcvr_out_rate = config['fpga']['vco'] / config['fpga']['d'] + xcvr_out_rate = config["fpga"]["vco"] / config["fpga"]["d"] else: qpll = Node("QPLL", ntype="qpll") phy.add_child(qpll) - xcvr_out = qpll - xcvr_out_rate = config['fpga']['vco'] + # Put stuff in QPLL + m = Node("M", ntype="divider") + m.value = config["fpga"]["m"] + qpll.add_child(m) + + pfd = Node("PFD", ntype="phase-frequency-detector") + qpll.add_child(pfd) + + cp = Node("CP", ntype="charge-pump") + qpll.add_child(cp) - in_c = qpll + lpf = Node("LPF", ntype="loop-filter") + qpll.add_child(lpf) + + vco = Node("VCO", ntype="vco") + qpll.add_child(vco) + n = Node("N", ntype="divider") + n.value = config["fpga"]["n"] + qpll.add_child(n) + + # d2 = Node("/2", ntype="divider") + # qpll.add_child(d2) + + d = Node("D", ntype="divider") + d.value = config["fpga"]["d"] + qpll.add_child(d) + + # Connect + phy.add_connection({"from": m, "to": pfd}) + phy.add_connection({"from": pfd, "to": cp}) + phy.add_connection({"from": cp, "to": lpf}) + phy.add_connection({"from": lpf, "to": vco}) + phy.add_connection({"from": vco, "to": n}) + # TRX is DDR devices so it uses both clock edges (skipping /2 and *2 dividers) + # phy.add_connection({"from": vco, "to": d2, "rate": config['fpga']['vco']}) + # phy.add_connection({"from": d2, "to": d, "rate": config['fpga']['vco'] / 2}) + phy.add_connection({"from": vco, "to": d, "rate": config["fpga"]["vco"]}) + phy.add_connection({"from": n, "to": pfd}) + + xcvr_out = d + xcvr_out_rate = config["fpga"]["vco"] / config["fpga"]["d"] + + in_c = m # Divider complex trx_dividers = Node("Transceiver Dividers", ntype="trx-dividers") phy.add_child(trx_dividers) - if config['fpga']['out_clk_select'] == "XCVR_OUTCLK_PCS": + if config["fpga"]["out_clk_select"] == "XCVR_OUTCLK_PCS": raise Exception("Only XCVR_PROGDIV_CLK supported for now") - elif config['fpga']['out_clk_select'] == "XCVR_OUTCLK_PMA": + elif config["fpga"]["out_clk_select"] == "XCVR_OUTCLK_PMA": raise Exception("Only XCVR_PROGDIV_CLK supported for now") - elif config['fpga']['out_clk_select'] == "XCVR_REF_CLK": - # raise Exception("Only XCVR_PROGDIV_CLK supported") + elif config["fpga"]["out_clk_select"] == "XCVR_REF_CLK": + + rmux = Node("REFCLKSEL-Mux", ntype="mux") + trx_dividers.add_child(rmux) + connect_to_input = [rmux] + + smux = Node("SYSCLKSEL-Mux", ntype="mux") + trx_dividers.add_child(smux) + trx_dividers.add_connection({"from": rmux, "to": smux, "rate": ref_in_rate}) - div_ref_clk = Node("DIV_REF_CLK", ntype="divider") - trx_dividers.add_child(div_ref_clk) - div_ref_clk.value = 1 - trx_dividers.add_connection({"from": xcvr_out, "to": div_ref_clk, "rate": xcvr_out_rate}) + out_rate = ref_in_rate + out = smux - out_rate = xcvr_out_rate - out = div_ref_clk + elif config["fpga"]["out_clk_select"] == "XCVR_REFCLK_DIV2": - elif config['fpga']['out_clk_select'] == "XCVR_REFCLK_DIV2": - raise Exception("Only XCVR_PROGDIV_CLK supported") - - elif config['fpga']['out_clk_select'] == "XCVR_PROGDIV_CLK": + rmux = Node("REFCLKSEL-Mux", ntype="mux") + trx_dividers.add_child(rmux) + # trx_dividers.add_connection( + # {"from": in_c, "to": rmux, "rate": ref_in_rate} + # ) + connect_to_input = [rmux] + + smux = Node("SYSCLKSEL-Mux", ntype="mux") + trx_dividers.add_child(smux) + trx_dividers.add_connection({"from": rmux, "to": smux, "rate": ref_in_rate}) + + div2 = Node("DIV2", ntype="divider") + trx_dividers.add_child(div2) + div2.value = 2 + trx_dividers.add_connection({"from": smux, "to": div2, "rate": ref_in_rate}) + + out_rate = ref_in_rate / 2 + out = div2 + + elif config["fpga"]["out_clk_select"] == "XCVR_PROGDIV_CLK": mux = Node("PLLCLKSEL-Mux", ntype="mux") - mux.value = config['fpga']['type'] + mux.value = config["fpga"]["type"] trx_dividers.add_child(mux) phy.add_connection({"from": xcvr_out, "to": mux, "rate": xcvr_out_rate}) @@ -155,33 +220,37 @@ def _draw_phy(self, config: Dict) -> None: progdiv = Node("ProgDiv", ntype="divider") trx_dividers.add_child(progdiv) - progdiv.value = int(config['fpga']['progdiv']) + progdiv.value = int(config["fpga"]["progdiv"]) trx_dividers.add_connection({"from": cdr, "to": progdiv}) - out_rate = xcvr_out_rate / config['fpga']['progdiv'] + out_rate = xcvr_out_rate / config["fpga"]["progdiv"] out = progdiv + connect_to_input = [] else: - raise Exception(f"Unknown out_clk_select: {config['fpga']['out_clk_select']}") - + raise Exception( + f"Unknown out_clk_select: {config['fpga']['out_clk_select']}" + ) out_mux = Node("OUTCLKSEL-Mux", ntype="mux") trx_dividers.add_child(out_mux) trx_dividers.add_connection({"from": out, "to": out_mux, "rate": out_rate}) - - return in_c, out_mux - def draw(self, config, lo=None) -> str: + return in_c, out_mux, connect_to_input + + def draw(self, config, lo=None, converters=None) -> str: """Draw diagram in d2 language for IC alone with reference clock. Args: clocks (Dict): Clock settings + lo (Layout): Layout object to draw on (optional) + converters (List): List of converters to draw (optional) Returns: str: SVG data """ if not self._saved_solution: raise Exception("No solution to draw. Must call solve first.") - + system_draw = lo is not None if not system_draw: @@ -189,6 +258,7 @@ def draw(self, config, lo=None) -> str: else: # Verify lo is a Layout object assert isinstance(lo, Layout), "lo must be a Layout object" + assert len(converters) == 1, "Only one converter supported" lo.add_node(self.ic_diagram_node) @@ -196,63 +266,90 @@ def draw(self, config, lo=None) -> str: if not system_draw: ref_in = Node("REF_IN", ntype="input") + lo.add_node(ref_in) else: - to_node = lo.get_node("AD9680_fpga_ref_clk") - # Locate node connected to this one - from_node = lo.get_connection(to=to_node.name) - assert from_node, "No connection found" - assert isinstance(from_node, list), "Connection must be a list" - ref_in = from_node[0]["from"] - lo.remove_node(to_node.name) - lo.add_node(ref_in) - - in_c, out_c = self._draw_phy(config) - self.ic_diagram_node.add_connection({"from": ref_in, "to": in_c, "rate": clocks["AD9680_ref_clk"]}) + for converter in converters: + to_node = lo.get_node(f"{converter.name.upper()}_fpga_ref_clk") + # Locate node connected to this one + from_node = lo.get_connection(to=to_node.name) + assert from_node, "No connection found" + assert isinstance(from_node, list), "Connection must be a list" + ref_in = from_node[0]["from"] + lo.remove_node(to_node.name) + + # TO DO, ADD PHY PER CONVERTER + in_c, out_c, connect_to_input = self._draw_phy(config, converters[0]) + + for converter in converters: + rcn = f"{converter.name.upper()}_fpga_ref_clk" + assert rcn in clocks, f"Missing clock {rcn}" + self.ic_diagram_node.add_connection( + {"from": ref_in, "to": in_c, "rate": clocks[rcn]} + ) + if connect_to_input: + for c in connect_to_input: + self.ic_diagram_node.add_connection( + {"from": ref_in, "to": c, "rate": clocks[rcn]} + ) + # Delete Transceiver node self.ic_diagram_node.remove_child("Transceiver") # Connect out_c to JESD204-Link-IP - self.ic_diagram_node.add_connection( - { - "from": out_c, - "to": self.ic_diagram_node.get_child("JESD204-Link-IP"), - "rate": clocks["AD9680_fpga_link_out_clk"], - } - ) + for converter in converters: + self.ic_diagram_node.add_connection( + { + "from": out_c, + "to": self.ic_diagram_node.get_child("JESD204-Link-IP"), + "rate": clocks[f"{converter.name.upper()}_fpga_link_out_clk"], + } + ) # Connect device clock to JESD204-Link-IP if not system_draw: device_clock = Node("Device Clock", ntype="input") + lo.add_node(device_clock) + self.ic_diagram_node.add_connection( + { + "from": device_clock, + "to": self.ic_diagram_node.get_child("JESD204-Link-IP"), + } + ) + else: - to_node = lo.get_node("AD9680_fpga_link_out_clk") - # Locate node connected to this one - from_node = lo.get_connection(to=to_node.name) - assert from_node, "No connection found" - assert isinstance(from_node, list), "Connection must be a list" - device_clock = from_node[0]["from"] - lo.remove_node(to_node.name) - lo.add_node(device_clock) - self.ic_diagram_node.add_connection( - { - "from": device_clock, - "to": self.ic_diagram_node.get_child("JESD204-Link-IP"), - "rate": clocks["AD9680_fpga_link_out_clk"], - } - ) + for converter in converters: + c_name = f"{converter.name.upper()}_fpga_link_out_clk" + to_node = lo.get_node(c_name) + # Locate node connected to this one + from_node = lo.get_connection(to=to_node.name) + assert from_node, "No connection found" + assert isinstance(from_node, list), "Connection must be a list" + device_clock = from_node[0]["from"] + lo.remove_node(to_node.name) + + self.ic_diagram_node.add_connection( + { + "from": device_clock, + "to": self.ic_diagram_node.get_child("JESD204-Link-IP"), + "rate": clocks[c_name], + } + ) # Connect SYSREF to JESD204-Link-IP if not system_draw: sysref = Node("SYSREF", ntype="input") + lo.add_node(sysref) else: - parent = lo.get_node("AD9680") - to_node = parent.get_child("JESD204 Framer") - # Locate node connected to this one - from_node = lo.get_connection(to=to_node.name) - assert from_node, "No connection found" - assert isinstance(from_node, list), "Connection must be a list" - sysref = from_node[0]["from"] - # lo.remove_node(to_node.name) - lo.add_node(sysref) + for converter in converters: + parent = lo.get_node(converter.name.upper()) + to_node = parent.get_child("JESD204 Framer") + # Locate node connected to this one + from_node = lo.get_connection(to=to_node.name) + assert from_node, "No connection found" + assert isinstance(from_node, list), "Connection must be a list" + sysref = from_node[0]["from"] + # lo.remove_node(to_node.name) + self.ic_diagram_node.add_connection( { "from": sysref, diff --git a/adijif/system_draw.py b/adijif/system_draw.py index 01e32c6..8b8606d 100644 --- a/adijif/system_draw.py +++ b/adijif/system_draw.py @@ -41,7 +41,9 @@ def draw(self, config): print(config) fpga_clocking = {"clocks": cnv_clocking, 'fpga': config['fpga_AD9680']} - self.fpga.draw(fpga_clocking, lo) + if not isinstance(self.converter, list): + cnvers = [self.converter] + self.fpga.draw(fpga_clocking, lo, cnvers) # Draw the diagram return lo.draw() diff --git a/tests/test_draw.py b/tests/test_draw.py index f1d4bb0..0447a41 100644 --- a/tests/test_draw.py +++ b/tests/test_draw.py @@ -2,6 +2,7 @@ import adijif as jif + @pytest.mark.drawing def test_ad9680_draw(): adc = jif.ad9680() @@ -37,6 +38,7 @@ def test_ad9680_draw(): # with open("ad9680_example.svg", "w") as f: # f.write(image_data) + @pytest.mark.drawing def test_xilinx_draw(): import adijif as jif @@ -51,11 +53,12 @@ def test_xilinx_draw(): # dc = dummy_converter() dc = jif.ad9680() - fpga_ref = jif.types.arb_source("FPGA_REF") link_out_ref = jif.types.arb_source("LINK_OUT_REF") - clocks = fpga.get_required_clocks(dc, fpga_ref(fpga.model), link_out_ref(fpga.model)) + clocks = fpga.get_required_clocks( + dc, fpga_ref(fpga.model), link_out_ref(fpga.model) + ) print(clocks) solution = fpga.model.solve(LogVerbosity="Quiet") @@ -68,6 +71,47 @@ def test_xilinx_draw(): clock_values.update(clk.get_config(solution)) settings["clocks"] = clock_values - - settings['fpga'] = fpga.get_config(dc, settings['clocks']['FPGA_REF'], solution) + settings["fpga"] = fpga.get_config(dc, settings["clocks"]["FPGA_REF"], solution) print(settings) + + +@pytest.mark.drawing +def test_system_draw(): + import pprint + import adijif + + vcxo = 125000000 + + sys = adijif.system("ad9680", "hmc7044", "xilinx", vcxo) + + # Get Converter clocking requirements + sys.converter.sample_clock = 1e9 + sys.converter.decimation = 1 + sys.converter.set_quick_configuration_mode(str(0x88)) + sys.converter.K = 32 + sys.Debug_Solver = False + + # Get FPGA clocking requirements + sys.fpga.setup_by_dev_kit_name("zc706") + sys.fpga.force_qpll = 1 + + cfg = sys.solve() + + pprint.pprint(cfg) + + # print("Clock config:") + # pprint.pprint(cfg["clock"]) + + # print("Converter config:") + # pprint.pprint(cfg["converter"]) + + print("FPGA config:") + pprint.pprint(cfg["fpga_AD9680"]) + + # print("JESD config:") + # pprint.pprint(cfg["jesd_AD9680"]) + + data = sys.draw(cfg) + + # with open("daq2_example.svg", "w") as f: + # f.write(data)