add get_*() functions to ad9910, ad9912, and urukul. closes #1616

Signed-off-by: Leon Riesebos <leon.riesebos@duke.edu>
This commit is contained in:
Leon Riesebos 2021-03-14 23:40:26 -04:00 committed by Sébastien Bourdeauducq
parent c22f731a61
commit d04bcd8754
6 changed files with 304 additions and 11 deletions

View File

@ -8,6 +8,7 @@ ARTIQ-7
Highlights:
* WRPLL
* ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912
Breaking changes:

View File

@ -532,6 +532,25 @@ class AD9910:
# future IO_UPDATE will activate
return pow_
@kernel
def get_mu(self, profile: TInt32 = 0) -> TTuple([TInt32, TInt32, TInt32]):
"""Get the frequency tuning word, phase offset word,
and amplitude scale factor.
.. seealso:: :meth:`get`
:param profile: Profile number to get (0-7, default: 0)
:return: A tuple ``(ftw, pow, asf)``
"""
# Read data
data = int64(self.read64(_AD9910_REG_PROFILE0 + profile))
# Extract and return fields
ftw = int32(data)
pow_ = int32((data >> 32) & 0xffff)
asf = int32((data >> 48) & 0x3fff)
return ftw, pow_, asf
@kernel
def set_profile_ram(self, start: TInt32, end: TInt32, step: TInt32 = 1,
profile: TInt32 = 0, nodwell_high: TInt32 = 0,
@ -585,6 +604,33 @@ class AD9910:
"""
self.write16(_AD9910_REG_POW, pow_)
@kernel
def get_ftw(self) -> TInt32:
"""Get the value stored to the AD9910's frequency tuning word (FTW)
register.
:return: Frequency tuning word
"""
return self.read32(_AD9910_REG_FTW)
@kernel
def get_asf(self) -> TInt32:
"""Get the value stored to the AD9910's amplitude scale factor (ASF)
register.
:return: Amplitude scale factor
"""
return self.read32(_AD9910_REG_ASF) >> 2
@kernel
def get_pow(self) -> TInt32:
"""Get the value stored to the AD9910's phase offset word (POW)
register.
:return: Phase offset word
"""
return self.read16(_AD9910_REG_POW)
@portable(flags={"fast-math"})
def frequency_to_ftw(self, frequency: TFloat) -> TInt32:
"""Return the 32-bit frequency tuning word corresponding to the given
@ -708,6 +754,33 @@ class AD9910:
"""
self.set_pow(self.turns_to_pow(turns))
@kernel
def get_frequency(self) -> TFloat:
"""Get the value stored to the AD9910's frequency tuning word (FTW)
register.
:return: frequency in Hz.
"""
return self.ftw_to_frequency(self.get_ftw())
@kernel
def get_amplitude(self) -> TFloat:
"""Get the value stored to the AD9910's amplitude scale factor (ASF)
register.
:return: amplitude in units of full scale.
"""
return self.asf_to_amplitude(self.get_asf())
@kernel
def get_phase(self) -> TFloat:
"""Get the value stored to the AD9910's phase offset word (POW)
register.
:return: phase offset in turns.
"""
return self.pow_to_turns(self.get_pow())
@kernel
def set(self, frequency: TFloat, phase: TFloat = 0.0,
amplitude: TFloat = 1.0, phase_mode: TInt32 = _PHASE_MODE_DEFAULT,
@ -729,6 +802,22 @@ class AD9910:
self.amplitude_to_asf(amplitude), phase_mode, ref_time_mu,
profile))
@kernel
def get(self, profile: TInt32 = 0) -> TTuple([TFloat, TFloat, TFloat]):
"""Get the frequency, phase, and amplitude.
.. seealso:: :meth:`get_mu`
:param profile: Profile number to get (0-7, default: 0)
:return: A tuple ``(frequency, phase, amplitude)``
"""
# Get values
ftw, pow_, asf = self.get_mu(profile)
# Convert and return
return (self.ftw_to_frequency(ftw), self.pow_to_turns(pow_),
self.asf_to_amplitude(asf))
@kernel
def set_att_mu(self, att: TInt32):
"""Set digital step attenuator in machine units.
@ -753,6 +842,26 @@ class AD9910:
"""
self.cpld.set_att(self.chip_select - 4, att)
@kernel
def get_att_mu(self) -> TInt32:
"""Get digital step attenuator value in machine units.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att_mu`
:return: Attenuation setting, 8 bit digital.
"""
return self.cpld.get_channel_att_mu(self.chip_select - 4)
@kernel
def get_att(self) -> TFloat:
"""Get digital step attenuator value in SI units.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att`
:return: Attenuation in dB.
"""
return self.cpld.get_channel_att(self.chip_select - 4)
@kernel
def cfg_sw(self, state: TInt32):
"""Set CPLD CFG RF switch state. The RF switch is controlled by the

View File

@ -134,6 +134,26 @@ class AD9912:
"""
self.cpld.set_att(self.chip_select - 4, att)
@kernel
def get_att_mu(self) -> TInt32:
"""Get digital step attenuator value in machine units.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att_mu`
:return: Attenuation setting, 8 bit digital.
"""
return self.cpld.get_channel_att_mu(self.chip_select - 4)
@kernel
def get_att(self) -> TFloat:
"""Get digital step attenuator value in SI units.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att`
:return: Attenuation in dB.
"""
return self.cpld.get_channel_att(self.chip_select - 4)
@kernel
def set_mu(self, ftw: TInt64, pow_: TInt32):
"""Set profile 0 data in machine units.
@ -156,6 +176,24 @@ class AD9912:
self.bus.write(int32(ftw))
self.cpld.io_update.pulse(10 * ns)
@kernel
def get_mu(self) -> TTuple([TInt64, TInt32]):
"""Get the frequency tuning word and phase offset word.
.. seealso:: :meth:`get`
:return: A tuple ``(ftw, pow)``.
"""
# Read data
high = self.read(AD9912_POW1, 4)
self.core.break_realtime() # Regain slack to perform second read
low = self.read(AD9912_FTW3, 4)
# Extract and return fields
ftw = (int64(high & 0xffff) << 32) | (int64(low) & int64(0xffffffff))
pow_ = (high >> 16) & 0x3fff
return ftw, pow_
@portable(flags={"fast-math"})
def frequency_to_ftw(self, frequency: TFloat) -> TInt64:
"""Returns the 48-bit frequency tuning word corresponding to the given
@ -200,6 +238,20 @@ class AD9912:
self.set_mu(self.frequency_to_ftw(frequency),
self.turns_to_pow(phase))
@kernel
def get(self) -> TTuple([TFloat, TFloat]):
"""Get the frequency and phase.
.. seealso:: :meth:`get_mu`
:return: A tuple ``(frequency, phase)``.
"""
# Get values
ftw, pow_ = self.get_mu()
# Convert and return
return self.ftw_to_frequency(ftw), self.pow_to_turns(pow_)
@kernel
def cfg_sw(self, state: TInt32):
"""Set CPLD CFG RF switch state. The RF switch is controlled by the

View File

@ -284,6 +284,27 @@ class CPLD:
"""
self.cfg_write((self.cfg_reg & ~0xf) | state)
@portable(flags={"fast-math"})
def mu_to_att(self, att_mu: TInt32) -> TFloat:
"""Convert a digital attenuation setting to dB.
:param att_mu: Digital attenuation setting.
:return: Attenuation setting in dB.
"""
return (255 - (att_mu & 0xff)) / 8
@portable(flags={"fast-math"})
def att_to_mu(self, att: TFloat) -> TInt32:
"""Convert an attenuation setting in dB to machine units.
:param att: Attenuation setting in dB.
:return: Digital attenuation setting.
"""
code = int32(255) - int32(round(att * 8))
if code < 0 or code > 255:
raise ValueError("Invalid urukul.CPLD attenuation!")
return code
@kernel
def set_att_mu(self, channel: TInt32, att: TInt32):
"""Set digital step attenuator in machine units.
@ -349,6 +370,34 @@ class CPLD:
self.bus.write(self.att_reg) # shift in current value again and latch
return self.att_reg
@kernel
def get_channel_att_mu(self, channel: TInt32) -> TInt32:
"""Get digital step attenuator value for a channel in machine units.
The result is stored and will be used in future calls of
:meth:`set_att_mu` and :meth:`set_att`.
.. seealso:: :meth:`get_att_mu`
:param channel: Attenuator channel (0-3).
:return: 8-bit digital attenuation setting:
255 minimum attenuation, 0 maximum attenuation (31.5 dB)
"""
return int32((self.get_att_mu() >> (channel * 8)) & 0xff)
@kernel
def get_channel_att(self, channel: TInt32) -> TFloat:
"""Get digital step attenuator value for a channel in SI units.
.. seealso:: :meth:`get_channel_att_mu`
:param channel: Attenuator channel (0-3).
:return: Attenuation setting in dB. Higher value is more
attenuation. Minimum attenuation is 0*dB, maximum attenuation is
31.5*dB.
"""
return self.mu_to_att(self.get_channel_att_mu(channel))
@kernel
def set_sync_div(self, div: TInt32):
"""Set the SYNC_IN AD9910 pulse generator frequency

View File

@ -42,11 +42,52 @@ class AD9910Exp(EnvExperiment):
self.core.break_realtime()
self.dev.cpld.init()
self.dev.init()
self.dev.set_att(20*dB)
f = 81.2345*MHz
self.dev.set(frequency=f, phase=.33, amplitude=.89)
p = .33
a = .89
att = 20*dB
self.dev.set_att(att)
self.dev.set(frequency=f, phase=p, amplitude=a)
self.core.break_realtime()
ftw, pow_, asf = self.dev.get_mu()
self.core.break_realtime()
att_mu = self.dev.get_att_mu()
self.set_dataset("ftw_set", self.dev.frequency_to_ftw(f))
self.set_dataset("ftw_get", self.dev.read32(_AD9910_REG_FTW))
self.set_dataset("ftw_get", ftw)
self.set_dataset("pow_set", self.dev.turns_to_pow(p))
self.set_dataset("pow_get", pow_)
self.set_dataset("asf_set", self.dev.amplitude_to_asf(a))
self.set_dataset("asf_get", asf)
self.set_dataset("att_set", self.dev.cpld.att_to_mu(att))
self.set_dataset("att_get", att_mu)
@kernel
def set_get_io_update_regs(self):
self.core.break_realtime()
self.dev.cpld.init()
self.dev.init()
f = 81.2345*MHz
p = .33
a = .89
self.dev.set_frequency(f)
self.dev.set_phase(p)
self.dev.set_amplitude(a)
self.core.break_realtime()
ftw = self.dev.get_ftw()
self.core.break_realtime()
pow_ = self.dev.get_pow()
self.core.break_realtime()
asf = self.dev.get_asf()
self.set_dataset("ftw_set", self.dev.frequency_to_ftw(f))
self.set_dataset("ftw_get", ftw)
self.set_dataset("pow_set", self.dev.turns_to_pow(p))
self.set_dataset("pow_get", pow_)
self.set_dataset("asf_set", self.dev.amplitude_to_asf(a))
self.set_dataset("asf_get", asf)
@kernel
def read_write64(self):
@ -316,9 +357,19 @@ class AD9910Test(ExperimentCase):
def test_set_get(self):
self.execute(AD9910Exp, "set_get")
ftw_get = self.dataset_mgr.get("ftw_get")
ftw_set = self.dataset_mgr.get("ftw_set")
self.assertEqual(ftw_get, ftw_set)
for attr in ['ftw', 'pow', 'asf', 'att']:
with self.subTest(attribute=attr):
get = self.dataset_mgr.get("{}_get".format(attr))
set_ = self.dataset_mgr.get("{}_set".format(attr))
self.assertEqual(get, set_)
def test_set_get_io_update_regs(self):
self.execute(AD9910Exp, "set_get_io_update_regs")
for attr in ['ftw', 'pow', 'asf']:
with self.subTest(attribute=attr):
get = self.dataset_mgr.get("{}_get".format(attr))
set_ = self.dataset_mgr.get("{}_set".format(attr))
self.assertEqual(get, set_)
def test_read_write64(self):
self.execute(AD9910Exp, "read_write64")

View File

@ -101,6 +101,28 @@ class UrukulExp(EnvExperiment):
self.set_dataset("att_get", att_get)
self.set_dataset("att_reg", att_reg)
@kernel
def att_channel_get(self):
self.core.break_realtime()
self.dev.init()
# clear backing state
self.dev.att_reg = 0
att_set = [int32(0x21), int32(0x43),
int32(0x65), int32(0x87)]
# set individual attenuators
for i in range(len(att_set)):
self.dev.set_att_mu(i, att_set[i])
# confirm that we can set all attenuators and read back
att_get = [0 for _ in range(len(att_set))]
for i in range(len(att_set)):
self.core.break_realtime()
att_get[i] = self.dev.get_channel_att_mu(i)
# confirm backing state
att_reg = self.dev.att_reg
self.set_dataset("att_set", att_set)
self.set_dataset("att_get", att_get)
self.set_dataset("att_reg", att_reg)
@kernel
def att_speed(self):
self.core.break_realtime()
@ -175,6 +197,15 @@ class UrukulTest(ExperimentCase):
self.assertEqual(att_set, self.dataset_mgr.get("att_get"))
self.assertEqual(att_set, self.dataset_mgr.get("att_reg"))
def test_att_channel_get(self):
self.execute(UrukulExp, "att_channel_get")
att_set = self.dataset_mgr.get("att_set")
self.assertListEqual(att_set, self.dataset_mgr.get("att_get"))
att_reg = self.dataset_mgr.get("att_reg")
for att in att_set:
self.assertEqual(att, att_reg & 0xff)
att_reg >>= 8
def test_att_speed(self):
self.execute(UrukulExp, "att_speed")
dt = self.dataset_mgr.get("dt")