The OPX+ uses memory in a completely different way from your garden-variety AWG, and we should first understand how so as not to compare apples to oranges.
Consider how you would play a Ramsey sequence from an AWG with a 1 GSPS sampling rate. This involves uploading a waveform long enough to contain the two excitation pulses as well as the delay in between. If each pulse is 20 ns long, and the delay between them is 1000 ns, then at a sampling rate of 1 GSPS this waveform would consist of 1040 samples.
The OPX+ pulse processor operates in a completely different manner. Firstly, only the pulse amplitude is stored in the memory; upconversion to the intermediate frequency happens in real time. A pulse of constant amplitude and arbitrary length is thus generated from a single sample!
For the Ramsey sequence we might want the 20 ns pulse to have a Gaussian envelope, and accordingly use 20 samples of waveform memory. The QUA program to run a Ramsey sequence would look like this:
play(“Gaussian”,”Qubit”)
wait(t_delay)
play(“Gaussian”,”Qubit”)
The OPX+ uses the same waveform memory to play the Gaussian twice, and can just as easily play it a thousand times — with the same 20 samples! In fact, it can dynamically change the Gaussian amplitude, or stretch the Gaussian for a pulse duration longer than 20 ns — whether pre-programmed or in a real-time response to measurement — without using additional memory.
What about the wait(t_delay)
command? An AWG requires a long sequence of zeros to space the pulses, and since characterization of high-coherence devices require long delay times, memory limitations can be prohibitive. But in the OPX+ the wait()
command does not use any waveform memory!
A full Ramsey experiment, including a measurement operation followed by a wait()
command to allow the qubit to return to the ground state, would look like this:
with for_each_(t_delay, t_values):
play(“Gaussian”,”Qubit”)
wait(t_delay)
play(“Gaussian”,”Qubit”)
measure(“Readout”,”Qubit”,...)
wait(reset)
The array t_values
over which we are looping for t_delay
can contain a million values, and the variables t_delay
and reset
can have values of seconds — and the entire experiment will still exploit the same 20 samples of waveform memory.
Now that we understand how powerfully and intelligently the OPX+ utilizes its memory, we can give a short answer: Each OPX+ channel has a waveform memory of 2^16 = 65,536 samples. This sounds small for an AWG, but is huge for a pulse processor!
Well, there’s the long and the short of it. So let’s start with the short:
Why: Because we’ve built it to operate as quickly, efficiently, and easily with the Pulse Processor, giving you an adaptive, intuitive, and fully controllable way to probe your qubits.
How: Surprisingly easily!
Now, let’s get into the heart of the matter:
As physicists, we understand the pain of having to learn yet another programming language, but writing FPGA and low-level code every time you want to run a Ramsey measurement? That’s a worse kind of pain. How many lines of code do you need to painstakingly write in order to run the experiments of your dreams? Probably more than you care to admit. This is where QUA comes in. QUA is a pulse-level quantum coding language that allows quantum researchers to run any experiment, on any type of qubits, quickly and easily. That Ramsey pulse, for instance, can be written, sent, and measured in just 10 lines of QUA code. But more on that in a minute.
When designing QUA, we set out to find the easiest way to program our pulses and send them to the qubit. We wanted to create a direct line of communication with our FPGA-based Pulse Processor in the OPX+, allowing us to have complete control of everything we might want. This quantum language was built by quantum physicists for quantum physicists, all with the purpose of making your quantum experiments as seamless as can be.
You write QUA the way you would explain it to other physicists in your group. You play this pulse to that element, listen on this measurement channel, demodulate that result.
As for how the language actually works, there are three main steps: define a pulse sequence, play that pulse sequence, and measure the readout of that pulse sequence.
The piece-de-resistance is that the compiler does all of the hard work: translating the pulses to FPGA commands. In other words, you need to program only on the high-level pulse programming language QUA, which is translated into the low-level FPGA. Programming in such a way is very useful; imagine you want to generate parametric Gaussian wavefunctions. With an AWG, you would need to upload a bunch of Gaussian profiles and figure out the timing in between. With QUA, you can set a for loop, which loops over various Gaussian amplitudes, sequentially. Turning your experiment into instructions that the FPGA can understand and run is already taken care of.
Feel free to check out this blog post with more info on QUA and the OPX+. And here is a quick guide covering the essentials of QUA.
Do feel free to reach out to us if you would like any more insight or information on how we can help you perform your experiments.
Yes, and much more! Time tagging and TTL counting are key elements of many quantum computing platforms (e.g., defect-center and AMO) and can be performed with the OPX/OPX+ with ease. Signals coming out of single-photon counting modules (SPCMs, or similar) can be directly connected to the OPX/OPX+ inputs, and tagging/counting is then performed natively within the Pulse Processing Unit. OPX/OPX+ users employ this technique for a great deal of different applications, such as optical quantum sensing, communication, and quantum information processing. As such, counting & tagging are key components of the Quantum Orchestration Platform (QOP).
The OPX+ standard operational mode time-tags events with 1 ns timing resolution with 1 ns dead time, for each of the analog input channels. Additionally, a high-resolution time tagging mode is available, pushing the resolution down to a few tens of picoseconds (~50ps) with increased (<100ns) deadtime.
A time-tag is generated when a voltage trigger edge is detected at one of the analog inputs. The trigger edge can be defined in configuration and can be a simple threshold or an arbitrarily complicated dynamic multi-threshold, polarity, and derivative check. This allows you to easily implement complicated sequences spending virtually no time in setting up your time tagging configuration. Then, the time tagging is done easily within a measure command in a single line:
times = declare(int, size=10)
counts = declare(int)
measure([pulse],[element],[stream], time_tagging.analog([times],[duration],[counts])
This approach is universal and is fully embedded in the real-time logic of the QOP. Therefore, a time tagging command will allow for results to be used in real-time during an experiment, e.g. setting dynamic thresholds, performing estimations on the fly, or for conditional triggers. This could mean sending out a trigger pulse to a laser only if and when a signal is time-tagged and recognized as satisfying a certain threshold. It could also mean performing Bayesian estimation on a vector of tags, updating it while new tags come in. This is done with the smallest latency possible (on the order of ~100 ns for the simplest case), as all computation, tagging, and decision making is done in real-time on the FPGA-based pulse processor.
The ability to write complex sequences with only a few lines of code while retaining the full performance of the FPGA ensures ease of use and the fastest time to result. Coding a dynamic Bayesian estimation protocol on a real experimental setup just became a first-year programming exercise.
In the QOP framework, time tagging is one of many tools that can be used for real-time branching, computation, and control. All of the analog inputs of the OPX+ allow for flexible and independent time tagging capabilities, while our next-generation product, available soon, will offer many more inexpensive digital input channels, to be used for the experiments with many digital signals involved.
Easily! Analog feedback is a powerful tool for quantum experiments. Potential applications include correcting a drifting magnetic flux bias in real-time to stabilize a qubit frequency, tuning a pulse’s frequency in real-time to stay resonant with (or detuned by a constant amount from) a dynamically changing transition, or reversing partial wavefunction collapse induced by a weak quantum measurement.
Implementing analog feedback with the OPX+ is straightforward, and is accomplished with the following code snippet inserted in a QUA program:
for n in range(0, 20):
measure(“Readout”, ”Qubit”, None, demod.full(“cos”, I), demod.full(“sin”, Q))
a = FeedbackAmplitude(I,Q)
play(“Pulse”*amp(a), “Qubit”)
The program outputs a measurement pulse “Readout”
to the “Qubit”
element and demodulates the transmitted signal to produce the I
and Q
quadratures. A variable a
is calculated from these quantities through an arbitrary user-defined function FeedbackAmplitude(I,Q)
, which then sets the amplitude of the feedback signal. This control sequence runs continuously, as we can see with the following figure produced by our hardware simulator:
The upper plot shows the quadratures output by the OPX+ for the continuous measurement signal, which after transmission through the sample is demodulated for measurement at 100 ns slices (a parameter set by the user). A real-time calculation then produces a correction to the pulse amplitude.
Correcting the pulse frequency is just as easy with the update_frequency()
command:
for n in range(0, 20):
measure(“Readout”, ”Qubit”, None, demod.full(“cos”, I), demod.full(“sin”, Q))
f = FeedbackFrequency(I,Q)
update_frequency(f, “Qubit”)
With this program the OPX+ outputs the following:
The OPX+ uses memory in a completely different way from your garden-variety AWG, and we should first understand how so as not to compare apples to oranges.
Consider how you would play a Ramsey sequence from an AWG with a 1 GSPS sampling rate. This involves uploading a waveform long enough to contain the two excitation pulses as well as the delay in between. If each pulse is 20 ns long, and the delay between them is 1000 ns, then at a sampling rate of 1 GSPS this waveform would consist of 1040 samples.
The OPX+ pulse processor operates in a completely different manner. Firstly, only the pulse amplitude is stored in the memory; upconversion to the intermediate frequency happens in real time. A pulse of constant amplitude and arbitrary length is thus generated from a single sample!
For the Ramsey sequence we might want the 20 ns pulse to have a Gaussian envelope, and accordingly use 20 samples of waveform memory. The QUA program to run a Ramsey sequence would look like this:
play(“Gaussian”,”Qubit”)
wait(t_delay)
play(“Gaussian”,”Qubit”)
The OPX+ uses the same waveform memory to play the Gaussian twice, and can just as easily play it a thousand times — with the same 20 samples! In fact, it can dynamically change the Gaussian amplitude, or stretch the Gaussian for a pulse duration longer than 20 ns — whether pre-programmed or in a real-time response to measurement — without using additional memory.
What about the wait(t_delay)
command? An AWG requires a long sequence of zeros to space the pulses, and since characterization of high-coherence devices require long delay times, memory limitations can be prohibitive. But in the OPX+ the wait()
command does not use any waveform memory!
A full Ramsey experiment, including a measurement operation followed by a wait()
command to allow the qubit to return to the ground state, would look like this:
with for_each_(t_delay, t_values):
play(“Gaussian”,”Qubit”)
wait(t_delay)
play(“Gaussian”,”Qubit”)
measure(“Readout”,”Qubit”,...)
wait(reset)
The array t_values
over which we are looping for t_delay
can contain a million values, and the variables t_delay
and reset
can have values of seconds — and the entire experiment will still exploit the same 20 samples of waveform memory.
Now that we understand how powerfully and intelligently the OPX+ utilizes its memory, we can give a short answer: Each OPX+ channel has a waveform memory of 2^16 = 65,536 samples. This sounds small for an AWG, but is huge for a pulse processor!
The Quantum Orchestration Platform is a whole new paradigm for quantum control and is fundamentally different from general-purpose test equipment like AWGs, lock-ins, digitizers, etc. The main differences are:
1) The span of quantum experiments & algorithms which can be run out-of-the-box
We like to think of the span of experiments & algorithms which a system can run as the subspace of the experimental phase-space that it covers. While AWGs, Lock-ins, digitizers cover specific points or small regions in this phase space, the quantum orchestration platform covers it entirely. In other words, each general-purpose test tool, even if it is re-branded as a quantum controller, has a fixed set of allowable functions. The Quantum Orchestration Platform (QOP) however, is a full-stack system allowing you to easily and quickly run even your dream experiments and real-time sequences out-of-the-box, from a high-level programming language, QUA. In most cases, each test and measure tool can be expressed and implemented as a single QUA program that can run on the QOP. Alternatively, each such instrument can be described by omitting a different subspace of the full QOP’s phase-space.
2) The pace of the research and development
Every once in a while you have a new brilliant idea for an experiment. While these ideas are more groundbreaking, they are also more challenging and end up being outside the scope of your general-purpose test equipment (its subspace). Once this happens, you have 3 choices:
In experimental physics, there are many bottlenecks. Long fabrication processes, mirrors alignment (and re-alignment!), helium leakages, vacuum-chamber baking, lead times of crucial equipment, and last but not least: in-house development of quantum control capabilities. Specifically, in quantum computing, the control layer can either be an enabler to progress rapidly and run even the most complex experiments seamlessly or be one of the leading bottlenecks in the lab. Our mission is to allow all teams to run even the wildest experiments of their dreams seamlessly and push the boundaries of the physics they can explore to a whole new level.
3) The level of adequacy for the specific specs & capabilities required for quantum research & development
The general-purpose equipment available today was not built for quantum. In the best-case scenario, it was rebranded. AWGs, lock-ins, and digitizers are used for communication systems, lidars, medical device research, and the list goes on. Of course, we don’t mind non-quantum-experimentalists using the same machines, but this has several consequences. First, these machines are limited in the feature-set they provide. They are also misaligned with the requirements of quantum computing by not supplying you with the critical features you require. And finally, they equip you with quite a few features you simply don’t need (that you’re still paying for). The QOP full-stack quantum control hardware and software and all of its features was created by quantum physicists for quantum physicists, with your experimental needs in mind.
As physicists, we always like to ask the more fundamental questions, even when at first glance they seem trivial. In order to answer “what is the pulse processor,” it is useful to first answer the trivial question “what is a quantum experiment?”This is because the pulse processor was architected from the ground-up to run even the most complex quantum experiments one could think of. Now, let’s break-down a quantum experiment to four main components:
Every quantum experiment (or protocol) is a combination of these four elements. Every quantum protocol is an entangled sequence of gates, measurements, and classical processing, all combined in various ways and wrapped with various control-flow statements. And someone has to orchestrate all that!
The Pulse Processor is a processor architected to run sequences that combine all the above in real-time, in a perfectly synchronized and orchestrated way. That includes:
And above all, these four elements are NOT to be regarded as independent. Quantum protocols are an interacting system, where waveform generation leads to waveform acquisition, followed by classical processing which then affects the following generated pulses. And many such threads running in parallel, and affecting each other as well.
To enable such performance, the pulse processor is built in a multi-core architecture containing several pulsers. Each pulser is an independent real-time core capable of driving one or more quantum elements (qubits, collective modes, two-/multi-level transitions, resonators, etc.). Every pulser is essentially a specialized processing unit that may simultaneously handle both waveform generation, waveform acquisition, and all the real-time calculations (classical processing) required (it is Turing complete!) in a deterministic manner and with ultra-low latency.
The pulse-processor orchestrates all the waveform generation, waveform acquisition, classical processing, and control flow in real-time. But what is its API?
The API for the pulse processor is QUA: a powerful yet intuitive quantum programming language. In QUA you can formulate any protocol/experiment – from spectroscopy to quantum-error-correction. Once the program is formulated it is compiled by the XQP compiler to the assembly language of the pulse processor. Next, the program, now formulated in the pulse processor’s assembly language, is sent to the pulse processor which runs it in real-time.
Using the intuitive QUA language and our compiler, you can now directly and intuitively code complex sequences from a high-level programming language, including real-time feedback, classical calculations (Turing-complete), comprehensive control flow, etc.
The OPX+’s Pulse Processor is a multi-core processor. Each pulser core executes its own sequence independently of the others, unless a protocol calls for inter-core dependencies. Synchronizing and coordinating different threads is handled by the compiler behind the scenes, making it easy to set up complex experiments with simple instructions.
Suppose we want to play a Gaussian pulse with amplitude A1 to qubit_1, simultaneously with another Gaussian with amplitude A2 to qubit_2. This would be done with the following QUA program:
play(‘gaussian’*amp(A1), ‘qubit_1’)
play(‘gaussian’*amp(A2), ‘qubit_2’)
The two play()
commands address different threads, and therefore play simultaneously. This results in an output as shown below:
We can delay one of the pulses by using the wait()
command. In the following code, the qubit_1
thread alone is delayed by 20 clock cycles, while the qubit_2
thread is unaffected:
wait(20,’qubit_1’)
play(‘gaussian’*amp(A1), ‘qubit_1’)
play(‘gaussian’*amp(A2), ‘qubit_2’)
Many experiment protocols call for one sequence to begin only after another sequence is finished. Rather than manually calculate the duration of the first sequence, this can be implemented in QUA with the align()
command:
play(‘gaussian’*amp(A1), ‘qubit_1’)
wait(20,’qubit_1’)
play(‘gaussian’*amp(A1), ‘qubit_1’)
align(‘qubit_1’, ‘qubit_2’)
play(‘gaussian’*amp(A2), ‘qubit_2’)
This results in the following output:
The align(‘qubit_1’, ‘qubit_2’)
command causes the two threads to wait for each other. Any command appearing below this line that addresses either of the two threads will only be implemented after both have completed all commands appearing above this line. This dependency is evaluated in real time, and synchronizes the threads even if the duration of the first sequence is not known at compile time!
This last point is vital for many sequences, such as repeat-until-success protocols. Consider the following code:
with while_(result>0.2 && N < 1000):
### Subroutine involving ‘qubit_1’ ###
align(‘qubit_1’, ’qubit_2’)
play(‘gaussian’*amp(A2), ‘qubit_2’)
This will run a nondeterministic while()
loop, within which the OPX+ might play pulses to qubit_1
, measure it, update the result
variable based on the measurement, and increment the counter variable N
. It might run a single iteration before exiting the loop, and it might run 1,000. This is not known at compile time. But the simple align()
still synchronizes the pulses, with all of the real-time control complexity handled by the compiler.