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

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.

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. Phase settings in the command table supercede any settings in the GUI. When using the command table phase entries, make sure to either set sine generator phases either through the command table or via the setSinePhase sequencer command, but do not use the Labone GUI. Otherwise the default phases are set to zero at the start of the sequence. |
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",
"header": {
"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.

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)]]
# upload waveforms to instrument
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",
"header": {
"version": "0.2"
},
"table": ct
}
The sequencer program can now be run, and will produce output as shown in Figure 5.

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

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.
Field | Description | Type | Range/Value | Mandatory | Default |
---|---|---|---|---|---|
index |
Index of the entry |
Integer |
[0—1024] |
yes |
mandatory |
waveform |
Waveform command and its properties |
Waveform |
no |
No waveform play |
|
phase0 |
Phase command for first AWG channel |
Phase |
no |
No phase changes |
|
phase1 |
Phase command for second AWG channel |
Phase |
no |
No phase changes |
|
amplitude0 |
Amplitude command for first AWG channel |
Amplitude |
no |
No amplitude changes |
|
amplitude1 |
Amplitude command for second AWG channel |
Amplitude |
no |
No amplitude changes |
Field | Description | Type | Range/Value | Mandatory | Default |
---|---|---|---|---|---|
index |
Index of the waveform to play as defined with the |
integer |
[0—65535] |
if playZero is False |
No waveform played |
length |
The length of the waveform in samples |
integer |
[32—WFM_LEN] |
if playZero is True |
the waveform length as declared in the sequence |
samplingRateDivider |
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 |
awgChannel1 |
Assign the second AWG channel to signal outputs |
list |
{"sigout0","sigout1"} |
no |
The channel assignment declared in |
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 |
Field | Description | Type | Range/Value | Mandatory | Default |
---|---|---|---|---|---|
value |
Phase value of the given sine generator in degree |
float |
[0.0—360.0) |
Yes |
mandatory |
increment |
Set to true for incremental phase value, or to false for absolute |
bool |
[True,False] |
No |
False |
Field | Description | Type | Range/Value | Mandatory | Default |
---|---|---|---|---|---|
value |
Amplitude scaling factor of the given AWG channel |
float |
[-1.0—1.0] |
Yes |
mandatory |
increment |
Set to true for incremental amplitude value, or to false for absolute |
bool |
[True,False] |
No |
False |