Skip to content

Commit ba05b26

Browse files
authored
Merge pull request #216 from analogdevicesinc/hal/draw
2 parents 3f4677e + 06725c8 commit ba05b26

17 files changed

Lines changed: 1859 additions & 7 deletions

File tree

adijif/clocks/ad9545.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import docplex.cp.modeler as mod
88

99
from adijif.clocks.clock import clock
10+
from adijif.draw import Layout, Node
1011
from adijif.solvers import CpoSolveResult
1112

1213

@@ -198,8 +199,103 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict:
198199
for i in range(0, 10):
199200
config["q" + str(i)] = self._get_val(self.config["q" + str(i)])
200201

202+
self._saved_solution = config
203+
201204
return config
202205

206+
def draw(self, lo: Layout = None) -> str:
207+
"""Draw clock tree diagram for AD9545.
208+
209+
Args:
210+
lo: Layout for drawing
211+
212+
Returns:
213+
str: SVG diagram string
214+
215+
Raises:
216+
Exception: If no solution is saved
217+
"""
218+
if not self._saved_solution:
219+
raise Exception("No solution to draw. Must call solve first.")
220+
221+
config = self._saved_solution
222+
system_draw = lo is not None
223+
if not system_draw:
224+
lo = Layout("AD9545 Example")
225+
else:
226+
assert isinstance(lo, Layout), "lo must be a Layout object"
227+
228+
ic_node = Node("AD9545")
229+
lo.add_node(ic_node)
230+
231+
# Add active input references with R dividers
232+
for i in range(4):
233+
if self.input_refs[i] != 0:
234+
r_val = config[f"r{i}"]
235+
r_node = Node(f"R{i}", ntype="divider")
236+
r_node.value = str(r_val)
237+
ic_node.add_child(r_node)
238+
239+
if not system_draw:
240+
ref_node = Node(f"REF{i}", ntype="input")
241+
lo.add_node(ref_node)
242+
lo.add_connection(
243+
{
244+
"from": ref_node,
245+
"to": r_node,
246+
"rate": self.input_refs[i],
247+
}
248+
)
249+
250+
# Add PLLs
251+
for i in range(2):
252+
if self.PLL_used[i]:
253+
pll_rate = config[f"PLL{i}"].get("rate_hz", 0)
254+
pll_node = Node(f"PLL{i}", ntype="voltage-controlled-oscillator")
255+
pll_node.shape = "circle"
256+
ic_node.add_child(pll_node)
257+
258+
# Connect active input R dividers to PLL
259+
for j in range(4):
260+
if self.input_refs[j] != 0:
261+
r_node = ic_node.get_child(f"R{j}")
262+
pll_in_rate = self.input_refs[j] / config[f"r{j}"]
263+
ic_node.add_connection(
264+
{"from": r_node, "to": pll_node, "rate": pll_in_rate}
265+
)
266+
267+
# Add output Q dividers
268+
out_dividers = Node("Output Dividers", ntype="shell")
269+
ic_node.add_child(out_dividers)
270+
271+
for i in range(10):
272+
if self.out_freqs[i] != 0:
273+
q_val = config[f"q{i}"]
274+
q_node = Node(f"Q{i}", ntype="divider")
275+
q_node.value = str(q_val)
276+
out_dividers.add_child(q_node)
277+
278+
# Determine which PLL feeds this output
279+
pll_idx = 0 if i <= 5 else 1
280+
if self.PLL_used[pll_idx]:
281+
pll_node = ic_node.get_child(f"PLL{pll_idx}")
282+
pll_rate = config[f"PLL{pll_idx}"].get("rate_hz", 0)
283+
ic_node.add_connection(
284+
{"from": pll_node, "to": q_node, "rate": pll_rate}
285+
)
286+
287+
out_node = Node(f"OUT{i}", ntype="out_clock_connected")
288+
lo.add_node(out_node)
289+
lo.add_connection(
290+
{
291+
"from": q_node,
292+
"to": out_node,
293+
"rate": self.out_freqs[i],
294+
}
295+
)
296+
297+
return lo.draw()
298+
203299
def _setup_solver_constraints(
204300
self, input_refs: List[int], out_freqs: List[int]
205301
) -> None:

adijif/clocks/ltc6953.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from docplex.cp.solution import CpoSolveResult # type: ignore
66

77
from adijif.clocks.clock import clock
8+
from adijif.draw import Layout, Node
89
from adijif.solvers import CpoExpr, GK_Intermediate
910

1011

@@ -421,6 +422,74 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict:
421422

422423
return config
423424

425+
def draw(self, lo: Layout = None) -> str:
426+
"""Draw clock tree diagram for LTC6953.
427+
428+
Args:
429+
lo: Layout for drawing
430+
431+
Returns:
432+
str: SVG diagram string
433+
434+
Raises:
435+
Exception: If no solution is saved
436+
"""
437+
if not self._saved_solution:
438+
raise Exception("No solution to draw. Must call solve first.")
439+
440+
system_draw = lo is not None
441+
if not system_draw:
442+
lo = Layout("LTC6953 Example")
443+
else:
444+
assert isinstance(lo, Layout), "lo must be a Layout object"
445+
446+
ic_node = Node("LTC6953")
447+
lo.add_node(ic_node)
448+
449+
# Output dividers inside IC
450+
out_dividers_node = Node("Output Dividers", ntype="shell")
451+
ic_node.add_child(out_dividers_node)
452+
453+
if not system_draw:
454+
ref_in = Node("REF_IN", ntype="input")
455+
lo.add_node(ref_in)
456+
else:
457+
ref_name = None
458+
for key in self._saved_solution.keys():
459+
if "input_ref" in key.lower():
460+
ref_name = key
461+
break
462+
if ref_name:
463+
to_node = lo.get_node(ref_name)
464+
from_node = lo.get_connection(to=to_node.name)
465+
assert from_node, "No connection found"
466+
assert isinstance(from_node, list), "Connection must be a list"
467+
assert len(from_node) == 1, "Only one connection allowed"
468+
ref_in = from_node[0]["from"]
469+
lo.remove_node(to_node.name)
470+
else:
471+
ref_in = Node("REF_IN", ntype="input")
472+
lo.add_node(ref_in)
473+
474+
input_ref = self._saved_solution["input_ref"]
475+
lo.add_connection({"from": ref_in, "to": ic_node, "rate": input_ref})
476+
477+
# Add each output clock with its divider
478+
for i, (clk_name, clk_cfg) in enumerate(
479+
self._saved_solution["output_clocks"].items()
480+
):
481+
div_node = Node(f"D{i}", ntype="divider")
482+
div_node.value = str(int(clk_cfg["divider"]))
483+
out_dividers_node.add_child(div_node)
484+
485+
clk_node = Node(clk_name, ntype="out_clock_connected")
486+
lo.add_node(clk_node)
487+
lo.add_connection(
488+
{"from": div_node, "to": clk_node, "rate": clk_cfg["rate"]}
489+
)
490+
491+
return lo.draw()
492+
424493
def _setup_solver_constraints(self, input_ref: int) -> None:
425494
"""Apply constraints to solver model.
426495

adijif/converters/ad9081.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from ..solvers import GEKKO, CpoModel, CpoSolveResult # type: ignore
99
from .ad9081_dp import ad9081_dp_rx, ad9081_dp_tx
10+
from .ad9081_draw import ad9081_rx_draw, ad9081_tx_draw
1011
from .ad9081_util import _load_rx_config_modes, _load_tx_config_modes
1112
from .adc import adc
1213
from .converter import converter
@@ -307,7 +308,7 @@ def get_required_clocks(self) -> List:
307308
return [clk, self.config["sysref"]]
308309

309310

310-
class ad9081_rx(adc, ad9081_core):
311+
class ad9081_rx(ad9081_rx_draw, adc, ad9081_core):
311312
"""AD9081 Receive model."""
312313

313314
name = "AD9081_RX"
@@ -424,7 +425,7 @@ def _check_valid_internal_configuration(self) -> None:
424425
raise Exception("Decimation not valid")
425426

426427

427-
class ad9081_tx(dac, ad9081_core):
428+
class ad9081_tx(ad9081_tx_draw, dac, ad9081_core):
428429
"""AD9081 Transmit model."""
429430

430431
name = "AD9081_TX"

0 commit comments

Comments
 (0)