Skip to content

quartiq/miqro-sim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MIQRO simulator

This is a small simulator for the MIQRO Phaser gateware and its ARTIQ interface. It's intended for experimentation and quick evaluation of the capabilities and the API. The output from the simulation can directly be used as input when integrating the differential equation of one or multiple qubits.

This work is sponsored in part by the Federal Ministry of Education and Research (BMBF) under contract 13N15524.

Resources

The ARTIQ MIQRO coredevice driver documentation in the ARTIQ Manual and here miqro.py describes the functionality and the signal flow.

Architecture

MIQRO Architecture diagram

Data flow description

A Miqro instance represents one RF output. The DSP components are fully contained in the Phaser gateware. The output is generated by with the following data flow:

Oscillators

  • There are n_osc = 16 oscillators with oscillator indices 0..n_osc-1.
  • Each oscillator outputs one tone at any given time
    • I/Q (quadrature, a.k.a. complex) 2x16 bit signed data at tau = 4 ns sample intervals, 250 MS/s, Nyquist 125 MHz, bandwidth 200 MHz (from f = -100..+100 MHz, taking into account the interpolation anti-aliasing filters in subsequent interpolators),
    • 32 bit frequency (f) resolution (~ 1/16 Hz),
    • 16 bit unsigned amplitude (a) resolution
    • 16 bit phase offset (p) resolution
  • The output phase p' of each oscillator at time t (boot/reset/initialization of the device at t=0) is then p' = f*t + p (mod 1 turn) where f and p are the (currently active) profile frequency and phase offset.
  • Note: The terms "phase coherent" and "phase tracking" are defined to refer to this choice of oscillator output phase p'. Note that the phase offset p is not relative to (on top of) previous phase/profiles/oscillator history. It is "absolute" in the sense that frequency f and phase offset p fully determine oscillator output phase p' at time t. This is unlike typical DDS behavior.
  • Frequency, phase, and amplitude of each oscillator are configurable by selecting one of n_profile = 32 profile with indices 0..n_profile-1. This selection is fast and can be done for each pulse. The phase coherence defined above is guaranteed for each profile individually.
  • Note: one profile per oscillator (usually profile index 0) should be reserved for the NOP (no operation, identity) profile, usually with zero amplitude.
  • Data for each profile for each oscillator can be configured individually. Storing profile data should be considered "expensive". "expensive" does not mean it is impossible, just that it may take a significant amount of time and resources to execute such that it may be impractical when used often or during fast pulse sequences. They are intended for use in calibration and initialization.

Summation

  • The oscillator outputs are added together (wrapping addition).
  • The user must ensure that the sum of oscillators outputs does not exceed the data range. In general that means that the sum of the amplitudes must not exceed one.

Shaper

  • The summed complex output stream is then multiplied with a the complex-valued output of a triggerable shaper.
  • Triggering the shaper corresponds to passing a pulse from all oscillators to the RF output.
  • Selected profiles become active simultaneously (on the same output sample) when triggering the shaper with the first shaper output sample.
  • The shaper reads (replays) window samples from a memory of size n_window = 1 << 10.
  • The window memory can be segmented by choosing different start indices to support different windows.
  • Each window memory segment starts with a header determining segment length and interpolation parameters.
  • The window samples are interpolated by a factor (rate change) between 1 and r = 1 << 12.
  • The interpolation order is constant, linear, quadratic, or cubic. This corresponds to interpolation modes from rectangular window (1st order CIC) or zero order hold) to Parzen window (4th order CIC or cubic spline).
  • This results in support for single shot pulse lengths (envelope support) between tau and a bit more than r * n_window * tau = (1 << 12 + 10) tau ~ 17 ms.
  • Windows can be configured to be head-less and/or tail-less, meaning, they do not feed zero-amplitude samples into the shaper before and after each window respectively. This is used to implement pulses with arbitrary length or CW output.

Overall properties

  • The DAC may upconvert the signal by applying a frequency offset f1 with phase p1.
  • In the Upconverter Phaser variant, the analog quadrature upconverter applies another frequency of f2 and phase p2.
  • The resulting phase of the signal from one oscillator at the SMA output is (f + f1 + f2)*t + p + s(t - t0) + p1 + p2 (mod 1 turn) where s(t - t0) is the phase of the interpolated shaper output, and t0 is the trigger time (fiducial of the shaper). Unsurprisingly the frequency is the derivative of the phase.
  • Group delays between pulse parameter updates are matched across oscillators, shapers, and channels.
  • The minimum time to change profiles and phase offsets is ~128 ns (estimate, TBC). This is the minimum pulse interval. The sustained pulse rate of the RTIO PHY/Fastlink is one pulse per Fastlink frame (may be increased, TBC).

Simulator output

Given the example experiment:

class Example(EnvExperiment):
    def build(self):
        self.phaser0 = miqro.Phaser()
        self.miqro0 = self.phaser0.channel0.miqro

    @kernel
    def setup(self):
        # Configure example data for some profiles on some oscillators
        # e.g.: profile 3 on oscillator 11 will be 3 MHz, 0.3 amplitude full scale,
        # -0.3 turn (coherent) phase
        for osc in [0, 4, 11]:
            for profile in [1, 2, 3]:
                self.miqro0.set_profile(
                    osc,
                    profile,
                    frequency=1 * MHz * (osc - 8),
                    amplitude=0.1 * profile,
                    phase=-0.1 * profile,
                )
        # Configure some window data and interpolation parameters
        iq = [(1, 0), (1, 0), (0, 1), (0, 1)]
        # Pulse shape will be:
        # * n = len(iq) = 4 samples full scale
        # * Note the window has a pi/2 phase shift for the second half.
        # * r = 128 cubic (Parzen window) interpolation:
        #   Each window memory sample will last r tau = 512 ns and those samples
        #   will see cubic interpolation like this:
        #   Repeat each input sample r = 128 times, convolve the sequence of
        #   n * r samples with a rectangular window of length r = 128.
        #   Do that order = 3 times.
        # * The output of the shaper is thus a rise to full scale,
        #   and then a pi/2 phase shift, then a tail to zero again, all "smooth"
        #   in the sense of cubic interpolation: a continuous second derivative
        #   of I and Q (piecewise constant third derivative).
        # * Total pulse duration (shape support) is ((n + order) * r - order) * tau = 3.572 µs.
        self.miqro0.set_window(start=0, iq=iq, period=128 * 4 * ns, order=3)

    @kernel
    def pulse(self):
        # Choose example profiles and phase offsets
        # profiles[oscillator index] = profile index
        profiles = [0] * 16
        profiles[0] = 1
        profiles[4] = 2
        profiles[11] = 3
        # Choose window start address
        window = 0x000
        # Trigger the pulse
        # This will load frequencies and amplitudes for the oscillators,
        # compute the initial oscillator phases, add the offsets,
        # load the window samples, interpolate them and multiply the window with
        # the sum of the oscillator outputs, all in gateware with matched latency accross
        # all data paths. The DAC will interpolate further (by 4),
        # shift everything by frequency f1 and then the IQ mixer will shift
        # further by f2.
        # The encoded pulse words can just as well be computed offline with `encode()` and then emitted with `pule_mu()` for even higher rates.
        self.miqro0.pulse(window, profiles)

    @kernel
    def run(self):
        # self.phaser0.init()
        # self.miqro0.reset()
        self.setup()
        self.pulse()

MIQRO simulator output

Actual gateware output

For a 16-tone pulse train (limited in phase noise, noise floor, dynamic range, and distortion by measurement equipment):

MIQRO scope analysis

MIQRO spectrum

Simulator accuracy/features

The API and features described and implemented here may be slightly different from the actual gateware/ARTIQ implementation. Check back before relying on it.

Differences are:

  • Integer quantization of samples not implemented
  • NCO/DDS spurs, phase truncation not modelled
  • IQ data overflow not implemented
  • Overall latency not implemented
  • Rounding not implemented
  • Window head/tail features not implemented
  • Lots of ARTIQ features (DMA, other devices, device db, datasets, ...)

About

MIQRO pulse generator simulator

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages