shuttler: port to NAC3

This commit is contained in:
Sebastien Bourdeauducq 2023-10-06 14:40:53 +08:00
parent fc082b62de
commit 24c3a2fd0a
4 changed files with 191 additions and 150 deletions

View File

@ -1,20 +1,21 @@
import numpy from numpy import int32, int64
from artiq.language.core import * from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable, Option, none
from artiq.language.types import *
from artiq.coredevice.rtio import rtio_output, rtio_input_data from artiq.coredevice.rtio import rtio_output, rtio_input_data
from artiq.coredevice import spi2 as spi from artiq.coredevice.core import Core
from artiq.coredevice.spi2 import *
from artiq.language.units import us from artiq.language.units import us
@portable @portable
def shuttler_volt_to_mu(volt): def shuttler_volt_to_mu(volt: float) -> int32:
"""Return the equivalent DAC code. Valid input range is from -10 to """Return the equivalent DAC code. Valid input range is from -10 to
10 - LSB. 10 - LSB.
""" """
return round((1 << 16) * (volt / 20.0)) & 0xffff return round(float(1 << 16) * (volt / 20.0)) & 0xffff
@nac3
class Config: class Config:
"""Shuttler configuration registers interface. """Shuttler configuration registers interface.
@ -30,10 +31,13 @@ class Config:
:param channel: RTIO channel number of this interface. :param channel: RTIO channel number of this interface.
:param core_device: Core device name. :param core_device: Core device name.
""" """
kernel_invariants = { core: KernelInvariant[Core]
"core", "channel", "target_base", "target_read", channel: KernelInvariant[int32]
"target_gain", "target_offset", "target_clr" target_base: KernelInvariant[int32]
} target_read: KernelInvariant[int32]
target_gain: KernelInvariant[int32]
target_offset: KernelInvariant[int32]
target_clr: KernelInvariant[int32]
def __init__(self, dmgr, channel, core_device="core"): def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -45,7 +49,7 @@ class Config:
self.target_clr = 1 * (1 << 5) self.target_clr = 1 * (1 << 5)
@kernel @kernel
def set_clr(self, clr): def set_clr(self, clr: int32):
"""Set/Unset waveform phase clear bits. """Set/Unset waveform phase clear bits.
Each bit corresponds to a Shuttler waveform generator core. Setting a Each bit corresponds to a Shuttler waveform generator core. Setting a
@ -59,7 +63,7 @@ class Config:
rtio_output(self.target_base | self.target_clr, clr) rtio_output(self.target_base | self.target_clr, clr)
@kernel @kernel
def set_gain(self, channel, gain): def set_gain(self, channel: int32, gain: int32):
"""Set the 16-bits pre-DAC gain register of a Shuttler Core channel. """Set the 16-bits pre-DAC gain register of a Shuttler Core channel.
The `gain` parameter represents the decimal portion of the gain The `gain` parameter represents the decimal portion of the gain
@ -72,7 +76,7 @@ class Config:
rtio_output(self.target_base | self.target_gain | channel, gain) rtio_output(self.target_base | self.target_gain | channel, gain)
@kernel @kernel
def get_gain(self, channel): def get_gain(self, channel: int32) -> int32:
"""Return the pre-DAC gain value of a Shuttler Core channel. """Return the pre-DAC gain value of a Shuttler Core channel.
:param channel: The Shuttler Core channel. :param channel: The Shuttler Core channel.
@ -83,7 +87,7 @@ class Config:
return rtio_input_data(self.channel) return rtio_input_data(self.channel)
@kernel @kernel
def set_offset(self, channel, offset): def set_offset(self, channel: int32, offset: int32):
"""Set the 16-bits pre-DAC offset register of a Shuttler Core channel. """Set the 16-bits pre-DAC offset register of a Shuttler Core channel.
.. seealso:: .. seealso::
@ -95,7 +99,7 @@ class Config:
rtio_output(self.target_base | self.target_offset | channel, offset) rtio_output(self.target_base | self.target_offset | channel, offset)
@kernel @kernel
def get_offset(self, channel): def get_offset(self, channel: int32) -> int32:
"""Return the pre-DAC offset value of a Shuttler Core channel. """Return the pre-DAC offset value of a Shuttler Core channel.
:param channel: The Shuttler Core channel. :param channel: The Shuttler Core channel.
@ -106,6 +110,7 @@ class Config:
return rtio_input_data(self.channel) return rtio_input_data(self.channel)
@nac3
class Volt: class Volt:
"""Shuttler Core cubic DC-bias spline. """Shuttler Core cubic DC-bias spline.
@ -127,7 +132,9 @@ class Volt:
:param channel: RTIO channel number of this DC-bias spline interface. :param channel: RTIO channel number of this DC-bias spline interface.
:param core_device: Core device name. :param core_device: Core device name.
""" """
kernel_invariants = {"core", "channel", "target_o"} core: KernelInvariant[Core]
channel: KernelInvariant[int32]
target_o: KernelInvariant[int32]
def __init__(self, dmgr, channel, core_device="core"): def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -135,7 +142,7 @@ class Volt:
self.target_o = channel << 8 self.target_o = channel << 8
@kernel @kernel
def set_waveform(self, a0: TInt32, a1: TInt32, a2: TInt64, a3: TInt64): def set_waveform(self, a0: int32, a1: int32, a2: int64, a3: int64):
"""Set the DC-bias spline waveform. """Set the DC-bias spline waveform.
Given `a(t)` as defined in :class:`Volt`, the coefficients should be Given `a(t)` as defined in :class:`Volt`, the coefficients should be
@ -168,12 +175,12 @@ class Volt:
a0, a0,
a1, a1,
a1 >> 16, a1 >> 16,
a2 & 0xFFFF, int32(a2 & int64(0xFFFF)),
(a2 >> 16) & 0xFFFF, int32((a2 >> int64(16)) & int64(0xFFFF)),
(a2 >> 32) & 0xFFFF, int32((a2 >> int64(32)) & int64(0xFFFF)),
a3 & 0xFFFF, int32(a3 & int64(0xFFFF)),
(a3 >> 16) & 0xFFFF, int32((a3 >> int64(16)) & int64(0xFFFF)),
(a3 >> 32) & 0xFFFF, int32((a3 >> int64(32)) & int64(0xFFFF)),
] ]
for i in range(len(coef_words)): for i in range(len(coef_words)):
@ -181,6 +188,7 @@ class Volt:
delay_mu(int64(self.core.ref_multiplier)) delay_mu(int64(self.core.ref_multiplier))
@nac3
class Dds: class Dds:
"""Shuttler Core DDS spline. """Shuttler Core DDS spline.
@ -206,7 +214,9 @@ class Dds:
:param channel: RTIO channel number of this DC-bias spline interface. :param channel: RTIO channel number of this DC-bias spline interface.
:param core_device: Core device name. :param core_device: Core device name.
""" """
kernel_invariants = {"core", "channel", "target_o"} core: KernelInvariant[Core]
channel: KernelInvariant[int32]
target_o: KernelInvariant[int32]
def __init__(self, dmgr, channel, core_device="core"): def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -214,8 +224,8 @@ class Dds:
self.target_o = channel << 8 self.target_o = channel << 8
@kernel @kernel
def set_waveform(self, b0: TInt32, b1: TInt32, b2: TInt64, b3: TInt64, def set_waveform(self, b0: int32, b1: int32, b2: int64, b3: int64,
c0: TInt32, c1: TInt32, c2: TInt32): c0: int32, c1: int32, c2: int32):
"""Set the DDS spline waveform. """Set the DDS spline waveform.
Given `b(t)` and `c(t)` as defined in :class:`Dds`, the coefficients Given `b(t)` and `c(t)` as defined in :class:`Dds`, the coefficients
@ -258,12 +268,12 @@ class Dds:
b0, b0,
b1, b1,
b1 >> 16, b1 >> 16,
b2 & 0xFFFF, int32(b2 & int64(0xFFFF)),
(b2 >> 16) & 0xFFFF, int32((b2 >> int64(16)) & int64(0xFFFF)),
(b2 >> 32) & 0xFFFF, int32((b2 >> int64(32)) & int64(0xFFFF)),
b3 & 0xFFFF, int32(b3 & int64(0xFFFF)),
(b3 >> 16) & 0xFFFF, int32((b3 >> int64(16)) & int64(0xFFFF)),
(b3 >> 32) & 0xFFFF, int32((b3 >> int64(32)) & int64(0xFFFF)),
c0, c0,
c1, c1,
c1 >> 16, c1 >> 16,
@ -276,13 +286,16 @@ class Dds:
delay_mu(int64(self.core.ref_multiplier)) delay_mu(int64(self.core.ref_multiplier))
@nac3
class Trigger: class Trigger:
"""Shuttler Core spline coefficients update trigger. """Shuttler Core spline coefficients update trigger.
:param channel: RTIO channel number of the trigger interface. :param channel: RTIO channel number of the trigger interface.
:param core_device: Core device name. :param core_device: Core device name.
""" """
kernel_invariants = {"core", "channel", "target_o"} core: KernelInvariant[Core]
channel: KernelInvariant[int32]
target_o: KernelInvariant[int32]
def __init__(self, dmgr, channel, core_device="core"): def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -290,7 +303,7 @@ class Trigger:
self.target_o = channel << 8 self.target_o = channel << 8
@kernel @kernel
def trigger(self, trig_out): def trigger(self, trig_out: int32):
"""Triggers coefficient update of (a) Shuttler Core channel(s). """Triggers coefficient update of (a) Shuttler Core channel(s).
Each bit corresponds to a Shuttler waveform generator core. Setting Each bit corresponds to a Shuttler waveform generator core. Setting
@ -304,15 +317,15 @@ class Trigger:
rtio_output(self.target_o, trig_out) rtio_output(self.target_o, trig_out)
RELAY_SPI_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END | RELAY_SPI_CONFIG = (0*SPI_OFFLINE | 1*SPI_END |
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | 0*SPI_INPUT | 0*SPI_CS_POLARITY |
0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | 0*SPI_CLK_POLARITY | 0*SPI_CLK_PHASE |
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) 0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
ADC_SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END | ADC_SPI_CONFIG = (0*SPI_OFFLINE | 0*SPI_END |
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | 0*SPI_INPUT | 0*SPI_CS_POLARITY |
1*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE | 1*SPI_CLK_POLARITY | 1*SPI_CLK_PHASE |
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) 0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
# SPI clock write and read dividers # SPI clock write and read dividers
# CS should assert at least 9.5 ns after clk pulse # CS should assert at least 9.5 ns after clk pulse
@ -335,6 +348,7 @@ _AD4115_REG_CH0 = 0x10
_AD4115_REG_SETUPCON0 = 0x20 _AD4115_REG_SETUPCON0 = 0x20
@nac3
class Relay: class Relay:
"""Shuttler AFE relay switches. """Shuttler AFE relay switches.
@ -349,7 +363,8 @@ class Relay:
:param spi_device: SPI bus device name. :param spi_device: SPI bus device name.
:param core_device: Core device name. :param core_device: Core device name.
""" """
kernel_invariant = {"core", "bus"} core: KernelInvariant[Core]
bus: KernelInvariant[SPIMaster]
def __init__(self, dmgr, spi_device, core_device="core"): def __init__(self, dmgr, spi_device, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -366,7 +381,7 @@ class Relay:
RELAY_SPI_CONFIG, 16, SPIT_RELAY_WR, CS_RELAY | CS_LED) RELAY_SPI_CONFIG, 16, SPIT_RELAY_WR, CS_RELAY | CS_LED)
@kernel @kernel
def enable(self, en: TInt32): def enable(self, en: int32):
"""Enable/Disable relay switches of corresponding channels. """Enable/Disable relay switches of corresponding channels.
Each bit corresponds to the relay switch of a channel. Asserting a bit Each bit corresponds to the relay switch of a channel. Asserting a bit
@ -379,20 +394,22 @@ class Relay:
self.bus.write(en << 16) self.bus.write(en << 16)
@nac3
class ADC: class ADC:
"""Shuttler AFE ADC (AD4115) driver. """Shuttler AFE ADC (AD4115) driver.
:param spi_device: SPI bus device name. :param spi_device: SPI bus device name.
:param core_device: Core device name. :param core_device: Core device name.
""" """
kernel_invariant = {"core", "bus"} core: KernelInvariant[Core]
bus: KernelInvariant[SPIMaster]
def __init__(self, dmgr, spi_device, core_device="core"): def __init__(self, dmgr, spi_device, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
self.bus = dmgr.get(spi_device) self.bus = dmgr.get(spi_device)
@kernel @kernel
def read_id(self) -> TInt32: def read_id(self) -> int32:
"""Read the product ID of the ADC. """Read the product ID of the ADC.
The expected return value is 0x38DX, the 4 LSbs are don't cares. The expected return value is 0x38DX, the 4 LSbs are don't cares.
@ -414,86 +431,86 @@ class ADC:
after the transfer appears to interrupt the start-up sequence. after the transfer appears to interrupt the start-up sequence.
""" """
self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC) self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC)
self.bus.write(0xffffffff) self.bus.write(-1)
self.bus.write(0xffffffff) self.bus.write(-1)
self.bus.set_config_mu( self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC) ADC_SPI_CONFIG | SPI_END, 32, SPIT_ADC_WR, CS_ADC)
self.bus.write(0xffffffff) self.bus.write(-1)
@kernel @kernel
def read8(self, addr: TInt32) -> TInt32: def read8(self, addr: int32) -> int32:
"""Read from 8 bit register. """Read from 8 bit register.
:param addr: Register address. :param addr: Register address.
:return: Read-back register content. :return: Read-back register content.
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, ADC_SPI_CONFIG | SPI_END | SPI_INPUT,
16, SPIT_ADC_RD, CS_ADC) 16, SPIT_ADC_RD, CS_ADC)
self.bus.write((addr | 0x40) << 24) self.bus.write((addr | 0x40) << 24)
return self.bus.read() & 0xff return self.bus.read() & 0xff
@kernel @kernel
def read16(self, addr: TInt32) -> TInt32: def read16(self, addr: int32) -> int32:
"""Read from 16 bit register. """Read from 16 bit register.
:param addr: Register address. :param addr: Register address.
:return: Read-back register content. :return: Read-back register content.
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, ADC_SPI_CONFIG | SPI_END | SPI_INPUT,
24, SPIT_ADC_RD, CS_ADC) 24, SPIT_ADC_RD, CS_ADC)
self.bus.write((addr | 0x40) << 24) self.bus.write((addr | 0x40) << 24)
return self.bus.read() & 0xffff return self.bus.read() & 0xffff
@kernel @kernel
def read24(self, addr: TInt32) -> TInt32: def read24(self, addr: int32) -> int32:
"""Read from 24 bit register. """Read from 24 bit register.
:param addr: Register address. :param addr: Register address.
:return: Read-back register content. :return: Read-back register content.
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, ADC_SPI_CONFIG | SPI_END | SPI_INPUT,
32, SPIT_ADC_RD, CS_ADC) 32, SPIT_ADC_RD, CS_ADC)
self.bus.write((addr | 0x40) << 24) self.bus.write((addr | 0x40) << 24)
return self.bus.read() & 0xffffff return self.bus.read() & 0xffffff
@kernel @kernel
def write8(self, addr: TInt32, data: TInt32): def write8(self, addr: int32, data: int32):
"""Write to 8 bit register. """Write to 8 bit register.
:param addr: Register address. :param addr: Register address.
:param data: Data to be written. :param data: Data to be written.
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END, 16, SPIT_ADC_WR, CS_ADC) ADC_SPI_CONFIG | SPI_END, 16, SPIT_ADC_WR, CS_ADC)
self.bus.write(addr << 24 | (data & 0xff) << 16) self.bus.write(addr << 24 | (data & 0xff) << 16)
@kernel @kernel
def write16(self, addr: TInt32, data: TInt32): def write16(self, addr: int32, data: int32):
"""Write to 16 bit register. """Write to 16 bit register.
:param addr: Register address. :param addr: Register address.
:param data: Data to be written. :param data: Data to be written.
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END, 24, SPIT_ADC_WR, CS_ADC) ADC_SPI_CONFIG | SPI_END, 24, SPIT_ADC_WR, CS_ADC)
self.bus.write(addr << 24 | (data & 0xffff) << 8) self.bus.write(addr << 24 | (data & 0xffff) << 8)
@kernel @kernel
def write24(self, addr: TInt32, data: TInt32): def write24(self, addr: int32, data: int32):
"""Write to 24 bit register. """Write to 24 bit register.
:param addr: Register address. :param addr: Register address.
:param data: Data to be written. :param data: Data to be written.
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC) ADC_SPI_CONFIG | SPI_END, 32, SPIT_ADC_WR, CS_ADC)
self.bus.write(addr << 24 | (data & 0xffffff)) self.bus.write(addr << 24 | (data & 0xffffff))
@kernel @kernel
def read_ch(self, channel: TInt32) -> TFloat: def read_ch(self, channel: int32) -> float:
"""Sample a Shuttler channel on the AFE. """Sample a Shuttler channel on the AFE.
It performs a single conversion using profile 0 and setup 0, on the It performs a single conversion using profile 0 and setup 0, on the
@ -507,9 +524,9 @@ class ADC:
self.write16(_AD4115_REG_SETUPCON0, 0x1300) self.write16(_AD4115_REG_SETUPCON0, 0x1300)
self.single_conversion() self.single_conversion()
delay(100*us) self.core.delay(100.*us)
adc_code = self.read24(_AD4115_REG_DATA) adc_code = self.read24(_AD4115_REG_DATA)
return ((adc_code / (1 << 23)) - 1) * 2.5 / 0.1 return ((float(adc_code) / float(1 << 23)) - 1.) * 2.5 / 0.1
@kernel @kernel
def single_conversion(self): def single_conversion(self):
@ -560,10 +577,10 @@ class ADC:
self.reset() self.reset()
# Although the datasheet claims 500 us reset wait time, only waiting # Although the datasheet claims 500 us reset wait time, only waiting
# for ~500 us can result in DOUT pin stuck in high # for ~500 us can result in DOUT pin stuck in high
delay(2500*us) self.core.delay(2500.*us)
@kernel @kernel
def calibrate(self, volts, trigger, config, samples=[-5.0, 0.0, 5.0]): def calibrate(self, volts: list[Volt], trigger: Trigger, config: Config, samples: Option[list[float]] = none):
"""Calibrate the Shuttler waveform generator using the ADC on the AFE. """Calibrate the Shuttler waveform generator using the ADC on the AFE.
It finds the average slope rate and average offset by samples, and It finds the average slope rate and average offset by samples, and
@ -588,33 +605,35 @@ class ADC:
:param samples: A list of sample voltages for calibration. There must :param samples: A list of sample voltages for calibration. There must
be at least 2 samples to perform slope rate calculation. be at least 2 samples to perform slope rate calculation.
""" """
assert len(volts) == 16 samples_l = samples.unwrap() if samples.is_some() else [-5.0, 0.0, 5.0]
assert len(samples) > 1
measurements = [0.0] * len(samples) assert len(volts) == 16
assert len(samples_l) > 1
measurements = [0.0 for _ in range(len(samples_l))]
for ch in range(16): for ch in range(16):
# Find the average slope rate and offset # Find the average slope rate and offset
for i in range(len(samples)): for i in range(len(samples_l)):
self.core.break_realtime() self.core.break_realtime()
volts[ch].set_waveform( volts[ch].set_waveform(
shuttler_volt_to_mu(samples[i]), 0, 0, 0) shuttler_volt_to_mu(samples_l[i]), 0, int64(0), int64(0))
trigger.trigger(1 << ch) trigger.trigger(1 << ch)
measurements[i] = self.read_ch(ch) measurements[i] = self.read_ch(ch)
# Find the average output slope # Find the average output slope
slope_sum = 0.0 slope_sum = 0.0
for i in range(len(samples) - 1): for i in range(len(samples_l) - 1):
slope_sum += (measurements[i+1] - measurements[i])/(samples[i+1] - samples[i]) slope_sum += (measurements[i+1] - measurements[i])/(samples_l[i+1] - samples_l[i])
slope_avg = slope_sum / (len(samples) - 1) slope_avg = slope_sum / float(len(samples_l) - 1)
gain_code = int32(1 / slope_avg * (2 ** 16)) & 0xffff gain_code = int32(1. / slope_avg * float(2 ** 16)) & 0xffff
# Scale the measurements by 1/slope, find average offset # Scale the measurements by 1/slope, find average offset
offset_sum = 0.0 offset_sum = 0.0
for i in range(len(samples)): for i in range(len(samples_l)):
offset_sum += (measurements[i] / slope_avg) - samples[i] offset_sum += (measurements[i] / slope_avg) - samples_l[i]
offset_avg = offset_sum / len(samples) offset_avg = offset_sum / float(len(samples_l))
offset_code = shuttler_volt_to_mu(-offset_avg) offset_code = shuttler_volt_to_mu(-offset_avg)

View File

@ -1,61 +1,82 @@
from artiq.experiment import * from numpy import int32, int64
from artiq.coredevice.shuttler import shuttler_volt_to_mu
DAC_Fs_MHZ = 125 from artiq.experiment import *
from artiq.coredevice.core import Core
from artiq.coredevice.ttl import TTLOut
from artiq.coredevice.shuttler import (
shuttler_volt_to_mu,
Config as ShuttlerConfig,
Trigger as ShuttlerTrigger,
Volt as ShuttlerDCBias,
Dds as ShuttlerDDS,
Relay as ShuttlerRelay,
ADC as ShuttlerADC)
DAC_Fs_MHZ = 125.
CORDIC_GAIN = 1.64676 CORDIC_GAIN = 1.64676
@portable @portable
def shuttler_phase_offset(offset_degree): def shuttler_phase_offset(offset_degree: float) -> int32:
return round(offset_degree / 360 * (2 ** 16)) return round(offset_degree / 360. * float(2 ** 16))
@portable @portable
def shuttler_freq_mu(freq_mhz): def shuttler_freq_mu(freq_mhz: float) -> int32:
return round(float(2) ** 32 / DAC_Fs_MHZ * freq_mhz) return round(float(2) ** 32 / DAC_Fs_MHZ * freq_mhz)
@portable @portable
def shuttler_chirp_rate_mu(freq_mhz_per_us): def shuttler_chirp_rate_mu(freq_mhz_per_us: float) -> int32:
return round(float(2) ** 32 * freq_mhz_per_us / (DAC_Fs_MHZ ** 2)) return round(float(2) ** 32 * freq_mhz_per_us / (DAC_Fs_MHZ ** 2))
@portable @portable
def shuttler_freq_sweep(start_f_MHz, end_f_MHz, time_us): def shuttler_freq_sweep(start_f_MHz: float, end_f_MHz: float, time_us: float) -> int32:
return shuttler_chirp_rate_mu((end_f_MHz - start_f_MHz)/(time_us)) return shuttler_chirp_rate_mu((end_f_MHz - start_f_MHz)/time_us)
@portable @portable
def shuttler_volt_amp_mu(volt): def shuttler_volt_amp_mu(volt: float) -> int32:
return shuttler_volt_to_mu(volt) return shuttler_volt_to_mu(volt)
@portable @portable
def shuttler_volt_damp_mu(volt_per_us): def shuttler_volt_damp_mu(volt_per_us: float) -> int32:
return round(float(2) ** 32 * (volt_per_us / 20) / DAC_Fs_MHZ) return round(float(2) ** 32 * (volt_per_us / 20.) / DAC_Fs_MHZ)
@portable @portable
def shuttler_volt_ddamp_mu(volt_per_us_square): def shuttler_volt_ddamp_mu(volt_per_us_square: float) -> int64:
return round(float(2) ** 48 * (volt_per_us_square / 20) * 2 / (DAC_Fs_MHZ ** 2)) return round64(float(2) ** 48 * (volt_per_us_square / 20.) * 2. / (DAC_Fs_MHZ ** 2))
@portable @portable
def shuttler_volt_dddamp_mu(volt_per_us_cube): def shuttler_volt_dddamp_mu(volt_per_us_cube: float) -> int64:
return round(float(2) ** 48 * (volt_per_us_cube / 20) * 6 / (DAC_Fs_MHZ ** 3)) return round64(float(2) ** 48 * (volt_per_us_cube / 20.) * 6. / (DAC_Fs_MHZ ** 3))
@portable @portable
def shuttler_dds_amp_mu(volt): def shuttler_dds_amp_mu(volt: float) -> int32:
return shuttler_volt_amp_mu(volt / CORDIC_GAIN) return shuttler_volt_amp_mu(volt / CORDIC_GAIN)
@portable @portable
def shuttler_dds_damp_mu(volt_per_us): def shuttler_dds_damp_mu(volt_per_us: float) -> int32:
return shuttler_volt_damp_mu(volt_per_us / CORDIC_GAIN) return shuttler_volt_damp_mu(volt_per_us / CORDIC_GAIN)
@portable @portable
def shuttler_dds_ddamp_mu(volt_per_us_square): def shuttler_dds_ddamp_mu(volt_per_us_square: float) -> int64:
return shuttler_volt_ddamp_mu(volt_per_us_square / CORDIC_GAIN) return shuttler_volt_ddamp_mu(volt_per_us_square / CORDIC_GAIN)
@portable @portable
def shuttler_dds_dddamp_mu(volt_per_us_cube): def shuttler_dds_dddamp_mu(volt_per_us_cube: float) -> int64:
return shuttler_volt_dddamp_mu(volt_per_us_cube / CORDIC_GAIN) return shuttler_volt_dddamp_mu(volt_per_us_cube / CORDIC_GAIN)
@nac3
class Shuttler(EnvExperiment): class Shuttler(EnvExperiment):
core: KernelInvariant[Core]
shuttler0_leds: KernelInvariant[list[TTLOut]]
shuttler0_config: KernelInvariant[ShuttlerConfig]
shuttler0_trigger: KernelInvariant[ShuttlerTrigger]
shuttler0_volt: KernelInvariant[list[ShuttlerDCBias]]
shuttler0_dds: KernelInvariant[list[ShuttlerDDS]]
shuttler0_relay: KernelInvariant[ShuttlerRelay]
shuttler0_adc: KernelInvariant[ShuttlerADC]
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
self.setattr_device("core_dma")
self.setattr_device("scheduler") self.setattr_device("scheduler")
self.shuttler0_leds = [ self.get_device("shuttler0_led{}".format(i)) for i in range(2) ] self.shuttler0_leds = [ self.get_device("shuttler0_led{}".format(i)) for i in range(2) ]
self.setattr_device("shuttler0_config") self.setattr_device("shuttler0_config")
@ -64,12 +85,6 @@ class Shuttler(EnvExperiment):
self.shuttler0_dds = [ self.get_device("shuttler0_dds{}".format(i)) for i in range(16) ] self.shuttler0_dds = [ self.get_device("shuttler0_dds{}".format(i)) for i in range(16) ]
self.setattr_device("shuttler0_relay") self.setattr_device("shuttler0_relay")
self.setattr_device("shuttler0_adc") self.setattr_device("shuttler0_adc")
@kernel
def record(self):
with self.core_dma.record("example_waveform"):
self.example_waveform()
@kernel @kernel
def init(self): def init(self):
@ -84,35 +99,33 @@ class Shuttler(EnvExperiment):
self.core.break_realtime() self.core.break_realtime()
self.init() self.init()
self.record() print_rpc("Example Waveforms are on OUT0 and OUT1")
example_waveform_handle = self.core_dma.get_handle("example_waveform")
print("Example Waveforms are on OUT0 and OUT1")
self.core.break_realtime() self.core.break_realtime()
while not(self.scheduler.check_termination()): #while not(self.scheduler.check_termination()):
delay(1*s) while True:
self.core_dma.playback_handle(example_waveform_handle) self.core.delay(1.*s)
self.example_waveform()
@kernel @kernel
def shuttler_reset(self): def shuttler_reset(self):
for i in range(16): for i in range(16):
self.shuttler_channel_reset(i) self.shuttler_channel_reset(i)
# To avoid RTIO Underflow # To avoid RTIO Underflow
delay(50*us) self.core.delay(50.*us)
@kernel @kernel
def shuttler_channel_reset(self, ch): def shuttler_channel_reset(self, ch: int32):
self.shuttler0_volt[ch].set_waveform( self.shuttler0_volt[ch].set_waveform(
a0=0, a0=0,
a1=0, a1=0,
a2=0, a2=int64(0),
a3=0, a3=int64(0),
) )
self.shuttler0_dds[ch].set_waveform( self.shuttler0_dds[ch].set_waveform(
b0=0, b0=0,
b1=0, b1=0,
b2=0, b2=int64(0),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=0, c1=0,
c2=0, c2=0,
@ -163,13 +176,13 @@ class Shuttler(EnvExperiment):
## Step 2 ## ## Step 2 ##
start_f_MHz = 0.01 start_f_MHz = 0.01
end_f_MHz = 0.05 end_f_MHz = 0.05
duration_us = 500 duration_us = 500.
# OUT0 and OUT1 have their frequency and phase aligned at 500us # OUT0 and OUT1 have their frequency and phase aligned at 500us
self.shuttler0_dds[0].set_waveform( self.shuttler0_dds[0].set_waveform(
b0=shuttler_dds_amp_mu(1.0), b0=shuttler_dds_amp_mu(1.0),
b1=0, b1=0,
b2=0, b2=int64(0),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=shuttler_freq_mu(start_f_MHz), c1=shuttler_freq_mu(start_f_MHz),
c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us), c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us),
@ -177,22 +190,22 @@ class Shuttler(EnvExperiment):
self.shuttler0_dds[1].set_waveform( self.shuttler0_dds[1].set_waveform(
b0=shuttler_dds_amp_mu(1.0), b0=shuttler_dds_amp_mu(1.0),
b1=0, b1=0,
b2=0, b2=int64(0),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=shuttler_freq_mu(end_f_MHz), c1=shuttler_freq_mu(end_f_MHz),
c2=0, c2=0,
) )
self.shuttler0_trigger.trigger(0b11) self.shuttler0_trigger.trigger(0b11)
delay(500*us) self.core.delay(500.*us)
## Step 3 ## ## Step 3 ##
# OUT0 and OUT1 has 180 degree phase difference # OUT0 and OUT1 has 180 degree phase difference
self.shuttler0_dds[0].set_waveform( self.shuttler0_dds[0].set_waveform(
b0=shuttler_dds_amp_mu(1.0), b0=shuttler_dds_amp_mu(1.0),
b1=0, b1=0,
b2=0, b2=int64(0),
b3=0, b3=int64(0),
c0=shuttler_phase_offset(180.0), c0=shuttler_phase_offset(180.0),
c1=shuttler_freq_mu(end_f_MHz), c1=shuttler_freq_mu(end_f_MHz),
c2=0, c2=0,
@ -200,7 +213,7 @@ class Shuttler(EnvExperiment):
# Phase and Output Setting of OUT1 is retained # Phase and Output Setting of OUT1 is retained
# if the channel is not triggered or config is not cleared # if the channel is not triggered or config is not cleared
self.shuttler0_trigger.trigger(0b1) self.shuttler0_trigger.trigger(0b1)
delay(500*us) self.core.delay(500.*us)
## Step 4 ## ## Step 4 ##
# b(0) = 0, b(250) = 8.545, b(500) = 0 # b(0) = 0, b(250) = 8.545, b(500) = 0
@ -208,7 +221,7 @@ class Shuttler(EnvExperiment):
b0=0, b0=0,
b1=shuttler_dds_damp_mu(0.06835937), b1=shuttler_dds_damp_mu(0.06835937),
b2=shuttler_dds_ddamp_mu(-0.0001367187), b2=shuttler_dds_ddamp_mu(-0.0001367187),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=shuttler_freq_mu(end_f_MHz), c1=shuttler_freq_mu(end_f_MHz),
c2=0, c2=0,
@ -217,26 +230,26 @@ class Shuttler(EnvExperiment):
b0=0, b0=0,
b1=shuttler_dds_damp_mu(0.06835937), b1=shuttler_dds_damp_mu(0.06835937),
b2=shuttler_dds_ddamp_mu(-0.0001367187), b2=shuttler_dds_ddamp_mu(-0.0001367187),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=0, c1=0,
c2=0, c2=0,
) )
self.shuttler0_trigger.trigger(0b11) self.shuttler0_trigger.trigger(0b11)
delay(500*us) self.core.delay(500.*us)
## Step 5 ## ## Step 5 ##
self.shuttler0_volt[0].set_waveform( self.shuttler0_volt[0].set_waveform(
a0=shuttler_volt_amp_mu(-5.0), a0=shuttler_volt_amp_mu(-5.0),
a1=int32(shuttler_volt_damp_mu(0.01)), a1=int32(shuttler_volt_damp_mu(0.01)),
a2=0, a2=int64(0),
a3=0, a3=int64(0),
) )
self.shuttler0_dds[0].set_waveform( self.shuttler0_dds[0].set_waveform(
b0=shuttler_dds_amp_mu(1.0), b0=shuttler_dds_amp_mu(1.0),
b1=0, b1=0,
b2=0, b2=int64(0),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=shuttler_freq_mu(end_f_MHz), c1=shuttler_freq_mu(end_f_MHz),
c2=0, c2=0,
@ -244,59 +257,59 @@ class Shuttler(EnvExperiment):
self.shuttler0_volt[1].set_waveform( self.shuttler0_volt[1].set_waveform(
a0=shuttler_volt_amp_mu(-5.0), a0=shuttler_volt_amp_mu(-5.0),
a1=int32(shuttler_volt_damp_mu(0.01)), a1=int32(shuttler_volt_damp_mu(0.01)),
a2=0, a2=int64(0),
a3=0, a3=int64(0),
) )
self.shuttler0_dds[1].set_waveform( self.shuttler0_dds[1].set_waveform(
b0=0, b0=0,
b1=0, b1=0,
b2=0, b2=int64(0),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=0, c1=0,
c2=0, c2=0,
) )
self.shuttler0_trigger.trigger(0b11) self.shuttler0_trigger.trigger(0b11)
delay(1000*us) self.core.delay(1000.*us)
## Step 6 ## ## Step 6 ##
self.shuttler0_volt[0].set_waveform( self.shuttler0_volt[0].set_waveform(
a0=shuttler_volt_amp_mu(-2.5), a0=shuttler_volt_amp_mu(-2.5),
a1=int32(shuttler_volt_damp_mu(0.01)), a1=int32(shuttler_volt_damp_mu(0.01)),
a2=0, a2=int64(0),
a3=0, a3=int64(0),
) )
self.shuttler0_dds[0].set_waveform( self.shuttler0_dds[0].set_waveform(
b0=0, b0=0,
b1=shuttler_dds_damp_mu(0.06835937), b1=shuttler_dds_damp_mu(0.06835937),
b2=shuttler_dds_ddamp_mu(-0.0001367187), b2=shuttler_dds_ddamp_mu(-0.0001367187),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=shuttler_freq_mu(start_f_MHz), c1=shuttler_freq_mu(start_f_MHz),
c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us), c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us),
) )
self.shuttler0_trigger.trigger(0b1) self.shuttler0_trigger.trigger(0b1)
self.shuttler_channel_reset(1) self.shuttler_channel_reset(1)
delay(500*us) self.core.delay(500.*us)
## Step 7 ## ## Step 7 ##
self.shuttler0_volt[0].set_waveform( self.shuttler0_volt[0].set_waveform(
a0=shuttler_volt_amp_mu(2.5), a0=shuttler_volt_amp_mu(2.5),
a1=int32(shuttler_volt_damp_mu(-0.01)), a1=int32(shuttler_volt_damp_mu(-0.01)),
a2=0, a2=int64(0),
a3=0, a3=int64(0),
) )
self.shuttler0_dds[0].set_waveform( self.shuttler0_dds[0].set_waveform(
b0=0, b0=0,
b1=shuttler_dds_damp_mu(-0.06835937), b1=shuttler_dds_damp_mu(-0.06835937),
b2=shuttler_dds_ddamp_mu(0.0001367187), b2=shuttler_dds_ddamp_mu(0.0001367187),
b3=0, b3=int64(0),
c0=shuttler_phase_offset(180.0), c0=shuttler_phase_offset(180.0),
c1=shuttler_freq_mu(end_f_MHz), c1=shuttler_freq_mu(end_f_MHz),
c2=shuttler_freq_sweep(end_f_MHz, start_f_MHz, duration_us), c2=shuttler_freq_sweep(end_f_MHz, start_f_MHz, duration_us),
) )
self.shuttler0_trigger.trigger(0b1) self.shuttler0_trigger.trigger(0b1)
delay(500*us) self.core.delay(500.*us)
## Step 8 ## ## Step 8 ##
self.shuttler0_relay.enable(0) self.shuttler0_relay.enable(0)
@ -308,7 +321,7 @@ class Shuttler(EnvExperiment):
for i in range(2): for i in range(2):
for j in range(3): for j in range(3):
self.shuttler0_leds[i].pulse(.1*s) self.shuttler0_leds[i].pulse(.1*s)
delay(.1*s) self.core.delay(.1*s)
@kernel @kernel
def relay_init(self): def relay_init(self):

View File

@ -3,7 +3,7 @@
"min_artiq_version": "9.0", "min_artiq_version": "9.0",
"variant": "nac3devices", "variant": "nac3devices",
"hw_rev": "v2.0", "hw_rev": "v2.0",
"base": "standalone", "drtio_role": "master",
"core_addr": "192.168.1.70", "core_addr": "192.168.1.70",
"peripherals": [ "peripherals": [
{ {
@ -51,6 +51,10 @@
{ {
"type": "phaser", "type": "phaser",
"ports": [10] "ports": [10]
},
{
"type": "shuttler",
"ports": [11]
} }
] ]
} }

View File

@ -14,6 +14,7 @@ from artiq.coredevice.edge_counter import EdgeCounter
from artiq.coredevice.grabber import Grabber from artiq.coredevice.grabber import Grabber
from artiq.coredevice.fastino import Fastino from artiq.coredevice.fastino import Fastino
from artiq.coredevice.phaser import Phaser from artiq.coredevice.phaser import Phaser
from artiq.coredevice.shuttler import Volt as ShuttlerDCBias, Dds as ShuttlerDDS
@nac3 @nac3
@ -34,6 +35,8 @@ class NAC3Devices(EnvExperiment):
grabber0: KernelInvariant[Grabber] grabber0: KernelInvariant[Grabber]
fastino0: KernelInvariant[Fastino] fastino0: KernelInvariant[Fastino]
phaser0: KernelInvariant[Phaser] phaser0: KernelInvariant[Phaser]
shuttler0_volt0: KernelInvariant[ShuttlerDCBias]
shuttler0_dds0: KernelInvariant[ShuttlerDDS]
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
@ -52,6 +55,8 @@ class NAC3Devices(EnvExperiment):
self.setattr_device("grabber0") self.setattr_device("grabber0")
self.setattr_device("fastino0") self.setattr_device("fastino0")
self.setattr_device("phaser0") self.setattr_device("phaser0")
self.setattr_device("shuttler0_volt0")
self.setattr_device("shuttler0_dds0")
@kernel @kernel
def run(self): def run(self):