artiq/artiq/coredevice/suservo.py

275 lines
8.8 KiB
Python

from artiq.language.core import kernel, delay, portable, now_mu, delay_mu
from artiq.language.units import us, ms
from artiq.coredevice.rtio import rtio_output, rtio_input_data
from numpy import int32, int64
from artiq.coredevice import spi2 as spi
from artiq.coredevice import urukul, sampler
COEFF_WIDTH = 18
COEFF_DEPTH = 10 + 1
WE = 1 << COEFF_DEPTH + 1
STATE_SEL = 1 << COEFF_DEPTH
CONFIG_SEL = 1 << COEFF_DEPTH - 1
CONFIG_ADDR = CONFIG_SEL | STATE_SEL
class SUServo:
kernel_invariants = {"channel", "core", "pgia", "cpld0", "cpld1",
"dds0", "dds1", "ref_period_mu"}
def __init__(self, dmgr, channel, pgia_device,
cpld0_device, cpld1_device,
dds0_device, dds1_device,
core_device="core"):
self.core = dmgr.get(core_device)
self.pgia = dmgr.get(pgia_device)
self.dds0 = dmgr.get(dds0_device)
self.dds1 = dmgr.get(dds1_device)
self.cpld0 = dmgr.get(cpld0_device)
self.cpld1 = dmgr.get(cpld1_device)
self.channel = channel
self.gains = 0x0000
self.ref_period_mu = self.core.seconds_to_mu(
self.core.coarse_ref_period)
assert self.ref_period_mu == self.core.ref_multiplier
@kernel
def init(self):
"""Initialize the Servo, Sampler and both Urukuls.
Leaves the Servo disabled (see :meth:`set_config`), resets all DDS.
Urukul initialization is performed blindly as there is no readback from
the DDS or the CPLDs.
This method does not alter the profile configuration memory
or the channel controls.
"""
self.set_config(0)
delay(3*us) # pipeline flush
self.pgia.set_config_mu(
sampler.SPI_CONFIG | spi.SPI_END,
16, 4, sampler.SPI_CS_PGIA)
self.cpld0.init(blind=True)
cfg0 = self.cpld0.cfg_reg
self.cpld0.cfg_write(cfg0 | (0xf << urukul.CFG_MASK_NU))
self.dds0.init(blind=True)
self.cpld0.cfg_write(cfg0)
self.cpld1.init(blind=True)
cfg1 = self.cpld1.cfg_reg
self.cpld1.cfg_write(cfg1 | (0xf << urukul.CFG_MASK_NU))
self.dds1.init(blind=True)
self.cpld1.cfg_write(cfg1)
@kernel
def write(self, addr, value):
"""Write to Servo memory.
This method advances the timeline by one coarse RTIO cycle.
:param addr: Memory location address.
:param value: Data to be written.
"""
rtio_output(now_mu(), self.channel, addr | WE, value)
delay_mu(self.ref_period_mu)
@kernel
def read(self, addr):
"""Read from Servo memory.
This method does not advance the timeline but consumes all slack.
:param addr: Memory location address.
"""
rtio_output(now_mu(), self.channel, addr, 0)
return rtio_input_data(self.channel)
@kernel
def set_config(self, enable):
"""Set SU Servo configuration.
This method advances the timeline by one Servo memory access.
:param enable: Enable Servo operation. Disabling takes up to 2 Servo
cycles (~2.2 µs).
"""
self.write(CONFIG_ADDR, enable)
@kernel
def get_status(self):
"""Get current SU Servo status.
This method does not advance the timeline but consumes all slack.
:return: Status. Bit 0: enabled, bit 1: done
"""
return self.read(CONFIG_ADDR)
@kernel
def get_adc_mu(self, adc):
"""Get an ADC reading (IIR filter input X0).
This method does not advance the timeline but consumes all slack.
:param adc: ADC channel number (0-7)
:return: 16 bit signed Y0
"""
return self.read(STATE_SEL | (adc << 1) | (1 << 8))
@kernel
def set_pgia_mu(self, channel, gain):
"""Set instrumentation amplifier gain of a ADC channel.
The four gain settings (0, 1, 2, 3) corresponds to gains of
(1, 10, 100, 1000) respectively.
:param channel: Channel index
:param gain: Gain setting
"""
gains = self.gains
gains &= ~(0b11 << (channel*2))
gains |= gain << (channel*2)
self.pgia.write(gains << 16)
self.gains = gains
@kernel
def get_adc(self, adc):
raise NotImplementedError # FIXME
class Channel:
kernel_invariants = {"channel", "core", "servo", "servo_channel"}
def __init__(self, dmgr, channel, servo_device,
core_device="core"):
self.core = dmgr.get(core_device)
self.servo = dmgr.get(servo_device)
self.channel = channel
# FIXME: this assumes the mem channel is right after the control
# channels
self.servo_channel = self.channel + 8 - self.servo.channel
@kernel
def set(self, en_out, en_iir=0, profile=0):
"""Operate channel.
This method does not advance the timeline.
:param en_out: RF switch enable
:param en_iir: IIR updates enable
:param profile: Active profile (0-31)
"""
rtio_output(now_mu(), self.channel, 0,
en_out | (en_iir << 1) | (profile << 2))
@kernel
def set_dds_mu(self, profile, ftw, offset, pow=0):
"""Set profile DDS coefficients.
This method advances the timeline by four Servo memory accesses.
:param profile: Profile number (0-31)
:param ftw: Frequency tuning word (32 bit unsigned)
:param offset: IIR offset (setpoint)
:param pow: Phase offset word (16 bit unsigned)
"""
base = (self.servo_channel << 8) | (profile << 3)
self.servo.write(base + 0, ftw >> 16)
self.servo.write(base + 6, ftw)
self.servo.write(base + 4, offset)
self.servo.write(base + 2, pow)
@kernel
def set_dds(self, profile, frequency, offset, phase=0.):
raise NotImplementedError # FIXME
@kernel
def set_iir_mu(self, profile, adc, a1, b0, b1, delay=0):
"""Set profile IIR coefficients.
This method advances the timeline by four Servo memory accesses.
:param profile: Profile number (0-31)
:param adc: ADC channel to use (0-7)
:param a1: 18 bit signed A1 coefficient (Y1 coefficient,
feedback, integrator gain)
:param b0: 18 bit signed B0 coefficient (recent,
X0 coefficient, feed forward, proportional gain)
:param b1: 18 bit signed B1 coefficient (old,
X1 coefficient, feed forward, proportional gain)
:param delay: Number of Servo cycles (~1.1 µs each) to suppress
IIR updates for after either (1) enabling or disabling RF output,
(2) enabling or disabling IIR updates, or (3) setting the active
profile number: i.e. after invoking :meth:`set`.
"""
base = (self.servo_channel << 8) | (profile << 3)
self.servo.write(base + 1, b1)
self.servo.write(base + 3, adc | (delay << 8))
self.servo.write(base + 5, a1)
self.servo.write(base + 7, b0)
@kernel
def set_iir(self, profile, adc, i_gain, p_gain, delay=0.):
raise NotImplementedError # FIXME
@kernel
def get_profile_mu(self, profile, data):
"""Retrieve profile data.
The data is returned in the `data` argument as:
`[ftw >> 16, b1, pow, adc | (delay << 8), offset, a1, ftw, b0]`.
This method advances the timeline by 32 µs and consumes all slack.
:param profile: Profile number (0-31)
:param data: List of 8 integers to write the profile data into
"""
base = (self.servo_channel << 8) | (profile << 3)
for i in range(len(data)):
data[i] = self.servo.read(base + i)
delay(4*us)
@kernel
def get_y_mu(self, profile):
"""Get a profile's IIR state (filter output, Y0).
The IIR state is also know as the "integrator", or the DDS amplitude
scale factor. It is 18 bits wide and unsigned.
This method does not advance the timeline but consumes all slack.
:param profile: Profile number (0-31)
:return: 18 bit unsigned Y0
"""
return self.servo.read(STATE_SEL | (self.servo_channel << 5) | profile)
@kernel
def get_y(self, profile):
raise NotImplementedError # FIXME
@kernel
def set_y_mu(self, profile, y):
"""Set a profile's IIR state (filter output, Y0).
The IIR state is also know as the "integrator", or the DDS amplitude
scale factor. It is 18 bits wide and unsigned.
This method advances the timeline by one Servo memory access.
:param profile: Profile number (0-31)
:param y: 18 bit unsigned Y0
"""
return self.servo.write(
STATE_SEL | (self.servo_channel << 5) | profile, y)
@kernel
def set_y(self, profile, y):
raise NotImplementedError # FIXME