Thanks for the feedback!
Could you elaborate what your mental model of the chip is at the different code steps and what should happen vs does in fact happen? I have a hard time diff-ing mine and yours 😉
Also the line numbers aren't available immediately.
Sorry about that. I think that we understood the issue. Here's the incorrect code again, slightly refactored, for reference:
from artiq.experiment import *
from artiq.coredevice import ad9910
class ProfileSwitch(EnvExperiment):
def build(self):
self.setattr_device("core")
# Urukul v1.3 => use different boards because
# PROFILE[2:0] is shared between channels
self.dds0 = self.get_device("urukul0_ch0")
self.dds1 = self.get_device("urukul2_ch0")
self.trg = self.get_device("ttl4")
self.ftw = self.dds0.frequency_to_ftw(123.2 * MHz)
@kernel
def run(self):
self.core.reset()
self.init_dds()
self.do()
with parallel:
self.trg.pulse_mu(400)
self.dds1.sw.on()
self.dds0.sw.on()
@kernel
def init_dds(self):
for dds in [self.dds0, self.dds1]:
dds.cpld.init()
dds.init()
dds.sw.off()
dds.set_att_mu(180)
@kernel
def do(self):
self.dds1.cpld.set_profile(1)
self.dds1.set_mu(self.ftw, phase_mode=ad9910.PHASE_MODE_TRACKING)
self.dds1.cpld.set_profile(0)
self.dds0.set_mu(self.ftw, phase_mode=ad9910.PHASE_MODE_TRACKING)
Changing to the following works:
@kernel
def do(self):
self.dds1.set_mu(self.ftw, profile=1)
self.dds1.cpld.set_profile(1)
self.dds1.set_mu(self.ftw, phase_mode=ad9910.PHASE_MODE_TRACKING)
self.dds1.cpld.set_profile(0)
self.dds0.set_mu(self.ftw, phase_mode=ad9910.PHASE_MODE_TRACKING)
A plausible explanation goes as: the phase accumulator only runs if there's a non-0 FTW loaded in the DDS core (otherwise, the SYSCLK-clocked flip-flop just shifts 0s all the time), which is not the case on profile 1 in the incorrect code above. The situation is therefore equivalent to having a phase accumulator reset upon the change to profile 0. The fix is effective only if one sets the same FTW for both profile 0 and profile 1, which relates to your comment about tracking profile-specific virtual oscillators. Does this make sense?
One thing to keep in mind: Are we certain that the PROFILE pins meet S/H w.r.t. SYNC_CLK. Unlike IO_UPDATE they go through the CPLD and are not delay-tuned.
Good point. Originally, the idea was that it shouldn't matter since there're no new values being loaded from the I/O registers, but this is indeed wrong since this also triggers loading new values into the DDS core. Is that correct?