Skip to content

Commit 341224f

Browse files
committed
test(hw): add RX data-capture smoke test to every hw test
``assert_jesd_links_data`` verifies the JESD204 link reports ``Link status: DATA``, but that only says the transport reached the data state at link-up — drivers can probe cleanly, IIO devices can appear, the link can show DATA, and yet no samples actually arrive through the AXI-DMA path. Silent "drivers look OK, buffer comes back all-zero / constant" is the single most common CI failure mode and the existing assertions miss it completely. Add ``assert_rx_capture_valid(device, n_samples=2**12, min_std=1.0, context="")`` to ``test/hw/hw_helpers.py``. Takes any pyadi-iio RX-capable instance, configures the buffer size, calls ``rx()``, and asserts: - Every channel delivered exactly ``n_samples``. - At least one channel is not all-zero (DMA actually transferred bytes). - At least one channel's ``|std|`` is ``>= min_std`` LSBs (samples vary; a latched converter or a disconnected FIFO does not clear this even at one LSB). Wire it into every hw test that already has IIO verification, using the chip-appropriate pyadi-iio class: - ``test_ad9081_zcu102_xsa_hw.py`` → ``adi.ad9081`` - ``test_ad9081_zcu102_system_hw.py`` → ``adi.ad9081`` - ``test_adrv9009_zcu102_hw.py`` → ``adi.adrv9009`` - ``test_adrv9371_zc706_hw.py`` → ``adi.ad9371`` - ``test_fmcdaq3_vcu118_hw.py`` → ``adi.daq3`` Each site pulls the IP address out of the existing ``open_iio_context(shell)`` return tuple so no extra boot traffic is needed. The capture happens after ``assert_jesd_links_data`` so a link-down regression still surfaces on the clearer JESD check rather than an opaque "samples look wrong" failure.
1 parent d6abe63 commit 341224f

6 files changed

Lines changed: 105 additions & 4 deletions

test/hw/hw_helpers.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,75 @@ def assert_jesd_links_data(
521521
return rx_status, tx_status
522522

523523

524+
def assert_rx_capture_valid(
525+
device,
526+
n_samples: int = 2**12,
527+
min_std: float = 1.0,
528+
context: str = "",
529+
) -> list:
530+
"""Capture ``n_samples`` from a pyadi-iio RX device and verify data is flowing.
531+
532+
Covers the "IIO device probed but no samples actually arrive" failure
533+
mode: the JESD204 link reports DATA, drivers probe cleanly, IIO
534+
devices appear, but the DMA / JESD transport / clock path silently
535+
stops delivering samples. ``rx()`` returns a buffer, but every
536+
sample is zero, or every sample is the same value.
537+
538+
Asserts:
539+
540+
- Every channel delivers exactly ``n_samples`` samples.
541+
- At least one channel is not all-zero (DMA actually transferred
542+
bytes).
543+
- At least one channel's |std| is ``>= min_std`` LSBs (samples
544+
actually vary — noise floor alone clears this threshold easily,
545+
but a latched converter does not).
546+
547+
Args:
548+
device: pyadi-iio Rx-capable instance (e.g. ``adi.ad9081``,
549+
``adi.adrv9009``, ``adi.daq3``).
550+
n_samples: buffer depth for the capture.
551+
min_std: minimum |std| across all channels, in raw-LSB units.
552+
context: tag prepended to assertion-failure messages.
553+
554+
Returns:
555+
The per-channel captured arrays, as returned by ``device.rx()``
556+
but always wrapped into a list of ``numpy.ndarray``.
557+
"""
558+
import numpy as np
559+
560+
device.rx_buffer_size = n_samples
561+
data = device.rx()
562+
channels = data if isinstance(data, list) else [data]
563+
channels = [np.asarray(ch) for ch in channels]
564+
565+
suffix = f" ({context})" if context else ""
566+
assert channels, f"rx() returned no channels{suffix}"
567+
for i, ch in enumerate(channels):
568+
assert ch.size == n_samples, (
569+
f"rx channel {i}{suffix}: got {ch.size} samples, expected "
570+
f"{n_samples}"
571+
)
572+
573+
nonzero = [i for i, ch in enumerate(channels) if np.any(ch != 0)]
574+
assert nonzero, (
575+
f"All {len(channels)} rx channel(s) returned zero samples{suffix}"
576+
" — JESD/DMA/clock path is likely stalled."
577+
)
578+
579+
stds = [float(np.abs(ch).std()) for ch in channels]
580+
max_std = max(stds)
581+
assert max_std >= min_std, (
582+
f"All rx channels latched to a constant value{suffix} "
583+
f"(max |std|={max_std:.3g} < {min_std}) — data path stuck."
584+
)
585+
586+
print(
587+
f"rx capture{suffix}: {len(channels)} channel(s), {n_samples} samples, "
588+
f"non-zero={nonzero}, max |std|={max_std:.2f}"
589+
)
590+
return channels
591+
592+
524593
def _kernel_cache_key(platform_arch: str, config_path: Path) -> str:
525594
"""Return a short sha256 over *platform_arch* and the config file bytes."""
526595
import hashlib

test/hw/test_ad9081_zcu102_system_hw.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
assert_jesd_links_data,
4848
assert_no_kernel_faults,
4949
assert_no_probe_errors,
50+
assert_rx_capture_valid,
5051
collect_dmesg,
5152
compile_dts_to_dtb,
5253
deploy_and_boot,
@@ -272,7 +273,7 @@ def test_ad9081_zcu102_system_hw(board, built_kernel_image_zynqmp, tmp_path):
272273
"AD9081 probe signature was not found in kernel dmesg output"
273274
)
274275

275-
ctx, _ = open_iio_context(shell)
276+
ctx, ip_address = open_iio_context(shell)
276277

277278
found = [d.name for d in ctx.devices]
278279
assert "hmc7044" in found, (
@@ -294,3 +295,8 @@ def test_ad9081_zcu102_system_hw(board, built_kernel_image_zynqmp, tmp_path):
294295
)
295296
print(f"$ cat .../84a90000.axi?jesd204?rx/status\n{rx_status}")
296297
print(f"$ cat .../84b90000.axi?jesd204?tx/status\n{tx_status}")
298+
299+
# Data-path smoke test: capture a real buffer and verify samples flow.
300+
import adi # noqa: PLC0415
301+
ad9081 = adi.ad9081(uri=f"ip:{ip_address}")
302+
assert_rx_capture_valid(ad9081, n_samples=2**12, context="ad9081 system")

test/hw/test_ad9081_zcu102_xsa_hw.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
assert_jesd_links_data,
3737
assert_no_kernel_faults,
3838
assert_no_probe_errors,
39+
assert_rx_capture_valid,
3940
collect_dmesg,
4041
compile_dts_to_dtb,
4142
deploy_and_boot,
@@ -190,7 +191,7 @@ def test_ad9081_zcu102_xsa_hw(board, built_kernel_image_zynqmp, tmp_path):
190191
"AD9081 probe signature was not found in kernel dmesg output"
191192
)
192193

193-
ctx, _ = open_iio_context(shell)
194+
ctx, ip_address = open_iio_context(shell)
194195

195196
found = {d.name for d in ctx.devices if d.name}
196197
assert "hmc7044" in found, (
@@ -212,3 +213,8 @@ def test_ad9081_zcu102_xsa_hw(board, built_kernel_image_zynqmp, tmp_path):
212213
)
213214
print(f"$ cat .../84a90000.axi?jesd204?rx/status\n{rx_status}")
214215
print(f"$ cat .../84b90000.axi?jesd204?tx/status\n{tx_status}")
216+
217+
# Data-path smoke test: capture a real buffer and verify samples flow.
218+
import adi # noqa: PLC0415
219+
ad9081 = adi.ad9081(uri=f"ip:{ip_address}")
220+
assert_rx_capture_valid(ad9081, n_samples=2**12, context="ad9081 xsa")

test/hw/test_adrv9009_zcu102_hw.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
assert_jesd_links_data,
4040
assert_no_kernel_faults,
4141
assert_no_probe_errors,
42+
assert_rx_capture_valid,
4243
collect_dmesg,
4344
compile_dts_to_dtb,
4445
deploy_and_boot,
@@ -220,7 +221,7 @@ def test_adrv9009_zcu102_hw(board, built_kernel_image_zynqmp, tmp_path):
220221
"ADRV9009 phy probe signature was not found in kernel dmesg output"
221222
)
222223

223-
ctx, _ = open_iio_context(shell)
224+
ctx, ip_address = open_iio_context(shell)
224225

225226
found = [d.name for d in ctx.devices]
226227
expected = {
@@ -240,6 +241,11 @@ def test_adrv9009_zcu102_hw(board, built_kernel_image_zynqmp, tmp_path):
240241
print(f"$ cat .../axi?jesd204?rx/status\n{rx_status}")
241242
print(f"$ cat .../axi?jesd204?tx/status\n{tx_status}")
242243

244+
# --- 8b. Data-path smoke test: capture a real RX buffer. ---
245+
import adi # noqa: PLC0415
246+
adrv = adi.adrv9009(uri=f"ip:{ip_address}")
247+
assert_rx_capture_valid(adrv, n_samples=2**12, context="adrv9009 initial boot")
248+
243249
# --- 9. Load all four canonical Talise filter profiles ---
244250
# The remote libiio write path drops its TCP socket when the driver
245251
# holds the CPU for Talise re-init, surfacing as ``BrokenPipeError``.

test/hw/test_adrv9371_zc706_hw.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
acquire_xsa,
3838
assert_no_kernel_faults,
3939
assert_no_probe_errors,
40+
assert_rx_capture_valid,
4041
collect_dmesg,
4142
compile_dts_to_dtb,
4243
deploy_and_boot,
@@ -143,7 +144,7 @@ def test_adrv9371_zc706_xsa_hw(board, built_kernel_image_zynq, tmp_path):
143144
"AD9371 driver probe signature not found in dmesg"
144145
)
145146

146-
ctx, _ = open_iio_context(shell)
147+
ctx, ip_address = open_iio_context(shell)
147148
found = {d.name for d in ctx.devices if d.name}
148149
assert any("9371" in n or "adrv9" in n.lower() for n in found), (
149150
f"No AD9371/ADRV9xxx IIO device found. Devices: {sorted(found)}"
@@ -152,6 +153,11 @@ def test_adrv9371_zc706_xsa_hw(board, built_kernel_image_zynq, tmp_path):
152153
f"No AD9528 clock device found. Devices: {sorted(found)}"
153154
)
154155

156+
# Data-path smoke test: capture a real AD9371 RX buffer.
157+
import adi # noqa: PLC0415
158+
ad9371 = adi.ad9371(uri=f"ip:{ip_address}")
159+
assert_rx_capture_valid(ad9371, n_samples=2**12, context="ad9371 xsa")
160+
155161

156162
# ---------------------------------------------------------------------------
157163
# System API test

test/hw/test_fmcdaq3_vcu118_hw.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@
3737
DEFAULT_OUT_DIR,
3838
assert_no_kernel_faults,
3939
assert_no_probe_errors,
40+
assert_rx_capture_valid,
4041
collect_dmesg,
42+
open_iio_context,
4143
)
4244

4345

@@ -65,6 +67,12 @@ def test_fmcdaq3_vcu118_boot_hw(board):
6567

6668
_assert_iio_devices(shell)
6769

70+
# Data-path smoke test: capture a real AD9680 RX buffer.
71+
_, ip_address = open_iio_context(shell)
72+
import adi # noqa: PLC0415
73+
daq3 = adi.daq3(uri=f"ip:{ip_address}")
74+
assert_rx_capture_valid(daq3, n_samples=2**12, context="fmcdaq3 boot")
75+
6876

6977
def _assert_iio_devices(shell) -> None:
7078
"""Fail unless the FMCDAQ3 converters show up under /sys/bus/iio."""

0 commit comments

Comments
 (0)