From d04bcd8754f11780d02d179285712980701d6518 Mon Sep 17 00:00:00 2001 From: Leon Riesebos Date: Sun, 14 Mar 2021 23:40:26 -0400 Subject: [PATCH] add get_*() functions to ad9910, ad9912, and urukul. closes #1616 Signed-off-by: Leon Riesebos --- RELEASE_NOTES.rst | 1 + artiq/coredevice/ad9910.py | 109 +++++++++++++++++++++++++++ artiq/coredevice/ad9912.py | 52 +++++++++++++ artiq/coredevice/urukul.py | 49 ++++++++++++ artiq/test/coredevice/test_ad9910.py | 63 ++++++++++++++-- artiq/test/coredevice/test_urukul.py | 41 ++++++++-- 6 files changed, 304 insertions(+), 11 deletions(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 0b3e22337..11a126813 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -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: diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index f2beedba0..ff57fa28f 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -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 diff --git a/artiq/coredevice/ad9912.py b/artiq/coredevice/ad9912.py index b2987c3a3..13cf1a121 100644 --- a/artiq/coredevice/ad9912.py +++ b/artiq/coredevice/ad9912.py @@ -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 diff --git a/artiq/coredevice/urukul.py b/artiq/coredevice/urukul.py index dd19728b4..7cda7a4ce 100644 --- a/artiq/coredevice/urukul.py +++ b/artiq/coredevice/urukul.py @@ -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 diff --git a/artiq/test/coredevice/test_ad9910.py b/artiq/test/coredevice/test_ad9910.py index 4af554784..fc8093df5 100644 --- a/artiq/test/coredevice/test_ad9910.py +++ b/artiq/test/coredevice/test_ad9910.py @@ -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") diff --git a/artiq/test/coredevice/test_urukul.py b/artiq/test/coredevice/test_urukul.py index be43e6641..da329fbc1 100644 --- a/artiq/test/coredevice/test_urukul.py +++ b/artiq/test/coredevice/test_urukul.py @@ -53,7 +53,7 @@ class UrukulExp(EnvExperiment): for i in range(n): self.dev.cfg_sw(3, i & 1) self.set_dataset("dt", self.core.mu_to_seconds( - self.core.get_rtio_counter_mu() - t0)/n) + self.core.get_rtio_counter_mu() - t0) / n) @kernel def switches_readback(self): @@ -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() @@ -108,9 +130,9 @@ class UrukulExp(EnvExperiment): n = 10 t0 = self.core.get_rtio_counter_mu() for i in range(n): - self.dev.set_att(3, 30*dB) + self.dev.set_att(3, 30 * dB) self.set_dataset("dt", self.core.mu_to_seconds( - self.core.get_rtio_counter_mu() - t0)/n) + self.core.get_rtio_counter_mu() - t0) / n) @kernel def io_update(self): @@ -155,7 +177,7 @@ class UrukulTest(ExperimentCase): self.execute(UrukulExp, "switch_speed") dt = self.dataset_mgr.get("dt") print(dt) - self.assertLess(dt, 5*us) + self.assertLess(dt, 5 * us) def test_switches_readback(self): self.execute(UrukulExp, "switches_readback") @@ -175,11 +197,20 @@ 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") print(dt) - self.assertLess(dt, 5*us) + self.assertLess(dt, 5 * us) def test_io_update(self): self.execute(UrukulExp, "io_update")