See also

An IPython notebook version of this tutorial can be downloaded here:

acquisition.ipynb

Acquisition

In this tutorial we will demonstrate the sequencer based acquisition procedure as well as how to average multiple acquisitions in hardware (see section Acquisition). We will do this by using a Pulsar QRM and directly connect outputs \(\text{O}^{[1-2]}\) to inputs \(\text{I}^{[1-2]}\) respectively. We will then use the Pulsar QRM’s sequencers to sequence waveforms on the outputs and simultaneously acquire the resulting waveforms on the inputs.

Setup

First, we are going to import the required packages and connect to the instrument.

[1]:
#Set up the environment.
import pprint
import os
import scipy.signal
import math
import json
import matplotlib.pyplot
import numpy

from pulsar_qrm.pulsar_qrm import pulsar_qrm

#Connect to the Pulsar QRM at default IP address.
pulsar = pulsar_qrm("qrm", "192.168.0.2", debug=1)

#Reset the instrument for good measure.
pulsar.reset()
print("Status:")
print(pulsar.get_system_status())
Status:
{'status': 'OKAY', 'flags': []}

Generate waveforms

Next, we need to create the waveforms for the sequence.

[2]:
#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.

[3]:
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()

matplotlib.pyplot.draw()
matplotlib.pyplot.show()   # add this at EOF to prevent execution stall
../_images/tutorials_acquisition_6_0.png

Create Q1ASM program

Now that we have the waveforms for the sequence, we need a simple Q1ASM program that sequences and acquires the waveforms.

[4]:
#Sequence program.
seq_prog = """
play    0,1,4     #Play waveforms and wait 4ns.
acquire 0,0,16380 #Acquire waveforms over remaining duration of acquisition.
stop              #Stop.
"""

Upload sequence

Now that we have the waveforms and Q1ASM program, we can combine them in a sequence stored in a JSON file.

[5]:
#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 sequence program and waveforms to single dictionary and write to JSON file.
wave_and_prog_dict = {"waveforms": {"awg": waveforms, "acq": waveforms}, "program": seq_prog}
with open("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, which will drive outputs \(\text{O}^{[1-2]}\) and acquire on inputs \(\text{I}^{[1-2]}\).

[6]:
#Upload waveforms and programs.
pulsar.sequencer0_waveforms_and_program(os.path.join(os.getcwd(), "sequence.json"))

Play sequence

The sequence has been uploaded to the instrument. Now we need to configure the sequencers to trigger the acquisition with the acquire instruction.

[7]:
#Configure the sequencer to trigger the acquisition.
pulsar.sequencer0_trigger_mode_acq_path0("sequencer")
pulsar.sequencer0_trigger_mode_acq_path1("sequencer")

Now let’s start the sequence.

[8]:
#Arm and start sequencer.
pulsar.arm_sequencer()
pulsar.start_sequencer()

#Print status of sequencer.
print("Status:")
print(pulsar.get_sequencer_state(0))
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. Lets make sure that the sequencer has finished it’s acquisition and then retrieve the resulting data. The acquisition data is stored in a temporary memory in the instrument’s FPGA. We need to first move the data from this memory into the into the instrument’s acquisition list. From there we can retrieve it from the instrument.

[9]:
#Wait for the sequencer to stop with a timeout period of one second.
pulsar.get_sequencer_state(0, 1)

#Wait for the acquisition to finish with a timeout period of one second.
pulsar.get_acquisition_state(0, 1)

#Move acquisition data from temporary memory to acquisition list.
pulsar.store_acquisition(0, "measurement")

#Get acquisition list from instrument.
acq = pulsar.get_acquisitions(0)

Let’s plot the result.

[10]:
#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()
../_images/tutorials_acquisition_20_0.png

Retrieve multiple acquisitions

We can also run the sequence multiple times consecutively and store the acquisition data in the instrument’s acquisition list before retrieving them all in one go. To demonstrate this we will run the same sequence three times and vary the output gain for each run to create a clear distinction between the acquisitions.

[11]:
#Clear acquisition list of any previously made acquisitions
pulsar.delete_acquisitions(0)

#First run
pulsar.sequencer0_gain_awg_path0(0.33)
pulsar.sequencer0_gain_awg_path1(0.33)

pulsar.arm_sequencer()
pulsar.start_sequencer()

pulsar.get_sequencer_state(0, 1)
pulsar.get_acquisition_state(0, 1)

pulsar.store_acquisition(0, "measurement_0")

#Second run
pulsar.sequencer0_gain_awg_path0(0.66)
pulsar.sequencer0_gain_awg_path1(0.66)

pulsar.arm_sequencer()
pulsar.start_sequencer()

pulsar.get_sequencer_state(0, 1)
pulsar.get_acquisition_state(0, 1)

pulsar.store_acquisition(0, "measurement_1")

#Second run
pulsar.sequencer0_gain_awg_path0(1)
pulsar.sequencer0_gain_awg_path1(1)

pulsar.arm_sequencer()
pulsar.start_sequencer()

pulsar.get_sequencer_state(0, 1)
pulsar.get_acquisition_state(0, 1)

pulsar.store_acquisition(0, "measurement_2")

#Get acquisition list from instrument.
acq = pulsar.get_acquisitions(0)

Let’s plot the result again.

[12]:
#Plot acquired signals (add acquisition index to separate acquisitions in plot).
fig, ax = matplotlib.pyplot.subplots(1, 1, figsize=(15, 15 / 1.61))
for acq_idx in range(0, 3):
    ax.plot(numpy.array(acq["measurement_{}".format(acq_idx)]["path_0"]["data"][100:260]) + acq_idx)
    ax.plot(numpy.array(acq["measurement_{}".format(acq_idx)]["path_1"]["data"][100:260]) + acq_idx)
    ax.set_xlabel('Time (ns)')
    ax.set_ylabel('Relative amplitude')
matplotlib.pyplot.show()
../_images/tutorials_acquisition_24_0.png

Hardware-based averaging

We can also use hardware in the instrument itself to automatically accumulate acquisition data on-the-fly. This can be used to do averaging, by dividing the final accumulated result by the number of accumulations. To use this feature, we first need to modify the Q1ASM to run the sequence multiple consecutive times.

[13]:
#Sequence program.
seq_prog = """
      move    1000,R0   #Loop iterator.

loop: play    0,1,4     #Play waveforms and wait 4ns.
      acquire 0,0,16380 #Acquire waveforms remaining duration of acquisition.
      loop    R0,@loop  #Run until number of iterations is done.

      stop              #Stop.
"""

Next, we need to program, configure and start the sequencer. This time we will also configure the sequencer to run in averaging mode.

[14]:
#Clear acquisition list of any previously made acquisitions
pulsar.delete_acquisitions(0)

#Add sequence program and waveforms to single dictionary and write to JSON file.
wave_and_prog_dict = {"waveforms": {"awg": waveforms, "acq": waveforms}, "program": seq_prog}
with open("sequence_avg.json", 'w', encoding='utf-8') as file:
    json.dump(wave_and_prog_dict, file, indent=4)
    file.close()

#Upload waveforms and programs.
pulsar.sequencer0_waveforms_and_program(os.path.join(os.getcwd(), "sequence_avg.json"))

#Configure the sequencer to trigger the acquisition.
pulsar.sequencer0_trigger_mode_acq_path0("sequencer")
pulsar.sequencer0_trigger_mode_acq_path1("sequencer")
pulsar.sequencer0_avg_mode_en_acq_path0(True)
pulsar.sequencer0_avg_mode_en_acq_path1(True)

#Arm and start sequencer.
pulsar.arm_sequencer()
pulsar.start_sequencer()

#Wait for sequence and acquisitions to finish.
pulsar.get_sequencer_state(0, 1)
pulsar.get_acquisition_state(0, 1)

#Move accumulated result from temporary memory to the instrument's acquisition list.
pulsar.store_acquisition(0, "measurement_avg")

#Get acquisition list from instrument.
acq = pulsar.get_acquisitions(0)

The sequence has now run and accumulated a 1000 times. Time to finish the averaging process and print the result.

[15]:
#Divide accumulated result by a 1000 and plot results.
fig, ax = matplotlib.pyplot.subplots(1, 1, figsize=(15, 15/2/1.61))
ax.plot(numpy.array(acq["measurement_avg"]["path_0"]["data"][100:260]) / 1000)
ax.plot(numpy.array(acq["measurement_avg"]["path_1"]["data"][100:260]) / 1000)
ax.set_xlabel('Time (ns)')
ax.set_ylabel('Relative amplitude')
matplotlib.pyplot.show()
../_images/tutorials_acquisition_30_0.png

Stop

Finally, let’s stop the sequencers if they haven’t already and close the instrument connection.

[16]:
#Stop sequencer.
pulsar.stop_sequencer()

#Print status of sequencer.
print("Status:")
print(pulsar.get_sequencer_state(0))
print()

#Print an overview of the instrument parameters.
print("Snapshot:")
pulsar.print_readable_snapshot(update=True)

#Close the instrument connection.
pulsar.close()
Status:
{'status': 'STOPPED', 'flags': ['FORCED STOP', 'ACQ WAVE CAPTURE DONE PATH 0', 'ACQ WAVE CAPTURE OVERWRITTEN PATH 0', 'ACQ WAVE CAPTURE DONE PATH 1', 'ACQ WAVE CAPTURE OVERWRITTEN PATH 1']}

Snapshot:
qrm:
        parameter                                  value
--------------------------------------------------------------------------------
IDN                                         :   {'manufacturer': 'Qblox', 'devi...
in0_amp_gain                                :   -6 (dB)
in1_amp_gain                                :   -6 (dB)
reference_source                            :   internal
sequencer0_avg_mode_en_acq_path0            :   True
sequencer0_avg_mode_en_acq_path1            :   True
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                          :   False
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_...