Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions adidt/devices/clocks/ad952x.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,20 @@ def extra_dt_lines(self, context: dict | None = None) -> list[str]:
lines: list[str] = []
for gl in self.gpio_lines:
lines.append(f"{gl.prop} = <&{gl.controller} {int(gl.index)} 0>;")
if self.jesd204_sysref_provider:
lines.append("jesd204-device;")
lines.append("#jesd204-cells = <2>;")
lines.append("jesd204-sysref-provider;")
if self.jesd204_max_sysref_hz is not None:
lines.append(
f"adi,jesd204-max-sysref-frequency-hz = <{int(self.jesd204_max_sysref_hz)}>;"
)
return lines

# Both device families share these fields:
gpio_lines: Annotated[list[_GpioLine], DtSkip()] = Field(default_factory=list)
jesd204_sysref_provider: Annotated[bool, DtSkip()] = False
jesd204_max_sysref_hz: Annotated[int | None, DtSkip()] = None


class AD9523_1(_AD952xBase):
Expand Down
4 changes: 2 additions & 2 deletions adidt/eval/adrv937x_fmc.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ def __init__(self, *, reference_frequency: int = 122_880_000) -> None:
label="trx0_ad9371",
node_name_base="ad9371-phy",
compatible_strings=["adi,ad9371"],
reset_gpio=130,
sysref_req_gpio=136,
reset_gpio=106,
sysref_req_gpio=112,
)

def _named(self, name: str) -> ClockOutput:
Expand Down
92 changes: 88 additions & 4 deletions adidt/xsa/builders/adrv937x.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,48 @@
JesdLinkModel,
)
from ...devices.clocks import AD9528_1
from ...devices.clocks.ad952x import AD9528_1Channel, _GpioLine
from ...devices.fpga_ip import build_jesd204_overlay_ctx
from ...model.renderer import BoardModelRenderer
from ..topology import XsaTopology

_ADRV937X_KEYWORDS = ("ad9371", "adrv937")


# Default Mykonos (AD9371) initial device profile — baked into the DT
# as ``adi,*-profile-*`` / ``adi,clocks-*`` properties on
# ``ad9371-phy@1`` when the caller doesn't supply a per-profile
# override.
#
# The AD9371 driver consumes these at probe to configure the Mykonos
# ARM before userspace ever sees the chip; the values must encode a
# configuration whose JESD framing (M, L, F, K, Np, CS, CF) matches
# the FPGA's compiled-in ``axi-jesd204-{tx,rx}`` overlays, otherwise
# the deframer reports an ILAS mismatch at link-up and the TPL DMA
# sits idle. Because "matching" is HDL-build-specific (see the
# ``TX_JESD_*`` / ``RX_JESD_*`` knobs in
# ``analogdevicesinc/hdl/projects/adrv937x/zc706/README.md``), this
# module ships an empty default and expects the per-board profile
# JSON (e.g. ``adidt/xsa/profiles/adrv937x_zc706.json``) to supply a
# full ``trx_profile_props`` list. Callers without a profile JSON
# can still override via ``board_cfg["trx_profile_props"]``.
_DEFAULT_MYKONOS_PROFILE_PROPS: tuple[str, ...] = ()


# AD9528 output-channel map baked into the DT so the clock distributor
# driver configures dividers + signal sources before the Mykonos driver
# requests ``dev_clk`` at 122.88 MHz. Each AD9528_1Channel renders to
# an ``adi,channels/channel@N`` subnode. Values mirror the Kuiper
# zc706-adrv9371 reference DT.
def _default_ad9528_channels() -> dict[int, AD9528_1Channel]:
return {
13: AD9528_1Channel(id=13, name="DEV_CLK", divider=10, signal_source=0),
1: AD9528_1Channel(id=1, name="FMC_CLK", divider=10, signal_source=0),
12: AD9528_1Channel(id=12, name="DEV_SYSREF", divider=10, signal_source=2),
3: AD9528_1Channel(id=3, name="FMC_SYSREF", divider=10, signal_source=2),
}


def _is_adrv937x_name(value: str) -> bool:
lower = value.lower()
return any(key in lower for key in _ADRV937X_KEYWORDS)
Expand Down Expand Up @@ -186,8 +221,14 @@ def build_model(
spi_bus = str(board_cfg.get("spi_bus", "spi0"))
clk_cs = int(board_cfg.get("clk_cs", 0))
trx_cs = int(board_cfg.get("trx_cs", 1))
trx_reset_gpio = int(board_cfg.get("trx_reset_gpio", 130))
trx_sysref_req_gpio = int(board_cfg.get("trx_sysref_req_gpio", 136))
# GPIO defaults match the Kuiper ``zc706-adrv9371`` reference DT
# (EMIO pin numbering relative to Zynq GPIO controller base=54):
# - AD9371 reset = 106 (``trx_reset_gpio``)
# - AD9371 sysref = 112 (``trx_sysref_req_gpio``)
# - AD9528 reset = 113 (``ad9528_reset_gpio``)
trx_reset_gpio = int(board_cfg.get("trx_reset_gpio", 106))
trx_sysref_req_gpio = int(board_cfg.get("trx_sysref_req_gpio", 112))
ad9528_reset_gpio = int(board_cfg.get("ad9528_reset_gpio", 113))
trx_spi_max_frequency = int(board_cfg.get("trx_spi_max_frequency", 25000000))
ad9528_vcxo_freq = int(board_cfg.get("ad9528_vcxo_freq", 122880000))

Expand All @@ -208,6 +249,23 @@ def build_model(
label=clock_chip_label,
spi_max_hz=10_000_000,
vcxo_hz=ad9528_vcxo_freq,
channels=_default_ad9528_channels(),
gpio_lines=[
_GpioLine(
prop="reset-gpios",
controller=gpio_label,
index=ad9528_reset_gpio,
),
],
# Mark AD9528 as the JESD204 topology's SYSREF provider.
# Without this the AD9371 driver's
# ``opt_post_running_stage`` callback can't find a sysref
# source in the jesd204 graph and rolls back with -EFAULT.
# Matches the Kuiper zc706-adrv9371 reference DT.
jesd204_sysref_provider=True,
jesd204_max_sysref_hz=int(
board_cfg.get("ad9528_jesd204_max_sysref_hz", 78125)
),
)
clock_component = ComponentModel(
role="clock",
Expand All @@ -234,16 +292,27 @@ def build_model(
trx_clock_names_value = (
'"dev_clk", "fmc_clk", "sysref_dev_clk", "sysref_fmc_clk"'
)
trx_link_ids_value = f"{rx_link_id} {tx_link_id}"
# Add the AD9528 sysref-provider link to jesd204-link-ids and
# jesd204-inputs so the AD9371 driver's post-running verify
# sees a full RX / TX / sysref topology. Matches the working
# Kuiper zc706-adrv9371 reference (RX=1, TX=0, SYSREF=2).
ad9528_sysref_link_id = 2
trx_link_ids_value = f"{rx_link_id} {tx_link_id} {ad9528_sysref_link_id}"
trx_inputs_value = (
f"<&{rx_xcvr_label} 0 {rx_link_id}>, <&{tx_xcvr_label} 0 {tx_link_id}>"
f"<&{rx_xcvr_label} 0 {rx_link_id}>, "
f"<&{tx_xcvr_label} 0 {tx_link_id}>, "
f"<&{clock_chip_label} 0 {ad9528_sysref_link_id}>"
)
profile_props = tuple(
board_cfg.get("trx_profile_props", _DEFAULT_MYKONOS_PROFILE_PROPS)
)
phy_context = {
"gpio_label": gpio_label,
"clocks_value": trx_clocks_value,
"clock_names_value": trx_clock_names_value,
"link_ids": trx_link_ids_value,
"jesd204_inputs": trx_inputs_value,
"profile_props": profile_props,
}
phy_component = ComponentModel(
role="transceiver",
Expand Down Expand Up @@ -315,13 +384,25 @@ def build_model(
]

# --- Raw XCVR overlay nodes ---
# The ``adi,sys-clk-select = <0>`` + ``adi,out-clk-select =
# <3>`` + ``adi,use-lpm-enable`` triplet is required by the
# axi-adxcvr driver to bind — without them the platform
# device sits in deferred probe on this build. Clocks are
# kept pointing at the clkgen (through-path) and the
# ``div40`` shape is preserved to minimise risk of a kernel
# hang (the direct-AD9528 rewire attempted in commit 607663b
# did match the reference DT structurally but hung the
# kernel post-JESD).
rx_xcvr_node = (
f"\t&{rx_xcvr_label} {{\n"
'\t\tcompatible = "adi,axi-adxcvr-1.0";\n'
f"\t\tclocks = {rx_xcvr_conv_clk_ref}, {rx_xcvr_div40_ref};\n"
'\t\tclock-names = "conv", "div40";\n'
"\t\t#clock-cells = <1>;\n"
'\t\tclock-output-names = "rx_gt_clk", "rx_out_clk";\n'
"\t\tadi,sys-clk-select = <0>;\n"
"\t\tadi,out-clk-select = <3>;\n"
"\t\tadi,use-lpm-enable;\n"
"\t\tjesd204-device;\n"
"\t\t#jesd204-cells = <2>;\n"
"\t};"
Expand All @@ -333,6 +414,9 @@ def build_model(
'\t\tclock-names = "conv", "div40";\n'
"\t\t#clock-cells = <1>;\n"
'\t\tclock-output-names = "tx_gt_clk", "tx_out_clk";\n'
"\t\tadi,sys-clk-select = <0>;\n"
"\t\tadi,out-clk-select = <3>;\n"
"\t\tadi,use-lpm-enable;\n"
"\t\tjesd204-device;\n"
"\t\t#jesd204-cells = <2>;\n"
"\t};"
Expand Down
4 changes: 4 additions & 0 deletions adidt/xsa/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
"trx_sysref_req_gpio",
"trx_spi_max_frequency",
"ad9528_vcxo_freq",
"ad9528_reset_gpio",
"ad9528_jesd204_max_sysref_hz",
"rx_link_id",
"rx_os_link_id",
"tx_link_id",
Expand Down Expand Up @@ -154,6 +156,8 @@
"trx_sysref_req_gpio",
"trx_spi_max_frequency",
"ad9528_vcxo_freq",
"ad9528_reset_gpio",
"ad9528_jesd204_max_sysref_hz",
"rx_link_id",
"rx_os_link_id",
"tx_link_id",
Expand Down
62 changes: 58 additions & 4 deletions adidt/xsa/profiles/adrv937x_zc706.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,73 @@
"name": "adrv937x_zc706",
"defaults": {
"adrv9009_board": {
"misc_clk_hz": 245760000,
"misc_clk_hz": 122880000,
"spi_bus": "spi0",
"clk_cs": 0,
"trx_cs": 1,
"trx_reset_gpio": 130,
"trx_sysref_req_gpio": 136,
"trx_reset_gpio": 106,
"trx_sysref_req_gpio": 112,
"ad9528_reset_gpio": 113,
"trx_spi_max_frequency": 25000000,
"ad9528_vcxo_freq": 122880000,
"rx_link_id": 1,
"rx_os_link_id": 2,
"tx_link_id": 0,
"tx_octets_per_frame": 2,
"rx_os_octets_per_frame": 2
"rx_os_octets_per_frame": 2,
"trx_profile_props": [
"adi,clocks-clk-pll-vco-freq_khz = <0x960000>;",
"adi,clocks-device-clock_khz = <0x1e000>;",
"adi,clocks-clk-pll-hs-div = <0x04>;",
"adi,clocks-clk-pll-vco-div = <0x02>;",
"adi,jesd204-obs-framer-over-sample = <0x00>;",
"adi,rx-profile-adc-div = <0x01>;",
"adi,rx-profile-en-high-rej-dec5 = <0x01>;",
"adi,rx-profile-iq-rate_khz = <0x1e000>;",
"adi,rx-profile-rf-bandwidth_hz = <0x5f5e100>;",
"adi,rx-profile-rhb1-decimation = <0x01>;",
"adi,rx-profile-rx-bbf-3db-corner_khz = <0x186a0>;",
"adi,rx-profile-rx-dec5-decimation = <0x05>;",
"adi,rx-profile-rx-fir-decimation = <0x02>;",
"adi,rx-profile-rx-fir-gain_db = <0xfffffffa>;",
"adi,rx-profile-rx-fir-num-fir-coefs = <0x30>;",
"adi,rx-profile-rx-fir-coefs = <0xfffbffe6 0x200033 0xffbdff8c 0x8c00d4 0xff04fe91 0x1ad0253 0xfd50fc5d 0x4300593 0xf98ef774 0xa340da8 0xed3ee259 0x25b87e3d 0x7e3d25b8 0xe259ed3e 0xda80a34 0xf774f98e 0x5930430 0xfc5dfd50 0x25301ad 0xfe91ff04 0xd4008c 0xff8cffbd 0x330020 0xffe6fffb>;",
"adi,rx-profile-custom-adc-profile = <0x2160182 0xc90062 0x50001eb 0x6370117 0x51a0068 0x318001c 0x300027 0x1700bb>;",
"adi,obs-profile-adc-div = <0x01>;",
"adi,obs-profile-en-high-rej-dec5 = <0x01>;",
"adi,obs-profile-iq-rate_khz = <0x3c000>;",
"adi,obs-profile-rf-bandwidth_hz = <0xbebc200>;",
"adi,obs-profile-rhb1-decimation = <0x01>;",
"adi,obs-profile-rx-bbf-3db-corner_khz = <0x186a0>;",
"adi,obs-profile-rx-dec5-decimation = <0x05>;",
"adi,obs-profile-rx-fir-decimation = <0x01>;",
"adi,obs-profile-rx-fir-gain_db = <0x06>;",
"adi,obs-profile-rx-fir-num-fir-coefs = <0x18>;",
"adi,obs-profile-rx-fir-coefs = <0xfedf0051 0xffe9ffaa 0xe5fe9e 0x18dff17 0xfd6f06a3 0xefb459e2 0xefb406a3 0xfd6fff17 0x18dfe9e 0xe5ffaa 0xffe90051 0xfedf0000>;",
"adi,obs-profile-custom-adc-profile = <0x1c2015d 0xc90062 0x50002da 0x65a0332 0x5c402dc 0x3420014 0x290024 0x1800c8>;",
"adi,obs-settings-custom-loopback-adc-profile = <0x2390171 0xc90062 0x5000123 0x6050095 0x528003a 0x3270022 0x300028 0x1700bd>;",
"adi,tx-profile-dac-div = <0x01>;",
"adi,tx-profile-iq-rate_khz = <0x3c000>;",
"adi,tx-profile-primary-sig-bandwidth_hz = <0x47868c0>;",
"adi,tx-profile-rf-bandwidth_hz = <0xbebc200>;",
"adi,tx-profile-thb1-interpolation = <0x02>;",
"adi,tx-profile-thb2-interpolation = <0x01>;",
"adi,tx-profile-tx-bbf-3db-corner_khz = <0x186a0>;",
"adi,tx-profile-tx-dac-3db-corner_khz = <0x2da78>;",
"adi,tx-profile-tx-fir-interpolation = <0x01>;",
"adi,tx-profile-tx-input-hb-interpolation = <0x01>;",
"adi,tx-profile-tx-fir-gain_db = <0x06>;",
"adi,tx-profile-tx-fir-num-fir-coefs = <0x10>;",
"adi,tx-profile-tx-fir-coefs = <0x6fef2 0xcbff58 0xffac03d7 0xf36a5297 0xf36a03d7 0xffacff58 0xcbfef2 0x60000>;",
"adi,sniffer-profile-adc-div = <0x01>;",
"adi,sniffer-profile-en-high-rej-dec5 = <0x00>;",
"adi,sniffer-profile-iq-rate_khz = <0x7800>;",
"adi,sniffer-profile-rf-bandwidth_hz = <0x1312d00>;",
"adi,sniffer-profile-rhb1-decimation = <0x02>;",
"adi,sniffer-profile-rx-bbf-3db-corner_khz = <0x186a0>;",
"adi,sniffer-profile-rx-dec5-decimation = <0x05>;",
"adi,sniffer-profile-rx-fir-decimation = <0x04>;"
]
}
}
}
80 changes: 75 additions & 5 deletions doc/source/developer/hardware_ci.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@ Adding a new hardware node
One-time setup:

1. Stand up an exporter that registers a new place ``<name>`` with
the coordinator at ``10.0.0.41:20408``. Verify with
the coordinator at ``10.0.0.41:20408`` — install it as a systemd
unit via ``scripts/labgrid-exporter/install.sh``
(see :ref:`exporter-systemd`). Verify with
``labgrid-client -x 10.0.0.41:20408 places``.
2. Register a new self-hosted runner with labels
``self-hosted,hw-<name>`` on the host physically attached to the
Expand Down Expand Up @@ -286,6 +288,69 @@ Coordinator-mode tests additionally require SSH key-auth from the
``MassStorageDriver`` SSH-proxy path) and write access to
``~/.cache/adidt/kernel/`` for the kernel image cache.

.. _exporter-systemd:

Exporter systemd service
------------------------

Each hw-node also runs a ``labgrid-exporter`` process that publishes
its local hardware resources to the coordinator. Production hosts
run it as a systemd template unit — ``labgrid-exporter@<place>`` —
installed by ``scripts/labgrid-exporter/install.sh``:

.. code-block:: bash

# On the exporter host (bq, mini2, nuc, ...):
cd /path/to/pyadi-dt
sudo scripts/labgrid-exporter/install.sh <place> <exporter-yaml>

# Concrete example — nuc:
sudo scripts/labgrid-exporter/install.sh nuc \
/home/tcollins/dev/lg-coordinator/lg_fmcdaq3_vcu118_exporter.yaml

The installer writes two files and enables the unit:

- ``/etc/systemd/system/labgrid-exporter@.service`` — the template
unit. ``User=`` is baked in at install time (defaults to
``$SUDO_USER``; override with ``--user``).
- ``/etc/default/labgrid-exporter-<place>`` — per-instance env
(``LG_EXPORTER_BIN``, ``LG_COORDINATOR``, ``LG_EXPORTER_NAME``,
``LG_EXPORTER_YAML``, ``PATH``).

Install-time options:

- ``--coordinator ADDR`` — default ``10.0.0.41:20408``.
- ``--user USER`` — default ``$SUDO_USER``.
- ``--bin PATH`` — default auto-detected via ``command -v``
as the service user (picks up the
``~/.local/share/uv/tools/labgrid/bin/labgrid-exporter`` that
``uv tool install labgrid`` produces).
- ``--ser2net-path DIR`` — prepend DIR to the service PATH so
``labgrid-exporter`` finds the right ``ser2net`` binary (e.g.
``$HOME/opt/ser2net-4.6.1/sbin``). Omit if ``ser2net`` on the
system PATH is the one you want.
- ``--no-start`` — install files but don't
``enable --now``.

Day-to-day operation:

.. code-block:: bash

# After a yaml change:
sudo systemctl restart labgrid-exporter@<place>

# Live logs:
journalctl -u labgrid-exporter@<place> -f

# Unit status:
systemctl status labgrid-exporter@<place>

``Restart=on-failure`` brings the exporter back after a crash, and
``multi-user.target`` is the enable target, so the exporter also
starts automatically after a reboot. Re-running ``install.sh`` is
idempotent — safe to use to pick up a new coordinator address,
binary path, or yaml file.

Fork-PR approval gate
---------------------

Expand Down Expand Up @@ -397,8 +462,12 @@ Troubleshooting

**One node's jobs skip while others run.**
The corresponding exporter is not advertising its place to the
coordinator. SSH to that node, restart the exporter, and verify
the place shows up in ``labgrid-client places``.
coordinator. SSH to that node and restart the unit
(``sudo systemctl restart labgrid-exporter@<place>`` — see
:ref:`exporter-systemd`), then verify the place shows up in
``labgrid-client places``. Check
``journalctl -u labgrid-exporter@<place> -n 50`` if the restart
alone doesn't recover it.

**``hw-direct`` fails with "LG_DIRECT_ENV is not set".**
The runner's ``~/actions-runner/.env`` does not define
Expand All @@ -421,8 +490,9 @@ Troubleshooting
Check the ``uart_log_kernel_banner_attempt1_*.txt`` artifact — if
it's empty, the first power-on was silent (the pre-emptive
cold-cycle should have handled this, so getting here usually means
the serial exporter on the hw node is misbehaving). Restart
``ser2net`` on the exporter host.
the serial exporter on the hw node is misbehaving). Restart the
exporter — ``sudo systemctl restart labgrid-exporter@<place>`` —
which also respawns ``ser2net``.

**Board left powered on after a local test run.**
The ``board`` fixture in ``test/hw/conftest.py`` powers the board
Expand Down
Loading
Loading