Source code for FIREQ_SYSTEM.acquisition_node

"""Acquisition Node class for FIREQ system node representation."""

from __future__ import annotations

import logging

import numpy as np

from FIREQ_LL_API import AcquisitionDriver

from ._generic_node import _GenericNode
from ._utils import _get_periods_from_clock, _MutableRef

logger = logging.getLogger(__name__)


[docs] class AcquisitionNode(_GenericNode): """Object representing the acquisition IP. Configuration dictionary keys: - ``_name`` (str): Name of the acquisition node instance. - ``_ll_handler`` (AcquisitionDriver): Low-level driver handler. - ``$duration`` (float): Duration of the acquisition, in ns. - ``$output_type`` (str): Output type. One of ``"raw"``, ``"decimated"``, or ``"accumulated"``. - ``$rfrequency`` (float): Demodulation frequency, in MHz. - ``$rphase`` (float): Demodulation initial phase, in radians. - ``$rchannel`` (int): Trigger channel. Set to ``0`` for no external trigger. - ``$tof`` (float): Time of flight, in ns. """ nodetype = "acquisition" wraps = [AcquisitionDriver.__name__] def __init__( self, name: str, parent: _GenericNode, _ll_handler: AcquisitionDriver, ) -> None: """Initialize the acquisition node. :param name: Name of the node :type name: str :param parent: Parent node in the system tree :type parent: _GenericNode :param _ll_handler: Low-level acquisition driver :type _ll_handler: AcquisitionDriver """ super().__init__(name=name, parent=parent) self._ll_handler = _ll_handler # get clocking info from root node self._clock_frequency = self.root.get_fabric_frequency() self._sampling_frequency = self.root.get_acqisition_sampling_frequency() # get interface mapping, to translate payload interface to interface id self._interface_map = self.root.get_axi_stream_interface_map(self.name) # create payloads dictionary self.payload: dict[str, _MutableRef] = {} # register payload update functions, one for each output interface for output_if in set(self._ll_handler._output_interfaces.values()): if_id = self._interface_map[output_if] self.payload[output_if] = _MutableRef() self.root.register_update_function(f"{if_id}/payload", self._make_payload_updater(output_if)) self.root.add_reference(f"{if_id}/payload", self.payload[output_if]) def _reset_all(self) -> None: """Call the reset all function to all children.""" # reset the internal state of the driver self._ll_handler.reset_state() for _, payload in self.payload.items(): payload.reset_hash()
[docs] @_GenericNode.parameter_callback("$duration", sweepable=True, cost=1) def set_acquisition_duration(self, duration: float) -> int: """Set the acquisition duration. :param duration: Duration in nanoseconds of the acquisition window :type duration: float :return: Error code (0 on success) :rtype: int """ clock_cycles = _get_periods_from_clock(duration, self._clock_frequency) return self._ll_handler.set_acquisition_duration(int(clock_cycles))
[docs] @_GenericNode.parameter_callback("$output_type", sweepable=False, cost=1) def set_output_mode(self, output_type: str) -> int: """Set the output type of the acquisition. :param output_type: Output type, can be ``"raw"``, ``"decimated"`` or ``"accumulated"`` :type output_type: str :return: Error code (0 on success) :rtype: int """ return self._ll_handler.set_output_mode(output_type)
[docs] @_GenericNode.parameter_callback("$rfrequency", sweepable=True, cost=1) def set_demodulation_frequency(self, frequency: float) -> int: """Set the demodulation frequency. :param frequency: Frequency in MHz :type frequency: float :return: Error code (0 on success) :rtype: int """ normal_frequency = frequency / self._sampling_frequency return self._ll_handler.set_demodulation_frequency(normal_frequency)
[docs] @_GenericNode.parameter_callback("$rphase", sweepable=True, cost=1) def set_demodulation_initial_phase(self, phase: float) -> int: """Set the demodulation initial phase. :param phase: Initial phase in radians :type phase: float :return: Error code (0 on success) :rtype: int """ normal_phase = phase / (2 * np.pi) return self._ll_handler.set_demodulation_initial_phase(normal_phase)
[docs] @_GenericNode.parameter_callback("$rchannel", sweepable=False, cost=1) def set_trigger_channel(self, channel: int) -> int: """Set the trigger channel. :param channel: Trigger channel number, set to 0 for no external trigger :type channel: int :return: Error code (0 on success) :rtype: int """ return self._ll_handler.set_trigger_channel(channel)
[docs] @_GenericNode.parameter_callback("$tof", sweepable=True, cost=1) def set_time_of_flight(self, time_of_flight: float) -> int: """Set the time of flight. :param time_of_flight: Time of flight in nanoseconds :type time_of_flight: float :return: Error code (0 on success) :rtype: int """ clock_cycles = _get_periods_from_clock(time_of_flight, self._clock_frequency) return self._ll_handler.set_time_of_flight(int(clock_cycles))
def _make_payload_updater(self, output_if: str) -> callable: """Create a closure that updates the payload for a specific interface. :param output_if: The output interface to bind the updater to :type output_if: str :return: A callable that updates only the bound interface's payload :rtype: callable """ def updater() -> bool: return self._update_payload_for_interface(output_if) return updater def _update_payload_for_interface(self, output_if: str) -> bool: """Update the payload for a single interface and notify whether a change happened. :param output_if: The output interface to update :type output_if: str :return: ``True`` if the payload has changed since the last call, ``False`` otherwise :rtype: bool """ if self._ll_handler.payload and self._ll_handler.payload["on_interface"] == output_if: self.payload[output_if]["size"] = self._ll_handler.payload["size"] self.payload[output_if]["source"] = self.name self.payload[output_if]["format"] = self._ll_handler.payload["format"] else: self.payload[output_if].clear() return self.payload[output_if].hash_and_compare()