forked from M-Labs/artiq
275 lines
8.8 KiB
Python
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
|