Source code for atomiq.components.sinara.mirny

"""Sinara 4456 Mirny (4-channel ADF5356 PLL)."""

from __future__ import annotations

from atomiq.components.primitives import Component, Switchable
from atomiq.components.electronics.rfsource import RFSource
from atomiq.helper import replace_member

from artiq.experiment import kernel, portable
from artiq.language.core import delay_mu
import artiq.coredevice.mirny
from artiq.language.types import TFloat, TInt32, TStr


[docs] @portable(flags={"fast-math"}) def _amplitude_to_att_db(amplitude: TFloat) -> TFloat: return (1.0 - amplitude) * 31.5
[docs] @portable(flags={"fast-math"}) def _att_db_to_amplitude(att_db: TFloat) -> TFloat: return 1.0 - att_db / 31.5
[docs] class Mirny(Component): """Sinara Mirny 4 Channel PLL/VCO-based microwave frequency synthesizer Args: cpld: ARTIQ :class:`artiq.coredevice.mirny.Mirny` instance from the device database (e.g. ``@mirny0_cpld``). """ kernel_invariants = {"cpld"} def __init__(self, cpld: artiq.coredevice.mirny.Mirny, *args, **kwargs): Component.__init__(self, *args, **kwargs) self.cpld = cpld
[docs] class MirnyChannel(RFSource, Switchable): """Single Mirny ADF5356 channel: PLL frequency, step attenuator, and RF switch. Args: mirny: Parent :class:`Mirny` board the channels belongs to. device: ARTIQ :class:`artiq.coredevice.adf5356.ADF5356` channel device from the `device_db` (e.g. ``@mirny0_ch0``). ttl: The ARTIQ device from the `device_db` representing the Urukul fast RF switch, (e.g. ``@ttl_mirny0_sw0``). RF is switched on and off by disabling the PLL chip output (slow). default_amplitude: Default amplitude in interval [0,1] applied in ``prerun``. default_powerlevel: Default PLL chip power index applied in ``prerun``, this determines the maximum power. Defaults to 3 which is the maximum output power. See `the datasheet <https://m-labs.hk/docs/sinara-datasheets/4456-4457.pdf>`_ for more info. freq_limit: Allowed output frequency range in Hz. blind: If ``True``, skip the usual :meth:`_prerun` reprogramming (see :class:`~atomiq.components.electronics.rfsource.RFSource`). Note: Call :meth:`init` once after power-on before experiments; it is not invoked automatically. Warning: The Mirny can not ramp frequencies continuously, between each step there will be a gap of ~1-2ms as the PLL needs to relock. """ kernel_invariants = {"mirny", "device", "sw", "default_powerlevel", "default_attenuation"} def __init__( self, mirny: Mirny, device: TStr, ttl=None, default_attenuation: TFloat = 0.0, default_powerlevel: TFloat = 3, freq_limit: tuple = (53.125e6, 4.0e9), **kwargs, ): # Validate default_attenuation range if default_attenuation < 0.0 or default_attenuation > 31.5: raise ValueError(f"default_attenuation {default_attenuation} must be in range [0.0, 31.5] dB") default_amplitude = _att_db_to_amplitude(default_attenuation) RFSource.__init__( self, default_amplitude=default_amplitude, freq_limit=freq_limit, **kwargs, ) Switchable.__init__(self, ["RF_on"]) self.mirny = mirny self.device = device if ttl is not None: self.sw = ttl else: replace_member(self, "on", "_on_pll") replace_member(self, "off", "_off_pll") self.sw = None self.default_attenuation = default_attenuation self.default_powerlevel = default_powerlevel
[docs] @kernel def init(self): """Initialize CPLD (if needed) and PLL. Slow; run once after power-on.""" delay_mu(10000) self.mirny.cpld.init() delay_mu(100000) self.device.init()
[docs] @kernel def _prerun(self): delay_mu(150000) self.set_att(self.default_attenuation) delay_mu(150000) self.set_output_power_mu(self.default_powerlevel)
[docs] @kernel def set_att(self, attenuation: TFloat): """Set hardware digital attenuation in dB (0–31.5 in 0.5 dB steps). Updates :attr:`attenuation` and keeps :attr:`amplitude` consistent with the mapping used by :meth:`set`. """ # Validate attenuation range if attenuation < 0.0 or attenuation > 31.5: self.experiment.log.error( "Requested attenuation {0} outside valid range [0.0, 31.5] dB for MirnyChannel " + self.identifier, [attenuation], ) return self.amplitude = _att_db_to_amplitude(attenuation) self.device.set_att(attenuation) delay_mu(30)
[docs] @kernel def _set_frequency(self, frequency: TFloat): self.device.set_frequency(f=frequency)
[docs] @kernel def _set_amplitude(self, amplitude: TFloat): self.set_att(_amplitude_to_att_db(amplitude))
[docs] @kernel def _set_phase(self, phase: TFloat): """ADF5356 phase offset is not programmed by this Atomiq wrapper.""" self.experiment.log.error("Setting the phase on Mirny PLL is not possible, discarding phase %f", (phase,))
[docs] @kernel def on(self): delay_mu(4) self.sw.on() delay_mu(4)
[docs] @kernel def off(self): delay_mu(4) self.sw.off() delay_mu(4)
[docs] @kernel def set_output_power_mu(self, n: TInt32): """Set PLL chip output power index (0–3); see ADF5356 datasheet.""" self.device.set_output_power_mu(n)
[docs] @portable def output_power_mu(self) -> TInt32: return self.device.output_power_mu()
[docs] @kernel def _on_pll(self): self.device.enable_output()
[docs] @kernel def _off_pll(self): self.device.disable_output()