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 16384 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> Device Node Tree

• 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 Device Node Tree

Basic command table use

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

// Define two waveforms
wave w_a = gauss(2048, 1, 1024, 256);
wave w_b = gauss(2048, 1, 1024, 384);

// 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);

Compile the above program on the HDAWG sequencer. 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. In the next step, we will use the API to upload it to the instrument.

 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.

The command table is in general 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 Python dictionary, which we convert into a JSON string only at upload.

# index of wave table and command table entries
ct_index = 0
wave_index = 0

# command table as python dictionary
ct = [{"index": ct_index,
"waveform": {"index": wave_index},
"amplitude0": {
"value": 1.0
},
"amplitude1": {
"value": -0.5
},
"phase0": {
"value": 0.0,
"increment": True
},
"phase1": {
"value": 90.0,
"increment": True
}
},
{"index": ct_index + 1,
"waveform": {"index": 0},
"amplitude0": {
"value": 0.5
}
}
]

command_table = {"$schema": "http://docs.zhinst.com/hdawg/commandtable/v2/schema", "header": {"version": "0.2"}, "table": ct} In this example, we generate a first command table entry with index "ct_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. To upload the command table to the HDAWG, we need to connect to the device and then write the command table as a vector to the correct node. In python this can be done with the code below, using the zhinst API module. import zhinst.utils import json # Connect to device device_id = 'dev8xyz' # to be replaced by instrument serial number api_level = 6 (daq, device, _) = zhinst.utils.create_api_session( device_id, api_level) awg_index = 0 # which AWG core to be used, here: first two channels # Upload command table - generate string from dictionary daq.setVector(f"/{device}/awgs/{awg_index}}/commandtable/data", json.dumps(command_table))  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.  When a command table entry is called, the amplitude and phase is set persistently. Subsequent waveform playbacks on the same channel will need to take this into account, unless amplitude and phase is set for them also. 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. // 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 a command table with just two entries, in this case both referencing the same wave table entry. # Define command table as dict ct = [{"index": 0, "waveform": { "index": 0 }, "amplitude0": { "value": 1.0 }, "amplitude1": { "value": 0.0 } }, {"index": 1, "waveform": { "index": 0 }, "amplitude0": { "value": -0.1, "increment": True }, "amplitude1": { "value": 0.1, "increment": True } } ] command_table = { "$schema": "http://docs.zhinst.com/hdawg/commandtable/v2/schema",
"version": "0.2"
},
"table": ct
}

Uploading the command table to the instrument works in the same way as before. 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
 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.

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.

// Define two wave table entries through paceholders
assignWaveIndex(placeholder(1024), placeholder(1024), 0);
assignWaveIndex(placeholder(1024), placeholder(1024), 1);

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

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.

import numpy as np
import zhinst.utils

# 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)]]

for i, wave in enumerate(waves):
wave_raw = zhinst.utils.convert_awg_waveform(wave[0],wave[1])
daq.setVector(f'/{device_id}/awgs/{awg_index}/waveform/waves/{i}', wave_raw)

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

# Define command table as dict
ct = [{"index": 0,
"waveform": {
"index": 0,
"awgChannel0": ["sigout0"],
"awgChannel1": ["sigout1"]
},
"amplitude0": {
"value": 1.0
},
"amplitude1": {
"value": -1.0
}
},
{"index": 1,
"waveform": {
"index": 1,
"awgChannel0": ["sigout1"],
"awgChannel1": ["sigout0"]
}
},
{"index": 2,
"waveform": {
"index": 1,
"awgChannel0": ["sigout0", "sigout1"],
"awgChannel1": ["sigout0", "sigout1"]
}
}
]

command_table = {
"\$schema": "http://docs.zhinst.com/hdawg/commandtable/v2/schema",
"version": "0.2"
},
"table": ct
}

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

Figure 5. 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.

The documentation of all possible parameters in the command table JSON file is contained in the publicly hosted JSON Schema file at http://docs.zhinst.com/hdawg/commandtable/v2/schema. The URL of this schema file should be referenced from the JSON string when working on it in a JSON-enabled editor. In this way, you can easily access the documentation via auto complete and in-line help, as shown in the screenshot below for the example of Microsoft’s Visual Studio Code.

Figure 6. JSON editing with inline documentation in Visual Studio Code