artiq/artiq/coredevice/sawg.py

339 lines
14 KiB
Python
Raw Normal View History

2017-06-07 15:11:16 +08:00
from artiq.language.types import TInt32, TFloat
2017-06-17 01:31:57 +08:00
from numpy import int32, int64
2017-05-23 00:27:32 +08:00
from artiq.language.core import kernel, now_mu
2016-12-04 23:52:08 +08:00
from artiq.coredevice.spline import Spline
2017-05-23 00:27:32 +08:00
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_OUT_MIN = 4
_SAWG_OUT_MAX = 5
_SAWG_DUC_MIN = 6
_SAWG_DUC_MAX = 7
2017-05-23 00:27:32 +08:00
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.
"""
2017-06-17 01:31:57 +08:00
kernel_invariants = {"channel", "core", "_out_scale", "_duc_scale",
"_rtio_interval"}
2017-05-23 00:27:32 +08:00
2017-06-07 15:11:16 +08:00
def __init__(self, channel, core, cordic_gain=1.):
2017-05-23 00:27:32 +08:00
self.channel = channel
self.core = core
2017-06-17 01:31:57 +08:00
# normalized DAC output
2017-06-07 15:11:16 +08:00
self._out_scale = (1 << 15) - 1.
2017-06-17 01:31:57 +08:00
# normalized DAC output including DUC cordic gain
2017-06-07 15:11:16 +08:00
self._duc_scale = self._out_scale/cordic_gain
2017-06-17 01:31:57 +08:00
# configuration channel access interval
self._rtio_interval = int64(3*self.core.ref_multiplier)
2017-05-23 00:27:32 +08:00
@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
2017-06-09 18:26:24 +08:00
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.
2017-05-23 00:27:32 +08:00
: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
2017-05-23 00:50:58 +08:00
def set_clr(self, clr0: TInt32, clr1: TInt32, clr2: TInt32):
2017-06-09 18:26:24 +08:00
"""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
2017-05-23 00:27:32 +08:00
2017-06-09 18:26:24 +08:00
Where:
2017-05-23 00:27:32 +08:00
2017-06-09 18:26:24 +08:00
* :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
2017-05-23 00:27:32 +08:00
: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, clr0 |
(clr1 << 1) | (clr2 << 2))
2017-05-23 00:27:32 +08:00
@kernel
2017-05-23 00:50:58 +08:00
def set_iq_en(self, i_enable: TInt32, q_enable: TInt32):
2017-05-23 00:27:32 +08:00
"""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
2017-06-09 18:26:24 +08:00
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``.
2017-05-23 00:27:32 +08:00
: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``.
"""
2017-05-23 00:50:58 +08:00
rtio_output(now_mu(), self.channel, _SAWG_IQ_EN, i_enable |
(q_enable << 1))
2017-05-23 00:27:32 +08:00
@kernel
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.
2017-06-07 15:11:16 +08:00
2017-06-14 00:51:48 +08:00
The default limits are chosen to reach maximum and minimum DAC output
amplitude.
2017-06-07 15:11:16 +08:00
For a description of the limiter functions in normalized units see:
.. seealso:: :meth:`set_duc_max`
2017-06-07 15:11:16 +08:00
"""
rtio_output(now_mu(), self.channel, _SAWG_DUC_MAX, limit)
2017-06-07 15:11:16 +08:00
@kernel
def set_duc_min_mu(self, limit: TInt32):
""".. seealso:: :meth:`set_duc_max_mu`"""
rtio_output(now_mu(), self.channel, _SAWG_DUC_MIN, limit)
2017-06-07 15:11:16 +08:00
@kernel
def set_out_max_mu(self, limit: TInt32):
""".. seealso:: :meth:`set_duc_max_mu`"""
2017-06-07 15:11:16 +08:00
rtio_output(now_mu(), self.channel, _SAWG_OUT_MAX, limit)
@kernel
def set_out_min_mu(self, limit: TInt32):
""".. seealso:: :meth:`set_duc_max_mu`"""
2017-06-07 15:11:16 +08:00
rtio_output(now_mu(), self.channel, _SAWG_OUT_MIN, limit)
@kernel
def set_duc_max(self, limit: TFloat):
"""Set the digital up-converter (DUC) I and Q data summing junctions
upper limit.
2017-05-23 00:27:32 +08:00
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,
2017-06-14 00:51:48 +08:00
after the anti-aliasing FIR filter.
2017-05-23 00:27:32 +08:00
* 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.
2017-05-23 00:27:32 +08:00
* 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.
2017-06-07 15:11:16 +08:00
: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]``.
2017-05-23 00:27:32 +08:00
.. 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.
2017-05-23 00:27:32 +08:00
* :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)))
2017-05-23 00:27:32 +08:00
@kernel
def set_duc_min(self, limit: TFloat):
""".. seealso:: :meth:`set_duc_max`"""
self.set_duc_min_mu(int32(round(limit*self._duc_scale)))
2017-05-23 00:27:32 +08:00
@kernel
2017-06-07 15:11:16 +08:00
def set_out_max(self, limit: TFloat):
""".. seealso:: :meth:`set_duc_max`"""
2017-06-07 15:11:16 +08:00
self.set_out_max_mu(int32(round(limit*self._out_scale)))
2017-05-23 00:27:32 +08:00
@kernel
2017-06-07 15:11:16 +08:00
def set_out_min(self, limit: TFloat):
""".. seealso:: :meth:`set_duc_max`"""
2017-06-07 15:11:16 +08:00
self.set_out_min_mu(int32(round(limit*self._out_scale)))
phaser: add jesd204b rtio dds gateware: add jesd204b awg gateware: copy phaser (df3825a) dsp/tools: update satadd mixin phaser: no DDS stubs dsp: accu fix phaser: cleanup/reduce sawg: kernel support and docs sawg: coredevice api fixes sawg: example ddb/experiment phaser: add conda package examples/phaser: typo sawg: adapt tests, fix accu stb sawg: tweak dds parameters sawg: move/adapt/extend tests sawg: test phy, refactor phaser: non-rtio spi phaser: target cli update phaser: ad9154-fmc-ebz pins phaser: reorganize fmc signal naming phaser: add test mode stubs phaser: txen is LVTTL phaser: clk spi xfer test phaser: spi for ad9154 and ad9516 phaser: spi tweaks ad9154: add register map from ad9144.xml ad9516: add register map from ad9517.xml and manual adaptation ad9154_reg: just generate getter/setter macros as well ad9154: reg WIP ad9154: check and fix registers kc705: single ended rtio_external_clk use single ended user_sma_clk_n instead of p/n to free up one clock sma kc705: mirror clk200 at user_sma_clock_p ad9516_regs.h: fix B_COUNTER_MSB phase: wire up clocking differently needs patched misoc kc705: feed rtio_external_clock directly kc705: remove rtio_external_clk for phaser phaser: spi tweaks ad9516: some startup ad9516_reg fixes phaser: setup ad9516 for supposed 500 MHz operation ad9516: use full duplex spi ad9154_reg: add CONFIG_REG_2 ad9154_reg: fixes phaser: write some ad9154 config ad9154_reg: fixes ad9154: more init, and human readable setup ad9154/ad9516: merge spi support ad9154: status readout Revert "kc705: remove rtio_external_clk for phaser" This reverts commit d500288bb44f2bf2eeb0c2f237aa207b0a8b1366. Revert "kc705: feed rtio_external_clock directly" This reverts commit 8dc7825519e3e75b7d3d29c9abf10fc6e3a8b4c5. Revert "phase: wire up clocking differently" This reverts commit ad9cc450ffa35abb54b0842d56f6cf6c53c6fbcc. Revert "kc705: mirror clk200 at user_sma_clock_p" This reverts commit 7f0dffdcdd28e648af84725682f82ec6e5642eba. Revert "kc705: single ended rtio_external_clk" This reverts commit a9426d983fbf5c1cb768da8f1da26d9b7335e9cf. ad9516: 2000 MHz clock phaser: test clock dist phaser: test freqs ad9154: iostandards phaser: drop clock monitor phaser: no separate i2c phaser: drive rtio from refclk, wire up sysref phaser: ttl channel for sync ad9154: 4x interp, status, tweaks phaser: sync/sysref 33V banks phaser: sync/sysref LVDS_25 inputs are VCCO tolerant phaser: user input-only ttls phaser: rtio fully from refclk ad9154: reg name usage fix ad9154: check register modifications Revert "ad9154: check register modifications" This reverts commit 45121d90edf89f7bd8703503f9f317ad050f9564. ad9154: fix status code ad9154: addrinc, recal serdes pll phaser: coredevice, example tweaks sawg: missing import sawg: type fixes ad9514: move setup functions ad9154: msb first also decreasing addr phaser: use sys4x for rtio internal ref phaser: move init code to main phaser: naming cleanup phaser: cleanup pins phaser: move spi to kernel cpu phaser: kernel support for ad9154 spi ad9154: add r/w methods ad9154: need return annotations ad9154: r/w methods are kernels ad9154_reg: portable helpers phaser: cleanup startup kernel ad9154: status test ad9154: prbs test ad9154: move setup, document phaser: more documentation
2016-07-22 21:56:09 +08:00
2016-11-20 23:39:53 +08:00
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)) +
2016-11-30 06:16:32 +08:00
amplitude2*exp(2j*pi*(frequency2*t + phase2)))
2016-11-20 23:39:53 +08:00
output = (offset +
i_enable*Re(oscillators) +
q_enable*Im(buddy_oscillators))
2017-05-23 16:28:23 +08:00
This parametrization can be viewed as two complex (quadrature) oscillators
2017-06-14 00:51:48 +08:00
(``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 ``device_db.py``.
2017-06-14 00:51:48 +08:00
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.
2017-06-14 00:51:48 +08:00
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.
2017-05-23 16:28:23 +08:00
.. 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``.
2017-05-23 16:33:04 +08:00
The configuration channel and the nine
:class:`artiq.coredevice.spline.Spline` interpolators are accessible as
attributes:
2016-12-07 02:25:40 +08:00
2017-05-23 00:27:32 +08:00
* :attr:`config`: :class:`Config`
2016-12-07 02:25:40 +08:00
* :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
2016-11-20 23:39:53 +08:00
:param channel_base: RTIO channel number of the first channel (amplitude).
2017-05-23 00:27:32 +08:00
The configuration channel and frequency/phase/amplitude channels are
then assumed to be successive channels.
2016-12-07 02:25:40 +08:00
:param parallelism: Number of output samples per coarse RTIO clock cycle.
:param core_device: Name of the core device that this SAWG is on.
2016-11-20 23:39:53 +08:00
"""
2017-06-14 00:51:48 +08:00
kernel_invariants = {"channel_base", "core", "parallelism",
2016-11-20 23:39:53 +08:00
"amplitude1", "frequency1", "phase1",
2016-11-21 20:16:44 +08:00
"amplitude2", "frequency2", "phase2",
2016-11-20 23:39:53 +08:00
"frequency0", "phase0", "offset"}
def __init__(self, dmgr, channel_base, parallelism, core_device="core"):
self.core = dmgr.get(core_device)
self.channel_base = channel_base
2017-06-14 00:51:48 +08:00
self.parallelism = parallelism
2016-11-20 23:39:53 +08:00
width = 16
time_width = 16
cordic_gain = 1.646760258057163 # Cordic(width=16, guard=None).gain
head_room = 1.001
2017-06-07 15:11:16 +08:00
self.config = Config(channel_base, self.core, cordic_gain)
2016-11-20 23:39:53 +08:00
self.offset = Spline(width, time_width, channel_base + 1,
self.core, 2.*head_room)
2016-11-20 23:39:53 +08:00
self.amplitude1 = Spline(width, time_width, channel_base + 2,
self.core, 2*head_room*cordic_gain**2)
2016-11-20 23:39:53 +08:00
self.frequency1 = Spline(3*width, time_width, channel_base + 3,
2016-12-07 02:25:40 +08:00
self.core, 1/self.core.coarse_ref_period)
2016-11-20 23:39:53 +08:00
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)
2016-11-20 23:39:53 +08:00
self.frequency2 = Spline(3*width, time_width, channel_base + 6,
2016-12-07 02:25:40 +08:00
self.core, 1/self.core.coarse_ref_period)
2016-11-20 23:39:53 +08:00
self.phase2 = Spline(width, time_width, channel_base + 7,
self.core, 1.)
self.frequency0 = Spline(2*width, time_width, channel_base + 8,
self.core,
2016-12-07 02:25:40 +08:00
parallelism/self.core.coarse_ref_period)
2016-11-20 23:39:53 +08:00
self.phase0 = Spline(width, time_width, channel_base + 9,
self.core, 1.)
2017-06-17 01:31:57 +08:00
@kernel
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
2017-06-22 19:27:49 +08:00
seven writes to the configuration channel.
2017-06-17 01:31:57 +08:00
"""
2017-06-22 19:27:49 +08:00
self.config.set_div(0, 0)
delay_mu(self.config._rtio_interval)
2017-06-17 01:31:57 +08:00
self.config.set_clr(1, 1, 1)
delay_mu(self.config._rtio_interval)
self.config.set_iq_en(1, 0)
delay_mu(self.config._rtio_interval)
self.config.set_duc_min(-1.)
2017-06-17 01:31:57 +08:00
delay_mu(self.config._rtio_interval)
self.config.set_duc_max(1.)
2017-06-17 01:31:57 +08:00
delay_mu(self.config._rtio_interval)
self.config.set_out_min(-1.)
delay_mu(self.config._rtio_interval)
self.config.set_out_max(1.)
delay_mu(self.config._rtio_interval)
2017-06-22 19:27:49 +08:00
self.frequency0.set_mu(0)
self.frequency1.set_mu(0)
self.frequency2.set_mu(0)
self.phase0.set_mu(0)
self.phase1.set_mu(0)
self.phase2.set_mu(0)
self.amplitude1.set_mu(0)
self.amplitude2.set_mu(0)
self.offset.set_mu(0)