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
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Drawing features for AD9680."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
from adijif.draw import Layout, Node # type: ignore # isort: skip # noqa: I202
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ad9680_draw:
|
|
9
|
+
"""AD9680 drawing features."""
|
|
10
|
+
|
|
11
|
+
_system_draw = False
|
|
12
|
+
show_rates = True
|
|
13
|
+
|
|
14
|
+
def _init_diagram(self) -> None:
|
|
15
|
+
"""Initialize diagram for AD9680 alone."""
|
|
16
|
+
self.ic_diagram_node = None
|
|
17
|
+
self._diagram_output_dividers = []
|
|
18
|
+
|
|
19
|
+
self.ic_diagram_node = Node("AD9680")
|
|
20
|
+
|
|
21
|
+
# External
|
|
22
|
+
# ref_in = Node("REF_IN", ntype="input")
|
|
23
|
+
# lo.add_node(ref_in)
|
|
24
|
+
|
|
25
|
+
crossbar = Node("Crossbar", ntype="crossbar")
|
|
26
|
+
self.ic_diagram_node.add_child(crossbar)
|
|
27
|
+
for adc in range(2):
|
|
28
|
+
adc_node = Node(f"ADC{adc}", ntype="adc")
|
|
29
|
+
self.ic_diagram_node.add_child(adc_node)
|
|
30
|
+
adc_node.shape = "parallelogram"
|
|
31
|
+
self.ic_diagram_node.add_connection(
|
|
32
|
+
{"from": adc_node, "to": crossbar, "type": "data"}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
for ddc in range(4):
|
|
36
|
+
ddc_node = Node(f"DDC{ddc}", ntype="ddc")
|
|
37
|
+
self.ic_diagram_node.add_child(ddc_node)
|
|
38
|
+
self.ic_diagram_node.add_connection(
|
|
39
|
+
{"from": crossbar, "to": ddc_node, "type": "data"}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
jesd204_framer = Node("JESD204 Framer", ntype="jesd204framer")
|
|
43
|
+
self.ic_diagram_node.add_child(jesd204_framer)
|
|
44
|
+
|
|
45
|
+
for ddc in range(4):
|
|
46
|
+
ddc = self.ic_diagram_node.get_child(f"DDC{ddc}")
|
|
47
|
+
self.ic_diagram_node.add_connection(
|
|
48
|
+
{"from": ddc, "to": jesd204_framer, "type": "data"}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def _update_diagram(self, config: Dict) -> None:
|
|
52
|
+
"""Update diagram with configuration.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
config (Dict): Configuration dictionary
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
Exception: If key is not D followed by a number
|
|
59
|
+
"""
|
|
60
|
+
# Add output dividers
|
|
61
|
+
keys = config.keys()
|
|
62
|
+
output_dividers = self.ic_diagram_node.get_child("Output Dividers")
|
|
63
|
+
for key in keys:
|
|
64
|
+
if key.startswith("D"):
|
|
65
|
+
div = Node(key, ntype="divider")
|
|
66
|
+
output_dividers.add_child(div)
|
|
67
|
+
self.ic_diagram_node.add_connection(
|
|
68
|
+
{"from": output_dividers, "to": div}
|
|
69
|
+
)
|
|
70
|
+
else:
|
|
71
|
+
raise Exception(
|
|
72
|
+
f"Unknown key {key}. Must be of for DX where X is a number"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def draw(
|
|
76
|
+
self, clocks: Dict, lo: Layout = None, clock_chip_node: Node = None
|
|
77
|
+
) -> str:
|
|
78
|
+
"""Draw diagram in d2 language for IC alone with reference clock.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
clocks (Dict): Dictionary of clocks
|
|
82
|
+
lo (Layout): Layout object to add to. Defaults to None.
|
|
83
|
+
clock_chip_node (Node): Node to connect to. Defaults to None.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
str: Diagram in d2 language
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
Exception: If no solution is saved
|
|
90
|
+
"""
|
|
91
|
+
if not self._saved_solution:
|
|
92
|
+
raise Exception("No solution to draw. Must call solve first.")
|
|
93
|
+
|
|
94
|
+
system_draw = lo is not None
|
|
95
|
+
|
|
96
|
+
if not system_draw:
|
|
97
|
+
lo = Layout("AD9680 Example")
|
|
98
|
+
lo.show_rates = self.show_rates
|
|
99
|
+
else:
|
|
100
|
+
# Verify lo is a Layout object
|
|
101
|
+
assert isinstance(lo, Layout), "lo must be a Layout object"
|
|
102
|
+
lo.add_node(self.ic_diagram_node)
|
|
103
|
+
|
|
104
|
+
static_options = self.get_config()
|
|
105
|
+
|
|
106
|
+
if not system_draw:
|
|
107
|
+
ref_in = Node("REF_IN", ntype="input")
|
|
108
|
+
lo.add_node(ref_in)
|
|
109
|
+
else:
|
|
110
|
+
to_node = lo.get_node("AD9680_ref_clk")
|
|
111
|
+
# Locate node connected to this one
|
|
112
|
+
from_node = lo.get_connection(to=to_node.name)
|
|
113
|
+
assert from_node, "No connection found"
|
|
114
|
+
assert isinstance(from_node, list), "Connection must be a list"
|
|
115
|
+
assert len(from_node) == 1, "Only one connection allowed"
|
|
116
|
+
ref_in = from_node[0]["from"]
|
|
117
|
+
# Remove to_node since it is not needed
|
|
118
|
+
lo.remove_node(to_node.name)
|
|
119
|
+
|
|
120
|
+
for i in range(2):
|
|
121
|
+
adc = self.ic_diagram_node.get_child(f"ADC{i}")
|
|
122
|
+
lo.add_connection(
|
|
123
|
+
{"from": ref_in, "to": adc, "rate": clocks["AD9680_ref_clk"]}
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Update Node values
|
|
127
|
+
for ddc in range(4):
|
|
128
|
+
rate = clocks["AD9680_ref_clk"]
|
|
129
|
+
self.ic_diagram_node.update_connection("Crossbar", f"DDC{ddc}", rate)
|
|
130
|
+
|
|
131
|
+
ddc_node = self.ic_diagram_node.get_child(f"DDC{ddc}")
|
|
132
|
+
ddc_node.value = str(static_options["decimation"])
|
|
133
|
+
drate = rate / static_options["decimation"]
|
|
134
|
+
|
|
135
|
+
self.ic_diagram_node.update_connection(f"DDC{ddc}", "JESD204 Framer", drate)
|
|
136
|
+
|
|
137
|
+
# Connect clock to framer
|
|
138
|
+
if not system_draw:
|
|
139
|
+
sysref_in = Node("SYSREF_IN", ntype="input")
|
|
140
|
+
|
|
141
|
+
lo.add_connection(
|
|
142
|
+
{
|
|
143
|
+
"from": sysref_in,
|
|
144
|
+
"to": self.ic_diagram_node.get_child("JESD204 Framer"),
|
|
145
|
+
"rate": clocks["AD9680_sysref"],
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
to_node = lo.get_node("AD9680_sysref")
|
|
150
|
+
# Locate node connected to this one
|
|
151
|
+
from_node = lo.get_connection(to=to_node.name)
|
|
152
|
+
assert from_node, "No connection found"
|
|
153
|
+
assert isinstance(from_node, list), "Connection must be a list"
|
|
154
|
+
assert len(from_node) == 1, "Only one connection allowed"
|
|
155
|
+
sysref_in = from_node[0]["from"]
|
|
156
|
+
# Remove to_node since it is not needed
|
|
157
|
+
lo.remove_node(to_node.name)
|
|
158
|
+
|
|
159
|
+
lo.add_connection(
|
|
160
|
+
{
|
|
161
|
+
"from": sysref_in,
|
|
162
|
+
"to": self.ic_diagram_node.get_child("JESD204 Framer"),
|
|
163
|
+
"rate": clocks["AD9680_sysref"],
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Connect Remote Deframer
|
|
168
|
+
remote_deframer = Node("JESD204 Deframer", ntype="deframer")
|
|
169
|
+
lo.add_node(remote_deframer)
|
|
170
|
+
|
|
171
|
+
# Add connect for each lane
|
|
172
|
+
for _ in range(self.L):
|
|
173
|
+
lane_rate = self.bit_clock
|
|
174
|
+
lo.add_connection(
|
|
175
|
+
{
|
|
176
|
+
"from": self.ic_diagram_node.get_child("JESD204 Framer"),
|
|
177
|
+
"to": remote_deframer,
|
|
178
|
+
"rate": lane_rate,
|
|
179
|
+
"type": "data",
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if not system_draw:
|
|
184
|
+
return lo.draw()
|
adijif/converters/adc.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Converter base meta class for all converter clocking models."""
|
|
2
|
+
|
|
3
|
+
from abc import ABCMeta, abstractmethod
|
|
4
|
+
from typing import List, Union
|
|
5
|
+
|
|
6
|
+
from adijif.converters.converter import converter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class adc(converter, metaclass=ABCMeta):
|
|
10
|
+
"""ADC base meta class used to enforce all ADC classes.
|
|
11
|
+
|
|
12
|
+
This class should be inherited from for all ADCs and transceivers.
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def _check_valid_internal_configuration(self) -> None:
|
|
17
|
+
"""Verify current internal clocking configuration for part is valid.
|
|
18
|
+
|
|
19
|
+
Raises:
|
|
20
|
+
Exception: Invalid clocking configuration
|
|
21
|
+
"""
|
|
22
|
+
if self.decimation * self.sample_clock > self.converter_clock_max:
|
|
23
|
+
raise Exception(
|
|
24
|
+
"ADC rate too fast for configuration {}".format(
|
|
25
|
+
self.decimation * self.sample_clock
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
if self.decimation * self.sample_clock < self.converter_clock_min:
|
|
29
|
+
raise Exception(
|
|
30
|
+
"ADC rate too slow for configuration {}".format(
|
|
31
|
+
self.decimation * self.sample_clock
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def decimation_available(self) -> List[int]:
|
|
38
|
+
"""Decimation settings available.
|
|
39
|
+
|
|
40
|
+
Must be a list or None
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
NotImplementedError: If child classes do not implement method/property
|
|
44
|
+
"""
|
|
45
|
+
raise NotImplementedError # pragma: no cover
|
|
46
|
+
|
|
47
|
+
_decimation = 1
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def decimation(self) -> Union[int, float]:
|
|
51
|
+
"""Decimation between DAC and JESD framer. If none device is not an ADC.
|
|
52
|
+
|
|
53
|
+
Generally a multiple of 2
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
int: decimation value
|
|
57
|
+
"""
|
|
58
|
+
return self._decimation
|
|
59
|
+
|
|
60
|
+
@decimation.setter
|
|
61
|
+
def decimation(self, value: int) -> None:
|
|
62
|
+
"""Decimation between DAC and JESD framer. If none device is not an ADC.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
value (int): decimation
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
Exception: decimation not an integer or not in range
|
|
69
|
+
"""
|
|
70
|
+
if int(value) != value:
|
|
71
|
+
raise Exception("decimation_available must be an integer")
|
|
72
|
+
if value not in self.decimation_available:
|
|
73
|
+
raise Exception("decimation_available not in range for device")
|
|
74
|
+
self._decimation = value
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def converter_clock(self) -> Union[int, float]:
|
|
78
|
+
"""Get rate of converter in samples per second.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
float: converter clock rate in samples per second
|
|
82
|
+
"""
|
|
83
|
+
return self.sample_clock * self.decimation
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
"""ADRV9009 transceiver clocking model."""
|
|
2
|
+
|
|
3
|
+
from abc import ABCMeta
|
|
4
|
+
from typing import Any, Dict, List, Union
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from adijif.converters.adrv9009_bf import adrv9009_bf
|
|
9
|
+
|
|
10
|
+
from ..solvers import CpoModel # type: ignore # noqa: I202,BLK100
|
|
11
|
+
from ..solvers import GEKKO, CpoSolveResult
|
|
12
|
+
from .adc import adc
|
|
13
|
+
from .adrv9009_util import quick_configuration_modes_rx # type: ignore
|
|
14
|
+
from .adrv9009_util import _extra_jesd_check, quick_configuration_modes_tx
|
|
15
|
+
from .converter import converter
|
|
16
|
+
from .dac import dac
|
|
17
|
+
|
|
18
|
+
# References
|
|
19
|
+
# https://ez.analog.com/wide-band-rf-transceivers/design-support-adrv9008-1-adrv9008-2-adrv9009/f/q-a/103757/adrv9009-clock-configuration/308013#308013
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class adrv9009_core(converter, metaclass=ABCMeta):
|
|
23
|
+
"""ADRV9009 transceiver clocking model.
|
|
24
|
+
|
|
25
|
+
This model manage the JESD configuration and input clock constraints.
|
|
26
|
+
External LO constraints are not modeled.
|
|
27
|
+
|
|
28
|
+
Clocking: ADRV9009 uses onboard PLLs to generate the JESD clocks
|
|
29
|
+
|
|
30
|
+
Lane Rate = I/Q Sample Rate * M * Np * (10 / 8) / L
|
|
31
|
+
Lane Rate = sample_clock * M * Np * (10 / 8) / L
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
device_clock_available = None # FIXME
|
|
35
|
+
device_clock_ranges = None # FIXME
|
|
36
|
+
|
|
37
|
+
name = "ADRV9009"
|
|
38
|
+
|
|
39
|
+
# JESD configurations
|
|
40
|
+
quick_configuration_modes = None # FIXME
|
|
41
|
+
available_jesd_modes = ["jesd204b"]
|
|
42
|
+
M_available = [1, 2, 4]
|
|
43
|
+
L_available = [1, 2, 3, 4, 6, 8]
|
|
44
|
+
N_available = [12, 16]
|
|
45
|
+
Np_available = [12, 16, 24]
|
|
46
|
+
F_available = [1, 2, 3, 4, 8]
|
|
47
|
+
S_available = [1] # FIXME?
|
|
48
|
+
K_available = [*np.arange(1, 32 + 1)]
|
|
49
|
+
CS_available = [0]
|
|
50
|
+
CF_available = [0]
|
|
51
|
+
|
|
52
|
+
# Clock constraints
|
|
53
|
+
converter_clock_min = 39.063e6 * 8
|
|
54
|
+
converter_clock_max = 12288e6
|
|
55
|
+
|
|
56
|
+
sample_clock_min = 39.063e6
|
|
57
|
+
sample_clock_max = 491520000
|
|
58
|
+
|
|
59
|
+
device_clock_min = 10e6
|
|
60
|
+
device_clock_max = 1e9
|
|
61
|
+
|
|
62
|
+
clocking_option_available = ["integrated_pll"]
|
|
63
|
+
_clocking_option = "integrated_pll"
|
|
64
|
+
|
|
65
|
+
# Divider ranges
|
|
66
|
+
input_clock_dividers_available = [1 / 2, 1, 2, 4, 8, 16]
|
|
67
|
+
input_clock_dividers_times2_available = [1, 2, 4, 8, 16, 32]
|
|
68
|
+
|
|
69
|
+
_lmfc_divisor_sysref_available = [*range(1, 20)]
|
|
70
|
+
|
|
71
|
+
# Unused
|
|
72
|
+
max_rx_sample_clock = 250e6
|
|
73
|
+
max_tx_sample_clock = 500e6
|
|
74
|
+
max_obs_sample_clock = 500e6
|
|
75
|
+
|
|
76
|
+
def _check_valid_internal_configuration(self) -> None:
|
|
77
|
+
# FIXME
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
def get_required_clock_names(self) -> List[str]:
|
|
81
|
+
"""Get list of strings of names of requested clocks.
|
|
82
|
+
|
|
83
|
+
This list of names is for the clocks defined by
|
|
84
|
+
get_required_clocks
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
List[str]: List of strings of clock names mapped by get_required_clocks
|
|
88
|
+
"""
|
|
89
|
+
return ["adrv9009_ref_clk", "adrv9009_sysref"]
|
|
90
|
+
|
|
91
|
+
def get_config(self, solution: CpoSolveResult = None) -> Dict:
|
|
92
|
+
"""Extract configurations from solver results.
|
|
93
|
+
|
|
94
|
+
Collect internal converter configuration and output clock definitions
|
|
95
|
+
leading to connected devices (clock chips, FPGAs)
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Dict: Dictionary of clocking rates and dividers for configuration
|
|
102
|
+
"""
|
|
103
|
+
if solution:
|
|
104
|
+
self.solution = solution
|
|
105
|
+
return {}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class adrv9009_clock_common(adrv9009_core, adrv9009_bf):
|
|
109
|
+
"""ADRV9009 class managing common singleton (Rx,Tx) methods."""
|
|
110
|
+
|
|
111
|
+
def _check_valid_jesd_mode(self) -> None:
|
|
112
|
+
"""Verify current JESD configuration for part is valid."""
|
|
113
|
+
_extra_jesd_check(self)
|
|
114
|
+
return super()._check_valid_jesd_mode()
|
|
115
|
+
|
|
116
|
+
def get_config(self, solution: CpoSolveResult = None) -> Dict:
|
|
117
|
+
"""Extract configurations from solver results.
|
|
118
|
+
|
|
119
|
+
Collect internal converter configuration and output clock definitions
|
|
120
|
+
leading to connected devices (clock chips, FPGAs)
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
solution (CpoSolveResult): CPlex solution. Only needed for CPlex solver
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Dict: Dictionary of clocking rates and dividers for configuration
|
|
127
|
+
"""
|
|
128
|
+
return {"clocking_option": self.clocking_option}
|
|
129
|
+
|
|
130
|
+
def _gekko_get_required_clocks(self) -> List[Dict]:
|
|
131
|
+
possible_sysrefs = []
|
|
132
|
+
for n in range(1, 20):
|
|
133
|
+
r = self.multiframe_clock / (n * n)
|
|
134
|
+
if r == int(r):
|
|
135
|
+
possible_sysrefs.append(r)
|
|
136
|
+
|
|
137
|
+
self.config = {"sysref": self.model.sos1(possible_sysrefs)}
|
|
138
|
+
self.model.Obj(self.config["sysref"])
|
|
139
|
+
|
|
140
|
+
possible_device_clocks = []
|
|
141
|
+
for div in self.input_clock_dividers_available:
|
|
142
|
+
dev_clock = self.sample_clock / div
|
|
143
|
+
if self.device_clock_min <= dev_clock <= self.device_clock_max:
|
|
144
|
+
possible_device_clocks.append(dev_clock)
|
|
145
|
+
|
|
146
|
+
self.config["ref_clk"] = self.model.sos1(possible_device_clocks)
|
|
147
|
+
# self.model.Obj(-1 * self.config["device_clock"])
|
|
148
|
+
|
|
149
|
+
return [self.config["ref_clk"], self.config["sysref"]]
|
|
150
|
+
|
|
151
|
+
def get_required_clocks(self) -> List[Dict]:
|
|
152
|
+
"""Generate list of required clocks.
|
|
153
|
+
|
|
154
|
+
For ADRV9009 this will contain:
|
|
155
|
+
[device clock requirement SOS, sysref requirement SOS]
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
list[dict]: List of dictionaries of solver variables, equations, and constants
|
|
159
|
+
"""
|
|
160
|
+
if self.solver == "gekko":
|
|
161
|
+
return self._gekko_get_required_clocks()
|
|
162
|
+
self.config = {}
|
|
163
|
+
self.config["lmfc_divisor_sysref"] = self._convert_input(
|
|
164
|
+
[*range(1, 20)], name="lmfc_divisor_sysref"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
self.config["input_clock_divider_x2"] = self._convert_input(
|
|
168
|
+
self.input_clock_dividers_times2_available
|
|
169
|
+
)
|
|
170
|
+
self.config["ref_clk"] = self._add_intermediate(
|
|
171
|
+
self.sample_clock / self.config["input_clock_divider_x2"]
|
|
172
|
+
)
|
|
173
|
+
self.config["sysref"] = self._add_intermediate(
|
|
174
|
+
self.multiframe_clock
|
|
175
|
+
/ (self.config["lmfc_divisor_sysref"] * self.config["lmfc_divisor_sysref"])
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
self._add_equation(
|
|
179
|
+
[
|
|
180
|
+
self.device_clock_min <= self.config["ref_clk"],
|
|
181
|
+
self.config["ref_clk"] <= self.device_clock_max,
|
|
182
|
+
]
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return [self.config["ref_clk"], self.config["sysref"]]
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class adrv9009_rx(adc, adrv9009_clock_common, adrv9009_core):
|
|
189
|
+
"""ADRV9009 Receive model."""
|
|
190
|
+
|
|
191
|
+
quick_configuration_modes = {"jesd204b": quick_configuration_modes_rx}
|
|
192
|
+
name = "ADRV9009_RX"
|
|
193
|
+
converter_type = "adc"
|
|
194
|
+
|
|
195
|
+
# JESD configurations
|
|
196
|
+
K_available = [*np.arange(1, 32 + 1)]
|
|
197
|
+
L_available = [1, 2, 4]
|
|
198
|
+
M_available = [1, 2, 4]
|
|
199
|
+
N_available = [12, 14, 16, 24]
|
|
200
|
+
Np_available = [12, 16, 24]
|
|
201
|
+
F_available = [
|
|
202
|
+
1,
|
|
203
|
+
2,
|
|
204
|
+
3,
|
|
205
|
+
4,
|
|
206
|
+
6,
|
|
207
|
+
8,
|
|
208
|
+
]
|
|
209
|
+
CS_available = [0]
|
|
210
|
+
CF_available = [0]
|
|
211
|
+
S_available = [1, 2, 4]
|
|
212
|
+
|
|
213
|
+
# Clock constraints
|
|
214
|
+
bit_clock_min_available = {"jesd204b": 3.6864e9}
|
|
215
|
+
bit_clock_max_available = {"jesd204b": 12.288e9}
|
|
216
|
+
|
|
217
|
+
_decimation = 8
|
|
218
|
+
decimation_available = [4, 5, 8, 10, 16, 20, 32, 40]
|
|
219
|
+
"""
|
|
220
|
+
ADRV9009 Rx decimation stages.
|
|
221
|
+
|
|
222
|
+
.. code-block:: none
|
|
223
|
+
|
|
224
|
+
+-----------+
|
|
225
|
+
+-----------+ Dec 5 (5) +---------+
|
|
226
|
+
| +-----------+ |
|
|
227
|
+
| |
|
|
228
|
+
| +----------+ +----------+ | +------------+ +--------------+
|
|
229
|
+
>---+---+ RHB3 (2) +---+ RHB2 (2) +---+--+ RHB1 (1,2) +---+ RFIR (1,2,4) +
|
|
230
|
+
+----------+ +----------+ +------------+ +--------------+
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401
|
|
234
|
+
"""Initialize ADRV9009-RX class.
|
|
235
|
+
|
|
236
|
+
Objects will default to mode and sample_clock.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
*args (Any): Pass through arguments
|
|
240
|
+
**kwargs (Any): Pass through keyword arguments
|
|
241
|
+
"""
|
|
242
|
+
# Set default mode
|
|
243
|
+
self.set_quick_configuration_mode(str(16))
|
|
244
|
+
# self.K = 32
|
|
245
|
+
self.sample_clock = int(122.88e6)
|
|
246
|
+
super().__init__(*args, **kwargs)
|
|
247
|
+
# self._init_diagram()
|
|
248
|
+
|
|
249
|
+
def get_required_clock_names(self) -> List[str]:
|
|
250
|
+
"""Get list of strings of names of requested clocks.
|
|
251
|
+
|
|
252
|
+
This list of names is for the clocks defined by
|
|
253
|
+
get_required_clocks
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
List[str]: List of strings of clock names mapped by get_required_clocks
|
|
257
|
+
"""
|
|
258
|
+
return ["adrv9009_rx_ref_clk", "adrv9009_rx_sysref"]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class adrv9009_tx(dac, adrv9009_clock_common, adrv9009_core):
|
|
262
|
+
"""ADRV9009 Transmit model."""
|
|
263
|
+
|
|
264
|
+
quick_configuration_modes = {"jesd204b": quick_configuration_modes_tx}
|
|
265
|
+
name = "ADRV9009_TX"
|
|
266
|
+
converter_type = "dac"
|
|
267
|
+
|
|
268
|
+
# JESD configurations
|
|
269
|
+
K_available = [*np.arange(1, 32 + 1)]
|
|
270
|
+
L_available = [1, 2, 4]
|
|
271
|
+
M_available = [1, 2, 4]
|
|
272
|
+
N_available = [12, 16]
|
|
273
|
+
Np_available = [12, 16]
|
|
274
|
+
F_available = [1, 2, 3, 4, 8]
|
|
275
|
+
CS_available = [0]
|
|
276
|
+
CF_available = [0]
|
|
277
|
+
S_available = [1]
|
|
278
|
+
|
|
279
|
+
# Clock constraints
|
|
280
|
+
bit_clock_min_available = {"jesd204b": 2457.6e6}
|
|
281
|
+
bit_clock_max_available = {"jesd204b": 12.288e9}
|
|
282
|
+
|
|
283
|
+
_interpolation = 8
|
|
284
|
+
interpolation_available = [1, 2, 4, 5, 8, 10, 16, 20, 32]
|
|
285
|
+
"""
|
|
286
|
+
ADRV9009 Tx interpolation stages.
|
|
287
|
+
|
|
288
|
+
.. code-block:: none
|
|
289
|
+
|
|
290
|
+
+------------+
|
|
291
|
+
+--------------------+ Int 5 (5) +--------------------+
|
|
292
|
+
| +------------+ |
|
|
293
|
+
| |
|
|
294
|
+
| +------------+ +------------+ +------------+ | +--------------+
|
|
295
|
+
<---+---+ THB3 (1,2) +---+ THB2 (1,2) +---+ THB1 (1,2) +---+---+ TFIR (1,2,4) +
|
|
296
|
+
+------------+ +------------+ +------------+ +--------------+
|
|
297
|
+
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401
|
|
301
|
+
"""Initialize ADRV9009-TX class.
|
|
302
|
+
|
|
303
|
+
Objects will default to mode and sample_clock.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
*args (Any): Pass through arguments
|
|
307
|
+
**kwargs (Any): Pass through keyword arguments
|
|
308
|
+
"""
|
|
309
|
+
# Set default mode
|
|
310
|
+
self.set_quick_configuration_mode(str(6))
|
|
311
|
+
self.sample_clock = int(122.88e6)
|
|
312
|
+
super().__init__(*args, **kwargs)
|
|
313
|
+
# self._init_diagram()
|
|
314
|
+
|
|
315
|
+
def get_required_clock_names(self) -> List[str]:
|
|
316
|
+
"""Get list of strings of names of requested clocks.
|
|
317
|
+
|
|
318
|
+
This list of names is for the clocks defined by
|
|
319
|
+
get_required_clocks
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
List[str]: List of strings of clock names mapped by get_required_clocks
|
|
323
|
+
"""
|
|
324
|
+
return ["adrv9009_tx_ref_clk", "adrv9009_tx_sysref"]
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class adrv9009(adrv9009_core):
|
|
328
|
+
"""ADRV9009 combined transmit and receive model."""
|
|
329
|
+
|
|
330
|
+
name = "ADRV9009"
|
|
331
|
+
solver = "CPLEX"
|
|
332
|
+
_nested = ["adc", "dac"]
|
|
333
|
+
converter_type = "adc_dac"
|
|
334
|
+
|
|
335
|
+
def __init__(
|
|
336
|
+
self, model: Union[GEKKO, CpoModel] = None, solver: str = None
|
|
337
|
+
) -> None:
|
|
338
|
+
"""Initialize ADRV9009 clocking model for TX and RX.
|
|
339
|
+
|
|
340
|
+
This is a common class used to handle TX and RX constraints
|
|
341
|
+
together.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
model (GEKKO,CpoModel): Solver model
|
|
345
|
+
solver (str): Solver name (gekko or CPLEX)
|
|
346
|
+
"""
|
|
347
|
+
if solver:
|
|
348
|
+
self.solver = solver
|
|
349
|
+
self.adc = adrv9009_rx(model, solver=self.solver)
|
|
350
|
+
self.dac = adrv9009_tx(model, solver=self.solver)
|
|
351
|
+
self.model = model
|
|
352
|
+
|
|
353
|
+
def validate_config(self) -> None:
|
|
354
|
+
"""Validate device configurations including JESD and clocks of both ADC and DAC.
|
|
355
|
+
|
|
356
|
+
This check only is for static configuration that does not include
|
|
357
|
+
variables which are solved.
|
|
358
|
+
"""
|
|
359
|
+
self.adc.validate_config()
|
|
360
|
+
self.dac.validate_config()
|
|
361
|
+
|
|
362
|
+
def _get_converters(self) -> List[Union[converter, converter]]:
|
|
363
|
+
return [self.adc, self.dac]
|
|
364
|
+
|
|
365
|
+
def get_required_clocks(self) -> List[Dict]:
|
|
366
|
+
"""Generate list of required clocks.
|
|
367
|
+
|
|
368
|
+
For ADRV9009 this will contain:
|
|
369
|
+
[device clock requirement SOS, sysref requirement SOS]
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
list[dict]: List of dictionaries of solver variables, equations, and constants
|
|
373
|
+
|
|
374
|
+
Raises:
|
|
375
|
+
Exception: Invalid relation of rates between RX and TX
|
|
376
|
+
AssertionError: Gekko called
|
|
377
|
+
"""
|
|
378
|
+
# Validate sample rates feasible
|
|
379
|
+
if self.dac.sample_clock / self.adc.sample_clock not in [
|
|
380
|
+
1,
|
|
381
|
+
2,
|
|
382
|
+
4,
|
|
383
|
+
] or self.adc.sample_clock / self.dac.sample_clock not in [1, 2, 4]:
|
|
384
|
+
raise Exception(
|
|
385
|
+
"ADRV9009 RX and TX sample rates must be related by power of 2"
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
if self.solver == "gekko":
|
|
389
|
+
raise AssertionError
|
|
390
|
+
|
|
391
|
+
self.config = {}
|
|
392
|
+
self.config["adc_lmfc_divisor_sysref"] = self._convert_input(
|
|
393
|
+
self._lmfc_divisor_sysref_available, name="adc_lmfc_divisor_sysref"
|
|
394
|
+
)
|
|
395
|
+
self.config["dac_lmfc_divisor_sysref"] = self._convert_input(
|
|
396
|
+
self._lmfc_divisor_sysref_available, name="dac_lmfc_divisor_sysref"
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
self.config["input_clock_divider_x2"] = self._convert_input(
|
|
400
|
+
self.input_clock_dividers_times2_available
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
faster_clk = max([self.adc.sample_clock, self.dac.sample_clock])
|
|
404
|
+
self.config["ref_clk"] = self._add_intermediate(
|
|
405
|
+
faster_clk / self.config["input_clock_divider_x2"]
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
self.config["sysref_adc"] = self._add_intermediate(
|
|
409
|
+
self.adc.multiframe_clock / self.config["adc_lmfc_divisor_sysref"]
|
|
410
|
+
)
|
|
411
|
+
self.config["sysref_dac"] = self._add_intermediate(
|
|
412
|
+
self.dac.multiframe_clock / self.config["dac_lmfc_divisor_sysref"]
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
self._add_equation(
|
|
416
|
+
[
|
|
417
|
+
self.device_clock_min <= self.config["ref_clk"],
|
|
418
|
+
self.config["ref_clk"] <= self.device_clock_max,
|
|
419
|
+
]
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
return [
|
|
423
|
+
self.config["ref_clk"],
|
|
424
|
+
self.config["sysref_adc"],
|
|
425
|
+
self.config["sysref_dac"],
|
|
426
|
+
]
|