from numpy import int32
from artiq.language.core import compile, Kernel, KernelInvariant, kernel, portable
from artiq.language.units import us
from artiq.coredevice.core import Core
from artiq.coredevice.mirny import Mirny
from artiq.coredevice.spi2 import *
# almazny-specific data
ALMAZNY_LEGACY_REG_BASE = 0x0C
ALMAZNY_LEGACY_OE_SHIFT = 12
# higher SPI write divider to match almazny shift register timing
# min SER time before SRCLK rise = 125ns
# -> div=32 gives 125ns for data before clock rise
# works at faster dividers too but could be less reliable
ALMAZNY_LEGACY_SPIT_WR = 32
[docs]
@compile
class AlmaznyLegacy:
"""
Almazny (High-frequency mezzanine board for Mirny)
This applies to Almazny hardware v1.1 and earlier.
Use :class:`~artiq.coredevice.almazny.AlmaznyChannel` for Almazny v1.2 and later.
:param host_mirny: :class:`~artiq.coredevice.mirny.Mirny` device Almazny is connected to
"""
core: KernelInvariant[Core]
mirny_cpld: KernelInvariant[Mirny]
att_mu: Kernel[list[int32]]
channel_sw: Kernel[list[int32]]
output_enable: Kernel[bool]
def __init__(self, dmgr, host_mirny):
self.mirny_cpld = dmgr.get(host_mirny)
self.core = self.mirny_cpld.core
self.att_mu = [0x3f] * 4
self.channel_sw = [0] * 4
self.output_enable = False
@kernel
def init(self):
self.output_toggle(self.output_enable)
[docs]
@kernel
def att_to_mu(self, att: float) -> int32:
"""
Convert an attenuator setting in dB to machine units.
:param att: attenuator setting in dB [0-31.5]
:return: attenuator setting in machine units
"""
mu = round(att * 2.0)
if mu > 63 or mu < 0:
raise ValueError("Invalid Almazny attenuator settings!")
return mu
[docs]
@kernel
def mu_to_att(self, att_mu: int32) -> float:
"""
Convert a digital attenuator setting to dB.
:param att_mu: attenuator setting in machine units
:return: attenuator setting in dB
"""
return float(att_mu) / 2.
[docs]
@kernel
def set_att(self, channel: int32, att: float, rf_switch: bool = True):
"""
Sets attenuators on chosen shift register (channel).
:param channel: index of the register [0-3]
:param att: attenuation setting in dBm [0-31.5]
:param rf_switch: rf switch (bool)
"""
self.set_att_mu(channel, self.att_to_mu(att), rf_switch)
[docs]
@kernel
def set_att_mu(self, channel: int32, att_mu: int32, rf_switch: bool = True):
"""
Sets attenuators on chosen shift register (channel).
:param channel: index of the register [0-3]
:param att_mu: attenuation setting in machine units [0-63]
:param rf_switch: rf switch (bool)
"""
self.channel_sw[channel] = 1 if rf_switch else 0
self.att_mu[channel] = att_mu
self._update_register(channel)
[docs]
@kernel
def output_toggle(self, oe: bool):
"""
Toggles output on all shift registers on or off.
:param oe: toggle output enable (bool)
"""
self.output_enable = oe
cfg_reg = self.mirny_cpld.read_reg(1)
en = 1 if self.output_enable else 0
self.core.delay(100. * us)
new_reg = (en << ALMAZNY_LEGACY_OE_SHIFT) | (cfg_reg & 0x3FF)
self.mirny_cpld.write_reg(1, new_reg)
self.core.delay(100. * us)
@kernel
def _flip_mu_bits(self, mu: int32) -> int32:
# in this form MSB is actually 0.5dB attenuator
# unnatural for users, so we flip the six bits
return (((mu & 0x01) << 5)
| ((mu & 0x02) << 3)
| ((mu & 0x04) << 1)
| ((mu & 0x08) >> 1)
| ((mu & 0x10) >> 3)
| ((mu & 0x20) >> 5))
@kernel
def _update_register(self, ch: int32):
self.mirny_cpld.write_ext(
ALMAZNY_LEGACY_REG_BASE + ch,
8,
self._flip_mu_bits(self.att_mu[ch]) | (self.channel_sw[ch] << 6),
ALMAZNY_LEGACY_SPIT_WR
)
self.core.delay(100. * us)
[docs]
@compile
class AlmaznyChannel:
"""
Driver for one Almazny channel.
Almazny is a mezzanine for the Quad PLL RF source Mirny that exposes and
controls the frequency-doubled outputs.
This driver requires Almazny hardware revision v1.2 or later
and Mirny CPLD gateware v0.3 or later.
Use :class:`~artiq.coredevice.almazny.AlmaznyLegacy` for Almazny hardware v1.1 and earlier.
:param host_mirny: Mirny CPLD device name
:param channel: channel index (0-3)
"""
core: KernelInvariant[Core]
mirny_cpld: KernelInvariant[Mirny]
channel: KernelInvariant[int32]
def __init__(self, dmgr, host_mirny, channel):
self.mirny_cpld = dmgr.get(host_mirny)
self.core = self.mirny_cpld.core
self.channel = channel
[docs]
@portable
def to_mu(self, att: float, enable: bool, led: bool) -> int32:
"""
Convert an attenuation in dB, RF switch state and LED state to machine
units.
:param att: attenuator setting in dB (0-31.5)
:param enable: RF switch state (bool)
:param led: LED state (bool)
:return: channel setting in machine units
"""
mu = round(att * 2.)
if mu >= 64 or mu < 0:
raise ValueError("Attenuation out of range")
# unfortunate hardware design: bit reverse
mu = ((mu & 0x15) << 1) | ((mu >> 1) & 0x15)
mu = ((mu & 0x03) << 4) | (mu & 0x0c) | ((mu >> 4) & 0x03)
if enable:
mu |= 1 << 6
if led:
mu |= 1 << 7
return mu
[docs]
@kernel
def set_mu(self, mu: int32):
"""
Set channel state (machine units).
:param mu: channel state in machine units.
"""
self.mirny_cpld.write_ext(
addr=0xc + self.channel, length=8, data=mu, ext_div=32)
[docs]
@kernel
def set(self, att: float, enable: bool, led: bool = False):
"""
Set attenuation, RF switch, and LED state (SI units).
:param att: attenuator setting in dB (0-31.5)
:param enable: RF switch state (bool)
:param led: LED state (bool)
"""
self.set_mu(self.to_mu(att, enable, led))