Skip to content

Pulse-level sequencing with the Command Table

Goals and Requirements

The goal of this tutorial is to demonstrate pulse-level sequencing using the command table feature of the HDAWG.

Preparation

Connect the cables as illustrated below. Make sure that the instrument is powered on and connected by Ethernet to your local area network (LAN) in which the control computer resides. After starting LabOne, the default web browser opens with the LabOne graphical user interface. The tutorial can be started with the default instrument configuration (e.g. after a power cycle) and the default user interface settings (e.g. as is after pressing F5 in the browser). Additionally, this tutorial requires the use of one of our APIs, in order to be able to define and upload the command table itself. The examples shown here use the Python API, similar functionality is available also for C++, Matlab and .Net.

Figure 1: Connections for the arbitrary waveform generator command table tutorial

Introduction to the Command table

The command table allows the sequencer to group waveform playback instructions with other timing-critical phase and amplitude setting commands in a single instruction within one clock cycle of 3.33 ns. The command table is a unit separate from the sequencer and waveform memory. Both the phase and the amplitude can be set in absolute and in incremental mode. Even when not using digital modulation or amplitude settings, working with the command table has the advantage of being more efficient in sequencer instruction memory compared to standard sequencing. Starting a waveform playback with the command table always requires just a single clock cycle, as opposed to 2 or 3 when using a playWave instruction.

When using the command table, three components play together during runtime to generate the waveform output and apply the phase and amplitude setting instructions: - Sequencer: the unit executing the runtime instructions, namely in this context the executeTableEntry instruction. This instruction executes one entry of the command table, and its input argument is a command table index. In its compiled form which can be seen in the AWG Sequencer Advanced sub-tab, the sequence program can contain up to 16384 instructions. - Wave table: a list of up to 16000 indexed waveforms. This list is defined by the sequence program using the index assignment instruction assignWaveIndex combined with a waveform or waveform placeholder. The wave table index referring to a waveform can be used in two ways: it is referred to from the command table, and it is used to directly write waveform data to the instrument memory using the node /<dev>/AWGS/<n>/WAVEFORM/WAVES/<index> - Command table: a list of up to 1024 indexed entries (command table index), each containing the index of a waveform to be played (wave table index), a sine generator phase setting instruction, and an AWG amplitude setting instruction. The command table is specified by a JSON formatted string written to the node /<dev>/AWGS/<n>/COMMANDTABLE/DATA

Basic command table use

We start in Python by defining a sequencer program that uses the command table.

seqc_program = """\
  // Define two waveforms
  wave w_a = gauss(2048, 1, 1024, 256);
  wave w_b = gauss(2048, 1, 1024, 192);

  // Assign a dual channel waveform to wave table entry 0
  assignWaveIndex(w_a, w_b, 0);

  // execute the first command table entry
  executeTableEntry(0);
  // execute the second command table entry
  executeTableEntry(1);
"""

It defines two Gaussian waveforms of equal amplitude and total length, but different width. These waveforms are then assigned as to two channels of the wave table entry with index 0, and the final lines executes the two first command table entries. This program cannot be run yet, as the command table is not yet defined, this is done in the next step. The command table must be uploaded after the sequence and never before.

Note

If a sequence program contains a reference to a command table entry that has not been defined, or if a command table entry refers to a waveform that has not been defined, the sequence program can’t be run.

In general the command table is defined as a JSON formatted string. Below we show an example of how to define a command table with two entries using Python. For ease of programming, here we define the command table as a CommandTable object, which is converted into a JSON string automatically at upload. Such object also validate the fields of the command table.

In this example, we generate a first command table entry with index "TABLE_INDEX", which plays the dual-channel waveform referenced in the wave table at index "WAVE_INDEX", with amplitude and phase settings specified for both channels individually. The second command table entry plays the same waveform again, but only sets the amplitude for the first output channel.

Here the Python code that uploads both the previously defined sequence and the command table.

## Imports
## Load the LabOne API and other necessary packages
from zhinst.toolkit import Session, CommandTable

device_id='dev8000'
server_host = 'localhost'
### connect to data server
session = Session(server_host)
### connect to device
device = session.connect_device(device_id)

AWG_INDEX = 0 # which AWG core to be used, here: first two channels
awg = device.awgs[AWG_INDEX]

## Load the sequence
awg.load_sequencer_program(seqc_program)

## Initialize command table
ct_schema = awg.commandtable.load_validation_schema()
ct = CommandTable(ct_schema)

## Index of wave table and command table entries
TABLE_INDEX = 0
WAVE_INDEX = 0

## Waveform with amplitude and phase settings
ct.table[TABLE_INDEX].waveform.index = WAVE_INDEX
ct.table[TABLE_INDEX].amplitude0.value = 1.0
ct.table[TABLE_INDEX].amplitude1.value = -0.5
ct.table[TABLE_INDEX].phase0.value = 0
ct.table[TABLE_INDEX].phase0.increment = False
ct.table[TABLE_INDEX].phase1.value = 90
ct.table[TABLE_INDEX].phase1.increment = False

ct.table[TABLE_INDEX+1].waveform.index = WAVE_INDEX
ct.table[TABLE_INDEX+1].amplitude0.value = 0.5

## Upload command table
awg.commandtable.upload_to_device(ct)

Note

During compilation of a sequencer program, any previously uploaded command table is reset, and will need to be uploaded again before it can be used.

Now the sequencer program can be run on the HDAWG. The expected output is shown in Figure 2. Note how the amplitude of the Gaussian waveform played on the second channel is negative and half the magnitude of the waveform on the first channel, even though they are both defined with the same amplitude in the sequencer program. This is due to the amplitude settings in the command table. Also note that these amplitude settings are persistent, as in the second command table entry, the amplitude of the first output channel is set to 0.5, while the second channel remains as in the previous playback.

Figure 2: Output of the first two HDAWG channels from the basic command table example

The phase settings we specified in the definition of the command table have no immediate impact on the output waveforms we show here, since we have not yet added digital modulation. Enabling digital modulation for the two output channels used here, with the same frequency and sine generator phase for both channels, the expected output is now shown in Figure 3. Note the phase offset between the two channels, due to the phase increment of 90 degrees for the second channel in the command table.

Figure 3: Output of the first two HDAWG channels from the basic command table example, when also enabling digital modulation of both channels at the same frequency and with the same initial sine generator phase.

Note

When a command table entry is called, the amplitude and phase are set persistently. Subsequent waveforms on the same channel will be played with the same amplitude and phase. To play a waveform with a different amplitude or phase, the new values must be set with another command table entry.

Phase entries in the command table supersede any settings in the GUI. In the presence of a command table, the phase of sine generators is reset to zero when enabling the sequencer. To apply a non-zero phase to the sine generators in the presence of a command table, the phase must either be set within the command table itself or with the setSinePhase sequencer command.

Efficient pulse incrementation

One illustrative use case of the command table feature is the efficient incrementation of amplitude or phase of waveforms.

We again start by writing a sequencer program that plays two entries of the command table.

seqc_program = """\
  // Define a single waveform
  wave w_a = ones(1024);

  // Assign a dual channel waveform to wave table entry
  assignWaveIndex(w_a, w_a, 0);

  // execute the first command table entry
  executeTableEntry(0);
  repeat(10) {
    executeTableEntry(1);
  }
"""

Here we have defined a single wave table entry, where both channels contain the same constant waveform.

In Python we then define and upload a command table with just two entries, in this case both referencing the same wave table entry.

## Load the sequence
awg.load_sequencer_program(seqc_program)

## Initialize command table
ct_schema = awg.commandtable.load_validation_schema()
ct = CommandTable(ct_schema)

## Waveform with amplitude and phase settings
ct.table[0].waveform.index = 0
ct.table[0].amplitude0.value = 1.0
ct.table[0].amplitude1.value = 0.0

ct.table[1].waveform.index = 0
ct.table[1].amplitude0.value = -0.1
ct.table[1].amplitude0.increment = True
ct.table[1].amplitude1.value = 0.1
ct.table[1].amplitude1.increment = True

## Upload command table
awg.commandtable.upload_to_device(ct)

Executing the sequencer program then produces the two-channel output shown in Figure 4. Here, the first call to the first command table entry plays the two-channel waveform with the amplitude of the first output channel set to one and for the second channel set to zero. The subsequent calls to the second command table entry increment these amplitudes every time by 0.1, with a negative increment on channel 1, and an positive increment on channel 2.

Figure 4: Incrementing waveform amplitudes using the command table increment functionality

Note

The amplitude of waveform playback can be influenced in multiple different ways, through the amplitude of the waveform itself, through the amplitude setting in the command table, and finally through the Range setting of the AWG output channel.

It is possible to perform multi-dimensional amplitude sweeps by making use of the amplitude registers of the command table. Each channel has four independent amplitude registers, with each register storing the amplitude last played on that register. By default, amplitude register zero is used. It is therefore possible to keep the amplitude of one register constant while sweeping the amplitude of another register. This can be useful for probing dynamics in a multi-level system, or for certain steps in the calibration of spin qubits.

As an example, we will use the following sequence:

seqc_program = """\
    assignWaveIndex(ones(128), 0);
    assignWaveIndex(rect(64,0.2), 1);

    var i = 10;
    executeTableEntry(0);
    do {
        executeTableEntry(2);
        executeTableEntry(1);
        i-=1;
    } while(i);
"""

## Upload the sequence
awg.load_sequencer_program(seqc_program)

The first executeTableEntry command initializes the amplitude that will be swept without playing a pulse. The second executeTableEntry plays a constant rectangular pulse (64 samples long, amplitude 0.2). The third executeTableEntry plays a rectangular pulse (128 samples long) whose amplitude will be swept. The loop will play 10 different amplitudes. We also need to define and upload a command table to go with the sequence:

## Initialize command table
ct_schema = awg.commandtable.load_validation_schema()
ct = CommandTable(ct_schema)
## Initialize amplitude register 1
ct.table[0].amplitude0.value = -0.8
ct.table[0].amplitude0.increment = False
ct.table[0].amplitude0.register = 1
## Swept rectangular pulse
ct.table[1].waveform.index = 0
ct.table[1].amplitude0.value = 0.15
ct.table[1].amplitude0.increment = True
ct.table[1].amplitude0.register = 1
## Constant rectangular pulse
ct.table[2].waveform.index = 1
ct.table[2].amplitude0.value = 1.0
ct.table[2].amplitude0.register = 0

## Upload command table
awg.commandtable.upload_to_device(ct)

The first command table entry (index 0) sets the initial amplitude (in this case, -0.8) of amplitude register 1. The second table entry (index 1) increments the amplitude of amplitude register 1 and plays the rectangular pulse with waveform index 0. The third table entry (index 2) plays the constant rectangular pulse (waveform index 1) using amplitude register 0.

We now run the sequence:

awg.enable(True)

We observe the signal shown in Figure 5, which shows a constant pulse of 100 mV interleaved with a pulse whose amplitude is swept in steps of 75 mV. In total, there are 10 different amplitudes of the swept pulse.

Figure 5: Using the amplitude registers to sweep one amplitude while keeping a second one constant.

Pulse-level sequencing with the command table

All previous examples have used the pulse library in the AWG sequencer to define waveforms. In more advanced scenarios, waveforms are uploaded through the API, as we will demonstrate next. We start with the following sequence program, where we assign wave table entries using the placeholder command with a waveform length as argument.

seqc_program = """\
  // Define two wave table entries through placeholders
  assignWaveIndex(placeholder(1024), placeholder(1024), 0);
  assignWaveIndex(placeholder(1024), placeholder(1024), 1);

  // execute command table
  executeTableEntry(0);
  executeTableEntry(1);
  executeTableEntry(2);
"""

## Upload the sequence
awg.load_sequencer_program(seqc_program)

In this form, the sequence program cannot be run, first because the command table is not yet uploaded, and second because the waveform memory in the wave table has not been defined. We can use the numpy package to define two-channel Gaussian waveforms directly in Python, and upload them to the instrument using the appropriate node.

from zhinst.toolkit import Waveforms
import numpy as np

## parameters for waveform generation
length = 1024
width = 1/4
x = np.linspace(-1, 1, length)

## define waveforms as list of real-values arrays - here: Gaussian functions
waves = [
    [np.exp(-x**2/width**2), np.zeros(length)],
    [np.zeros(length), np.exp(-x**2/width**2)]]

## Upload waveforms to instrument
waveforms = Waveforms()
for i, wave in enumerate(waves):
  waveforms[i] = (wave[0],wave[1])
awg.write_to_waveform_memory(waveforms)

Finally, we also generate and upload a command table to the instrument.

## Initialize command table
ct_schema = awg.commandtable.load_validation_schema()
ct = CommandTable(ct_schema)

## Waveform with amplitude and phase settings
ct.table[0].waveform.index = 0
ct.table[0].waveform.awgChannel0 = ["sigout0"]
ct.table[0].waveform.awgChannel1 = ["sigout1"]
ct.table[0].amplitude0.value = 1.0
ct.table[0].amplitude1.value = -1.0

ct.table[1].waveform.index = 1
ct.table[1].waveform.awgChannel0 = ["sigout1"]
ct.table[1].waveform.awgChannel1 = ["sigout0"]

ct.table[2].waveform.index = 1
ct.table[2].waveform.awgChannel0 = ["sigout0","sigout1"]
ct.table[2].waveform.awgChannel1 = ["sigout0","sigout1"]

## Upload command table
awg.commandtable.upload_to_device(ct)

The sequencer program can now be run, and will produce output as shown in Figure 6.

Figure 6: Advanced command table example output, including complex channel assignments

Here, the playback of the first command table entry plays the first waveform with the standard output channel assignment. It also sets the amplitude of the second output channel to -1.0, even though no signal is played on this channel. The second command table playback plays the second wave table entry, but with the output assignment switched, meaning channel 1 is played on output 2 and vice versa. Due to the amplitude settings in the first command table entry, the waveform defined for channel 2 is played with a negative amplitude. The final command table entry again plays the second entry of the wave table, now with both channels assigned to both outputs, and therefore results in the same waveform played on both outputs. This last setting would typically be used when using IQ upconversion to convert the HDAWG signal to RF frequencies, as demonstrated in the previous tutorial chapter.

Command table entries fields

The documentation of all possible parameters in the command table JSON file can be found by pulling the schema from the device itself using the node /<dev>/AWGS/<n>/COMMANDTABLE/SCHEMA. The Python CommandTable object automatically uses the schema from the device when initialized like this:

## Initialize command table
ct_schema = awg.commandtable.load_validation_schema()
ct = CommandTable(ct_schema)

Table 1 contains all elements that can be programmed as part of a command table entry as well as the default value which is applied if this element is not specified by the user. Table 2 contains all parameters of a waveform element, as well as each parameter’s default value. Analogously, Table 3 contains the parameters of a phase type element (phase0, phase1), and Table 4 those of an amplitude type entry (amplitude0 or amplitude1).

If a phase element is specified in any entry of the command table, the absolute phase will be set to zero at the start of the execution, while the amplitudes are set to one.

Table 1: Elements of a command table entry
Field Description Type Range/Value Mandatory Default
index Index of the entry Integer [0—​1023] yes mandatory
waveform Waveform command and its properties Waveform no No waveform played
phase0 Phase command for the first AWG channel Phase no No change to phase setting
phase1 Phase command for the second AWG channel Phase no No change to phase setting
amplitude0 Amplitude command for the first AWG channel Amplitude no No change to amplitude setting and amplitude register set to 0
amplitude1 Amplitude command for the second AWG channel Amplitude no No change to amplitude setting and amplitude register set to 0
Table 2: Parameters of the Waveform element of a command table entry
Field Description Type Range/Value Mandatory Default
index Index of the waveform to play as defined with the assignWaveIndex sequencer instruction integer [0—​15999] if playZero or playHold is False No waveform played
length The length of the waveform in samples integer [32—​WFM_LEN] if playZero or playHold is True the waveform length as declared in the sequence
samplingRate
Divider
Integer exponent n of the sampling rate divider: SampleRate / 2n integer [0—​13] no 0
awgChannel0 Assign the first AWG channel to signal outputs list {"sigout0","sigout1"} no The channel assignment declared in assignWaveIndex
awgChannel1 Assign the second AWG channel to signal outputs list {"sigout0","sigout1"} no The channel assignment declared in assignWaveIndex
precompClear Set to true to clear the precompensation filters bool [True,False] no False
playZero Play a zero-valued waveform for specified length of waveform bool [True,False] no False
playHold Hold the value of the last waveform and marker sample played for specified length bool [True,False] no False
Table 3: Parameters of a Phase element of a command table entry
Field Description Type Range/Value Mandatory Default
value Phase value of the given sine generator in degree float [-180.0—​180.0) values outside of this range will be clamped Yes mandatory
increment Set to true for incremental phase value, or to false for absolute bool [True,False] No False
Table 4: Parameters of an Amplitude element of a command table entry
Field Description Type Range/Value Mandatory Default
value Amplitude scaling factor of the given AWG channel float [-1.0—​1.0] If register is absent 1.0 or previous value in the register
increment Set to true for incremental amplitude value, or to false for absolute bool [True,False] No False
register Index of amplitude register that is selected for scaling the pulse amplitude. integer [0—​3] If value is absent 0