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.
- adijif/__init__.py +2 -2
- adijif/clocks/ad9523_1_bf.py +3 -3
- adijif/clocks/ad9528_bf.py +1 -1
- adijif/clocks/hmc7044_bf.py +1 -1
- adijif/clocks/ltc6952.py +2 -2
- adijif/clocks/ltc6952_bf.py +1 -1
- adijif/clocks/ltc6953.py +2 -2
- adijif/converters/__init__.py +12 -1
- adijif/converters/ad9081.py +1 -1
- adijif/converters/ad9081_dp.py +2 -0
- adijif/converters/ad9084.py +29 -7
- adijif/converters/ad9084_draw.py +20 -14
- adijif/converters/ad9084_util.py +24 -5
- adijif/converters/ad9088_dp.py +111 -0
- adijif/converters/converter.py +153 -2
- adijif/draw.py +11 -2
- adijif/fpgas/xilinx/__init__.py +4 -2
- adijif/jesd.py +3 -1
- adijif/plls/adf4030.py +6 -1
- adijif/plls/adf4382.py +0 -1
- adijif/solvers.py +5 -4
- adijif/system.py +16 -1
- adijif/tools/explorer/cli.py +36 -0
- adijif/tools/explorer/main.py +41 -0
- adijif/tools/explorer/src/pages/__init__.py +16 -0
- adijif/tools/explorer/src/pages/clockconfigurator.py +193 -0
- adijif/tools/explorer/src/pages/helpers/datapath.py +85 -0
- adijif/tools/explorer/src/pages/helpers/drawers.py +87 -0
- adijif/tools/explorer/src/pages/helpers/jesd.py +140 -0
- adijif/tools/explorer/src/pages/jesdmodeselector.py +209 -0
- adijif/tools/explorer/src/pages/systemconfigurator.py +252 -0
- adijif/tools/explorer/src/state.py +141 -0
- adijif/tools/explorer/src/utils.py +37 -0
- adijif/types.py +23 -4
- {pyadi_jif-0.1.0.dist-info → pyadi_jif-0.1.1.dist-info}/METADATA +36 -12
- {pyadi_jif-0.1.0.dist-info → pyadi_jif-0.1.1.dist-info}/RECORD +41 -30
- pyadi_jif-0.1.1.dist-info/entry_points.txt +2 -0
- adijif/d2/__init__.py +0 -26
- adijif/d2/d2lib.h +0 -81
- {pyadi_jif-0.1.0.dist-info → pyadi_jif-0.1.1.dist-info}/WHEEL +0 -0
- {pyadi_jif-0.1.0.dist-info → pyadi_jif-0.1.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {pyadi_jif-0.1.0.dist-info → pyadi_jif-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {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")
|