Direct and Mixed mode¶
Presto’s inputs and outputs can operate in two modes: “direct” and “mixed”. In direct mode, the
signals generated by the logic are output directly by the digital-to-analog converter (DAC), and
similarly the signals acquired by the analog-to-digital converter (ADC) are directly available to
the logic. With “logic” we mean the further signal processing and analysis provided by the
different modes of operation of Presto, like the pulsed
mode and lockin
mode.
In mixed mode, instead, we use the digital upconversion and downconversion features of Presto. On the logic side, we are in the intermediate-frequency (IF) domain and all samples are complex valued, or equivalently all samples are pairs of I and Q values. For the outputs, these samples are fed to the I and Q ports of a digital IQ mixer that performs frequency upconversion to the radio-frequency (RF) domain using a numerically-controlled oscillator (NCO) as the carrier tone (often also called local oscillator, LO). The real-valued RF signal is then output by the DAC.
Similarly for the input side, the ADC acquires a real-valued RF signal that is then fed to a digital IQ mixer that performs frequency downconversion to the IF domain using an NCO. The complex-valued IF signal is then propagated to the rest of the logic.
Digital frequency conversion is conceptually identical to the traditional way of generating high-frequency RF signals using baseband outputs and analog, external IQ mixers and local oscillators. The difference is that with Presto all these analog external components are replaced with digital logic (i.e. math on the signals), cutting down on the number of components in an experimental setup and thus reducing cost as well as troubleshooting and calibration efforts. Moreover, all the imperfections of real-world analog components (nonlinearities, amplitude and phase imbalance, LO leakage, …) are just not applicable anymore.
Programming direct and mixed mode¶
Starting from version 2.12.0, the Presto API has an autoconfiguration feature: all you need to do
is set the arguments adc_mode
and dac_mode
when you instantiate the Pulsed
or
Lockin
classes. For example:
from presto import pulsed
from presto.hardware import AdcMode, DacMode
with pulsed.Pulsed(
address="192.168.20.42",
adc_mode=AdcMode.Direct,
dac_mode=DacMode.Direct,
) as pls:
...
will initialize Presto in pulsed mode and direct mode. Similarly:
from presto import lockin
from presto.hardware import AdcMode, DacMode
with lockin.Lockin(
address="192.168.20.42",
adc_mode=AdcMode.Mixed,
dac_mode=DacMode.Mixed,
) as lck:
...
lck.hardware.configure_mixer(3.5e9, in_ports=1, out_ports=[1, 3])
...
will initialize Presto in lockin mode and mixed mode. It will also configure a carrier of 3.5
GHz for digital upconversion on output ports 1 and 3 and digital downconversion on input port 1.
See Hardware.configure_mixer()
for more details on programming the digital IQ mixers.
Finer control¶
With the above snippets of code, the Presto API will choose automatically the optimal sampling rate on each input and output port based on the provided carrier frequencies. If you want finer control on the chosen settings, or for the rare cases in which the automatic algorithm fails, you can provide explicitly the sampling rates. See Recommended DAC configuration and Advanced tile configuration for more information.
Migrating from version 2.11.0 and prior¶
If your measurement scripts were written using Presto API version 2.11.0 or earlier, they should continue to work just like before also on version 2.12.0 and later. If you find that’s not the case, please contact our support and we’ll look into it: you may have found a bug!
Nevertheless, you might still want to update your code to make use of the new “autoconfiguration” feature. That will make your code easier to read, more resilient to changes in parameters, and maybe even improve performance if you happened to use a suboptimal configuration. See below for some common coding patterns in earlier versions of the Presto API, and how they can be simplified with the autoconfiguration feature.
We’ll use the diff syntax below to make which lines of code should be added, removed or left unchanged. Like this:
this line should remain unchanged
- this line should be removed
+ this line should be added
Class instantiation¶
When instantiating the Lockin
or Pulsed
class, it is as simple as removing all
settings related to adc_fsample
and dac_fsample
. Also, for dac_mode
use either
DacMode.Direct
or DacMode.Mixed
, don’t use the low-level DacMode.Mixed02
, DacMode.Mixed04
and DacMode.Mixed42
.
Here’s some examples:
with lockin.Lockin(
address="192.168.20.42",
adc_mode=AdcMode.Direct,
- adc_fsample=AdcFSample.G4,
dac_mode=DacMode.Direct,
- dac_fsample=DacFSample.G6,
) as lck:
...
with pulsed.Pulsed(
address="192.168.20.42",
adc_mode=AdcMode.Mixed,
- adc_fsample=AdcFSample.G2,
+ dac_mode=DacMode.Mixed,
- dac_mode=DacMode.Mixed42,
- dac_fsample=DacFSample.G10,
) as pls:
...
with pulsed.Pulsed(
address="192.168.20.42",
adc_mode=AdcMode.Mixed,
- adc_fsample=AdcFSample.G4,
+ dac_mode=DacMode.Mixed,
- dac_mode=[DacMode.Mixed42, DacMode.Mixed02, DacMode.Mixed02, DacMode.Mixed02],
- dac_fsample=[DacFSample.G10, DacFSample.G6, DacFSample.G6, DacFSample.G6],
) as pls:
...
Configure mixer in lockin mode¶
Prior to version 2.12.0, Hardware.configure_mixer()
used to have effect (almost) immediately
in lockin
mode. A common pattern was to sleep for a small time after calling the method to
wait for the settings to propagate to the hardware before continuing with the experiment.
When using the autoconfiguration feature, this is no longer the case and a call to
apply_settings()
is required after configure_mixer()
for the
settings to take effect. On the other hand, no sleep is necessary anymore.
for freq in some_frequency_array:
lck.hardware.configure_mixer(freq, in_ports=1, out_ports=1)
- lck.hardware.sleep(1e-3, False)
+ lck.apply_settings()
data = lck.get_pixels(10_000)
...
You don’t need to call apply_settings()
every time you change something, you can
combine multiple changes and then apply settings only once:
og = lck.add_output_group(1, 1)
og.set_frequency(240e6) # requires apply_settings
for amp in some_amplitude_array:
for freq in some_frequency_array:
lck.hardware.configure_mixer(freq, in_ports=1, out_ports=1) # requires apply_settings
og.set_amplitude(amp) # requires apply_settings
lck.apply_settings() # HERE!
data = lck.get_pixels(10_000)
...
Synchronize multiple mixers¶
Prior to version 2.12.0, each call to Hardware.configure_mixer()
was handled independently.
That meant that a little extra care was required when using different mixer frequencies to achieve
a reproducible phase relationship. One would pass the sync=False
optional parameter to the first
call to configure_mixer()
, and sync=True
to the second so that the two mixers
would start their carriers simultaneously.
Using the autoconfiguration feature in version 2.12.0, this is not necessary anymore: all calls to
configure_mixer()
are handled together later on, when calling the methods
Pulsed.run()
and Lockin.apply_settings()
in pulsed
and lockin
mode,
respectively.
So, just drop the sync
parameters:
with pulsed.Pulsed(...) as pls:
...
- pls.hardware.configure_mixer(freq=4e9, in_ports=2, out_ports=2, sync=False)
- pls.hardware.configure_mixer(freq=8e9, out_ports=3, sync=True)
+ pls.hardware.configure_mixer(freq=4e9, in_ports=2, out_ports=2)
+ pls.hardware.configure_mixer(freq=8e9, out_ports=3)
...
pls.run(T, 1, 10_000)
with lockin.Lockin(...) as lck:
...
- lck.hardware.configure_mixer(freq=4e9, in_ports=2, out_ports=2, sync=False)
- lck.hardware.configure_mixer(freq=8e9, out_ports=3, sync=True)
+ lck.hardware.configure_mixer(freq=4e9, in_ports=2, out_ports=2)
+ lck.hardware.configure_mixer(freq=8e9, out_ports=3)
+ lck.apply_settings()