Hello!
I'm looking at the phase alignment of Phaser (single tone) and Urukul (AD9910) using the code below. The goal is to achieve deterministic absolute phase between a Urukul and a Phaser channel, as advertised by the Phaser docstring (i.e. for commensurate frequencies, the phases of the Urukul and Phaser outputs are equal w.r.t. e.g. a trigger edge).
Aligning the phase accumulator manipulation to the fastlink frame cycles as mentioned in this thread provides stable phase of both RF channels with respect to the trigger. However, their difference doesn't vanish (and the value obviously depends on the selected output frequency).
My understanding is that one would need to compensate for:
- the delay in applying the phase calculated in
ad9910.set_mu()
. Part of it is known (write64
), but how far is "as far away" from the SYNC_CLK
for the IO_UPDATE
alignment?
- the constant update delay in the Phaser signal chain.
Is this correct? If so, can the unknown values be calculated, to avoid calibration? If not, is there a conceptual mistake in the code below?
Thanks in advance!
from artiq.experiment import *
from artiq.coredevice import ad9910
from artiq.coredevice.rtio import rtio_output, rtio_input_timestamp
from artiq.coredevice.core import rtio_get_counter
from numpy import int32, int64
class PhaserUrukulPhase(EnvExperiment):
def build(self):
self.setattr_device("core")
self.phaser = self.get_device("phaser0")
self.dds = self.get_device("urukul0_ch0")
self.trg = self.get_device("ttl4")
self.frame_tstamp = int64(0)
@kernel
def run(self):
self.init()
ch = self.phaser.channel[0]
ch.set_duc_frequency(100 * MHz)
ch.set_duc_cfg(clr_once=1)
ch.set_att(3 * dB)
ch.oscillator[0].set_frequency(2 * MHz)
# align the phase origin to the fastlink frames
n = int64((now_mu() - self.frame_tstamp) / self.phaser.t_frame)
t_ref = self.frame_tstamp + (n + 1) * self.phaser.t_frame
at_mu(t_ref)
with parallel:
# setup DUC, clear DUC phase
self.phaser.duc_stb()
# reset Kasli oscillator phase
ch.oscillator[0].set_amplitude_phase(amplitude=0.0, phase=0.0, clr=1)
# enable Kasli oscillator output
ch.oscillator[0].set_amplitude_phase(amplitude=0.28)
# setup AD9910 output with phase origin at t_ref
self.dds.set(
102 * MHz,
phase_mode=ad9910.PHASE_MODE_TRACKING,
ref_time_mu=t_ref,
)
self.trg.pulse_mu(500)
self.dds.sw.on()
@kernel
def init(self):
self.core.reset()
self.trg.output()
# phaser init
self.phaser.init()
# determine fastlink frame alignment
# (see get_frame_alignment() in stft_pulsegen branch
# of github.com/quartiq/phaser)
rtio_output(self.phaser.channel_base << 8, 0)
delay_mu(int64(self.phaser.t_frame))
self.frame_tstamp = rtio_input_timestamp(
rtio_get_counter() + 0xFFFFFF, self.phaser.channel_base
)
delay(10 * ms)
# urukul channel init
self.dds.cpld.init()
self.dds.init()
self.dds.set_att_mu(220)
self.dds.sw.off()
self.core.break_realtime()