Source code for atomiq.components.sinara.urukul

from __future__ import annotations

from atomiq.components.primitives import Component, Switchable
from atomiq.components.electronics.rfsource import RFSource
from atomiq.helper import identity_float, identity_float_int32, replace_member, isnan, less_than_uint32, unsigned_bitshift32

from artiq.experiment import kernel, delay, delay_mu, parallel
from artiq.language.types import TFloat, TBool, TInt32, TList, TInt64, TStr
from artiq.language.units import us, ms
from artiq.language.core import now_mu, at_mu
from artiq.coredevice import ad9910
from artiq.coredevice import urukul
from artiq.coredevice import spi2 as spi
import artiq
import numpy as np


[docs] class Urukul(Component): """Sinara Urukul 4 Channel DDS This class represents the Sinara Urukul 4 channel DDS RF source. Args: cpld: The ARTIQ cpld device from the `device_db`, e.g. `@urukul0_cpld`. default_profile: Which profile in the Urukul CPLD to use by default, i.e. if no profile is given (default 7) """ kernel_invariants = {"cpld"} def __init__(self, cpld: artiq.coredevice.urukul.CPLD, default_profile: TInt32 = 7, *args, **kwargs): Component.__init__(self, *args, **kwargs) self.cpld = cpld self.profile = default_profile @kernel def _prerun(self): self.core.break_realtime() delay(250 * us) delay(10 * us) self.cpld.get_att_mu() self.set_profile(self.profile, trigger=False)
[docs] @kernel def set_profile(self, profile: TInt32 = ad9910.DEFAULT_PROFILE, trigger: TBool = True): """ Set the Urukul to the given profile. :param TInt32 profile: profile :param TBool trigger: pulse io_update """ self.cpld.set_profile(profile) if trigger: self.cpld.io_update.pulse_mu(8)
[docs] class UrukulChannel(RFSource, Switchable): """Single DDS Channel of a Sinara Urukul Args: urukul: The Urukul component this channel belongs to device: The ARTIQ device from the `device_db` representing the Urukul channel, e.g. `@urukul0_ch0` ttl: The ARTIQ device from the `device_db` representing the Urukul fast RF switch, e.g. `@ttl_urukul0_sw0` default_attenuation: Default attenuation to set for the channel on startup. (default -19dBm) profile_arb: Profile on the DDS to use for arbitrary function generation. (default 0) use_drg: Use the DRG for amplitude or frequency ramps (recommended). (default True) auto_sync_drg: Defined if the drg is synced to either frequency (1) or amplitude (2) in the prepare phase. Default is not synced (0). Syncing is recommended in combination with :attr:`blind` if continuous operation is required. Tip: The :meth:`ramp` method can be used in combination with the :code:`blind` component argument. This allows for generating continous, jump-free ramps between experiments. This makes it possible to use the dds output to generate a lock signal for a laser cavity lock. Attention: Setting parameters before the DRG is synced with the python object would break the continuity. Run :meth:`sync_drg` before setting amplitude, frequency or phase parameters or use :attr:`auto_sync_drg`. """ kernel_invariants = {"urukul", "device", "ttl", "profile_default", "profile_arb"} def __init__( self, urukul: Urukul, device: TStr, ttl=None, default_attenuation=19.0, profile_arb: TInt32 = 0, use_drg: TBool = True, auto_sync_drg: TInt32 = 0, *args, **kwargs ): RFSource.__init__(self, *args, **kwargs) Switchable.__init__(self, ["RF_on"]) self.urukul = urukul self.device = device if ttl is not None: self.ttl = ttl else: replace_member(self, "on", "_on_cpld") replace_member(self, "off", "_off_cpld") self.ttl = None self.attenuation = default_attenuation self.profile_arb = profile_arb self.profile_default = ad9910.DEFAULT_PROFILE # _drg_state = -1: DRG not synced but continous operation requested, takes last drg values as start point # _drg_state = 0 : DRG not synced, no continuous operation requested # _drg_state = 1 : DRG synced to self.frequency # _drg_state = 2 : DRG synced to self.amplitude self.use_drg = use_drg self.auto_sync_drg = auto_sync_drg if self.blind: self._drg_state = -1 else: self._drg_state = 0
[docs] @kernel def init(self): """ Initialize the urukul channel. Necessary only once after power on. Warning: This function is not called automatically as it is slow and resets the device leading to a frequency/amplitude jump. """ sta = urukul.urukul_sta_rf_sw(self.device.cpld.sta_read()) if int(sta) == 0: #cpld not initialized yet delay(10 * us) self.device.cpld.init() self.device.init()
@kernel def _prerun(self): delay(250 * us) # ATH, 150 -> 250 self.off() self.device.set_phase_mode(ad9910.PHASE_MODE_CONTINUOUS) # attenuation delay(150 * us) self.set_att(self.attenuation) # DDS print(self._drg_state, self.auto_sync_drg) if self._drg_state == 0: # do not set if continous drg mode without jumps is active delay(150 * us) self.set( frequency=self.frequency, phase=0.0, amplitude=self.amplitude, profile=-1 ) elif self._drg_state == -1 and self.auto_sync_drg > 0: self.sync_drg(self.auto_sync_drg) @kernel def _prerun_blind(self): if self._drg_state == -1 and self.auto_sync_drg > 0: self.sync_drg(self.auto_sync_drg)
[docs] @kernel def set( self, frequency: TFloat = float("nan"), amplitude: TFloat = float("nan"), phase: TFloat = 0.0, profile: TInt32 = -1, ): """ Set the frequency and amplitude. Frequency/amplitude are set to the last known value if ``float("nan")`` is passed (default). Args: frequency: Frequency in Hz (``float("nan")`` to use previous value) amplitude: Amplitude in units of full scale (``float("nan")`` to use previous value) phase: Phase tuning word in turns profile: DDS Profile (``-1`` to keep current profile) """ if not isnan(frequency): self.frequency = frequency if self._drg_state == 1: self._drg_state = 0 if not isnan(amplitude): self.amplitude = amplitude if self._drg_state == 2: self._drg_state = 0 profile = self.profile_default if profile < 0 else profile self.device.set( frequency=self.frequency, amplitude=self.amplitude, phase=phase, profile=profile, ) # also write to the arb profile self.device.set( frequency=self.frequency, amplitude=self.amplitude, phase=phase, profile=self.profile_arb, )
[docs] @kernel def set_att(self, attenuation: TFloat): """ Set the hardware attenuation for this urukul channel via CPLD. Args: attenuation: channel attenuation (0. to 31.0 in 0.5 increments) [dB] """ self.attenuation = attenuation self.device.set_att(attenuation) # self.kernel_log(self.attenuation) delay_mu(30)
@kernel def _set_frequency(self, frequency: TFloat): self.set(frequency=frequency) @kernel def _set_amplitude(self, amplitude: TFloat): self.set(amplitude=amplitude) @kernel def _set_phase(self, phase: TFloat): self.set(phase=phase)
[docs] @kernel def on(self): """ Turn on via ttl. :return """ delay_mu(4) self.ttl.on() delay_mu(4)
[docs] @kernel def off(self): """ Turn off via ttl. :return """ delay_mu(4) self.ttl.off() delay_mu(4)
@kernel def _on_cpld(self): self.device.cpld.cfg_sw(self.device.chip_select-4, True) delay_mu(30) @kernel def _off_cpld(self): self.device.cpld.cfg_sw(self.device.chip_select-4, False) delay_mu(30)
[docs] @kernel def amplitude_to_drg_step(self, val): return np.int32(round(0xffffffff*val))
[docs] @kernel def read64_32(self, addr: TInt32): """ Read two 32-bit values from 64-bit register. Advances time cursor by 8600ns Args: addr: Register address Returns: Tuple of two 32-bit integer register values """ self.device.bus.set_config_mu( urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.device.chip_select) self.device.bus.write((addr | 0x80) << 24) self.device.bus.set_config_mu( urukul.SPI_CONFIG | spi.SPI_INPUT, 32, urukul.SPIT_DDS_RD, self.device.chip_select) self.device.bus.write(0) self.device.bus.set_config_mu( urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 32, urukul.SPIT_DDS_RD, self.device.chip_select) self.device.bus.write(0) hi = self.device.bus.read() lo = self.device.bus.read() return hi, lo
@kernel def _drg_write_registers(self,ramp_low, ramp_high, step_word): """ Advances time cursor by 2500ns """ self.device.write64( ad9910._AD9910_REG_RAMP_LIMIT, ramp_high, ramp_low, ) self.device.write64( ad9910._AD9910_REG_RAMP_STEP, step_word, # destination parameter (here FTW) decrement (DRCTL = 0) 0, # destination parameter increment (DRCTL = 1) )
[docs] @kernel def sync_drg(self, target: TInt32): """ Synchronises the python object with the current register values of the DRG. Advances time cursor by 25us. Args: target: Target to synchronise to. 1 for frequency, 2 for amplitude. """ old_high, old_low = self.read64_32(ad9910._AD9910_REG_RAMP_LIMIT) delay_mu(7800) last_ramp_step, _ = self.read64_32(ad9910._AD9910_REG_RAMP_STEP) if last_ramp_step > 0: # Ramp was going down before, DRG is in lower limit current_mu = old_low else: # Ramp was going up before, DRG is in upper limit current_mu = old_high if target == 1: self.frequency = self.device.ftw_to_frequency(current_mu) self._drg_state = 1 elif target == 2: self.amplitude = self.device.asf_to_amplitude(unsigned_bitshift32(current_mu,18)) self._drg_state = 2
@kernel def _write_drg_ramp(self, ramp_rate, end_val, old_low, old_high, step): start_time = now_mu() last_ramp_step = np.int32(self.device.read64(ad9910._AD9910_REG_RAMP_STEP) >> 32) hold_accumulator_down = False delay(10*us) # calculate and set ramp rate to work around the non-exposed ramp direction pin self.device.write32( ad9910._AD9910_REG_RAMP_RATE, ramp_rate << 16, # set negative slope rate ) delay(5*us) if last_ramp_step > 0: if less_than_uint32(end_val,old_low): # Last ramp was down, current ramp is down ramp_high = old_low ramp_low = end_val step_word = step+1 delay_mu(-8) # We need to bring the DRG to its upper limit first to avoid a jump self._drg_write_registers(old_low,old_low-1, -1*1<<31) self.device.cpld.io_update.pulse_mu(8) elif less_than_uint32(old_low,end_val): # Last ramp was down, current ramp is up ramp_high = end_val ramp_low = old_low step_word = -step hold_accumulator_down = True else: # Start frequency same as end frequency, no ramp necessary return else: if less_than_uint32(end_val,old_high): # Last ramp was up, current ramp is down step_word = step ramp_high = old_high ramp_low = end_val delay_mu(-8) # We need to make sure the DRG is in its upper limit first to avoid a jump self._drg_write_registers(old_high,old_high-1, -1*1<<31) self.device.cpld.io_update.pulse_mu(8) elif less_than_uint32(old_high, end_val): # Last ramp was up, current ramp is up step_word = -step ramp_high = end_val ramp_low = old_high hold_accumulator_down = True else: # Start frequency same as end frequency, no ramp necessary return delay(10*us) self._drg_write_registers(ramp_low, ramp_high, step_word) if hold_accumulator_down: self.device.write32(ad9910._AD9910_REG_CFR1, (1 << 12) | 2) self.device.cpld.io_update.pulse_mu(8) at_mu(start_time + 40000) @kernel def _amplitude_ramp_drg(self, duration: TFloat, amplitude_start: TFloat, amplitude_end: TFloat, ramp_timestep: TFloat = 200e-6): delay(-66.5*us) sync_lim_register = False if amplitude_start != self.amplitude and self._drg_state >= 0: # Fixed ramp from start to finish, we don't care about jumps start_asf = max(self.device.amplitude_to_asf(amplitude_start),1) << 18 old_low_asf = start_asf old_high_asf = old_low_asf sync_lim_register = True delay_mu(2*8600) # compensate for read delay in other cases elif self._drg_state < 0: # Use the current DRG state as starting point delay_mu(-7000) old_high_asf, old_low_asf = self.read64_32(ad9910._AD9910_REG_RAMP_LIMIT) delay_mu(7000) last_ramp_step, _ = self.read64_32(ad9910._AD9910_REG_RAMP_STEP) if last_ramp_step > 0: # Ramp was going down before, DRG is in lower limit start_asf = old_low_asf else: # Ramp was going up before, DRG is in upper limit start_asf = old_high_asf amplitude_start = self.device.asf_to_amplitude(unsigned_bitshift32(start_asf,18)) else: # Start from the current amplitude set in the component old_high_asf, old_low_asf = self.read64_32(ad9910._AD9910_REG_RAMP_LIMIT) start_asf = max(self.device.amplitude_to_asf(self.amplitude),1) << 18 delay_mu(8600) # compensate for read delay in other cases if (self._drg_state != 2 or sync_lim_register) and self._drg_state >= 0: # DRG limit register is not synced with current amplitude old_low_asf = start_asf old_high_asf = start_asf if ramp_timestep > 262e-6: delay_mu(10000) # We need this delay to shedule the RPC log call self.experiment.log.warning(self.identifier + " Amplitude ramp step length is too long for using the DRG. Must be" \ " <= 262us but is {0:.2f}, automatically reducing to 260us. This Warning introduces a delay!" \ " Set 'ramp_timestep' or the component property 'default_ramp_steps' to a smaller value!", [ramp_timestep*1e6,]) ramp_timestep = 260e-6 # Setting the ramp limit to 0 leads to undefind behavior -> cut end_asf = max(self.device.amplitude_to_asf(amplitude_end),1) << 18 amp_step = abs(amplitude_end - amplitude_start) /(duration / ramp_timestep -1) ramp_rate = int(round(self.device.sysclk*ramp_timestep/4)) cfr2 = ( (1 << 21) | # DRG destination = amplitude, see AD9910 datasheet table 11 (1 << 20) | # DRG destination = amplitude, see AD9910 datasheet table 11 (1 << 19) | # enable DRG (1 << 24) | # asf_profile enabled (1 << 16) # enable effective ftw ) delay_mu(7780) self.device.write32(ad9910._AD9910_REG_CFR2, cfr2) self._write_drg_ramp(ramp_rate, end_asf, old_low_asf, old_high_asf, self.device.amplitude_to_asf(amp_step) << 18) self.device.set_cfr1(drg_load_lrr=1) self.device.cpld.io_update.pulse_mu(8) with parallel: # minimum delay for very short ramps to avoid underflow delay(duration) delay_mu(10000) self.set_amplitude(amplitude_end) self.device.set_cfr2() # Switch ramp generator off again self.device.set_cfr1() self._drg_state = 2 # DRG registers now synced with amplitude delay_mu(-4000) @kernel def _frequency_ramp_drg(self, duration: TFloat, frequency_start: TFloat, frequency_end: TFloat, ramp_timestep: TFloat): delay(-66.5*us) sync_lim_register = False if frequency_start != self.frequency and self._drg_state >= 0: #fixed ramp from start to finish, we don't care about jumps old_low_ftw = self.device.frequency_to_ftw(frequency_start) old_high_ftw = old_low_ftw sync_lim_register = True start_ftw = self.device.frequency_to_ftw(frequency_start) delay_mu(2*8600) # compensate for read delay in other cases elif self._drg_state < 0: # use the current DRG state as starting point delay_mu(-7000) old_high_ftw, old_low_ftw = self.read64_32(ad9910._AD9910_REG_RAMP_LIMIT) delay_mu(7000) last_ramp_step, _ = self.read64_32(ad9910._AD9910_REG_RAMP_STEP) if last_ramp_step > 0: # ramp was going down before, drg is in lower limit start_ftw = old_low_ftw else: # ramp was going up before, drg is in upper limit start_ftw = old_high_ftw frequency_start = self.device.ftw_to_frequency(start_ftw) else: #start from the current frequency old_high_ftw, old_low_ftw = self.read64_32(ad9910._AD9910_REG_RAMP_LIMIT) start_ftw = self.device.frequency_to_ftw(self.frequency) delay_mu(8600) # compensate for read delay in other cases if (self._drg_state != 1 or sync_lim_register) and self._drg_state >= 0: # drg limit register is not synced with current frequency old_low_ftw = start_ftw old_high_ftw = start_ftw if ramp_timestep > 262e-6: delay_mu(10000) self.experiment.log.warning(self.identifier + " Frequency ramp step length is too long for using the DRG. Must be" \ " <= 262us but is {0:.2f}, automatically reducing to 260us. This Warning introduces a delay!" \ " Set 'ramp_timestep' or the component property 'default_ramp_steps' to a smaller value!", [ramp_timestep*1e6,]) ramp_timestep = 260e-6 end_ftw = self.device.frequency_to_ftw(frequency_end) freq_step = abs(frequency_end - frequency_start) *ramp_timestep / duration ramp_rate = int(round(self.device.sysclk*ramp_timestep/4)) cfr2 = ( (0 << 20) | # DRG destination = FTW, see AD9910 datasheet table 11 (0 << 21) | # DRG destination = FTW, see AD9910 datasheet table 11 (1 << 19) | # enable DRG (1 << 24) | # asf_profile enabled (1 << 16) # enable effective ftw ) delay_mu(7780) self.device.write32(ad9910._AD9910_REG_CFR2, cfr2) self._write_drg_ramp(ramp_rate, end_ftw, old_low_ftw, old_high_ftw, self.device.frequency_to_ftw(freq_step)) self.device.set_cfr1(drg_load_lrr=1) self.device.cpld.io_update.pulse_mu(8) with parallel: delay(duration) delay_mu(10000) self.set_frequency(frequency_end) self.device.set_cfr2() # switch ramp generator off again self.device.set_cfr1() self._drg_state = 1 # drg registers now synced with frequency delay_mu(-4000) @kernel(flags={"fast-math"}) def _ramp(self, duration: TFloat, frequency_start: TFloat, frequency_end: TFloat, amplitude_start: TFloat, amplitude_end: TFloat, ramp_timestep: TFloat = 50e-6): if amplitude_start == self.amplitude and amplitude_end == self.amplitude and self.use_drg: if frequency_start != self.frequency or frequency_end != self.frequency or self._drg_state == -1: # pure frequency ramp, use drg self._frequency_ramp_drg(duration, frequency_start, frequency_end, ramp_timestep) else: # only do something if there is a change in frequency with parallel: delay(duration) delay(10*us) elif frequency_start == self.frequency and frequency_end == self.frequency and self.use_drg: if amplitude_start != self.amplitude or amplitude_end != self.amplitude or self._drg_state == -1: # pure amplitude ramp, use drg self._amplitude_ramp_drg(duration, amplitude_start, amplitude_end, ramp_timestep) else: # only do something if there is a change in amplitude with parallel: delay(duration) delay(10*us) else: # frequency and amplitude ramp, we can't use the drg as it dosn't support parallel ramps or drg disabled if ramp_timestep < 3.0*us: ramp_timestep = 3.0 * us self.experiment.log.warning( self.identifier + ": Ramp timestep {0:.3f} below limit (3.0us). Reducing...", [ramp_timestep*1e6] ) n = int(duration / ramp_timestep) ramp_timestep = duration / float(n) if n < 2: self.experiment.log.error( self.identifier + ": Ramp impossible with duration {0}us and ramp_timestep {1}us", [duration*1e6, ramp_timestep*1e6] ) freq_step = (frequency_end - frequency_start) / (n - 1) amp_step = (amplitude_end - amplitude_start) / (n - 1) delay(-1.31*us) for i in range(n): _freq = frequency_start + i * freq_step _amp = amplitude_start + i * amp_step with parallel: self.set(frequency=_freq, amplitude=_amp) delay(ramp_timestep)
[docs] @kernel(flags={"fast-math"}) def ramp( self, duration: TFloat, frequency_start: TFloat = float("nan"), frequency_end: TFloat = float("nan"), amplitude_start: TFloat = float("nan"), amplitude_end: TFloat = float("nan"), ramp_timestep: TFloat = float("nan"), ramp_steps: TInt32 = -1 ): """ Ramp frequency and amplitude over a given duration. Parameters default to ``-1`` or ``nan`` to indicate no change. If the start frequency/amplitude is set to ``nan``, the ramp starts from the last frequency/amplitude which was set. This method advances the timeline by ´duration´ Tip: This method can be used in combination with the :code:`blind` component argument. This allows for generating continous, jump-free ramps between experiments. This makes it possible to use the dds output to generate a lock signal for a laser cavity lock. Note that in this mode using amplitude *and* frequency ramps leads to undefined behavior when switching between them. As the DRG can only address either amplitude or frequency, parallel ramps of both parameters are ramped in a simple step by step way. Usage of the DRG can be avoided by setting the component attribute :code:`use_drg` to False. Attention: When using the DRG, this method always advances the timeline by at least 10us, even if the ramp is shorter. Args: duration: ramp duration [s] frequency_start: initial frequency [Hz] frequency_end: end frequency [Hz] amplitude_start: initial amplitude [0..1] amplitude_end: end amplitude [0..1] ramp_timesteps: time between steps in the ramp [s] ramp_steps: number of steps the whole ramp should have. This takes precedence over `ramp_timesteps` """ RFSource.ramp( self, duration, frequency_start=frequency_start, frequency_end=frequency_end, amplitude_start=amplitude_start, amplitude_end=amplitude_end, ramp_timestep=ramp_timestep, ramp_steps=ramp_steps, )
@kernel def _write_ram(self, data: TList(TFloat), transform=identity_float_int32): """Write data to RAM. .. note:: This is copied from upstream artiq to fix a bug, where the data is written into the RAM in reversed order. Once this is fixed upstream, we can remove this The profile to write to and the step, start, and end address need to be configured before and separately using :meth:`set_profile_ram` and the parent CPLD `set_profile`. :param data: Data to be written to RAM. """ _AD9910_REG_RAM = 0x16 self.device.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.device.chip_select) self.device.bus.write(_AD9910_REG_RAM << 24) self.device.bus.set_config_mu(urukul.SPI_CONFIG, 32, urukul.SPIT_DDS_WR, self.device.chip_select) i = len(data) - 1 while i > 0: self.device.bus.write(transform(data[i])) i -= 1 self.device.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, urukul.SPIT_DDS_WR, self.device.chip_select) self.device.bus.write(transform(data[0])) @kernel def _arb(self, duration: TFloat, samples_amp: TList(TFloat), samples_freq: TList(TFloat), samples_phase: TList(TFloat), repetitions: TInt32 = 1, prepare_only: TBool = False, run_prepared: TBool = False, transform_amp=identity_float, transform_freq=identity_float, transform_phase=identity_float, profile: TInt32 = 0 ): # initialize to make the compiler happy ram_destination = 0 t_step = 0.0 _transform_amp = identity_float_int32 _transform_freq = identity_float_int32 _transform_phase = identity_float_int32 num_samples = max(len(samples_amp), max(len(samples_freq), len(samples_phase))) t_step = duration/float(num_samples) if len(samples_amp) + len(samples_freq) + len(samples_phase) != num_samples: self.experiment.log.warning(self.identifier + ": Trying ARB on more than one parameter (amp, freq, phase) " "but Urukul only supports one at a time. Falling back to software ARB.") # RFSource._arb(duration, samples_amp, samples_freq, samples_phase, repetitions) else: if t_step < 4e-9: self.experiment.log.error(self.identifier + ": ARB timestep {0:.1f} ns below minimum (4ns). Decrease " "number of samples or increase duration", [t_step*1e9]) if t_step < 40e-9: self.experiment.log.warning(self.identifier + ": ARB timestep {0:.1f} ns close to minimum (4ns). " "Rounding errors will occur", [t_step*1e9]) if not run_prepared: delay(-(5.0 + 0.535*num_samples)*us) if len(samples_amp) > 0: @kernel def _transform_amp(x): return self.device.amplitude_to_asf(transform_amp(x)) << 18 self._prepare_arb(samples_amp, t_step, repetitions > 1, profile=self.profile_arb, transform=_transform_amp) elif len(samples_freq) > 0: @kernel def _transform_freq(x): return self.device.frequency_to_ftw(transform_freq(x)) self._prepare_arb(samples_freq, t_step, repetitions > 1, profile=self.profile_arb, transform=_transform_freq) elif len(samples_phase) > 0: @kernel def _transform_phase(x): return self.device.turns_to_pow(transform_phase(x)) << 16 self._prepare_arb(samples_phase, t_step, repetitions > 1, profile=self.profile_arb, transform=_transform_phase) if prepare_only: self.device.cpld.set_profile(self.profile_default) else: delay(2.3*us) if not prepare_only: delay(-2.3*us) if len(samples_amp) > 0: ram_destination = ad9910.RAM_DEST_ASF elif len(samples_freq) > 0: ram_destination = ad9910.RAM_DEST_FTW elif len(samples_phase) > 0: ram_destination = ad9910.RAM_DEST_POW self._run_arb(repetitions*t_step*num_samples, profile=self.profile_arb, ram_destination=ram_destination) @kernel def _prepare_arb(self, samples: TList(TFloat), t_step: TFloat, repeat: TBool = False, ram_offset: TInt32 = 0, profile: TInt32 = 0, transform=identity_float_int32) -> TInt32: """ Prepare a RAM profile for arbitrary amplitude modulation with amplitude values and equidistant time steps. Args: samples: List of sample values in units im machine units. Maximum lenght is 1024 samples. t_step: Time that should pass between the samples of the amplitudes list in units of s. repeat: Should the sequence of samples be repeated? If not the last value is hold. (default False) ram_offset: Address offset of the RAM storage address. (default 0) profile: Profile of the DDS to use for the RAM mode. If none is given, profile 0 is used. """ num_samples = len(samples) # disable RAM for writing data self.device.set_cfr1(ram_enable=0) self.device.cpld.io_update.pulse_mu(8) self.device.cpld.set_profile(profile) # write to RAM self.device.set_profile_ram( start=ram_offset, end=ram_offset + num_samples - 1, step=round(t_step / 4e-9), profile=profile, mode=ad9910.RAM_MODE_CONT_RAMPUP if repeat else ad9910.RAM_MODE_RAMPUP, nodwell_high=0, ) self.device.cpld.io_update.pulse_mu(8) # write data to RAM, write only len(num_samples) words # self.device.write_ram(samples[:num_samples]) self._write_ram(samples[:num_samples], transform=transform) return num_samples - 1 + ram_offset @kernel def _run_arb(self, duration: TFloat, ram_destination: TInt32, trigger: TBool = True, profile: TInt32 = -1): self.device.set(frequency=self.frequency, amplitude=self.amplitude, phase=self.phase, ram_destination=ram_destination) if ram_destination != ad9910.RAM_DEST_ASF: # work around for https://github.com/m-labs/artiq/issues/1554: Activate OSK self.device.set_cfr1(ram_enable=1, ram_destination=ram_destination, manual_osk_external=0, osk_enable=1, select_auto_osk=0) else: self.device.set_cfr1(ram_enable=1, ram_destination=ram_destination) # switch_profile: self.device.cpld.set_profile(profile if profile >= 0 else self.profile_arb) if trigger: self.device.cpld.io_update.pulse_mu(8) delay(duration) # disable RAM mode delay(-0.8*us) self.device.set_cfr1(ram_enable=0) # switch_profile: self.device.cpld.set_profile(self.profile_default) if trigger: self.device.cpld.io_update.pulse_mu(8)