Rf mixer exampleΒΆ
This example demonstrates the RFMixer
component. It represents the RF output signal of an external frequency mixer whose IF
port is driven by a SUServo channel and whose LO port is driven by a bare Urukul
channel. With sideband=-1 the port frequencies obey
so setting the mixer frequency programs the IF (SUServo channel) to
f_if = f_lo - f_rf while the LO (Urukul channel) is programmed independently by
the experiment.
Since the mixer is an RFSource itself, the usual machinery such as ramps and arbs
works on the mixer output and is forwarded to the input ports. A typical use case is
to attach an AOM to the mixer output (as its rfsource) and a
SUServoModulatedLaser on top, reaching the
SUServo channel behind the mixer via the suservo_channel_path argument of the
laser (e.g. "rfsource.if_source").
Note
The suservo must be initialized manually once after power cycling by running:
self.mixer.if_source.suservo.suservo_device.init()
or by directly adding the suservo (here: suservo0) to the components and running:
self.suservo0.init()
Since initialization is very time-consuming, we recommend a separate init experiment that can be run after power cycling to handle these tasks.
Experiment
from atomiq import AtomiqExperiment
from artiq.experiment import kernel, delay
class RFMixerExample(AtomiqExperiment):
"""RF Mixer Example.
We produce the difference frequency of two RF sources in an external frequency mixer:
a SUServo channel as the IF and a bare Urukul channel as the LO. The RFMixer component
represents the mixer output, so its frequency and amplitude can be set (and ramped)
directly while the component drives the IF such that f_rf = f_lo - f_if holds, given
the current frequency of the LO.
"""
components = ["mixer", "if_channel", "lo_channel"]
arguments = {
"lo_frequency": {"default": 300e6, "unit": "MHz", "scale": 1e6},
"rf_frequency": {"default": 80e6, "unit": "MHz", "scale": 1e6},
"rf_amplitude": {"default": 0.5},
"pulse_time": {"default": 10e-3, "unit": "ms", "scale": 1e-3},
}
@kernel
def prerun(self):
# Program the LO frequency once per run. The mixer reads the frequency of its
# LO component at kernel runtime.
self.lo_channel.set_frequency(self.lo_frequency.value)
self.lo_channel.on()
@kernel
def step(self, point):
# Set frequency and amplitude of the mixer output. This drives the IF (the
# SUServo channel) such that f_rf = f_lo - f_if, i.e. here
# f_if = 300 MHz - 80 MHz = 220 MHz.
self.mixer.set_frequency(point.rf_frequency)
self.mixer.set_amplitude(point.rf_amplitude)
self.if_channel.on()
self.log.info(
"mixer output: {0} MHz (LO: {1} MHz, IF: {2} MHz)",
[
self.mixer.get_frequency() / 1e6,
self.lo_channel.get_frequency() / 1e6,
self.if_channel.get_frequency() / 1e6,
],
)
delay(point.pulse_time)
# Ramp the mixer output frequency. The IF follows through the mixer, i.e. the
# IF frequency is ramped from 220 MHz to 240 MHz here.
self.mixer.ramp(
point.pulse_time,
frequency_start=point.rf_frequency,
frequency_end=point.rf_frequency - 20e6,
ramp_steps=20,
)
self.if_channel.off()
self.lo_channel.off()
Components
components = {"type": None}
# Basics
components.update(
{
"log": {
"classname": "atomiq.components.basics.log.KernelLogger",
}
}
)
# Sinara modules
components.update(
{
"kasli0": {
"classname": "atomiq.components.sinara.Kasli",
"arguments": {},
},
"suservo0": {
"classname": "atomiq.components.sinara.suservo.SUServo",
"arguments": {
"suservo_device": "@suservo0",
},
},
"urukul0": {
"classname": "atomiq.components.sinara.urukul.Urukul",
"arguments": {
"cpld": "@urukul0_cpld",
},
},
}
)
# IF port of the mixer: a SUServo channel. The frequency and the amplitude of the
# mixer output are set through this channel (frequency_control_port / amplitude_
# control_port of the mixer).
components.update(
{
"if_channel": {
"classname": "atomiq.components.sinara.suservo.SUServoChannel",
"arguments": {
"suservo": "&suservo0",
"suservo_channel": "@suservo0_ch0",
"freq_limit": (15e6, 400e6),
"default_attenuation": 20.0,
},
}
}
)
# LO port of the mixer: a bare Urukul channel. Its frequency is programmed by the
# experiment (see the prerun phase of the example); the mixer follows it.
components.update(
{
"lo_channel": {
"classname": "atomiq.components.sinara.urukul.UrukulChannel",
"arguments": {
"urukul": "&urukul0",
"device": "@urukul0_ch0",
"ttl": "@ttl_urukul0_sw0",
"default_frequency": 300e6,
},
}
}
)
# The mixer itself. It represents the RF output signal of an external frequency
# mixer, here the difference of LO and IF (f_rf = f_lo - f_if). Setting the mixer
# frequency or amplitude drives the IF channel accordingly, given the current
# frequency of the LO channel.
components.update(
{
"mixer": {
"classname": "atomiq.components.electronics.rfsource.RFMixer",
"arguments": {
"if_source": "&if_channel",
"lo_source": "&lo_channel",
"sideband": -1,
"frequency_control_port": "if",
"amplitude_control_port": "if",
"default_frequency": 80e6,
"freq_limit": (10e6, 200e6),
},
}
}
)