pyadi-jif 0.1.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- adijif/__init__.py +32 -0
- adijif/adijif.py +1 -0
- adijif/cli.py +21 -0
- adijif/clocks/__init__.py +10 -0
- adijif/clocks/ad9523.py +321 -0
- adijif/clocks/ad9523_1_bf.py +91 -0
- adijif/clocks/ad9528.py +444 -0
- adijif/clocks/ad9528_bf.py +70 -0
- adijif/clocks/ad9545.py +553 -0
- adijif/clocks/clock.py +153 -0
- adijif/clocks/hmc7044.py +558 -0
- adijif/clocks/hmc7044_bf.py +68 -0
- adijif/clocks/ltc6952.py +624 -0
- adijif/clocks/ltc6952_bf.py +67 -0
- adijif/clocks/ltc6953.py +509 -0
- adijif/common.py +70 -0
- adijif/converters/__init__.py +3 -0
- adijif/converters/ad9081.py +679 -0
- adijif/converters/ad9081_dp.py +206 -0
- adijif/converters/ad9081_util.py +124 -0
- adijif/converters/ad9084.py +588 -0
- adijif/converters/ad9084_dp.py +111 -0
- adijif/converters/ad9084_draw.py +203 -0
- adijif/converters/ad9084_util.py +365 -0
- adijif/converters/ad9144.py +316 -0
- adijif/converters/ad9144_bf.py +44 -0
- adijif/converters/ad9680.py +201 -0
- adijif/converters/ad9680_bf.py +43 -0
- adijif/converters/ad9680_draw.py +184 -0
- adijif/converters/adc.py +83 -0
- adijif/converters/adrv9009.py +426 -0
- adijif/converters/adrv9009_bf.py +43 -0
- adijif/converters/adrv9009_util.py +89 -0
- adijif/converters/converter.py +399 -0
- adijif/converters/dac.py +85 -0
- adijif/converters/resources/AD9084_JTX_JRX.xlsx +0 -0
- adijif/converters/resources/ad9081_JRx_204B.csv +180 -0
- adijif/converters/resources/ad9081_JRx_204C.csv +411 -0
- adijif/converters/resources/ad9081_JTx_204B.csv +1488 -0
- adijif/converters/resources/ad9081_JTx_204C.csv +1064 -0
- adijif/converters/resources/full_rx_mode_table_ad9081.csv +1904 -0
- adijif/converters/resources/full_tx_mode_table_ad9081.csv +994 -0
- adijif/d2/__init__.py +26 -0
- adijif/d2/d2lib.h +81 -0
- adijif/draw.py +498 -0
- adijif/fpgas/__init__.py +1 -0
- adijif/fpgas/fpga.py +64 -0
- adijif/fpgas/xilinx/__init__.py +1143 -0
- adijif/fpgas/xilinx/bf.py +101 -0
- adijif/fpgas/xilinx/pll.py +232 -0
- adijif/fpgas/xilinx/sevenseries.py +531 -0
- adijif/fpgas/xilinx/ultrascaleplus.py +485 -0
- adijif/fpgas/xilinx/xilinx_draw.py +516 -0
- adijif/gekko_trans.py +295 -0
- adijif/jesd.py +760 -0
- adijif/plls/__init__.py +3 -0
- adijif/plls/adf4030.py +259 -0
- adijif/plls/adf4371.py +419 -0
- adijif/plls/adf4382.py +581 -0
- adijif/plls/pll.py +103 -0
- adijif/solvers.py +54 -0
- adijif/sys/__init__.py +1 -0
- adijif/sys/s_plls.py +185 -0
- adijif/system.py +567 -0
- adijif/system_draw.py +65 -0
- adijif/types.py +151 -0
- adijif/utils.py +191 -0
- pyadi_jif-0.1.0.dist-info/METADATA +62 -0
- pyadi_jif-0.1.0.dist-info/RECORD +73 -0
- pyadi_jif-0.1.0.dist-info/WHEEL +6 -0
- pyadi_jif-0.1.0.dist-info/licenses/AUTHORS.rst +13 -0
- pyadi_jif-0.1.0.dist-info/licenses/LICENSE +277 -0
- pyadi_jif-0.1.0.dist-info/top_level.txt +1 -0
adijif/clocks/clock.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Clock parent metaclass to maintain consistency for all clock chip."""
|
|
2
|
+
|
|
3
|
+
from abc import ABCMeta, abstractmethod
|
|
4
|
+
from typing import Dict, List, Union
|
|
5
|
+
|
|
6
|
+
from docplex.cp.solution import CpoSolveResult # type: ignore
|
|
7
|
+
|
|
8
|
+
from adijif.common import core
|
|
9
|
+
from adijif.draw import Layout, Node
|
|
10
|
+
from adijif.gekko_trans import gekko_translation
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class clock(core, gekko_translation, metaclass=ABCMeta):
|
|
14
|
+
"""Parent metaclass for all clock chip classes."""
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def find_dividers(self) -> Dict:
|
|
19
|
+
"""Find all possible divider settings that validate config.
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
NotImplementedError: Method not implemented
|
|
23
|
+
"""
|
|
24
|
+
raise NotImplementedError # pragma: no cover
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def list_available_references(self) -> List[int]:
|
|
29
|
+
"""Determine all references that can be generated.
|
|
30
|
+
|
|
31
|
+
Based on config list possible references that can be generated
|
|
32
|
+
based on VCO and output dividers
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
NotImplementedError: Method not implemented
|
|
36
|
+
"""
|
|
37
|
+
raise NotImplementedError # pragma: no cover
|
|
38
|
+
|
|
39
|
+
def _solve_gekko(self) -> bool:
|
|
40
|
+
"""Local solve method for clock model.
|
|
41
|
+
|
|
42
|
+
Call model solver with correct arguments.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
bool: Always False
|
|
46
|
+
"""
|
|
47
|
+
self.model.options.SOLVER = 1 # APOPT solver
|
|
48
|
+
self.model.solver_options = [
|
|
49
|
+
"minlp_maximum_iterations 1000", # minlp iterations with integer solution
|
|
50
|
+
"minlp_max_iter_with_int_sol 100", # treat minlp as nlp
|
|
51
|
+
"minlp_as_nlp 0", # nlp sub-problem max iterations
|
|
52
|
+
"nlp_maximum_iterations 500", # 1 = depth first, 2 = breadth first
|
|
53
|
+
"minlp_branch_method 1", # maximum deviation from whole number
|
|
54
|
+
"minlp_integer_tol 0", # covergence tolerance (MUST BE 0 TFC)
|
|
55
|
+
"minlp_gap_tol 0.1",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
self.model.solve(disp=False)
|
|
59
|
+
self.model.cleanup()
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
# def _add_objective(self, sysrefs: List) -> None:
|
|
63
|
+
# pass
|
|
64
|
+
|
|
65
|
+
def _solve_cplex(self) -> CpoSolveResult:
|
|
66
|
+
self.solution = self.model.solve(LogVerbosity="Quiet")
|
|
67
|
+
if self.solution.solve_status not in ["Feasible", "Optimal"]:
|
|
68
|
+
raise Exception("Solution Not Found")
|
|
69
|
+
return self.solution
|
|
70
|
+
|
|
71
|
+
def solve(self) -> Union[None, CpoSolveResult]:
|
|
72
|
+
"""Local solve method for clock model.
|
|
73
|
+
|
|
74
|
+
Call model solver with correct arguments.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
[None,CpoSolveResult]: When cplex solver is used CpoSolveResult is returned
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
Exception: If solver is not valid
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
if self.solver == "gekko":
|
|
84
|
+
return self._solve_gekko()
|
|
85
|
+
elif self.solver == "CPLEX":
|
|
86
|
+
return self._solve_cplex()
|
|
87
|
+
else:
|
|
88
|
+
raise Exception(f"Unknown solver {self.solver}")
|
|
89
|
+
|
|
90
|
+
def draw(self, lo: Layout = None) -> str:
|
|
91
|
+
"""Generic Draw converter model.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
lo (Layout): Layout object to draw on
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
str: Path to image file
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
Exception: If no solution is saved
|
|
101
|
+
"""
|
|
102
|
+
if not self._saved_solution:
|
|
103
|
+
raise Exception("No solution to draw. Must call solve first.")
|
|
104
|
+
clocks = self._saved_solution
|
|
105
|
+
system_draw = lo is not None
|
|
106
|
+
name = self.name.lower()
|
|
107
|
+
|
|
108
|
+
if not system_draw:
|
|
109
|
+
lo = Layout(f"{name} Example")
|
|
110
|
+
else:
|
|
111
|
+
assert isinstance(lo, Layout), "lo must be a Layout object"
|
|
112
|
+
|
|
113
|
+
ic_node = Node(self.name)
|
|
114
|
+
lo.add_node(ic_node)
|
|
115
|
+
|
|
116
|
+
# rate = clocks[f"{name}_ref_clk"]
|
|
117
|
+
# Find key with ending
|
|
118
|
+
ref_name = None
|
|
119
|
+
for key in clocks.keys():
|
|
120
|
+
if "vcxo" in key.lower():
|
|
121
|
+
ref_name = key
|
|
122
|
+
break
|
|
123
|
+
if ref_name is None:
|
|
124
|
+
raise Exception(f"No clock found for vcxo\n.Options: {clocks.keys()}")
|
|
125
|
+
|
|
126
|
+
if not system_draw:
|
|
127
|
+
ref_in = Node("REF_IN", ntype="input")
|
|
128
|
+
lo.add_node(ref_in)
|
|
129
|
+
else:
|
|
130
|
+
to_node = lo.get_node(ref_name)
|
|
131
|
+
from_node = lo.get_connection(to=to_node.name)
|
|
132
|
+
assert from_node, "No connection found"
|
|
133
|
+
assert isinstance(from_node, list), "Connection must be a list"
|
|
134
|
+
assert len(from_node) == 1, "Only one connection allowed"
|
|
135
|
+
ref_in = from_node[0]["from"]
|
|
136
|
+
# Remove to_node since it is not needed
|
|
137
|
+
lo.remove_node(to_node.name)
|
|
138
|
+
|
|
139
|
+
rate = clocks[ref_name]
|
|
140
|
+
|
|
141
|
+
lo.add_connection({"from": ref_in, "to": ic_node, "rate": rate})
|
|
142
|
+
|
|
143
|
+
# Add each output clock
|
|
144
|
+
for o_clk_name in clocks["output_clocks"]:
|
|
145
|
+
rate = clocks["output_clocks"][o_clk_name]["rate"]
|
|
146
|
+
# div = clocks['output_clocks'][o_clk_name]['divider']
|
|
147
|
+
if not system_draw:
|
|
148
|
+
out_node = Node(o_clk_name, ntype="out_clock_connected")
|
|
149
|
+
lo.add_node(out_node)
|
|
150
|
+
lo.add_connection({"from": ic_node, "to": out_node, "rate": rate})
|
|
151
|
+
|
|
152
|
+
if not system_draw:
|
|
153
|
+
return lo.draw()
|
adijif/clocks/hmc7044.py
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
"""HMC7044 clock chip model."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Union
|
|
4
|
+
|
|
5
|
+
from adijif.clocks.hmc7044_bf import hmc7044_bf
|
|
6
|
+
|
|
7
|
+
from adijif.solvers import CpoExpr, CpoModel # type: ignore # isort: skip # noqa: I202
|
|
8
|
+
from adijif.solvers import CpoSolveResult # type: ignore # isort: skip # noqa: I202
|
|
9
|
+
from adijif.solvers import GEKKO # type: ignore # isort: skip # noqa: I202
|
|
10
|
+
from adijif.solvers import GK_Intermediate # type: ignore # isort: skip # noqa: I202
|
|
11
|
+
|
|
12
|
+
from adijif.draw import Layout, Node # type: ignore # isort: skip # noqa: I202
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class hmc7044(hmc7044_bf):
|
|
16
|
+
"""HMC7044 clock chip model.
|
|
17
|
+
|
|
18
|
+
This model currently supports VCXO+PLL2 configurations
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
name = "HMC7044"
|
|
22
|
+
|
|
23
|
+
# Ranges
|
|
24
|
+
# r2_divider_min = 1
|
|
25
|
+
# r2_divider_max = 4095
|
|
26
|
+
r2_available = [*range(1, 4095 + 1)]
|
|
27
|
+
|
|
28
|
+
""" Output dividers """
|
|
29
|
+
d_available = [1, 3, 5, *range(2, 4095, 2)]
|
|
30
|
+
# When pulse generation is required (like for sysref) divder range
|
|
31
|
+
# is limited
|
|
32
|
+
d_syspulse_available = [*range(32, 4095, 2)]
|
|
33
|
+
|
|
34
|
+
# Defaults
|
|
35
|
+
_d: Union[int, List[int]] = [1, 3, 5, *range(2, 4095, 2)]
|
|
36
|
+
_r2: Union[int, List[int]] = [*range(1, 4095 + 1)]
|
|
37
|
+
|
|
38
|
+
# Limits
|
|
39
|
+
""" Internal limits """
|
|
40
|
+
vco_min = 2400e6
|
|
41
|
+
vco_max = 3200e6
|
|
42
|
+
pfd_max = 250e6
|
|
43
|
+
vcxo_min = 10e6
|
|
44
|
+
vcxo_max = 500e6
|
|
45
|
+
|
|
46
|
+
use_vcxo_double = True
|
|
47
|
+
vxco_doubler_available = [1, 2]
|
|
48
|
+
_vcxo_doubler = [1, 2]
|
|
49
|
+
|
|
50
|
+
minimize_feedback_dividers = True
|
|
51
|
+
|
|
52
|
+
# State management
|
|
53
|
+
_clk_names: List[str] = []
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self, model: Union[GEKKO, CpoModel] = None, solver: str = "CPLEX"
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Initialize HMC7044 clock chip model.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
model (Model): Model to add constraints to
|
|
62
|
+
solver (str): Solver to use. Should be one of "CPLEX" or "gekko"
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
Exception: Invalid solver
|
|
66
|
+
"""
|
|
67
|
+
super(hmc7044, self).__init__(model, solver)
|
|
68
|
+
if solver == "gekko":
|
|
69
|
+
self.n2_available = [*range(8, 65535 + 1)]
|
|
70
|
+
self._n2 = [*range(8, 65535 + 1)]
|
|
71
|
+
elif solver == "CPLEX":
|
|
72
|
+
self.n2_available = [*range(8, 65535 + 1)]
|
|
73
|
+
self._n2 = [*range(8, 65535 + 1)]
|
|
74
|
+
else:
|
|
75
|
+
raise Exception("Unknown solver {}".format(solver))
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def d(self) -> Union[int, List[int]]:
|
|
79
|
+
"""Output dividers.
|
|
80
|
+
|
|
81
|
+
Valid dividers are 1,2,3,4,5,6->(even)->4094
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
int: Current allowable dividers
|
|
85
|
+
"""
|
|
86
|
+
return self._d
|
|
87
|
+
|
|
88
|
+
@d.setter
|
|
89
|
+
def d(self, value: Union[int, List[int]]) -> None:
|
|
90
|
+
"""Output dividers.
|
|
91
|
+
|
|
92
|
+
Valid dividers are 1,2,3,4,5,6->(even)->4094
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
value (int, list[int]): Allowable values for divider
|
|
96
|
+
"""
|
|
97
|
+
self._check_in_range(value, self.d_available, "d")
|
|
98
|
+
self._d = value
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def n2(self) -> Union[int, List[int]]:
|
|
102
|
+
"""n2: VCO feedback divider.
|
|
103
|
+
|
|
104
|
+
Valid dividers are 8->65536
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
int: Current allowable dividers
|
|
108
|
+
"""
|
|
109
|
+
return self._n2
|
|
110
|
+
|
|
111
|
+
@n2.setter
|
|
112
|
+
def n2(self, value: Union[int, List[int]]) -> None:
|
|
113
|
+
"""VCO feedback divider.
|
|
114
|
+
|
|
115
|
+
Valid dividers are 8->65536
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
value (int, list[int]): Allowable values for divider
|
|
119
|
+
"""
|
|
120
|
+
self._check_in_range(value, self.n2_available, "n2")
|
|
121
|
+
self._n2 = value
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def r2(self) -> Union[int, List[int]]:
|
|
125
|
+
"""VCXO input dividers.
|
|
126
|
+
|
|
127
|
+
Valid dividers are 1->4096
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
int: Current allowable dividers
|
|
131
|
+
"""
|
|
132
|
+
return self._r2
|
|
133
|
+
|
|
134
|
+
@r2.setter
|
|
135
|
+
def r2(self, value: Union[int, List[int]]) -> None:
|
|
136
|
+
"""VCXO input dividers.
|
|
137
|
+
|
|
138
|
+
Valid dividers are 1->4096
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
value (int, list[int]): Allowable values for divider
|
|
142
|
+
"""
|
|
143
|
+
self._check_in_range(value, self.r2_available, "r2")
|
|
144
|
+
self._r2 = value
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def vxco_doubler(self) -> Union[int, List[int]]:
|
|
148
|
+
"""VCXO doubler.
|
|
149
|
+
|
|
150
|
+
Valid dividers are 1,2
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
int: Current doubler value
|
|
154
|
+
"""
|
|
155
|
+
return self._vcxo_doubler
|
|
156
|
+
|
|
157
|
+
@vxco_doubler.setter
|
|
158
|
+
def vxco_doubler(self, value: Union[int, List[int]]) -> None:
|
|
159
|
+
"""VCXO doubler.
|
|
160
|
+
|
|
161
|
+
Valid dividers are 1,2
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
value (int, list[int]): Allowable values for divider
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
self._check_in_range(value, self.vxco_doubler_available, "vxco_doubler")
|
|
168
|
+
self._vcxo_doubler = value
|
|
169
|
+
|
|
170
|
+
def _init_diagram(self) -> None:
|
|
171
|
+
"""Initialize diagram for HMC7044 alone."""
|
|
172
|
+
self.ic_diagram_node = None
|
|
173
|
+
self._diagram_output_dividers = []
|
|
174
|
+
|
|
175
|
+
# lo = Layout("HMC7044 Example")
|
|
176
|
+
|
|
177
|
+
self.ic_diagram_node = Node("HMC7044")
|
|
178
|
+
# lo.add_node(root)
|
|
179
|
+
|
|
180
|
+
# External
|
|
181
|
+
# ref_in = Node("REF_IN", ntype="input")
|
|
182
|
+
# lo.add_node(ref_in)
|
|
183
|
+
|
|
184
|
+
vcxo_doubler = Node("VCXO Doubler", ntype="shell")
|
|
185
|
+
self.ic_diagram_node.add_child(vcxo_doubler)
|
|
186
|
+
|
|
187
|
+
# Inside the IC
|
|
188
|
+
r2_div = Node("R2", ntype="divider")
|
|
189
|
+
# r2_div.value = "2"
|
|
190
|
+
self.ic_diagram_node.add_child(r2_div)
|
|
191
|
+
pfd = Node("PFD", ntype="phase-frequency-detector")
|
|
192
|
+
self.ic_diagram_node.add_child(pfd)
|
|
193
|
+
lf = Node("LF", ntype="loop-filter")
|
|
194
|
+
self.ic_diagram_node.add_child(lf)
|
|
195
|
+
vco = Node("VCO", ntype="voltage-controlled-oscillator")
|
|
196
|
+
vco.shape = "circle"
|
|
197
|
+
self.ic_diagram_node.add_child(vco)
|
|
198
|
+
n2 = Node("N2", ntype="divider")
|
|
199
|
+
self.ic_diagram_node.add_child(n2)
|
|
200
|
+
|
|
201
|
+
out_dividers = Node("Output Dividers", ntype="shell")
|
|
202
|
+
# ds = 4
|
|
203
|
+
# out_divs = []
|
|
204
|
+
# for i in range(ds):
|
|
205
|
+
# div = Node(f"D{i+1}", ntype="divider")
|
|
206
|
+
# out_dividers.add_child(div)
|
|
207
|
+
# out_divs.append(div)
|
|
208
|
+
|
|
209
|
+
self.ic_diagram_node.add_child(out_dividers)
|
|
210
|
+
|
|
211
|
+
# Connections inside the IC
|
|
212
|
+
# lo.add_connection({"from": ref_in, "to": r2_div, 'rate': 125000000})
|
|
213
|
+
self.ic_diagram_node.add_connection({"from": vcxo_doubler, "to": r2_div})
|
|
214
|
+
self.ic_diagram_node.add_connection(
|
|
215
|
+
{"from": r2_div, "to": pfd, "rate": 125000000 / 2}
|
|
216
|
+
)
|
|
217
|
+
self.ic_diagram_node.add_connection({"from": pfd, "to": lf})
|
|
218
|
+
self.ic_diagram_node.add_connection({"from": lf, "to": vco})
|
|
219
|
+
self.ic_diagram_node.add_connection({"from": vco, "to": n2})
|
|
220
|
+
self.ic_diagram_node.add_connection({"from": n2, "to": pfd})
|
|
221
|
+
|
|
222
|
+
self.ic_diagram_node.add_connection(
|
|
223
|
+
{"from": vco, "to": out_dividers, "rate": 4000000000}
|
|
224
|
+
)
|
|
225
|
+
# for div in out_divs:
|
|
226
|
+
# self.ic_diagram_node.add_connection({"from": out_dividers, "to": div})
|
|
227
|
+
# # root.add_connection({"from": vco, "to": div})
|
|
228
|
+
|
|
229
|
+
def _update_diagram(self, config: Dict) -> None:
|
|
230
|
+
"""Update diagram with configuration.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
config (Dict): Configuration dictionary
|
|
234
|
+
|
|
235
|
+
Raises:
|
|
236
|
+
Exception: If key is not D followed by a number
|
|
237
|
+
"""
|
|
238
|
+
# Add output dividers
|
|
239
|
+
keys = config.keys()
|
|
240
|
+
output_dividers = self.ic_diagram_node.get_child("Output Dividers")
|
|
241
|
+
for key in keys:
|
|
242
|
+
if key.startswith("D"):
|
|
243
|
+
div = Node(key, ntype="divider")
|
|
244
|
+
output_dividers.add_child(div)
|
|
245
|
+
self.ic_diagram_node.add_connection(
|
|
246
|
+
{"from": output_dividers, "to": div}
|
|
247
|
+
)
|
|
248
|
+
else:
|
|
249
|
+
raise Exception(
|
|
250
|
+
f"Unknown key {key}. Must be of for DX where X is a number"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def draw(self, lo: Layout = None) -> str:
|
|
254
|
+
"""Draw diagram in d2 language for IC alone with reference clock.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
lo: Layout for drawing
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
str: Diagram in d2 language
|
|
261
|
+
|
|
262
|
+
Raises:
|
|
263
|
+
Exception: If no solution is saved
|
|
264
|
+
"""
|
|
265
|
+
if not self._saved_solution:
|
|
266
|
+
raise Exception("No solution to draw. Must call solve first.")
|
|
267
|
+
|
|
268
|
+
system_draw = lo is not None
|
|
269
|
+
if not system_draw:
|
|
270
|
+
lo = Layout("HMC7044 Example")
|
|
271
|
+
else:
|
|
272
|
+
# Verify lo is a Layout object
|
|
273
|
+
assert isinstance(lo, Layout), "lo must be a Layout object"
|
|
274
|
+
lo.add_node(self.ic_diagram_node)
|
|
275
|
+
|
|
276
|
+
ref_in = Node("REF_IN", ntype="input")
|
|
277
|
+
lo.add_node(ref_in)
|
|
278
|
+
vcxo_double = self.ic_diagram_node.get_child("VCXO Doubler")
|
|
279
|
+
lo.add_connection(
|
|
280
|
+
{"from": ref_in, "to": vcxo_double, "rate": self._saved_solution["vcxo"]}
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Update Node values
|
|
284
|
+
node = self.ic_diagram_node.get_child("VCXO Doubler")
|
|
285
|
+
node.value = str(self._saved_solution["vcxo_doubler"])
|
|
286
|
+
node = self.ic_diagram_node.get_child("R2")
|
|
287
|
+
node.value = str(self._saved_solution["r2"])
|
|
288
|
+
node = self.ic_diagram_node.get_child("N2")
|
|
289
|
+
node.value = str(self._saved_solution["n2"])
|
|
290
|
+
|
|
291
|
+
# Update VCXO Doubler to R2
|
|
292
|
+
# con = self.ic_diagram_node.get_connection("VCXO Doubler", "R2")
|
|
293
|
+
rate = self._saved_solution["vcxo_doubler"] * self._saved_solution["vcxo"]
|
|
294
|
+
self.ic_diagram_node.update_connection("VCXO Doubler", "R2", rate)
|
|
295
|
+
|
|
296
|
+
# Update R2 to PFD
|
|
297
|
+
# con = self.ic_diagram_node.get_connection("R2", "PFD")
|
|
298
|
+
rate = (
|
|
299
|
+
self._saved_solution["vcxo"]
|
|
300
|
+
* self._saved_solution["vcxo_doubler"]
|
|
301
|
+
/ self._saved_solution["r2"]
|
|
302
|
+
)
|
|
303
|
+
self.ic_diagram_node.update_connection("R2", "PFD", rate)
|
|
304
|
+
|
|
305
|
+
# Update VCO
|
|
306
|
+
# con = self.ic_diagram_node.get_connection("VCO", "Output Dividers")
|
|
307
|
+
self.ic_diagram_node.update_connection(
|
|
308
|
+
"VCO", "Output Dividers", self._saved_solution["vco"]
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Update diagram with dividers and rates
|
|
312
|
+
d = 0
|
|
313
|
+
output_dividers = self.ic_diagram_node.get_child("Output Dividers")
|
|
314
|
+
|
|
315
|
+
for key, val in self._saved_solution["output_clocks"].items():
|
|
316
|
+
clk_node = Node(key, ntype="divider")
|
|
317
|
+
div_value = val["divider"]
|
|
318
|
+
div = output_dividers.get_child(f"D{d}")
|
|
319
|
+
div.value = str(div_value)
|
|
320
|
+
d += 1
|
|
321
|
+
lo.add_node(clk_node)
|
|
322
|
+
lo.add_connection({"from": div, "to": clk_node, "rate": val["rate"]})
|
|
323
|
+
|
|
324
|
+
if system_draw:
|
|
325
|
+
return lo.draw()
|
|
326
|
+
|
|
327
|
+
return lo.draw()
|
|
328
|
+
|
|
329
|
+
def get_config(self, solution: CpoSolveResult = None) -> Dict:
|
|
330
|
+
"""Extract configurations from solver results.
|
|
331
|
+
|
|
332
|
+
Collect internal clock chip configuration and output clock definitions
|
|
333
|
+
leading to connected devices (converters, FPGAs)
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Dict: Dictionary of clocking rates and dividers for configuration
|
|
340
|
+
|
|
341
|
+
Raises:
|
|
342
|
+
Exception: If solver is not called first
|
|
343
|
+
"""
|
|
344
|
+
if not self._clk_names:
|
|
345
|
+
raise Exception("set_requested_clocks must be called before get_config")
|
|
346
|
+
|
|
347
|
+
if solution:
|
|
348
|
+
self.solution = solution
|
|
349
|
+
|
|
350
|
+
out_dividers = [self._get_val(x) for x in self.config["out_dividers"]]
|
|
351
|
+
|
|
352
|
+
config: Dict = {
|
|
353
|
+
"r2": self._get_val(self.config["r2"]),
|
|
354
|
+
"n2": self._get_val(self.config["n2"]),
|
|
355
|
+
"out_dividers": out_dividers,
|
|
356
|
+
"output_clocks": [],
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if self.vcxo_i:
|
|
360
|
+
vcxo = self._get_val(self.vcxo_i["range"])
|
|
361
|
+
self.vcxo = vcxo
|
|
362
|
+
|
|
363
|
+
clk = self.vcxo / config["r2"] * config["n2"]
|
|
364
|
+
|
|
365
|
+
output_cfg = {}
|
|
366
|
+
vd = self._get_val(self.config["vcxo_doubler"])
|
|
367
|
+
for i, div in enumerate(out_dividers):
|
|
368
|
+
rate = vd * clk / div
|
|
369
|
+
output_cfg[self._clk_names[i]] = {"rate": rate, "divider": div}
|
|
370
|
+
|
|
371
|
+
config["output_clocks"] = output_cfg
|
|
372
|
+
config["vco"] = clk * vd
|
|
373
|
+
config["vcxo"] = self.vcxo
|
|
374
|
+
config["vcxo_doubler"] = vd
|
|
375
|
+
|
|
376
|
+
self._saved_solution = config
|
|
377
|
+
|
|
378
|
+
return config
|
|
379
|
+
|
|
380
|
+
def _setup_solver_constraints(self, vcxo: int) -> None:
|
|
381
|
+
"""Apply constraints to solver model.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
vcxo (int): VCXO frequency in hertz
|
|
385
|
+
|
|
386
|
+
Raises:
|
|
387
|
+
Exception: Invalid solver
|
|
388
|
+
"""
|
|
389
|
+
self.vcxo = vcxo
|
|
390
|
+
|
|
391
|
+
if self.solver == "gekko":
|
|
392
|
+
self.config = {"r2": self.model.Var(integer=True, lb=1, ub=4095, value=1)}
|
|
393
|
+
self.config["n2"] = self.model.Var(integer=True, lb=8, ub=4095)
|
|
394
|
+
if isinstance(vcxo, (int, float)):
|
|
395
|
+
vcxo_var = self.model.Const(int(vcxo))
|
|
396
|
+
else:
|
|
397
|
+
vcxo_var = vcxo
|
|
398
|
+
self.config["vcxo_doubler"] = self.model.sos1([1, 2])
|
|
399
|
+
self.config["vcxod"] = self.model.Intermediate(
|
|
400
|
+
self.config["vcxo_doubler"] * vcxo_var
|
|
401
|
+
)
|
|
402
|
+
elif self.solver == "CPLEX":
|
|
403
|
+
self.config = {
|
|
404
|
+
"r2": self._convert_input(self._r2, "r2"),
|
|
405
|
+
"n2": self._convert_input(self._n2, "n2"),
|
|
406
|
+
}
|
|
407
|
+
self.config["vcxo_doubler"] = self._convert_input(
|
|
408
|
+
self._vcxo_doubler, "vcxo_doubler"
|
|
409
|
+
)
|
|
410
|
+
self.config["vcxod"] = self._add_intermediate(
|
|
411
|
+
self.config["vcxo_doubler"] * vcxo
|
|
412
|
+
)
|
|
413
|
+
else:
|
|
414
|
+
raise Exception("Unknown solver {}".format(self.solver))
|
|
415
|
+
|
|
416
|
+
# PLL2 equations
|
|
417
|
+
self._add_equation(
|
|
418
|
+
[
|
|
419
|
+
self.config["vcxod"] <= self.pfd_max * self.config["r2"],
|
|
420
|
+
self.config["vcxod"] * self.config["n2"]
|
|
421
|
+
<= self.vco_max * self.config["r2"],
|
|
422
|
+
self.config["vcxod"] * self.config["n2"]
|
|
423
|
+
>= self.vco_min * self.config["r2"],
|
|
424
|
+
]
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
# Objectives
|
|
428
|
+
if self.minimize_feedback_dividers:
|
|
429
|
+
if self.solver == "CPLEX":
|
|
430
|
+
self._add_objective(self.config["r2"])
|
|
431
|
+
# self.model.minimize(self.config["r2"])
|
|
432
|
+
elif self.solver == "gekko":
|
|
433
|
+
self.model.Obj(self.config["r2"])
|
|
434
|
+
else:
|
|
435
|
+
raise Exception("Unknown solver {}".format(self.solver))
|
|
436
|
+
|
|
437
|
+
def _setup(self, vcxo: int) -> None:
|
|
438
|
+
# Setup clock chip internal constraints
|
|
439
|
+
|
|
440
|
+
# FIXME: ADD SPLIT m1 configuration support
|
|
441
|
+
|
|
442
|
+
# Convert VCXO into intermediate in case we have range type
|
|
443
|
+
if type(vcxo) not in [int, float]:
|
|
444
|
+
self.vcxo_i = vcxo(self.model)
|
|
445
|
+
vcxo = self.vcxo_i["range"]
|
|
446
|
+
else:
|
|
447
|
+
self.vcxo_i = False
|
|
448
|
+
|
|
449
|
+
self._setup_solver_constraints(vcxo)
|
|
450
|
+
|
|
451
|
+
# Add requested clocks to output constraints
|
|
452
|
+
self.config["out_dividers"] = []
|
|
453
|
+
self._clk_names = [] # Reset
|
|
454
|
+
|
|
455
|
+
def _get_clock_constraint(
|
|
456
|
+
self, clk_name: List[str]
|
|
457
|
+
) -> Union[int, float, CpoExpr, GK_Intermediate]:
|
|
458
|
+
"""Get abstract clock output.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
clk_name (str): String of clock name
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
(int or float or CpoExpr or GK_Intermediate): Abstract
|
|
465
|
+
or concrete clock reference
|
|
466
|
+
|
|
467
|
+
Raises:
|
|
468
|
+
Exception: Invalid solver
|
|
469
|
+
"""
|
|
470
|
+
if self.solver == "gekko":
|
|
471
|
+
|
|
472
|
+
__d = self._d if isinstance(self._d, list) else [self._d]
|
|
473
|
+
|
|
474
|
+
if __d.sort() != self.d_available.sort():
|
|
475
|
+
raise Exception("For solver gekko d is not configurable for HMC7044")
|
|
476
|
+
|
|
477
|
+
even = self.model.Var(integer=True, lb=3, ub=2047)
|
|
478
|
+
odd = self.model.Intermediate(even * 2)
|
|
479
|
+
od = self.model.sos1([1, 2, 3, 4, 5, odd])
|
|
480
|
+
|
|
481
|
+
elif self.solver == "CPLEX":
|
|
482
|
+
od = self._convert_input(self._d, "d_" + str(clk_name))
|
|
483
|
+
else:
|
|
484
|
+
raise Exception("Unknown solver {}".format(self.solver))
|
|
485
|
+
|
|
486
|
+
# Update diagram to include new divider
|
|
487
|
+
d_n = len(self.config["out_dividers"])
|
|
488
|
+
self._update_diagram({f"D{d_n}": od})
|
|
489
|
+
|
|
490
|
+
self._clk_names.append(clk_name)
|
|
491
|
+
|
|
492
|
+
self.config["out_dividers"].append(od)
|
|
493
|
+
return self.config["vcxod"] / self.config["r2"] * self.config["n2"] / od
|
|
494
|
+
|
|
495
|
+
def set_requested_clocks(
|
|
496
|
+
self, vcxo: int, out_freqs: List, clk_names: List[str]
|
|
497
|
+
) -> None:
|
|
498
|
+
"""Define necessary clocks to be generated in model.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
vcxo (int): VCXO frequency in hertz
|
|
502
|
+
out_freqs (List): list of required clocks to be output
|
|
503
|
+
clk_names (List[str]): list of strings of clock names
|
|
504
|
+
|
|
505
|
+
Raises:
|
|
506
|
+
Exception: If len(out_freqs) != len(clk_names)
|
|
507
|
+
"""
|
|
508
|
+
if len(clk_names) != len(out_freqs):
|
|
509
|
+
raise Exception("clk_names is not the same size as out_freqs")
|
|
510
|
+
|
|
511
|
+
# Setup clock chip internal constraints
|
|
512
|
+
self._setup(vcxo)
|
|
513
|
+
self._clk_names = clk_names
|
|
514
|
+
# if type(self.vcxo) not in [int,float]:
|
|
515
|
+
# vcxo = self.vcxo['range']
|
|
516
|
+
|
|
517
|
+
self._saved_solution = None
|
|
518
|
+
|
|
519
|
+
# Add requested clocks to output constraints
|
|
520
|
+
for d_n, out_freq in enumerate(out_freqs):
|
|
521
|
+
|
|
522
|
+
if self.solver == "gekko":
|
|
523
|
+
__d = self._d if isinstance(self._d, list) else [self._d]
|
|
524
|
+
if __d.sort() != self.d_available.sort():
|
|
525
|
+
raise Exception(
|
|
526
|
+
"For solver gekko d is not configurable for HMC7044"
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
# even = self.model.Var(integer=True, lb=3, ub=2047)
|
|
530
|
+
# odd = self.model.Intermediate(even * 2)
|
|
531
|
+
# od = self.model.sos1([1, 2, 3, 4, 5, odd])
|
|
532
|
+
|
|
533
|
+
# Since d is so disjoint it is very annoying to solve.
|
|
534
|
+
even = self.model.Var(integer=True, lb=1, ub=4094 // 2)
|
|
535
|
+
|
|
536
|
+
# odd = self.model.sos1([1, 3, 5])
|
|
537
|
+
odd_i = self.model.Var(integer=True, lb=0, ub=2)
|
|
538
|
+
odd = self.model.Intermediate(1 + odd_i * 2)
|
|
539
|
+
|
|
540
|
+
eo = self.model.Var(integer=True, lb=0, ub=1)
|
|
541
|
+
od = self.model.Intermediate(eo * odd + (1 - eo) * even * 2)
|
|
542
|
+
|
|
543
|
+
elif self.solver == "CPLEX":
|
|
544
|
+
od = self._convert_input(self._d, f"d_{out_freq}_{d_n}")
|
|
545
|
+
|
|
546
|
+
self._add_equation(
|
|
547
|
+
[
|
|
548
|
+
self.config["vcxod"] * self.config["n2"]
|
|
549
|
+
== out_freq * self.config["r2"] * od
|
|
550
|
+
]
|
|
551
|
+
)
|
|
552
|
+
self.config["out_dividers"].append(od)
|
|
553
|
+
|
|
554
|
+
# Update diagram to include new divider
|
|
555
|
+
self._update_diagram({f"D{d_n}": od})
|
|
556
|
+
|
|
557
|
+
# Objectives
|
|
558
|
+
# self.model.Obj(-1*eo) # Favor even dividers
|