# 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 {mod}`.pulsed` mode and {mod}`.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 {class}`.Pulsed` or {class}`.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 {meth}`.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](recommended_dac_config.md) and [Advanced tile configuration](<#adv tile>) 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](mailto:support@intermod.pro) 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: ```diff this line should remain unchanged - this line should be removed + this line should be added ``` ### Class instantiation When instantiating the {class}`.Lockin` or {class}`.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: ```diff 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: ... ``` ```diff 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: ... ``` ```diff 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, {meth}`.Hardware.configure_mixer` used to have effect (almost) immediately in {mod}`.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 {meth}`~.Lockin.apply_settings` is required after {meth}`~.Hardware.configure_mixer` for the settings to take effect. On the other hand, no sleep is necessary anymore. ```diff 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 {meth}`~.Lockin.apply_settings` every time you change something, you can combine multiple changes and then apply settings only once: ```python 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) ... ``` (autoconf-sync-mixers)= ### Synchronize multiple mixers Prior to version 2.12.0, each call to {meth}`.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 {meth}`~.Hardware.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 {meth}`~.Hardware.configure_mixer` are handled together later on, when calling the methods {meth}`.Pulsed.run` and {meth}`.Lockin.apply_settings` in {mod}`.pulsed` and {mod}`.lockin` mode, respectively. So, just drop the `sync` parameters: ```diff 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) ``` ```diff 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() ```