Board Model Module

The adidt.model package provides the unified BoardModel abstraction that both the manual board-class workflow and the XSA pipeline produce. A single BoardModelRenderer renders any BoardModel to DTS using per-component Jinja2 templates.

Overview

A BoardModel describes the complete hardware composition of a board: what components exist, how they connect via JESD204 links, and what FPGA configuration applies. Three workflows converge on this model:

XSA Pipeline                Manual Board Class         Direct Construction
─────────────               ──────────────────         ────────────────────
XsaPipeline.run()           daq2.to_board_model(cfg)   BoardModel(name=...,
  └─ AD9081Builder            ad9081_fmc                  components=[...],
       .build_model()           .to_board_model(cfg)       jesd_links=[...])
           │                        │                        │
           └────────────────────────┼────────────────────────┘
                                    ▼
                               BoardModel
                                    │
                           BoardModelRenderer
                               .render()
                                    │
                                    ▼
                           dict[str, list[str]]
                           (DTS node strings)
                                    │
                               DtsMerger
                               .merge()
                                    │
                                    ▼
                             .dts / .dtso files

The model is editable after construction. You can modify component configs, JESD parameters, or metadata before rendering:

from adidt.xsa.builders.fmcdaq2 import FMCDAQ2Builder
from adidt.model.renderer import BoardModelRenderer

# Build from XSA topology
model = FMCDAQ2Builder().build_model(topology, cfg, "zynqmp_clk", 71, "gpio")

# Edit before rendering
clock = model.get_component("clock")
clock.config["vcxo_hz"] = 100_000_000

# Render to DTS
nodes = BoardModelRenderer().render(model)

Supported boards

All six XSA builders produce BoardModel instances:

Builder

Clock Chip

Converters

FMCDAQ2Builder

AD9523-1

AD9680 (ADC) + AD9144 (DAC)

FMCDAQ3Builder

AD9528

AD9680 (ADC) + AD9152 (DAC)

AD9172Builder

HMC7044

AD9172 (DAC)

AD9081Builder

HMC7044

AD9081 MxFE (ADC + DAC)

ADRV9009Builder

AD9528

ADRV9009 (transceiver, RX + TX + ORX)

AD9084Builder

HMC7044 + ADF4382

AD9084 (RX transceiver)

Board classes with to_board_model():

  • adidt.boards.daq2 (ZCU102, ZC706)

Component factories

The easiest way to create components. Each factory returns a pre-configured ComponentModel — no template filenames needed:

from adidt.model import BoardModel, components

model = BoardModel(
    name="my_board",
    platform="rpi5",
    components=[
        components.adis16495(spi_bus="spi0", cs=0, interrupt_gpio=25),
    ],
)

Available factories (from adidt.model import components):

  • Simple SPI: components.adis16495 — ADIS16495/16497 IMU

  • Clock chips: components.hmc7044, components.ad9523_1, components.ad9528

  • ADCs / DACs: components.ad9680, components.ad9144, components.ad9152, components.ad9172

  • Transceivers: components.ad9081, components.ad9084

Context builders

Lower-level functions that produce template context dicts. Use these when you need full control over every field, or when no factory exists for your device:

from adidt.model.contexts import build_ad9523_1_ctx

ctx = build_ad9523_1_ctx(
    label="clk0_ad9523",
    cs=0,
    spi_max_hz=10_000_000,
    vcxo_hz=125_000_000,
)
# ctx is ready to pass to ad9523_1.tmpl

API Reference

Board Model

Dataclass definitions for the unified board model.

class adidt.model.board_model.ComponentModel(role: str, part: str, template: str, spi_bus: str, spi_cs: int, config: dict[str, ~typing.Any]=<factory>)

Bases: object

One physical device on the board (clock chip, converter, etc.).

role

Logical role on the board — "clock", "adc", "dac", or "transceiver".

Type:

str

part

Part number string, e.g. "ad9523_1", "ad9680".

Type:

str

template

Jinja2 template filename used to render this component, e.g. "ad9523_1.tmpl".

Type:

str

spi_bus

SPI bus label, e.g. "spi0", "spi1".

Type:

str

spi_cs

SPI chip-select index.

Type:

int

config

Template context dict — the same dicts that adidt.model.contexts functions produce.

Type:

dict[str, Any]

role: str
part: str
template: str
spi_bus: str
spi_cs: int
config: dict[str, Any]
class adidt.model.board_model.JesdLinkModel(direction: str, jesd_label: str, xcvr_label: str, core_label: str, dma_label: str | None, link_params: dict[str, int]=<factory>, xcvr_config: dict[str, ~typing.Any]=<factory>, jesd_overlay_config: dict[str, ~typing.Any]=<factory>, tpl_core_config: dict[str, ~typing.Any]=<factory>, dma_clocks_str: str | None = None)

Bases: object

One JESD204 link (RX or TX) with its associated FPGA IP labels and config.

direction

"rx" or "tx".

Type:

str

jesd_label

AXI JESD204 IP label, e.g. "axi_ad9680_jesd204_rx".

Type:

str

xcvr_label

ADXCVR IP label, e.g. "axi_ad9680_adxcvr".

Type:

str

core_label

TPL core IP label, e.g. "axi_ad9680_tpl_adc_tpl_core".

Type:

str

dma_label

AXI DMA IP label, e.g. "axi_ad9680_dma".

Type:

str | None

link_params

JESD framing parameters — keys F, K, M, L, Np, S.

Type:

dict[str, int]

xcvr_config

ADXCVR template context dict (for adxcvr.tmpl).

Type:

dict[str, Any]

jesd_overlay_config

JESD overlay template context dict (for jesd204_overlay.tmpl).

Type:

dict[str, Any]

tpl_core_config

TPL core template context dict (for tpl_core.tmpl).

Type:

dict[str, Any]

direction: str
jesd_label: str
xcvr_label: str
core_label: str
dma_label: str | None
link_params: dict[str, int]
xcvr_config: dict[str, Any]
jesd_overlay_config: dict[str, Any]
tpl_core_config: dict[str, Any]
dma_clocks_str: str | None = None
class adidt.model.board_model.FpgaConfig(platform: str, addr_cells: int, ps_clk_label: str, ps_clk_index: int | None, gpio_label: str)

Bases: object

Platform-level FPGA configuration.

platform

Target platform string, e.g. "zcu102", "vcu118".

Type:

str

addr_cells

Number of address cells — 1 for vcu118/zc706, 2 for zcu102/vpk180.

Type:

int

ps_clk_label

Processing-system clock label, e.g. "zynqmp_clk", "clkc".

Type:

str

ps_clk_index

PS clock index (e.g. 71), or None when the platform has no PS clock index.

Type:

int | None

gpio_label

GPIO controller label, e.g. "gpio", "gpio0".

Type:

str

platform: str
addr_cells: int
ps_clk_label: str
ps_clk_index: int | None
gpio_label: str
class adidt.model.board_model.BoardModel(name: str, platform: str, components: list[ComponentModel] = <factory>, jesd_links: list[JesdLinkModel] = <factory>, fpga_config: FpgaConfig | None = None, metadata: dict[str, ~typing.Any]=<factory>, extra_nodes: list[str] = <factory>)

Bases: object

Unified board model that both the manual and XSA workflows produce.

A BoardModel is an editable snapshot of the complete hardware composition. After construction (from either workflow), callers may inspect and modify components, JESD links, and metadata before passing the model to BoardModelRenderer for DTS rendering.

name

Board/design name, e.g. "fmcdaq2_zcu102".

Type:

str

platform

Target platform, e.g. "zcu102".

Type:

str

components

Physical devices (clock chips, converters, etc.).

Type:

list[adidt.model.board_model.ComponentModel]

jesd_links

JESD204 link definitions (RX and TX).

Type:

list[adidt.model.board_model.JesdLinkModel]

fpga_config

Platform-level FPGA configuration.

Type:

adidt.model.board_model.FpgaConfig | None

metadata

Free-form dict for rendering metadata — date, config_source, base_dts_include, etc.

Type:

dict[str, Any]

name: str
platform: str
components: list[ComponentModel]
jesd_links: list[JesdLinkModel]
fpga_config: FpgaConfig | None = None
metadata: dict[str, Any]
extra_nodes: list[str]
get_component(role: str) ComponentModel | None

Return the first component matching role, or None.

get_components(role: str) list[ComponentModel]

Return all components matching role.

get_jesd_link(direction: str) JesdLinkModel | None

Return the first JESD link matching direction, or None.

get_jesd_links(direction: str) list[JesdLinkModel]

Return all JESD links matching direction.

to_dts(output_path: str, config_source: str = 'board_model') str

Render this model to a standalone DTS file.

Convenience method that renders via BoardModelRenderer and writes the output with SPDX header and metadata.

Parameters:
  • output_path – Path to write the DTS file.

  • config_source – Config source string for the metadata header.

Returns:

The output_path string.

to_dict() dict[str, Any]

Serialize the model to a plain dict (JSON-compatible).

Useful for debugging, logging, and sharing configurations.

classmethod from_dict(data: dict[str, Any]) BoardModel

Deserialize a BoardModel from a dict (as produced by to_dict()).

Parameters:

data – Dict with keys matching BoardModel fields.

Returns:

A new BoardModel instance.

Renderer

Render a BoardModel to DTS node strings using per-component templates.

class adidt.model.renderer.BoardModelRenderer

Bases: object

Renders a BoardModel into DTS node strings.

The output dict has the same shape that build() returns:

{"clkgens": [...], "jesd204_rx": [...], "jesd204_tx": [...], "converters": [...]}

This allows the existing DtsMerger to consume the output without changes.

render(model: BoardModel) dict[str, list[str]]

Render model to DTS node strings (overlay mode).

Parameters:

model – The board model to render.

Returns:

Dict with keys clkgens, jesd204_rx, jesd204_tx, converters, each mapping to a list of DTS node strings.

Context Builder Functions

Shared template-context builders for board components.

Each function returns a plain dict ready to pass to a Jinja2 template. These are used by both the XSA pipeline (via board builders) and the manual board-class workflow (via to_board_model()).

adidt.model.contexts.fmt_hz(hz: int) str

Format hz as a human-readable frequency string (e.g. ‘245.76 MHz’).

adidt.model.contexts.coerce_board_int(value: Any, key_path: str) int

Convert value to int; raise ValueError with key_path context on failure.

adidt.model.contexts.build_ad9523_1_ctx(*, label: str = 'clk0_ad9523', cs: int = 0, spi_max_hz: int = 10000000, vcxo_hz: int = 125000000, gpio_controller: str = 'gpio0', sync_gpio: int | None = None, status0_gpio: int | None = None, status1_gpio: int | None = None, channels: list[dict] | None = None) dict

Build context dict for ad9523_1.tmpl.

Parameters:
  • label – DT node label.

  • cs – SPI chip select.

  • spi_max_hz – SPI max frequency.

  • vcxo_hz – VCXO frequency.

  • gpio_controller – GPIO controller label.

  • sync_gpio – GPIO index for sync pin, or None to omit.

  • status0_gpio – GPIO index for status0 pin, or None to omit.

  • status1_gpio – GPIO index for status1 pin, or None to omit.

  • channels – List of channel dicts with keys id, name, divider, freq_str. If None, uses FMCDAQ2 defaults.

adidt.model.contexts.build_ad9680_ctx(*, label: str = 'adc0_ad9680', cs: int = 2, spi_max_hz: int = 1000000, use_spi_3wire: bool = False, clks_str: str, clk_names_str: str, sampling_frequency_hz: int = 1000000000, rx_m: int = 2, rx_l: int = 4, rx_f: int = 1, rx_k: int = 32, rx_np: int = 16, jesd204_top_device: int = 0, jesd204_link_ids: list[int] | None = None, jesd204_inputs: str = '', gpio_controller: str = 'gpio0', powerdown_gpio: int | None = None, fastdetect_a_gpio: int | None = None, fastdetect_b_gpio: int | None = None) dict

Build context dict for ad9680.tmpl.

adidt.model.contexts.build_ad9144_ctx(*, label: str = 'dac0_ad9144', cs: int = 1, spi_max_hz: int = 1000000, clk_ref: str | None = None, jesd204_top_device: int = 1, jesd204_link_ids: list[int] | None = None, jesd204_inputs: str = '', gpio_controller: str = 'gpio0', txen_gpio: int | None = None, reset_gpio: int | None = None, irq_gpio: int | None = None) dict

Build context dict for ad9144.tmpl.

adidt.model.contexts.build_adxcvr_ctx(*, label: str, sys_clk_select: int, out_clk_select: int, clk_ref: str | None = None, use_div40: bool = True, div40_clk_ref: str | None = None, clock_output_names_str: str, use_lpm_enable: bool = True, jesd_l: int | None = None, jesd_m: int | None = None, jesd_s: int | None = None, jesd204_inputs: str | None = None, is_rx: bool = True) dict

Build context dict for adxcvr.tmpl.

adidt.model.contexts.build_jesd204_overlay_ctx(*, label: str, direction: str, clocks_str: str, clock_names_str: str, clock_output_name: str | None = None, f: int, k: int, jesd204_inputs: str, converter_resolution: int | None = None, converters_per_device: int | None = None, bits_per_sample: int | None = None, control_bits_per_sample: int | None = None) dict

Build context dict for jesd204_overlay.tmpl.

adidt.model.contexts.build_tpl_core_ctx(*, label: str, compatible: str, direction: str, dma_label: str | None, spibus_label: str | None = None, jesd_label: str | None = None, jesd_link_offset: int, link_id: int, pl_fifo_enable: bool = False, sampl_clk_ref: str | None = None, sampl_clk_name: str | None = None) dict

Build context dict for tpl_core.tmpl.

adidt.model.contexts.fmt_gpi_gpo(controls: list) str

Format a list of int/hex values as a space-separated hex string for DTS.

adidt.model.contexts.build_hmc7044_channel_ctx(pll2_hz: int, channels_spec: list) list

Pre-compute freq_str for each HMC7044 channel.

adidt.model.contexts.build_hmc7044_ctx(*, label: str, cs: int, spi_max_hz: int, pll1_clkin_frequencies: list, vcxo_hz: int, pll2_output_hz: int, clock_output_names: list, channels: list[dict] | None, raw_channels: str | None = None, jesd204_sysref_provider: bool = True, jesd204_max_sysref_hz: int = 2000000, pll1_loop_bandwidth_hz=None, pll1_ref_prio_ctrl=None, pll1_ref_autorevert: bool = False, pll1_charge_pump_ua=None, pfd1_max_freq_hz=None, sysref_timer_divider=None, pulse_generator_mode=None, clkin0_buffer_mode=None, clkin1_buffer_mode=None, clkin2_buffer_mode: str | None = None, clkin3_buffer_mode: str | None = None, oscin_buffer_mode=None, gpi_controls=None, gpo_controls=None, sync_pin_mode=None, high_perf_mode_dist_enable: bool = False, clkin0_ref: str | None = None) dict

Build context dict for hmc7044.tmpl.

adidt.model.contexts.build_ad9528_ctx(*, label: str = 'clk0_ad9528', cs: int = 0, spi_max_hz: int = 10000000, vcxo_hz: int = 122880000, gpio_lines: list[dict] | None = None, channels: list[dict] | None = None) dict

Build context dict for ad9528.tmpl.

If channels is None, uses FMCDAQ3 default channels.

adidt.model.contexts.build_ad9528_1_ctx(*, label: str = 'clk0_ad9528', cs: int = 0, spi_max_hz: int = 10000000, vcxo_hz: int = 122880000, gpio_lines: list[dict] | None = None, channels: list[dict] | None = None) dict

Build context dict for ad9528_1.tmpl (ADRV9009 variant).

If channels is None, uses standard ADRV9009 default channels.

adidt.model.contexts.build_ad9152_ctx(*, label: str = 'dac0_ad9152', cs: int = 1, spi_max_hz: int = 1000000, clk_ref: str | None = None, jesd_link_mode: int = 4, jesd204_top_device: int = 1, jesd204_link_ids: list[int] | None = None, jesd204_inputs: str = '', gpio_controller: str = 'gpio0', txen_gpio: int | None = None, irq_gpio: int | None = None) dict

Build context dict for ad9152.tmpl.

adidt.model.contexts.build_ad9172_device_ctx(*, label: str = 'dac0_ad9172', cs: int = 1, spi_max_hz: int = 1000000, clk_ref: str = 'hmc7044 2', dac_rate_khz: int, jesd_link_mode: int, dac_interpolation: int, channel_interpolation: int, clock_output_divider: int, jesd_link_ids: list[int] | None = None, jesd204_inputs: str = '') dict

Build context dict for ad9172.tmpl.

adidt.model.contexts.build_ad9081_mxfe_ctx(*, label: str, cs: int, gpio_label: str, reset_gpio: int | None = None, sysref_req_gpio: int, rx2_enable_gpio: int, rx1_enable_gpio: int, tx2_enable_gpio: int, tx1_enable_gpio: int, dev_clk_ref: str, rx_core_label: str, tx_core_label: str, rx_link_id: int, tx_link_id: int, dac_frequency_hz: int, tx_cduc_interpolation: int, tx_fduc_interpolation: int, tx_converter_select: str, tx_lane_map: str, tx_link_mode: int, tx_m: int, tx_f: int, tx_k: int, tx_l: int, tx_s: int, adc_frequency_hz: int, rx_cddc_decimation: int, rx_fddc_decimation: int, rx_converter_select: str, rx_lane_map: str, rx_link_mode: int, rx_m: int, rx_f: int, rx_k: int, rx_l: int, rx_s: int, spi_max_hz: int = 5000000) dict

Build context dict for ad9081_mxfe.tmpl.

adidt.model.contexts.build_adrv9009_device_ctx(*, phy_family: str, phy_compatible: str, trx_cs: int, spi_max_hz: int = 25000000, gpio_label: str, trx_reset_gpio: int, trx_sysref_req_gpio: int, trx_clocks_value: str, trx_clock_names_value: str, trx_link_ids_value: str, trx_inputs_value: str, trx_profile_props_block: str, is_fmcomms8: bool, trx2_cs: int | None = None, trx2_reset_gpio: int | None = None, trx1_clocks_value: str | None = None) dict

Build context dict for adrv9009.tmpl.

adidt.model.contexts.build_adf4382_ctx(*, label: str = 'adf4382', cs: int, spi_max_hz: int = 1000000, clks_str: str | None = None, clock_output_names_str: str | None = None, power_up_frequency: int | None = None, spi_3wire: bool = True, charge_pump_microamp: int | None = None, output_power: int | None = None) dict

Build context dict for adf4382.tmpl.

adidt.model.contexts.build_ad9084_ctx(*, label: str, cs: int, spi_max_hz: int = 5000000, gpio_label: str, reset_gpio: int | None = None, dev_clk_ref: str, dev_clk_scales: str | None = None, firmware_name: str | None = None, subclass: int = 1, side_b_separate_tpl: bool = False, jrx0_physical_lane_mapping: str | None = None, jtx0_logical_lane_mapping: str | None = None, jrx1_physical_lane_mapping: str | None = None, jtx1_logical_lane_mapping: str | None = None, hsci_label: str | None = None, hsci_auto_linkup: bool = False, link_ids: str = '', jesd204_inputs: str = '') dict

Build context dict for ad9084.tmpl.

adidt.model.contexts.build_adis16495_ctx(*, label: str = 'imu0', device: str = 'adis16495', compatible: str = 'adi,adis16495-1', cs: int = 0, spi_max_hz: int = 2000000, spi_cpol: bool = True, spi_cpha: bool = True, gpio_label: str = 'gpio', interrupt_gpio: int | None = None, irq_type: str = 'IRQ_TYPE_EDGE_FALLING') dict

Build context dict for adis16495.tmpl.

The ADIS16495 is a 6-DOF IMU connected via SPI. This context builder produces a minimal device tree node with SPI mode, interrupt, and compatible string.

adidt.model.contexts.build_adxl345_ctx(*, label: str = 'accel0', device: str = 'adxl345', compatible: str = 'adi,adxl345', cs: int = 0, spi_max_hz: int = 5000000, spi_cpol: bool = True, spi_cpha: bool = True, gpio_label: str = 'gpio', interrupt_gpio: int | None = None, irq_type: str = 'IRQ_TYPE_LEVEL_HIGH') dict

Build context dict for adxl345.tmpl.

adidt.model.contexts.build_ad7124_ctx(*, label: str = 'adc0', device: str = 'ad7124', compatible: str = 'adi,ad7124-8', cs: int = 0, spi_max_hz: int = 5000000, gpio_label: str = 'gpio', interrupt_gpio: int | None = None, irq_type: str = 'IRQ_TYPE_EDGE_FALLING', channels: list[dict] | None = None) dict

Build context dict for ad7124.tmpl.

Parameters:

channels – List of channel dicts with keys id and optional name. If None, creates 8 default channels.

Component Factories

Pre-configured component factories for common ADI devices.

Each factory returns a ComponentModel with the correct role, part name, and template already set. Pass device-specific parameters as keyword arguments — they are forwarded to the matching context builder.

Usage:

from adidt.model.components import adis16495, ad9680, hmc7044

model = BoardModel(
    name="my_board",
    platform="rpi5",
    components=[
        adis16495(spi_bus="spi0", cs=0, interrupt_gpio=25),
    ],
)
adidt.model.components.adis16495(spi_bus: str = 'spi0', cs: int = 0, **kwargs) ComponentModel

ADIS16495 6-DOF IMU.

Common kwargs: label, spi_max_hz, compatible, gpio_label, interrupt_gpio, spi_cpol, spi_cpha.

adidt.model.components.adxl345(spi_bus: str = 'spi0', cs: int = 0, **kwargs) ComponentModel

ADXL345 3-axis accelerometer.

Common kwargs: label, spi_max_hz, compatible, gpio_label, interrupt_gpio.

adidt.model.components.ad7124(spi_bus: str = 'spi0', cs: int = 0, **kwargs) ComponentModel

AD7124 24-bit precision ADC.

Common kwargs: label, spi_max_hz, compatible, gpio_label, interrupt_gpio, channels.

adidt.model.components.hmc7044(spi_bus: str = 'spi0', cs: int = 0, **kwargs) ComponentModel

HMC7044 14-channel clock distributor.

Common kwargs: label, spi_max_hz, pll1_clkin_frequencies, vcxo_hz, pll2_output_hz, clock_output_names, channels.

adidt.model.components.ad9523_1(spi_bus: str = 'spi0', cs: int = 0, **kwargs) ComponentModel

AD9523-1 clock generator.

Common kwargs: label, spi_max_hz, vcxo_hz, gpio_controller, sync_gpio, channels.

adidt.model.components.ad9528(spi_bus: str = 'spi0', cs: int = 0, **kwargs) ComponentModel

AD9528 clock generator.

Common kwargs: label, spi_max_hz, vcxo_hz, channels.

adidt.model.components.ad9680(spi_bus: str = 'spi0', cs: int = 0, **kwargs) ComponentModel

AD9680 dual-channel ADC.

Common kwargs: label, spi_max_hz, clks_str, clk_names_str, sampling_frequency_hz, rx_m, rx_l, rx_f, rx_k, rx_np.

adidt.model.components.ad9144(spi_bus: str = 'spi0', cs: int = 0, **kwargs) ComponentModel

AD9144 quad-channel DAC.

Common kwargs: label, spi_max_hz, clk_ref, jesd204_top_device, jesd204_link_ids, jesd204_inputs.

adidt.model.components.ad9152(spi_bus: str = 'spi0', cs: int = 0, **kwargs) ComponentModel

AD9152 dual-channel DAC.

Common kwargs: label, spi_max_hz, clk_ref, jesd_link_mode.

adidt.model.components.ad9172(spi_bus: str = 'spi0', cs: int = 0, **kwargs) ComponentModel

AD9172 RF DAC.

Common kwargs: label, spi_max_hz, clk_ref, dac_rate_khz, jesd_link_mode, dac_interpolation, channel_interpolation, clock_output_divider.

adidt.model.components.ad9081(spi_bus: str = 'spi0', cs: int = 0, **kwargs) ComponentModel

AD9081 MxFE transceiver (combined ADC + DAC).

Common kwargs: label, gpio_label, reset_gpio, dev_clk_ref, rx_core_label, tx_core_label, dac_frequency_hz, adc_frequency_hz, and JESD params.

adidt.model.components.ad9084(spi_bus: str = 'spi0', cs: int = 0, **kwargs) ComponentModel

AD9084 RX transceiver.

Common kwargs: label, gpio_label, reset_gpio, dev_clk_ref, firmware_name, subclass.