Hello!
The investigations in this other thread were actually triggered by attempts at getting coherent phase updates when using the AD9910 RAM mode.
Considering the following code (the initial intent was to use the bidirectional ramp mode, hence the profile changes, simplified here to use the ramp-up mode):
from artiq.experiment import *
from artiq.coredevice import ad9910
from numpy import int32
from numpy.testing import assert_array_equal
class RAMPhase(EnvExperiment):
def build(self):
self.setattr_device("core")
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(234 * MHz)
asf = self.dds1.amplitude_to_asf(1.0)
# a box pulse, 4 µs long
self.ram_data = [0, int32(asf << 18), 0]
self.ram_step = 1000
@kernel
def run(self):
self.core.reset()
self.trg.output()
self.init_dds()
self.do()
with parallel:
self.trg.pulse_mu(300)
self.dds0.sw.on()
self.dds1.sw.on()
@kernel
def do(self):
# dds0: phase reference
self.dds0.set_mu(self.ftw, phase_mode=ad9910.PHASE_MODE_TRACKING)
# Reset dds1 phase accumulator and set POW to track dds0 phase with 0 offset.
# Accumulate phase with ftw
pow_ = self.dds1.set_mu(self.ftw, phase_mode=ad9910.PHASE_MODE_TRACKING)
# Copy FTW and POW values into the registers that control these
# when in RAM mode
self.dds1.set_ftw(self.ftw)
self.dds1.set_pow(pow_)
# Write RAM profile register (this overwrites the single-tone profile data)
self.dds1.set_profile_ram(
start=0,
end=len(self.ram_data) - 1,
step=self.ram_step,
profile=0,
mode=ad9910.RAM_MODE_RAMPUP,
)
# Setup RAM enable and destination
self.dds1.set_cfr1(
ram_destination=ad9910.RAM_DEST_ASF,
ram_enable=1,
)
# Enable RAM:
# this loads the FTW and POW values from the eponymous registers
# (addresses 0x07 and 0x08) into the DDS core and uses the
# RAM values as ASF
self.dds1.cpld.io_update.pulse_mu(8)
@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)
# write amplitude pattern in dds1 RAM
# and check
self.dds1.set_profile_ram(
start=0,
end=len(self.ram_data) - 1,
step=self.ram_step,
profile=0,
mode=ad9910.RAM_MODE_RAMPUP,
)
self.dds1.cpld.io_update.pulse_mu(8)
self.dds1.write_ram(self.ram_data)
read_data = [0] * len(self.ram_data)
self.dds1.read_ram(read_data)
delay(200 * ms)
assert_array_equal(self.ram_data, read_data)
delay(200 * ms)
This unfortunately doesn't provide the desired output: the phase difference between the dds0
and dds1
outputs is not zero and depends on their (common) frequency (but is stable across repetitions of the experiment). Adding a variable delay just before the IO_UPDATE
pulse that loads the ram_enable
settings does not change the observed phase difference, which seems to indicate that the issue is rather a constant phase jump than a unintended accumulator reset.
As outlined in the comments, the idea is to setup the FTW and POW in the known-to-work single-tone mode, and copy them to the FTW (0x07) and POW (0x08) registers such that they stay unchanged when entering RAM mode. If the phase accumulator keeps running with the same FTW, the phases of dds1
and dds0
should stay aligned, correct?
Is there something conceptually wrong with the above approach? Do you have other ideas how to achieve phase tracking with the RAM controlling the amplitude?