Re-arch: typed devices + hardware CI + ADRV9371/AD9081/FMCDAQ3 coverage#56
Merged
Re-arch: typed devices + hardware CI + ADRV9371/AD9081/FMCDAQ3 coverage#56
Conversation
ADI Binding Audit Report
Undocumented bindings
|
642edde to
9ddc72a
Compare
Refactor the legacy board/parts/template layers into a typed device model and add the plumbing for running hw tests through a labgrid coordinator as well as the existing direct-mode (exporter-on-DUT) path. - ``adidt.devices.*`` typed device model replacing the old ``boards`` / ``parts`` / ``templates`` stack. - ``test/hw/conftest.py`` that drives either connection mode from a single ``board`` fixture — labgrid picks the mode from ``LG_COORDINATOR`` (coordinator) or ``LG_ENV`` only (direct). - ``pytest-dotenv`` so the mode vars can live in ``.env``; a ``.env.example`` template documents every knob. - AD9081 and ADRV9009 hw test skip guards accept either ``LG_COORDINATOR`` or ``LG_ENV`` to enable the test. - Design spec at ``doc/source/developer/`` capturing the coordinator-vs-direct architecture.
End-to-end ADRV9371-FMC on ZC706 coverage: - ``adidt.fpga.zc706`` and ``adidt.eval.adrv937x_fmc`` device models. - ``adidt.xsa.builders.adrv9009.ADRV937xBuilder`` for the XSA pipeline. Unit tests + topology fixture exercise the builder. - ``test/hw/test_adrv9371_zc706_hw.py`` drives real hardware via the declarative System API and the XSA pipeline (dual coverage). - Fixes to the emitted DT overlays and the ZC706 GPIO label so the generated DTB actually boots the board. - Developer-guide pages documenting the new builder + FPGA board, and the coordinator-mode hw flow.
Add a file-based kernel cache keyed by sha256 of the pyadi-build config YAML so consecutive hw test runs skip the ``prepare_source`` + ``build`` cycle (typically ~8 minutes). Wrap Zynq-7000 ``zImage`` output with ``mkimage`` so the cached filename matches what ``BootFPGASoCTFTP``'s ``tftpboot uImage`` expects. Session-scope the ``built_kernel_image_*`` fixtures so the cached path is shared across all hw tests in one pytest run. Cache controls via env vars: - ``ADIDT_KERNEL_CACHE=0`` forces a rebuild. - ``ADIDT_KERNEL_CACHE_DIR`` relocates the cache (default ``~/.cache/adidt/kernel``). Measured: ADRV9371+ZC706 hw test dropped from ~600s to ~70s on a cache hit — the kernel build dominates the uncached runtime.
Real-hardware coverage for the XSA-pipeline path on ZCU102 + AD9081 and the DT-emission corrections the hardware exposed: - ``test/hw/test_ad9081_zcu102_xsa_hw.py`` — XSA → sdtgen → merged DTS → DTB → boot → IIO + JESD204 link verification. - HMC7044 PLL / SD-card / SPI wiring corrections for the AD9081-FMCA-EBZ + ZCU102 combination. - AD9081 DT fixes: lane-mapping direction, DAC/ADC property completeness (crossbar-select, nco-frequency-shift-hz, gain), and alignment with the upstream analogdevicesinc/linux reference DT (rx_link_mode=10, tx_link_mode=9, CLKIN1=30.72 MHz, tpl-phase-adjust=3).
Prebuilt-image smoke test that drives FMCDAQ3 on VCU118 via the coordinator path and ``BootFabric`` (MicroBlaze JTAG boot). Documents the new test alongside the AD9081 XSA hw test and the kernel caching feature in the developer guide.
Land a new standalone developer walkthrough at ``doc/source/developer/authoring_devices.rst`` aimed at contributors extending the declarative device layer. The existing coverage (`api/devices` "Writing a new device" bullets, `xsa_developer.rst` black-box reference to the device layer) was too thin for a first-time contributor to add a clock, converter, or eval-board class without reverse-engineering from source. The new guide (11 sections) covers: - Class hierarchy at a glance — Device / ClockDevice / ConverterDevice / FpgaBoard plus the Port / ClockOutput / GtLane / SpiMaster plumbing. - End-to-end call flow from ``System.generate_dts()`` through ``to_board_model`` → per-device ``render_dt`` → ``render_node`` field walk → ``BoardModelRenderer`` aggregation. - Anatomy of a Device: ``part`` / ``role`` / ``label``, the ClassVars consumed by ``render_node`` (``compatible``, ``dt_header``, ``dt_flags``), pydantic ``Field(alias="adi,...")``, the ``DtSkip`` / ``DtSubnodes`` / ``DtBits64`` markers, and the ``extra_dt_lines`` / ``trailing_blocks`` hooks with guidance on which to override when. - Port & clock-output plumbing — when ``SpiPort`` / ``SpiMaster`` / ``ClockOutput`` / ``GtLane`` are instantiated, how ``System._spi_location`` resolves a device's bus / CS, and the named-alias pattern EvalBoards use. - Converter shapes — MxFE with ``.adc`` / ``.dac`` sides (``ConverterSide`` + ``Jesd204Settings`` + ``MODE_TABLE``) vs. single-transducer (ADRV9009 / AD9371) vs. single-side parts (AD9172 / AD9680) — with pointers to the representative source files and guidance on which template to start from. - Three cookbook recipes — adding a clock, adding a converter / transceiver, and adding an eval board or FPGA board — each citing real files (``adidt/devices/clocks/hmc7044.py``, ``adidt/devices/converters/ad9081.py``, ``adidt/eval/ad9081_fmc.py``, ``adidt/fpga/zcu102.py``, …) with minimal inline excerpts so the doc stays in sync with the code. - Bridging to the XSA pipeline — how a new device class is picked up by an ``XsaBuilder`` and feeds the same renderer, with a pointer back to ``xsa_developer.rst`` for pipeline authoring. - Testing layers — device unit test, System-API smoke test, and XSA builder / golden-file regression tests, each with a concrete ``test/…`` file to copy. Also: - ``doc/source/developer/index.rst`` — tiny nested toctree page that gives the developer/ subdirectory a home in the main "Developer Guide" navigation section. - ``doc/source/index.rst`` — swap the former bullet to ``:doc:\`developer/authoring_devices\``` and add ``developer/index`` to the Developer Guide toctree. - ``doc/source/api/devices.rst`` — collapse the old six-bullet "Writing a new device" section to a paragraph pointing at the new guide. - ``doc/source/xsa_developer.rst`` — the Declarative-devices lead-in and the Step-5-wire-into-builder section both gain a cross-reference to the new authoring guide. Sphinx build is clean; all 20 source-file citations and all 7 test-file citations resolve to files that exist on disk.
Full hardware CI plumbing:
- ``.github/workflows/hardware-test.yml`` — preflight job that probes
the labgrid coordinator, then a direct-mode matrix and a
coordinator-mode matrix fanned out over the available nodes
listed in ``.github/hw-nodes.json``. The workflow has no
hardcoded board names; adding a node means one manifest entry +
one runner.
- ``.github/hw-nodes.json`` declarative manifest mapping place →
runner label → env yaml → test list.
- ``.github/scripts/register-hw-runners.sh`` — bulk-register the
self-hosted runners (one per lab host plus the coordinator host),
ship a fresh token via ``gh api``, and poll ``/actions/runners``
until every registered runner is online.
- ``env_remote_{bq,mini2,nuc,all}.yaml`` — RemotePlace-only
coordinator-mode env YAMLs; safe-to-commit (no SSH creds, no
local paths).
Make the Hardware Tests workflow self-contained on the self-hosted runners: - Run ``preflight`` on the ``hw-coordinator`` self-hosted runner so the labgrid-client probe has a route to the private-lab coordinator (GitHub-hosted runners can't reach 10.0.0.41:20408). - Robust ``labgrid-client`` discovery: probe several common install paths, surface which binary is being used, and log full ``labgrid-client places`` output for diagnostics. - Use ``astral-sh/uv`` to manage venvs on every hw-node runner, sidestepping PEP 668 on Debian/Ubuntu. Two helper scripts (``.github/scripts/bootstrap-uv.sh``, ``install-adidt-venv.sh``) keep the workflow YAML compact. - Pull pytsk3 through ``adi-labgrid-plugins[kuiper]``, document the ``python3-venv`` prerequisite, and plumb a ``PYADI_BUILD_TOKEN`` PAT through a process-local ``GIT_CONFIG_COUNT`` triple so ``uv pip install`` can clone the private ``tfcollins/pyadi-build`` dependency without leaving the secret in ``~/.gitconfig``.
Run each hw-coord matrix leg on the same per-node self-hosted runner as hw-direct so XSA-pipeline tests have access to the local Vivado toolchain (sdtgen, xsct, xsdb) — the coordinator host lacks Vivado and silently skipped every XSA-dependent test there. Source ``/tools/Xilinx/2025.1/Vivado/settings64.sh`` before pytest (with ``set -u`` toggled so the script's unset PYTHONPATH reference doesn't abort). Wrap each hw-coord pytest invocation in explicit labgrid-client ``acquire`` / ``release`` calls so the RemotePlace flow doesn't fail at ``UserError: place <X> is not acquired``. Skip hw-direct with a green job when ``LG_DIRECT_ENV`` isn't set on a runner — direct-mode requires a node-local yaml authored by the lab owner; until that exists the job is a no-op (the same tests still run via hw-coord). Drop the custom ``hardware-tests`` GitHub environment and rely on GitHub Actions' built-in *Approval for outside collaborators* gate for fork-PR protection. Same-repo PRs, pushes to main, and workflow_dispatch bypass the gate so trusted authors don't rubber-stamp their own runs. Ship the ADRV9371+ZC706 XSA as a committed test fixture (``test/hw/xsa/system_top_adrv9371_zc706.xsa``) now that the 2023_R2_P1 Kuiper CDN URL it used to fetch from returns 404.
Three correctness gaps in the declarative System-API path that made the AD9081+ZCU102 driver fail to probe on real hardware: - ``System._find_sink_reference_clock`` only matched links whose endpoint was the parent ``ConverterDevice``. MxFE parts target their ``adc`` / ``dac`` sides instead, so the lookup returned ``None`` and the AD9081 DT node lost its ``clocks = <&hmc7044 N>`` + ``clock-names = "dev_clk"`` pair, producing ``ENOENT`` at kernel probe. Now matches against the converter or either side. - Eval board HMC7044 ``pll2_output_hz`` set to ``vcxo * 25`` (3072 MHz), giving DEV_REFCLK = 768 MHz. The AD9081 internal PLL can't hit 12 GHz DAC clock from 768 MHz (non-integer multiplier). Pin at 3000 MHz matching the XSA builder. - ``test_ad9081_zcu102_system_hw`` called ``converter.set_jesd204_mode(rx_mode, rx_class)`` which applied the SAME mode to both sides; DAC's MODE_TABLE lookup missed, framing params stayed at defaults (L=0). Apply ``rx_mode`` to ADC and ``tx_mode`` to DAC separately.
Two DT-emission correctness gaps the AD9081+ZCU102 System-API hw
test surfaced on real hardware:
- ``_AD9081_{RX,TX}_MODE_TABLE`` had ``M=4`` / ``F=2`` where the
reference DTS + AD9081 API mode table use ``M=8`` / ``F=4`` for
jesd204b mode-10 (JTX) and mode-9 (JRX). The kernel driver
programmed the TPL + ADXCVR for fewer converters than the part's
jtx actually frames; JESD PLL never locked ("ad9081 ...: JESD PLL
is not locked. Failed to initialize: -24"). Correct to
``M=8, L=4, F=4``.
- ``ad9081_fmc`` eval-board HMC7044 channel dividers diverged from
the XSA builder's values. With PLL2 at 3000 MHz they produced
375 MHz on CORE_CLK_*, 750 MHz on DEV_REFCLK — the kernel
reported a 250 MHz JESD link clock and the link stayed disabled
("Link is disabled / Measured Link Clock: 375.029 MHz"). Sync
dividers to ``(12, 12, 1536, 12, 6, 12, 6, 1536)`` matching the
XSA builder and the stock Kuiper
``zynqmp-zcu102-rev10-ad9081-m8-l4`` reference DTS.
Fast offline debug loop for System-API vs XSA-pipeline DT-emission
divergences — every bug chased in this branch is now a <1 s unit
test failure instead of a 5 min hw-coord CI round-trip.
- ``adidt.tools.dts_inspect`` — grep-based DT parser keyed by
"grouping ancestor" (``ad9081:``, ``tx-dacs:``, ``rx-adcs:``,
``channel@N:``) with a ``compare_properties`` diff helper.
- ``adidt.tools.dts_compare_cli`` — ``python3 -m`` CLI that diffs
two DTSs on the curated ``KERNEL_CRITICAL_KEYS`` set.
- ``test/devices/fixtures/ad9081_zcu102_xsa_reference.dts`` —
committed reference DTS from a passing XSA-pipeline run.
- ``test/devices/test_system_ad9081_dts_parity.py`` — parametrizes
the XSA reference over ``KERNEL_CRITICAL_KEYS`` so any
per-property divergence is its own focused pytest failure.
- ``.github/workflows/hardware-test.yml`` — upload
``test/hw/output/*.{dts,pp.dts,dtb,dtbo,log}`` and workspace-root
``uart_log_*.txt`` as per-leg artifacts (retention 14 d), so
flaky boots and DT regressions can be post-mortem'd from the
Actions run page.
Wrap the ``board`` fixture's ``yield`` in try/finally so every hw test module exits with the board transitioned to ``powered_off``. Fallback path calls ``power.off()`` directly on the bound ``PowerProtocol`` if the strategy is in a broken state from a prior failure. Lab hardware is never left energised between runs. Document the complete Hardware CI design in ``doc/source/developer/hardware_ci.rst`` — preflight + matrix topology, the ``hardware-tests`` fork-PR gate, ``LG_DIRECT_ENV`` convention, private-dep PAT plumbing, Vivado ``settings64.sh`` sourcing, boot-reliability (pre-emptive cold-cycle + one-shot retry, tunable via strategy attrs), artifact bundles + local DTS parity test recipe, teardown semantics, and troubleshooting entries for the common failure modes.
…lper Ship redacted direct-mode labgrid YAML templates under ``doc/source/developer/samples/`` so the ``hw-direct (<place>)`` matrix legs can exercise the direct-mode test path without the lab owner hand-authoring the YAML from scratch: - ``lg_direct_mini2.yaml.example`` — AD9081 + ZCU102 on mini2 (CP2108 serial, USB SD-mux, MassStorageDevice partuuid, VeSync). - ``lg_direct_nuc.yaml.example`` — FMCDAQ3 + VCU118 on nuc (CP2105 serial, XilinxDeviceJTAG target IDs, XilinxVivadoTool, VeSync). Every host-specific path and credential is marked ``<FILL>``. The developer-guide walkthrough (``Direct-mode YAML templates`` sub- section of ``hardware_ci.rst``) covers the per-node bring-up: scp → fill placeholders from the existing exporter YAML on the host → ``LG_DIRECT_ENV`` + ``svc.sh`` restart → workflow_dispatch to verify the ``hw-direct (<place>)`` leg now runs instead of skipping. Narrow the root ``.gitignore``'s ``lg_*`` pattern with a ``!doc/source/developer/samples/lg_*.yaml.example`` un-ignore so the templates can be tracked while real ``lg_*`` YAMLs anywhere else still stay out of git. Also ignore a local operator helper ``/setup-direct-mode.sh`` (never tracked — pushes templates to the hosts, opens an editor, sets ``LG_DIRECT_ENV``, restarts the runner service).
74108b0 to
2a26fbc
Compare
Unit Test Results 3 files 3 suites 8s ⏱️ Results for commit 1d15664. ♻️ This comment has been updated with latest results. |
Kuiper64 Test Results625 tests 601 ✅ 2s ⏱️ Results for commit 1d15664. ♻️ This comment has been updated with latest results. |
Hardware Test Results6 files 6 suites 18m 2s ⏱️ Results for commit 1d15664. ♻️ This comment has been updated with latest results. |
Narrow real nullable dereferences and silence ty 0.0.31 rules that the current pydantic plugin can't infer through ``ConfigDict``. Real narrowing fixes: - ``adrv9009.py`` — assert ``trx2_cs`` is not None in the ``is_fmcomms8`` branch the caller already requires. - ``fmcdaq2.py`` — AD9523 channel specs become typed 3-tuples ``(id: int, name: str, divider: int)`` so ``_m1 // divider`` is ``int // int`` again. - ``adxcvr.py`` — widen the ``use_div40`` guard to narrow all three of ``jesd_l/m/s`` before dereference. - ``system.py`` — swap ``hasattr(x, 'foo')`` + ``x.foo`` for ``getattr(x, 'foo', None)`` + explicit-None checks so the optional- attribute fact is visible via the returned type. Workflow + rule config: - ``type-check.yml`` — drop the stale ``adidt/boards/`` path and add ``adidt/devices/``, ``adidt/system.py``, ``adidt/tools/``. - ``pyproject.toml`` — silence ``unknown-argument`` and ``invalid-argument-type`` on the typed surface with revisit notes (pydantic-plugin false positives on populate_by_name + flow- sensitive narrowing of pydantic ``int | None`` fields). ``ty check`` now completes with "All checks passed!" on the typed path list.
Extend the offline DT-emission parity framework to the second hw
board in CI. Commits the XSA-pipeline output DTS from a passing
``hw-direct (bq)`` artifact as
``test/devices/fixtures/adrv9371_zc706_xsa_reference.dts``, and
adds a new parametrized test
``test/devices/test_system_adrv9371_zc706_dts_parity.py`` that
pins 31 kernel-relevant properties where the System-API and XSA
paths already agree (AD9371 top-level + AD9528 clock-chip PLL/SYSREF
configuration).
Four properties the System-API path is known to miss vs XSA —
``ad9371:clocks``, ``ad9371:clock-names``,
``ad9371:jesd204-inputs``, ``ad9371:jesd204-link-ids`` — are
recorded as ``@pytest.mark.xfail`` with reasons referencing the
gap noted in ``test/hw/test_adrv9371_zc706_hw.py`` (System API
doesn't yet emit the XCVR/TPL-core/clkgen overlay for ZC706).
Each xfail becomes a regression guard the moment the System-API
starts emitting the property.
Along the way, extend ``adidt.tools.dts_inspect`` so the parser
covers this board's DTS too:
- ``_GROUPING_ANCESTORS`` gains ``ad9371-phy``, ``adrv9009-phy``,
``ad9528-1``, ``ad9523-1``, ``axi-adxcvr``, ``axi-clkgen``, and
the JESD204 TPL core nodes. ``_ANCESTOR_ALIAS`` strips the
``-phy`` / ``-1`` suffix so keys stay canonical
(``ad9371:compatible`` regardless of whether the node is
``ad9371`` or ``ad9371-phy@1``).
- ``extract_props`` now normalises the ``}; foo: bar@0 {``
multi-node-on-one-line pattern that appears in merged DTS output
by splitting on ``};`` before the line-oriented matchers run.
Without the split the parser leaves the previous node open and
misattributes the next node's properties to it — the AD9371's
clocks/gpios were landing under ``ad9528:`` before this change.
FMCDAQ3+VCU118 doesn't ship a DT generation path (boots a prebuilt
simpleImage via JTAG), so no parity fixture is possible for that
board.
31 parity keys * ADRV9371 + 14 parity keys * AD9081 now run in
<0.5 s locally — keeps the "catch DT-emission regressions before
hw" story covered for the two boards with a generated DT path.
Bandit's ``B610:django_extra_used`` rule pattern-matches any
``x.extra(...)`` call as a potential Django ORM ``.extra()`` SQL
injection vector. Here ``extra`` is just a local variable holding
a callable (the device model's optional ``extra_dt_lines`` hook),
not a Django queryset — inline ``# nosec B610`` silences the
check with a comment explaining the context. Fixes the only new
security-scanning error on the PR branch; the other failing
"Analyze (c-cpp)" is a CodeQL default-setup artifact ("No source
files found" on a Python-only repo) and is configured at the
repo-settings level, outside this PR.
Emit JUnit XML from every pytest invocation in CI and surface the
results two ways on each PR:
1. ``dorny/test-reporter@v1`` per job — creates a GitHub Check Run
per Python version / hw-node / kuiper flavour with the full
markdown table of failures, so the failing test is one click
from the PR checks tab.
2. ``EnricoMi/publish-unit-test-result-action@v2`` — aggregates the
XMLs across all legs and posts a single conversation comment
per workflow ("Unit Test Results", "Hardware Test Results",
"Kuiper64 Test Results"), visible inline on the PR without
drilling into check details.
Workflow changes:
- ``test.yml`` — ``--junitxml=junit-py<ver>.xml``, upload standalone
artifact, dorny report "Tests (Python X.YZ)", PR-comment summary
job gated on ``pull_request``.
- ``hardware-test.yml`` — ``--junitxml=junit-hw-{direct,coord}-
<place>.xml`` per matrix leg, standalone artifact + dorny report
per leg, aggregated PR-comment job needing both matrix groups.
- ``test-kuiper.yml`` — ``--junitxml`` inside ``docker exec``, dorny
with ``working-directory: ${{env.APP_NAME}}`` to match the
checkout sub-path (dorny shells ``git ls-files`` from CWD), then
the EnricoMi PR comment.
Permissions bumped to ``checks: write`` + ``pull-requests: write``
on every workflow that writes a report.
661fea8 to
e17957d
Compare
Four coordinator-mode labgrid env YAMLs lived at the repo root with
a redundant ``env_remote_`` prefix. Group them with the hw tests
they configure and drop the prefix:
env_remote_bq.yaml → test/hw/env/bq.yaml
env_remote_mini2.yaml → test/hw/env/mini2.yaml
env_remote_nuc.yaml → test/hw/env/nuc.yaml
env_remote_all.yaml → test/hw/env/all.yaml
Update every reference:
- ``.github/hw-nodes.json`` — ``env_remote`` field now holds the
full repo-relative path (workflow reads it verbatim).
- ``.github/scripts/register-hw-runners.sh`` — comment updated.
- ``doc/source/api/devices.rst`` + ``doc/source/developer/hardware_ci.rst``
— docs now document the ``test/hw/env/`` location and the
``env_remote`` field as a repo-relative path.
- ``test/hw/test_fmcdaq3_vcu118_hw.py`` — usage docstring updated.
- YAML file docstrings updated for the new ``LG_ENV`` paths.
The hardware-test workflow needs no change — it already reads the
manifest's ``env_remote`` field verbatim via
``${{ github.workspace }}/${{ matrix.node.env_remote }}``.
…cker The declarative ``adidt.System`` pattern has fully replaced the old ``BoardModel`` + ``XsaPipeline`` rendering paths. The 17-file ``test/hw/deprecated/`` tree has carried a ``RUN_DEPRECATED_HW`` opt-in gate for months with no user and no plan to promote any of it — remove it outright. Also remove the now-orphaned petalinux docker runner — it only ever wrapped ``test/hw/deprecated/xsa/test_petalinux_build_hw.py`` and has no other consumer: - ``docker/Dockerfile.petalinux`` - ``docker/entrypoint.sh`` - ``docker/run-petalinux-tests.sh`` noxfile.py cleanup: - Drop ``tests_remote`` — required ``--ip`` from the old BoardModel flow; no current hw test accepts it. - Drop ``petalinux_build`` — pointed at the removed deprecated test. - Fix ``ty`` path list to match ``.github/workflows/type-check.yml`` (``adidt/boards/`` was removed during the re-arch). - Drop the stale commented ``PYTHON_VERSIONS`` line. - Drop the empty try/except wrappers around the ruff sessions. - Drop commented ``_install_local_pyd2lang`` calls in ``docs`` / ``docs_serve`` (the sessions now install ``pyd2lang-native`` from PyPI like CI does).
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Coalesces the
tfcollins/re-archbranch's 55 iterative commits into a 12-commit deliverable. Three threads:Typed device model + hw test infrastructure. Replace the legacy board/parts/template layers with the typed
adidt.devicesmodel and add aboardpytest fixture that drives labgrid in either coordinator or direct mode from a single env-var set (LG_COORDINATOR/LG_ENV).New hardware coverage. End-to-end hw tests for ADRV9371+ZC706 (System API + XSA pipeline), AD9081+ZCU102 (XSA pipeline), and FMCDAQ3+VCU118 (coordinator BootFabric / JTAG). File-based kernel-image cache keyed on the sha256 of the
pyadi-buildYAML config.Hardware CI workflow. New
Hardware TestsGitHub Actions workflow fanning out over a declarative.github/hw-nodes.jsonmanifest. Preflight on the coordinator host,hw-directandhw-coordmatrix legs on per-node self-hosted runners,uv-managed venvs, private-dep PAT plumbing, artifact uploads (DTS / DTB / dmesg / UART logs), pre-emptive cold-cycle + one-shot retry inBootFPGASoCto eliminate the silent-first-power-on flake, and board power-off on test teardown. Fork-PR protection via GitHub's built-in workflow approval gate (no customenvironment:).Test plan
test/devices/test_system_ad9081_dts_parity.py) pin the System API's emission against the committed XSA-reference DTS fixture.preflight(ubuntu-latest viahw-coordinatorself-hosted) 3–4 shw-direct (bq)70–73 s (full ADRV9371+ZC706 XSA pipeline + boot + IIO)hw-coord (bq)75–80 shw-coord (mini2)238–250 s (AD9081+ZCU102 XSA + System API, both green)hw-coord (nuc)272–281 s (FMCDAQ3+VCU118 BootFabric)hw-direct (mini2),hw-direct (nuc)skip cleanly (noLG_DIRECT_ENVon those runners yet)Commit layout
Twelve commits, each a self-contained, reviewable unit:
feat: coordinator + direct-mode hardware test infrastructurefeat: ADRV9371 + ZC706 support via System API and XSA pipelinefeat: cache built kernel images across test runstest: AD9081 + ZCU102 XSA pipeline hardware test + DT correctionstest: FMCDAQ3 + VCU118 coordinator hw test (BootFabric / JTAG)doc: add Authoring a new device class developer guideci: add Hardware Tests workflow + manifest + runner registration helperci: preflight + adidt venv plumbing for self-hosted hw runnersci: hw-coord on per-node runners, labgrid acquire/release, Vivado sourcefix(system): AD9081 DT emission — dev_clk + pll2 + per-side JESD modesfix(ad9081): mode-table + HMC7044 dividers + CI artifact + local-DTS paritytest(hw): power boards off on teardown; doc the CI behaviourDependencies
adi-labgrid-pluginsfork (tfcollins/labgrid-plugins) ships theBootFPGASoCcold-cycle + retry additions used here. Upstream-compatible; the strategy attrs are all opt-in defaults.pyadi-buildis private — CI pulls it through a repo-scopedPYADI_BUILD_TOKENsecret configured in the workflow's install step.Out of scope
See
doc/source/developer/hardware_ci.rstfor the full operator manual (runner registration,LG_DIRECT_ENVconvention, PAT setup, troubleshooting, artifact recipes, local DT-parity-test flow).