Skip to content

Commit 844f728

Browse files
authored
Merge pull request #218 from analogdevicesinc/tfcollins/web-jesd-basic
Tfcollins/web jesd basic
2 parents b2abbcb + 48e694c commit 844f728

3 files changed

Lines changed: 349 additions & 0 deletions

File tree

adijif/tools/explorer/src/pages/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44

55
from ..utils import Page
66
from .clockconfigurator import ClockConfigurator
7+
from .jesdbasic import JESDBasic
78
from .jesdmodeselector import JESDModeSelector
89
from .systemconfigurator import SystemConfigurator
910

1011
PAGE_MAP: Dict[str, Type[Page]] = {
1112
"JESD204 Mode Selector": JESDModeSelector,
1213
"Clock Configurator": ClockConfigurator,
1314
"System Configurator": SystemConfigurator,
15+
"Basic JESD204 Calculator": JESDBasic,
1416
}
1517

1618
__all__ = ["PAGE_MAP"]
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"""Basic JESD204 Calculator."""
2+
3+
from typing import Optional
4+
5+
import pandas as pd
6+
import streamlit as st
7+
8+
from ..utils import Page
9+
10+
11+
class JESDBasic(Page):
12+
"""Basic JESD204 calculator page."""
13+
14+
def __init__(self, state: Optional[object]) -> None:
15+
"""Initialize basic JESD204 calculator page.
16+
17+
Args:
18+
state: Application state object
19+
"""
20+
self.state = state
21+
22+
def write(self) -> None:
23+
"""Render the basic JESD204 calculator page."""
24+
st.title("Basic JESD204 Calculator")
25+
26+
# Horizontal line
27+
st.markdown("---")
28+
29+
# Add int boxes for L, M, Np, JESD Class
30+
jesd_params, output_table = st.columns(2)
31+
with jesd_params:
32+
st.header("JESD204 Parameters")
33+
34+
L_c, M_c = st.columns(2)
35+
36+
with L_c:
37+
L = st.number_input(
38+
"L (number of lanes)", min_value=1, step=1, value=4
39+
)
40+
with M_c:
41+
M = st.number_input(
42+
"M (number of converters)", min_value=1, step=1, value=4
43+
)
44+
45+
np_c, class_c = st.columns(2)
46+
with np_c:
47+
Np = st.number_input(
48+
"Np (Bits per sample)", min_value=1, step=1, value=16
49+
)
50+
51+
with class_c:
52+
jesd_class = st.selectbox(
53+
"JESD204 Class", ["JESD204B", "JESD204C"]
54+
)
55+
56+
label_c, value_c = st.columns(2)
57+
with label_c:
58+
clock_ref_source = st.selectbox(
59+
"Clock Reference Source", ["Sample Rate", "Lane Rate"]
60+
)
61+
62+
with value_c:
63+
if clock_ref_source == "Sample Rate":
64+
sample_rate = st.number_input(
65+
"Sample Rate (SPS)",
66+
min_value=1.0,
67+
step=1.0,
68+
value=100e6,
69+
)
70+
else:
71+
lane_rate = st.number_input(
72+
"Lane Rate (Gbps)", min_value=0.1, step=0.1, value=10.0
73+
)
74+
75+
if jesd_class == "JESD204B":
76+
encoding_factor = float(10) / float(
77+
8
78+
) # Assuming 16-bit samples for simplicity
79+
else:
80+
encoding_factor = float(66) / float(
81+
64
82+
) # Assuming 16-bit samples for simplicity
83+
84+
# Lane rate = (M * Np * Sample Rate * encoding_factor) / L
85+
if clock_ref_source == "Sample Rate":
86+
rate = (
87+
(M * Np * sample_rate * encoding_factor) / L / 1e9
88+
) # Convert to Gbps
89+
label = "Lane Rate (Gbps)"
90+
if jesd_class == "JESD204B":
91+
core_clock = rate / 40 * 1e3 # Convert to MHz
92+
else:
93+
core_clock = rate / 66 * 1e3 # Convert to MHz
94+
else:
95+
# lane_rate_gbps = lane_rate
96+
rate = (
97+
(lane_rate * 1e9 * L) / (M * Np * encoding_factor) / 1e6
98+
) # Convert to MSPS
99+
label = "Sample Rate (MSPS)"
100+
if jesd_class == "JESD204B":
101+
core_clock = lane_rate / 40 * 1e3 # Convert to MHz
102+
else:
103+
core_clock = lane_rate / 66 * 1e3 # Convert to MHz
104+
105+
with output_table:
106+
st.header("Derived Parameters")
107+
df = pd.DataFrame(
108+
{
109+
"Parameter": [label, "Core Clock (MHz)"],
110+
"Value": [rate, core_clock],
111+
}
112+
)
113+
st.table(df)

tests/tools/test_jesdbasic.py

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
"""Streamlit testing for Basic JESD204 Calculator page."""
2+
3+
import pathlib
4+
import sys
5+
import time
6+
7+
from streamlit.testing.v1 import AppTest
8+
9+
app_path = (
10+
pathlib.Path(__file__).parent.parent.parent
11+
/ "adijif"
12+
/ "tools"
13+
/ "explorer"
14+
)
15+
app_file_path = app_path / "main.py"
16+
17+
sys.path.append(str(app_path))
18+
19+
20+
def navigate_to_jesdbasic(at: AppTest) -> AppTest:
21+
"""Navigate the sidebar to the Basic JESD204 Calculator page."""
22+
sb = at.sidebar
23+
for item in sb.radio:
24+
if item.label == "Select a Tool":
25+
item.set_value("Basic JESD204 Calculator").run()
26+
break
27+
time.sleep(0.5)
28+
return at
29+
30+
31+
def test_jesdbasic_page_loads() -> None:
32+
"""Test that the Basic JESD204 Calculator page loads without errors."""
33+
at = AppTest.from_file(app_file_path).run()
34+
navigate_to_jesdbasic(at)
35+
36+
assert not at.exception
37+
assert len(at.title) > 0
38+
assert at.title[0].value == "Basic JESD204 Calculator"
39+
40+
41+
def test_jesdbasic_default_inputs_present() -> None:
42+
"""Test that default input widgets are present with expected defaults."""
43+
at = AppTest.from_file(app_file_path).run()
44+
navigate_to_jesdbasic(at)
45+
46+
labels = {ni.label: ni for ni in at.number_input}
47+
assert "L (number of lanes)" in labels, "L input not found"
48+
assert "M (number of converters)" in labels, "M input not found"
49+
assert "Np (Bits per sample)" in labels, "Np input not found"
50+
51+
assert labels["L (number of lanes)"].value == 4
52+
assert labels["M (number of converters)"].value == 4
53+
assert labels["Np (Bits per sample)"].value == 16
54+
55+
assert not at.exception
56+
57+
58+
def test_jesdbasic_jesd_class_selector() -> None:
59+
"""Test that the JESD204 Class selector is present with correct options."""
60+
at = AppTest.from_file(app_file_path).run()
61+
navigate_to_jesdbasic(at)
62+
63+
class_sel = None
64+
for sb in at.selectbox:
65+
if sb.label == "JESD204 Class":
66+
class_sel = sb
67+
break
68+
69+
assert class_sel is not None, "JESD204 Class selectbox not found"
70+
assert "JESD204B" in class_sel.options
71+
assert "JESD204C" in class_sel.options
72+
assert class_sel.value == "JESD204B"
73+
74+
assert not at.exception
75+
76+
77+
def test_jesdbasic_clock_ref_sample_rate_mode() -> None:
78+
"""Test that Sample Rate mode shows the Sample Rate input."""
79+
at = AppTest.from_file(app_file_path).run()
80+
navigate_to_jesdbasic(at)
81+
82+
# Default mode is Sample Rate — verify the sample rate input is visible
83+
labels = [ni.label for ni in at.number_input]
84+
assert any("Sample Rate" in lbl for lbl in labels), (
85+
"Sample Rate input not found in default mode"
86+
)
87+
88+
assert not at.exception
89+
90+
91+
def test_jesdbasic_clock_ref_lane_rate_mode() -> None:
92+
"""Test that switching to Lane Rate mode shows the Lane Rate input."""
93+
at = AppTest.from_file(app_file_path).run()
94+
navigate_to_jesdbasic(at)
95+
96+
for sb in at.selectbox:
97+
if sb.label == "Clock Reference Source":
98+
sb.set_value("Lane Rate").run()
99+
break
100+
101+
labels = [ni.label for ni in at.number_input]
102+
assert any("Lane Rate" in lbl for lbl in labels), (
103+
"Lane Rate input not found after switching Clock Reference Source"
104+
)
105+
106+
assert not at.exception
107+
108+
109+
def test_jesdbasic_output_table_present() -> None:
110+
"""Test that the Derived Parameters table is rendered."""
111+
at = AppTest.from_file(app_file_path).run()
112+
navigate_to_jesdbasic(at)
113+
114+
assert len(at.table) > 0, "Output table not found"
115+
116+
# Table should contain Lane Rate and Core Clock rows
117+
table_df = at.table[0].value
118+
params = list(table_df["Parameter"])
119+
assert "Lane Rate (Gbps)" in params, (
120+
"Lane Rate row missing from output table"
121+
)
122+
assert "Core Clock (MHz)" in params, (
123+
"Core Clock row missing from output table"
124+
)
125+
126+
assert not at.exception
127+
128+
129+
def test_jesdbasic_lane_rate_calculation_sample_rate_mode() -> None:
130+
"""Test lane rate calculation in Sample Rate mode with known values."""
131+
at = AppTest.from_file(app_file_path).run()
132+
navigate_to_jesdbasic(at)
133+
134+
# Set known values: L=4, M=4, Np=16, Sample Rate=1e8 SPS, JESD204B
135+
# Expected lane rate = (4 * 16 * 1e8 * 10/8) / 4 / 1e9 = 2.0 Gbps
136+
for ni in at.number_input:
137+
if ni.label == "Sample Rate (SPS)":
138+
ni.set_value(1e8).run()
139+
break
140+
141+
assert not at.exception
142+
table_df = at.table[0].value
143+
row = table_df[table_df["Parameter"] == "Lane Rate (Gbps)"]
144+
assert len(row) == 1
145+
assert abs(float(row["Value"].iloc[0]) - 2.0) < 1e-6
146+
147+
148+
def test_jesdbasic_sample_rate_calculation_lane_rate_mode() -> None:
149+
"""Test sample rate calculation in Lane Rate mode with known values."""
150+
at = AppTest.from_file(app_file_path).run()
151+
navigate_to_jesdbasic(at)
152+
153+
for sb in at.selectbox:
154+
if sb.label == "Clock Reference Source":
155+
sb.set_value("Lane Rate").run()
156+
break
157+
158+
# Set lane rate to 10 Gbps with defaults L=4, M=4, Np=16, JESD204B
159+
# Expected sample rate = (10e9 * 4) / (4 * 16 * 10/8) / 1e6 = 500 MSPS
160+
for ni in at.number_input:
161+
if ni.label == "Lane Rate (Gbps)":
162+
ni.set_value(10.0).run()
163+
break
164+
165+
assert not at.exception
166+
table_df = at.table[0].value
167+
row = table_df[table_df["Parameter"] == "Sample Rate (MSPS)"]
168+
assert len(row) == 1
169+
assert abs(float(row["Value"].iloc[0]) - 500.0) < 1e-6
170+
171+
172+
def test_jesdbasic_jesd204c_encoding() -> None:
173+
"""Test that switching to JESD204C uses 66/64 encoding factor."""
174+
at = AppTest.from_file(app_file_path).run()
175+
navigate_to_jesdbasic(at)
176+
177+
for sb in at.selectbox:
178+
if sb.label == "JESD204 Class":
179+
sb.set_value("JESD204C").run()
180+
break
181+
182+
assert not at.exception
183+
184+
# With L=4, M=4, Np=16, SR=1e8, JESD204C (66/64 encoding):
185+
# lane rate = (4 * 16 * 1e8 * 66/64) / 4 / 1e9 = 1.65625 Gbps
186+
for ni in at.number_input:
187+
if ni.label == "Sample Rate (SPS)":
188+
ni.set_value(1e8).run()
189+
break
190+
191+
assert not at.exception
192+
table_df = at.table[0].value
193+
row = table_df[table_df["Parameter"] == "Lane Rate (Gbps)"]
194+
assert len(row) == 1
195+
expected = (4 * 16 * 1e8 * 66 / 64) / 4 / 1e9
196+
assert abs(float(row["Value"].iloc[0]) - expected) < 1e-6
197+
198+
199+
def test_jesdbasic_core_clock_jesd204b() -> None:
200+
"""Test core clock calculation for JESD204B."""
201+
at = AppTest.from_file(app_file_path).run()
202+
navigate_to_jesdbasic(at)
203+
204+
for ni in at.number_input:
205+
if ni.label == "Sample Rate (SPS)":
206+
ni.set_value(1e8).run()
207+
break
208+
209+
assert not at.exception
210+
211+
# With lane rate = 2.0 Gbps, JESD204B core clock = 2.0 / 40 * 1e3 = 50 MHz
212+
table_df = at.table[0].value
213+
row = table_df[table_df["Parameter"] == "Core Clock (MHz)"]
214+
assert len(row) == 1
215+
assert abs(float(row["Value"].iloc[0]) - 50.0) < 1e-6
216+
217+
218+
def test_jesdbasic_no_exception_on_param_changes() -> None:
219+
"""Test that changing L, M, Np inputs produces no exceptions."""
220+
at = AppTest.from_file(app_file_path).run()
221+
navigate_to_jesdbasic(at)
222+
223+
for label, value in [
224+
("L (number of lanes)", 8),
225+
("M (number of converters)", 2),
226+
("Np (Bits per sample)", 12),
227+
]:
228+
for ni in at.number_input:
229+
if ni.label == label:
230+
ni.set_value(value).run()
231+
assert not at.exception, (
232+
f"Exception when setting {label}={value}"
233+
)
234+
break

0 commit comments

Comments
 (0)