Download

Download this file as Jupyter notebook: waveforms.ipynb.

Introduction to RF waveforms

This guide demonstrates how to generate RFWaveforms using pulse Envelopes as introduced in Introduction to pulse envelopes.

An RFWaveform specifies a duration, an amplitude, a target frequency, an instantaneous phase, and a post-increment phase, which are used to modulate (or upconvert) a base Envelope to address a system at a specific frequency. The post-increment phase is frequently used to implement a virtual Z gate.

[2]:
# initialize a Gaussian envelope
import keysight.qcs as qcs
import numpy as np

gauss_envelope = qcs.GaussianEnvelope(num_sigma=3)

# initialize an RF waveform with that envelope
rf_waveform = qcs.RFWaveform(
    duration=1,
    envelope=gauss_envelope,
    amplitude=1,
    rf_frequency=10,
    instantaneous_phase=np.pi / 3,
    post_phase=np.pi,
)

# instantiate a channel to play an AWG on
awg = qcs.Channels(range(1), "awg")

# initialize the program and add the waveform
program = qcs.Program()
program.add_waveform(rf_waveform, awg)

# render
program.render(sample_rate=1e3)

The arguments rf_frequency, instantaneous_phase, and post_phase can also be Scalars, which allows their values to be modified later on.

[3]:
# initialize variables for the frequency and phases
rf_frequency = qcs.Scalar("frequency", value=10, dtype=float)
instantaneous_phase = qcs.Scalar("instantaneous_phase", value=np.pi, dtype=float)
post_phase = qcs.Scalar("post_phase", value=np.pi, dtype=float)

# initialize RF waveforms
rf_waveform = qcs.RFWaveform(
    1,
    gauss_envelope,
    1,
    rf_frequency=rf_frequency,
    instantaneous_phase=instantaneous_phase,
    post_phase=post_phase,
)

# to illustrate how phases accumulate, we add a constant RF waveform
constant_rf = qcs.RFWaveform(1, qcs.ConstantEnvelope(), 1, rf_frequency)

# instantiate another channel to play an AWG on
awg2 = qcs.Channels([2], "awg")

# initialize the program and add the waveforms
program = qcs.Program()
program.add_waveform([rf_waveform, constant_rf], awg)
# add only the constant waveform to awg2, and delay it by the duration of the RF
program.add_waveform(constant_rf, awg2, pre_delay=rf_waveform.duration)

# render
program.render(sample_rate=1e3, channel_subplots=False)

Note that the constant waveform after the RF waveform has a different starting phase in the above two plots. This is because the starting phase of each waveform in a waveform sequence is determined by the previous waveforms. Specifically, each waveform is multiplied by the product of the outputs of the phase_update() for all preceding RF waveforms.

We now change the value of instantaneous_phase and re-render the program. Note that changing instantaneous_phase only changes the phase of the Gaussian waveform.

[4]:
instantaneous_phase.value = 0.0
program.render(sample_rate=1e3, channel_subplots=False)

We now change the value of post_phase and re-render the program. Note that changing post_phase only changes the phase of the constant waveform after the Gaussian waveform.

[5]:
post_phase.value = np.pi / 2
program.render(sample_rate=1e3, channel_subplots=False)

Signal generation background

We now describe how RF signals are generated.

The output \(V(t)\) of the AWG at time \(t\) for a single waveform is

\[V(t) = E(t)\cos(\omega t + \phi),\]

where \(E(t)\) is the envelope, \(\omega\) is the angular frequency, and \(\phi\) is a phase.

A waveform specifies the signal in a time interval \([0, T]\) where \(T\) is the duration of the waveform. When it is in a program, the time can be translated in one of two ways, which is handled automatically based on the keysight.qcs.channels.Channels.absolute_phase property:

  1. The time at the start of the waveform can be treated as \(t = 0\) (absolute_phase == True).

  2. The time at the start of the waveform can be set to the end of the time of the previous operation (absolute_phase == False).

In order to explain how these translations are handled, we now explain the two-stage process for producing high frequency signals:

  1. We modulate by an intermediate frequency \(\omega_{IF}\) on the FPGA, taking a complex signal \(E_0(t)\) to the complex signal

    \[E_\alpha(t) = E_0(t) \exp[i (\omega_{IF} t + \phi_{\rm DUC})]\]

    where \(\phi_{\rm DUC}\) is the phase of the FPGA upconversion.

  2. The signal at the intermediate frequency is converted up to the target frequency on an ASIC, giving the output signal

    \[V(t) = \mathrm{Re}\left( E_0(t) \exp[i (\omega t + \phi_{\rm DUC} + \phi_{\rm LO})]\right)\]

    where \(\phi_{\rm LO}\) is the phase of the local oscillator on the ASIC.

When an RF waveform is used to stimulate a system and the response is downconverted by a downconverter with the same local oscillator frequency and phase, the value of the local oscillator phase will not affect the output signal. Thus, we only need to account for \(\phi_{\rm DUC}\). This phase is determined by the start time.


Download

Download this file as Jupyter notebook: waveforms.ipynb.

On this page