Readout Pulse Generator
The Readout Pulse Generator generates the Readout pulses in the Qubit Readout mode. It consists of a Waveform Memory that stores up to 16 arbitrary waveforms (8 for the basic 2-channel SHFQA), and a Sequencer. The Sequencer not only controls the playback of the Readout Pulses, but also has control over the Qubit Measurement Unit, and the DIO and ZSync. All nodes that are used to program the Readout Pulse Generator can be found under /DEV…./QACHANNELS/n/GENERATOR/….
Sequencer
Features Overview
-
Separate Sequencers for each Readout Channel.
-
Control over all available complex-valued Waveform Memories (32 kSa for 8 memories, 64 kSa for 16 memories)
-
Sequence branching
-
Access to Hardware Trigger Engine
-
Control over Qubit Measurement Unit
-
Interface to DIO and ZSync
-
High-level programming language
Description
The Sequencer can be considered the central control unit of the SHFQA as it has access to the playback of the Waveform Memories to generate Readout Pulses, the start of the Integration of the Readout Signals from the experiment and the communication with additional devices through the DIO or ZSync. The programming language SeqC
is based on C and specified in detail in SeqC language. In contrast to other AWG Sequencers, e.g. from the HDAWG, it does not provide writing access to the Waveform Memories and hence does not come with predefined waveforms. The stricter separation between Sequencer and Waveform Memory allows implementation of more advanced and application-specific features, e.g. Staggered Readout, while still providing hard real-time sequencing.
The Sequencer features a compiler which translates the high-level sequence program (SeqC) into machine instructions to be stored in the instrument sequencer memory. The sequence program is written using high-level control structures and syntax that are inspired by human language, whereas machine instructions reflect exactly what happens on the hardware level. Writing the sequence program using SeqC represents a more natural and efficient way of working in comparison to writing lists of machine instructions, which is the traditional way of programming AWGs. Concretely, the improvements rely on features such as:
-
Waveform playback and sequencing in a single script
-
Easily readable syntax and naming for run-time variables and constants
-
Definition of user functions and procedures for advanced structuring
-
Syntax validation
By design, there is no one-to-one link between the list of statements in the high-level language and the list of instructions executed by the Sequencer. In order to understand the execution timing, it’s helpful to consider the internal architecture of the Readout Pulse Generator, consisting of the Sequencer itself, and the Waveform Memory including a Waveform Player.
Sequencer Operation
To operate the sequencer, an AWG-module first needs to be instantiated, e.g. through the a Python API:
awgModule = daq.awgModule()
awgModule.set('device', _device_name)
awgModule.set('index', qa_channel)
awgModule.execute()
After defining a SeqC program (here as awg_program
) the sequence program can be uploaded to the device and the upload status checked using:
awgModule.set('compiler/sourcestring', awg_program)
print(awgModule.getString('compiler/statusstring'))
See the Tutorials or the "AWG module" in the Programming Manual for more information on how to use |
As soon as the Ready
node is true, the compilation is successful and the program is transferred to the device. If the compilation fails, the Status
node will display debug messages.
After successful uploading of a sequence to the instrument, the Sequencer can be started using the Enable
node.
If the Sequencer should listen to a Trigger Input Signal, it can either directly wait for a ZSync Trigger, or access the Hardware Trigger Engine through two digital Auxiliary Triggers.
All nodes for the Sequencer can be accessed through the /DEV…./QACHANNELS/n/GENERATOR/SEQUENCER/… node tree.
SeqC
The syntax of the LabOne AWG Sequencer programming language is based on C, but with a few simplifications. Each statement is concluded with a semicolon, several statements can be grouped with curly brackets, and comment lines are identified with a double slash.
The following example shows some of the fundamental functionalities: repeated playback, triggering, and single/dual-channel waveform playback and readout. See Tutorials for a step-by-step introduction with more examples.
// repeat sequence 100 times
repeat (100) {
//play the pulse stored in Waveform Memory 0 and read out using Integration Weight 0
startQA(QA_GEN_0, QA_INT_0|QA_INT_1, true, 0, 0x0);
//wait 10 Sequencer samples
wait(10);
// wait for a Trigger over ZSync
waitZSyncTrigger()
//activate Generators 0,1, and 2 and readout with all Integration Weights
startQA(QA_GEN_0|QA_GEN_1|QA_GEN_2, QA_INT_ALL, true, 0, 0x0);
}
Keywords and Comments
The following table lists the keywords used in the LabOne AWG Sequencer language.
Keyword | Description |
---|---|
|
Constant declaration |
|
Integer variable declaration |
|
Compile-time variable declaration |
|
Constant string declaration |
|
Boolean true constant |
|
Boolean false constant |
|
For-loop declaration |
|
While-loop declaration |
|
Repeat-loop declaration |
|
If-statement |
|
Else-part of an if-statement |
|
Switch-statement |
|
Case-statement within a switch |
|
Default-statement within a switch |
|
Return from function or procedure, optionally with a return value |
The following code example shows how to use comments.
const a = 10; // This is a line comment. Everything between the double
// slash and the end of the line will be ignored.
/* This is a block comment. Everything between the start-of-block-comment and end-of-block-comment markers is ignored.
For example, the following statement will be ignored by the compiler.
const b = 100;
*/
Constants and Variables
Constants may be used to make the program more readable. They may be of integer or floating-point type. It must be possible for the compiler to compute the value of a constant at compile time, i.e., on the host computer. Constants are declared using the const
keyword.
Compile-time variables may be used in computations and loop iterations during compile time, e.g. to create large numbers of waveforms in a loop. They may be of integer or floating-point type. They are used in a similar way as constants, except that they can change their value during compile time operations. Compile-time variables are declared using the cvar
keyword.
Variables may be used for making simple computations during run time, i.e., on the instrument. The Sequencer supports integer variables, addition, and subtraction. Not supported are floating-point variables, multiplication, and division. Typical uses of variables are to step waiting times, to output DIO values, or to tag digital measurement data with a numerical identifier. Variables are declared using the var
keyword.
The following code example shows how to use variables.
var b = 100; // Create and initialize a variable
// Repeat the following block of statements 100 times
repeat (100) {
b = b + 1; // Increment b
wait(b); // Wait 'b' cycles
}
The following table shows the predefined constants. These constants are intended to be used as arguments in certain run-time evaluated functions that encode device parameters with integer numbers.
Name | Value | Description |
---|---|---|
M_E |
2.71828182845904523536028747135266250 |
e |
M_LOG2E |
1.44269504088896340735992468100189214 |
log2(e) |
M_LOG10E |
0.434294481903251827651128918916605082 |
log10(e) |
M_LN2 |
0.693147180559945309417232121458176568 |
loge(2) |
M_LN10 |
2.30258509299404568401799145468436421 |
loge(10) |
M_PI |
3.14159265358979323846264338327950288 |
pi |
M_PI_2 |
1.57079632679489661923132169163975144 |
pi/2 |
M_PI_4 |
0.785398163397448309615660845819875721 |
pi/4 |
M_1_PI |
0.318309886183790671537767526745028724 |
1/pi |
M_2_PI |
0.636619772367581343075535053490057448 |
2/pi |
M_2_SQRTPI |
1.12837916709551257389615890312154517 |
2/sqrt(pi) |
M_SQRT2 |
1.41421356237309504880168872420969808 |
sqrt(2) |
M_SQRT1_2 |
0.707106781186547524400844362104849039 |
1/sqrt(2) |
Numbers can be expressed using either of the following formatting.
const a = 10; // Integer notation
const b = -10; // Negative number
const h = 0xdeadbeef; // Hexadecimal integer
const bin = 0b10101; // Binary integer
const f = 0.1e-3; // Floating point number.
const not_float = 10e3; // Not a floating point number
Booleans are specified with the keywords true and false. Furthermore, all numbers that evaluate to a nonzero value are considered true
. All numbers that evaluate to zero are considered false
.
Strings are delimited using "" and are interpreted as constants. Strings may be concatenated using the +
operator.
string AWG_PATH = "awgs/0/";
string AWG_GAIN_PATH = AWG_PATH + "gains/0";
Waveform Playback and Predefined Functions
The following table contains the definition of functions for waveform playback and other purposes.
The value can be either a const or a var value. Configure the Mode setting in the DIO tab when using this command.
void setDIO(var value)
value:
The value to write to the DIO (const or var)
var containing the read value
var getDIO()
var containing the read value
var getDIOTriggered()
Allowed parameter values are 0 or 1. For higher integer values, only the least-significant bit will have an effect.
void setTrigger(var value)
value:
to be written to the trigger distribution unit
Note: the minimum waiting time amounts to 3 cycles, which means that wait(0) and wait(1) will both result in a waiting time of 3 * 4 ns = 12 ns.
void wait(var cycles)
cycles:
number of cycles to wait
void waitTrigger(const mask, const value)
mask:
mask to be applied to the input signalvalue:
value to be compared with the trigger input
void waitDIOTrigger()
The physical signal connected to the Digital Trigger input is to be configured in the Readout section of the Quantum Analyzer Setup tab. trigger state, either 0 or 1
var getDigTrigger(const index)
index:
index of the Digital Trigger input to be read; can be either 1 or 2
void error(string msg,…)
msg:
Message to be displayed
void info(string msg,…)
msg:
Message to be displayed
void waitDigTrigger(const index)
index:
Index of the digital trigger input; can be either 1 or 2.
var containing the read value
var getZSyncData(const data_type)
data_type:
Specifies which data the function should return: ZSYNC_DATA_RAW: Return the data received on the ZSync as-is without parsing. The structure of the message can change across different LabOne releases.
void waitZSyncTrigger()
void startQA(const waveform_generator_mask, const weighted_integrator_mask, const monitor, const result_address, const trigger)
monitor:
Enable for QA monitor, default: falseresult_address:
Set address associated with result, default: 0x0trigger:
Trigger value, default: 0x0waveform_generator_mask:
Waveform generator enable maskweighted_integrator_mask:
Integration unit enable mask, default: QA_INT_ALL
Expressions
Expressions may be used for making computations based on mathematical functions and operators. There are two kinds of expressions: those evaluated at compile time (when the sequencer program is compiled on the computer), and those evaluated at run time.
Compile-time evaluated expressions only involve constants (const
) or
compile-time variables (cvar
) and can be computed at compile time by the host computer. Such expressions can make use of standard mathematical functions and floating point arithmetic.
Run-time evaluated expressions involve variables (var
) and are evaluated by the Sequencer on the instrument. Due to the limited computational capabilities of the Sequencer, these expressions may only operate on integer numbers and there are less operators available than at compile time.
The following table contains the list of mathematical functions supported at compile time.
Function | Description |
---|---|
const abs(const c) |
absolute value |
const acos(const c) |
inverse cosine |
const acosh(const c) |
hyperbolic inverse cosine |
const asin(const c) |
inverse sine |
const asinh(const c) |
hyperbolic inverse sine |
const atan(const c) |
inverse tangent |
const atanh(const c) |
hyperbolic inverse tangent |
const cos(const c) |
cosine |
const cosh(const c) |
hyperbolic cosine |
const exp(const c) |
exponential function |
const ln(const c) |
logarithm to base e (2.71828…) |
const log(const c) |
logarithm to the base 10 |
const log2(const c) |
logarithm to the base 2 |
const log10(const c) |
logarithm to the base 10 |
const sign(const c) |
sign function -1 if x<0; 1 if x>0 |
const sin(const c) |
sine |
const sinh(const c) |
hyperbolic sine |
const sqrt(const c) |
square root |
const tan(const c) |
tangent |
const tanh(const c) |
hyperbolic tangent |
const ceil(const c) |
smallest integer value not less than the argument |
const round(const c) |
round to nearest integer |
const floor(const c) |
largest integer value not greater than the argument |
const avg(const c1, const c2,…) |
mean value of all arguments |
const max(const c1, const c2,…) |
maximum of all arguments |
const min(const c1, const c2,…) |
minimum of all arguments |
const pow(const base, const exp) |
first argument raised to the power of second argument |
const sum(const c1, const c2,…) |
sum of all arguments |
The following table contains the list of predefined mathematical constants. These can be used for convenience in compile-time evaluated expressions.
Name | Value | Description |
---|---|---|
ZSYNC_DATA_RAW |
0 |
Constant to use as argument to getZSyncData. |
Control Structures
Functions may be declared using the var
keyword. Procedures may be declared using the void
keyword. Functions must return a value, which should be specified using the return
keyword. Procedures can not return values. Functions and procedures may be declared with an arbitrary number of arguments. The return
keyword may also be used without arguments to return from an arbitrary point within the function or procedure. Functions and procedures may contain variable and constant declarations. These declarations are local to the scope of the function or procedure.
var function_name(argument1, argument2, ...) {
// Statements to be executed as part of the function.
return constant-or-variable;
}
void procedure_name(argument1, argument2, ...) {
// Statements to be executed as part of the procedure.
// Optional return statement
return;
}
An if-then-else structure is used to create a conditional branching point in a sequencer program.
// If-then-else statement syntax
if (expression) {
// Statements to execute if 'expression' evaluates to 'true'.
} else {
// Statements to execute if 'expression' evaluates to 'false'.
}
// If-then-else statement short syntax
(expression)?(statement if true):(statement if false)
// If-then-else statement example
const REQUEST_BIT = 0x0001;
const ACKNOWLEDGE_BIT = 0x0002;
const IDLE_BIT = 0x8000;
var dio = getDIO();
if (dio & REQUEST_BIT) {
dio = dio | ACKNOWLEDGE_BIT;
setDIO(dio);
} else {
dio = dio | IDLE_BIT;
setDIO(dio);
}
A switch-case structure serves to define a conditional branching point similarly to the if-then-else
statement, but is used to split the sequencer thread into more than two branches. Unlike the if-then-else
structure, the switch statement is synchronous, which means that the execution time is the same for all branches and determined by the execution time of the longest branch. If no default case is provided and no case matches the condition, all cases will be skipped. The case
arguments need to be of type const
.
// Switch-case statement syntax
switch (expression) {
case const-expression:
expression;
...
default:
expression;
}
// Switch-case statement example
switch (getDIO()) {
case 0:
startQA(QA_GEN_0, QA_INT_0, true, 0, 0x0);
case 1:
startQA(QA_GEN_1, QA_INT_1, true, 0, 0x0);
case 2:
startQA(QA_GEN_2, QA_INT_2, true, 0, 0x0);
default:
startQA(QA_GEN_3, QA_INT_3, true, 0, 0x0);
}
The for loop is used to iterate through a code block several times. The initialization statement is executed before the loop starts. The end-expression is evaluated at the start of each iteration and determines when the loop should stop. The loop is executed as long as this expression is true
. The iteration-expression is executed at the end of each loop iteration. Depending on how the for loop is set up, it can be either evaluated at compile time or at run
time.
For a run-time evaluated for loop, use the var
data type as a loop index. To ensure that a loop is evaluated at compile time, use the cvar
data type as a loop index. Furthermore, the compile-time for loop should only contain waveform generation/editing operations and it can’t contain any variables of type var
.
The following code example shows both versions of the loop.
// For loop syntax
for (initialization; end-expression; iteration-expression) {
// Statements to execute while end-expression evaluates to true
}
// For loop example (compile-time execution)
cvar i;
wave w_pulses;
for (i = 0; i < 10; i = i + 1) {
startQA(QA_GEN_0<<1, QA_INT_0, true, 0, 0x0);
}
// For loop example (run-time execution)
var k;
var j;
for (j = 9; j >= 0; j = j - 1) {
startQA(QA_GEN_0, QA_INT_0, true, 0, 0x0);
k += j;
}
The while loop is a simplified version of the for
loop. The end-expression is evaluated at the start of each loop iteration. The contents of the loop are executed as long as this expression is true
. Like the for
loop, this loop comes in a compile-time version (if the end-expression involves only cvar
and const
) and in a run-time version (if the end-expression involves also var
data types).
// While loop syntax
while (end-expression) {
// Statements to execute while end-expression evaluates to true
}
// While loop example
const STOP_BIT = 0x8000;
var run = 1;
var i = 0;
var dio = 0;
while (run) {
dio = getDIO();
run = dio & STOP_BIT;
dio = dio | (i & 0xff);
setDIO(dio);
i = i + 1;
}
The repeat loop is a simplified version of the for loop. It repeats the contents of the loop a fixed number of times. In contrast to the for
loop, the repetition number of the repeat loop must be known at compile time, i.e., const-expression can only depend on constants and not on variables. Unlike the for
and the while
loop, this loop comes only in a run-time version. Thus, no cvar
data types may be modified in the loop body.
// Repeat loop syntax
repeat (constant-expression) {
// Statements to execute
}
// Repeat loop example
repeat (100) {
setDIO(0x1);
wait(10);
setDIO(0x0);
wait(10);
}
Waveform Memory
The Waveform Memory stores the different complex-valued arbitrary waveforms that are used to readout the qubits. They can be accessed through /DEV…./QACHANNELS/n/GENERATOR/WAVEFORMS/n/WAVE and have a maximal length of 4096 samples and a vertical range between -1 and 1 relative to the full scale of the Output Range.