Skip to content

Commit

Permalink
Add system draw features. This WIP as it breaks standalone drawing
Browse files Browse the repository at this point in the history
Signed-off-by: Travis F. Collins <[email protected]>
  • Loading branch information
tfcollins committed Jun 14, 2024
1 parent 9343aa2 commit d2f6bb1
Show file tree
Hide file tree
Showing 10 changed files with 597 additions and 30 deletions.
17 changes: 14 additions & 3 deletions adijif/clocks/hmc7044.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def _update_diagram(self, config: Dict) -> None:
f"Unknown key {key}. Must be of for DX where X is a number"
)

def draw(self) -> str:
def draw(self, lo = None) -> str:
"""Draw diagram in d2 language for IC alone with reference clock.
Returns:
Expand All @@ -258,7 +258,13 @@ def draw(self) -> str:
"""
if not self._saved_solution:
raise Exception("No solution to draw. Must call solve first.")
lo = Layout("HMC7044 Example")

system_draw = lo is not None
if not system_draw:
lo = Layout("HMC7044 Example")
else:
# Verify lo is a Layout object
assert isinstance(lo, Layout), "lo must be a Layout object"
lo.add_node(self.ic_diagram_node)

ref_in = Node("REF_IN", ntype="input")
Expand Down Expand Up @@ -309,7 +315,8 @@ def draw(self) -> str:
lo.add_node(clk_node)
lo.add_connection({"from": div, "to": clk_node, "rate": val["rate"]})

return lo.draw()
if system_draw:
return lo.draw()

def get_config(self, solution: CpoSolveResult = None) -> Dict:
"""Extract configurations from solver results.
Expand Down Expand Up @@ -468,6 +475,10 @@ def _get_clock_constraint(
else:
raise Exception("Unknown solver {}".format(self.solver))

# Update diagram to include new divider
d_n = len(self.config["out_dividers"])
self._update_diagram({f"D{d_n}": od})

self.config["out_dividers"].append(od)
return self.config["vcxod"] / self.config["r2"] * self.config["n2"] / od

Expand Down
72 changes: 56 additions & 16 deletions adijif/converters/ad9680_draw.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

class ad9680_draw:

_system_draw = False

def _init_diagram(self) -> None:
"""Initialize diagram for AD9680 alone."""
self.ic_diagram_node = None
Expand Down Expand Up @@ -62,7 +64,7 @@ def _update_diagram(self, config: Dict) -> None:
f"Unknown key {key}. Must be of for DX where X is a number"
)

def draw(self, clocks) -> str:
def draw(self, clocks, lo=None, clock_chip_node=None) -> str:
"""Draw diagram in d2 language for IC alone with reference clock.
Args:
Expand All @@ -76,20 +78,39 @@ def draw(self, clocks) -> str:
"""
if not self._saved_solution:
raise Exception("No solution to draw. Must call solve first.")
lo = Layout("AD9680 Example")

system_draw = lo is not None

if not system_draw:
lo = Layout("AD9680 Example")
else:
# Verify lo is a Layout object
assert isinstance(lo, Layout), "lo must be a Layout object"
lo.add_node(self.ic_diagram_node)

static_options = self.get_config()

ref_in = Node("REF_IN", ntype="input")
if not system_draw:
ref_in = Node("REF_IN", ntype="input")
else:
to_node = lo.get_node("AD9680_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"
assert len(from_node) == 1, "Only one connection allowed"
ref_in = from_node[0]["from"]
# 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_adc_clock"]})
lo.add_connection({"from": ref_in, "to": adc, "rate": clocks["AD9680_ref_clk"]})

# Update Node values
for ddc in range(4):
rate = clocks["ad9680_adc_clock"]
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}")
Expand All @@ -99,15 +120,34 @@ def draw(self, clocks) -> str:
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"],
}
)
if not system_draw:
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"],
}
)
else:
to_node = lo.get_node("AD9680_sysref")
# 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"
assert len(from_node) == 1, "Only one connection allowed"
sysref_in = from_node[0]["from"]
# Remove to_node since it is not needed
lo.remove_node(to_node.name)

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")
Expand All @@ -117,5 +157,5 @@ def draw(self, clocks) -> str:
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()
if not system_draw:
return lo.draw()
140 changes: 131 additions & 9 deletions adijif/draw.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,38 @@ 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.
Args:
name (str): Name of the child node to remove.
"""
# Remove connections with the child first
connections_to_keep = []
for conn in self.connections:
if conn["to"].name != name:
connections_to_keep.append(conn)
self.connections = connections_to_keep
connections_to_keep = []
for conn in self.connections:
if conn["from"].name != name:
connections_to_keep.append(conn)
self.connections = connections_to_keep

children_to_keep = []
for child in self.children:
if child.name != name:
children_to_keep.append(child)
self.children = children_to_keep


class Layout:
"""Layout model for diagraming which contains nodes and connections."""

si = " "
use_d2_cli = False
_write_out_d2_file = True

def __init__(self, name: str) -> None:
"""Initialize layout with name.
Expand All @@ -165,6 +190,30 @@ def add_node(self, node: Node) -> None:
"""
self.nodes.append(node)

def remove_node(self, name: str) -> None:
"""Remove node by name.
Args:
name (str): Name of the node to remove.
"""
# Remove connections with the node first
connections_to_keep = []
for conn in self.connections:
if conn["to"].name != name:
connections_to_keep.append(conn)
self.connections = connections_to_keep
connections_to_keep = []
for conn in self.connections:
if conn["from"].name != name:
connections_to_keep.append(conn)
self.connections = connections_to_keep

nodes_to_keep = []
for node in self.nodes:
if node.name != name:
nodes_to_keep.append(node)
self.nodes = nodes_to_keep

def add_connection(self, connection: dict) -> None:
"""Add connection between two nodes.
Expand All @@ -183,6 +232,54 @@ def add_connection(self, connection: dict) -> None:
rate /= 1000
self.connections.append(connection)

def get_connection(self, from_s: str = None, to: str = None) -> dict:
"""Get connection between two nodes.
Args:
from_s (str): Name of the node from which connection originates.
to (str): Name of the node to which connection goes.
Returns:
List[dict] or dict: List of or single Connection dictionary with keys "from", "to" and optionally "rate".
Raises:
ValueError: If connection not found.
"""
if from_s is None and to is None:
raise ValueError("Both from and to cannot be None.")
found = []
if from_s is None:
for conn in self.connections:
if conn["to"].name == to:
found.append(conn)
return found
if to is None:
for conn in self.connections:
if conn["from"].name == from_s:
found.append(conn)
return found
for conn in self.connections:
if conn["from"].name == from_s and conn["to"].name == to:
return conn


def get_node(self, name: str) -> Node:
"""Get node by name.
Args:
name (str): Name of the node.
Returns:
Node: Node with the given name.
Raises:
ValueError: If node not found.
"""
for node in self.nodes:
if node.name == name:
return node
raise ValueError(f"Node with name {name} not found.")

def draw(self) -> str:
"""Draw diagram in d2 language.
Expand Down Expand Up @@ -259,15 +356,35 @@ def draw_subnodes(node: Node, spacing: str = " ") -> str:
diag += ": " + label if label else ""
diag += "\n"

for node in self.nodes:
for connection in node.connections:
from_p_name = get_parents_names(connection["from"])
to_p_name = get_parents_names(connection["to"])
label = f"{connection['rate']}" if "rate" in connection else ""
diag += f"{from_p_name}{connection['from'].name} -> "
diag += f"{to_p_name}{connection['to'].name}"
diag += ": " + label if label else ""
diag += "\n"
def draw_nodes_connections(nodes):
diag = ""
for node in nodes:
for connection in node.connections:
from_p_name = get_parents_names(connection["from"])
to_p_name = get_parents_names(connection["to"])
label = f"{connection['rate']}" if "rate" in connection else ""
diag += f"{from_p_name}{connection['from'].name} -> "
diag += f"{to_p_name}{connection['to'].name}"
diag += ": " + label if label else ""
diag += "\n"

if node.children:
diag += draw_nodes_connections(node.children)

return diag

# for node in self.nodes:
diag += draw_nodes_connections(self.nodes)

# for node in self.nodes:
# for connection in node.connections:
# from_p_name = get_parents_names(connection["from"])
# to_p_name = get_parents_names(connection["to"])
# label = f"{connection['rate']}" if "rate" in connection else ""
# diag += f"{from_p_name}{connection['from'].name} -> "
# diag += f"{to_p_name}{connection['to'].name}"
# diag += ": " + label if label else ""
# diag += "\n"

if self.use_d2_cli:
with open(self.output_filename, "w") as f:
Expand All @@ -278,6 +395,11 @@ def draw_subnodes(node: Node, spacing: str = " ") -> str:
os.system(cmd) # noqa: S605
return self.output_image_filename
else:
if self._write_out_d2_file:
with open(self.output_filename, "w") as f:
f.write(diag)
print(f"Saved to {self.output_filename}")

# Use bindings
from .d2 import compile
out = compile(diag)
Expand Down
8 changes: 7 additions & 1 deletion adijif/fpgas/xilinx.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@

from adijif.converters.converter import converter as conv
from adijif.fpgas.xilinx_bf import xilinx_bf
from adijif.fpgas.xilinx_draw import xilinx_draw
from adijif.solvers import CpoSolveResult # type: ignore
from adijif.solvers import integer_var # type: ignore
from adijif.solvers import CpoIntVar, GK_Intermediate, GK_Operators, GKVariable


class xilinx(xilinx_bf):
class xilinx(xilinx_bf, xilinx_draw):
"""Xilinx FPGA clocking model.
This model captures different limitations of the Xilinx
PLLs and interfaces used for JESD.
Currently only Zynq 7000 devices have been fully tested.
"""
name = "Xilinx-FPGA"

favor_cpll_over_qpll = False
minimize_fpga_ref_clock = False
Expand Down Expand Up @@ -432,6 +434,7 @@ def setup_by_dev_kit_name(self, name: str) -> None:
self.max_serdes_lanes = 24
else:
raise Exception(f"No boardname found in library for {name}")
self.name = name

def determine_pll(self, bit_clock: int, fpga_ref_clock: int) -> Dict:
"""Determin if configuration is possible with CPLL or QPLL.
Expand Down Expand Up @@ -500,6 +503,8 @@ def get_config(
if solution:
self.solution = solution

self._saved_solution = solution

for config in self.configs:
pll_config: Dict[str, Union[str, int, float]] = {}

Expand All @@ -526,6 +531,7 @@ def get_config(
fpga_ref * pll_config["n1"] * pll_config["n2"] / pll_config["m"] # type: ignore # noqa: B950
)
# Check
print(converter.bit_clock, pll_config["d"], pll_config["vco"])
assert (
pll_config["vco"] * 2 / pll_config["d"] == converter.bit_clock # type: ignore # noqa: B950
), "Invalid CPLL lane rate"
Expand Down
Loading

0 comments on commit d2f6bb1

Please sign in to comment.