See also
A Jupyter notebook version of this tutorial can be downloaded here.
TTL Pulse Generation#
This tutorial will demonstrate how to use the pulse generation capabilities of the Timetagging Module (QTM) in a Q1ASM sequencer program. Before going through this tutorial it is highly recommended to peruse the QTM Module page and the Timetag Sequencer page. In this tutorial we will learn how to:
Play simple TTL pulses on a 1 nanosecond timegrid
Place TTL pulses with 39 picosecond precision using the fine delay argument
Play the shortest possible TTL pulse on the QTM, 1 nanosecond wide
[1]:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from qblox_instruments.qcodes_drivers.io_channel_qtm import IOChannelQTM
from qblox_instruments.qcodes_drivers.sequencer import Sequencer
from __future__ import annotations
from qcodes.instrument import find_or_create_instrument
from qblox_instruments import Cluster, ClusterType
Scan For Clusters#
We scan for the available devices connected via ethernet using the Plug & Play functionality of the Qblox Instruments package (see Plug & Play for more info).
!qblox-pnp list
[2]:
cluster_ip = "10.10.200.42"
cluster_name = "cluster0"
Connect to Cluster#
We now make a connection with the Cluster.
[3]:
cluster: Cluster = find_or_create_instrument(
Cluster,
recreate=True,
name=cluster_name,
identifier=cluster_ip,
dummy_cfg=(
{
2: ClusterType.CLUSTER_QCM,
4: ClusterType.CLUSTER_QRM,
6: ClusterType.CLUSTER_QCM_RF,
8: ClusterType.CLUSTER_QRM_RF,
10: ClusterType.CLUSTER_QTM,
12: ClusterType.CLUSTER_QRC,
16: ClusterType.CLUSTER_QSM,
}
if cluster_ip is None
else None
),
)
cluster.reset()
print(cluster.get_system_status())
Status: ERROR, Flags: FEEDBACK_NETWORK_CALIBRATION_FAILED, Slot flags: NONE
Get connected modules#
[4]:
# QTM modules
modules = cluster.get_connected_modules(lambda mod: mod.is_qtm_type)
# This uses the module of the correct type with the lowest slot index
module = list(modules.values())[0]
Helper function#
[5]:
def get_wait(wait_time: int, script: str = "") -> str:
"""
Return long wait times as a series of wait statements, since wait has maximum argument 65535 ns.
Parameters
----------
wait_time : int
The amount of wait time required. Minimum 4ns.
script : string
The Q1ASM script to which the waits must be added.
Returns
-------
The series of decomposed wait instructions for the long wait.
"""
for i in range(wait_time // 65532):
script = script + "\n \twait 65532 #waiting time."
if wait_time % 65532:
script = script + f"\n \twait {wait_time % 65532} #waiting time."
return script
Placing TTL pulses on 1ns timegrid.#
[6]:
# Defining the function to produce the correct sequence and module settings, upload the sequence, and arm the sequencer.
def digital_output(
io: IOChannelQTM,
seq: Sequencer,
pulse_duration: int,
wait_duration: int = 100,
loop: bool = False,
sync: bool = False,
) -> None:
"""
Produce a pulse_duration ns long TTL pulse from the specified QTM channel.
An infinite pulse train can also be produced for easy observation on a scope.
Parameters
----------
io : IOChannelQTM
IOChannel being used.
seq : Sequencer
Corresponding sequencer being used.
pulse_duration : int
Duration of the TTL pulse being high in nanoseconds. Must be minimum 8 ns.
wait_duration : int
Duration in nanoseconds of the output being low after pulse ends. Must be minimum 8 ns.
loop : bool
Set to True if infinite pulse train is desired.
sync : bool
Set to True if SYNQ needs to be enabled; for example when using multiple channels. The function can be called multiple times before the sequencers are started.
"""
io.mode("output")
seq.sync_en(False)
program = """"""
if sync:
seq.sync_en(True)
program += """ wait_sync 4
"""
if not loop:
program += f"""
set_digital 1,1,0 # arguments - level (1=high), mask (always 1), fine_delay in steps of 1/128 ns
upd_param 4
{get_wait(pulse_duration - 4)}
set_digital 0,1,0 # arguments - level (0=low), mask (always 1), fine_delay in steps of 1/128 ns
upd_param 4
{get_wait(wait_duration - 4)}
stop
"""
elif loop:
program += f"""
loop:
set_digital 1,1,0 #level (1=high), mask (always 1), fine_delay in steps of 1/128 ns
upd_param 4
{get_wait(pulse_duration - 4)}
set_digital 0,1,0 #level (0=low), mask (always 1), fine_delay in steps of 1/128 ns
upd_param 4
{get_wait(wait_duration - 4)}
jmp @loop
stop
"""
seq.sequence(
{
"waveforms": {},
"weights": {},
"acquisitions": {},
"program": program,
}
)
seq.arm_sequencer()
[7]:
# Using QTM Channel 2 only for output
digital_output(module.io_channel1, module.sequencer1, 100, loop=True)
module.start_sequencer()
[8]:
module.stop_sequencer()
Placing TTL pulses on 39 ps timegrid using fine delay#
Fine delay argument allows for shifting of the output’s rising or falling edge up to 16 ns in steps of 5/128 ns ~ 39 ps.
Here, we shorten the pulse by shifting only the rising edge.
Note that the fine_delay argument of set_digital is in units of 1/128 ~ 7.8 ps. Hence, the maximum pulse placement precision of 39 ps means one can effectively increase the fine_delay argument in steps of 5.
[9]:
def digital_output_shorten_pulse(
io: IOChannelQTM,
seq: Sequencer,
fine_delay_step: float,
sync: bool = False,
) -> None:
"""
Produce 2 TTL pulses from the specified QTM channel initially separated by 50 ns.
Each cycle the second rising edge moves forward by fine_delay_step ns.
An infinite pulse train of these 2 pulses is produced for easy observation on a scope.
Parameters
----------
io : IOChannelQTM
IOChannel being used.
seq : Sequencer
Corresponding sequencer being used.
fine_delay_step : float
Delay per cycle in the second rising edge in units of ns.
sync : bool
Set to True if SYNQ needs to be enabled; for example when using multiple channels. The function can be called multiple times before the sequencers are started.
"""
io.mode("output")
seq.sync_en(False)
program = """"""
if sync:
seq.sync_en(True)
program += """ wait_sync 4
"""
# Converting the specified fine delay step into units of 1/128 ns
fine_delay_step = int(fine_delay_step * 128) # e.g. delay of 0.5 ns: fine_delay = 64
program += f"""
move 0, R1 #fine_delay
move 1, R2
move 0, R3
loop:
# First pulse, 50 ns long
set_digital 1,1,0
upd_param 10
set_digital 0,1,0
upd_param 4
# Waiting for 50 ns
wait 46
# Here we set the fine_delay argument and increment it every cycle
set_digital R2,1,R1 #level (1=high), mask (always 1), fine_delay in steps of 1/128 ns
upd_param 4
wait 80
set_digital 0,1,0
upd_param 4
{get_wait(60000000)}
# Incrementing the fine delay asa specified
add R1, {fine_delay_step}, R1
{get_wait(10000000)}
jmp @loop
stop
"""
seq.sequence(
{
"waveforms": {},
"weights": {},
"acquisitions": {},
"program": program,
}
)
seq.arm_sequencer()
[10]:
# Using QTM Channel 2 for output
digital_output_shorten_pulse(module.io_channel1, module.sequencer1, fine_delay_step=1 / 8)
module.start_sequencer()
[11]:
module.stop_sequencer()
In this part, following the example of the last section, we shift the whole pulse by delaying both the rising and falling edge of the digital signal by using the fine delay argument.
[12]:
def digital_output_shift_pulse(
io: IOChannelQTM,
seq: Sequencer,
fine_delay_step: float,
sync: bool = False,
) -> None:
"""
Produce 2 TTL pulses from the specified QTM channel initially separated by 50 ns.
Each cycle the entire second pulse moves forward by fine_delay_step ns.
An infinite pulse train of these 2 pulses is produced for easy observation on a scope.
Parameters
----------
io : IOChannelQTM
IOChannel being used.
seq : Sequencer
Corresponding sequencer being used.
fine_delay_step : float
Delay per cycle in the second pulse in units of ns
sync : bool
Set to True if SYNQ needs to be enabled; for example when using multiple channels.
The function can be called multiple times before the sequencers are started.
"""
io.mode("output")
seq.sync_en(False)
program = """"""
if sync:
seq.sync_en(True)
program += """ wait_sync 4
"""
fine_delay_step = int(fine_delay_step * 128) # e.g. delay of 0.5 ns: fine_delay = 64
program = f"""
wait_sync 4
move 0, R1 #fine_delay
move 1, R2
move 0, R3
loop:
# First pulse, 20 ns long
set_digital 1,1,0 #level (1=high), mask (always 1), fine_delay in steps of 1/128 ns
upd_param 20
set_digital 0,1, 0
upd_param 20
# Wait 50 ns
wait 30
# Start of second pulse. Note that in contrast to the last example, now both rising and falling edges contain the fine delay argument
set_digital R2,1,R1 #level (1=high), mask (always 1), fine_delay in steps of 1/128 ns
upd_param 20
set_digital R3,1,R1
upd_param 20
{get_wait(100000000)}
# Increment the fine delay as specified
add R1, {fine_delay_step}, R1
{get_wait(10000000)}
jmp @loop
stop
"""
seq.sequence(
{
"waveforms": {},
"weights": {},
"acquisitions": {},
"program": program,
}
)
seq.arm_sequencer()
[13]:
digital_output_shift_pulse(module.io_channel1, module.sequencer1, fine_delay_step=1 / 8)
module.start_sequencer()
[14]:
cluster.reset()
Minimum width (1 ns) pulse#
Here we produce a 1 ns pulse from the QTM via Q1ASM using fine delay.
[15]:
def digital_output_1ns(
io: IOChannelQTM,
seq: Sequencer,
loop: bool = False,
sync: bool = False,
) -> None:
"""
Produce a 1 ns pulse from the specified QTM channel.
An infinite pulse train of these pulses can be produced.
Parameters
----------
io : IOChannelQTM
IOChannel being used.
seq : Sequencer
Corresponding sequencer being used.
loop : bool
Set to True if infinite pulse train is desired
sync : bool
Set to True if SYNQ needs to be enabled; for example when using multiple channels. The function can be called multiple times before the sequencers are started
"""
io.mode("output")
seq.sync_en(False)
program = """"""
if sync:
seq.sync_en(True)
program += """ wait_sync 4
"""
if not loop:
program += """
# Introducing fine delay of 3ns in the rising edge
set_digital 1,1,384 #level (1=high), mask (always 1), fine_delay in steps of 1/128 ns
upd_param 4
set_digital 0,1,0
upd_param 396
stop
"""
elif loop:
program += """
loop:
# Introducing fine delay of 3ns in the rising edge
set_digital 1,1,384 #level (1=high), mask (always 1), fine_delay in steps of 1/128 ns
upd_param 4
set_digital 0,1,0
upd_param 400
jmp @loop
stop
"""
seq.sequence(
{
"waveforms": {},
"weights": {},
"acquisitions": {},
"program": program,
}
)
seq.arm_sequencer()
[16]:
digital_output_1ns(module.io_channel1, module.sequencer1, loop=True)
module.start_sequencer()
Stopping all sequencers and then resetting the cluster#
[17]:
module.stop_sequencer()
[18]:
cluster.reset()
print(cluster.get_system_status())
Status: ERROR, Flags: FEEDBACK_NETWORK_CALIBRATION_FAILED, Slot flags: NONE