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

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.

Table 1. Programming keywords
Keyword Description

const

Constant declaration

var

Integer variable declaration

cvar

Compile-time variable declaration

string

Constant string declaration

true

Boolean true constant

false

Boolean false constant

for

For-loop declaration

while

While-loop declaration

repeat

Repeat-loop declaration

if

If-statement

else

Else-part of an if-statement

switch

Switch-statement

case

Case-statement within a switch

default

Default-statement within a switch

return

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.

Table 2. Mathematical Constants
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.

void setDIO(var value)
Writes the value as a 32-bit value to the DIO bus.

The value can be either a const or a var value. Configure the Mode setting in the DIO tab when using this command.

Parameter
  • value: The value to write to the DIO (const or var)

var getDIO()
Reads a 32-bit value from the DIO bus.

Return

var containing the read value

var getDIOTriggered()
Reads a 32-bit value from the DIO bus as recorded at the last DIO trigger position.

Return

var containing the read value

void setTrigger(var value)
Sets the Sequencer Trigger output signal.

Allowed parameter values are 0 or 1. For higher integer values, only the least-significant bit will have an effect.

Parameter
  • value: to be written to the trigger distribution unit

void wait(var cycles)
Waits for the given number of Sequencer clock cycles (4 ns per cycle). The execution of the instruction adds an offset of 2 clock cycles, i.e., the statement wait(3) leads to a waiting time of 5 * 4 ns = 20 ns.

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.

Parameter
  • cycles: number of cycles to wait

void waitTrigger(const mask, const value)
Waits until the masked trigger input is equal to the given value.

Parameter
  • mask: mask to be applied to the input signal

  • value: value to be compared with the trigger input

void waitDIOTrigger()
Waits until the DIO interface trigger is active. The trigger is specified by the Strobe Index and Strobe Slope settings in the AWG Sequencer tab.

var getDigTrigger(const index)
Gets the state of the indexed Digital Trigger input (1 or 2).

The physical signal connected to the Digital Trigger input is to be configured in the Readout section of the Quantum Analyzer Setup tab.

Parameter
  • index: index of the Digital Trigger input to be read; can be either 1 or 2

Return

trigger state, either 0 or 1

void error(string msg,…​)
Throws the given error message when reached.

Parameter
  • msg: Message to be displayed

void info(string msg,…​)
Returns the specified message when reached.

Parameter
  • msg: Message to be displayed

void waitDigTrigger(const index)
Waits for the reception of a digital trigger. The physical signal connected to the AWG Digital Trigger inputs is to be configured in the Trigger sub-tab of the AWG Sequencer tab.

Parameter
  • index: Index of the digital trigger input; can be either 1 or 2.

var getZSyncData(const data_type)
Read the last received message on ZSync. The argument specify which data the function should return.

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

Return

var containing the read value

void waitZSyncTrigger()
Waits for a trigger over ZSync.

void startQA(const waveform_generator_mask, const weighted_integrator_mask, const monitor, const result_address, const trigger)
Start the QA signal generation, readout and monitor.

Parameter
  • monitor: Enable for QA monitor, default: false

  • result_address: Set address associated with result, default: 0x0

  • trigger: Trigger value, default: 0x0

  • waveform_generator_mask: Waveform generator enable mask

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

Table 3. Mathematical Functions
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.

Table 4. Predefined Constants
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.