Pulsar QRM¶
In this demo we showcase the upload, play and digitization of a waveform using the Qblox Pulsar QRM.
NB: This hardware demo interacts with firmware low-level interfaces. When building applications, we highly recommend using Quantify framework.
Physical setup¶
In this demo we use a single QRM and we connect its outputs to its own inputs as follows using two SMA cables
\(\text{O}^1 \rightarrow \text{I}^1\)
\(\text{O}^2 \rightarrow \text{I}^2\)
# Import python utilities
import os
import scipy.signal
import math
import json
import matplotlib.pyplot as plt
Connect to the QRM¶
# Add Pulsar QRM interface
from pulsar_qrm.pulsar_qrm import pulsar_qrm
# Connect to device over Ethernet
pulsar = pulsar_qrm("qrm", "192.168.0.3")
Tip
If you need to re-instantiate the instrument without re-starting
the Jupyter Notebook (or the ipython shell) run the following command first pulsar.close()
. This closes the QCoDeS instrument instance.
# Print device information
print(pulsar.get_idn())
# Get system status
print(pulsar.get_system_status())
Generate waveforms for QRM¶
# Generate waveforms
waveform_len = 1000 # ns
waveforms = {
"gaussian": {"data": [], "index": 0},
"sine": {"data": [], "index": 1},
"sawtooth": {"data": [], "index": 2},
"block": {"data": [], "index": 3}
}
#Create gaussian waveform
if "gaussian" in waveforms:
waveforms["gaussian"]["data"] = scipy.signal.gaussian(waveform_len, std=0.133*waveform_len)
#Create gaussian waveform
if "sine" in waveforms:
waveforms["sine"]["data"] = [math.sin((2*math.pi/waveform_len)*i) for i in range(0, waveform_len)]
#Create sawtooth waveform
if "sawtooth" in waveforms:
waveforms["sawtooth"]["data"] = [(1.0 / (waveform_len)) * i for i in range(0, waveform_len)]
#Create block waveform
if "block" in waveforms:
waveforms["block"]["data"] = [1.0 for i in range(0, waveform_len)]
import matplotlib.pyplot as plt
import numpy as np
time = np.arange(0, max(map(lambda d: len(d["data"]), waveforms.values())), 1)
fig, ax = plt.subplots(1,1)
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"])
Upload program and waveforms into QRM¶
In order to play the waveforms and digitize the input the firmware needs to receive the waveforms and an assembly-like program.
The program for this demo is somewhat elaborated and we will not examine it in detail. It is intended to be generated by a higher-level compiler.
# Sequencer programs
seq_prog = (""
" wait_sync 4 #Wait for synchronization\n"
"start: move 4,R0 #Init number of waveforms\n"
" move 0,R1 #Init waveform index\n"
""
"mult_wave_loop: move 166,R2 #Init number of singe wave loops (increasing wait)\n"
" move 166,R3 #Init number of singe wave loops (decreasing wait)\n"
" move 24,R4 #Init number of dynamic wait time (total 4us)\n"
" move 3976,R5 #Init number of dynamic wait time\n"
" move 32768,R6 #Init gain\n"
""
"sngl_wave_loop_0: move 800,R7 #Init number of long delay cycles\n"
" set_mrk 15 #Set marker to 0xF\n"
" upd_param 4 #Update all parameters and wait 4ns\n"
" set_mrk 0 #Set marker to 0\n"
" upd_param 96 #Update all parameters and wait 96ns\n"
""
" wait R4 #Dynamic wait\n"
" add R4,24,R4 #Increase wait\n"
""
" set_mrk 1 #Set marker to 1\n"
" play R1,R1,4 #Play waveform and wait 992ns\n"
" acquire R1,R1,992 #Acquire while playing\n"
" set_mrk 0 #Set marker to 0\n"
" upd_param 4 #Update all parameters and wait for 4ns\n"
""
" wait R5 #Compensate previous dynamic wait\n"
" sub R5,24,R5 #Decrease wait\n"
""
" sub R6,98,R6 #Decrease gain\n"
" nop\n"
" set_awg_gain R6,R6 #Set gain\n"
""
"long_wait_loop_0: wait 50000 #Wait 50 us\n"
" loop R7,@long_wait_loop_0 #Wait number of long delay cycles\n"
" loop R2,@sngl_wave_loop_0 #Repeat single wave\n"
""
"sngl_wave_loop_1: move 800,R7 #Init number of long delay cycles\n"
" set_mrk 15 #Set marker to 0xF\n"
" upd_param 8 #Update all parameters and wait 8ns\n"
" set_mrk 0 #Set marker to 0\n"
" upd_param 92 #Update all parameters and wait 92ns\n"
""
" wait R4 #Dynamic wait\n"
" sub R4,24,R4 #Decrease wait\n"
""
" set_mrk 1 #Set marker to 1\n"
" play R1,R1,4 #Play waveform and wait 992ns\n"
" acquire R1,R1,992 #Acquire while playing\n"
" set_mrk 0 #Set marker to 0\n"
" upd_param 4 #Update all parameters and wait 4ns\n"
""
" wait R5 #Compensate previous dynamic wait\n"
" add R5,24,R5 #Increase wait\n"
""
" sub R6,98,R6 #Decrease gain\n"
" nop\n"
" set_awg_gain R6,R6 #Set gain\n"
""
"long_wait_loop_1: wait 50000 #Wait for 50 us\n"
" loop R7,@long_wait_loop_1 #Wait number of long delay cycles\n"
" loop R3,@sngl_wave_loop_1 #Repeat single wave\n"
""
" add R1,1,R1 #Adjust waveform index\n"
" loop R0,@mult_wave_loop #Move to next waveform\n"
" jmp @start #Jump back to start\n")
# Write waveforms and programs to JSON files
for name in waveforms:
if str(type(waveforms[name]["data"]).__name__) == "ndarray":
waveforms[name]["data"] = waveforms[name]["data"].tolist()
wave_and_prog_dict = {"waveforms": {"awg": waveforms,
"acq": waveforms},
"program": seq_prog}
seq = 0
with open("demo_seq{}.json".format(seq), 'w', encoding='utf-8') as file:
json.dump(wave_and_prog_dict, file, indent=4)
file.close()
# Upload waveforms and programs
pulsar.set("sequencer{}_waveforms_and_program".format(seq),
os.path.join(os.getcwd(), "demo_seq{}.json".format(seq)))
print(pulsar.get_assembler_log())
Configure the QRM¶
# Configure the sequencers
pulsar.set("sequencer{}_sync_en".format(seq), True) # Enable the sequecer
pulsar.set("sequencer{}_cont_mode_en_awg_path0".format(seq), False) # Disable continuous waveform repetition on O1
pulsar.set("sequencer{}_cont_mode_en_awg_path1".format(seq), False) # Disable continuous waveform repetition on O2
pulsar.set("sequencer{}_gain_awg_path0".format(seq), 1.0) # Set unitary gain on O1
pulsar.set("sequencer{}_gain_awg_path1".format(seq), 1.0) # Set unitary gain on O2
pulsar.set("sequencer{}_offset_awg_path0".format(seq), 0) # No offset on O1
pulsar.set("sequencer{}_offset_awg_path1".format(seq), 0) # No offset on O1
pulsar.set("sequencer{}_mod_en_awg".format(seq), True) # Enable modulation
pulsar.set("sequencer{}_nco_freq".format(seq), 10e6) # Set modulation frequency
pulsar.set("sequencer{}_nco_phase".format(seq), 0) # Set modulation phase
pulsar.set("sequencer{}_trigger_mode_acq_path0".format(seq), False) # Acquisition will be triggered "manually" in this notebook
pulsar.set("sequencer{}_trigger_mode_acq_path1".format(seq), False) # Acquisition will be triggered "manually" in this notebook
Play the modulated waveforms and digitize the inputs¶
# Arm the sequencer
pulsar.arm_sequencer()
# Start the sequencers
pulsar.start_sequencer()
print(pulsar.get_sequencer_state(seq))
# Stop the sequencers
pulsar.stop_sequencer()
print(pulsar.get_sequencer_state(seq))
Retrieve acquired data¶
The data acquired above was stored in a temporary memory (FPGA memory). This memory is fast but at the cost of being relatively small in size. Its contents are overwritten (automatically) on each new acquisition!
Because of this there is an intermediate memory (CPU RAM of the Pulsar) to which the acquisitions are stored and later retrieved into the host PC for offline analysis. Note that it is possible to copy many acquisitions into the intermediate memory before retrieving them all.
# Get acquisition
# Deletes any previous data stored in the intermediate memory (CPU RAM of the Pulsar)
pulsar.delete_acquisitions(seq)
# Copies last aquired data from the FPGA memory into the intermediate memory
# NB: The FPGA memory is only accessible when the sequencer is stopped
pulsar.store_acquisition(seq, "meas_0", 1200) # Copy only the first 1200 samples, if not specified will dump full FPGA memory
# Retriev aquired data from the intermediate memory of the Pulsar into the host PC
acq = pulsar.get_acquisitions(seq)
# Plot aquired signal on both inputs
fig, ax = plt.subplots(1, 1, figsize=(15, 15/2/1.61))
ax.plot(acq["meas_0"]["path_0"]["data"])
ax.set_xlabel('Time (ns)')
ax.set_ylabel('Relative amplitude')
ax.plot(acq["meas_0"]["path_1"]["data"])
plt.show()
The digitized signal values are relative to the ADC reference, see datasheet for details.
Above we showcase the capabilities of outputting and digitizing I and Q signals with a Gaussian envelope.
Retrieving multiple acquisitions at once¶
Below we showcase the retrieving of multiple measurements results at once. We also exemplify how distinct parts of the FPGA memory can be stored.
pulsar.delete_acquisitions(seq) # Clear intermediate memory
# Run and store first measurement
pulsar.set("sequencer{}_gain_awg_path0".format(seq), 0.25)
pulsar.set("sequencer{}_gain_awg_path1".format(seq), 0.25)
pulsar.arm_sequencer()
pulsar.start_sequencer()
print(pulsar.get_sequencer_state(seq))
pulsar.stop_sequencer()
pulsar.store_acquisition(seq, "meas_0", 800)
# Run and store second measurement
pulsar.set("sequencer{}_gain_awg_path0".format(seq), 0.5)
pulsar.set("sequencer{}_gain_awg_path1".format(seq), 0.3)
pulsar.arm_sequencer()
pulsar.start_sequencer()
print(pulsar.get_sequencer_state(seq))
pulsar.stop_sequencer()
pulsar.store_acquisition(seq, "meas_1", 900)
# Run and store third measurement
pulsar.set("sequencer{}_gain_awg_path0".format(seq), 1.0)
pulsar.set("sequencer{}_gain_awg_path1".format(seq), 1.0)
pulsar.arm_sequencer()
pulsar.start_sequencer()
print(pulsar.get_sequencer_state(seq))
pulsar.stop_sequencer()
pulsar.store_acquisition(seq, "meas_2") # Store full FPGA memory
# Retriev aquired data from the intermediate memory of the Pulsar into the host PC
acq = pulsar.get_acquisitions(seq)
fig, ax = plt.subplots(1, 1, figsize=(15, 15 / 1.61))
for msmt, plt_offset in zip(["meas_0", "meas_1", "meas_2"], [0, 1, 2]):
ax.plot(np.array(acq[msmt]["path_0"]["data"]) + plt_offset,
label="{} I".format(msmt)) # Plot I quadrature
ax.plot(np.array(acq[msmt]["path_1"]["data"]) + plt_offset,
label="{} q".format(msmt)) # Plot Q quadrature
ax.set_xlabel('Time (ns)')
ax.set_ylabel('Relative amplitude')
# Plot only relevant region
ax.set_xlim(0, 1300)
Since we are using a QCoDeS interface for our instrument we can retrieve an overview of its parameters:
# Print the instrument snapshot
# See QCoDeS documentation for details
pulsar.print_readable_snapshot(update=True)