artiq/artiq/coredevice/sawg.py

280 lines
11 KiB
Python

from artiq.language.types import TInt32, TFloat
from numpy import int32
from artiq.language.core import kernel, now_mu
from artiq.coredevice.spline import Spline
from artiq.coredevice.rtio import rtio_output
# sawg.Config addresses
_SAWG_DIV = 0
_SAWG_CLR = 1
_SAWG_IQ_EN = 2
# _SAWF_PAD = 3 # reserved
_SAWG_DUC_I_MIN = 4
_SAWG_DUC_I_MAX = 5
_SAWG_DUC_Q_MIN = 6
_SAWG_DUC_Q_MAX = 7
_SAWG_OUT_MIN = 8
_SAWG_OUT_MAX = 9
class Config:
"""SAWG configuration.
Exposes the configurable quantities of a single SAWG channel.
:param channel: RTIO channel number of the channel.
:param core: Core device.
"""
kernel_invariants = {"channel", "core", "_out_scale", "_duc_scale"}
def __init__(self, channel, core, cordic_gain=1.):
self.channel = channel
self.core = core
self._out_scale = (1 << 15) - 1.
self._duc_scale = self._out_scale/cordic_gain
@kernel
def set_div(self, div: TInt32, n: TInt32=0):
"""Set the spline evolution divider and current counter value.
The divider and the spline evolution are synchronized across all
spline channels within a SAWG channel. The phase accumulator always
evolves at full speed.
:param div: Spline evolution divider, such that
``t_sawg_spline/t_rtio_coarse = div + 1``. Default: ``0``.
:param n: Current value of the counter. Default: ``0``.
"""
rtio_output(now_mu(), self.channel, _SAWG_DIV, div | (n << 16))
@kernel
def set_clr(self, clr0: TInt32, clr1: TInt32, clr2: TInt32):
"""Set the phase clear mode for the three phase accumulators.
When the ``clr`` bit for a given phase accumulator is
set, that phase accumulator will be cleared with every phase RTIO
command and the output phase will be exactly the phase RTIO value
("absolute phase update mode").
In turn, when the bit is cleared, the phase RTIO channels only
provide a phase offset to the current value of the phase
accumulator ("relative phase update mode").
:param clr0: Auto-clear phase accumulator of the ``phase0``/
``frequency0`` DUC. Default: ``True``
:param clr1: Auto-clear phase accumulator of the ``phase1``/
``frequency1`` DDS. Default: ``True``
:param clr2: Auto-clear phase accumulator of the ``phase2``/
``frequency2`` DDS. Default: ``True``
"""
rtio_output(now_mu(), self.channel, _SAWG_CLR, clr1 |
(clr2 << 1) | (clr0 << 2))
@kernel
def set_iq_en(self, i_enable: TInt32, q_enable: TInt32):
"""Enable I/Q data on this DAC channel.
Every pair of SAWG channels forms a buddy pair.
The ``iq_en`` configuration controls which DDS data is emitted to the
DACs.
Refer to the documentation of :class:`SAWG` for a mathematical
description of ``i_enable`` and ``q_enable``.
.. note:: Quadrature data from the buddy channel is currently
ignored in the SAWG gateware and not added to the DAC output.
This is equivalent to the ``q_enable`` switch always being ``0``.
:param i_enable: Controls adding the in-phase
DUC-DDS data of *this* SAWG channel to *this* DAC channel.
Default: ``1``.
:param q_enable: controls adding the quadrature
DUC-DDS data of this SAWG's *buddy* channel to *this* DAC
channel. Default: ``0``.
"""
rtio_output(now_mu(), self.channel, _SAWG_IQ_EN, i_enable |
(q_enable << 1))
@kernel
def set_duc_i_max_mu(self, limit: TInt32):
"""Set the digital up-converter (DUC) I data summing junction upper
limit. In machine units.
The default limits are the full range of signed 16 bit data.
For a description of the limiter functions in normalized units see:
.. seealso:: :meth:`set_duc_i_max`
"""
rtio_output(now_mu(), self.channel, _SAWG_DUC_I_MAX, limit)
@kernel
def set_duc_i_min_mu(self, limit: TInt32):
""".. seealso:: :meth:`set_duc_i_max_mu`"""
rtio_output(now_mu(), self.channel, _SAWG_DUC_I_MIN, limit)
@kernel
def set_duc_q_max_mu(self, limit: TInt32):
""".. seealso:: :meth:`set_duc_i_max_mu`"""
rtio_output(now_mu(), self.channel, _SAWG_DUC_Q_MAX, limit)
@kernel
def set_duc_q_min_mu(self, limit: TInt32):
""".. seealso:: :meth:`set_duc_i_max_mu`"""
rtio_output(now_mu(), self.channel, _SAWG_DUC_Q_MIN, limit)
@kernel
def set_out_max_mu(self, limit: TInt32):
""".. seealso:: :meth:`set_duc_i_max_mu`"""
rtio_output(now_mu(), self.channel, _SAWG_OUT_MAX, limit)
@kernel
def set_out_min_mu(self, limit: TInt32):
""".. seealso:: :meth:`set_duc_i_max_mu`"""
rtio_output(now_mu(), self.channel, _SAWG_OUT_MIN, limit)
@kernel
def set_duc_i_max(self, limit: TFloat):
"""Set the digital up-converter (DUC) I data summing junction upper
limit.
Each of the three summing junctions has a saturating adder with
configurable upper and lower limits. The three summing junctions are:
* At the in-phase input to the ``phase0``/``frequency0`` fast DUC,
where the in-phase outputs of the two slow DDS (1 and 2) are
added together.
* At the quadrature input to the ``phase0``/``frequency0``
fast DUC, where the quadrature outputs of the two slow DDS
(1 and 2) are added together.
* Before the DAC, where the following three data streams
are added together:
* the output of the ``offset`` spline,
* (optionally, depending on ``i_enable``) the in-phase output
of the ``phase0``/``frequency0`` fast DUC, and
* (optionally, depending on ``q_enable``) the quadrature
output of the ``phase0``/``frequency0`` fast DUC of the
buddy channel.
Refer to the documentation of :class:`SAWG` for a mathematical
description of the summing junctions.
:param limit: Limit value ``[-1, 1]``. The output of the limiter will
never exceed this limit. The default limits are the full range
``[-1, 1]``.
.. seealso::
* :meth:`set_duc_i_max`: Upper limit of the in-phase input to
the DUC.
* :meth:`set_duc_i_min`: Lower limit of the in-phase input to
the DUC.
* :meth:`set_duc_q_max`: Upper limit of the quadrature input to
the DUC.
* :meth:`set_duc_q_min`: Lower limit of the quadrature input to
the DUC.
* :meth:`set_out_max`: Upper limit of the DAC output.
* :meth:`set_out_min`: Lower limit of the DAC output.
"""
self.set_duc_i_max_mu(int32(round(limit*self._duc_scale)))
@kernel
def set_duc_i_min(self, limit: TFloat):
""".. seealso:: :meth:`set_duc_i_max`"""
self.set_duc_i_min_mu(int32(round(limit*self._duc_scale)))
@kernel
def set_duc_q_max(self, limit: TFloat):
""".. seealso:: :meth:`set_duc_i_max`"""
self.set_duc_q_max_mu(int32(round(limit*self._duc_scale)))
@kernel
def set_duc_q_min(self, limit: TFloat):
""".. seealso:: :meth:`set_duc_i_max`"""
self.set_duc_q_min_mu(int32(round(limit*self._duc_scale)))
@kernel
def set_out_max(self, limit: TFloat):
""".. seealso:: :meth:`set_duc_i_max`"""
self.set_out_max_mu(int32(round(limit*self._out_scale)))
@kernel
def set_out_min(self, limit: TFloat):
""".. seealso:: :meth:`set_duc_i_max`"""
self.set_out_min_mu(int32(round(limit*self._out_scale)))
class SAWG:
"""Smart arbitrary waveform generator channel.
The channel is parametrized as: ::
oscillators = exp(2j*pi*(frequency0*t + phase0))*(
amplitude1*exp(2j*pi*(frequency1*t + phase1)) +
amplitude2*exp(2j*pi*(frequency2*t + phase2)))
output = (offset +
i_enable*Re(oscillators) +
q_enable*Im(buddy_oscillators))
This parametrization can be viewed as two complex (quadrature) oscillators
(``frequency1``/``phase1`` and ``frequency2``/``phase2``) followed by
a complex digital up-converter (DUC, ``frequency0``/``phase0``) on top of a
(real/in-phase) ``offset``. The ``i_enable``/``q_enable`` switches
enable emission of quadrature signals for later analog quadrature mixing
distinguishing upper and lower sidebands and thus doubling the bandwidth.
They can also be used to emit four-tone signals.
.. note:: Quadrature data from the buddy channel is currently
ignored in the SAWG gateware and not added to the DAC output.
This is equivalent to the ``q_enable`` switch always being ``0``.
The configuration channel and the nine
:class:`artiq.coredevice.spline.Spline` interpolators are accessible as
attributes:
* :attr:`config`: :class:`Config`
* :attr:`offset`, :attr:`amplitude1`, :attr:`amplitude2`: in units
of full scale
* :attr:`phase0`, :attr:`phase1`, :attr:`phase2`: in units of turns
* :attr:`frequency0`, :attr:`frequency1`, :attr:`frequency2`: in units
of Hz
:param channel_base: RTIO channel number of the first channel (amplitude).
The configuration channel and frequency/phase/amplitude channels are
then assumed to be successive channels.
:param parallelism: Number of output samples per coarse RTIO clock cycle.
:param core_device: Name of the core device that this SAWG is on.
"""
kernel_invariants = {"channel_base", "core",
"amplitude1", "frequency1", "phase1",
"amplitude2", "frequency2", "phase2",
"frequency0", "phase0", "offset"}
def __init__(self, dmgr, channel_base, parallelism, core_device="core"):
self.core = dmgr.get(core_device)
self.channel_base = channel_base
width = 16
time_width = 16
cordic_gain = 1.646760258057163 # Cordic(width=16, guard=None).gain
self.config = Config(channel_base, self.core, cordic_gain)
self.offset = Spline(width, time_width, channel_base + 1,
self.core, 2.)
self.amplitude1 = Spline(width, time_width, channel_base + 2,
self.core, 2*cordic_gain**2)
self.frequency1 = Spline(3*width, time_width, channel_base + 3,
self.core, 1/self.core.coarse_ref_period)
self.phase1 = Spline(width, time_width, channel_base + 4,
self.core, 1.)
self.amplitude2 = Spline(width, time_width, channel_base + 5,
self.core, 2*cordic_gain**2)
self.frequency2 = Spline(3*width, time_width, channel_base + 6,
self.core, 1/self.core.coarse_ref_period)
self.phase2 = Spline(width, time_width, channel_base + 7,
self.core, 1.)
self.frequency0 = Spline(2*width, time_width, channel_base + 8,
self.core,
parallelism/self.core.coarse_ref_period)
self.phase0 = Spline(width, time_width, channel_base + 9,
self.core, 1.)