2014-07-11 00:13:37 +08:00
|
|
|
from artiq.language.core import *
|
2015-01-12 18:51:23 +08:00
|
|
|
from artiq.language.db import *
|
2014-05-31 00:20:13 +08:00
|
|
|
from artiq.language.units import *
|
2015-05-02 10:35:21 +08:00
|
|
|
from artiq.coredevice import ttl
|
2014-05-31 00:20:13 +08:00
|
|
|
|
2014-09-05 12:03:22 +08:00
|
|
|
|
2014-11-21 04:32:56 +08:00
|
|
|
PHASE_MODE_DEFAULT = -1
|
|
|
|
PHASE_MODE_CONTINUOUS = 0
|
|
|
|
PHASE_MODE_ABSOLUTE = 1
|
|
|
|
PHASE_MODE_TRACKING = 2
|
|
|
|
|
|
|
|
|
2015-01-12 18:51:23 +08:00
|
|
|
class DDS(AutoDB):
|
2014-09-30 17:38:52 +08:00
|
|
|
"""Core device Direct Digital Synthesis (DDS) driver.
|
|
|
|
|
2014-09-30 18:10:40 +08:00
|
|
|
Controls DDS devices managed directly by the core device's runtime. It also
|
2015-05-02 10:35:21 +08:00
|
|
|
uses a RTIO TTL channel (through :class:`artiq.coredevice.ttl.TTLOut`) to
|
2014-09-30 18:10:40 +08:00
|
|
|
control a RF switch that gates the output of the DDS device.
|
2014-09-30 17:38:52 +08:00
|
|
|
|
|
|
|
:param dds_sysclk: DDS system frequency, used for computing the frequency
|
|
|
|
tuning words.
|
|
|
|
:param reg_channel: channel number of the DDS device to control.
|
2014-10-16 23:36:28 +08:00
|
|
|
:param rtio_switch: RTIO channel number of the RF switch associated with
|
2014-09-30 17:38:52 +08:00
|
|
|
the DDS device.
|
|
|
|
|
|
|
|
"""
|
2015-01-12 18:51:23 +08:00
|
|
|
class DBKeys:
|
2015-03-08 18:37:53 +08:00
|
|
|
core = Device()
|
2015-01-12 18:51:23 +08:00
|
|
|
dds_sysclk = Parameter(1*GHz)
|
|
|
|
reg_channel = Argument()
|
|
|
|
rtio_switch = Argument()
|
2014-05-31 00:20:13 +08:00
|
|
|
|
2014-09-05 12:03:22 +08:00
|
|
|
def build(self):
|
2014-12-01 17:47:24 +08:00
|
|
|
self.previous_on = False
|
2014-09-12 15:36:17 +08:00
|
|
|
self.previous_frequency = 0*MHz
|
2014-11-21 04:32:56 +08:00
|
|
|
self.set_phase_mode(PHASE_MODE_CONTINUOUS)
|
2015-05-02 10:35:21 +08:00
|
|
|
self.sw = ttl.TTLOut(core=self.core, channel=self.rtio_switch)
|
2014-05-31 00:20:13 +08:00
|
|
|
|
2014-10-13 17:05:35 +08:00
|
|
|
@portable
|
|
|
|
def frequency_to_ftw(self, frequency):
|
|
|
|
"""Returns the frequency tuning word corresponding to the given
|
|
|
|
frequency.
|
|
|
|
|
|
|
|
"""
|
2014-11-18 09:36:00 +08:00
|
|
|
return round(2**32*frequency/self.dds_sysclk)
|
2014-10-13 17:05:35 +08:00
|
|
|
|
|
|
|
@portable
|
|
|
|
def ftw_to_frequency(self, ftw):
|
|
|
|
"""Returns the frequency corresponding to the given frequency tuning
|
|
|
|
word.
|
|
|
|
|
|
|
|
"""
|
|
|
|
return ftw*self.dds_sysclk/2**32
|
|
|
|
|
2014-09-05 12:03:22 +08:00
|
|
|
@kernel
|
2014-11-21 04:32:56 +08:00
|
|
|
def set_phase_mode(self, phase_mode):
|
|
|
|
"""Sets the phase mode of the DDS channel. Supported phase modes are:
|
|
|
|
|
|
|
|
* ``PHASE_MODE_CONTINUOUS``: the phase accumulator is unchanged when
|
|
|
|
switching frequencies. The DDS phase is the sum of the phase
|
|
|
|
accumulator and the phase offset. The only discrete jumps in the
|
|
|
|
DDS output phase come from changes to the phase offset.
|
|
|
|
|
|
|
|
* ``PHASE_MODE_ABSOLUTE``: the phase accumulator is reset when
|
|
|
|
switching frequencies. Thus, the phase of the DDS at the time of
|
|
|
|
the frequency change is equal to the phase offset.
|
|
|
|
|
|
|
|
* ``PHASE_MODE_TRACKING``: when switching frequencies, the phase
|
|
|
|
accumulator is set to the value it would have if the DDS had been
|
|
|
|
running at the specified frequency since the start of the
|
|
|
|
experiment.
|
|
|
|
|
|
|
|
"""
|
|
|
|
self.phase_mode = phase_mode
|
|
|
|
syscall("dds_phase_clear_en", self.reg_channel,
|
|
|
|
self.phase_mode != PHASE_MODE_CONTINUOUS)
|
|
|
|
|
|
|
|
@kernel
|
|
|
|
def on(self, frequency, phase_mode=PHASE_MODE_DEFAULT, phase_offset=0):
|
2014-09-30 17:38:52 +08:00
|
|
|
"""Sets the DDS channel to the specified frequency and turns it on.
|
|
|
|
|
2014-11-21 04:32:56 +08:00
|
|
|
If the DDS channel was already on, a real-time frequency and phase
|
|
|
|
update is performed.
|
|
|
|
|
|
|
|
:param frequency: frequency to generate.
|
|
|
|
:param phase_mode: if specified, overrides the default phase mode set
|
|
|
|
by ``set_phase_mode`` for this call.
|
|
|
|
:param phase_offset: adds an offset, in turns, to the phase.
|
2014-09-30 17:38:52 +08:00
|
|
|
|
|
|
|
"""
|
2014-11-21 04:32:56 +08:00
|
|
|
if phase_mode != PHASE_MODE_DEFAULT:
|
|
|
|
old_phase_mode = self.phase_mode
|
|
|
|
self.set_phase_mode(phase_mode)
|
|
|
|
|
2014-09-12 15:36:17 +08:00
|
|
|
if self.previous_frequency != frequency:
|
2015-05-02 10:35:21 +08:00
|
|
|
merge = self.sw.o_previous_timestamp == time_to_cycles(now())
|
2014-10-13 23:58:32 +08:00
|
|
|
if not merge:
|
2014-09-12 15:36:17 +08:00
|
|
|
self.sw.sync()
|
2014-11-21 04:32:56 +08:00
|
|
|
# Channel is already on:
|
|
|
|
# Precise timing of frequency change is required.
|
|
|
|
# Channel is off:
|
|
|
|
# Use soft timing on FUD to prevent conflicts when reprogramming
|
|
|
|
# several channels that need to be turned on at the same time.
|
2014-12-01 17:47:24 +08:00
|
|
|
rt_fud = merge or self.previous_on
|
2014-11-21 04:32:56 +08:00
|
|
|
if self.phase_mode != PHASE_MODE_CONTINUOUS:
|
2014-12-09 13:50:33 +08:00
|
|
|
sysclk_per_microcycle = int(self.dds_sysclk*
|
|
|
|
self.core.ref_period)
|
2014-09-12 15:36:17 +08:00
|
|
|
else:
|
2014-12-09 13:50:33 +08:00
|
|
|
sysclk_per_microcycle = 0
|
2014-11-21 04:32:56 +08:00
|
|
|
syscall("dds_program", time_to_cycles(now()), self.reg_channel,
|
2014-12-09 13:50:33 +08:00
|
|
|
self.frequency_to_ftw(frequency), int(phase_offset*2**14),
|
|
|
|
sysclk_per_microcycle,
|
2014-11-21 04:32:56 +08:00
|
|
|
rt_fud, self.phase_mode == PHASE_MODE_TRACKING)
|
2014-09-12 15:36:17 +08:00
|
|
|
self.previous_frequency = frequency
|
|
|
|
self.sw.on()
|
2014-12-01 17:47:24 +08:00
|
|
|
self.previous_on = True
|
2014-09-09 22:00:51 +08:00
|
|
|
|
2014-11-21 04:32:56 +08:00
|
|
|
if phase_mode != PHASE_MODE_DEFAULT:
|
|
|
|
self.set_phase_mode(old_phase_mode)
|
|
|
|
|
2014-09-09 22:00:51 +08:00
|
|
|
@kernel
|
2014-09-12 15:36:17 +08:00
|
|
|
def off(self):
|
2014-09-30 17:38:52 +08:00
|
|
|
"""Turns the DDS channel off.
|
|
|
|
|
|
|
|
"""
|
2014-09-12 15:36:17 +08:00
|
|
|
self.sw.off()
|
2014-12-01 17:47:24 +08:00
|
|
|
self.previous_on = False
|
2014-09-09 22:00:51 +08:00
|
|
|
|
|
|
|
@kernel
|
2014-11-21 04:32:56 +08:00
|
|
|
def pulse(self, frequency, duration,
|
|
|
|
phase_mode=PHASE_MODE_DEFAULT, phase_offset=0):
|
2014-09-30 17:38:52 +08:00
|
|
|
"""Pulses the DDS channel for the specified duration at the specified
|
|
|
|
frequency.
|
|
|
|
|
2014-11-21 04:32:56 +08:00
|
|
|
See ``on`` for a description of the parameters.
|
|
|
|
|
2014-09-30 17:38:52 +08:00
|
|
|
Equivalent to a ``on``, ``delay``, ``off`` sequence.
|
|
|
|
|
|
|
|
"""
|
2014-11-21 04:32:56 +08:00
|
|
|
self.on(frequency, phase_mode, phase_offset)
|
2014-09-12 15:36:17 +08:00
|
|
|
delay(duration)
|
|
|
|
self.off()
|