Source code for artiq.coredevice.sawg

Driver for the Smart Arbitrary Waveform Generator (SAWG) on RTIO.

The SAWG is an "improved DDS" built in gateware and interfacing to
high-speed DACs.

Output event replacement is supported except on the configuration channel.

from artiq.language.types import TInt32, TFloat
from numpy import int32, int64
from artiq.language.core import kernel, now_mu
from artiq.coredevice.spline import Spline
from artiq.coredevice.rtio import rtio_output

# sawg.Config addresses
# _SAWF_PAD = 3  # reserved

[docs]class Config: """SAWG configuration. Exposes the configurable quantities of a single SAWG channel. Access to the configuration registers for a SAWG channel can not be concurrent. There must be at least :attr:`_rtio_interval` machine units of delay between accesses. Replacement is not supported and will be lead to an ``RTIOCollision`` as this is likely a programming error. All methods therefore advance the timeline by the duration of one configuration register transfer. :param channel: RTIO channel number of the channel. :param core: Core device. """ kernel_invariants = {"channel", "core", "_out_scale", "_duc_scale", "_rtio_interval"} def __init__(self, channel, core, cordic_gain=1.): = channel self.core = core # normalized DAC output self._out_scale = (1 << 15) - 1. # normalized DAC output including DUC cordic gain self._duc_scale = self._out_scale/cordic_gain # configuration channel access interval self._rtio_interval = int64(3*self.core.ref_multiplier) @kernel
[docs] 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 DDS/DUC phase accumulators always evolves at full speed. .. note:: The spline evolution divider has not been tested extensively and is currently considered a technological preview only. :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(),, _SAWG_DIV, div | (n << 16)) delay_mu(self._rtio_interval)
[docs] def set_clr(self, clr0: TInt32, clr1: TInt32, clr2: TInt32): """Set the accumulator clear mode for the three phase accumulators. When the ``clr`` bit for a given DDS/DUC phase accumulator is set, that phase accumulator will be cleared with every phase offset RTIO command and the output phase of the DDS/DUC will be exactly the phase RTIO value ("absolute phase update mode"). .. math:: q^\prime(t) = p^\prime + (t - t^\prime) f^\prime In turn, when the bit is cleared, the phase RTIO channels determine a phase offset to the current (carrier-) value of the DDS/DUC phase accumulator. This "relative phase update mode" is sometimes also called “continuous phase mode”. .. math:: q^\prime(t) = q(t^\prime) + (p^\prime - p) + (t - t^\prime) f^\prime Where: * :math:`q`, :math:`q^\prime`: old/new phase accumulator * :math:`p`, :math:`p^\prime`: old/new phase offset * :math:`f^\prime`: new frequency * :math:`t^\prime`: timestamp of setting new :math:`p`, :math:`f` * :math:`t`: running time :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(),, _SAWG_CLR, clr0 | (clr1 << 1) | (clr2 << 2)) delay_mu(self._rtio_interval)
[docs] 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 a technological preview only. The data is 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(),, _SAWG_IQ_EN, i_enable | (q_enable << 1)) delay_mu(self._rtio_interval)
[docs] def set_duc_max_mu(self, limit: TInt32): """Set the digital up-converter (DUC) I and Q data summing junctions upper limit. In machine units. The default limits are chosen to reach maximum and minimum DAC output amplitude. For a description of the limiter functions in normalized units see: .. seealso:: :meth:`set_duc_max` """ rtio_output(now_mu(),, _SAWG_DUC_MAX, limit) delay_mu(self._rtio_interval)
[docs] def set_duc_min_mu(self, limit: TInt32): """.. seealso:: :meth:`set_duc_max_mu`""" rtio_output(now_mu(),, _SAWG_DUC_MIN, limit) delay_mu(self._rtio_interval)
[docs] def set_out_max_mu(self, limit: TInt32): """.. seealso:: :meth:`set_duc_max_mu`""" rtio_output(now_mu(),, _SAWG_OUT_MAX, limit) delay_mu(self._rtio_interval)
[docs] def set_out_min_mu(self, limit: TInt32): """.. seealso:: :meth:`set_duc_max_mu`""" rtio_output(now_mu(),, _SAWG_OUT_MIN, limit) delay_mu(self._rtio_interval)
[docs] def set_duc_max(self, limit: TFloat): """Set the digital up-converter (DUC) I and Q data summing junctions 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, after the anti-aliasing FIR filter. * At the quadrature input to the ``phase0``/``frequency0`` fast DUC, after the anti-aliasing FIR filter. The in-phase and quadrature data paths both use the same limits. * 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_max`: Upper limit of the in-phase and quadrature inputs to the DUC. * :meth:`set_duc_min`: Lower limit of the in-phase and quadrature inputs 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_max_mu(int32(round(limit*self._duc_scale)))
[docs] def set_duc_min(self, limit: TFloat): """.. seealso:: :meth:`set_duc_max`""" self.set_duc_min_mu(int32(round(limit*self._duc_scale)))
[docs] def set_out_max(self, limit: TFloat): """.. seealso:: :meth:`set_duc_max`""" self.set_out_max_mu(int32(round(limit*self._out_scale)))
[docs] def set_out_min(self, limit: TFloat): """.. seealso:: :meth:`set_duc_max`""" self.set_out_min_mu(int32(round(limit*self._out_scale)))
[docs]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``) that are executing and sampling at the coarse RTIO frequency. They can represent frequencies within the first Nyquist zone from ``-f_rtio_coarse/2`` to ``f_rtio_coarse/2``. .. note:: The coarse RTIO frequency ``f_rtio_coarse`` is the inverse of ``ref_period*multiplier``. Both are arguments of the ``Core`` device, specified in the device database ````. The sum of their outputs is then interpolated by a factor of :attr:`parallelism` (2, 4, 8 depending on the bitstream) using a finite-impulse-response (FIR) anti-aliasing filter (more accurately a half-band filter). The filter is followed by a configurable saturating limiter. After the limiter, the data is shifted in frequency using a complex digital up-converter (DUC, ``frequency0``/``phase0``) running at :attr:`parallelism` times the coarse RTIO frequency. The first Nyquist zone of the DUC extends from ``-f_rtio_coarse*parallelism/2`` to ``f_rtio_coarse*parallelism/2``. Other Nyquist zones are usable depending on the interpolation/modulation options configured in the DAC. The real/in-phase data after digital up-conversion can be offset using another spline interpolator ``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 .. note:: The latencies (pipeline depths) of the nine data channels (i.e. all except :attr:`config`) are matched. Equivalent channels (e.g. :attr:`phase1` and :attr:`phase2`) are exactly matched. Channels of different type or functionality (e.g. :attr:`offset` vs :attr:`amplitude1`, DDS vs DUC, :attr:`phase0` vs :attr:`phase1`) are only matched to within one coarse RTIO cycle. :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", "parallelism", "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 self.parallelism = parallelism width = 16 time_width = 16 cordic_gain = 1.646760258057163 # Cordic(width=16, guard=None).gain head_room = 1.001 self.config = Config(channel_base, self.core, cordic_gain) self.offset = Spline(width, time_width, channel_base + 1, self.core, 2.*head_room) self.amplitude1 = Spline(width, time_width, channel_base + 2, self.core, 2*head_room*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*head_room*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.) @kernel
[docs] def reset(self): """Re-establish initial conditions. This clears all spline interpolators, accumulators and configuration settings. This method advances the timeline by the time required to perform all 7 writes to the configuration channel, plus 9 coarse RTIO cycles. """ self.config.set_div(0, 0) self.config.set_clr(1, 1, 1) self.config.set_iq_en(1, 0) self.config.set_duc_min(-1.) self.config.set_duc_max(1.) self.config.set_out_min(-1.) self.config.set_out_max(1.) self.frequency0.set_mu(0) coarse_cycle = int64(self.core.ref_multiplier) delay_mu(coarse_cycle) self.frequency1.set_mu(0) delay_mu(coarse_cycle) self.frequency2.set_mu(0) delay_mu(coarse_cycle) self.phase0.set_mu(0) delay_mu(coarse_cycle) self.phase1.set_mu(0) delay_mu(coarse_cycle) self.phase2.set_mu(0) delay_mu(coarse_cycle) self.amplitude1.set_mu(0) delay_mu(coarse_cycle) self.amplitude2.set_mu(0) delay_mu(coarse_cycle) self.offset.set_mu(0) delay_mu(coarse_cycle)