Source code for FIREQ_SERVER.utils.exceptions

"""Custom exceptions for the hardware layer.

These exceptions provide a clean, typed error interface for the orchestrator, isolating
it from the inconsistent error handling in the low-level drivers.
"""


[docs] class FireqHardwareError(Exception): """Base exception for all hardware-related errors. All exceptions in the hardware layer inherit from this, allowing callers to catch all hardware errors with a single except clause. Exception Hierarchy:: FireqHardwareError ├── DriverError (low-level driver failed) ├── TimingError (timing constraints violated) ├── ConfigurationError (invalid configuration) │ ├── FrequencyError (frequency out of range) │ ├── EnvelopeUploadError (envelope upload failed) │ └── WaveCompilationError (wave compilation failed) ├── DMAError (DMA operation failed) │ ├── DMATimeoutError (DMA transfer timeout) │ └── RecoverableDMAError (error but data may be valid) ├── HardwareResourceError (resource access failed) └── HardwareStateError (unexpected hardware state) """ pass
[docs] class DriverError(FireqHardwareError): """Error originating from a low-level driver. This wraps the inconsistent return codes (-3, None, etc.) from the FIREQ_LL_API drivers into proper Python exceptions. Example:: >>> try: ... gen.create_waveform("invalid_name", 100, 0.5) ... except DriverError as e: ... print(f"Driver {e.driver_name} failed in {e.operation}") ... print(f"Return code: {e.return_code}") """ def __init__( self, message: str, driver_name: str | None = None, operation: str | None = None, return_code: int | None = None, ) -> None: """Initialize a DriverError with optional driver context. :param message: Human-readable error description. :type message: str :param driver_name: Name of the driver that failed. :type driver_name: str | None :param operation: The operation that was attempted. :type operation: str | None :param return_code: The original return code from the driver. :type return_code: int | None """ super().__init__(message) self.driver_name = driver_name self.operation = operation self.return_code = return_code
[docs] class TimingError(FireqHardwareError): """Invalid timing parameters for an experiment. Raised when experiment parameters violate hardware constraints such as: - Duration too short/long - Trigger delay >= experiment duration - Not enough time for acquisition to complete - TOF exceeds hardware limit These errors should be caught and parameters adjusted before retrying. Example:: >>> try: ... backend.start_experiment(duration_cycles=10) ... except TimingError as e: ... print(f"Need at least: {e.min_required}") ... backend.start_experiment(duration_cycles=e.min_required) """ def __init__( self, message: str, parameter: str | None = None, value: float | None = None, min_required: float | None = None, max_allowed: float | None = None, ) -> None: """Initialize a TimingError with constraint details. :param message: Human-readable error description. :type message: str :param parameter: Name of the timing parameter that violated constraints. :type parameter: str | None :param value: The invalid value that was provided. :type value: float | None :param min_required: Minimum valid value. :type min_required: float | None :param max_allowed: Maximum valid value. :type max_allowed: float | None """ super().__init__(message) self.parameter = parameter self.value = value self.min_required = min_required self.max_allowed = max_allowed
[docs] class ConfigurationError(FireqHardwareError): """Invalid hardware configuration. Raised when attempting to configure hardware with invalid parameters: - Frequency out of range - Gain out of [-1, 1] range - Channel index out of range - Invalid mode selection These errors should be caught and corrected before retrying. Example:: >>> try: ... gen.set_drive_frequency(-100) ... except ConfigurationError as e: ... print(f"Error: {e}") """ pass
[docs] class FrequencyError(ConfigurationError): """Raised when frequency is out of valid range. Provides information about valid Nyquist zones and frequency ranges. Example:: >>> try: ... gen.set_drive_frequency(10000) ... except FrequencyError as e: ... print(f"Frequency {e.freq_mhz} MHz") ... print(f"Valid ranges: {e.valid_ranges}") """ def __init__( self, freq_mhz: float, reason: str, valid_ranges: list[tuple[float, float]] | None = None, ) -> None: """Initialize a FrequencyError with optional valid ranges. :param freq_mhz: The frequency that was rejected. :type freq_mhz: float :param reason: Human-readable rejection reason. :type reason: str :param valid_ranges: List of (min_mhz, max_mhz) tuples showing valid ranges. :type valid_ranges: list[tuple[float, float]] | None """ self.freq_mhz = freq_mhz self.valid_ranges = valid_ranges or [] message = f"Frequency {freq_mhz} MHz: {reason}" if valid_ranges: message += f"\nValid ranges (MHz): {valid_ranges}" super().__init__(message)
[docs] class EnvelopeUploadError(ConfigurationError): """Raised when envelope upload fails. This exception wraps errors during the envelope upload stage, providing context about which generator and envelope failed. """ def __init__(self, gen_index: int, envelope_name: str, reason: str) -> None: """Initialize an EnvelopeUploadError with context. :param gen_index: Generator index where upload failed. :type gen_index: int :param envelope_name: Name of the envelope that failed. :type envelope_name: str :param reason: Human-readable failure reason. :type reason: str """ self.gen_index = gen_index self.envelope_name = envelope_name message = f"Envelope '{envelope_name}' upload failed on gen {gen_index}: {reason}" super().__init__(message)
[docs] class WaveCompilationError(ConfigurationError): """Raised when wave compilation fails. This exception wraps errors during the wave compilation stage, providing context about which generator and wave failed. """ def __init__(self, gen_index: int, wave_id: str, reason: str) -> None: """Initialize a WaveCompilationError with context. :param gen_index: Generator index where compilation failed. :type gen_index: int :param wave_id: Identifier of the wave that failed. :type wave_id: str :param reason: Human-readable failure reason. :type reason: str """ self.gen_index = gen_index self.wave_id = wave_id message = f"Wave '{wave_id}' compilation failed on gen {gen_index}: {reason}" super().__init__(message)
[docs] class DMAError(FireqHardwareError): """DMA-related error. Raised when DMA operations fail, such as: - DMA channel stuck/busy - Buffer allocation failure - Transfer timeout (see DMATimeoutError for specific timeout case) """ def __init__( self, message: str, recovery_strategy: str = "fatal", ) -> None: """Initialize a DMAError with recovery strategy. :param message: Human-readable error description. :type message: str :param recovery_strategy: ``'fatal'`` or ``'recoverable'``. :type recovery_strategy: str """ super().__init__(message) self.recovery_strategy = recovery_strategy
[docs] class DMATimeoutError(DMAError): """Exception raised when a DMA transfer exceeds the allowed time limit. This is a specific, recoverable case of DMA failure where the transfer simply took too long. """ def __init__( self, message: str, timeout_seconds: float, recovery_strategy: str = "fatal", ) -> None: """Initialize a DMATimeoutError with timeout details. :param message: Human-readable error description. :type message: str :param timeout_seconds: The timeout that was exceeded. :type timeout_seconds: float :param recovery_strategy: ``'fatal'`` or ``'recoverable'``. :type recovery_strategy: str """ super().__init__(message, recovery_strategy=recovery_strategy) self.timeout_seconds = timeout_seconds
[docs] class RecoverableDMAError(DMAError): """DMA error that occurred but data may still be recoverable. Example: Internal error bit set but TLAST (transfer last) received, indicating the transfer actually completed. Recovery strategy: RECOVERABLE — attempt to parse data anyway. """ def __init__( self, message: str, status_code: int | None = None, ) -> None: """Initialize a RecoverableDMAError with status code. :param message: Human-readable error description. :type message: str :param status_code: The DMA status register value. :type status_code: int | None """ super().__init__(message, recovery_strategy="recoverable") self.status_code = status_code
[docs] class HardwareResourceError(FireqHardwareError): """Raised when a hardware resource cannot be accessed or allocated. Examples: - Invalid generator/acquisition index - Buffer allocation failure - Clock not locked - Channel not available Recovery strategy: FATAL — fix configuration and retry. """ def __init__( self, message: str, resource_type: str | None = None, resource_id: object | None = None, ) -> None: """Initialize a HardwareResourceError with resource metadata. :param message: Human-readable error description. :type message: str :param resource_type: Type of resource that failed (e.g., 'generator', 'buffer'). :type resource_type: str | None :param resource_id: Identifier of the resource. :type resource_id: object | None """ super().__init__(message) self.resource_type = resource_type self.resource_id = resource_id
[docs] class HardwareStateError(FireqHardwareError): """Raised when hardware is in unexpected state. Examples: - DMA not idle after stop command - Clock not locked - Switch routing failed - RF-DC tile not responding Recovery strategy: Usually FATAL — indicates firmware or hardware issue. """ def __init__( self, message: str, status_code: int | None = None, expected_state: str | None = None, actual_state: str | None = None, ) -> None: """Initialize a HardwareStateError with state details. :param message: Human-readable error description. :type message: str :param status_code: Hardware status register value. :type status_code: int | None :param expected_state: Description of expected state. :type expected_state: str | None :param actual_state: Description of actual state. :type actual_state: str | None """ super().__init__(message) self.status_code = status_code self.expected_state = expected_state self.actual_state = actual_state
[docs] class ClientDisconnectedError(Exception): """Raised when the remote peer disconnects (gracefully or abruptly).""" pass
[docs] class IncompleteTransferError(Exception): """Raised when a socket transfer was not completed successfully.""" pass
[docs] class InvalidPayloadError(Exception): """Raised when payload parsing fails.""" pass
__all__ = [ "FireqHardwareError", "DriverError", "TimingError", "ConfigurationError", "FrequencyError", "EnvelopeUploadError", "WaveCompilationError", "DMAError", "DMATimeoutError", "RecoverableDMAError", "HardwareResourceError", "HardwareStateError", "ClientDisconnectedError", "IncompleteTransferError", "InvalidPayloadError", ]