Skip to content

Commit 6a1962a

Browse files
committed
feat: add draw support for ADF4382 and fix ADF4030 drawing
1 parent 74457d8 commit 6a1962a

6 files changed

Lines changed: 347 additions & 7 deletions

File tree

adijif/plls/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""ADI JIF PLL models."""
22

3-
supported_parts = ["adf4371", "adf4382"]
3+
supported_parts = ["adf4371", "adf4382", "adf4030"]

adijif/plls/adf4030.py

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,139 @@
88
from adijif.plls.pll import pll
99
from adijif.solvers import CpoExpr, GK_Intermediate
1010

11+
from adijif.draw import Layout, Node
12+
13+
14+
class adf4030_drawer(object):
15+
"""ADF4030 diagram drawer."""
16+
17+
def _init_diagram(self) -> None:
18+
"""Initialize diagram with PLL block."""
19+
20+
self._diagram_output_dividers = []
21+
22+
self.ic_diagram_node = Node("ADF4030")
23+
24+
rdiv = Node("R", ntype="divider")
25+
self.ic_diagram_node.add_child(rdiv)
26+
27+
pfd = Node("PFD", ntype="phase-frequency-detector")
28+
self.ic_diagram_node.add_child(pfd)
29+
30+
charge_pump = Node("CP", ntype="charge-pump")
31+
self.ic_diagram_node.add_child(charge_pump)
32+
33+
loop_filter = Node("LF", ntype="loop-filter")
34+
self.ic_diagram_node.add_child(loop_filter)
35+
36+
vco = Node("VCO", ntype="vco")
37+
self.ic_diagram_node.add_child(vco)
38+
39+
ndiv = Node("N", ntype="divider")
40+
self.ic_diagram_node.add_child(ndiv)
41+
42+
output_dividers = Node("Output Dividers", ntype="shell")
43+
self.ic_diagram_node.add_child(output_dividers)
44+
45+
# Connections
46+
self.ic_diagram_node.add_connection({
47+
"from": rdiv,
48+
"to": pfd,
49+
})
50+
self.ic_diagram_node.add_connection({
51+
"from": pfd,
52+
"to": charge_pump,
53+
})
54+
self.ic_diagram_node.add_connection({
55+
"from": charge_pump,
56+
"to": loop_filter,
57+
})
58+
self.ic_diagram_node.add_connection({
59+
"from": loop_filter,
60+
"to": vco,
61+
})
62+
self.ic_diagram_node.add_connection({
63+
"from": vco,
64+
"to": ndiv,
65+
})
66+
self.ic_diagram_node.add_connection({
67+
"from": ndiv,
68+
"to": pfd,
69+
})
70+
self.ic_diagram_node.add_connection({
71+
"from": vco,
72+
"to": output_dividers,
73+
})
74+
75+
def _update_diagram(self, config: Dict) -> None:
76+
"""Update diagram with new dividers."""
77+
78+
keys = config.keys()
79+
output_dividers = self.ic_diagram_node.get_child("Output Dividers")
80+
for key in keys:
81+
if "o_div" in key and key not in self._diagram_output_dividers:
82+
od_node = Node(key, ntype="divider")
83+
output_dividers.add_child(od_node)
84+
self.ic_diagram_node.add_connection({
85+
"from": output_dividers,
86+
"to": od_node,
87+
})
88+
else:
89+
raise Exception("Unexpected config key: {}".format(key))
90+
91+
def draw(self, lo: Layout = None) -> Union[str, Layout]:
92+
"""Draw diagram with configuration.
93+
94+
Args:
95+
lo (Layout): Diagram layout object
96+
97+
Returns:
98+
Layout: Diagram layout object
99+
"""
100+
if not self._saved_solution:
101+
raise Exception("No solution to draw. Must call solve first")
102+
103+
system_draw = lo is not None
104+
if not system_draw:
105+
lo = Layout("ADF4030 Diagram")
106+
else:
107+
assert isinstance(lo, Layout), "Layout object must be provided for system drawing"
108+
lo.add_node(self.ic_diagram_node)
109+
110+
ref_in = Node("REF_IN", ntype="input")
111+
rdiv = self.ic_diagram_node.get_child("R")
112+
lo.add_connection({
113+
"from": ref_in,
114+
"to": rdiv,
115+
"rate": 100e6, # TODO: Get actual rate
116+
})
117+
118+
# Update node values
119+
node = self.ic_diagram_node.get_child("R")
120+
node.value = str(self._saved_solution["r"])
121+
node = self.ic_diagram_node.get_child("N")
122+
node.value = str(self._saved_solution["n"])
123+
# for clk in self._clk_names:
124+
# od_node = self.ic_diagram_node.get_child(f"o_div_{clk}_adf4030")
125+
# od_node.value = str(self._saved_solution[f"o_div_{clk}_adf4030"])
126+
for key, val in self._saved_solution["output_clocks"].items():
127+
div = Node(key, ntype="divider")
128+
div.value = str(val["divider"])
129+
lo.add_node(div)
130+
lo.add_connection({
131+
"from": self.ic_diagram_node.get_child("Output Dividers"),
132+
"to": div,
133+
"rate": self._saved_solution["vco"] / val["divider"],
134+
})
135+
136+
if system_draw:
137+
return lo.draw()
138+
139+
return lo.draw()
140+
141+
142+
class adf4030(pll, adf4030_drawer):
11143

12-
class adf4030(pll):
13144
"""ADF4030 PLL model.
14145
15146
This model currently supports all divider configurations
@@ -157,6 +288,8 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict:
157288

158289
config["output_clocks"] = output_config
159290

291+
self._saved_solution = config
292+
160293
return config
161294

162295
def _setup_solver_constraints(
@@ -322,3 +455,4 @@ def set_requested_clocks(
322455
self._add_equation(
323456
self.config[o_div_name] * out_freq[i] == self.config["vco"],
324457
)
458+

adijif/plls/adf4382.py

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from docplex.cp.modeler import if_then
66
from docplex.cp.solution import CpoSolveResult # type: ignore
77

8+
from adijif.draw import Layout, Node
89
from adijif.plls.pll import pll
910
from adijif.solvers import CpoExpr, GK_Intermediate, integer_var
1011

@@ -21,7 +22,127 @@ def to_int(
2122
raise TypeError(f"Unsupported type: {type(value)}")
2223

2324

24-
class adf4382(pll):
25+
class adf4382_drawer(object):
26+
"""ADF4382 diagram drawer."""
27+
28+
def _init_diagram(self) -> None:
29+
"""Initialize diagram with PLL block."""
30+
31+
self._diagram_output_dividers = []
32+
33+
self.ic_diagram_node = Node("ADF4382")
34+
35+
doubler = Node("D", ntype="doubler")
36+
self.ic_diagram_node.add_child(doubler)
37+
38+
rdiv = Node("R", ntype="divider")
39+
self.ic_diagram_node.add_child(rdiv)
40+
41+
pfd = Node("PFD", ntype="phase-frequency-detector")
42+
self.ic_diagram_node.add_child(pfd)
43+
44+
charge_pump = Node("CP", ntype="charge-pump")
45+
self.ic_diagram_node.add_child(charge_pump)
46+
47+
loop_filter = Node("LF", ntype="loop-filter")
48+
self.ic_diagram_node.add_child(loop_filter)
49+
50+
vco = Node("VCO", ntype="vco")
51+
self.ic_diagram_node.add_child(vco)
52+
53+
ndiv = Node("N", ntype="divider")
54+
self.ic_diagram_node.add_child(ndiv)
55+
56+
output_dividers = Node("Output Dividers", ntype="shell")
57+
self.ic_diagram_node.add_child(output_dividers)
58+
59+
od_node = Node("O", ntype="divider")
60+
output_dividers.add_child(od_node)
61+
62+
# Connections
63+
self.ic_diagram_node.add_connection({
64+
"from": doubler,
65+
"to": rdiv,
66+
})
67+
self.ic_diagram_node.add_connection({
68+
"from": rdiv,
69+
"to": pfd,
70+
})
71+
self.ic_diagram_node.add_connection({
72+
"from": pfd,
73+
"to": charge_pump,
74+
})
75+
self.ic_diagram_node.add_connection({
76+
"from": charge_pump,
77+
"to": loop_filter,
78+
})
79+
self.ic_diagram_node.add_connection({
80+
"from": loop_filter,
81+
"to": vco,
82+
})
83+
self.ic_diagram_node.add_connection({
84+
"from": vco,
85+
"to": ndiv,
86+
})
87+
self.ic_diagram_node.add_connection({
88+
"from": ndiv,
89+
"to": pfd,
90+
})
91+
self.ic_diagram_node.add_connection({
92+
"from": vco,
93+
"to": output_dividers,
94+
})
95+
self.ic_diagram_node.add_connection({
96+
"from": output_dividers,
97+
"to": od_node,
98+
})
99+
100+
def draw(self, lo: Layout = None) -> Union[str, Layout]:
101+
"""Draw diagram with configuration.
102+
103+
Args:
104+
lo (Layout): Diagram layout object
105+
106+
Returns:
107+
Layout: Diagram layout object
108+
"""
109+
if not self._saved_solution:
110+
raise Exception("No solution to draw. Must call solve first")
111+
112+
system_draw = lo is not None
113+
if not system_draw:
114+
lo = Layout("ADF4382 Diagram")
115+
else:
116+
assert isinstance(lo, Layout), "Layout object must be provided for system drawing"
117+
118+
lo.add_node(self.ic_diagram_node)
119+
120+
ref_in = Node("REF_IN", ntype="input")
121+
doubler = self.ic_diagram_node.get_child("D")
122+
lo.add_connection({
123+
"from": ref_in,
124+
"to": doubler,
125+
"rate": 100e6, # TODO: Get actual rate
126+
})
127+
128+
# Update node values
129+
node = self.ic_diagram_node.get_child("D")
130+
node.value = str(self._saved_solution["d"])
131+
node = self.ic_diagram_node.get_child("R")
132+
node.value = str(self._saved_solution["r"])
133+
node = self.ic_diagram_node.get_child("N")
134+
node.value = str(self._saved_solution["n"])
135+
136+
output_dividers = self.ic_diagram_node.get_child("Output Dividers")
137+
od_node = output_dividers.get_child("O")
138+
od_node.value = str(self._saved_solution["o"])
139+
140+
if system_draw:
141+
return lo.draw()
142+
143+
return lo.draw()
144+
145+
class adf4382(pll, adf4382_drawer):
25146
"""ADF4382 PLL model.
26147
27148
This model does not support fractional mode
@@ -316,6 +437,8 @@ def get_config(self, solution: CpoSolveResult = None) -> Dict:
316437
vco = self.solution.get_kpis()["vco"]
317438
config["rf_out_frequency"] = vco / config["o"]
318439

440+
self._saved_solution = config
441+
319442
return config
320443

321444
def _setup_solver_constraints(

adijif/system_draw.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,42 @@ def draw(self, config: Dict) -> str:
3636

3737
self.clock.draw(lo)
3838

39+
# External PLLs (SYSREF and/or Direct)
40+
if self.plls is not None:
41+
if not isinstance(self.plls, list):
42+
plls = [self.plls]
43+
else:
44+
plls = self.plls
45+
46+
for pll in plls:
47+
pll.draw(lo)
48+
49+
if hasattr(self, "plls_sysref") and self.plls_sysref is not None:
50+
if not isinstance(self.plls_sysref, list):
51+
plls_sysref = [self.plls_sysref]
52+
else:
53+
plls_sysref = self.plls_sysref
54+
55+
for pll in plls_sysref:
56+
pll.draw(lo)
57+
3958
# Converter
4059
assert self.converter is not None
4160
assert not isinstance(self.converter, list), (
4261
"Only one converter supported"
4362
)
4463

4564
cnv_clocking = config["clock"]["output_clocks"].copy()
65+
if "plls" in config:
66+
for pll_cfg in config["plls"]:
67+
if "output_clocks" in pll_cfg:
68+
cnv_clocking.update(pll_cfg["output_clocks"])
69+
70+
# Also grab output_clocks from any ext plls in config directly
71+
for key in config:
72+
if "pll" in key and isinstance(config[key], dict) and "output_clocks" in config[key]:
73+
cnv_clocking.update(config[key]["output_clocks"])
74+
4675
for clk in cnv_clocking:
4776
rate = cnv_clocking[clk]["rate"]
4877
cnv_clocking[clk] = rate

0 commit comments

Comments
 (0)