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.parse.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.parse.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.parse.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.parse.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.parse.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.parse.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 = True, emit_clock_graphs: bool = True, emit_wiring_graph: bool = True, 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.

  • emit_wiring_graph – When True (default) a control-plane wiring graph (SPI / JESD / GPIO / IRQ / I2C edges) is generated as DOT + D2 alongside the clock-tree diagrams. Pass False to skip.

  • 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. "wiring_dot" and "wiring_d2" (plus optionally "wiring_dot_svg" / "wiring_d2_svg") are present when emit_wiring_graph 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.

run_petalinux_only(xsa_path: Path, cfg: PipelineConfig | dict[str, Any], output_dir: Path, profile: str | None = None) dict[str, Path]

Generate system-user.dtsi + device-tree.bbappend without sdtgen.

Use when PetaLinux’s own DTG (invoked by petalinux-config --get-hw-description) provides the base device tree — pyadi-dt’s sdtgen call is then redundant and forces a Vitis dependency that callers in PetaLinux-only environments don’t have.

Produces the same system-user.dtsi and device-tree.bbappend as run() with output_format="petalinux", but skips sdtgen, the merged DTS, the HTML report, and the clock graphs.

The overlay’s bus reference (&amba vs &amba_pl) is determined the same way: a stub base DTS declares amba; the PetalinuxFormatter rewrites to amba_pl for ZynqMP platforms based on topology.inferred_platform().

Returns:

Dict with "system_user_dtsi" and "bbappend" keys.

Node Builder

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

class adidt.xsa.build.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.merge.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.viz.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.

Wiring Graph

Generate control-plane wiring graphs for an XSA design or System composition.

Renders a single combined diagram showing SPI buses, JESD204 links, GPIO control lines, interrupts, and I2C buses, with edges color-keyed by kind. Two output formats: Graphviz DOT and D2 (with optional SVG when dot / d2 are on PATH).

Two input adapters:

class adidt.xsa.viz.wiring_graph.WiringNode(label: str, node_name: str, kind: str)

Bases: object

One node in the wiring graph.

label is the unique identifier (DTS label). node_name is the DTS node name with optional @addr suffix; falls back to label when no node name is known. kind is the visual category used to colour the node — one of the categories returned by adidt.xsa.viz._common.categorise() plus the wiring-specific spi_master, gpio_controller, i2c_master, and interrupt_controller synthetic categories.

label: str
node_name: str
kind: str
class adidt.xsa.viz.wiring_graph.WiringEdge(src: str, dst: str, kind: Literal['spi', 'jesd', 'gpio', 'irq', 'i2c'], label: str = '')

Bases: object

One directed edge in the wiring graph.

src: str
dst: str
kind: Literal['spi', 'jesd', 'gpio', 'irq', 'i2c']
label: str = ''
class adidt.xsa.viz.wiring_graph.WiringGraph(nodes: list[WiringNode] = <factory>, edges: list[WiringEdge] = <factory>)

Bases: object

Collected nodes + edges, source-agnostic.

nodes: list[WiringNode]
edges: list[WiringEdge]
add_node(label: str, *, node_name: str | None = None, kind: str | None = None) None
add_edge(edge: WiringEdge) None
classmethod from_topology(topology: XsaTopology, cfg: dict[str, Any] | None = None, merged_dts: str | None = None) WiringGraph

Build a WiringGraph from an XSA topology.

cfg is currently unused but accepted for symmetry with the pipeline call sites that already plumb config dicts. merged_dts enables GPIO and I2C edge extraction; without it those edge kinds are skipped.

classmethod from_system(system: Any) WiringGraph

Build a WiringGraph from a adidt.system.System.

class adidt.xsa.viz.wiring_graph.WiringGraphGenerator

Bases: object

Write DOT + D2 wiring diagrams (and optional SVGs) for a graph.

generate(graph: WiringGraph, output_dir: Path, name: str, *, kinds: set[Literal['spi', 'jesd', 'gpio', 'irq', 'i2c']] | None = None) dict[str, Path]

Render graph to {name}_wiring.dot/.d2 under output_dir.

Returns a dict of artifact paths with keys wiring_dot, wiring_d2, plus wiring_dot_svg / wiring_d2_svg when the respective renderer is available on PATH.

kinds filters edges to the given kinds; None keeps all.

SDT Generator Runner

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

class adidt.xsa.parse.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.build.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.config.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.config.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.config.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.config.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.config.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.config.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.config.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.config.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.config.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.config.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.build.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.

class adidt.xsa.build.builders.ADRV937xBuilder

Bases: object

Board builder for ADRV937x (AD9371) transceiver designs.

matches(topology: XsaTopology, cfg: dict[str, Any]) bool
skips_generic_jesd() bool
skip_ip_types() set[str]
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]
build_model(topology: XsaTopology, cfg: dict[str, Any], ps_clk_label: str, ps_clk_index: int | None, gpio_label: str) BoardModel | None

Construct a BoardModel for an ADRV937x (AD9371) design.

Returns None if no ADRV937x instances are found in the topology.

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.validate.dts_lint import DtsLinter

diagnostics = DtsLinter().lint(dts_text)
errors = [d for d in diagnostics if d.severity == "error"]
class adidt.xsa.validate.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.validate.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.