"""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()