pyadi-jif 0.1.0__py2.py3-none-any.whl → 0.1.1__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.
Files changed (43) hide show
  1. adijif/__init__.py +2 -2
  2. adijif/clocks/ad9523_1_bf.py +3 -3
  3. adijif/clocks/ad9528_bf.py +1 -1
  4. adijif/clocks/hmc7044_bf.py +1 -1
  5. adijif/clocks/ltc6952.py +2 -2
  6. adijif/clocks/ltc6952_bf.py +1 -1
  7. adijif/clocks/ltc6953.py +2 -2
  8. adijif/converters/__init__.py +12 -1
  9. adijif/converters/ad9081.py +1 -1
  10. adijif/converters/ad9081_dp.py +2 -0
  11. adijif/converters/ad9084.py +29 -7
  12. adijif/converters/ad9084_draw.py +20 -14
  13. adijif/converters/ad9084_util.py +24 -5
  14. adijif/converters/ad9088_dp.py +111 -0
  15. adijif/converters/converter.py +153 -2
  16. adijif/draw.py +11 -2
  17. adijif/fpgas/xilinx/__init__.py +4 -2
  18. adijif/jesd.py +3 -1
  19. adijif/plls/adf4030.py +6 -1
  20. adijif/plls/adf4382.py +0 -1
  21. adijif/solvers.py +5 -4
  22. adijif/system.py +16 -1
  23. adijif/tools/explorer/cli.py +36 -0
  24. adijif/tools/explorer/main.py +41 -0
  25. adijif/tools/explorer/src/pages/__init__.py +16 -0
  26. adijif/tools/explorer/src/pages/clockconfigurator.py +193 -0
  27. adijif/tools/explorer/src/pages/helpers/datapath.py +85 -0
  28. adijif/tools/explorer/src/pages/helpers/drawers.py +87 -0
  29. adijif/tools/explorer/src/pages/helpers/jesd.py +140 -0
  30. adijif/tools/explorer/src/pages/jesdmodeselector.py +209 -0
  31. adijif/tools/explorer/src/pages/systemconfigurator.py +252 -0
  32. adijif/tools/explorer/src/state.py +141 -0
  33. adijif/tools/explorer/src/utils.py +37 -0
  34. adijif/types.py +23 -4
  35. {pyadi_jif-0.1.0.dist-info → pyadi_jif-0.1.1.dist-info}/METADATA +36 -12
  36. {pyadi_jif-0.1.0.dist-info → pyadi_jif-0.1.1.dist-info}/RECORD +41 -30
  37. pyadi_jif-0.1.1.dist-info/entry_points.txt +2 -0
  38. adijif/d2/__init__.py +0 -26
  39. adijif/d2/d2lib.h +0 -81
  40. {pyadi_jif-0.1.0.dist-info → pyadi_jif-0.1.1.dist-info}/WHEEL +0 -0
  41. {pyadi_jif-0.1.0.dist-info → pyadi_jif-0.1.1.dist-info}/licenses/AUTHORS.rst +0 -0
  42. {pyadi_jif-0.1.0.dist-info → pyadi_jif-0.1.1.dist-info}/licenses/LICENSE +0 -0
  43. {pyadi_jif-0.1.0.dist-info → pyadi_jif-0.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,85 @@
1
+ """Helper functions for datapath configuration UI elements."""
2
+
3
+ import streamlit as st
4
+
5
+ from adijif.converters import converter as conv
6
+
7
+
8
+ def gen_datapath(converter: conv) -> int:
9
+ """Generate datapath configuration UI elements for Streamlit.
10
+
11
+ Args:
12
+ converter: The converter object to generate the datapath for.
13
+
14
+ Returns:
15
+ The selected decimation or interpolation value.
16
+
17
+ Raises:
18
+ Exception: If no interpolation setting is found for DACs.
19
+ """
20
+ if converter.converter_type.lower() == "adc":
21
+ decimation = 1
22
+ if converter.datapath:
23
+ if hasattr(converter.datapath, "cddc_decimations_available"):
24
+ options = converter.datapath.cddc_decimations_available
25
+ cddc_decimation = st.selectbox(
26
+ "CDDC Decimation",
27
+ options=options,
28
+ format_func=lambda x: str(x),
29
+ )
30
+ decimation = cddc_decimation
31
+ v = len(converter.datapath.cddc_decimations)
32
+ converter.datapath.cddc_decimations = [cddc_decimation] * v
33
+ if hasattr(converter.datapath, "fddc_decimations_available"):
34
+ options = converter.datapath.fddc_decimations_available
35
+ fddc_decimation = st.selectbox(
36
+ "FDDC Decimation",
37
+ options=options,
38
+ format_func=lambda x: str(x),
39
+ )
40
+ decimation *= fddc_decimation
41
+ v = len(converter.datapath.fddc_decimations)
42
+ converter.datapath.fddc_decimations = [fddc_decimation] * v
43
+ elif hasattr(converter, "decimation_available"):
44
+ decimation = st.selectbox(
45
+ "Decimation",
46
+ options=converter.decimation_available,
47
+ format_func=lambda x: str(x),
48
+ )
49
+ decimation = int(decimation)
50
+ converter.decimation = decimation
51
+ else:
52
+ interpolation = 1
53
+ if converter.datapath:
54
+ if hasattr(converter.datapath, "cduc_interpolations_available"):
55
+ options = converter.datapath.cduc_interpolations_available
56
+ cduc_interpolation = st.selectbox(
57
+ "CDUC Interpolation",
58
+ options=options,
59
+ format_func=lambda x: str(x),
60
+ )
61
+ interpolation = cduc_interpolation
62
+ converter.datapath.cduc_interpolation = cduc_interpolation
63
+ if hasattr(converter.datapath, "fduc_interpolations_available"):
64
+ options = converter.datapath.fduc_interpolations_available
65
+ fduc_interpolation = st.selectbox(
66
+ "FDUC Interpolation",
67
+ options=options,
68
+ format_func=lambda x: str(x),
69
+ )
70
+ interpolation *= fduc_interpolation
71
+ converter.datapath.fduc_interpolation = fduc_interpolation
72
+ elif hasattr(converter, "interpolation_available"):
73
+ interpolation = st.selectbox(
74
+ "Interpolation",
75
+ options=converter.interpolation_available,
76
+ format_func=lambda x: str(x),
77
+ )
78
+ interpolation = int(interpolation)
79
+ converter.interpolation = interpolation
80
+ else:
81
+ raise Exception("No interpolation setting found")
82
+
83
+ decimation = interpolation
84
+
85
+ return decimation
@@ -0,0 +1,87 @@
1
+ """Helper functions for drawing ADC diagrams."""
2
+
3
+ import logging
4
+ from typing import Optional
5
+
6
+ import adijif as jif
7
+
8
+ logger = logging.getLogger(__name__)
9
+ logger.setLevel(logging.INFO)
10
+
11
+
12
+ def draw_adc(adc: Optional[object] = None) -> str:
13
+ """Draw ADC clock tree diagram.
14
+
15
+ Args:
16
+ adc: ADC converter object. If None, uses ad9680 as default.
17
+
18
+ Returns:
19
+ Path to generated SVG file.
20
+ """
21
+ if adc is None:
22
+ adc = jif.ad9680()
23
+
24
+ # Check static
25
+ adc.validate_config()
26
+
27
+ required_clocks = adc.get_required_clocks()
28
+ required_clock_names = adc.get_required_clock_names()
29
+
30
+ # Add generic clock sources for solver
31
+ clks = []
32
+ for clock, name in zip(required_clocks, required_clock_names): # noqa: B905
33
+ clk = jif.types.arb_source(name)
34
+ adc._add_equation(clk(adc.model) == clock)
35
+ clks.append(clk)
36
+
37
+ # Solve
38
+ solution = adc.model.solve(LogVerbosity="Quiet")
39
+ settings = adc.get_config(solution)
40
+
41
+ # Get clock values
42
+ clock_values = {}
43
+ for clk in clks:
44
+ clock_values.update(clk.get_config(solution))
45
+ settings["clocks"] = clock_values
46
+
47
+ adc.show_rates = False
48
+ return adc.draw(settings["clocks"])
49
+
50
+
51
+ def draw_dac(dac: Optional[object] = None) -> str:
52
+ """Draw DAC clock tree diagram.
53
+
54
+ Args:
55
+ dac: DAC converter object. If None, uses ad9144 as default.
56
+
57
+ Returns:
58
+ Path to generated SVG file.
59
+ """
60
+ if dac is None:
61
+ dac = jif.ad9144()
62
+
63
+ # Check static
64
+ dac.validate_config()
65
+
66
+ required_clocks = dac.get_required_clocks()
67
+ required_clock_names = dac.get_required_clock_names()
68
+
69
+ # Add generic clock sources for solver
70
+ clks = []
71
+ for clock, name in zip(required_clocks, required_clock_names): # noqa: B905
72
+ clk = jif.types.arb_source(name)
73
+ dac._add_equation(clk(dac.model) == clock)
74
+ clks.append(clk)
75
+
76
+ # Solve
77
+ solution = dac.model.solve(LogVerbosity="Quiet")
78
+ settings = dac.get_config(solution)
79
+
80
+ # Get clock values
81
+ clock_values = {}
82
+ for clk in clks:
83
+ clock_values.update(clk.get_config(solution))
84
+ settings["clocks"] = clock_values
85
+
86
+ dac.show_rates = False
87
+ return dac.draw(settings["clocks"])
@@ -0,0 +1,140 @@
1
+ """Helper functions for JESD mode handling."""
2
+
3
+ import copy
4
+ import logging
5
+ from typing import Any, Dict, List, Optional, Tuple
6
+
7
+ from adijif.utils import get_jesd_mode_from_params
8
+
9
+ log = logging.getLogger(__name__)
10
+
11
+
12
+ def get_jesd_controls(converter: object) -> Tuple[Dict[str, List[Any]], Dict[str, Any]]:
13
+ """Get JESD control options from converter.
14
+
15
+ Args:
16
+ converter: JESD converter object
17
+
18
+ Returns:
19
+ Tuple of (options dict, all_modes dict)
20
+
21
+ Raises:
22
+ Exception: When mode settings are not consistent across all subclasses
23
+ """
24
+ options_to_skip = ["global_index", "decimations", "interpolations"]
25
+
26
+ # Derive configurable jesd settings for specific converter based on mode table
27
+ all_modes = converter.quick_configuration_modes
28
+ all_modes = copy.deepcopy(all_modes)
29
+ subclasses = list(all_modes.keys())
30
+
31
+ mode_knobs = None
32
+
33
+ for subclass in subclasses:
34
+ # Pick first mode of subclass and extract settings
35
+ ks = all_modes[subclass].keys()
36
+ ks = list(ks)
37
+ if len(ks) == 0:
38
+ # print(f"No modes found for subclass {subclass}")
39
+ continue
40
+ first_mode = list(all_modes[subclass].keys())[0]
41
+ mode_settings = all_modes[subclass][first_mode].keys()
42
+ mode_settings = sorted(mode_settings)
43
+ if not mode_knobs:
44
+ mode_knobs = mode_settings
45
+ continue
46
+
47
+ # Compare settings in other subclasses to make sure they are the same
48
+ if mode_settings != mode_knobs:
49
+ differences = set(mode_settings) ^ set(mode_knobs)
50
+ log.warning(
51
+ f"Mode settings are not consistent across all subclasses: {differences}"
52
+ )
53
+ raise Exception("Mode settings are not consistent across all subclasses")
54
+
55
+ # Parse all options for each control across modes
56
+ options = {}
57
+ for setting in mode_knobs:
58
+ if setting in options_to_skip:
59
+ continue
60
+ options[setting] = []
61
+ for subclass in subclasses:
62
+ for mode in all_modes[subclass]:
63
+ data = all_modes[subclass][mode][setting]
64
+ if isinstance(data, list):
65
+ print(f"Skipping list data for {setting}")
66
+ continue
67
+ options[setting].append(data)
68
+
69
+ # Make sure options only contain unique values
70
+ for option in options:
71
+ options[option] = list(set(options[option]))
72
+
73
+ return options, all_modes
74
+
75
+
76
+ def get_valid_jesd_modes(
77
+ converter: object, all_modes: Dict[str, Any], selections: Dict[str, Any]
78
+ ) -> Tuple[List[Dict[str, Any]], Optional[List[Dict[str, Any]]]]:
79
+ """Get valid JESD modes based on selections.
80
+
81
+ Args:
82
+ converter: JESD converter object
83
+ all_modes: Dictionary of all available modes
84
+ selections: User selections for filtering
85
+
86
+ Returns:
87
+ Tuple of (modes info list, found modes list or None)
88
+ """
89
+ modes_all_info: Any = {}
90
+
91
+ try:
92
+ found_modes = get_jesd_mode_from_params(converter, **selections)
93
+
94
+ except Exception:
95
+ log.info("No modes found")
96
+ found_modes = None
97
+ return modes_all_info, found_modes
98
+
99
+ options_to_skip = ["global_index", "decimations"]
100
+
101
+ # Get remaining mode parameters
102
+ modes_all_info = []
103
+ for mode in found_modes:
104
+ jesd_cfg = all_modes[mode["jesd_class"]][mode["mode"]]
105
+ jesd_cfg["mode"] = mode["mode"]
106
+ jesd_cfg["jesd_class"] = mode["jesd_class"]
107
+
108
+ # Remove options to skip
109
+ for option in options_to_skip:
110
+ jesd_cfg.pop(option, None)
111
+
112
+ modes_all_info.append(jesd_cfg)
113
+
114
+ # For each mode calculate the clocks and if valid
115
+ for mode in modes_all_info:
116
+ rate = converter.sample_clock
117
+ # print("A", converter.sample_clock)
118
+ converter.set_quick_configuration_mode(mode["mode"], mode["jesd_class"])
119
+ # print("B", converter.sample_clock)
120
+ log.warning("Logical BUG")
121
+ converter.sample_clock = rate
122
+
123
+ clocks = {
124
+ "Sample Rate (MSPS)": converter.sample_clock / 1e6,
125
+ "Lane Rate (GSPS)": converter.bit_clock / 1e9,
126
+ }
127
+
128
+ for clock in clocks:
129
+ mode[clock] = clocks[clock]
130
+
131
+ try:
132
+ converter.validate_config()
133
+ mode["Valid"] = "Yes"
134
+ except Exception:
135
+ mode["Valid"] = "No"
136
+
137
+ # from pprint import pprint
138
+ # pprint(modes_all_info)
139
+
140
+ return modes_all_info, found_modes
@@ -0,0 +1,209 @@
1
+ """JESD204 mode selector page."""
2
+
3
+ from typing import Optional
4
+
5
+ import pandas as pd
6
+ import streamlit as st
7
+
8
+ from adijif.converters import supported_parts as sp
9
+
10
+ from ..utils import Page
11
+ from .helpers.datapath import gen_datapath
12
+ from .helpers.drawers import draw_adc, draw_dac
13
+ from .helpers.jesd import get_jesd_controls, get_valid_jesd_modes
14
+
15
+ # options_to_skip = ["global_index", "decimations"]
16
+
17
+
18
+ class JESDModeSelector(Page):
19
+ """JESD204 mode selector tool page."""
20
+
21
+ def __init__(self, state: Optional[object]) -> None:
22
+ """Initialize JESD mode selector page.
23
+
24
+ Args:
25
+ state: Application state object
26
+ """
27
+ self.state = state
28
+ self.part_images = {}
29
+
30
+ def write(self) -> None:
31
+ """Render the JESD mode selector page."""
32
+ # Get supported parts that have quick_configuration_modes
33
+ import adijif # noqa: F401
34
+
35
+ supported_parts_filtered = []
36
+ for part in sp:
37
+ try:
38
+ converter = eval(f"adijif.{part}()") # noqa: S307
39
+ qsm = converter.quick_configuration_modes # noqa: F841
40
+ supported_parts_filtered.append(part)
41
+ except Exception: # noqa: S110
42
+ pass
43
+ supported_parts = supported_parts_filtered
44
+
45
+ st.title("JESD204 Mode Selector")
46
+
47
+ @st.dialog("About JESD204 Mode Selector")
48
+ def help() -> None:
49
+ """Display help dialog."""
50
+ st.write( # noqa: S608
51
+ "This tool helps you select the appropriate JESD204 mode for "
52
+ "your application. It supports both ADCs, DACs, MxFEs, and "
53
+ "Transceivers from the ADI portfolio. Modeling their JESD204 "
54
+ "mode tables and clocking limitations of the individual "
55
+ "devices.\n\n"
56
+ "To use the tool, select a part from the dropdown menu. You "
57
+ "can then configure the datapath settings such as "
58
+ "decimation/interpolation and the converter sample rate. The "
59
+ "tool will derive the necessary clocks for the selected "
60
+ "configuration. Filter different JESD204 parameters to find a "
61
+ "suitable mode for your application. The valid modes will be "
62
+ "displayed in a table, along with the derived settings."
63
+ "\n\n"
64
+ "JESD204 settings can be exported as CSV from the table."
65
+ )
66
+
67
+ st.button("Help", on_click=help)
68
+
69
+ sb = st.selectbox(
70
+ label="Select a part",
71
+ options=supported_parts,
72
+ format_func=lambda x: x.upper(),
73
+ key="converter_part_select",
74
+ )
75
+
76
+ converter = eval(f"adijif.{sb}()") # noqa: S307
77
+
78
+ # Show diagram
79
+ with st.expander(label="Diagram", expanded=True):
80
+ if sb not in self.part_images:
81
+ if converter.converter_type.lower() == "adc":
82
+ self.part_images[sb] = draw_adc(converter)
83
+ # FIXME: State is not being saved
84
+ else:
85
+ self.part_images[sb] = draw_dac(converter)
86
+ st.image(self.part_images[sb], width="stretch")
87
+
88
+ # Datapath Configuration
89
+ with st.expander("Datapath Configuration", expanded=True):
90
+ decimation = gen_datapath(converter)
91
+
92
+ scalar = st.selectbox("Units", options=["Hz", "kHz", "MHz", "GHz"], index=3)
93
+ if scalar == "Hz":
94
+ scalar_value = 1
95
+ new_default = 1e9
96
+ min_value = 1
97
+ max_value = 28e9
98
+ elif scalar == "kHz":
99
+ scalar_value = 1e3
100
+ new_default = 1e6
101
+ min_value = 100
102
+ max_value = 28e6
103
+ elif scalar == "MHz":
104
+ scalar_value = 1e6
105
+ new_default = 1e3
106
+ min_value = 1e3
107
+ max_value = 28e3
108
+ elif scalar == "GHz":
109
+ scalar_value = 1e9
110
+ new_default = 1
111
+ min_value = 0.1
112
+ max_value = 28
113
+
114
+ min_value = float(min_value)
115
+ max_value = float(max_value)
116
+ new_default = float(new_default)
117
+
118
+ converter_rate = st.number_input(
119
+ f"Converter Rate ({scalar})",
120
+ value=new_default,
121
+ min_value=min_value,
122
+ max_value=max_value,
123
+ )
124
+ converter_rate = scalar_value * converter_rate
125
+ converter.sample_clock = converter_rate / decimation
126
+
127
+ # Derived settings
128
+ dict_data = {
129
+ "Derived Setting": ["Sample Rate (MSPS)"],
130
+ "Value": [converter.sample_clock / 1e6],
131
+ }
132
+ df = pd.DataFrame.from_dict(dict_data)
133
+ st.dataframe(df, hide_index=True, width="stretch")
134
+
135
+ cols = st.columns(2, border=True)
136
+
137
+ # JESD204 Configuration Inputs
138
+ options, all_modes = get_jesd_controls(converter)
139
+ selections = {}
140
+
141
+ with cols[0]:
142
+ st.subheader("Configuration")
143
+
144
+ for option in options:
145
+ selections[option] = st.multiselect(
146
+ option, options[option]
147
+ ) # noqa: S608
148
+
149
+ # Output table of valid modes and calculate clocks
150
+ selections = {k: v for k, v in selections.items() if v != []}
151
+ modes_all_info, found_modes = get_valid_jesd_modes(
152
+ converter, all_modes, selections
153
+ )
154
+
155
+ with cols[1]:
156
+ st.subheader("JESD204 Modes")
157
+
158
+ if found_modes:
159
+ # Create formatted table of modes
160
+
161
+ # Convert to DataFrame so we can change orientation
162
+ df = pd.DataFrame(modes_all_info)
163
+
164
+ show_valid = st.toggle("Show only valid modes", value=True)
165
+ if show_valid:
166
+ df = df[df["Valid"] == "Yes"]
167
+ df = df.drop(columns=["Valid"])
168
+
169
+ # Create new index column and move mode to separate column
170
+ df["Mode"] = df.index
171
+ df = df.reset_index(drop=True)
172
+ # Make mode first column
173
+ cols = df.columns.tolist()
174
+ cols = cols[-1:] + cols[:-1]
175
+ df = df[cols]
176
+
177
+ # Change jesd_class column name to be JESD204 Class
178
+ df = df.rename(columns={"jesd_class": "JESD204 Class"})
179
+ df = df.rename(columns={"Mode": "Quickset Mode"})
180
+
181
+ # Change data in jesd_class column to be more human readable
182
+ df["JESD204 Class"] = df["JESD204 Class"].replace(
183
+ {"jesd204b": "204B", "jesd204c": "204C"}
184
+ )
185
+
186
+ # Remove any blank rows (rows where all values are NaN or empty)
187
+ df = df.dropna(how="all")
188
+ df = df[df.astype(str).ne("").any(axis=1)]
189
+
190
+ to_disable = df.columns
191
+ # Use fixed height with scrolling for consistent display
192
+ # Calculate dynamic height but cap it for better UX
193
+ min_height = 150
194
+ max_height = 600
195
+ row_height = 35
196
+ calculated_height = min(
197
+ max(len(df) * row_height + 38, min_height), max_height
198
+ )
199
+
200
+ st.data_editor( # noqa: F841
201
+ df,
202
+ width="stretch",
203
+ disabled=to_disable,
204
+ hide_index=True,
205
+ height=calculated_height,
206
+ )
207
+
208
+ else:
209
+ st.write("No modes found")