Source code for openlabctrl.io.rf
from .base import BaseIo
from enum import Enum
CLK_FREQ = 125e6
class RfCmd(Enum):
PHASE = 0x0
PHASE_RST = 0x1
FREQ = 0x2
AMPL = 0x3
UPDATE = 0x8
[docs]
class RfBase(BaseIo):
"""
Base driver class for RF IOs (DDS-based signal generation).
Wraps a direct digital synthesizer (DDS) core and provides control over
frequency, phase, and amplitude. The ``update`` flag on each method controls
whether the new value is applied immediately (``True``) or staged for a later
atomic update (``False``), which allows frequency, phase, and amplitude to be
changed simultaneously.
"""
def __init__(self, addr, clk_freq):
super().__init__(addr, clk_freq)
[docs]
def frequency(self, val: float, update: bool = True):
"""
Set the output frequency.
The valid range is ``[-clk_freq/2, clk_freq/2]`` (i.e. ±62.5 MHz for a 125 MHz clock).
:param val: Frequency in Hz.
:param update: If ``True``, apply the new value immediately. Set to ``False`` to stage
the change and apply it atomically together with other staged parameters.
"""
FREQ_MIN = -self._clk_freq / 2
FREQ_MAX = self._clk_freq / 2
if (val < FREQ_MIN) or (val > FREQ_MAX):
raise Exception(f"Frequency value {val} is out of range [{FREQ_MIN}, {FREQ_MAX}].")
cmd = RfCmd.FREQ.value
if update:
cmd |= RfCmd.UPDATE.value
data = int(val / self._clk_freq * ((1 << 32) - 1))
self._add_instruction(cmd=cmd, data=data)
[docs]
def phase(self, val: float, update: bool = True):
"""
Set the output phase.
:param val: Phase in degrees.
:param update: If ``True``, apply the new value immediately. Set to ``False`` to stage
the change and apply it later with other staged parameters.
"""
cmd = RfCmd.PHASE.value
if update:
cmd |= RfCmd.UPDATE.value
data = int((val % 360) / 360 * ((1 << 32) - 1))
self._add_instruction(cmd=cmd, data=data)
[docs]
def amplitude(self, val: float, update: bool = True):
"""
Set the output amplitude.
:param val: Relative amplitude in range ``[-1, 1]``.
:param update: If ``True``, apply the new value immediately. Set to ``False`` to stage
the change and apply it later with other staged parameters.
"""
AMPL_MIN = -1
AMPL_MAX = 1
if (val < AMPL_MIN) or (val > AMPL_MAX):
raise Exception(f"Amplitude value {val} is out of range [{AMPL_MIN}, {AMPL_MAX}].")
cmd = RfCmd.AMPL.value
if update:
cmd |= RfCmd.UPDATE.value
data = int(val * ((1 << 15) - 1))
self._add_instruction(cmd=cmd, data=data)
[docs]
def phase_reset(self, update: bool = True):
"""
Reset the DDS phase accumulator to zero.
:param update: If ``True``, apply immediately. Set to ``False`` to stage the reset
and apply it later with other staged parameters.
"""
cmd = RfCmd.PHASE_RST.value
if update:
cmd |= RfCmd.UPDATE.value
self._add_instruction(cmd=cmd, data=1)
[docs]
class RfOut(RfBase):
"""
Driver class for RF output channels.
"""
def __init__(self, addr, clk_freq):
super().__init__(addr, clk_freq)
class RfIn(RfBase):
"""
Driver class for RF input channels (TODO: missing FPGA backend).
"""
def __init__(self, addr, clk_freq):
super().__init__(addr, clk_freq)