Synchronization¶
In this tutorial we will demonstrate how to synchronize two Qblox instruments using the SYNQ technology (see section Synchronization). For this tutorial we will use one Pulsar QCM and one Pulsar QRM and we will be acquiring waveforms sequenced by the Pulsar QCM using the Pulsar QRM. By synchronizing the two instruments using the SYNQ technology, timing the acquisition of the waveforms becomes trivial.
For this tutorial to work, we need to connect both instruments to the same network, connect the \(\text{REF}^{out}\) of the Pulsar QCM to the \(\text{REF}^{in}\) of the Pulsar QRM using a 50cm coaxial cable, connect their SYNQ ports using the SYNQ cable and finally connect \(\text{O}^{[1-2]}\) of the Pulsar QCM to \(\text{I}^{[1-2]}\) of the Pulsar QRM respectively.
This tutorial is designed with the Pulsar QCM as output instrument in mind, but the Pulsar QCM can easily be swapped with another Pulsar QRM as well. Just change the Pulsar QCM instantiation to a Pulsar QRM instantiation.
Setup¶
First, we are going to import the required packages and connect to the instruments.
[1]:
#Set up the environment.
import pprint
import os
import scipy.signal
import math
import json
import matplotlib.pyplot
import numpy
from pulsar_qcm.pulsar_qcm import pulsar_qcm
from pulsar_qrm.pulsar_qrm import pulsar_qrm
#Connect to the Pulsar QCM at default IP address.
pulsar_qcm = pulsar_qcm("qcm", "192.168.0.2")
#Reset the Pulsar QCM for good measure.
pulsar_qcm.reset()
print("QCM status:")
print(pulsar_qcm.get_system_status())
print()
#Connect to the Pulsar QRM at alternate address.
pulsar_qrm = pulsar_qrm("qrm", "192.168.0.3")
#Reset the Pulsar QRM for good measure.
pulsar_qrm.reset()
print("QRM status:")
print(pulsar_qrm.get_system_status())
QCM status:
{'status': 'OKAY', 'flags': []}
QRM status:
{'status': 'OKAY', 'flags': []}
We also need to configure the reference clock sources of the instruments. The Pulsar QCM is used as the overal reference source and needs to be configured to use its internal reference clock (the default setting). The Pulsar QRM will use the Pulsar QCM’s reference clock and needs to be configured to use the external reference clock source.
[2]:
#Set reference clock source.
pulsar_qrm.reference_source("external")
Generate waveforms¶
Next, we need to create the waveforms for the sequence.
[3]:
#Waveform parameters
waveform_length = 120 #nanoseconds
#Waveform dictionary (data will hold the samples and index will be used to select the waveforms in the instrument).
waveforms = {
"gaussian": {"data": [], "index": 0},
"sine": {"data": [], "index": 1}
}
#Create gaussian waveform
if "gaussian" in waveforms:
waveforms["gaussian"]["data"] = scipy.signal.gaussian(waveform_length, std=0.12 * waveform_length)
#Create sine waveform
if "sine" in waveforms:
waveforms["sine"]["data"] = [math.sin((2*math.pi/waveform_length)*i) for i in range(0, waveform_length)]
Let’s plot the waveforms to see what we have created.
[4]:
time = numpy.arange(0, max(map(lambda d: len(d["data"]), waveforms.values())), 1)
fig, ax = matplotlib.pyplot.subplots(1,1, figsize=(10, 10/1.61))
ax.set_ylabel("Waveform primitive amplitude")
ax.set_xlabel("Time (ns)")
for wf, d in waveforms.items():
ax.plot(time[:len(d["data"])], d["data"], ".-", linewidth=0.5, label=wf)
ax.legend(loc=4)
ax.yaxis.grid()
ax.xaxis.grid()
Create Q1ASM programs¶
Now that we have the waveforms for the sequence, we need a simple Q1ASM program that sequences the waveforms in the Pulsar QCM and acquires the waveforms in the Pulsar QRM.
[5]:
#Pulsar QCM sequence program.
qcm_seq_prog = """
wait_sync 4 #Synchronize sequencers over multiple instruments.
play 0,1,16384 #Play waveforms and wait duration of acquisition.
stop #Stop.
"""
#Pulsar QRM sequence program.
qrm_seq_prog = """
wait_sync 4 #Synchronize sequencers over multiple instruments.
acquire 0,0,16384 #Acquire waveforms and wait duration of acquisition.
stop #Stop.
"""
Upload sequences¶
Now that we have the waveforms and Q1ASM programs, we can combine them in the sequences stored in JSON files.
[6]:
#Reformat waveforms to lists if necessary.
for name in waveforms:
if str(type(waveforms[name]["data"]).__name__) == "ndarray":
waveforms[name]["data"] = waveforms[name]["data"].tolist() # JSON only supports lists
#Add QCM sequence program and waveforms to single dictionary and write to JSON file.
wave_and_prog_dict = {"waveforms": {"awg": waveforms}, "program": qcm_seq_prog}
with open("qcm_sequence.json", 'w', encoding='utf-8') as file:
json.dump(wave_and_prog_dict, file, indent=4)
file.close()
#Add QRM sequence program and waveforms to single dictionary and write to JSON file.
wave_and_prog_dict = {"waveforms": {"acq": waveforms}, "program": qrm_seq_prog}
with open("qrm_sequence.json", 'w', encoding='utf-8') as file:
json.dump(wave_and_prog_dict, file, indent=4)
file.close()
Let’s write the JSON file to the instruments. We will use sequencer 0 of both the Pulsar QCM and Pulsar QCM, which will drive outputs \(\text{O}^{[1-2]}\) of the Pulsar QCM and acquire on inputs \(\text{I}^{[1-2]}\) of the Pulsar QRM.
[7]:
#Upload waveforms and programs to Pulsar QCM.
pulsar_qcm.sequencer0_waveforms_and_program(os.path.join(os.getcwd(), "qcm_sequence.json"))
#Upload waveforms and programs to Pulsar QRM.
pulsar_qrm.sequencer0_waveforms_and_program(os.path.join(os.getcwd(), "qrm_sequence.json"))
Play sequences¶
The sequence has been uploaded to the instruments. Now we need to configure the sequencers of both the Pulsar QCM and Pulsar QRM to use the wait_sync
instruction to synchronize and we need to configure the sequencer of the Pulsar QRM to trigger the acquisition with the acquire
instruction. Furthermore we also need to attenuate the Pulsar QCM’s outputs to 40% to be able to capture the full range of the waveforms on the Pulsar QRM’s inputs.
\(\text{Attenuation}={Input}/{Output}={2V}/{5V}={0.4}\)
[8]:
#Configure the sequencer of the Pulsar QCM.
pulsar_qcm.sequencer0_sync_en(True)
pulsar_qcm.sequencer0_gain_awg_path0(0.35) #Adding a bit of margin to the 0.4
pulsar_qcm.sequencer0_gain_awg_path1(0.35)
#Configure the sequencer of the Pulsar QRM.
pulsar_qrm.sequencer0_sync_en(True)
pulsar_qrm.sequencer0_trigger_mode_acq_path0("sequencer")
pulsar_qrm.sequencer0_trigger_mode_acq_path1("sequencer")
Now let’s start the sequences.
[9]:
#Arm and start sequencer of the Pulsar QCM (only sequencer 0).
pulsar_qcm.arm_sequencer(0)
pulsar_qcm.start_sequencer(0)
#Print status of sequencer of the Pulsar QCM.
print("QCM status:")
print(pulsar_qcm.get_sequencer_state(0))
print()
#Arm and start sequencer of the Pulsar QRM (only sequencer 0).
pulsar_qrm.arm_sequencer(0)
pulsar_qrm.start_sequencer(0)
#Print status of sequencer of the Pulsar QRM.
print("QRM status:")
print(pulsar_qrm.get_sequencer_state(0))
QCM status:
{'status': 'Q1 STOPPED', 'flags': []}
QRM status:
{'status': 'STOPPED', 'flags': ['ACQ WAVE CAPTURE DONE PATH 0', 'ACQ WAVE CAPTURE DONE PATH 1']}
Retrieve acquisition¶
The waveforms have now been sequenced on the outputs and acquired on the inputs by both instruments. And as you might have noticed, timing these operations was simplified significantly by the SYNQ technology. Lets retrieve the resulting data, but first let’s make sure the sequencers have finished.
[10]:
#Wait for the sequencers to stop with a timeout period of one second.
pulsar_qcm.get_sequencer_state(0, 1)
pulsar_qrm.get_sequencer_state(0, 1)
#Wait for the acquisition to finish with a timeout period of one second.
pulsar_qrm.get_acquisition_state(0, 1)
#Move acquisition data from temporary memory to acquisition list.
pulsar_qrm.store_acquisition(0, "measurement")
#Get acquisition list from instrument.
acq = pulsar_qrm.get_acquisitions(0)
Let’s plot the result.
[11]:
#Plot acquired signal on both inputs.
fig, ax = matplotlib.pyplot.subplots(1, 1, figsize=(15, 15/2/1.61))
ax.plot(acq["measurement"]["path_0"]["data"][100:260])
ax.plot(acq["measurement"]["path_1"]["data"][100:260])
ax.set_xlabel('Time (ns)')
ax.set_ylabel('Relative amplitude')
matplotlib.pyplot.show()
Stop¶
Finally, let’s stop the sequencers if they haven’t already and close the instrument connections.
[12]:
#Stop sequencers.
pulsar_qcm.stop_sequencer()
pulsar_qrm.stop_sequencer()
#Print status of sequencers.
print("QCM status:")
print(pulsar_qcm.get_sequencer_state(0))
print()
print("QRM status:")
print(pulsar_qrm.get_sequencer_state(0))
print()
#Print an overview of instrument parameters.
print("QCM snapshot:")
pulsar_qcm.print_readable_snapshot(update=True)
print()
print("QRM snapshot:")
pulsar_qrm.print_readable_snapshot(update=True)
#Close the instrument connections.
pulsar_qcm.close()
pulsar_qrm.close()
QCM status:
{'status': 'STOPPED', 'flags': ['FORCED STOP']}
QRM status:
{'status': 'STOPPED', 'flags': ['FORCED STOP', 'ACQ WAVE CAPTURE DONE PATH 0', 'ACQ WAVE CAPTURE DONE PATH 1']}
QCM snapshot:
qcm:
parameter value
--------------------------------------------------------------------------------
IDN : {'manufacturer': 'Qblox', 'devi...
reference_source : internal
sequencer0_cont_mode_en_awg_path0 : False
sequencer0_cont_mode_en_awg_path1 : False
sequencer0_cont_mode_waveform_idx_awg_path0 : 0
sequencer0_cont_mode_waveform_idx_awg_path1 : 0
sequencer0_gain_awg_path0 : 0.34999
sequencer0_gain_awg_path1 : 0.34999
sequencer0_marker_ovr_en : False
sequencer0_marker_ovr_value : 0
sequencer0_mod_en_awg : False
sequencer0_nco_freq : 0 (Hz)
sequencer0_nco_phase_offs : 0 (Degrees)
sequencer0_offset_awg_path0 : 0
sequencer0_offset_awg_path1 : 0
sequencer0_sync_en : True
sequencer0_upsample_rate_awg_path0 : 0
sequencer0_upsample_rate_awg_path1 : 0
sequencer0_waveforms_and_program : C:\Users\jordy\Projects\pulsar_...
sequencer1_cont_mode_en_awg_path0 : False
sequencer1_cont_mode_en_awg_path1 : False
sequencer1_cont_mode_waveform_idx_awg_path0 : 0
sequencer1_cont_mode_waveform_idx_awg_path1 : 0
sequencer1_gain_awg_path0 : 1
sequencer1_gain_awg_path1 : 1
sequencer1_marker_ovr_en : False
sequencer1_marker_ovr_value : 0
sequencer1_mod_en_awg : False
sequencer1_nco_freq : 0 (Hz)
sequencer1_nco_phase_offs : 0 (Degrees)
sequencer1_offset_awg_path0 : 0
sequencer1_offset_awg_path1 : 0
sequencer1_sync_en : False
sequencer1_upsample_rate_awg_path0 : 0
sequencer1_upsample_rate_awg_path1 : 0
sequencer1_waveforms_and_program : None
QRM snapshot:
qrm:
parameter value
--------------------------------------------------------------------------------
IDN : {'manufacturer': 'Qblox', 'devi...
in0_amp_gain : -6 (dB)
in1_amp_gain : -6 (dB)
reference_source : external
sequencer0_avg_mode_en_acq_path0 : False
sequencer0_avg_mode_en_acq_path1 : False
sequencer0_cont_mode_en_awg_path0 : False
sequencer0_cont_mode_en_awg_path1 : False
sequencer0_cont_mode_waveform_idx_awg_path0 : 0
sequencer0_cont_mode_waveform_idx_awg_path1 : 0
sequencer0_gain_awg_path0 : 1
sequencer0_gain_awg_path1 : 1
sequencer0_marker_ovr_en : False
sequencer0_marker_ovr_value : 0
sequencer0_mod_en_awg : False
sequencer0_nco_freq : 0 (Hz)
sequencer0_nco_phase_offs : 0 (Degrees)
sequencer0_offset_awg_path0 : 0
sequencer0_offset_awg_path1 : 0
sequencer0_sync_en : True
sequencer0_trigger_level_acq_path0 : 0
sequencer0_trigger_level_acq_path1 : 0
sequencer0_trigger_mode_acq_path0 : sequencer
sequencer0_trigger_mode_acq_path1 : sequencer
sequencer0_upsample_rate_awg_path0 : 0
sequencer0_upsample_rate_awg_path1 : 0
sequencer0_waveforms_and_program : C:\Users\jordy\Projects\pulsar_...