Control Your NV Centers by Combining Zurich Instruments and Swabian Instruments

December 16, 2024 by Emilio Depero

In this blog post, we will show you how to operate the leading solutions for Nitrogen-vacancy (NV) center control and readout, the SHFSG Signal Generator and the Swabian Instruments Time Tagger Ultra, seamlessly integrated with the LabOne Q control software.

Controlling an NV center spin state requires spectrally clean, complex pulsed signals directly at the NV center's frequency--all offered out of the box by the SHFSG with its AWG functionality and double superheterodyne signal generation that allows gapless operation from DC to 8.5 GHz. For readout, the Swabian Instruments Time Tagger Ultra is the choice of many NV center and color center research groups around the world due to its high timing resolution, minimal jitter of 8 ps (RMS), and high data transfer rate of up to 80 million tags per second.

Our experiment control software LabOne Q is designed to control the full measurement setup, offering sample-level timing control and easy integration of third-party instruments on the level of its domain specific language (DSL).

Here we will demonstrate the integration of the time tagger based on a typical element of an NV center characterization, a Ramsey fringe measurement. This demonstration implements all necessary microwave control pulses, TTL pulses for laser control and for synchronizing SHFSG and Time Tagger Ultra, and handling time tagger result data directly in the LabOne Q framework. Every step of this process is detailed in our LabOne Q repository on GitHub, where you can find code examples specifically designed for color centers.

Setting up the SHFSG and the Time Tagger Ultra

First, we will use two channels of the SHFSG to simulate an experiment with a single NV center. We use a microwave signal for driving electron spin transitions labeled drive_line, while the channel designated as apd_line will simulate the output of an avalanche photodiode (APD) commonly used for detecting the spin state in color centers applications. Let's begin by programmatically setting up a device configuration with these specified properties.

setup = DeviceSetup("TimeTagger_setup")

# add a dataserver
setup.add_dataserver(host="localhost", port=8004)

# add an SHFSG
setup.add_instruments(SHFSG(uid="device_shfsg", 
    address=shfsg_addres, device_options="SHFSG8"))

# add connections
setup.add_connections(
    "device_shfsg",
    create_connection(to_signal="q0/drive_line", 
        ports=f"SGCHANNELS/{drive_channel}/OUTPUT"),
    create_connection(to_signal="q0/apd_line", 
        ports=f"SGCHANNELS/{apd_channel}/OUTPUT"),
)

# shortcut to connections
drive_lsg = setup.logical_signal_groups["q0"].logical_signals["drive_line"]
apd_lsg   = setup.logical_signal_groups["q0"].logical_signals["apd_line"]

Next, we apply appropriate modulation settings and other hardware settings to the two defined logical signal lines. For the drive line, we apply modulation suitable for color centers (in this case, a microwave frequency of ~2.8 GHz). Meanwhile, for the apd line, we eliminate modulation and upconversion to generate constant amplitude pulses, similar to the TTL pulses generated by an APD. This calibration ensures the signals are precisely tailored for their respective functions in the experiment.

# simple NV center drive
drive_lsg.calibration      = SignalCalibration()
drive_lsg.local_oscillator = Oscillator("NV_lo_osc", frequency=2.8e9)
drive_lsg.oscillator       = Oscillator("NV_osc",    frequency=-13e6)
drive_lsg.port_mode        = PortMode.RF
drive_lsg.range            = 10 #dBm

# APD simulation
apd_lsg.port_mode        = PortMode.LF
apd_lsg.range            = 10 #dBm
apd_lsg.local_oscillator = Oscillator(uid="apd_lo", frequency=0)
apd_lsg.oscillator       = Oscillator(uid="apd_osc", frequency=0)

Next, we create a Python object representing the Time Tagger Ultra, which allows us to integrate it into our experimental framework. You can find more details on this object and the Python driver in Swabian Instruments' user manual.

address = f'{timetagger_server}:{timetagger_port}'

# Connect to the server
timetagger = createTimeTaggerNetwork(address)

We use one input of the time tagger to count the pulses emitted by the “APD” (actually one of the Signal Outputs of the SHFSG in this case) and another input to establish a gate for when measurements should occur. This configuration enables us to use the markers from our SHFSG to define a time window for measuring the fluorescence of the NV center. We operate the time tagger with zero added delay between trigger input and the start of the counting window, and set an initial estimate for the trigger level. In a subsequent experiment, we will explore how to determine the optimal trigger level.

# for the input connection of the time tagger, 
# we set an initial trigger level and delay
timetagger.setTriggerLevel(counter_input, 0.25)
timetagger.setInputDelay(counter_input, 0)

# for the gate connection of the time tagger, 
# we set an initial trigger level and delay
timetagger.setTriggerLevel(gate_start_input, 0.5)
timetagger.setInputDelay(gate_start_input, 0)

Using the Time Tagger Ultra with LabOne Q

We can now demonstrate the time tagger integration in LabOne Q by performing a sweep of its input trigger level. Following the guidelines provided in the LabOne Q manual for Near-Time Callbacks, we define a function that sets the trigger level for the specific channel and register it in our session.

# function to set timetagger at a specific level
def settimetagger(session : Session,
                    trigger_level,
                  ):
    timetagger.setTriggerLevel(counter_input, trigger_level)
 
# register it to session
session.register_neartime_callback(settimetagger, "trigger_sweep")

Next, let's set up a LabOne Q Experiment that sends a fixed number of pulses to the time tagger. We have the flexibility to adjust parameters such as delay between pulses, amplitude, and pulse shape, to closely mimic the signal of our APD. By employing a sweep and a call instruction within the LabOne Q DSL, we can repeat the experiment across various trigger values. To test whether the time tagger operates as we intend, we connect the second output of the SHFSG to one of the counter inputs to simulate the APD output, while using the marker of the same channel to provide the gate.

Setup with time tagger

Setup used in this Section to test the time tagger

def simulatepulses(
    pulse_count : int,
    timetagger_trigger_sweep,
    click_up_length   = 13e-9,
    click_down_length = 10e-9,    
    click_amplitude   = 1.,
):
    exp = Experiment(
        uid     = "testtrigger",
        signals = [ExperimentSignal("apd", map_to=apd_lsg)],
    )
       
    # define what a click is
    click = pulse_library.const("click", length=click_up_length, 
                 amplitude=click_amplitude)
                 
    # call trigger level settings inside the sweep
    with exp.sweep(parameter=timetagger_trigger_sweep):
        exp.call("trigger_sweep", trigger_level=timetagger_trigger_sweep)
        with exp.acquire_loop_rt(uid="counts",
                                    count=1,
                                    ):
                                    
            # send fixed number of pulses
            with exp.section(uid="readout",
                                trigger={"apd" : {"state" : True}},
                                ):
                for i in range(pulse_count):
                    exp.delay(signal="apd", time=click_down_length)
                    exp.play(signal="apd", pulse=click)
    return exp

Using the function outlined above, we now design an experiment that transmits 100 pulses at maximum power to the time tagger across 10 different trigger levels, ranging from 100 mV to 1 V. This setup allows us to assess the response of the system across a spectrum of trigger sensitivities.

The numeric values used for this experiment are just for illustrative purpose. While the length of the pulse used to simulate an APD is in the range of the ones commercially available, the power output of an SG channel is lower than a typical APD TTL pulse. To accurate estimate the trigger level, it is always best to use as reference the APD of your setup.

# define a sweep with trigger levels
trigger_levels = LinearSweepParameter(uid="tlevel", start=0.1, stop=1, 
    count=30, axis_name="trigger level [V]")
    
# create an experiment to perform the calibration
timetagger_calibration_experiment = simulatepulses(100, trigger_levels)

# compile experiment 
cexp = session.compile(timetagger_calibration_experiment)

Next, we configure the time tagger to run a measurement. Swabian Instruments provides a comprehensive set of classes to adapt the Time Tagger Ultra to different purposes. In this example, we will count the number of pulses received within each gate using the CountBetweenMarkers class. As an input, we provide the number of measurements the time tagger should expect, which corresponds to the number of gates we will send in our LabOne Q experiment. Here, this value matches the sweep dimension, as we are not performing any high-level averaging. Once the measurement setup is complete, we can proceed with the experiment, and the measurement results will be stored within this class.

# prepare counter based on the dimension of the sweep
counter = CountBetweenMarkers(tagger=timetagger,
                            click_channel=counter_input,
                            begin_channel=gate_start_input,
                            end_channel=gate_end_input,
                            n_values=trigger_levels.count,
                            )
# run the experiment
result = session.run(cexp)    

The code below allows us to plot our measurement data once the experiment is completed. We can see a sharp drop of the detected counts from 100 to 0 at a trigger level of around 0.3 V. Choosing a trigger level lower than that thus allows us to count all pulses reliably.

# take the x-axis values from the sweep parameter
x = trigger_levels.values

# take the result connected to each sweep from the measurement 
# class of the time tagger
y = counter.getData()

# plotting the two using matplotlib
plt.plot(x, y, 'o-')
plt.title("calibration results of time tagger")
plt.xlabel(trigger_levels.axis_name)
plt.ylabel("detected counts")
measurement results of the time tagger trigger level calibration

With this approach, all parameters available in the Time Tagger Ultra API can be swept using the LabOne Q DSL, providing significant flexibility for both experimentation and automatic tuning in the laboratory. If you’d like to learn more, check out our Github example which demonstrates these capabilities further. For a more general introduction, consider also browsing the manuals of the LabOne Q Applications Library, which covers all qubit technologies in more detail.

NV center characterization using a Time Tagger together with LabOne Q

Having covered the basics, we can now explore how to use the time tagger in a experiment involving a NV center. We will assume a setup and connections like the one illustrated in the picture below, which is a typical setup for a single NV center. For simplicity, we will assume that there is only a single green laser in use. This means only channel 1 and channel 2 from the picture below will be used.

Setup for NV centers

Setup for NV centers control using a time tagger

Ideally, we aim to simplify the definition of the different LabOne Q experiments as much as possible, allowing researchers to concentrate primarily on designing the sequence. In a typical sequence involving color centers, a trigger is sent to an Acousto-Optic Modulator (AOM) to reset the qubit to its ground state. Subsequent operations are then performed on the qubit, followed by reactivating the laser in tandem with our measurement device to capture the system's fluorescence. Since two of these operations remain consistent across experiments, we can predefine two LabOne Q sections to efficiently handle these repetitive tasks:

# AOM, activate laser
AOM = Section(uid="AOM")
AOM.play(signal="AOM", pulse=None, 
         marker={"marker1" : {"start" : 0, "length" : AOM_pulse_length}})
AOM.reserve(signal="drive")

# Readout, activate laser and the timetagger
Readout = Section(
    uid="readout",    
)
Readout.play(signal="AOM",   pulse=None, 
    marker={"marker1" : {"start" : 0, "length" : Trigger_Pulse_length}})
Readout.play(signal="drive", pulse=None, 
    marker={"marker1" : {"start" : 0, "length" : Trigger_Pulse_length}})

Notice that above we started using Markers instead of Triggers for the sections in order to achieve a sample-precise TTL signal. To explore more the difference between these two type of signals, see the section on Markers and Triggers in the LabOne Q manual.

For instance, let's perform a Ramsey sequence in LabOne Q. The sequence is now highly streamlined, requiring only the addition of a Readout section to activate the gate with proper timing.

ramsey_exp = Experiment(uid="ramsey", 
    signals=[ExperimentSignal("drive", map_to=drive_lsg), 
    ExperimentSignal("AOM", map_to=aom_lsg)])
    
ramsey_sweep = LinearSweepParameter(start=0, stop=5e-6, 
    count=11, axis_name="Delay [s]")
    
with ramsey_exp.acquire_loop_rt(count=1000, 
    repetition_mode=RepetitionMode.CONSTANT, 
    repetition_time=repetition_time):
    with ramsey_exp.sweep(parameter=ramsey_sweep) as delay:
    
        # shine laser
        ramsey_exp.add(AOM)
        
        # qubit manipulation
        with ramsey_exp.section(uid="manipulation"):
            ramsey_exp.play(signal="drive", pulse=pi_half_pulse)
            ramsey_exp.delay(signal="drive", time=delay)
            ramsey_exp.play(signal="drive", pulse=pi_half_pulse)
            
        # readout
        ramsey_exp.add(Readout)

Finally, we can automate the configuration of the time tagger to seamlessly integrate with the planned measurement. For most sequences, the number of measurements is equal to the number of sweep steps multiplied by the counts in the averaging loop. This information is stored in the LabOne Q experiment and can be used to automatically set the time tagger. This makes running a LabOne Q experiment with a time tagger as simple as invoking a single function!  Here, this is done using the run_with_counter, you can check its implementation here. This command ensures that the results are conveniently stored within the LabOne Q Result object, facilitating easy access from a single point and supporting all of LabOne Q's averaging modes. Additionally, the Counter object is returned, providing continuous access to the raw data for further analysis.

ramsey_result, ramsey_counter = run_with_counter(session, ramsey_exp)

To visualize the timing of the experiments, LabOne Q helps with a rich set of tools. Below you can see the output of the Ramsey experiment in our Pulse Sheet Viewer and the zoom over a single shot to show to check the timing between pulses. We used a lower repetition time than in a real experiment to make it easier to visualize the complete sweep. Notice the letter M present over the box of the two pulses in the readout section, this means that a trigger signal is issued during that time.

Overview of a Ramsey Experiment using the Pulse Sheet Viewer

Overview of a Ramsey Experiment using the Pulse Sheet Viewer

Zoom over a single shot of the Ramsey Experiment

Zoom over a single shot of the Ramsey Experiment

Summary

In this demo, we have shown how to integrate the SHFSG Signal Generator and the Time Tagger Ultra to control a Nitrogen-Vacancy center and perform measurements using LabOne Q.

We illustrated the simplicity and efficiency in automating experimental procedures, by incorporating predefined sections for common tasks like spin manipulation and spin state readout. This framework not only streamlined operations but also opened the door for broader exploration and optimization with minimal manual intervention, allowing researchers to focus on experiment design and analysis.

Going forward, the methods and scripts demonstrated herein provide a robust foundation for further exploration of NV centers and similar quantum systems. By enhancing automatic tuning and flexible setup configurations, LabOne Q and the Time Tagger Ultra present a valuable synergy for advancing quantum research. In our example on Github more functionalities are shown, on top of the implementation of most common NV centers experiment using the same framework. We encourage users to experiment further with our methods for a deeper understanding and utilization of these tools in their own research endeavors.