XSA Pipeline Module

The XSA module converts Vivado .xsa archives into Linux device tree sources. See XSA to Device Tree for the user guide and XSA Pipeline — Developer Guide for the architecture documentation.

XSA-to-DeviceTree pipeline: parse Vivado archives and generate Linux DTS overlays.

Topology

Data model and parser for extracting IP topology from a Vivado XSA archive.

class adidt.xsa.topology.Jesd204Instance(name: str, base_addr: int, num_lanes: int, irq: int | None, link_clk: str, direction: str)

Bases: object

One JESD204 TX or RX IP core instance found in the HWH netlist.

name: str
base_addr: int
num_lanes: int
irq: int | None
direction: str
class adidt.xsa.topology.ClkgenInstance(name: str, base_addr: int, output_clks: list[str] = <factory>)

Bases: object

One AXI clock-generator IP core instance found in the HWH netlist.

name: str
base_addr: int
output_clks: list[str]
class adidt.xsa.topology.ConverterInstance(name: str, ip_type: str, base_addr: int, spi_bus: int | None, spi_cs: int | None)

Bases: object

One ADC/DAC/transceiver IP core instance found in the HWH netlist.

name: str
ip_type: str
base_addr: int
spi_bus: int | None
spi_cs: int | None
class adidt.xsa.topology.SignalConnection(signal: str, producers: list[str] = <factory>, consumers: list[str] = <factory>, bidirectional: list[str] = <factory>)

Bases: object

Connectivity information for one HWH signal net.

signal: str
producers: list[str]
consumers: list[str]
bidirectional: list[str]
class adidt.xsa.topology.XsaTopology(jesd204_rx: list[Jesd204Instance] = <factory>, jesd204_tx: list[Jesd204Instance] = <factory>, clkgens: list[ClkgenInstance] = <factory>, converters: list[ConverterInstance] = <factory>, signal_connections: list[SignalConnection] = <factory>, fpga_part: str = '')

Bases: object

Full topology extracted from a single Vivado XSA archive.

jesd204_rx: list[Jesd204Instance]
jesd204_tx: list[Jesd204Instance]
clkgens: list[ClkgenInstance]
converters: list[ConverterInstance]
signal_connections: list[SignalConnection]
fpga_part: str = ''
has_converter_types(*ip_types: str) bool

Return True if every requested ip_types is present among the topology’s converters.

is_fmcdaq2_design() bool

Return True if the topology matches an FMCDAQ2 design (AD9680 + AD9144).

is_fmcdaq3_design() bool

Return True if the topology matches an FMCDAQ3 design (AD9680 + AD9152).

inferred_converter_family() str

Infer a short converter-family string (e.g. "ad9081", "adrv9009") from the topology.

inferred_platform() str

Infer the target platform name (e.g. "zcu102") from the FPGA part string.

class adidt.xsa.topology.XsaParser

Bases: object

Parses a Vivado .xsa file and returns an XsaTopology.

parse_hwh_map(hwh_content: str) dict

Parse a HWH XML string into an IP-instance map and write debug diagram files.

parse(xsa_path: Path) XsaTopology

Extract the hardware topology from an XSA archive and return an XsaTopology.

Pipeline

Orchestrate the full XSA-to-DeviceTree pipeline from archive to merged DTS.

class adidt.xsa.pipeline.XsaPipeline

Bases: object

Orchestrates the five-stage XSA-to-DeviceTree pipeline.

run(xsa_path: Path, cfg: PipelineConfig | dict[str, Any], output_dir: Path, sdtgen_timeout: int = 120, profile: str | None = None, reference_dts: Path | None = None, strict_parity: bool = False, emit_report: bool = False, emit_clock_graphs: bool = False, lint: bool = False, strict_lint: bool = False, output_format: str = 'default') dict[str, Path]

Run the full pipeline.

Parameters:
  • xsa_path – Path to the Vivado .xsa archive.

  • cfg – User-supplied configuration dictionary passed to NodeBuilder.

  • output_dir – Directory where all output files are written. Created automatically if it does not exist.

  • sdtgen_timeout – Maximum seconds to wait for sdtgen to finish generating the base DTS. Defaults to 120.

  • profile – Name of a built-in profile to load (e.g. "adrv9009_zcu102"). When None the pipeline attempts to auto-detect a matching profile from the XSA topology.

  • reference_dts – Optional path to a reference DTS used for parity checking. When provided, "map" and "coverage" keys are added to the result.

  • strict_parity – When True and reference_dts is provided, raise ParityError if the merged DTS is missing required roles, links, or properties.

  • emit_report – When True (default) the HTML topology report is generated and "report" is included in the result dict. Pass False to skip report generation.

  • emit_clock_graphs – When True (default) DOT and D2 clock-tree diagrams are generated and their paths included in the result dict. Pass False to skip clock-graph generation.

  • lint – When True, run the structural DTS linter on the merged DTS and write a diagnostics JSON file. Defaults to False.

  • strict_lint – When True, raise DtsLintError if the linter finds any errors. Implies lint=True.

  • output_format"default" produces the standard overlay + merged outputs. "petalinux" additionally generates a system-user.dtsi and device-tree.bbappend suitable for dropping into a PetaLinux project.

Returns:

Dict always containing "base_dir", "overlay", and "merged". "report" is present when emit_report is True. "clock_dot" and "clock_d2" (plus optionally "clock_dot_svg" / "clock_d2_svg") are present when emit_clock_graphs is True. "map" and "coverage" are present when reference_dts is provided. "diagnostics" is present when lint or strict_lint is True.

Raises:
  • ParityError – When strict_parity is True and the merged DTS fails the parity check against reference_dts.

  • DtsLintError – When strict_lint is True and the linter finds errors in the generated DTS.

Node Builder

Build ADI device-driver DTS overlay nodes from an XSA topology and config.

class adidt.xsa.node_builder.NodeBuilder

Bases: object

Builds ADI DTS node strings from XsaTopology + pyadi-jif JSON config.

build(topology: XsaTopology, cfg: PipelineConfig | dict[str, Any]) dict[str, list[str]]

Render ADI DTS nodes.

Parameters:
  • topology – Parsed XSA topology.

  • cfg – Pipeline configuration as a PipelineConfig or raw dict. Dicts are used as-is for backward compatibility. PipelineConfig instances are converted to dict via PipelineConfig.to_dict().

Returns:

Dict with keys “jesd204_rx”, “jesd204_tx”, “converters”.

DTS Merger

Merge a generated base DTS with ADI overlay nodes into a single DTS file.

class adidt.xsa.merger.DtsMerger

Bases: object

Merges ADI DTS node strings into a base DTS, producing overlay and merged outputs.

merge(base_dts: str, nodes: dict[str, list[str]], output_dir: Path, name: str) tuple[str, str]

Produce overlay (.dtso) and merged (.dts) and write files.

Returns:

(overlay_content, merged_content)

HTML Visualizer

Generate an interactive HTML report visualising XSA topology and DTS output.

class adidt.xsa.visualizer.HtmlVisualizer

Bases: object

Generates a self-contained interactive HTML report.

generate(topology: XsaTopology, cfg: dict[str, Any], merged_dts: str, output_dir: Path, name: str) str

Render and write a self-contained HTML report; returns the HTML string.

SDT Generator Runner

Wrapper for invoking the sdtgen tool to generate a base SDT/DTS from an XSA.

class adidt.xsa.sdtgen.SdtgenRunner(binary: str = 'sdtgen')

Bases: object

Invokes sdtgen as a subprocess to generate a base SDT/DTS from an XSA file.

__init__(binary: str = 'sdtgen')

Initialize the runner with the sdtgen binary name or path.

run(xsa_path: Path, output_dir: Path, timeout: int = 120) Path

Run sdtgen and return the path to the generated base DTS file.

Raises:

Exceptions

Custom exception types for the XSA-to-DeviceTree pipeline.

exception adidt.xsa.exceptions.SdtgenNotFoundError(message: str = 'sdtgen not found on PATH')

Bases: Exception

Raised when sdtgen/lopper binary is not found on PATH.

INSTALL_URL = 'https://github.com/devicetree-org/lopper'
__init__(message: str = 'sdtgen not found on PATH')

Initialize with an optional custom message and the install URL hint.

exception adidt.xsa.exceptions.SdtgenError(message: str, stderr: str = '')

Bases: Exception

Raised when sdtgen exits with a non-zero status or produces no output.

__init__(message: str, stderr: str = '')

Initialize with a human-readable message and optional stderr text.

exception adidt.xsa.exceptions.XsaParseError

Bases: Exception

Raised when the XSA file cannot be parsed.

exception adidt.xsa.exceptions.ConfigError(missing_field: str)

Bases: Exception

Raised when the pyadi-jif JSON config is missing required fields.

__init__(missing_field: str)

Initialize with the name of the missing_field in the config.

exception adidt.xsa.exceptions.ProfileError

Bases: Exception

Raised when a board profile cannot be loaded or is invalid.

exception adidt.xsa.exceptions.ParityError

Bases: Exception

Raised when strict manifest parity checks fail.

exception adidt.xsa.exceptions.DtsLintError(message: str, diagnostics: list | None = None)

Bases: Exception

Raised when strict DTS lint checks find errors.

__init__(message: str, diagnostics: list | None = None)

Initialize with a summary message and optional list of LintDiagnostic.

Board Configurations

Public board configuration dataclasses for the XSA-to-DeviceTree pipeline.

These types formalize the raw dict[str, Any] configuration that the pipeline has historically accepted. Each board family has a dedicated dataclass with typed fields and defaults matching the existing .get(key, default) patterns in adidt.xsa.node_builder.

Backward compatibility

Every config type provides a from_dict class method so that JSON profiles, MCP server requests, and existing test dicts continue to work unchanged:

cfg = FMCDAQ2BoardConfig.from_dict(raw_dict)

Validation

__post_init__ on each type runs the same checks that profiles.py _validate_typed_keys previously performed (non-negative ints, non-empty strings). Construct the object to validate; catch ValueError on failure.

class adidt.xsa.board_configs.JesdLinkParams(F: int = 1, K: int = 32, M: int = 2, L: int = 4, Np: int = 16, S: int = 1)

Bases: object

JESD204 framing parameters for one direction (RX or TX).

F: int = 1
K: int = 32
M: int = 2
L: int = 4
Np: int = 16
S: int = 1
classmethod from_dict(d: dict[str, Any]) JesdLinkParams

Construct from a dict, coercing values to int.

class adidt.xsa.board_configs.JesdConfig(rx: JesdLinkParams = <factory>, tx: JesdLinkParams = <factory>)

Bases: object

JESD204 configuration for RX and TX directions.

rx: JesdLinkParams
tx: JesdLinkParams
classmethod from_dict(d: dict[str, Any]) JesdConfig

Construct from a {"rx": {...}, "tx": {...}} dict.

class adidt.xsa.board_configs.ClockConfig(rx_device_clk_label: str = 'clkgen', rx_device_clk_index: int = 0, tx_device_clk_label: str = 'clkgen', tx_device_clk_index: int = 0, rx_b_device_clk_index: int | None = None, tx_b_device_clk_index: int | None = None, hmc7044_rx_channel: int | None = None, hmc7044_tx_channel: int | None = None)

Bases: object

Clock routing configuration shared across board families.

rx_device_clk_label: str = 'clkgen'
rx_device_clk_index: int = 0
tx_device_clk_label: str = 'clkgen'
tx_device_clk_index: int = 0
rx_b_device_clk_index: int | None = None
tx_b_device_clk_index: int | None = None
hmc7044_rx_channel: int | None = None
hmc7044_tx_channel: int | None = None
classmethod from_dict(d: dict[str, Any]) ClockConfig

Construct from a clock config dict, ignoring unknown keys.

class adidt.xsa.board_configs.FMCDAQ2BoardConfig(spi_bus: str = 'spi0', clock_cs: int = 0, adc_cs: int = 2, dac_cs: int = 1, clock_vcxo_hz: int = 125000000, clock_spi_max_frequency: int = 10000000, adc_spi_max_frequency: int = 1000000, dac_spi_max_frequency: int = 1000000, adc_dma_label: str = 'axi_ad9680_dma', dac_dma_label: str = 'axi_ad9144_dma', adc_core_label: str = 'axi_ad9680_core', dac_core_label: str = 'axi_ad9144_core', adc_xcvr_label: str = 'axi_ad9680_adxcvr', dac_xcvr_label: str = 'axi_ad9144_adxcvr', adc_jesd_label: str = 'axi_ad9680_jesd204_rx', dac_jesd_label: str = 'axi_ad9144_jesd204_tx', adc_jesd_link_id: int = 0, dac_jesd_link_id: int = 0, gpio_controller: str = 'gpio0', adc_device_clk_idx: int = 13, adc_sysref_clk_idx: int = 5, adc_xcvr_ref_clk_idx: int = 4, adc_sampling_frequency_hz: int = 1000000000, dac_device_clk_idx: int = 1, dac_xcvr_ref_clk_idx: int = 9, clk_sync_gpio: Any = None, clk_status0_gpio: Any = None, clk_status1_gpio: Any = None, dac_txen_gpio: Any = None, dac_reset_gpio: Any = None, dac_irq_gpio: Any = None, adc_powerdown_gpio: Any = None, adc_fastdetect_a_gpio: Any = None, adc_fastdetect_b_gpio: Any = None)

Bases: object

Board-level configuration for FMCDAQ2 designs (AD9523-1 + AD9680 + AD9144).

Example:

cfg = FMCDAQ2BoardConfig.from_dict({
    "spi_bus": "spi0",
    "clock_cs": 0,
    "adc_cs": 2,
    "dac_cs": 1,
})
spi_bus: str = 'spi0'
clock_cs: int = 0
adc_cs: int = 2
dac_cs: int = 1
clock_vcxo_hz: int = 125000000
clock_spi_max_frequency: int = 10000000
adc_spi_max_frequency: int = 1000000
dac_spi_max_frequency: int = 1000000
adc_dma_label: str = 'axi_ad9680_dma'
dac_dma_label: str = 'axi_ad9144_dma'
adc_core_label: str = 'axi_ad9680_core'
dac_core_label: str = 'axi_ad9144_core'
adc_xcvr_label: str = 'axi_ad9680_adxcvr'
dac_xcvr_label: str = 'axi_ad9144_adxcvr'
adc_jesd_label: str = 'axi_ad9680_jesd204_rx'
dac_jesd_label: str = 'axi_ad9144_jesd204_tx'
gpio_controller: str = 'gpio0'
adc_device_clk_idx: int = 13
adc_sysref_clk_idx: int = 5
adc_xcvr_ref_clk_idx: int = 4
adc_sampling_frequency_hz: int = 1000000000
dac_device_clk_idx: int = 1
dac_xcvr_ref_clk_idx: int = 9
clk_sync_gpio: Any = None
clk_status0_gpio: Any = None
clk_status1_gpio: Any = None
dac_txen_gpio: Any = None
dac_reset_gpio: Any = None
dac_irq_gpio: Any = None
adc_powerdown_gpio: Any = None
adc_fastdetect_a_gpio: Any = None
adc_fastdetect_b_gpio: Any = None
classmethod from_dict(d: dict[str, Any]) FMCDAQ2BoardConfig

Construct from a board config dict, ignoring unknown keys.

class adidt.xsa.board_configs.FMCDAQ3BoardConfig(spi_bus: str = 'spi0', clock_cs: int = 0, adc_cs: int = 2, dac_cs: int = 1, clock_vcxo_hz: int = 100000000, clock_spi_max_frequency: int = 10000000, adc_spi_max_frequency: int = 10000000, dac_spi_max_frequency: int = 10000000, adc_dma_label: str = 'axi_ad9680_dma', dac_dma_label: str = 'axi_ad9152_dma', adc_core_label: str = 'axi_ad9680_tpl_core_adc_tpl_core', dac_core_label: str = 'axi_ad9152_tpl_core_dac_tpl_core', adc_xcvr_label: str = 'axi_ad9680_xcvr', dac_xcvr_label: str = 'axi_ad9152_xcvr', adc_jesd_label: str = 'axi_ad9680_jesd_rx_axi', dac_jesd_label: str = 'axi_ad9152_jesd_tx_axi', adc_jesd_link_id: int = 0, dac_jesd_link_id: int = 0, gpio_controller: str = 'gpio', adc_device_clk_idx: int = 13, adc_xcvr_ref_clk_idx: int = 9, adc_sampling_frequency_hz: int = 1233333333, dac_device_clk_idx: int = 2, dac_xcvr_ref_clk_idx: int = 4, clk_status0_gpio: Any = None, clk_status1_gpio: Any = None, dac_txen_gpio: Any = None, dac_irq_gpio: Any = None, adc_powerdown_gpio: Any = None, adc_fastdetect_a_gpio: Any = None, adc_fastdetect_b_gpio: Any = None, ad9152_jesd_link_mode: int = 4)

Bases: object

Board-level configuration for FMCDAQ3 designs (AD9528 + AD9680 + AD9152).

spi_bus: str = 'spi0'
clock_cs: int = 0
adc_cs: int = 2
dac_cs: int = 1
clock_vcxo_hz: int = 100000000
clock_spi_max_frequency: int = 10000000
adc_spi_max_frequency: int = 10000000
dac_spi_max_frequency: int = 10000000
adc_dma_label: str = 'axi_ad9680_dma'
dac_dma_label: str = 'axi_ad9152_dma'
adc_core_label: str = 'axi_ad9680_tpl_core_adc_tpl_core'
dac_core_label: str = 'axi_ad9152_tpl_core_dac_tpl_core'
adc_xcvr_label: str = 'axi_ad9680_xcvr'
dac_xcvr_label: str = 'axi_ad9152_xcvr'
adc_jesd_label: str = 'axi_ad9680_jesd_rx_axi'
dac_jesd_label: str = 'axi_ad9152_jesd_tx_axi'
gpio_controller: str = 'gpio'
adc_device_clk_idx: int = 13
adc_xcvr_ref_clk_idx: int = 9
adc_sampling_frequency_hz: int = 1233333333
dac_device_clk_idx: int = 2
dac_xcvr_ref_clk_idx: int = 4
clk_status0_gpio: Any = None
clk_status1_gpio: Any = None
dac_txen_gpio: Any = None
dac_irq_gpio: Any = None
adc_powerdown_gpio: Any = None
adc_fastdetect_a_gpio: Any = None
adc_fastdetect_b_gpio: Any = None
classmethod from_dict(d: dict[str, Any]) FMCDAQ3BoardConfig

Construct from a board config dict, ignoring unknown keys.

class adidt.xsa.board_configs.AD9172BoardConfig(spi_bus: str = 'spi0', clock_cs: int = 0, dac_cs: int = 1, clock_spi_max_frequency: int = 10000000, dac_spi_max_frequency: int = 1000000, dac_core_label: str = 'axi_ad9172_core', dac_xcvr_label: str = 'axi_ad9172_adxcvr', dac_jesd_label: str = 'axi_ad9172_jesd_tx_axi', dac_jesd_link_id: int = 0, hmc7044_ref_clk_hz: int = 122880000, hmc7044_vcxo_hz: int = 122880000, hmc7044_out_freq_hz: int = 2949120000, ad9172_dac_rate_khz: int = 11796480, ad9172_jesd_link_mode: int = 4, ad9172_dac_interpolation: int = 8, ad9172_channel_interpolation: int = 4, ad9172_clock_output_divider: int = 4)

Bases: object

Board-level configuration for AD9172 DAC designs (HMC7044 + AD9172).

spi_bus: str = 'spi0'
clock_cs: int = 0
dac_cs: int = 1
clock_spi_max_frequency: int = 10000000
dac_spi_max_frequency: int = 1000000
dac_core_label: str = 'axi_ad9172_core'
dac_xcvr_label: str = 'axi_ad9172_adxcvr'
dac_jesd_label: str = 'axi_ad9172_jesd_tx_axi'
hmc7044_ref_clk_hz: int = 122880000
hmc7044_vcxo_hz: int = 122880000
hmc7044_out_freq_hz: int = 2949120000
ad9172_dac_rate_khz: int = 11796480
ad9172_dac_interpolation: int = 8
ad9172_channel_interpolation: int = 4
ad9172_clock_output_divider: int = 4
classmethod from_dict(d: dict[str, Any]) AD9172BoardConfig

Construct from a board config dict, ignoring unknown keys.

class adidt.xsa.board_configs.AD9084BoardConfig(converter_spi: str = 'axi_spi_2', converter_cs: int = 0, clock_spi: str = 'axi_spi', hmc7044_cs: int = 1, converter_spi_max_hz: int = 1000000, hmc7044_spi_max_hz: int = 1000000, adf4382_cs: int | None = None, pll1_clkin_frequencies: list[int] = <factory>, vcxo_hz: int = 125000000, pll2_output_hz: int = 2500000000, fpga_refclk_channel: int = 10, pll1_loop_bandwidth_hz: int = 200, pll1_ref_prio_ctrl: str = '0xE1', pll1_ref_autorevert: bool = True, pll1_charge_pump_ua: int = 720, pfd1_max_freq_hz: int = 1000000, sysref_timer_divider: int = 1024, pulse_generator_mode: int = 0, clkin0_buffer_mode: str = '0x07', clkin1_buffer_mode: str = '0x07', oscin_buffer_mode: str = '0x15', gpi_controls: list[int] = <factory>, gpo_controls: list[int] = <factory>, jesd204_max_sysref_hz: int = 2000000, hmc7044_channels: list[dict[str, ~typing.Any]] | None=None, hmc7044_channel_blocks: list[Any] | None = None, dev_clk_source: str | None = None, dev_clk_ref: str | None = None, dev_clk_scales: str | None = None, dev_clk_channel: int = 9, firmware_name: str | None = None, reset_gpio: int | None = None, subclass: int = 0, side_b_separate_tpl: bool = True, rx_sys_clk_select: int = 3, tx_sys_clk_select: int = 3, rx_out_clk_select: int = 4, tx_out_clk_select: int = 4, rx_a_link_id: int = 0, rx_b_link_id: int = 1, tx_a_link_id: int = 2, tx_b_link_id: int = 3, 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_speed_mhz: int = 800, hsci_auto_linkup: bool = False)

Bases: object

Board-level configuration for AD9084 dual-link designs.

Captures SPI bus assignments, clock chip settings, XCVR PLL selection, JESD204 link IDs, and lane mappings specific to AD9084 boards (e.g., AD9084-FMC-EBZ on VCU118).

Example:

cfg = AD9084BoardConfig.from_dict({
    "converter_spi": "axi_spi_2",
    "converter_cs": 0,
    "clock_spi": "axi_spi",
    "hmc7044_cs": 1,
})
converter_spi: str = 'axi_spi_2'
converter_cs: int = 0
clock_spi: str = 'axi_spi'
hmc7044_cs: int = 1
converter_spi_max_hz: int = 1000000
hmc7044_spi_max_hz: int = 1000000
adf4382_cs: int | None = None
pll1_clkin_frequencies: list[int]
vcxo_hz: int = 125000000
pll2_output_hz: int = 2500000000
fpga_refclk_channel: int = 10
pll1_loop_bandwidth_hz: int = 200
pll1_ref_prio_ctrl: str = '0xE1'
pll1_ref_autorevert: bool = True
pll1_charge_pump_ua: int = 720
pfd1_max_freq_hz: int = 1000000
sysref_timer_divider: int = 1024
pulse_generator_mode: int = 0
clkin0_buffer_mode: str = '0x07'
clkin1_buffer_mode: str = '0x07'
oscin_buffer_mode: str = '0x15'
gpi_controls: list[int]
gpo_controls: list[int]
jesd204_max_sysref_hz: int = 2000000
hmc7044_channels: list[dict[str, Any]] | None = None
hmc7044_channel_blocks: list[Any] | None = None
dev_clk_source: str | None = None
dev_clk_ref: str | None = None
dev_clk_scales: str | None = None
dev_clk_channel: int = 9
firmware_name: str | None = None
reset_gpio: int | None = None
subclass: int = 0
side_b_separate_tpl: bool = True
rx_sys_clk_select: int = 3
tx_sys_clk_select: int = 3
rx_out_clk_select: int = 4
tx_out_clk_select: int = 4
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_speed_mhz: int = 800
hsci_auto_linkup: bool = False
classmethod from_dict(d: dict[str, Any]) AD9084BoardConfig

Construct from a board config dict, ignoring unknown keys.

class adidt.xsa.board_configs.AD9081BoardConfig(clock_spi: str = 'spi1', clock_cs: int = 0, adc_spi: str = 'spi0', adc_cs: int = 0, reset_gpio: int | None = None, sysref_req_gpio: int | None = None, rx1_enable_gpio: int | None = None, rx2_enable_gpio: int | None = None, tx1_enable_gpio: int | None = None, tx2_enable_gpio: int | None = None, hmc7044_channel_blocks: list[Any] | None = None)

Bases: object

Board-level configuration for AD9081/AD9082/AD9083 MxFE designs.

clock_spi: str = 'spi1'
clock_cs: int = 0
adc_spi: str = 'spi0'
adc_cs: int = 0
reset_gpio: int | None = None
sysref_req_gpio: int | None = None
rx1_enable_gpio: int | None = None
rx2_enable_gpio: int | None = None
tx1_enable_gpio: int | None = None
tx2_enable_gpio: int | None = None
hmc7044_channel_blocks: list[Any] | None = None
classmethod from_dict(d: dict[str, Any]) AD9081BoardConfig

Construct from a board config dict, ignoring unknown keys.

class adidt.xsa.board_configs.ADRV9009BoardConfig(spi_bus: str = 'spi0', clk_cs: int = 0, trx_cs: int = 1, misc_clk_hz: int = 0, trx_reset_gpio: int | None = None, trx_sysref_req_gpio: int | None = None, trx_spi_max_frequency: int = 1000000, ad9528_vcxo_freq: int = 122880000, rx_link_id: int = 0, rx_os_link_id: int = 1, tx_link_id: int = 2, tx_octets_per_frame: int | None = None, rx_os_octets_per_frame: int | None = None, trx_profile_props: list[Any] | None = None, ad9528_channel_blocks: list[Any] | None = None)

Bases: object

Board-level configuration for ADRV9009/9025/9026 transceiver designs.

spi_bus: str = 'spi0'
clk_cs: int = 0
trx_cs: int = 1
misc_clk_hz: int = 0
trx_reset_gpio: int | None = None
trx_sysref_req_gpio: int | None = None
trx_spi_max_frequency: int = 1000000
ad9528_vcxo_freq: int = 122880000
tx_octets_per_frame: int | None = None
rx_os_octets_per_frame: int | None = None
trx_profile_props: list[Any] | None = None
ad9528_channel_blocks: list[Any] | None = None
classmethod from_dict(d: dict[str, Any]) ADRV9009BoardConfig

Construct from a board config dict, ignoring unknown keys.

Pipeline Configuration

Top-level pipeline configuration wrapping JESD, clock, and board configs.

PipelineConfig is the typed entry point for all pipeline configuration. It can be constructed from a raw dict (backward compatible) or directly with typed sub-configs:

# From a raw dict (JSON profile, MCP server, existing tests)
cfg = PipelineConfig.from_dict(raw_dict)

# Directly with typed configs
cfg = PipelineConfig(
    jesd=JesdConfig(rx=JesdLinkParams(F=4, K=32)),
    clock=ClockConfig(rx_device_clk_label="hmc7044"),
    fmcdaq2_board=FMCDAQ2BoardConfig(spi_bus="spi0"),
)
class adidt.xsa.pipeline_config.PipelineConfig(jesd: JesdConfig = <factory>, clock: ClockConfig = <factory>, fmcdaq2_board: FMCDAQ2BoardConfig | None = None, fmcdaq3_board: FMCDAQ3BoardConfig | None = None, ad9172_board: AD9172BoardConfig | None = None, ad9084_board: AD9084BoardConfig | None = None, ad9081_board: AD9081BoardConfig | None = None, adrv9009_board: ADRV9009BoardConfig | None = None, fpga_adc: dict[str, ~typing.Any]=<factory>, fpga_dac: dict[str, ~typing.Any]=<factory>, _extra: dict[str, ~typing.Any]=<factory>)

Bases: object

Top-level configuration for the XSA-to-DeviceTree pipeline.

Wraps JesdConfig, ClockConfig, and an optional board-family config. At most one board config should be set.

The raw dict form (cfg["jesd"]["rx"]["F"]) is still accepted everywhere via from_dict(), which auto-detects the board family from key presence.

jesd: JesdConfig
clock: ClockConfig
fmcdaq2_board: FMCDAQ2BoardConfig | None = None
fmcdaq3_board: FMCDAQ3BoardConfig | None = None
ad9172_board: AD9172BoardConfig | None = None
ad9084_board: AD9084BoardConfig | None = None
ad9081_board: AD9081BoardConfig | None = None
adrv9009_board: ADRV9009BoardConfig | None = None
fpga_adc: dict[str, Any]
fpga_dac: dict[str, Any]
classmethod from_dict(d: dict[str, Any]) PipelineConfig

Construct from a raw config dict, auto-detecting board family.

This is the backward-compatibility bridge for JSON profiles, MCP server requests, and existing test dicts.

to_dict() dict[str, Any]

Convert back to a raw dict (for serialization or backward compat).

Builders

Per-board builder modules for the XSA-to-DeviceTree pipeline.

Each builder implements the BoardBuilder protocol and is responsible for a single board family (e.g., FMCDAQ2, AD9084). The NodeBuilder iterates registered builders, calling matches() to determine which builder handles the current topology, then build_nodes() to generate the DTS node strings.

Adding a new board family:

  1. Create builders/new_board.py implementing BoardBuilder.

  2. Add it to DEFAULT_BUILDERS below.

  3. No changes to node_builder.py or pipeline.py are needed.

class adidt.xsa.builders.BoardBuilder(*args, **kwargs)

Bases: Protocol

Protocol for board-family specific DTS node builders.

Each builder is responsible for: - Detecting whether a given topology + config matches this board family. - Generating all DTS node strings for the matched design. - Reporting which JESD/clkgen/converter instances it handles (so the

generic rendering loop in NodeBuilder can skip them).

matches(topology: XsaTopology, cfg: dict[str, Any]) bool

Return True if topology and cfg represent this board family.

build_nodes(node_builder: Any, topology: XsaTopology, cfg: dict[str, Any], ps_clk_label: str, ps_clk_index: int | None, gpio_label: str) list[str]

Generate DTS node strings for this board family.

Parameters:
  • node_builder – The owning NodeBuilder instance, providing access to _render(), _wrap_spi_bus(), and other shared infrastructure.

  • topology – Parsed XSA topology.

  • cfg – Raw pipeline config dict.

  • ps_clk_label – Platform PS clock label (e.g., "zynqmp_clk").

  • ps_clk_index – Platform PS clock index (e.g., 71).

  • gpio_label – Platform GPIO controller label.

Returns:

List of DTS node strings to append to result["converters"].

skips_generic_jesd() bool

Return True if this builder handles its own JESD/clkgen rendering.

When True, the generic JESD RX/TX and converter rendering loops in NodeBuilder.build() will skip instances that belong to this design.

skip_ip_types() set[str]

Return the set of converter IP types handled by this builder.

Used by the generic converter rendering loop to skip converters that this builder will render itself.

Template Contexts

Typed context dataclasses for Jinja2 DTS node templates.

Each dataclass corresponds to a .tmpl template in adidt/templates/xsa/ and defines the exact fields that the template expects. Using typed contexts instead of raw dicts ensures that all call sites for a shared template produce the same shape, catching mismatches at construction time.

Usage:

ctx = AdxcvrContext(label="axi_ad9680_adxcvr", sys_clk_select=0, ...)
rendered = node_builder._render("adxcvr.tmpl", ctx.as_dict())
class adidt.xsa.template_contexts.AdxcvrContext(label: str, sys_clk_select: int, out_clk_select: int, clk_ref: str, use_div40: bool, div40_clk_ref: str | None, clock_output_names_str: str, use_lpm_enable: bool, jesd_l: int | None, jesd_m: int | None, jesd_s: int | None, jesd204_inputs: str | None, is_rx: bool)

Bases: object

Context for adxcvr.tmpl (Xilinx transceiver overlay node).

Used by FMCDAQ2, FMCDAQ3, AD9084, AD9172, and ADRV9009 builders.

label: str
sys_clk_select: int
out_clk_select: int
clk_ref: str
use_div40: bool
div40_clk_ref: str | None
clock_output_names_str: str
use_lpm_enable: bool
jesd_l: int | None
jesd_m: int | None
jesd_s: int | None
jesd204_inputs: str | None
is_rx: bool
as_dict() dict[str, Any]

Convert to a template context dict.

class adidt.xsa.template_contexts.Jesd204OverlayContext(label: str, direction: str, clocks_str: str, clock_names_str: str, clock_output_name: str | None, f: int, k: int, jesd204_inputs: str, converter_resolution: int | None, converters_per_device: int | None, bits_per_sample: int | None, control_bits_per_sample: int | None)

Bases: object

Context for jesd204_overlay.tmpl (JESD204 link-layer overlay node).

Used by FMCDAQ2, FMCDAQ3, AD9084, AD9172, and ADRV9009 builders.

label: str
direction: str
clocks_str: str
clock_names_str: str
clock_output_name: str | None
f: int
k: int
jesd204_inputs: str
converter_resolution: int | None
converters_per_device: int | None
bits_per_sample: int | None
control_bits_per_sample: int | None
as_dict() dict[str, Any]

Convert to a template context dict.

class adidt.xsa.template_contexts.TplCoreContext(label: str, compatible: str, direction: str, dma_label: str, spibus_label: str, jesd_label: str, jesd_link_offset: int, link_id: int, pl_fifo_enable: bool, sampl_clk_ref: str | None, sampl_clk_name: str | None)

Bases: object

Context for tpl_core.tmpl (JESD204 transport-protocol-layer core).

Used by FMCDAQ2, FMCDAQ3, AD9084, AD9081, and AD9172 builders.

label: str
compatible: str
direction: str
dma_label: str
spibus_label: str
jesd_label: str
pl_fifo_enable: bool
sampl_clk_ref: str | None
sampl_clk_name: str | None
as_dict() dict[str, Any]

Convert to a template context dict.

class adidt.xsa.template_contexts.Hmc7044ChannelContext(id: int, name: str, divider: int, freq_str: str, driver_mode: int = 2, coarse_digital_delay: int | None = None, startup_mode_dynamic: bool = False, high_perf_mode_disable: bool = False, is_sysref: bool = False)

Bases: object

Context for one HMC7044 channel child node.

id: int
name: str
divider: int
freq_str: str
driver_mode: int = 2
coarse_digital_delay: int | None = None
startup_mode_dynamic: bool = False
high_perf_mode_disable: bool = False
is_sysref: bool = False
as_dict() dict[str, Any]

Convert to a template context dict.

class adidt.xsa.template_contexts.Hmc7044Context(label: str, cs: int, spi_max_hz: int, pll1_clkin_frequencies: list[int], vcxo_hz: int, pll2_output_hz: int, clock_output_names_str: str, jesd204_sysref_provider: bool = True, jesd204_max_sysref_hz: int = 2000000, pll1_loop_bandwidth_hz: int | None = None, pll1_ref_prio_ctrl: str | None = None, pll1_ref_autorevert: bool = False, pll1_charge_pump_ua: int | None = None, pfd1_max_freq_hz: int | None = None, sysref_timer_divider: int | None = None, pulse_generator_mode: int | None = None, clkin0_buffer_mode: str | None = None, clkin1_buffer_mode: str | None = None, clkin2_buffer_mode: str | None = None, clkin3_buffer_mode: str | None = None, oscin_buffer_mode: str | None = None, gpi_controls_str: str = '', gpo_controls_str: str = '', sync_pin_mode: int | None = None, high_perf_mode_dist_enable: bool = False, clkin0_ref: str | None = None, channels: list[dict[str, Any]] | None = None, raw_channels: str | None = None)

Bases: object

Context for hmc7044.tmpl (HMC7044 clock distribution IC).

Used by AD9084, AD9081, AD9172, and ADRV9009 (FMComms8) builders.

label: str
cs: int
spi_max_hz: int
pll1_clkin_frequencies: list[int]
vcxo_hz: int
pll2_output_hz: int
clock_output_names_str: str
jesd204_sysref_provider: bool = True
jesd204_max_sysref_hz: int = 2000000
pll1_loop_bandwidth_hz: int | None = None
pll1_ref_prio_ctrl: str | None = None
pll1_ref_autorevert: bool = False
pll1_charge_pump_ua: int | None = None
pfd1_max_freq_hz: int | None = None
sysref_timer_divider: int | None = None
pulse_generator_mode: int | None = None
clkin0_buffer_mode: str | None = None
clkin1_buffer_mode: str | None = None
clkin2_buffer_mode: str | None = None
clkin3_buffer_mode: str | None = None
oscin_buffer_mode: str | None = None
gpi_controls_str: str = ''
gpo_controls_str: str = ''
sync_pin_mode: int | None = None
high_perf_mode_dist_enable: bool = False
clkin0_ref: str | None = None
channels: list[dict[str, Any]] | None = None
raw_channels: str | None = None
as_dict() dict[str, Any]

Convert to a template context dict.

class adidt.xsa.template_contexts.ClkgenContext(instance: Any, ps_clk_label: str, ps_clk_index: int | None)

Bases: object

Context for clkgen.tmpl (AXI clock generator overlay node).

instance: Any
ps_clk_label: str
ps_clk_index: int | None
as_dict() dict[str, Any]

Convert to a template context dict.

class adidt.xsa.template_contexts.Ad9523Context(label: str, cs: int, spi_max_hz: int, vcxo_hz: int, gpio_lines: list[dict[str, Any]], channels: list[dict[str, Any]])

Bases: object

Context for ad9523_1.tmpl (AD9523-1 clock generator).

label: str
cs: int
spi_max_hz: int
vcxo_hz: int
gpio_lines: list[dict[str, Any]]
channels: list[dict[str, Any]]
as_dict() dict[str, Any]

Convert to a template context dict.

class adidt.xsa.template_contexts.Ad9528Context(label: str, cs: int, spi_max_hz: int, vcxo_hz: int, gpio_lines: list[dict[str, Any]], channels: list[dict[str, Any]])

Bases: object

Context for ad9528.tmpl and ad9528_1.tmpl (AD9528 clock generator).

label: str
cs: int
spi_max_hz: int
vcxo_hz: int
gpio_lines: list[dict[str, Any]]
channels: list[dict[str, Any]]
as_dict() dict[str, Any]

Convert to a template context dict.

class adidt.xsa.template_contexts.Ad9680Context(label: str, cs: int, spi_max_hz: int, use_spi_3wire: bool, clks_str: str, clk_names_str: str, sampling_frequency_hz: int, m: int, l: int, f: int, k: int, np: int, jesd204_top_device: int, jesd204_link_ids: list[int], jesd204_inputs: str, gpio_lines: list[dict[str, Any]])

Bases: object

Context for ad9680.tmpl (AD9680 ADC).

label: str
cs: int
spi_max_hz: int
use_spi_3wire: bool
clks_str: str
clk_names_str: str
sampling_frequency_hz: int
m: int
l: int
f: int
k: int
np: int
jesd204_top_device: int
jesd204_inputs: str
gpio_lines: list[dict[str, Any]]
as_dict() dict[str, Any]

Convert to a template context dict.

class adidt.xsa.template_contexts.Ad9144Context(label: str, cs: int, spi_max_hz: int, clk_ref: str, jesd204_top_device: int, jesd204_link_ids: list[int], jesd204_inputs: str, gpio_lines: list[dict[str, Any]])

Bases: object

Context for ad9144.tmpl (AD9144 DAC).

label: str
cs: int
spi_max_hz: int
clk_ref: str
jesd204_top_device: int
jesd204_inputs: str
gpio_lines: list[dict[str, Any]]
as_dict() dict[str, Any]

Convert to a template context dict.

class adidt.xsa.template_contexts.Ad9152Context(label: str, cs: int, spi_max_hz: int, clk_ref: str, jesd_link_mode: int, jesd204_top_device: int, jesd204_link_ids: list[int], jesd204_inputs: str, gpio_lines: list[dict[str, Any]])

Bases: object

Context for ad9152.tmpl (AD9152 DAC).

label: str
cs: int
spi_max_hz: int
clk_ref: str
jesd204_top_device: int
jesd204_inputs: str
gpio_lines: list[dict[str, Any]]
as_dict() dict[str, Any]

Convert to a template context dict.

class adidt.xsa.template_contexts.Ad9172DeviceContext(label: str, cs: int, spi_max_hz: int, clk_ref: str, dac_rate_khz: int, jesd_link_mode: int, dac_interpolation: int, channel_interpolation: int, clock_output_divider: int, jesd_link_ids: list[int], jesd204_inputs: str)

Bases: object

Context for ad9172.tmpl (AD9172 DAC device node).

label: str
cs: int
spi_max_hz: int
clk_ref: str
dac_rate_khz: int
dac_interpolation: int
channel_interpolation: int
clock_output_divider: int
jesd204_inputs: str
as_dict() dict[str, Any]

Convert to a template context dict.

DTS Linter

Structural DTS linter for generated device tree source files.

Operates on merged DTS text using regex-based parsing — no external tools (dtc, dt-schema) required. Produces a list of LintDiagnostic items with severity, rule ID, node location, and actionable message.

Usage:

from adidt.xsa.dts_lint import DtsLinter

diagnostics = DtsLinter().lint(dts_text)
errors = [d for d in diagnostics if d.severity == "error"]
class adidt.xsa.dts_lint.LintDiagnostic(severity: str, rule: str, node: str, message: str, binding_confidence: str | None = None)

Bases: object

One issue found by the DTS linter.

severity: str
rule: str
node: str
message: str
binding_confidence: str | None = None
class adidt.xsa.dts_lint.DtsLinter

Bases: object

Structural linter for generated DTS files.

Example:

linter = DtsLinter()
diagnostics = linter.lint(dts_text)
for d in diagnostics:
    print(d)
lint(dts_text: str, topology: Any = None, bindings: Any = None) list[LintDiagnostic]

Run all lint rules on dts_text and return diagnostics.

Parameters:
  • dts_text – Merged DTS content as a string.

  • topology – Optional XsaTopology for topology-aware rules.

  • bindings – Optional BindingRegistry for binding cross-reference rules (Phase 8).

Returns:

List of LintDiagnostic items, sorted by severity.

lint_file(dts_path: Path, topology: Any = None, bindings: Any = None) list[LintDiagnostic]

Convenience wrapper that reads a file and lints its content.